[FEAT] 설정 페이지 개인정보·탈퇴 화면 구현 - #127
Conversation
- Figma 디자인 기반으로 개인정보/약관/회원탈퇴 공용 네비를 구현했습니다 - 각 서브페이지에 제목+구분선 뼈대를 연결했습니다 - 회원 탈퇴용 신규 라우트(/settings/withdrawal)를 추가했습니다
- 프로필/캘린더 연동/언어/태그 설정 폼을 구현했습니다 - 기본 제공 태그(과제, 업무, 운동, 일상)는 삭제 버튼을 숨기고, 사용자가 추가한 태그만 삭제 가능하도록 했습니다
- 스크롤 영역을 main에만 적용해 사이드바가 화면에 고정되도록 수정했습니다
- 태그 칩, 태그 추가, 연동 버튼, 로그아웃 버튼 등에서 공통으로 쓰이는 pill 스타일 버튼 컴포넌트를 추가했습니다 - 삭제 아이콘이 있는 태그는 버튼 중첩을 피하기 위해 내부에만 실제 버튼을 렌더링하도록 했습니다
- 개별 버튼들을 공통 TagButton 컴포넌트로 교체했습니다 - 구글 캘린더 연동 해제 시 확인 다이얼로그를, 태그 추가 시 입력 다이얼로그를 임시로 노출하도록 구현했습니다 - 변경사항 저장 버튼을 데이터 변경 여부에 따라 활성화하고, 저장 실패 시 안내 메시지를 노출하도록 구현했습니다
- 사이드바 하위 메뉴와 개인정보 폼의 h2 라벨·버튼 텍스트를 Settings 네임스페이스 번역 키로 교체했습니다 (사용자 추가 태그는 번역 대상에서 제외) - 기본 태그를 캐노니컬 키로 저장하고 화면에서만 번역하도록 해서, 로케일과 무관하게 기본/커스텀 태그를 정확히 구분하도록 했습니다 - 언어 토글이 next-intl 로케일 라우팅과 실제로 연동되어 URL이 전환되도록 했습니다 - 구글 캘린더 연동 박스와 태그 목록의 불필요한 줄바꿈·여백 문제를 수정했습니다
- 언어 토글 클릭 시 하이라이트만 이동하고 변경사항 저장을 활성화하도록 하고, 실제 로케일 전환은 저장 성공 시에만 수행하도록 수정했습니다 - 로그아웃 시 로그인 페이지 이동 의도를 콘솔 로그로 남겨뒀습니다 (로그인 라우트가 아직 없어 실제 이동은 보류)
- Figma 디자인대로 탈퇴 안내 문구, 유의사항 박스, 탈퇴하기 버튼을 구현했습니다 - 탈퇴 버튼 클릭 시 확인 팝업(임시 window.confirm)을 노출하고, 확인 시에만 탈퇴 처리하도록 했습니다 - 다른 설정 페이지와 동일하게 Settings.withdrawal 네임스페이스로 한/영 번역을 적용했습니다 - 디자인 시스템에 없던 경고 아이콘을 WarningGrayIcon으로 추가했습니다
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough설정 페이지에 프로필 편집, 언어·태그·캘린더 관리, 사이드 네비게이션, 정책 및 회원 탈퇴 화면이 추가되었습니다. 관련 타입, 번역, 상태 관리 훅과 Changes설정 데이터 계약
설정용 공통 컨트롤
프로필 설정 흐름
회원 탈퇴 화면 흐름
설정 레이아웃과 네비게이션
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant SettingsNavContainer
participant SettingsTabsContainer
participant SettingsProfileContainer
participant SettingsProfileForm
User->>SettingsNavContainer: 설정 탭 선택
SettingsNavContainer->>SettingsTabsContainer: tab 쿼리 변경
SettingsTabsContainer->>SettingsProfileContainer: 프로필 탭 렌더링
SettingsProfileContainer->>SettingsProfileForm: 상태·라벨·핸들러 전달
User->>SettingsProfileForm: 태그·언어 변경 및 저장
SettingsProfileForm->>SettingsProfileContainer: 이벤트 콜백 호출
SettingsProfileContainer-->>SettingsProfileForm: 갱신된 상태 반영
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/timo-web/app/`[locale]/(main)/settings/_containers/SettingsProfileContainer.tsx:
- Around line 64-79: The temporary dialog text in SettingsProfileContainer is
hardcoded in Korean, so replace the strings used by handleConnectCalendar and
the related dialog/toast helpers with translation keys via useTranslations. Add
the needed keys to the locale files, then read them in SettingsProfileContainer
instead of passing literal Korean text to window.confirm, window.prompt, and
window.alert so the same flow works for English users too.
In `@apps/timo-web/app/`[locale]/(main)/settings/policy/page.tsx:
- Around line 1-8: The SettingsPolicyPage component is hardcoding the page title
as Korean text, so replace that literal in SettingsPolicyPage with a translated
value from next-intl instead of rendering "약관" directly. Use useTranslations in
this Server Component to read the existing policy/nav translation key (for
example the same settings namespace used by tSettings("nav.policy")) and render
that value in the h1 so the locale switches correctly for all languages.
In
`@apps/timo-web/app/`[locale]/(main)/settings/withdrawal/_containers/SettingsWithdrawalContainer.tsx:
- Around line 25-32: Replace the temporary withdrawal flow in
SettingsWithdrawalContainer’s handleWithdraw with the real implementation
instead of the window.confirm placeholder and console.log stub. Hook the handler
up to the actual confirmation modal UI when available, and then call the
withdrawal API service for member deletion/data removal using the existing
handleWithdraw entry point so the TODOs are fully removed.
In `@packages/timo-design-system/src/components/button/tag-button/TagButton.tsx`:
- Around line 13-21: `TagButtonProps` currently allows `onRemove` without a
guaranteed accessible label, so update the `TagButton` types to use a
discriminated union where the branch with `onRemove` requires `removeLabel` and
the branch without `onRemove` disallows it or keeps it optional as appropriate.
Then adjust `TagButton` to rely on that type contract when setting the remove
button’s `aria-label`, using the `TagButton`/`TagButtonProps` symbols to find
the relevant props and render logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2fd93976-c4cd-4223-9bca-38ec73cfddc5
⛔ Files ignored due to path filters (3)
apps/timo-web/public/images/google-calendar.pngis excluded by!**/*.pngapps/timo-web/public/images/google-logo.pngis excluded by!**/*.pngpackages/timo-design-system/src/icons/source/warning-gray.svgis excluded by!**/*.svg
📒 Files selected for processing (19)
apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsxapps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsxapps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsxapps/timo-web/app/[locale]/(main)/settings/_mocks/profile-mock.tsapps/timo-web/app/[locale]/(main)/settings/_types/profile-type.tsapps/timo-web/app/[locale]/(main)/settings/layout.tsxapps/timo-web/app/[locale]/(main)/settings/page.tsxapps/timo-web/app/[locale]/(main)/settings/policy/page.tsxapps/timo-web/app/[locale]/(main)/settings/withdrawal/_components/SettingsWithdrawalView.tsxapps/timo-web/app/[locale]/(main)/settings/withdrawal/_containers/SettingsWithdrawalContainer.tsxapps/timo-web/app/[locale]/(main)/settings/withdrawal/_queries/.gitkeepapps/timo-web/app/[locale]/(main)/settings/withdrawal/_types/withdrawal-type.tsapps/timo-web/app/[locale]/(main)/settings/withdrawal/page.tsxapps/timo-web/constants/routes.tsapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.jsonpackages/timo-design-system/src/components/button/tag-button/TagButton.stories.tsxpackages/timo-design-system/src/components/button/tag-button/TagButton.tsxpackages/timo-design-system/src/components/index.ts
| const handleWithdraw = () => { | ||
| // TODO: 실제 확인 모달로 교체 | ||
| const confirmed = window.confirm(t("confirmMessage")); | ||
| if (!confirmed) return; | ||
|
|
||
| // TODO: API - 회원 탈퇴 및 데이터 영구 삭제 | ||
| console.log("회원 탈퇴 API를 호출합니다."); | ||
| }; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial
TODO 항목 구현을 도울 수 있습니다.
window.confirm 임시 다이얼로그와 console.log 플레이스홀더에 대한 TODO가 명시되어 있습니다. PR 목표에서도 모달 UI가 준비되면 교체한다고 언급되어 있네요. 확인 모달 컴포넌트 구현이나 탈퇴 API 연동 로직 작성이 필요하시다면 도와드릴 수 있습니다. 새 이슈를 열어 추적하시겠습니까?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@apps/timo-web/app/`[locale]/(main)/settings/withdrawal/_containers/SettingsWithdrawalContainer.tsx
around lines 25 - 32, Replace the temporary withdrawal flow in
SettingsWithdrawalContainer’s handleWithdraw with the real implementation
instead of the window.confirm placeholder and console.log stub. Hook the handler
up to the actual confirmation modal UI when available, and then call the
withdrawal API service for member deletion/data removal using the existing
handleWithdraw entry point so the TODOs are fully removed.
| export interface SettingsProfile { | ||
| name: string; | ||
| googleEmail: string; | ||
| isCalendarConnected: boolean; | ||
| tags: string[]; | ||
| } | ||
|
|
||
| export interface SettingsTagItem { | ||
| id: string; | ||
| label: string; | ||
| isDefault: boolean; | ||
| } | ||
|
|
||
| export interface SettingsProfileLabels { | ||
| title: string; | ||
| profileSection: string; | ||
| calendarSection: string; | ||
| connect: string; | ||
| disconnect: string; | ||
| languageSection: string; | ||
| languageKorean: string; | ||
| languageEnglish: string; | ||
| tagsSection: string; | ||
| addTag: string; | ||
| logout: string; | ||
| save: string; | ||
| removeTag: (tag: string) => string; | ||
| } |
There was a problem hiding this comment.
API 명세와 직접 연관되는 타입들은 zod 사용해서 런타임 타입 검증을 고려하면 좋을 것 같아요
There was a problem hiding this comment.
동기화 되어야하는 값이 많아서 확실히 zod 공부해보고 고려햐봐야할 것 같네요 😢
- 설정 프로필 폼 리팩터링을 위해 react-hook-form 패키지를 추가했습니다
- 컨테이너에서 직접 관리하던 draftLanguage·isDirty·isSaving useState를 제거하고 React Hook Form의 formState로 대체했습니다 - language·tags 변경을 setValue로 처리하고 저장 로직을 handleSubmit으로 감싸 컨테이너의 책임을 폼 상태 조율로 좁혔습니다
ehye1
left a comment
There was a problem hiding this comment.
설정 페이지 깔끔하게 빠르게 잘 만들어주셨네요!!
나중에 모달이랑 태그 입력 조건 등등 추가해주시면 되겟당
수고하셨습니다!!🤗😙
- 어떤 소비처에서도 TagButtonVariant를 import하지 않아 불필요한 export를 제거했습니다
- (main) 라우트 그룹에 이미 main 랜드마크가 있어 중복되는 settings 레이아웃의 main을 section으로 변경했습니다
- 라우팅 훅과 상태 선언 사이에 빈 줄을 추가해 가독성을 정리했습니다
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/timo-web/app/[locale]/(main)/settings/_types/profile-type.ts (1)
1-8: 📐 Maintainability & Code Quality | 🔵 Trivialtype alias에
Types접미사 누락
SettingsLanguage와SettingsDefaultTagKey는 유니언 타입 alias이므로 컨벤션에 따라Types접미사가 필요합니다. As per coding guidelines, "type alias는 유니언·튜플·리터럴에만 사용, 접미사Types"를 따라야 합니다.♻️ 제안 수정안
-export type SettingsLanguage = "ko" | "en"; +export type SettingsLanguageTypes = "ko" | "en"; -export type SettingsDefaultTagKey = +export type SettingsDefaultTagKeyTypes = | "assignment" | "work" | "exercise" | "dailyLife";🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/app/`[locale]/(main)/settings/_types/profile-type.ts around lines 1 - 8, Rename the union type aliases SettingsLanguage and SettingsDefaultTagKey to SettingsLanguageTypes and SettingsDefaultTagKeyTypes, and update all references throughout the codebase to use the new names.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/timo-web/app/`[locale]/(main)/settings/_types/profile-type.ts:
- Around line 1-8: Rename the union type aliases SettingsLanguage and
SettingsDefaultTagKey to SettingsLanguageTypes and SettingsDefaultTagKeyTypes,
and update all references throughout the codebase to use the new names.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 1facf3cf-b845-4890-8cad-2f898042bdcb
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (5)
apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsProfileContainer.tsxapps/timo-web/app/[locale]/(main)/settings/_types/profile-type.tsapps/timo-web/app/[locale]/(main)/settings/layout.tsxapps/timo-web/package.jsonpackages/timo-design-system/src/components/index.ts
💤 Files with no reviewable changes (1)
- packages/timo-design-system/src/components/index.ts
- TagButton이 태그 표시(정적·삭제가능)와 일반 클릭 액션(캘린더 연동·로그아웃 등)을 한 컴포넌트로 처리해 이름과 책임이 맞지 않아 분리했습니다 - TagChip은 태그 표시 전용으로 onRemove 유무만 분기하도록 단순화하고, PillButton은 onClick이 필수인 클릭 가능한 작은 버튼으로 정리했습니다
- TagButton 분리에 맞춰 태그 목록은 TagChip으로, 캘린더 연동·태그 추가·로그아웃 버튼은 PillButton으로 교체했습니다
- flex-col 부모의 기본 stretch로 버튼이 전체 너비를 차지해 Figma의 Hug 크기(111x44)와 어긋나 self-start를 추가했습니다
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/timo-web/messages/ko.json (1)
62-62: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win한국어 띄어쓰기를 수정해 주세요.
더이상은더 이상으로 표기해야 합니다.수정 예시
- "guideTitle": "더이상 Timo를 이용하지 않으시나요?", + "guideTitle": "더 이상 Timo를 이용하지 않으시나요?",🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/timo-web/messages/ko.json` at line 62, In the Korean translation entry "guideTitle", correct the spacing in the displayed text by changing "더이상" to "더 이상".
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@packages/timo-design-system/src/components/button/pill-button/PillButton.stories.tsx`:
- Around line 1-2: Update the imports in the PillButton Storybook file to use
the package’s configured absolute path aliases instead of the relative
"./PillButton" and "../../../icons" paths, preserving the existing symbols.
In
`@packages/timo-design-system/src/components/button/pill-button/PillButton.tsx`:
- Line 1: Update the cn import in PillButton.tsx to use the repository’s
configured absolute path alias instead of the relative "../../../lib" path,
following the existing TypeScript/React import convention.
- Around line 5-9: Rename the union type alias PillButtonVariant to
PillButtonVariantTypes and update all references, including the Record key in
PILL_BUTTON_VARIANT and any component props or exports using the original name.
In `@packages/timo-design-system/src/components/tag/tag-chip/TagChip.stories.tsx`:
- Line 1: TagChip.stories.tsx의 TagChip import를 상대 경로 "./TagChip"에서 프로젝트에 정의된 절대
경로 alias import로 변경하세요.
In `@packages/timo-design-system/src/components/tag/tag-chip/TagChip.tsx`:
- Around line 1-2: Update the imports in TagChip.tsx to use the repository’s
configured absolute package aliases instead of the relative paths
"../../../icons" and "../../../lib", preserving the existing DeleteIcon and cn
symbols.
- Around line 8-9: TagChip의 삭제 버튼이 접근성 이름 없이 렌더링되지 않도록 onRemove와 removeLabel의
타입을 함께 조정하세요. onRemove가 제공되는 경우 removeLabel을 필수로 요구하고, TagChip 내부 삭제 버튼의
aria-label에 해당 값을 전달하며 관련 타입 정의와 사용처도 업데이트하세요.
---
Outside diff comments:
In `@apps/timo-web/messages/ko.json`:
- Line 62: In the Korean translation entry "guideTitle", correct the spacing in
the displayed text by changing "더이상" to "더 이상".
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: ebc52247-d46a-448c-9a52-df5fffb15caa
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (10)
apps/timo-web/app/[locale]/(main)/settings/_components/SettingsProfileForm.tsxapps/timo-web/app/[locale]/(main)/settings/withdrawal/_components/SettingsWithdrawalView.tsxapps/timo-web/messages/en.jsonapps/timo-web/messages/ko.jsonapps/timo-web/package.jsonpackages/timo-design-system/src/components/button/pill-button/PillButton.stories.tsxpackages/timo-design-system/src/components/button/pill-button/PillButton.tsxpackages/timo-design-system/src/components/index.tspackages/timo-design-system/src/components/tag/tag-chip/TagChip.stories.tsxpackages/timo-design-system/src/components/tag/tag-chip/TagChip.tsx
| import { PillButton } from "./PillButton"; | ||
| import { PlusGrayIcon } from "../../../icons"; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Storybook import도 절대 경로로 통일해 주세요.
./PillButton와 ../../../icons는 TypeScript/React 파일의 상대 경로 import 규칙을 위반합니다. 패키지에서 사용하는 절대 경로 alias로 변경해 주세요.
As per path instructions: TypeScript/React 파일은 상대 경로보다 절대 경로 import를 사용해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@packages/timo-design-system/src/components/button/pill-button/PillButton.stories.tsx`
around lines 1 - 2, Update the imports in the PillButton Storybook file to use
the package’s configured absolute path aliases instead of the relative
"./PillButton" and "../../../icons" paths, preserving the existing symbols.
Source: Path instructions
| @@ -0,0 +1,39 @@ | |||
| import { cn } from "../../../lib"; | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
상대 경로 import를 절대 경로로 변경해 주세요.
../../../lib는 저장소의 TypeScript/React import 규칙을 위반합니다. 패키지에서 사용하는 절대 경로 alias로 cn을 가져오면 디렉터리 구조 변경에도 더 안전합니다. 참고: https://www.typescriptlang.org/docs/handbook/module-resolution.html
As per path instructions: TypeScript/React 파일은 상대 경로보다 절대 경로 import를 사용해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/timo-design-system/src/components/button/pill-button/PillButton.tsx`
at line 1, Update the cn import in PillButton.tsx to use the repository’s
configured absolute path alias instead of the relative "../../../lib" path,
following the existing TypeScript/React import convention.
Source: Path instructions
| export type PillButtonVariant = "gray" | "blue"; | ||
|
|
||
| const PILL_BUTTON_VARIANT: Record<PillButtonVariant, string> = { | ||
| gray: "bg-timo-gray-300 text-timo-gray-900", | ||
| blue: "bg-timo-blue-300 text-white", |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
유니언 타입 alias 이름에 Types 접미사를 사용해 주세요.
PillButtonVariant는 유니언 타입 alias이므로 PillButtonVariantTypes로 이름을 변경하고 Record 선언도 함께 갱신해야 합니다.
As per path instructions: 유니언·튜플·리터럴을 제외한 타입 alias에는 Types 접미사 규칙을 적용하며, 이 프로젝트의 해당 타입도 일관된 이름을 사용해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/timo-design-system/src/components/button/pill-button/PillButton.tsx`
around lines 5 - 9, Rename the union type alias PillButtonVariant to
PillButtonVariantTypes and update all references, including the Record key in
PILL_BUTTON_VARIANT and any component props or exports using the original name.
Source: Path instructions
| @@ -0,0 +1,44 @@ | |||
| import { TagChip } from "./TagChip"; | |||
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Storybook import를 절대 경로로 변경해 주세요.
./TagChip는 TypeScript/React 파일의 상대 경로 import 규칙을 위반합니다. 프로젝트의 절대 경로 alias를 사용해 주세요.
As per path instructions: TypeScript/React 파일은 상대 경로보다 절대 경로 import를 사용해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/timo-design-system/src/components/tag/tag-chip/TagChip.stories.tsx`
at line 1, TagChip.stories.tsx의 TagChip import를 상대 경로 "./TagChip"에서 프로젝트에 정의된 절대
경로 alias import로 변경하세요.
Source: Path instructions
| import { DeleteIcon } from "../../../icons"; | ||
| import { cn } from "../../../lib"; |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
상대 경로 import를 절대 경로로 변경해 주세요.
../../../icons와 ../../../lib는 저장소의 TypeScript/React import 규칙을 위반합니다. 패키지 alias를 사용해 import 경로를 고정해 주세요.
As per path instructions: TypeScript/React 파일은 상대 경로보다 절대 경로 import를 사용해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/timo-design-system/src/components/tag/tag-chip/TagChip.tsx` around
lines 1 - 2, Update the imports in TagChip.tsx to use the repository’s
configured absolute package aliases instead of the relative paths
"../../../icons" and "../../../lib", preserving the existing DeleteIcon and cn
symbols.
Source: Path instructions
| onRemove?: () => void; | ||
| removeLabel?: string; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
삭제 버튼의 접근성 이름을 타입 수준에서 보장해 주세요.
onRemove가 있으면 아이콘 전용 버튼이 렌더링되지만, removeLabel은 선택 사항이라 aria-label={undefined}인 버튼이 만들어질 수 있습니다. 삭제 가능한 상태에서는 removeLabel을 필수로 만들거나, 항상 현지화된 기본 라벨을 제공해 주세요. 참고: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-label
As per path instructions: 텍스트 없는 버튼에는 aria-label이 필수입니다.
Also applies to: 24-30
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/timo-design-system/src/components/tag/tag-chip/TagChip.tsx` around
lines 8 - 9, TagChip의 삭제 버튼이 접근성 이름 없이 렌더링되지 않도록 onRemove와 removeLabel의 타입을 함께
조정하세요. onRemove가 제공되는 경우 removeLabel을 필수로 요구하고, TagChip 내부 삭제 버튼의 aria-label에 해당
값을 전달하며 관련 타입 정의와 사용처도 업데이트하세요.
Source: Path instructions
yumin-kim2
left a comment
There was a problem hiding this comment.
전체적으로 컨테이너와 뷰 컴포넌트의 역할이 잘 나뉘어 있어서 읽기 좋았습니다. 특히 개인정보 설정에서 언어/태그 변경값을 react-hook-form으로 관리한 덕분에 변경 여부와 저장 상태를 직접 상태로 하나씩 관리하지 않아도 되어 코드가 깔끔해진 것 같아요! (덕분에 react-hook-form 공부도 했네요✨)
작은 코멘트 남겼는데 확인해주세요! 수고하셨씁니다아아
| const handleAddTag = () => { | ||
| // TODO: 실제 태그 추가 모달로 교체 | ||
| const newTag = window.prompt("추가할 태그를 입력해주세요")?.trim(); | ||
| if (!newTag || tags.includes(newTag)) return; |
There was a problem hiding this comment.
화면설계서에 "1~10자의 중복 없는 신규 태그명 입력 시 활성화 상태로 클릭 가능" 이라는 조건이 있는데 현재는 중복 검사만 있고 길이 제한 로직이 없는 것 같습니다 확인 부탁드릴게용🙇♀️
There was a problem hiding this comment.
모달 추가하면서 로직 같이 추가하려고합니다-!
| export const SettingsWithdrawalContainer = () => { | ||
| const t = useTranslations("Settings.withdrawal"); | ||
|
|
||
| const labels: SettingsWithdrawalLabels = { |
There was a problem hiding this comment.
탈퇴 화면 문구를 전부 i18n으로 관리해서 한국어/영어 확장에 대응한 점이 좋네용 👍
| removeTag: (tag: string) => tSettings("profile.removeTag", { tag }), | ||
| }; | ||
|
|
||
| const handleConnectCalendar = () => { |
There was a problem hiding this comment.
handleConnectCalendar가 실제로는 연동과 연동 해제를 모두 처리하고 있어서, handleToggleCalendarConnection처럼 연동/해제를 모두 포함하는 동작이 드러나는 이름도 고려해볼 수 있을 것 같습니다! 아니면 함수명이 너무 길어져서 안 좋을까요?
- 다국어 라벨 매핑을 useSettingsProfileLabels 훅으로 분리했습니다 - 프로필 상태·핸들러를 useSettingsProfile 훅으로 분리하고 profileState/profileActions로 그룹핑해 반환하도록 했습니다 - 컨테이너는 두 훅을 호출해 결과를 그대로 폼에 전달하도록 단순화했습니다
Timo Performance ReportBundle Size — timo-web
Lighthouse — timo-web
Image Optimization — timo-web
측정 커밋: |
- 언어 선택 상태를 react-hook-form 대신 ?lang= 쿼리 파라미터로 관리하도록 했습니다 - 언어 선택 즉시 router.replace로 로케일을 전환하도록 했습니다 (기존에는 저장 시점에만 전환) - 저장 버튼 활성화 조건에서 언어 관련 dirty 체크를 제거했습니다
- /settings/policy, /settings/withdrawal 별도 라우트를 없애고 /settings?tab=으로 통합했습니다 - 탭 값에 따라 프로필·약관·탈퇴 컨테이너를 렌더링하는 SettingsTabsContainer를 추가했습니다 - 네비게이션의 active 판정을 pathname 비교 대신 useSettingsTab 비교로 변경했습니다 - ROUTES에서 탭 관련 pseudo-route를 제거하고 nav에서 직접 href를 조립하도록 했습니다
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@apps/timo-web/app/`[locale]/(main)/settings/_hooks/useSettingsLanguageParam.ts:
- Around line 27-34: Update setLanguage so it only updates the language query
parameter without passing locale: next to router.replace; move the actual locale
change to the settings save handler, ensuring selecting a language updates draft
state while the application locale changes only after saving.
In
`@apps/timo-web/app/`[locale]/(main)/settings/policy/_containers/SettingsPolicyContainer.tsx:
- Around line 1-8: Update SettingsPolicyContainer by adding the required 'use
client' directive and replacing the hardcoded “약관” heading with the established
i18n translation mechanism, using the appropriate key for Korean and English
while preserving the existing layout and styling.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f82b10ba-8889-45b3-8da1-4382f81b892d
📒 Files selected for processing (8)
apps/timo-web/app/[locale]/(main)/settings/_containers/SettingsNavContainer.tsxapps/timo-web/app/[locale]/(main)/settings/_containers/SettingsTabsContainer.tsxapps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsLanguageParam.tsapps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsProfile.tsapps/timo-web/app/[locale]/(main)/settings/_hooks/useSettingsTab.tsapps/timo-web/app/[locale]/(main)/settings/_types/profile-type.tsapps/timo-web/app/[locale]/(main)/settings/page.tsxapps/timo-web/app/[locale]/(main)/settings/policy/_containers/SettingsPolicyContainer.tsx
💤 Files with no reviewable changes (1)
- apps/timo-web/app/[locale]/(main)/settings/_types/profile-type.ts
- 언어 선택 시 로케일을 즉시 전환하지 않고 ?lang= 쿼리에만 드래프트로 반영하도록 되돌렸습니다 - 실제 로케일 전환은 저장 성공 시 commitLanguage()로 커밋하도록 분리했습니다 - 저장 버튼 활성화 조건에 언어 draft 변경 여부를 다시 포함시켰습니다
…to feat/web/122-settings-page-view # Conflicts: # packages/timo-design-system/src/components/index.ts
ISSUE 🔗
close #122
What is this PR? 🔍
설정(Settings) 페이지의 개인정보 화면과 회원 탈퇴 화면을 Figma 디자인 기준으로 구현하고, 한/영 다국어를 적용했습니다.
배경
settings/page.tsx와settings/withdrawal/page.tsx는 헤더(h1)만 있고 실제 콘텐츠가 없는 뼈대 상태였습니다.window.confirm/prompt/alert로 임시 대체했습니다. 태그·버튼 등 반복되는 UI는 디자인 시스템 공용 컴포넌트로 분리했습니다.설정 사이드바
layout.tsx에서 스크롤 컨테이너를main에만 적용(overflow-hidden+overflow-y-auto)해 사이드바를 고정시켰습니다.SettingsNavContainer는useTranslations("Settings")로nav.account/nav.policy/nav.withdrawal키를 조회하고, 최상단 "설정" 타이틀은 기존Navigation.settings키를 재사용해 중복 정의를 피했습니다.설정 하위 페이지 라우팅
/settings/policy,/settings/withdrawal별도 라우트를 없애고/settings?tab=policy//settings?tab=withdrawal형태의 단일 라우트 + 쿼리 파라미터 구조로 통합했습니다.page.tsx를 갖다 보니, 사실상 같은 설정 레이아웃 안에서 탭만 바뀌는 동작인데도 매번 별도 라우트 이동으로 처리되고 있었습니다.useSettingsTab훅이useSearchParams()로tab값을 읽어"account" | "policy" | "withdrawal"로 정규화하고,SettingsTabsContainer가 그 값에 따라SettingsProfileContainer/SettingsPolicyContainer/SettingsWithdrawalContainer중 하나를 렌더링합니다.SettingsNavContainer의 active 판정도pathname비교 대신 같은 훅으로 통일했습니다.ROUTES에는 실제 라우트인SETTINGS: "/settings"만 남기고, 탭별 href는 nav 쪽getSettingsTabHref(tab)에서 조립해 "라우트 상수 = 실제 경로"라는 원칙이 깨지지 않도록 했습니다./settings/policy,/settings/withdrawal경로는 더 이상 존재하지 않습니다 (해당page.tsx삭제). 북마크·외부 링크 호환을 위한 리다이렉트는 이번 PR 범위 밖입니다.개인정보 화면
SettingsProfileContainer(정확히는 내부의useSettingsProfile훅)가isDirty플래그로 태그 변경 여부를 추적해 저장 버튼 활성/비활성을 결정합니다. 캘린더 연동 해제 시window.confirm으로 확인 팝업을 흉내 내고, 확인 시에만 상태를 되돌립니다. 언어 토글은 리뷰 과정에서 방향이 바뀌어, 클릭 즉시useSettingsLanguageParam훅이?lang=쿼리를 갱신하면서@/i18n/navigation의router.replace(pathname, { locale })로 실제 로케일을 전환합니다 — 더 이상 저장 버튼과 연동되지 않고, 저장은 태그 변경 여부만 봅니다. 태그는 기본 태그(과제/업무/운동/일상)를 캐노니컬 키(assignment/work/exercise/dailyLife)로 저장하고 화면에서Common.tag.*로 번역해, 로케일이 바뀌어도 기본/커스텀 태그 구분이 깨지지 않도록 했습니다. 태그 추가는window.promptplaceholder에 10자 제한과 중복 검사를 추가했습니다.공용 버튼 컴포넌트
TagButton으로 먼저 묶었다가, 리뷰 과정에서TagChip(태그 표시)과PillButton(클릭 액션)으로 용도별로 다시 분리했습니다.TagButton이라는 이름과 달리 실제로는 태그 표시(정적 기본 태그/삭제 가능한 커스텀 태그)와 캘린더 연동, 태그 추가, 로그아웃 같은 일반 클릭 액션까지 한 컴포넌트가 함께 떠맡고 있어 이름이 가리키는 역할과 실제 책임이 어긋났어요.variant·icon·onClick·onRemove4개 prop을 모두 받다 보니, 소비처마다 실제로 쓰는 조합은 서로 겹치지 않는데도onRemove→onClick유무 순으로 분기하는 로직 하나에 섞여 있었습니다.그래서
TagChip(packages/timo-design-system/src/components/tag/tag-chip)은 태그 표시 전용으로onRemove유무만으로 분기하고,variant·icon·onClick을 제거해 항상 gray 배경만 렌더링하도록 했어요.PillButton(.../components/button/pill-button)은 반대로onClick을 필수 prop으로 바꿔 클릭 가능한 액션(캘린더 연동variant="blue", 태그 추가icon, 로그아웃)만 담당합니다.삭제 가능한 태그는
<button>안에<button>을 중첩할 수 없어TagChip바깥을<div>로, 삭제 아이콘만 내부<button>으로 렌더링하는 기존 구조는 그대로 유지했습니다.태그 추가 버튼을
TagChip이 아니라PillButton으로 분류한 이유는 "태그 추가" 버튼은 실제 태그를 표시하는 게 아니라 클릭 가능한 액션(모달 열기/prompt 띄우기)이기 때문이에요. TagChip은 이번에 onRemove 유무로만 분기하는 순수 표시용 컴포넌트로 단순화했고 (onClick, icon prop을 아예 없앴음), 클릭 가능한 것들(캘린더 연동, 태그 추가, 로그아웃)은 전부 PillButton으로 모았습니다.다국어(i18n) 지원
messages/ko.json·en.json의Settings네임스페이스 번역을 적용했습니다.next-intl로 번역되고 있었는데, 설정 하위 화면들만 한국어로 고정되어 있어/en로케일 진입 시 일관성이 깨졌습니다._components는 순수 UI 원칙에 따라 직접 번역 훅을 쓰지 않고,_containers가useTranslations로 만든labels객체를 prop으로 내려받습니다. 사용자가 직접 추가한 커스텀 태그(예: "안녕")는 번역 대상에서 제외했습니다.회원 탈퇴 화면
window.confirm으로 "정말 탈퇴하시겠어요?" 확인을 거치고, 확인 시에만 콘솔 로그로 탈퇴 처리 placeholder를 실행합니다. 디자인 시스템에 없던 경고 아이콘은 Figma에서 내려받아warning-gray.svg소스로 추가하고icons:generate로WarningGrayIcon을 생성했습니다(생성 산출물은 gitignore 대상이라 소스 SVG만 커밋에 포함됩니다).To Reviewers
window.confirm/prompt/alert로 임시 처리했습니다. 실제 모달 컴포넌트로 교체하는 작업은 후속 PR로 남겨뒀습니다.?lang=쿼리 파라미터 방식으로 다시 바꿨습니다. 실제 기획 의도와 맞는지,/settings/policy·/settings/withdrawal라우트를 없애고/settings?tab=으로 통합한 방향이 괜찮은지 한 번 더 확인 부탁드립니다.SettingsProfile.tags가 캐노니컬 키(영문)와 커스텀 원문(한국어)을 한 배열에 섞어서 담는 구조라, 실제 API 연동 시에는 백엔드가 태그를 어떤 형태로 내려주는지에 따라 이 부분을 다시 다듬어야 할 수 있습니다.Screenshot 📷
Common.tag/Settings.profile번역 적용Test Checklist ✔
pnpm check-types통과pnpm lint통과/ko,/en개인정보·탈퇴 화면 렌더링 및 텍스트 확인/ko↔/en)와?lang=쿼리가 즉시 함께 바뀌는지 확인/settings개인정보·약관·회원 탈퇴 탭이?tab=쿼리로 전환되는지,/settings/policy·/settings/withdrawal경로가 더 이상 쓰이지 않는지 확인pnpm build— 미실행: CI에서 확인 예정