From 04489567e068fcb9767006d13c2c025a3fbcf80e Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 8 Jul 2026 20:38:53 +0900 Subject: [PATCH 01/17] =?UTF-8?q?feat(web):=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EA=B3=B5=EC=9A=A9=20=EB=84=A4?= =?UTF-8?q?=EB=B9=84=EC=99=80=20=EC=84=9C=EB=B8=8C=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EB=BC=88=EB=8C=80=EB=A5=BC=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?=ED=95=9C=EB=8B=A4=20(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Figma 디자인 기반으로 개인정보/약관/회원탈퇴 공용 네비를 구현했습니다 - 각 서브페이지에 제목+구분선 뼈대를 연결했습니다 - 회원 탈퇴용 신규 라우트(/settings/withdrawal)를 추가했습니다 --- .../_containers/SettingsNavContainer.tsx | 49 +++++++++++++++++++ .../app/[locale]/(main)/settings/layout.tsx | 11 +++-- .../app/[locale]/(main)/settings/page.tsx | 7 ++- .../[locale]/(main)/settings/policy/page.tsx | 7 ++- .../settings/withdrawal/_components/.gitkeep | 0 .../settings/withdrawal/_containers/.gitkeep | 0 .../settings/withdrawal/_queries/.gitkeep | 0 .../(main)/settings/withdrawal/page.tsx | 8 +++ apps/timo-web/constants/routes.ts | 2 + 9 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx create mode 100644 apps/timo-web/app/[locale]/(main)/settings/withdrawal/_components/.gitkeep create mode 100644 apps/timo-web/app/[locale]/(main)/settings/withdrawal/_containers/.gitkeep create mode 100644 apps/timo-web/app/[locale]/(main)/settings/withdrawal/_queries/.gitkeep create mode 100644 apps/timo-web/app/[locale]/(main)/settings/withdrawal/page.tsx diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx new file mode 100644 index 00000000..bd09c445 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { cn } from "@repo/timo-design-system/utils"; + +import { ROUTES } from "@/constants/routes"; +import { Link, usePathname } from "@/i18n/navigation"; + +const SETTINGS_NAV_ITEMS = [ + { label: "개인 정보", href: ROUTES.SETTINGS }, + { label: "약관", href: ROUTES.SETTINGS_POLICY }, + { label: "회원 탈퇴", href: ROUTES.SETTINGS_WITHDRAWAL }, +]; + +export const SettingsNavContainer = () => { + const pathname = usePathname(); + + return ( + + ); +}; diff --git a/apps/timo-web/app/[locale]/(main)/settings/layout.tsx b/apps/timo-web/app/[locale]/(main)/settings/layout.tsx index be819660..48de2da4 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/layout.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/layout.tsx @@ -1,4 +1,5 @@ import { SettingsHeaderContainer } from "@/app/[locale]/(main)/settings/_containers/SettingsHeaderContainer"; +import { SettingsNavContainer } from "@/app/[locale]/(main)/settings/_containers/SettingsNavContainer"; export default function SettingsLayout({ children, @@ -6,9 +7,13 @@ export default function SettingsLayout({ children: React.ReactNode; }) { return ( - <> +
- {children} - + +
+ +
{children}
+
+
); } diff --git a/apps/timo-web/app/[locale]/(main)/settings/page.tsx b/apps/timo-web/app/[locale]/(main)/settings/page.tsx index fe25cb52..a1d884bc 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/page.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/page.tsx @@ -1,3 +1,8 @@ export default function SettingsPage() { - return <>; + return ( +
+

개인 정보

+
+
+ ); } diff --git a/apps/timo-web/app/[locale]/(main)/settings/policy/page.tsx b/apps/timo-web/app/[locale]/(main)/settings/policy/page.tsx index b621d253..2645d493 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/policy/page.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/policy/page.tsx @@ -1,3 +1,8 @@ export default function SettingsPolicyPage() { - return <>; + return ( +
+

약관

+
+
+ ); } diff --git a/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_components/.gitkeep b/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_components/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_containers/.gitkeep b/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_containers/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_queries/.gitkeep b/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_queries/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/apps/timo-web/app/[locale]/(main)/settings/withdrawal/page.tsx b/apps/timo-web/app/[locale]/(main)/settings/withdrawal/page.tsx new file mode 100644 index 00000000..ed0c51b6 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/settings/withdrawal/page.tsx @@ -0,0 +1,8 @@ +export default function SettingsWithdrawalPage() { + return ( +
+

탈퇴

+
+
+ ); +} diff --git a/apps/timo-web/constants/routes.ts b/apps/timo-web/constants/routes.ts index 2f9e6a91..9b3301f2 100644 --- a/apps/timo-web/constants/routes.ts +++ b/apps/timo-web/constants/routes.ts @@ -4,4 +4,6 @@ export const ROUTES = { FOCUS: "/focus", STATISTICS: "/statistics", SETTINGS: "/settings", + SETTINGS_POLICY: "/settings/policy", + SETTINGS_WITHDRAWAL: "/settings/withdrawal", } as const; From 2cfa22d902c1ac40714424b4c70d177c376135cf Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 8 Jul 2026 23:09:41 +0900 Subject: [PATCH 02/17] =?UTF-8?q?feat(web):=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EA=B0=9C=EC=9D=B8=EC=A0=95=EB=B3=B4=20=ED=8F=BC=EC=9D=84=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=ED=95=9C=EB=8B=A4=20(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 프로필/캘린더 연동/언어/태그 설정 폼을 구현했습니다 - 기본 제공 태그(과제, 업무, 운동, 일상)는 삭제 버튼을 숨기고, 사용자가 추가한 태그만 삭제 가능하도록 했습니다 --- .../_components/SettingsProfileForm.tsx | 163 ++++++++++++++++++ .../_containers/SettingsProfileContainer.tsx | 66 +++++++ .../(main)/settings/_mocks/profile-mock.ts | 9 + .../(main)/settings/_types/profile-type.ts | 9 + .../app/[locale]/(main)/settings/page.tsx | 9 +- .../public/images/google-calendar.png | Bin 0 -> 37072 bytes apps/timo-web/public/images/google-logo.png | Bin 0 -> 27437 bytes 7 files changed, 250 insertions(+), 6 deletions(-) create mode 100644 apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx create mode 100644 apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx create mode 100644 apps/timo-web/app/[locale]/(main)/settings/_mocks/profile-mock.ts create mode 100644 apps/timo-web/app/[locale]/(main)/settings/_types/profile-type.ts create mode 100644 apps/timo-web/public/images/google-calendar.png create mode 100644 apps/timo-web/public/images/google-logo.png diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx new file mode 100644 index 00000000..ed8b9d52 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx @@ -0,0 +1,163 @@ +import { DeleteIcon, PlusGrayIcon } from "@repo/timo-design-system/icons"; +import { CreateButton, TogglePanel } from "@repo/timo-design-system/ui"; +import Image from "next/image"; + +import type { SettingsLanguage } from "@/app/[locale]/(main)/settings/_types/profile-type"; + +const DEFAULT_SETTINGS_TAGS = ["과제", "업무", "운동", "일상"]; + +export interface SettingsProfileFormProps { + name: string; + googleEmail: string; + isCalendarConnected: boolean; + language: SettingsLanguage; + tags: string[]; + onConnectCalendar: () => void; + onChangeLanguage: (language: SettingsLanguage) => void; + onAddTag: () => void; + onRemoveTag: (tag: string) => void; + onLogout: () => void; + onSave: () => void; +} + +export const SettingsProfileForm = ({ + name, + googleEmail, + isCalendarConnected, + language, + tags, + onConnectCalendar, + onChangeLanguage, + onAddTag, + onRemoveTag, + onLogout, + onSave, +}: SettingsProfileFormProps) => { + return ( +
+
+

개인 정보

+
+ +
+

프로필

+
+
+
+

{name}

+
+ Google + + {googleEmail} + +
+
+
+
+ +
+ +
+

+ 구글 캘린더 연동 +

+
+
+ + + Google Calendar + +
+ + +
+
+ +
+ +
+

언어

+ onChangeLanguage(value as SettingsLanguage)} + options={[ + { value: "ko", label: "한국어" }, + { value: "en", label: "English" }, + ]} + /> +
+ +
+ +
+

태그

+
+ {tags.map((tag) => { + const isDefaultTag = DEFAULT_SETTINGS_TAGS.includes(tag); + + return ( +
+ + {tag} + + {!isDefaultTag && ( + + )} +
+ ); + })} + + +
+
+ +
+ + +
+ + +
+ ); +}; diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx new file mode 100644 index 00000000..a9d174ab --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx @@ -0,0 +1,66 @@ +"use client"; + +import { useState } from "react"; + +import type { + SettingsLanguage, + SettingsProfile, +} from "@/app/[locale]/(main)/settings/_types/profile-type"; + +import { SettingsProfileForm } from "@/app/[locale]/(main)/settings/_components/SettingsProfileForm"; +import { settingsProfileMock } from "@/app/[locale]/(main)/settings/_mocks/profile-mock"; + +export const SettingsProfileContainer = () => { + const [profile, setProfile] = useState(settingsProfileMock); + + const handleConnectCalendar = () => { + // TODO: API + setProfile((prev) => ({ + ...prev, + isCalendarConnected: !prev.isCalendarConnected, + })); + }; + + const handleChangeLanguage = (language: SettingsLanguage) => { + // TODO: next-intl 로케일 전환 연동 + setProfile((prev) => ({ ...prev, language })); + }; + + const handleAddTag = () => { + // TODO: API + }; + + const handleRemoveTag = (tag: string) => { + // TODO: API + setProfile((prev) => ({ + ...prev, + tags: prev.tags.filter((existingTag) => existingTag !== tag), + })); + }; + + const handleLogout = () => { + // TODO: API + }; + + const handleSave = () => { + // TODO: API + }; + + return ( +
+ +
+ ); +}; diff --git a/apps/timo-web/app/[locale]/(main)/settings/_mocks/profile-mock.ts b/apps/timo-web/app/[locale]/(main)/settings/_mocks/profile-mock.ts new file mode 100644 index 00000000..be4fca67 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/settings/_mocks/profile-mock.ts @@ -0,0 +1,9 @@ +import type { SettingsProfile } from "@/app/[locale]/(main)/settings/_types/profile-type"; + +export const settingsProfileMock: SettingsProfile = { + name: "김도연", + googleEmail: "timogoogle@gmail.com", + isCalendarConnected: false, + language: "ko", + tags: ["과제", "업무", "운동", "일상", "안녕"], +}; diff --git a/apps/timo-web/app/[locale]/(main)/settings/_types/profile-type.ts b/apps/timo-web/app/[locale]/(main)/settings/_types/profile-type.ts new file mode 100644 index 00000000..b15dd290 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/settings/_types/profile-type.ts @@ -0,0 +1,9 @@ +export type SettingsLanguage = "ko" | "en"; + +export interface SettingsProfile { + name: string; + googleEmail: string; + isCalendarConnected: boolean; + language: SettingsLanguage; + tags: string[]; +} diff --git a/apps/timo-web/app/[locale]/(main)/settings/page.tsx b/apps/timo-web/app/[locale]/(main)/settings/page.tsx index a1d884bc..f0e8940e 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/page.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/page.tsx @@ -1,8 +1,5 @@ +import { SettingsProfileContainer } from "@/app/[locale]/(main)/settings/_containers/SettingsProfileContainer"; + export default function SettingsPage() { - return ( -
-

개인 정보

-
-
- ); + return ; } diff --git a/apps/timo-web/public/images/google-calendar.png b/apps/timo-web/public/images/google-calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..76c7c56433defb59e08d22e468c14e13dc50bbaf GIT binary patch literal 37072 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 literal 0 HcmV?d00001 diff --git a/apps/timo-web/public/images/google-logo.png b/apps/timo-web/public/images/google-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..831de1aa1a4fabd2a5f3d9691ae35919913e0d5c GIT binary patch literal 27437 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%0Dl- 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 literal 0 HcmV?d00001 From d22ee51435095db1b91684ac5b24a32f8e7b6f1c Mon Sep 17 00:00:00 2001 From: jjangminii Date: Wed, 8 Jul 2026 23:11:16 +0900 Subject: [PATCH 03/17] =?UTF-8?q?fix(web):=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=82=AC=EC=9D=B4=EB=93=9C=EB=B0=94=EA=B0=80=20=EC=BD=98?= =?UTF-8?q?=ED=85=90=EC=B8=A0=EC=99=80=20=ED=95=A8=EA=BB=98=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A1=A4=EB=90=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EA=B3=A0=EC=A0=95=ED=95=9C=EB=8B=A4=20(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 스크롤 영역을 main에만 적용해 사이드바가 화면에 고정되도록 수정했습니다 --- apps/timo-web/app/[locale]/(main)/settings/layout.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/settings/layout.tsx b/apps/timo-web/app/[locale]/(main)/settings/layout.tsx index 48de2da4..e28888f0 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/layout.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/layout.tsx @@ -10,9 +10,9 @@ export default function SettingsLayout({
-
+
-
{children}
+
{children}
); From 6dc08a0ffaa807fbeb9d2c2406cf2d5d730bc5be Mon Sep 17 00:00:00 2001 From: jjangminii Date: Thu, 9 Jul 2026 00:06:11 +0900 Subject: [PATCH 04/17] =?UTF-8?q?feat(ui):=20TagButton=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=9C=EB=8B=A4=20(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 태그 칩, 태그 추가, 연동 버튼, 로그아웃 버튼 등에서 공통으로 쓰이는 pill 스타일 버튼 컴포넌트를 추가했습니다 - 삭제 아이콘이 있는 태그는 버튼 중첩을 피하기 위해 내부에만 실제 버튼을 렌더링하도록 했습니다 --- .../button/tag-button/TagButton.stories.tsx | 71 +++++++++++++++++++ .../button/tag-button/TagButton.tsx | 64 +++++++++++++++++ .../src/components/index.ts | 2 + 3 files changed, 137 insertions(+) create mode 100644 packages/timo-design-system/src/components/button/tag-button/TagButton.stories.tsx create mode 100644 packages/timo-design-system/src/components/button/tag-button/TagButton.tsx diff --git a/packages/timo-design-system/src/components/button/tag-button/TagButton.stories.tsx b/packages/timo-design-system/src/components/button/tag-button/TagButton.stories.tsx new file mode 100644 index 00000000..30ae9ef7 --- /dev/null +++ b/packages/timo-design-system/src/components/button/tag-button/TagButton.stories.tsx @@ -0,0 +1,71 @@ +import { TagButton } from "./TagButton"; +import { PlusGrayIcon } from "../../../icons"; + +import type { Meta, StoryObj } from "@storybook/react"; + +const meta = { + title: "Components/Button/TagButton", + component: TagButton, + parameters: { + layout: "centered", + }, + argTypes: { + variant: { + control: "select", + options: ["gray", "blue"], + }, + children: { + control: "text", + }, + }, + args: { + variant: "gray", + children: "과제", + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const Blue: Story = { + args: { variant: "blue", children: "연동하기", onClick: () => {} }, +}; + +export const Removable: Story = { + args: { + children: "안녕", + onRemove: () => {}, + removeLabel: "안녕 태그 삭제", + }, +}; + +export const WithAddIcon: Story = { + args: { + children: "태그 추가", + icon: , + onClick: () => {}, + }, +}; + +export const AllStates: Story = { + parameters: { controls: { disable: true } }, + render: () => ( +
+ 과제 + {}} removeLabel="안녕 태그 삭제"> + 안녕 + + } + onClick={() => {}} + > + 태그 추가 + + {}}> + 연동하기 + +
+ ), +}; diff --git a/packages/timo-design-system/src/components/button/tag-button/TagButton.tsx b/packages/timo-design-system/src/components/button/tag-button/TagButton.tsx new file mode 100644 index 00000000..c9c0d8c9 --- /dev/null +++ b/packages/timo-design-system/src/components/button/tag-button/TagButton.tsx @@ -0,0 +1,64 @@ +import { DeleteIcon } from "../../../icons"; +import { cn } from "../../../lib"; + +import type { ReactNode } from "react"; + +export type TagButtonVariant = "gray" | "blue"; + +const TAG_BUTTON_VARIANT: Record = { + gray: "bg-timo-gray-300 text-timo-gray-900", + blue: "bg-timo-blue-300 text-white", +}; + +export interface TagButtonProps { + children: ReactNode; + variant?: TagButtonVariant; + icon?: ReactNode; + onClick?: () => void; + onRemove?: () => void; + removeLabel?: string; + className?: string; +} + +export const TagButton = ({ + children, + variant = "gray", + icon, + onClick, + onRemove, + removeLabel, + className, +}: TagButtonProps) => { + const chipClassName = cn( + "typo-body-m-12 flex h-7.5 shrink-0 items-center justify-center gap-1.5 rounded-[4px] px-3", + TAG_BUTTON_VARIANT[variant], + className, + ); + + if (onRemove) { + return ( +
+ {children} + +
+ ); + } + + if (!onClick) { + return ( +
+ {children} + {icon} +
+ ); + } + + return ( + + ); +}; diff --git a/packages/timo-design-system/src/components/index.ts b/packages/timo-design-system/src/components/index.ts index e3169a5d..b036951b 100644 --- a/packages/timo-design-system/src/components/index.ts +++ b/packages/timo-design-system/src/components/index.ts @@ -36,3 +36,5 @@ export { TodayButton } from "./button/today-button/TodayButton"; export { WeeklyButton } from "./button/weekly-button/WeeklyButton"; export { TimeSelector } from "./time/time-selector/TimeSelector"; export { RepeatSelector } from "./repeat/repeat-selector/RepeatSelector"; +export { TagButton } from "./button/tag-button/TagButton"; +export type { TagButtonVariant } from "./button/tag-button/TagButton"; From f479b6089904fbcf96f3213487c3d4c188c8873d Mon Sep 17 00:00:00 2001 From: jjangminii Date: Thu, 9 Jul 2026 00:06:24 +0900 Subject: [PATCH 05/17] =?UTF-8?q?feat(web):=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EA=B0=9C=EC=9D=B8=EC=A0=95=EB=B3=B4=20=ED=8F=BC=EC=97=90=20?= =?UTF-8?q?=EC=BA=98=EB=A6=B0=EB=8D=94=20=EC=97=B0=EB=8F=99=C2=B7=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80=C2=B7=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=EC=9D=84=20=EA=B5=AC=ED=98=84=ED=95=9C?= =?UTF-8?q?=EB=8B=A4=20(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 개별 버튼들을 공통 TagButton 컴포넌트로 교체했습니다 - 구글 캘린더 연동 해제 시 확인 다이얼로그를, 태그 추가 시 입력 다이얼로그를 임시로 노출하도록 구현했습니다 - 변경사항 저장 버튼을 데이터 변경 여부에 따라 활성화하고, 저장 실패 시 안내 메시지를 노출하도록 구현했습니다 --- .../_components/SettingsProfileForm.tsx | 64 ++++++++----------- .../_containers/SettingsProfileContainer.tsx | 50 ++++++++++++--- 2 files changed, 67 insertions(+), 47 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx index ed8b9d52..aee9ba9b 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx @@ -1,5 +1,9 @@ -import { DeleteIcon, PlusGrayIcon } from "@repo/timo-design-system/icons"; -import { CreateButton, TogglePanel } from "@repo/timo-design-system/ui"; +import { PlusIcon } from "@repo/timo-design-system/icons"; +import { + CreateButton, + TagButton, + TogglePanel, +} from "@repo/timo-design-system/ui"; import Image from "next/image"; import type { SettingsLanguage } from "@/app/[locale]/(main)/settings/_types/profile-type"; @@ -12,6 +16,7 @@ export interface SettingsProfileFormProps { isCalendarConnected: boolean; language: SettingsLanguage; tags: string[]; + isSaveDisabled: boolean; onConnectCalendar: () => void; onChangeLanguage: (language: SettingsLanguage) => void; onAddTag: () => void; @@ -26,6 +31,7 @@ export const SettingsProfileForm = ({ isCalendarConnected, language, tags, + isSaveDisabled, onConnectCalendar, onChangeLanguage, onAddTag, @@ -79,13 +85,9 @@ export const SettingsProfileForm = ({
- + @@ -113,51 +115,37 @@ export const SettingsProfileForm = ({ const isDefaultTag = DEFAULT_SETTINGS_TAGS.includes(tag); return ( -
onRemoveTag(tag)} + removeLabel={`${tag} 태그 삭제`} > - - {tag} - - {!isDefaultTag && ( - - )} -
+ {tag} + ); })} - + 태그 추가 +
- + - + ); }; diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx index a9d174ab..2e602b5e 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx @@ -12,38 +12,69 @@ import { settingsProfileMock } from "@/app/[locale]/(main)/settings/_mocks/profi export const SettingsProfileContainer = () => { const [profile, setProfile] = useState(settingsProfileMock); + const [isDirty, setIsDirty] = useState(false); + const [isSaving, setIsSaving] = useState(false); const handleConnectCalendar = () => { - // TODO: API - setProfile((prev) => ({ - ...prev, - isCalendarConnected: !prev.isCalendarConnected, - })); + if (profile.isCalendarConnected) { + // TODO: 실제 확인 모달로 교체 + const confirmed = window.confirm("구글 캘린더 연동을 해제하시겠습니까?"); + if (!confirmed) return; + + // TODO: API - 연동 토큰 파기 + console.log("구글 캘린더 연동 토큰을 파기합니다."); + setProfile((prev) => ({ ...prev, isCalendarConnected: false })); + return; + } + + // TODO: Google 계정 인증 및 캘린더 접근 권한 동의 팝업 호출 + console.log("Google Calendar 연동 인증 팝업을 호출합니다."); + setProfile((prev) => ({ ...prev, isCalendarConnected: true })); }; const handleChangeLanguage = (language: SettingsLanguage) => { // TODO: next-intl 로케일 전환 연동 setProfile((prev) => ({ ...prev, language })); + setIsDirty(true); }; const handleAddTag = () => { - // TODO: API + // TODO: 실제 태그 추가 모달로 교체 + const newTag = window.prompt("추가할 태그를 입력해주세요")?.trim(); + if (!newTag || profile.tags.includes(newTag)) return; + + setProfile((prev) => ({ ...prev, tags: [...prev.tags, newTag] })); + setIsDirty(true); }; const handleRemoveTag = (tag: string) => { - // TODO: API setProfile((prev) => ({ ...prev, tags: prev.tags.filter((existingTag) => existingTag !== tag), })); + setIsDirty(true); }; const handleLogout = () => { // TODO: API + console.log("로그아웃 API 호출"); }; - const handleSave = () => { - // TODO: API + const handleSave = async () => { + if (!isDirty || isSaving) return; + + setIsSaving(true); + + try { + // TODO: API 연동 + await new Promise((resolve) => setTimeout(resolve, 1000)); + setIsDirty(false); + } catch { + // TODO: 실제 토스트 컴포넌트로 교체 + window.alert("저장에 실패했습니다. 잠시 후 다시 시도해 주세요."); + } finally { + setIsSaving(false); + } }; return ( @@ -54,6 +85,7 @@ export const SettingsProfileContainer = () => { isCalendarConnected={profile.isCalendarConnected} language={profile.language} tags={profile.tags} + isSaveDisabled={!isDirty || isSaving} onConnectCalendar={handleConnectCalendar} onChangeLanguage={handleChangeLanguage} onAddTag={handleAddTag} From 0637127bec16f8ff950f3fa99d5617b582e6008b Mon Sep 17 00:00:00 2001 From: jjangminii Date: Thu, 9 Jul 2026 01:20:40 +0900 Subject: [PATCH 06/17] =?UTF-8?q?feat(web):=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EA=B0=9C=EC=9D=B8=EC=A0=95=EB=B3=B4=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=EC=97=90=20=EB=8B=A4=EA=B5=AD=EC=96=B4=20=EC=A7=80=EC=9B=90?= =?UTF-8?q?=EC=9D=84=20=EC=A0=81=EC=9A=A9=ED=95=98=EA=B3=A0=20=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EC=95=84=EC=9B=83=EC=9D=84=20=EB=8B=A4=EB=93=AC?= =?UTF-8?q?=EB=8A=94=EB=8B=A4=20(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사이드바 하위 메뉴와 개인정보 폼의 h2 라벨·버튼 텍스트를 Settings 네임스페이스 번역 키로 교체했습니다 (사용자 추가 태그는 번역 대상에서 제외) - 기본 태그를 캐노니컬 키로 저장하고 화면에서만 번역하도록 해서, 로케일과 무관하게 기본/커스텀 태그를 정확히 구분하도록 했습니다 - 언어 토글이 next-intl 로케일 라우팅과 실제로 연동되어 URL이 전환되도록 했습니다 - 구글 캘린더 연동 박스와 태그 목록의 불필요한 줄바꿈·여백 문제를 수정했습니다 --- .../_components/SettingsProfileForm.tsx | 70 +++++++++++-------- .../_containers/SettingsNavContainer.tsx | 19 ++--- .../_containers/SettingsProfileContainer.tsx | 54 ++++++++++++-- .../(main)/settings/_mocks/profile-mock.ts | 3 +- .../(main)/settings/_types/profile-type.ts | 29 +++++++- apps/timo-web/messages/en.json | 22 ++++++ apps/timo-web/messages/ko.json | 22 ++++++ 7 files changed, 172 insertions(+), 47 deletions(-) diff --git a/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx b/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx index aee9ba9b..3013af5d 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsx @@ -6,17 +6,20 @@ import { } from "@repo/timo-design-system/ui"; import Image from "next/image"; -import type { SettingsLanguage } from "@/app/[locale]/(main)/settings/_types/profile-type"; - -const DEFAULT_SETTINGS_TAGS = ["과제", "업무", "운동", "일상"]; +import type { + SettingsLanguage, + SettingsProfileLabels, + SettingsTagItem, +} from "@/app/[locale]/(main)/settings/_types/profile-type"; export interface SettingsProfileFormProps { name: string; googleEmail: string; isCalendarConnected: boolean; language: SettingsLanguage; - tags: string[]; + tags: SettingsTagItem[]; isSaveDisabled: boolean; + labels: SettingsProfileLabels; onConnectCalendar: () => void; onChangeLanguage: (language: SettingsLanguage) => void; onAddTag: () => void; @@ -32,6 +35,7 @@ export const SettingsProfileForm = ({ language, tags, isSaveDisabled, + labels, onConnectCalendar, onChangeLanguage, onAddTag, @@ -40,13 +44,17 @@ export const SettingsProfileForm = ({ onSave, }: SettingsProfileFormProps) => { return ( -
+
-

개인 정보

+

+ {labels.title} +


-

프로필

+

+ {labels.profileSection} +

@@ -70,9 +78,9 @@ export const SettingsProfileForm = ({

- 구글 캘린더 연동 + {labels.calendarSection}

-
+
- {isCalendarConnected ? "연동 해제" : "연동하기"} + {isCalendarConnected ? labels.disconnect : labels.connect}
@@ -94,42 +102,42 @@ export const SettingsProfileForm = ({
-

언어

+

+ {labels.languageSection} +

onChangeLanguage(value as SettingsLanguage)} options={[ - { value: "ko", label: "한국어" }, - { value: "en", label: "English" }, + { value: "ko", label: labels.languageKorean }, + { value: "en", label: labels.languageEnglish }, ]} />

-
-

태그

+
+

+ {labels.tagsSection} +

- {tags.map((tag) => { - const isDefaultTag = DEFAULT_SETTINGS_TAGS.includes(tag); - - return ( - onRemoveTag(tag)} - removeLabel={`${tag} 태그 삭제`} - > - {tag} - - ); - })} + {tags.map((tag) => ( + onRemoveTag(tag.id)} + removeLabel={labels.removeTag(tag.label)} + > + {tag.label} + + ))} } onClick={onAddTag} > - 태그 추가 + {labels.addTag}
@@ -137,12 +145,12 @@ export const SettingsProfileForm = ({
- 로그아웃 + {labels.logout}
diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx index bd09c445..7b63d7c4 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsx @@ -1,30 +1,33 @@ "use client"; import { cn } from "@repo/timo-design-system/utils"; +import { useTranslations } from "next-intl"; import { ROUTES } from "@/constants/routes"; import { Link, usePathname } from "@/i18n/navigation"; -const SETTINGS_NAV_ITEMS = [ - { label: "개인 정보", href: ROUTES.SETTINGS }, - { label: "약관", href: ROUTES.SETTINGS_POLICY }, - { label: "회원 탈퇴", href: ROUTES.SETTINGS_WITHDRAWAL }, -]; - export const SettingsNavContainer = () => { + const t = useTranslations("Navigation"); + const tSettings = useTranslations("Settings"); const pathname = usePathname(); + const settingsNavItems = [ + { label: tSettings("nav.account"), href: ROUTES.SETTINGS }, + { label: tSettings("nav.policy"), href: ROUTES.SETTINGS_POLICY }, + { label: tSettings("nav.withdrawal"), href: ROUTES.SETTINGS_WITHDRAWAL }, + ]; + return (
- + {isCalendarConnected ? labels.disconnect : labels.connect} - +
@@ -124,29 +125,29 @@ export const SettingsProfileForm = ({
{tags.map((tag) => ( - onRemoveTag(tag.id)} removeLabel={labels.removeTag(tag.label)} > {tag.label} - + ))} - } onClick={onAddTag} > {labels.addTag} - +

- + {labels.logout} - +
Date: Fri, 10 Jul 2026 15:44:15 +0900 Subject: [PATCH 16/17] =?UTF-8?q?fix(web):=20=ED=83=88=ED=87=B4=ED=95=98?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=EC=9D=B4=20=EB=B6=80=EB=AA=A8=20?= =?UTF-8?q?=EB=84=88=EB=B9=84=EB=A1=9C=20=EB=8A=98=EC=96=B4=EB=82=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EA=B3=A0=20hug=20=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95=ED=95=9C=EB=8B=A4=20(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - flex-col 부모의 기본 stretch로 버튼이 전체 너비를 차지해 Figma의 Hug 크기(111x44)와 어긋나 self-start를 추가했습니다 --- .../settings/withdrawal/_components/SettingsWithdrawalView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_components/SettingsWithdrawalView.tsx b/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_components/SettingsWithdrawalView.tsx index f03b2ddb..10773d1e 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_components/SettingsWithdrawalView.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/withdrawal/_components/SettingsWithdrawalView.tsx @@ -45,7 +45,7 @@ export const SettingsWithdrawalView = ({ From 7c202f7aaea0f4faa7e583e13ca46340359cb3c8 Mon Sep 17 00:00:00 2001 From: jjangminii Date: Sat, 11 Jul 2026 18:37:58 +0900 Subject: [PATCH 17/17] =?UTF-8?q?refactor(web):=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EC=BB=A8=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=84=88=20=EB=A1=9C=EC=A7=81=EC=9D=84=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=ED=9B=85=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=ED=95=9C=EB=8B=A4=20(#122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 다국어 라벨 매핑을 useSettingsProfileLabels 훅으로 분리했습니다 - 프로필 상태·핸들러를 useSettingsProfile 훅으로 분리하고 profileState/profileActions로 그룹핑해 반환하도록 했습니다 - 컨테이너는 두 훅을 호출해 결과를 그대로 폼에 전달하도록 단순화했습니다 --- .../_containers/SettingsProfileContainer.tsx | 151 ++---------------- .../settings/_hooks/useSettingsProfile.ts | 129 +++++++++++++++ .../_hooks/useSettingsProfileLabels.ts | 25 +++ 3 files changed, 170 insertions(+), 135 deletions(-) create mode 100644 apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfile.ts create mode 100644 apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfileLabels.ts diff --git a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx index f02a6d2c..4fba9d48 100644 --- a/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx +++ b/apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx @@ -1,148 +1,29 @@ "use client"; -import { useLocale, useTranslations } from "next-intl"; -import { useState } from "react"; -import { useForm } from "react-hook-form"; - -import type { - SettingsDefaultTagKey, - SettingsLanguage, - SettingsProfile, - SettingsProfileFormValues, - SettingsProfileLabels, -} from "@/app/[locale]/(main)/settings/_types/profile-type"; - import { SettingsProfileForm } from "@/app/[locale]/(main)/settings/_components/SettingsProfileForm"; -import { settingsProfileMock } from "@/app/[locale]/(main)/settings/_mocks/profile-mock"; -import { usePathname, useRouter } from "@/i18n/navigation"; - -const DEFAULT_TAG_KEYS: SettingsDefaultTagKey[] = [ - "assignment", - "work", - "exercise", - "dailyLife", -]; - -const isDefaultTagKey = (tag: string): tag is SettingsDefaultTagKey => - DEFAULT_TAG_KEYS.includes(tag as SettingsDefaultTagKey); +import { useSettingsProfile } from "@/app/[locale]/(main)/settings/_hooks/useSettingsProfile"; +import { useSettingsProfileLabels } from "@/app/[locale]/(main)/settings/_hooks/useSettingsProfileLabels"; export const SettingsProfileContainer = () => { - const tCommon = useTranslations("Common"); - const tSettings = useTranslations("Settings"); - const locale = useLocale(); - const router = useRouter(); - const pathname = usePathname(); - - const [profile, setProfile] = useState(settingsProfileMock); - - const { watch, setValue, handleSubmit, reset, formState } = - useForm({ - defaultValues: { language: locale, tags: profile.tags }, - }); - const language = watch("language"); - const tags = watch("tags"); - - const tagItems = tags.map((tag) => { - const isDefault = isDefaultTagKey(tag); - - return { - id: tag, - label: isDefault ? tCommon(`tag.${tag}`) : tag, - isDefault, - }; - }); - - const labels: SettingsProfileLabels = { - title: tSettings("profile.title"), - profileSection: tSettings("profile.profileSection"), - calendarSection: tSettings("profile.calendarSection"), - connect: tSettings("profile.connect"), - disconnect: tSettings("profile.disconnect"), - languageSection: tSettings("profile.languageSection"), - languageKorean: tSettings("profile.languageKorean"), - languageEnglish: tSettings("profile.languageEnglish"), - tagsSection: tSettings("profile.tagsSection"), - addTag: tSettings("profile.addTag"), - logout: tSettings("profile.logout"), - save: tSettings("profile.save"), - removeTag: (tag: string) => tSettings("profile.removeTag", { tag }), - }; - - const handleConnectCalendar = () => { - if (profile.isCalendarConnected) { - // TODO: 실제 확인 모달로 교체 - const confirmed = window.confirm("구글 캘린더 연동을 해제하시겠습니까?"); - if (!confirmed) return; - - // TODO: API - 연동 토큰 파기 - console.log("구글 캘린더 연동 토큰을 파기합니다."); - setProfile((prev) => ({ ...prev, isCalendarConnected: false })); - return; - } - - // TODO: Google 계정 인증 및 캘린더 접근 권한 동의 팝업 호출 - console.log("Google Calendar 연동 인증 팝업을 호출합니다."); - setProfile((prev) => ({ ...prev, isCalendarConnected: true })); - }; - - const handleChangeLanguage = (nextLanguage: SettingsLanguage) => { - setValue("language", nextLanguage, { shouldDirty: true }); - }; - - const handleAddTag = () => { - // TODO: 실제 태그 추가 모달로 교체 - const newTag = window.prompt("추가할 태그를 입력해주세요")?.trim(); - if (!newTag || tags.includes(newTag)) return; - - setValue("tags", [...tags, newTag], { shouldDirty: true }); - }; - - const handleRemoveTag = (tag: string) => { - setValue( - "tags", - tags.filter((existingTag) => existingTag !== tag), - { shouldDirty: true }, - ); - }; - - const handleLogout = () => { - // TODO: API - 로그아웃 처리 및 브라우저 저장 데이터 파기 - console.log("로그아웃 API 호출"); - // TODO: 로그인 페이지 라우트 추가 후 이동 연결 (뒤로가기로 재접근 차단 포함) - console.log("[Login] 페이지로 이동합니다."); - }; - - const handleSave = handleSubmit(async (values) => { - try { - // TODO: API 연동 - await new Promise((resolve) => setTimeout(resolve, 1000)); - reset(values); - - if (values.language !== locale) { - router.replace(pathname, { locale: values.language }); - } - } catch { - // TODO: 실제 토스트 컴포넌트로 교체 - window.alert("저장에 실패했습니다. 잠시 후 다시 시도해 주세요."); - } - }); + const { profileState, profileActions } = useSettingsProfile(); + const labels = useSettingsProfileLabels(); return (
); diff --git a/apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfile.ts b/apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfile.ts new file mode 100644 index 00000000..5abf5546 --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfile.ts @@ -0,0 +1,129 @@ +"use client"; + +import { useLocale, useTranslations } from "next-intl"; +import { useState } from "react"; +import { useForm } from "react-hook-form"; + +import type { + SettingsDefaultTagKey, + SettingsLanguage, + SettingsProfile, + SettingsProfileFormValues, +} from "@/app/[locale]/(main)/settings/_types/profile-type"; + +import { settingsProfileMock } from "@/app/[locale]/(main)/settings/_mocks/profile-mock"; +import { usePathname, useRouter } from "@/i18n/navigation"; + +const DEFAULT_TAG_KEYS: SettingsDefaultTagKey[] = [ + "assignment", + "work", + "exercise", + "dailyLife", +]; + +const isDefaultTagKey = (tag: string): tag is SettingsDefaultTagKey => + DEFAULT_TAG_KEYS.includes(tag as SettingsDefaultTagKey); + +export const useSettingsProfile = () => { + const tCommon = useTranslations("Common"); + const locale = useLocale(); + const router = useRouter(); + const pathname = usePathname(); + + const [profile, setProfile] = useState(settingsProfileMock); + + const { watch, setValue, handleSubmit, reset, formState } = + useForm({ + defaultValues: { language: locale, tags: profile.tags }, + }); + const language = watch("language"); + const tags = watch("tags"); + + const tagItems = tags.map((tag) => { + const isDefault = isDefaultTagKey(tag); + + return { + id: tag, + label: isDefault ? tCommon(`tag.${tag}`) : tag, + isDefault, + }; + }); + + const handleConnectCalendar = () => { + if (profile.isCalendarConnected) { + // TODO: 실제 확인 모달로 교체 + const confirmed = window.confirm("구글 캘린더 연동을 해제하시겠습니까?"); + if (!confirmed) return; + + // TODO: API - 연동 토큰 파기 + console.log("구글 캘린더 연동 토큰을 파기합니다."); + setProfile((prev) => ({ ...prev, isCalendarConnected: false })); + return; + } + + // TODO: Google 계정 인증 및 캘린더 접근 권한 동의 팝업 호출 + console.log("Google Calendar 연동 인증 팝업을 호출합니다."); + setProfile((prev) => ({ ...prev, isCalendarConnected: true })); + }; + + const handleChangeLanguage = (nextLanguage: SettingsLanguage) => { + setValue("language", nextLanguage, { shouldDirty: true }); + }; + + const handleAddTag = () => { + // TODO: 실제 태그 추가 모달로 교체 + const newTag = window.prompt("추가할 태그를 입력해주세요")?.trim(); + if (!newTag || tags.includes(newTag)) return; + + setValue("tags", [...tags, newTag], { shouldDirty: true }); + }; + + const handleRemoveTag = (tag: string) => { + setValue( + "tags", + tags.filter((existingTag) => existingTag !== tag), + { shouldDirty: true }, + ); + }; + + const handleLogout = () => { + // TODO: API - 로그아웃 처리 및 브라우저 저장 데이터 파기 + console.log("로그아웃 API 호출"); + // TODO: 로그인 페이지 라우트 추가 후 이동 연결 (뒤로가기로 재접근 차단 포함) + console.log("[Login] 페이지로 이동합니다."); + }; + + const handleSave = handleSubmit(async (values) => { + try { + // TODO: API 연동 + await new Promise((resolve) => setTimeout(resolve, 1000)); + reset(values); + + if (values.language !== locale) { + router.replace(pathname, { locale: values.language }); + } + } catch { + // TODO: 실제 토스트 컴포넌트로 교체 + window.alert("저장에 실패했습니다. 잠시 후 다시 시도해 주세요."); + } + }); + + return { + profileState: { + name: profile.name, + googleEmail: profile.googleEmail, + isCalendarConnected: profile.isCalendarConnected, + language, + tags: tagItems, + isSaveDisabled: !formState.isDirty || formState.isSubmitting, + }, + profileActions: { + onConnectCalendar: handleConnectCalendar, + onChangeLanguage: handleChangeLanguage, + onAddTag: handleAddTag, + onRemoveTag: handleRemoveTag, + onLogout: handleLogout, + onSave: handleSave, + }, + }; +}; diff --git a/apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfileLabels.ts b/apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfileLabels.ts new file mode 100644 index 00000000..de96066d --- /dev/null +++ b/apps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfileLabels.ts @@ -0,0 +1,25 @@ +"use client"; + +import { useTranslations } from "next-intl"; + +import type { SettingsProfileLabels } from "@/app/[locale]/(main)/settings/_types/profile-type"; + +export const useSettingsProfileLabels = (): SettingsProfileLabels => { + const tSettings = useTranslations("Settings"); + + return { + title: tSettings("profile.title"), + profileSection: tSettings("profile.profileSection"), + calendarSection: tSettings("profile.calendarSection"), + connect: tSettings("profile.connect"), + disconnect: tSettings("profile.disconnect"), + languageSection: tSettings("profile.languageSection"), + languageKorean: tSettings("profile.languageKorean"), + languageEnglish: tSettings("profile.languageEnglish"), + tagsSection: tSettings("profile.tagsSection"), + addTag: tSettings("profile.addTag"), + logout: tSettings("profile.logout"), + save: tSettings("profile.save"), + removeTag: (tag: string) => tSettings("profile.removeTag", { tag }), + }; +};