diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 975b267b88..b119fe681a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -17,6 +17,7 @@ "@radix-ui/react-form": "^0.1.1", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-menubar": "^1.1.3", + "@radix-ui/react-radio-group": "^1.2.2", "@radix-ui/react-scroll-area": "^1.2.2", "@radix-ui/react-select": "^2.1.3", "@radix-ui/react-switch": "^1.1.2", diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index aa91727851..64c1129da2 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -39,4 +39,4 @@ function App() { ); } -export default App; +export default App; \ No newline at end of file diff --git a/apps/desktop/src/components/layout/ExportMenu.tsx b/apps/desktop/src/components/layout/ExportMenu.tsx index d29edab72a..e9d8cb2543 100644 --- a/apps/desktop/src/components/layout/ExportMenu.tsx +++ b/apps/desktop/src/components/layout/ExportMenu.tsx @@ -1,46 +1,55 @@ -import { useRef } from "react"; +import { useRef, useState } from "react"; import { useClickOutside } from "../../hooks/useClickOutside"; +import { Share, Copy, File, FileText } from "lucide-react"; -interface ExportMenuProps { - isOpen: boolean; - onToggle: () => void; -} - -export default function ExportMenu({ isOpen, onToggle }: ExportMenuProps) { +export default function ExportMenu() { + const [isOpen, setIsOpen] = useState(false); const exportRef = useRef(null); useClickOutside(exportRef, () => { - if (isOpen) onToggle(); + if (isOpen) setIsOpen(false); }); return (
{isOpen && ( -
+
+
diff --git a/apps/desktop/src/components/layout/NavBar.tsx b/apps/desktop/src/components/layout/NavBar.tsx index 500124065a..539d8d24fd 100644 --- a/apps/desktop/src/components/layout/NavBar.tsx +++ b/apps/desktop/src/components/layout/NavBar.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback } from "react"; +import { useCallback } from "react"; import { useNavigate, useLocation } from "react-router-dom"; import SearchModal from "../modals/search/SearchModal"; import SettingsModal from "../modals/settings/SettingsModal"; @@ -10,7 +10,6 @@ import NavigationButtons from "./NavigationButtons"; export default function NavBar() { const { isPanelOpen, setIsPanelOpen } = useUI(); - const [isExportMenuOpen, setIsExportMenuOpen] = useState(false); const navigate = useNavigate(); const location = useLocation(); @@ -29,10 +28,6 @@ export default function NavBar() { window.dispatchEvent(new Event("openSearch")); }, []); - const toggleExportMenu = useCallback(() => { - setIsExportMenuOpen((prev) => !prev); - }, []); - const togglePanel = useCallback(() => { setIsPanelOpen(!isPanelOpen); }, [setIsPanelOpen, isPanelOpen]); @@ -63,12 +58,7 @@ export default function NavBar() {
- {isNotePage && ( - - )} + {isNotePage && } {/* New Note Button */} {isNotePage ? ( diff --git a/apps/desktop/src/components/modals/search/SearchModal.tsx b/apps/desktop/src/components/modals/search/SearchModal.tsx index 30acb77138..4d1ed672c1 100644 --- a/apps/desktop/src/components/modals/search/SearchModal.tsx +++ b/apps/desktop/src/components/modals/search/SearchModal.tsx @@ -1,6 +1,6 @@ import "../../../styles/cmdk.css"; -import { useEffect, useState } from "react"; +import { useState, useEffect } from "react"; import { Command } from "cmdk"; import { useNavigate } from "react-router-dom"; import { mockNotes } from "../../../mocks/data"; @@ -61,33 +61,32 @@ const SearchModal = () => { /> )} {open && ( - - - - No notes found. - - {filteredNotes.map((note) => ( - { - navigate(`/note/${note.id}`); - setOpen(false); - }} - > - {note.title} - - ))} - - + + + + + No notes found. + + {filteredNotes.map((note) => ( + { + navigate(`/note/${note.id}`); + setOpen(false); + }} + > + {note.title} + + ))} + + + )} diff --git a/apps/desktop/src/components/modals/settings/SettingsModal.tsx b/apps/desktop/src/components/modals/settings/SettingsModal.tsx index 4d131f53d4..161c40d02e 100644 --- a/apps/desktop/src/components/modals/settings/SettingsModal.tsx +++ b/apps/desktop/src/components/modals/settings/SettingsModal.tsx @@ -1,71 +1,21 @@ -import { useState, useEffect } from "react"; +import { useEffect, useState } from "react"; import * as Dialog from "@radix-ui/react-dialog"; import * as Tabs from "@radix-ui/react-tabs"; -import { GeneralSettings } from "./tabs/GeneralSettings"; -import { FeedbackSettings } from "./tabs/FeedbackSettings"; -import { LicenseSettings } from "./tabs/LicenseSettings"; -import { CalendarSettings } from "./tabs/CalendarSettings"; -import { NotificationSettings } from "./tabs/NotificationSettings"; -import { SlackSettings } from "./tabs/SlackSettings"; -import { SettingsTabs } from "./tabs/SettingsTabs"; - -interface SettingsModalProps { - onTrigger?: () => void; - initialTab?: - | "general" - | "feedback" - | "license" - | "calendar" - | "notification" - | "slack"; - type?: "invite" | "settings" | "feedback"; - onClose?: () => void; -} - -interface LicenseInfo { - type: "Free" | "Starter Pack" | "For Life"; - price: string; - features: string[]; - duration: string; - buttonText: string; -} - -export default function SettingsModal({ - onTrigger, - type = "settings", - onClose, -}: SettingsModalProps) { +import { General } from "./tabs/General"; +import { Feedback } from "./tabs/Feedback"; +import { Billing } from "./tabs/Billing"; +import { Calendars } from "./tabs/Calendars"; +import { Notifications } from "./tabs/Notifications"; +import { Integrations } from "./tabs/Integrations"; +import { Profile } from "./tabs/Profile"; +import { SettingsTabs } from "./SettingsTabs"; + +export default function SettingsModal() { const [isOpen, setIsOpen] = useState(false); - const [fullName, setFullName] = useState(""); - const [showMeetingIndicator, setShowMeetingIndicator] = useState(true); - const [openOnLogin, setOpenOnLogin] = useState(true); - const [theme, setTheme] = useState("system"); - const [jargons, setJargons] = useState(""); - const [googleCalendar, setGoogleCalendar] = useState(false); - const [iCalCalendar, setICalCalendar] = useState(false); - const [scheduledMeetings, setScheduledMeetings] = useState(true); - const [autoDetectedMeetings, setAutoDetectedMeetings] = useState(true); - const [feedbackType, setFeedbackType] = useState< - "feedback" | "problem" | "question" - >("feedback"); - const [feedbackText, setFeedbackText] = useState(""); - - const [activeTab, setActiveTab] = useState(() => { - if (type === "feedback") return "feedback"; - if (type === "invite") return "license"; - return "general"; - }); - - useEffect(() => { - if (type === "feedback") setActiveTab("feedback"); - else if (type === "invite") setActiveTab("license"); - else setActiveTab("general"); - }, [type]); useEffect(() => { const handleSettingsTrigger = () => { setIsOpen(true); - setActiveTab("general"); }; const handleKeyboardShortcut = (e: KeyboardEvent) => { @@ -84,122 +34,44 @@ export default function SettingsModal({ }; }, []); - const licenses: LicenseInfo[] = [ - { - type: "Free", - price: "무료", - duration: "2주 무료 체험", - features: ["기본 기능 사용", "초대 시 1주일 연장 (최대 3회)"], - buttonText: "현재 플랜", - }, - { - type: "Starter Pack", - price: "$10", - duration: "1개월", - features: ["모든 기본 기능", "무제한 사용"], - buttonText: "업그레이드", - }, - { - type: "For Life", - price: "$149", - duration: "평생 사용", - features: ["모든 기능 평생 사용", "1년간 무료 업데이트"], - buttonText: "업그레이드", - }, - ]; - - const handleFeedbackSubmit = (event: React.FormEvent) => { - event.preventDefault(); - console.log("Feedback submitted:", { - type: feedbackType, - text: feedbackText, - }); - setFeedbackText(""); - }; - return ( { setIsOpen(open); - if (!open && onClose) { - onClose(); - } }} > - - -
- - {type === "feedback" - ? "피드백 보내기" - : type === "invite" - ? "초대하기" - : "설정"} - - -
- -
- - -
- - - - - - - - - - - - - - - - - - - - - - - -
-
-
+ + + +
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
-
+ diff --git a/apps/desktop/src/components/modals/settings/SettingsTabs.tsx b/apps/desktop/src/components/modals/settings/SettingsTabs.tsx new file mode 100644 index 0000000000..69995ee0c7 --- /dev/null +++ b/apps/desktop/src/components/modals/settings/SettingsTabs.tsx @@ -0,0 +1,49 @@ +import * as Tabs from "@radix-ui/react-tabs"; +import { + Settings, + MessageSquare, + CreditCard, + Calendar, + Bell, + UserCircle, + Cable, +} from "lucide-react"; + +interface TabItem { + value: string; + label: string; + icon: React.ComponentType<{ className?: string }>; +} + +export function SettingsTabs() { + const mainTabs: TabItem[] = [ + { value: "profile", label: "프로필", icon: UserCircle }, + { value: "general", label: "일반", icon: Settings }, + { value: "feedback", label: "피드백", icon: MessageSquare }, + { value: "billing", label: "결제", icon: CreditCard }, + { value: "calendar", label: "캘린더", icon: Calendar }, + { value: "notification", label: "알림", icon: Bell }, + { value: "integrations", label: "연동", icon: Cable }, + ]; + + const TabButton = ({ tab }: { tab: TabItem }) => ( + + + {tab.label} + + ); + + return ( +
+ + {mainTabs.map((tab) => ( + + ))} + +
+ ); +} diff --git a/apps/desktop/src/components/modals/settings/tabs/Billing.tsx b/apps/desktop/src/components/modals/settings/tabs/Billing.tsx new file mode 100644 index 0000000000..361c1a0064 --- /dev/null +++ b/apps/desktop/src/components/modals/settings/tabs/Billing.tsx @@ -0,0 +1,191 @@ +import { useState } from "react"; +import * as Tabs from "@radix-ui/react-tabs"; + +interface BillingPlan { + type: "Free" | "Pro" | "Business"; + price: { + monthly: number; + yearly: number; + }; + features: string[]; + isComingSoon?: boolean; + level: number; +} + +export function Billing() { + const [billingCycle, setBillingCycle] = useState<"monthly" | "yearly">( + "monthly", + ); + const [currentPlan, setCurrentPlan] = useState<"Free" | "Pro" | "Business">( + "Free", + ); + const [daysLeft, setDaysLeft] = useState(14); // 남은 무료 사용 기간 (일) + + const billingPlans: BillingPlan[] = [ + { + type: "Free", + level: 0, + price: { + monthly: 0, + yearly: 0, + }, + features: [ + "2주 무료 체험", + "다중 언어 가능", + "캘린더 연동", + "자동 노트 생성", + ], + }, + { + type: "Pro", + level: 1, + price: { + monthly: 10, + yearly: 8, + }, + features: [ + "로컬 모델로 오프라인 사용", + "STT, LLM 모델 선택 가능", + "노트 공유 링크", + "노션, 슬랙 등 앱 연동", + ], + }, + { + type: "Business", + level: 2, + price: { + monthly: 15, + yearly: 12, + }, + features: [ + "팀 워크스페이스", + "상세 접근 권한 설정", + "공유 노트 수정 권한", + "팀 단위 결제", + ], + isComingSoon: true, + }, + ]; + + const getButtonText = ( + planLevel: number, + currentLevel: number, + planType: "Free" | "Pro" | "Business", + ) => { + if (planType === "Free" && currentPlan === "Free") { + if (daysLeft > 0) { + return `${daysLeft}일 남음`; + } + return "기간 만료"; + } + if (planLevel === currentLevel) return "사용중"; + return planLevel > currentLevel ? "업그레이드" : "다운그레이드"; + }; + + const getPlanLevel = (planType: "Free" | "Pro" | "Business") => { + return billingPlans.find((plan) => plan.type === planType)?.level || 0; + }; + + return ( +
+
+

결제

+

+ 구독 및 결제 정보를 관리하세요 +

+
+ +
+ + + setBillingCycle(value as "monthly" | "yearly") + } + > + + + Monthly + + + Yearly (20% off) + + + + +
+ {billingPlans.map((plan) => ( +
+
+

{plan.type}

+
+ {plan.isComingSoon && ( +
+ Coming Soon +
+ )} + {currentPlan === plan.type && ( +
+ 현재 플랜 +
+ )} +
+ + ${plan.price[billingCycle === "monthly" ? "monthly" : "yearly"]} + + /월 +
+
    + {plan.features.map((feature, index) => ( +
  • + + + + {feature} +
  • + ))} +
+ {!plan.isComingSoon && ( + + )} +
+ ))} +
+
+ ); +} diff --git a/apps/desktop/src/components/modals/settings/tabs/CalendarSettings.tsx b/apps/desktop/src/components/modals/settings/tabs/Calendars.tsx similarity index 51% rename from apps/desktop/src/components/modals/settings/tabs/CalendarSettings.tsx rename to apps/desktop/src/components/modals/settings/tabs/Calendars.tsx index ff69887207..3200040a93 100644 --- a/apps/desktop/src/components/modals/settings/tabs/CalendarSettings.tsx +++ b/apps/desktop/src/components/modals/settings/tabs/Calendars.tsx @@ -1,24 +1,30 @@ +import { useState } from "react"; import * as Switch from "@radix-ui/react-switch"; -interface CalendarSettingsProps { - googleCalendar: boolean; - setGoogleCalendar: (value: boolean) => void; - iCalCalendar: boolean; - setICalCalendar: (value: boolean) => void; -} +export function Calendars() { + const [googleCalendar, setGoogleCalendar] = useState(false); + const [iCalCalendar, setICalCalendar] = useState(false); -export function CalendarSettings({ - googleCalendar, - setGoogleCalendar, - iCalCalendar, - setICalCalendar, -}: CalendarSettingsProps) { return (
+
+

캘린더

+

+ 캘린더 연동 설정을 관리하세요 +

+
+ +
+
- +
+ +

+ Google 캘린더와 연동하여 일정을 자동으로 가져옵니다 +

+
- +
+ +

+ iCal 형식의 캘린더를 가져와서 일정을 동기화합니다 +

+
("feedback"); + const [feedbackText, setFeedbackText] = useState(""); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + // Handle feedback submission + }; + + return ( +
+
+

피드백

+

+ 서비스 개선을 위한 의견을 보내주세요 +

+
+ +
+ + +
+ + + setFeedbackType(value) + } + > +
+ + + + +
+
+ + + + +
+
+ + + + +
+
+
+ +
+ +