From 18fe3dffdeb930c4d82f2c5b7292b49ec2da1862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Sun, 4 Oct 2026 02:01:44 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=EC=84=A4=EC=A0=95=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) --- src/app/router.tsx | 8 ++ src/assets/icons/settings/arrow-right.svg | 5 + src/features/settings/SettingsScreen.tsx | 131 ++++++++++++++++++ .../settings/components/SettingsRow.tsx | 98 +++++++++++++ .../settings/components/SettingsSection.tsx | 42 ++++++ src/features/settings/constants/settings.ts | 51 +++++++ 6 files changed, 335 insertions(+) create mode 100644 src/assets/icons/settings/arrow-right.svg create mode 100644 src/features/settings/SettingsScreen.tsx create mode 100644 src/features/settings/components/SettingsRow.tsx create mode 100644 src/features/settings/components/SettingsSection.tsx create mode 100644 src/features/settings/constants/settings.ts diff --git a/src/app/router.tsx b/src/app/router.tsx index 3c00ff7..e436878 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -20,6 +20,7 @@ import LockersApplyScreen from "@/features/lockers/LockersApplyScreen"; import LockersSectionSelectScreen from "@/features/lockers/LockersSectionSelectScreen"; import NoticesDetailScreen from "@/features/notices/NoticesDetailScreen"; import NoticesListScreen from "@/features/notices/NoticesListScreen"; +import SettingsScreen from "@/features/settings/SettingsScreen"; // 앱의 모든 라우트는 이 객체 배열 한곳에서 정의한다 — 새 화면은 여기에 라우트를 추가한다. // satisfies로 선언 시점에 RouteObject 형태를 검사한다. @@ -107,6 +108,13 @@ const routes = [ } satisfies ScreenRouteHandle, path: "/notices/:noticeId", }, + { + element: , + // 흰 카드가 회색 배경 위에 쌓이는 화면이라 background는 기본값(alternative)을 쓴다. + // 설정은 뒤로가기로만 빠져나가는 흐름이라 Bottom Nav를 안 보여준다(Figma 3013:116135). + handle: { hasBottomNav: false } satisfies ScreenRouteHandle, + path: "/settings", + }, { element: , // 시트만 있는 화면이라 뒤에 Bottom Nav가 비쳐도 Figma(홈 위에 뜨는 시트)와 같다 diff --git a/src/assets/icons/settings/arrow-right.svg b/src/assets/icons/settings/arrow-right.svg new file mode 100644 index 0000000..be36674 --- /dev/null +++ b/src/assets/icons/settings/arrow-right.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/features/settings/SettingsScreen.tsx b/src/features/settings/SettingsScreen.tsx new file mode 100644 index 0000000..5ae3717 --- /dev/null +++ b/src/features/settings/SettingsScreen.tsx @@ -0,0 +1,131 @@ +import { TopNavigationButton, Typography } from "@wanteddev/wds"; +import { IconChevronLeft } from "@wanteddev/wds-icon"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import ConfirmModal from "@/components/ui/ConfirmModal"; +import ScreenHeader from "@/components/ui/ScreenHeader"; +import { useScreenHeader } from "@/components/ui/useScreenHeader"; +import SettingsRow from "@/features/settings/components/SettingsRow"; +import SettingsSection from "@/features/settings/components/SettingsSection"; +import { + APP_VERSION_LABEL, + SETTINGS_ACCOUNT, + SETTINGS_EXTERNAL_LINKS, +} from "@/features/settings/constants/settings"; + +// Figma: 설정 (nodeId 3013:116135) — 계정 정보 / 알림 설정 / 서비스 정보 / 로그아웃 네 장의 흰 +// 카드가 회색 배경 위에 8px 간격으로 쌓이고, 맨 아래에 Copyright가 깔린다. +// +// 의견 보내기·개인정보 처리방침은 화면을 따로 만들지 않고 구글폼으로 연결한다(폼 주소가 아직 +// 없어서 SETTINGS_EXTERNAL_LINKS가 빈 문자열이면 아무 동작도 하지 않는다). +// 회원 탈퇴는 Figma에 누른 뒤 화면·모달이 없어서 행만 그리고 동작은 비워뒀다 — 로그아웃만 +// 확인 모달(1799:87183)이 그려져 있어 공용 ConfirmModal(tone="negative")로 붙였다. +function SettingsScreen() { + const navigate = useNavigate(); + const [logoutOpen, setLogoutOpen] = useState(false); + + useScreenHeader( + navigate(-1)} + variant="icon" + > + + + } + title="설정" + variant="normal" + />, + ); + + const openExternalLink = (url: string) => { + if (!url) { + return; + } + window.open(url, "_blank", "noopener,noreferrer"); + }; + + return ( +
+
+ + + + navigate("/settings/phone")} + value={SETTINGS_ACCOUNT.phone} + variant="account" + /> + + + + navigate("/settings/notifications")} + /> + + + + + openExternalLink(SETTINGS_EXTERNAL_LINKS.feedbackForm) + } + /> + + openExternalLink(SETTINGS_EXTERNAL_LINKS.privacyPolicy) + } + /> + + + + + setLogoutOpen(true)} + tone="alternative" + /> + + +
+ + {/* Copyright는 카드 묶음 아래 남는 공간 맨 끝에 붙는다(Figma 3013:116181) */} + + © 2026 stream + + + setLogoutOpen(false)} + onConfirm={() => setLogoutOpen(false)} + open={logoutOpen} + title="정말 로그아웃할까요?" + tone="negative" + /> +
+ ); +} + +export default SettingsScreen; diff --git a/src/features/settings/components/SettingsRow.tsx b/src/features/settings/components/SettingsRow.tsx new file mode 100644 index 0000000..0f484c0 --- /dev/null +++ b/src/features/settings/components/SettingsRow.tsx @@ -0,0 +1,98 @@ +import { Typography } from "@wanteddev/wds"; + +import arrowRight from "@/assets/icons/settings/arrow-right.svg"; + +interface SettingsRowProps { + label: string; + /** 오른쪽에 붙는 값 — 계정 정보(이름·이메일·전화번호)와 앱 버전에만 있다 */ + value?: string; + /** 넘기면 행 전체가 버튼이 된다 */ + onClick?: () => void; + /** + * 오른쪽 화살표. 기본값은 onClick 유무를 따르지만, 회원 탈퇴처럼 Figma엔 화살표가 있는데 + * 누른 뒤 화면이 아직 없는 행은 이 값을 직접 켜준다. + */ + arrow?: boolean; + /** + * account: 계정 정보 행 — 라벨 Regular, 값 Medium·Label/Neutral + * menu: 그 외 메뉴·액션 행 — 라벨 Medium, 값 Regular·Label/Alternative(앱 버전) + */ + variant?: "account" | "menu"; + /** menu 행 라벨 색 — 로그아웃은 alternative, 회원 탈퇴는 negative */ + tone?: "normal" | "alternative" | "negative"; +} + +const TONE_COLOR = { + alternative: "semantic.label.alternative", + negative: "semantic.status.negative", + normal: "semantic.label.neutral", +} as const; + +// Figma: 설정 화면(nodeId 3013:116135)의 행들 — Account Row(3013:116144), Navigation Row +// (3013:116159), Info Row(3013:116174), Action Row(3013:116178)가 같은 높이(20px)·같은 +// justify-between 구조라 한 컴포넌트의 variant/tone으로 합쳤다. 넷의 차이는 라벨 굵기(계정 +// 정보만 Regular)와 라벨 색(로그아웃 alternative·회원 탈퇴 negative)뿐이다. +// +// 화살표는 Figma `Arrow`(445:12456) — WDS에 같은 이름이 없고(검색 결과 `Icon/Normal/Arrow *`· +// `Chevron *`뿐) wds-component-usage.md에도 Stream 전용 아이콘으로 분류돼 있어 SVG로 받아 쓴다. +// 색(#C2C4C8 = Atomic/Cool Neutral/90)이 에셋에 구워져 있어 토큰을 따로 추가하지 않았다. +// +// 라벨·값은 Typography 기본 태그(span)로 둔다 — onClick이 있으면 행 전체가 + ); +} + +export default SettingsRow; diff --git a/src/features/settings/components/SettingsSection.tsx b/src/features/settings/components/SettingsSection.tsx new file mode 100644 index 0000000..02f09ad --- /dev/null +++ b/src/features/settings/components/SettingsSection.tsx @@ -0,0 +1,42 @@ +import { Typography } from "@wanteddev/wds"; +import type { ReactNode } from "react"; + +interface SettingsSectionProps { + /** 섹션 라벨("계정 정보"·"서비스 정보") — 알림 설정·로그아웃 카드처럼 없는 섹션도 있다 */ + title?: string; + /** Figma 기준 행 사이 간격: 계정 정보·서비스 정보는 20px, 로그아웃은 16px */ + rowGap?: "default" | "compact"; + children: ReactNode; +} + +// Figma: 설정 화면(nodeId 3013:116135)의 흰 카드 — Account Section(3013:116141), +// Notification Settings Section(3013:116158), Service Info Section(3013:116162), +// Logout Section(3013:116177) 넷이 같은 배경·radius·padding이라 하나로 합쳤다. +// 섹션 라벨 유무와 행 간격만 다르다. +function SettingsSection({ + title, + rowGap = "default", + children, +}: SettingsSectionProps) { + return ( +
+ {title && ( + + {title} + + )} +
+ {children} +
+
+ ); +} + +export default SettingsSection; diff --git a/src/features/settings/constants/settings.ts b/src/features/settings/constants/settings.ts new file mode 100644 index 0000000..ad04125 --- /dev/null +++ b/src/features/settings/constants/settings.ts @@ -0,0 +1,51 @@ +// Figma: 설정 화면 계정 정보(nodeId 3013:116143)·앱 버전(3013:116176) 목데이터 — 실 API 연동 +// 전까지 사용. 전화번호는 변경 화면에서 바꾸면 반영돼야 하는 값이라 화면 state의 초기값으로 쓴다. +export const SETTINGS_ACCOUNT = { + email: "pog454@kookmin.ac.kr", + name: "이예담", + phone: "010-1234-5678", +}; + +// Figma 표기 그대로("26.0.1 · 최신버전") — 최신 여부 판정은 서버 응답을 그대로 보여주는 자리다. +export const APP_VERSION_LABEL = "26.0.1 · 최신버전"; + +// 의견 보내기·개인정보 처리방침은 화면을 따로 만들지 않고 구글폼으로 연결한다. 폼 주소가 아직 +// 없어서 빈 문자열로 두고, 값이 채워졌을 때만 새 탭으로 열도록 호출부에서 분기한다. +export const SETTINGS_EXTERNAL_LINKS = { + feedbackForm: "", + privacyPolicy: "", +}; + +// 화면의 on/off state(Record)와 키를 맞추려고 id를 유니온으로 둔다 — 문자열로 두면 양쪽 중 +// 한쪽에 오타가 나도 타입 에러가 안 난다. +export type NotificationCategoryId = "bililge" | "events" | "board" | "lockers"; + +interface NotificationCategory { + id: NotificationCategoryId; + title: string; + description: string; +} + +// Figma: 알림 설정 Category Notification List(nodeId 3013:116193) 순서·문구 그대로 +export const NOTIFICATION_CATEGORIES: NotificationCategory[] = [ + { + description: "대여 진행 상황과 반납 일정을 알려 드려요", + id: "bililge", + title: "빌릴게 알림", + }, + { + description: "진행 중인 행사와 신청 현황을 알려 드려요", + id: "events", + title: "행사 알림", + }, + { + description: "새 공지와 열린피드백 답변을 알려 드려요", + id: "board", + title: "게시판 알림", + }, + { + description: "배정 결과와 이용 기간 만료 임박을 알려 드려요", + id: "lockers", + title: "사물함 알림", + }, +]; From 77225a2803c8942f3f6c64f9f5a6a3a2b4535859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Sun, 4 Oct 2026 02:01:55 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20=EC=95=8C=EB=A6=BC=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=ED=99=94=EB=A9=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) --- src/app/router.tsx | 10 ++ .../settings/NotificationSettingsScreen.tsx | 95 +++++++++++++++++++ .../components/NotificationToggleRow.tsx | 52 ++++++++++ 3 files changed, 157 insertions(+) create mode 100644 src/features/settings/NotificationSettingsScreen.tsx create mode 100644 src/features/settings/components/NotificationToggleRow.tsx diff --git a/src/app/router.tsx b/src/app/router.tsx index e436878..a6e3057 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -20,6 +20,7 @@ import LockersApplyScreen from "@/features/lockers/LockersApplyScreen"; import LockersSectionSelectScreen from "@/features/lockers/LockersSectionSelectScreen"; import NoticesDetailScreen from "@/features/notices/NoticesDetailScreen"; import NoticesListScreen from "@/features/notices/NoticesListScreen"; +import NotificationSettingsScreen from "@/features/settings/NotificationSettingsScreen"; import SettingsScreen from "@/features/settings/SettingsScreen"; // 앱의 모든 라우트는 이 객체 배열 한곳에서 정의한다 — 새 화면은 여기에 라우트를 추가한다. @@ -115,6 +116,15 @@ const routes = [ handle: { hasBottomNav: false } satisfies ScreenRouteHandle, path: "/settings", }, + { + element: , + // 행이 배경까지 흰 면이고 섹션 구분만 8px 회색 띠로 하는 화면이다(Figma 3013:116182) + handle: { + background: "normal", + hasBottomNav: false, + } satisfies ScreenRouteHandle, + path: "/settings/notifications", + }, { element: , // 시트만 있는 화면이라 뒤에 Bottom Nav가 비쳐도 Figma(홈 위에 뜨는 시트)와 같다 diff --git a/src/features/settings/NotificationSettingsScreen.tsx b/src/features/settings/NotificationSettingsScreen.tsx new file mode 100644 index 0000000..4b4a228 --- /dev/null +++ b/src/features/settings/NotificationSettingsScreen.tsx @@ -0,0 +1,95 @@ +import { TopNavigationButton } from "@wanteddev/wds"; +import { IconChevronLeft } from "@wanteddev/wds-icon"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import ScreenHeader from "@/components/ui/ScreenHeader"; +import { useScreenHeader } from "@/components/ui/useScreenHeader"; +import NotificationToggleRow from "@/features/settings/components/NotificationToggleRow"; +import { + NOTIFICATION_CATEGORIES, + type NotificationCategoryId, +} from "@/features/settings/constants/settings"; + +// Figma 기본값: 전체 알림 off, 카테고리는 빌릴게만 on (3013:116191, 3013:116199) +const DEFAULT_CATEGORY_STATE: Record = { + bililge: true, + board: false, + events: false, + lockers: false, +}; + +// Figma: 알림 설정 (nodeId 3013:116182) — 전체 알림 행, 8px 두꺼운 구분선, 카테고리 4개. +// 구분선은 Figma `Divider(new)`의 8px 버전이라 1px 헤어라인(WDS Divider)과 다른 패턴이다 +// (wds-component-usage.md "Divider(new)의 8px 버전은 1px 구분선과 다른 별개 패턴" 참고). +// +// 전체 알림은 카테고리를 한 번에 켜고 끄는 마스터 스위치다. 그래서 자체 state를 따로 두지 않고 +// "카테고리가 전부 켜져 있는가"로 계산한다 — 이러면 카테고리를 하나라도 끄는 순간 전체 알림도 +// 같이 꺼져서 두 값이 어긋날 일이 없다. Figma 기본 상태(빌릴게만 on → 전체 알림 off)도 이 +// 계산 결과와 그대로 맞는다. on/off 저장은 API 연동 전이라 화면 state로만 둔다. +function NotificationSettingsScreen() { + const navigate = useNavigate(); + const [categoryEnabled, setCategoryEnabled] = useState( + DEFAULT_CATEGORY_STATE, + ); + const allEnabled = NOTIFICATION_CATEGORIES.every( + (category) => categoryEnabled[category.id], + ); + + const handleGlobalChange = (checked: boolean) => { + setCategoryEnabled((prev) => { + const next = { ...prev }; + for (const category of NOTIFICATION_CATEGORIES) { + next[category.id] = checked; + } + return next; + }); + }; + + useScreenHeader( + navigate(-1)} + variant="icon" + > + + + } + title="알림 설정" + variant="normal" + />, + ); + + return ( +
+ + +
+ +
+ {NOTIFICATION_CATEGORIES.map((category) => ( + + setCategoryEnabled((prev) => ({ + ...prev, + [category.id]: checked, + })) + } + title={category.title} + /> + ))} +
+
+ ); +} + +export default NotificationSettingsScreen; diff --git a/src/features/settings/components/NotificationToggleRow.tsx b/src/features/settings/components/NotificationToggleRow.tsx new file mode 100644 index 0000000..272845c --- /dev/null +++ b/src/features/settings/components/NotificationToggleRow.tsx @@ -0,0 +1,52 @@ +import { Switch, Typography } from "@wanteddev/wds"; + +interface NotificationToggleRowProps { + title: string; + /** 카테고리 알림에만 있는 설명 — 전체 알림 행은 제목만 쓴다 */ + description?: string; + checked: boolean; + onCheckedChange: (checked: boolean) => void; +} + +// Figma: 알림 설정(nodeId 3013:116182)의 Global Notification Row(3013:116188)와 Category +// Notification Row(3013:116194 등) — 설명 유무만 다르고 나머지(좌우 20px 패딩, 제목 Body2/Bold, +// 스위치)가 같아서 description optional 한 컴포넌트로 합쳤다. +// 스위치는 Figma `Switch/Switch`(679:16080, WDS 공식 문서 링크 확인)라 WDS `Switch`를 그대로 쓴다. +function NotificationToggleRow({ + title, + description, + checked, + onCheckedChange, +}: NotificationToggleRowProps) { + return ( +
+
+ + {title} + + {description && ( + + {description} + + )} +
+ +
+ ); +} + +export default NotificationToggleRow; From 59f1f1ab0047cbc4c0483b4f59431288989a8824 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Sun, 4 Oct 2026 02:02:04 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20=EC=A0=84=ED=99=94=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EB=B3=80=EA=B2=BD=20=ED=99=94=EB=A9=B4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) --- src/app/router.tsx | 10 ++ src/features/settings/PhoneChangeScreen.tsx | 128 ++++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 src/features/settings/PhoneChangeScreen.tsx diff --git a/src/app/router.tsx b/src/app/router.tsx index a6e3057..572519f 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -21,6 +21,7 @@ import LockersSectionSelectScreen from "@/features/lockers/LockersSectionSelectS import NoticesDetailScreen from "@/features/notices/NoticesDetailScreen"; import NoticesListScreen from "@/features/notices/NoticesListScreen"; import NotificationSettingsScreen from "@/features/settings/NotificationSettingsScreen"; +import PhoneChangeScreen from "@/features/settings/PhoneChangeScreen"; import SettingsScreen from "@/features/settings/SettingsScreen"; // 앱의 모든 라우트는 이 객체 배열 한곳에서 정의한다 — 새 화면은 여기에 라우트를 추가한다. @@ -125,6 +126,15 @@ const routes = [ } satisfies ScreenRouteHandle, path: "/settings/notifications", }, + { + element: , + // 입력칸 하나 + 하단 고정 버튼(Action Area)이 있는 흰 배경 화면(Figma 1799:87220) + handle: { + background: "normal", + hasBottomNav: false, + } satisfies ScreenRouteHandle, + path: "/settings/phone", + }, { element: , // 시트만 있는 화면이라 뒤에 Bottom Nav가 비쳐도 Figma(홈 위에 뜨는 시트)와 같다 diff --git a/src/features/settings/PhoneChangeScreen.tsx b/src/features/settings/PhoneChangeScreen.tsx new file mode 100644 index 0000000..963380f --- /dev/null +++ b/src/features/settings/PhoneChangeScreen.tsx @@ -0,0 +1,128 @@ +import { + ActionArea, + ActionAreaButton, + TextField, + TopNavigationButton, + Typography, +} from "@wanteddev/wds"; +import { IconChevronLeft } from "@wanteddev/wds-icon"; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import ScreenHeader from "@/components/ui/ScreenHeader"; +import ScreenToast from "@/components/ui/ScreenToast"; +import { useScreenHeader } from "@/components/ui/useScreenHeader"; + +// Figma: 전화번호 변경 (nodeId 1799:87220), 입력 중 (1799:87227), 완료 토스트 (1917:93781). +// 입력칸은 Figma `Textinput/Textfield`(445:8591, WDS 공식 문서 링크 확인)라 WDS `TextField`를 +// 그대로 쓴다. 다만 WDS `TextField`는 입력 박스만 그려서, 위 라벨("새 전화번호")과 아래 안내 +// 문구는 Figma 구조대로 형제 요소로 따로 그린다 — WDS `Label`은 required `*` 처리만 더해주는 +// Typography 래퍼라 여기선 쓸 이유가 없다(wds-component-usage.md의 행사 신청 폼 선례와 같다). +// +// "변경하기" 버튼은 번호를 형식에 맞게 다 입력했을 때만 활성화된다(Figma 비활성 #F4F4F5 / +// 활성 #0066FF). 높이 56px은 ActionAreaButton 기본(48px)과 달라 sx로 세로 padding만 보정한다. +// 실제 변경 API가 없어서 누르면 Figma처럼 완료 토스트만 띄운다. +// +// Figma 입력 중 상태(1799:87230)가 "010-1234-5"처럼 하이픈이 끼워진 모습이라 입력하는 대로 +// 하이픈을 넣어준다. 유효 기준은 placeholder와 같은 010 + 8자리다. +const PHONE_PATTERN = /^010-\d{4}-\d{4}$/; + +function formatPhone(value: string): string { + const digits = value.replace(/\D/g, "").slice(0, 11); + if (digits.length <= 3) { + return digits; + } + if (digits.length <= 7) { + return `${digits.slice(0, 3)}-${digits.slice(3)}`; + } + return `${digits.slice(0, 3)}-${digits.slice(3, 7)}-${digits.slice(7)}`; +} + +function PhoneChangeScreen() { + const navigate = useNavigate(); + const [phone, setPhone] = useState(""); + const [toastOpen, setToastOpen] = useState(false); + // 토스트가 떠 있는 중에 다시 저장해도 스크린리더가 재안내하도록 매번 새로 마운트한다 + // (EventsApplicationScreen·BililgeReturnSection과 같은 패턴). + const [toastToken, setToastToken] = useState(0); + + useScreenHeader( + navigate(-1)} + variant="icon" + > + + + } + title="전화번호 변경" + variant="normal" + />, + ); + + const handleSave = () => { + setToastOpen(true); + setToastToken((token) => token + 1); + }; + + return ( +
+
+ + 새 전화번호 + + setPhone(formatPhone(event.target.value))} + placeholder="010-1234-5678" + type="tel" + value={phone} + /> + + 행사 신청 시 사용돼요. + +
+ +
+ + {/* Figma Main Action은 56px인데 ActionAreaButton(size="large")은 48px이라 세로 padding만 보정 */} + + 변경하기 + + + {/* Figma Action Area(110px)의 버튼 아래 34px 중 WDS가 주는 padding 20px을 뺀 14px을 더한다 + (EventsApplicationScreen과 같은 보정) */} +
+
+ + +
+ ); +} + +export default PhoneChangeScreen; From 0bd02747032f862e8852da5ac82d09f3533fa964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Sun, 4 Oct 2026 02:02:11 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20=ED=99=88=20=ED=97=A4=EB=8D=94?= =?UTF-8?q?=EC=97=90=20=EC=84=A4=EC=A0=95=20=EC=A7=84=EC=9E=85=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) --- src/features/home/HomeScreen.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/features/home/HomeScreen.tsx b/src/features/home/HomeScreen.tsx index 62680c7..b8384a5 100644 --- a/src/features/home/HomeScreen.tsx +++ b/src/features/home/HomeScreen.tsx @@ -1,5 +1,5 @@ import { TopNavigationButton } from "@wanteddev/wds"; -import { IconBell, IconSearch } from "@wanteddev/wds-icon"; +import { IconBell, IconSearch, IconSetting } from "@wanteddev/wds-icon"; import { Link, useNavigate } from "react-router-dom"; import chatbotIcon from "@/assets/icons/chat/bot.svg"; import ScreenHeader from "@/components/ui/ScreenHeader"; @@ -17,6 +17,14 @@ function HomeScreen() { title="STREAM" trailing={ <> + {/* Figma 홈 Top Navigation(3147:146294)의 아이콘은 설정·검색·알림 3개 순서다 */} + navigate("/settings")} + variant="icon" + > + + From 2fe57f7d04e52db609263094fb8810ba6e6351f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Sun, 4 Oct 2026 02:02:11 +0900 Subject: [PATCH 05/12] =?UTF-8?q?docs:=20=EC=84=A4=EC=A0=95=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=97=90=EC=84=9C=20=ED=99=95=EC=A0=95=ED=95=9C=20WDS?= =?UTF-8?q?=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=A7=A4=ED=95=91=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5 (1M context) --- docs/conventions/wds-component-usage.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/conventions/wds-component-usage.md b/docs/conventions/wds-component-usage.md index 3f996e7..20f03d0 100644 --- a/docs/conventions/wds-component-usage.md +++ b/docs/conventions/wds-component-usage.md @@ -474,3 +474,23 @@ WDS에 이름이 비슷한 `ProgressIndicator`가 실제로 존재해서(`제외 ### 구역 카드 배경이 화면 배경과 같은 색이라 라우트에 `background: "normal"`이 필요하다 카드 배경이 `Background/Normal/Alternative`(`#f7f7f8`)인데 `ScreenLayout` 기본 배경도 같은 값이라, handle을 안 주면 카드가 배경에 완전히 묻힌다(실제로 처음 렌더에서 카드가 안 보였다). Figma 화면 배경이 흰 면이라 `handle: { background: "normal" }`을 준다. + +## 설정 화면 (설정 3013:116135 / 알림 설정 3013:116182 / 전화번호 변경 1799:87220) + +### 새로 확정된 WDS 매핑 + +| Figma 인스턴스 | 코드 | 확정 근거 | +|---|---|---| +| `Switch/Switch` | `Switch`(`@wanteddev/wds`) | 메인 컴포넌트 Node ID `679:16080` + [공식 문서](https://montage.wanted.co.kr/docs/components/selection-and-input/switch/design). `size="small"`이 Figma와 같은 39×24(실측 일치) | +| `Textinput/Textfield` | `TextField`(`@wanteddev/wds`) | 메인 컴포넌트 Node ID `445:8591` + [공식 문서](https://montage.wanted.co.kr/docs/components/selection-and-input/text-field/design). radius 12px·padding 12px 실측 일치 | +| `Icon/Normal/Setting` | `IconSetting`(`@wanteddev/wds-icon`) | 메인 컴포넌트 Node ID `492:5735`("설정으로 이동할 때 사용합니다") | + +**`TextField`는 입력 박스만 그린다** — Figma `Textinput/Textfield`는 라벨(Heading) + 입력칸 + 안내 문구를 한 컴포넌트로 묶고 있지만, 코드 쪽 `TextField`에는 label·helper 슬롯이 없다. 그래서 "새 전화번호" 라벨과 "행사 신청 시 사용돼요." 안내는 Figma 구조대로 형제 요소로 따로 그린다. WDS `Label`은 required `*`만 더해주는 `Typography` 래퍼라 쓸 이유가 없다(행사 신청 폼 절의 선례와 같은 판단). + +### 설정 행의 `Arrow`는 WDS가 아니다 + +설정 화면 모든 행 끝의 화살표(`445:12456` "Arrow", 8×12)는 `search_design_system`으로 WDS를 뒤져도 같은 이름이 없다(WDS는 `Icon/Normal/Arrow *`·`Icon/Normal/Chevron *` 네이밍). 133번째 줄 "제외됨" 목록의 `Icon/Arrow`와 같은 Stream 전용 아이콘이라 SVG를 받아 `src/assets/icons/settings/arrow-right.svg`로 커밋했다. 색(`#C2C4C8` = `Atomic/Cool Neutral/90`)이 에셋에 구워져 있어 토큰은 새로 추가하지 않았다. + +### 알림 설정의 섹션 구분선은 8px 버전 + +"`Divider(new)`의 8px 버전은 1px 구분선과 다른 별개 패턴" 절과 같은 케이스다 — `
`로 직접 그린다. From d9ea739eb8af4d8b4f13d14059b9fabd82b87d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:01:27 +0900 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20=ED=99=95=EC=9D=B8=20=EB=AA=A8?= =?UTF-8?q?=EB=8B=AC=20=EC=84=A4=EB=AA=85=EC=9D=98=20=EC=A4=84=EB=B0=94?= =?UTF-8?q?=EA=BF=88=EC=9D=B4=20=EB=AC=B4=EC=8B=9C=EB=90=98=EB=8A=94=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Figma 모달 설명은 줄바꿈 위치까지 디자인에 포함돼 있는데 `\n`이 공백으로 접혀서 의도와 다른 위치에서 줄이 바뀌었다. 기존 사용처는 모두 한 줄 문구라 렌더링 변화가 없다. Co-Authored-By: Claude Opus 5 (1M context) --- src/components/ui/ConfirmModal.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/ui/ConfirmModal.tsx b/src/components/ui/ConfirmModal.tsx index 2f9ae8a..baeb144 100644 --- a/src/components/ui/ConfirmModal.tsx +++ b/src/components/ui/ConfirmModal.tsx @@ -165,8 +165,11 @@ function ConfirmModal({ {title} {description && ( + // Figma 모달 설명은 줄바꿈 위치까지 디자인에 포함돼 있어서(예: 탈퇴 확인 모달 + // 3524:152535) `\n`을 그대로 살린다. 기존 사용처는 모두 한 줄 문구라 영향이 없다. Date: Mon, 5 Oct 2026 03:01:38 +0900 Subject: [PATCH 07/12] =?UTF-8?q?refactor:=20=EC=99=84=EB=A3=8C=20?= =?UTF-8?q?=EC=B2=B4=ED=81=AC=20=EB=AA=A8=EC=85=98=EC=9D=84=20=EA=B3=B5?= =?UTF-8?q?=EC=9A=A9=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EB=A1=9C=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 행사 신청 완료·회원 탈퇴 완료·사물함 신청 완료가 모두 같은 Figma 메인 컴포넌트(Circle Check Motion 1712:192849)의 인스턴스다. 타임라인 길이와 반복 설정은 화면마다 다르게 걸려 있지만 키프레임 값이 같아서 보이는 모션이 동일하다. json을 기능별로 복사해 두면 디자이너가 모션을 고칠 때 재export를 몇 번씩 해야 해서, Lottie와 재생 컴포넌트를 각각 common/과 components/ui/로 옮긴다. Co-Authored-By: Claude Opus 5 (1M context) --- docs/conventions/component-convention.md | 2 +- .../lottie/{events => common}/complete-check.json | 0 .../ui/CompleteCheck.tsx} | 14 ++++++++++---- .../events/EventsApplicationCompleteScreen.tsx | 4 ++-- 4 files changed, 13 insertions(+), 7 deletions(-) rename src/assets/lottie/{events => common}/complete-check.json (100%) rename src/{features/events/components/EventsCompleteCheck.tsx => components/ui/CompleteCheck.tsx} (63%) diff --git a/docs/conventions/component-convention.md b/docs/conventions/component-convention.md index 1a459b7..ef285ab 100644 --- a/docs/conventions/component-convention.md +++ b/docs/conventions/component-convention.md @@ -79,7 +79,7 @@ Figma Motion(키프레임 타임라인)이 붙은 요소는 키프레임을 손 - **export**: Figma Dev mode에는 Lottie export가 없다. LottieFiles 플러그인에서 **키프레임이 붙은 프레임을 선택**하고 Export 탭 → Preview 확인 후 json으로 받는다. 화면 전체 프레임이 아니라 모션 프레임을 골라야 한다(예: `행사 로딩 1133:43453`이 아니라 `Loading / Document Review 1133:44260`). - hidden 레이어와 boolean 연산(union/intersect/exclude)은 export에서 빠진다. Preview에 안 보이면 이걸 먼저 의심한다. -- **파일 위치**: `src/assets/lottie/<기능>/<이름>.json`. 이미지·아이콘과 같은 이유로 저장소에 커밋한다. +- **파일 위치**: `src/assets/lottie/<기능>/<이름>.json`. 이미지·아이콘과 같은 이유로 저장소에 커밋한다. 여러 기능이 같은 Figma 메인 컴포넌트의 모션을 쓰면 `src/assets/lottie/common/`에 한 벌만 두고, 재생 컴포넌트도 `src/components/ui/`로 올린다(예: 완료 체크 `Circle Check Motion 1712:192849` → `common/complete-check.json` + `CompleteCheck.tsx`). json을 기능별로 복사하면 디자이너가 모션을 고칠 때 재export를 몇 번씩 해야 한다. - **컴포넌트**: `lottie-react`의 **`LottieLight`**를 쓴다. `Lottie`(풀 빌드)는 canvas·HTML 렌더러와 `eval` 기반 표현식 엔진까지 끌고 와서 번들이 224KB(gzip 43KB) 커지고, 출하 번들에 `eval`이 남아 WebView CSP에서 깨질 수 있다. Figma에서 나온 Lottie는 표현식을 쓰지 않으므로 SVG 렌더러만 있으면 된다. - **문구는 Lottie에 넣지 않는다**: export 원본에 텍스트가 포함돼 있으면 그 레이어를 빼고 컴포지션을 일러스트 경계로 자른 뒤, 문구는 `Typography`로 따로 그린다. Lottie 안의 텍스트는 벡터 도형이라 스크린리더가 못 읽고 타이포 토큰도 안 따라간다. **어떤 레이어를 왜 뺐는지와 잘라낸 크기를 컴포넌트 주석에 남긴다** — 재export 때 같은 가공을 다시 해야 한다. - **모션 줄이기**: `usePrefersReducedMotion()`으로 판단해 `autoplay={false}` + `segment={[n, n + 1]}`로 다 그려진 프레임 한 장만 세운다. `autoplay`를 켠 채로 두면 lottie-react가 알아서 막긴 하지만 개발 콘솔에 경고를 남기므로 우리가 먼저 끈다. CSS transition으로 끌 수 있는 자리(`ConfirmModal` 등)는 Tailwind `motion-reduce:` 변형을 그대로 쓴다. diff --git a/src/assets/lottie/events/complete-check.json b/src/assets/lottie/common/complete-check.json similarity index 100% rename from src/assets/lottie/events/complete-check.json rename to src/assets/lottie/common/complete-check.json diff --git a/src/features/events/components/EventsCompleteCheck.tsx b/src/components/ui/CompleteCheck.tsx similarity index 63% rename from src/features/events/components/EventsCompleteCheck.tsx rename to src/components/ui/CompleteCheck.tsx index 6c0dd3f..7610210 100644 --- a/src/features/events/components/EventsCompleteCheck.tsx +++ b/src/components/ui/CompleteCheck.tsx @@ -1,19 +1,25 @@ import { LottieLight } from "lottie-react"; -import completeCheck from "@/assets/lottie/events/complete-check.json"; +import completeCheck from "@/assets/lottie/common/complete-check.json"; import { usePrefersReducedMotion } from "@/hooks/usePrefersReducedMotion"; -// Figma: Circle Check Motion (nodeId 1712:192849), 완료 화면에 놓인 인스턴스는 1712:192851. +// Figma: Circle Check Motion (nodeId 1712:192849, 설명 "System Check"). // 원이 튀어올랐다 자리를 잡고(back-out) 체크 선이 그려진다(path trim). 타임라인은 2초 1회 // 재생인데 실제 움직임은 앞 0.55초에 끝나고 나머지는 멈춰 있는 구간이다. // +// 완료 화면들이 모두 이 메인 컴포넌트의 인스턴스라 공용으로 둔다 — 행사 신청 완료 +// (인스턴스 1712:192851), 회원 탈퇴 완료(3524:152512), 사물함 신청 완료(1737:218066). +// 셋의 타임라인 길이·반복 설정은 Figma에서 제각각이지만(2초 loop / 1초 once / 2초 once) +// 키프레임 값은 동일해서(원 450ms, 체크 550ms, 같은 back-out 이징) 실제로 보이는 모션이 +// 같다. 그래서 한 벌만 두고 1회 재생으로 통일한다. +// // 모션은 Figma LottieFiles 플러그인으로 뽑은 Lottie를 그대로 재생한다 — 자세한 규칙은 // component-convention.md "모션 (Lottie)" 참고. 이 파일은 export 원본을 가공 없이 쓴다. // 모션 줄이기를 켠 사용자에게는 다 그려진 한 프레임만 세워둔다. 33프레임에서 체크가 완성된다. const DRAWN_SEGMENT = [33, 34] as const; -function EventsCompleteCheck() { +function CompleteCheck() { const shouldReduceMotion = usePrefersReducedMotion(); return ( @@ -30,4 +36,4 @@ function EventsCompleteCheck() { ); } -export default EventsCompleteCheck; +export default CompleteCheck; diff --git a/src/features/events/EventsApplicationCompleteScreen.tsx b/src/features/events/EventsApplicationCompleteScreen.tsx index 3d3cd37..61fd27e 100644 --- a/src/features/events/EventsApplicationCompleteScreen.tsx +++ b/src/features/events/EventsApplicationCompleteScreen.tsx @@ -7,9 +7,9 @@ import { import { IconClose } from "@wanteddev/wds-icon"; import { useNavigate } from "react-router-dom"; +import CompleteCheck from "@/components/ui/CompleteCheck"; import ScreenHeader from "@/components/ui/ScreenHeader"; import { useScreenHeader } from "@/components/ui/useScreenHeader"; -import EventsCompleteCheck from "@/features/events/components/EventsCompleteCheck"; import EventsSummaryCard from "@/features/events/components/EventsSummaryCard"; import { EVENTS_APPLICATION } from "@/features/events/constants/eventsApplication"; @@ -42,7 +42,7 @@ function EventsApplicationCompleteScreen() {
- +
Date: Mon, 5 Oct 2026 03:02:06 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=ED=99=95=EC=9D=B8=20=ED=99=94=EB=A9=B4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 안내 카드 네 장이 모두 같은 Figma 컴포넌트(Delete Accoount Card 3491:151566)의 인스턴스라 WithdrawalNoticeCard 하나로 묶고 문구는 상수로 분리했다. 동의 체크박스를 켜야 탈퇴하기가 활성화되고, 누르면 확인 모달이 뜬다. WDS Button은 color가 primary/assistive뿐이라 Figma의 Status/Negative는 sx로 배경만 덮는다. Co-Authored-By: Claude Opus 5 (1M context) --- src/app/router.tsx | 11 ++ src/features/settings/SettingsScreen.tsx | 10 +- src/features/settings/WithdrawalScreen.tsx | 131 ++++++++++++++++++ .../components/WithdrawalNoticeCard.tsx | 38 +++++ src/features/settings/constants/settings.ts | 35 +++++ 5 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 src/features/settings/WithdrawalScreen.tsx create mode 100644 src/features/settings/components/WithdrawalNoticeCard.tsx diff --git a/src/app/router.tsx b/src/app/router.tsx index 572519f..1d6c15c 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -23,6 +23,7 @@ import NoticesListScreen from "@/features/notices/NoticesListScreen"; import NotificationSettingsScreen from "@/features/settings/NotificationSettingsScreen"; import PhoneChangeScreen from "@/features/settings/PhoneChangeScreen"; import SettingsScreen from "@/features/settings/SettingsScreen"; +import WithdrawalScreen from "@/features/settings/WithdrawalScreen"; // 앱의 모든 라우트는 이 객체 배열 한곳에서 정의한다 — 새 화면은 여기에 라우트를 추가한다. // satisfies로 선언 시점에 RouteObject 형태를 검사한다. @@ -135,6 +136,16 @@ const routes = [ } satisfies ScreenRouteHandle, path: "/settings/phone", }, + { + element: , + // 안내 카드가 회색이라 화면은 흰 면이어야 한다(Figma 3524:152473). + // 하단 고정 버튼(Action Area)이 있어 Bottom Nav는 없다. + handle: { + background: "normal", + hasBottomNav: false, + } satisfies ScreenRouteHandle, + path: "/settings/withdraw", + }, { element: , // 시트만 있는 화면이라 뒤에 Bottom Nav가 비쳐도 Figma(홈 위에 뜨는 시트)와 같다 diff --git a/src/features/settings/SettingsScreen.tsx b/src/features/settings/SettingsScreen.tsx index 5ae3717..10ae40f 100644 --- a/src/features/settings/SettingsScreen.tsx +++ b/src/features/settings/SettingsScreen.tsx @@ -19,8 +19,8 @@ import { // // 의견 보내기·개인정보 처리방침은 화면을 따로 만들지 않고 구글폼으로 연결한다(폼 주소가 아직 // 없어서 SETTINGS_EXTERNAL_LINKS가 빈 문자열이면 아무 동작도 하지 않는다). -// 회원 탈퇴는 Figma에 누른 뒤 화면·모달이 없어서 행만 그리고 동작은 비워뒀다 — 로그아웃만 -// 확인 모달(1799:87183)이 그려져 있어 공용 ConfirmModal(tone="negative")로 붙였다. +// 로그아웃은 확인 모달(1799:87183)을 공용 ConfirmModal(tone="negative")로 붙였고, +// 회원 탈퇴는 별도 화면(3524:152473)으로 이동한다. function SettingsScreen() { const navigate = useNavigate(); const [logoutOpen, setLogoutOpen] = useState(false); @@ -99,7 +99,11 @@ function SettingsScreen() { onClick={() => setLogoutOpen(true)} tone="alternative" /> - + navigate("/settings/withdraw")} + tone="negative" + />
diff --git a/src/features/settings/WithdrawalScreen.tsx b/src/features/settings/WithdrawalScreen.tsx new file mode 100644 index 0000000..a23bd8e --- /dev/null +++ b/src/features/settings/WithdrawalScreen.tsx @@ -0,0 +1,131 @@ +import { + ActionArea, + ActionAreaButton, + Checkbox, + TopNavigationButton, + Typography, +} from "@wanteddev/wds"; +import { IconChevronLeft } from "@wanteddev/wds-icon"; +import { useId, useState } from "react"; +import { useNavigate } from "react-router-dom"; + +import ConfirmModal from "@/components/ui/ConfirmModal"; +import ScreenHeader from "@/components/ui/ScreenHeader"; +import { useScreenHeader } from "@/components/ui/useScreenHeader"; +import WithdrawalNoticeCard from "@/features/settings/components/WithdrawalNoticeCard"; +import { WITHDRAWAL_NOTICES } from "@/features/settings/constants/settings"; + +// Figma: 탈퇴 확인 페이지 (nodeId 3524:152473), 체크 상태 (3524:152489), 확인 모달 (3524:152517). +// 안내 문구 4장은 같은 컴포넌트의 인스턴스라 WithdrawalNoticeCard + WITHDRAWAL_NOTICES로 분리했다. +// +// 동의 체크박스를 켜야 "탈퇴하기"가 활성화되고(Figma 비활성 Interaction/Disable #F4F4F5 → +// 활성 Status/Negative #FF4242), 누르면 확인 모달이 뜬다. WDS Button은 color가 +// primary/assistive뿐이라 빨간 버튼은 ConfirmModal과 같은 방식으로 sx로 배경을 덮는다. +// +// 모달에서 탈퇴를 확정했을 때 갈 완료 화면은 아직 없다 — 지금은 모달만 닫는다. +function WithdrawalScreen() { + const navigate = useNavigate(); + const consentId = useId(); + const [agreed, setAgreed] = useState(false); + const [confirmOpen, setConfirmOpen] = useState(false); + + useScreenHeader( + navigate(-1)} + variant="icon" + > + + + } + variant="normal" + />, + ); + + return ( +
+ {/* Figma Withdrawal Content(3524:152476): 헤더 아래 12px, 제목과 목록 사이 16px */} +
+ + 탈퇴하기 전에 꼭 +
+ 확인해 주세요 +
+ +
    + {WITHDRAWAL_NOTICES.map((notice) => ( + + ))} +
+
+ +
+
+ + +
+ + + {/* Figma Main Action은 56px인데 ActionAreaButton(size="large")은 48px이라 세로 padding을 + 보정하고, WDS Button엔 negative 색이 없어 배경만 status/negative로 덮는다. */} + setConfirmOpen(true)} + sx={ + agreed + ? { + backgroundColor: "var(--color-status-negative)", + paddingBlock: "16px", + } + : { paddingBlock: "16px" } + } + > + 탈퇴하기 + + + {/* Figma Action Area(110px)의 버튼 아래 34px 중 WDS가 주는 padding 20px을 뺀 14px */} +
+
+ + setConfirmOpen(false)} + onConfirm={() => setConfirmOpen(false)} + open={confirmOpen} + title="정말 탈퇴하시겠어요?" + tone="negative" + /> +
+ ); +} + +export default WithdrawalScreen; diff --git a/src/features/settings/components/WithdrawalNoticeCard.tsx b/src/features/settings/components/WithdrawalNoticeCard.tsx new file mode 100644 index 0000000..f877f14 --- /dev/null +++ b/src/features/settings/components/WithdrawalNoticeCard.tsx @@ -0,0 +1,38 @@ +import { Typography } from "@wanteddev/wds"; + +interface WithdrawalNoticeCardProps { + title: string; + description: string; +} + +// Figma: 탈퇴 확인 페이지의 Withdrawal Notice Card (메인 컴포넌트 nodeId 3491:151566 +// "Delete Accoount Card"). 네 장이 모두 같은 컴포넌트의 인스턴스고 문구만 다르다. +// 회색 카드(Background/Normal/Alternative)라 설정 화면의 흰 카드(SettingsSection)와는 +// 배경이 반대다 — 같은 카드로 묶지 않고 따로 둔다. +function WithdrawalNoticeCard({ + title, + description, +}: WithdrawalNoticeCardProps) { + return ( +
  • + + {title} + + + {description} + +
  • + ); +} + +export default WithdrawalNoticeCard; diff --git a/src/features/settings/constants/settings.ts b/src/features/settings/constants/settings.ts index ad04125..4ae425c 100644 --- a/src/features/settings/constants/settings.ts +++ b/src/features/settings/constants/settings.ts @@ -49,3 +49,38 @@ export const NOTIFICATION_CATEGORIES: NotificationCategory[] = [ title: "사물함 알림", }, ]; + +interface WithdrawalNotice { + id: string; + title: string; + description: string; +} + +// Figma: 탈퇴 확인 페이지 Withdrawal Notice List(nodeId 3524:152481) 순서·문구 그대로. +// 렌더마다 배열을 다시 만들지 않도록 모듈 스코프 상수로 둔다. +export const WITHDRAWAL_NOTICES: WithdrawalNotice[] = [ + { + description: + "탈퇴 시 계정 및 회원 정보가 삭제되며, 삭제된 정보는 다시 복구할 수 없어요.", + id: "account-deletion", + title: "회원 정보가 삭제돼요", + }, + { + description: + "신청 중인 행사나 이용 중인 복지 서비스가 있다면 탈퇴 후 이용이 어려울 수 있어요.", + id: "active-services", + title: "이용 내역을 확인해 주세요", + }, + { + description: + "탈퇴 전에 작성한 열린피드백은 탈퇴 후에도 삭제되지 않고 유지돼요.", + id: "feedback-retained", + title: "작성한 열린피드백은 남아 있어요", + }, + { + description: + "탈퇴 후 재가입하더라도 기존 계정의 활동 및 이용 내역은 다시 불러올 수 없어요.", + id: "records-not-restored", + title: "다시 가입해도 이전 기록은 복구되지 않아요", + }, +]; From 0eb4a2749a79fec7317091e86ccf8a98bb8ad881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Mon, 5 Oct 2026 03:02:30 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=20=ED=83=88?= =?UTF-8?q?=ED=87=B4=20=EC=99=84=EB=A3=8C=20=ED=99=94=EB=A9=B4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 행사 신청 완료 화면과 같은 뼈대지만 신청 정보 카드가 없고 버튼이 하나뿐이라, 표본이 둘뿐인 지금은 화면 껍데기까지 묶지 않고 체크 모션만 공유한다. 탈퇴 후에는 로그아웃 상태라 로그인/온보딩으로 나가야 하지만 그 화면이 아직 없어서 홈으로 내보낸다. 인증을 붙일 때 EXIT_PATH만 바꾸면 된다. Co-Authored-By: Claude Opus 5 (1M context) --- src/app/router.tsx | 10 +++ .../settings/WithdrawalCompleteScreen.tsx | 83 +++++++++++++++++++ src/features/settings/WithdrawalScreen.tsx | 8 +- 3 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/features/settings/WithdrawalCompleteScreen.tsx diff --git a/src/app/router.tsx b/src/app/router.tsx index 1d6c15c..f2cc5ee 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -23,6 +23,7 @@ import NoticesListScreen from "@/features/notices/NoticesListScreen"; import NotificationSettingsScreen from "@/features/settings/NotificationSettingsScreen"; import PhoneChangeScreen from "@/features/settings/PhoneChangeScreen"; import SettingsScreen from "@/features/settings/SettingsScreen"; +import WithdrawalCompleteScreen from "@/features/settings/WithdrawalCompleteScreen"; import WithdrawalScreen from "@/features/settings/WithdrawalScreen"; // 앱의 모든 라우트는 이 객체 배열 한곳에서 정의한다 — 새 화면은 여기에 라우트를 추가한다. @@ -146,6 +147,15 @@ const routes = [ } satisfies ScreenRouteHandle, path: "/settings/withdraw", }, + { + element: , + // 완료 화면 — 하단 탭 없이 흰 배경 전체 화면이다(Figma 3524:152505) + handle: { + background: "normal", + hasBottomNav: false, + } satisfies ScreenRouteHandle, + path: "/settings/withdraw/complete", + }, { element: , // 시트만 있는 화면이라 뒤에 Bottom Nav가 비쳐도 Figma(홈 위에 뜨는 시트)와 같다 diff --git a/src/features/settings/WithdrawalCompleteScreen.tsx b/src/features/settings/WithdrawalCompleteScreen.tsx new file mode 100644 index 0000000..0132d85 --- /dev/null +++ b/src/features/settings/WithdrawalCompleteScreen.tsx @@ -0,0 +1,83 @@ +import { + ActionArea, + ActionAreaButton, + TopNavigationButton, + Typography, +} from "@wanteddev/wds"; +import { IconClose } from "@wanteddev/wds-icon"; +import { useNavigate } from "react-router-dom"; + +import CompleteCheck from "@/components/ui/CompleteCheck"; +import ScreenHeader from "@/components/ui/ScreenHeader"; +import { useScreenHeader } from "@/components/ui/useScreenHeader"; + +// 탈퇴하면 로그아웃된 상태라 로그인/온보딩으로 나가야 하지만 아직 그 화면이 없다 — +// 인증을 붙일 때 이 상수만 바꾼다. 그때까지는 행사 신청 완료 화면과 같이 홈으로 내보낸다. +const EXIT_PATH = "/"; + +// Figma: 탈퇴 완료 페이지 (nodeId 3524:152505) +// 행사 신청 완료 화면(1712:192283)과 같은 뼈대다 — 체크 모션 + 제목/부제, 하단 고정 버튼. +// 다른 점은 신청 정보 카드가 없고 버튼이 "확인" 하나라는 것뿐이라, 공용 컴포넌트로 묶기엔 +// 아직 표본이 둘뿐이고 Action Area 구성이 달라서 화면별로 따로 조립한다. +function WithdrawalCompleteScreen() { + const navigate = useNavigate(); + + useScreenHeader( + navigate(EXIT_PATH, { replace: true })} + variant="icon" + > + + + } + variant="normal" + />, + ); + + return ( +
    + {/* Figma Withdrawal Complete Header(3524:152509): 헤더와 메시지 사이 104px */} +
    + +
    + + 회원 탈퇴가 완료되었어요 + + + 그동안 stream과 함께해 주셔서 감사해요. + +
    +
    + +
    + + {/* WDS는 버튼 세로 padding을 12px(48px)로 주는데 Figma Main Action은 16px(56px)이다 */} + navigate(EXIT_PATH, { replace: true })} + sx={{ padding: "16px 28px" }} + > + 확인 + + + {/* 다른 완료 화면과 같은 이유의 14px — WDS ActionArea 아래 padding 20px에 Home Bar 여백을 더해 34px */} +
    +
    +
    + ); +} + +export default WithdrawalCompleteScreen; diff --git a/src/features/settings/WithdrawalScreen.tsx b/src/features/settings/WithdrawalScreen.tsx index a23bd8e..fe130cf 100644 --- a/src/features/settings/WithdrawalScreen.tsx +++ b/src/features/settings/WithdrawalScreen.tsx @@ -22,7 +22,8 @@ import { WITHDRAWAL_NOTICES } from "@/features/settings/constants/settings"; // 활성 Status/Negative #FF4242), 누르면 확인 모달이 뜬다. WDS Button은 color가 // primary/assistive뿐이라 빨간 버튼은 ConfirmModal과 같은 방식으로 sx로 배경을 덮는다. // -// 모달에서 탈퇴를 확정했을 때 갈 완료 화면은 아직 없다 — 지금은 모달만 닫는다. +// 모달에서 탈퇴를 확정하면 탈퇴 완료 페이지(3524:152505)로 넘어간다. 실제 탈퇴 API 호출은 +// 연동할 때 onConfirm에 붙인다. function WithdrawalScreen() { const navigate = useNavigate(); const consentId = useId(); @@ -119,7 +120,10 @@ function WithdrawalScreen() { "탈퇴하면 삭제된 회원 정보와\n이용 내역은 다시 복구할 수 없어요." } onCancel={() => setConfirmOpen(false)} - onConfirm={() => setConfirmOpen(false)} + // 탈퇴를 확정하면 되돌아올 화면이 아니라 완료 화면으로 치환한다 + onConfirm={() => + navigate("/settings/withdraw/complete", { replace: true }) + } open={confirmOpen} title="정말 탈퇴하시겠어요?" tone="negative" From cd77dfdf921ec6657bb22fed97e2f71c9f9c44c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:40:59 +0900 Subject: [PATCH 10/12] =?UTF-8?q?refactor:=20=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20=ED=99=94=EB=A9=B4=EC=9D=84=20=EA=B3=B5?= =?UTF-8?q?=EC=9A=A9=20ResultScreen=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main에 신청 결과 화면 공용 컴포넌트(ResultScreen)가 들어왔다. 버튼이 하나인 경우(secondaryAction 생략)와 체크 모션용 8px 간격(illustrationGap)을 이미 지원해서 탈퇴 완료 화면이 그대로 들어맞는다. 직접 조립한 마크업을 걷어낸다. 렌더링 결과는 같다 — 헤더↔체크 104px, 체크 72x72, 체크↔제목 8px, 제목↔부제 4px, 버튼 335x56으로 Figma(3524:152505)와 실측 일치를 확인했다. Co-Authored-By: Claude Opus 5 (1M context) --- .../settings/WithdrawalCompleteScreen.tsx | 80 +++---------------- 1 file changed, 13 insertions(+), 67 deletions(-) diff --git a/src/features/settings/WithdrawalCompleteScreen.tsx b/src/features/settings/WithdrawalCompleteScreen.tsx index 0132d85..d882f75 100644 --- a/src/features/settings/WithdrawalCompleteScreen.tsx +++ b/src/features/settings/WithdrawalCompleteScreen.tsx @@ -1,82 +1,28 @@ -import { - ActionArea, - ActionAreaButton, - TopNavigationButton, - Typography, -} from "@wanteddev/wds"; -import { IconClose } from "@wanteddev/wds-icon"; import { useNavigate } from "react-router-dom"; import CompleteCheck from "@/components/ui/CompleteCheck"; -import ScreenHeader from "@/components/ui/ScreenHeader"; -import { useScreenHeader } from "@/components/ui/useScreenHeader"; +import ResultScreen from "@/components/ui/ResultScreen"; // 탈퇴하면 로그아웃된 상태라 로그인/온보딩으로 나가야 하지만 아직 그 화면이 없다 — -// 인증을 붙일 때 이 상수만 바꾼다. 그때까지는 행사 신청 완료 화면과 같이 홈으로 내보낸다. +// 인증을 붙일 때 이 상수만 바꾼다. 그때까지는 다른 결과 화면과 같이 홈으로 내보낸다. const EXIT_PATH = "/"; // Figma: 탈퇴 완료 페이지 (nodeId 3524:152505) -// 행사 신청 완료 화면(1712:192283)과 같은 뼈대다 — 체크 모션 + 제목/부제, 하단 고정 버튼. -// 다른 점은 신청 정보 카드가 없고 버튼이 "확인" 하나라는 것뿐이라, 공용 컴포넌트로 묶기엔 -// 아직 표본이 둘뿐이고 Action Area 구성이 달라서 화면별로 따로 조립한다. +// 신청 결과 화면과 같은 뼈대라 ResultScreen을 쓴다. 버튼이 "확인" 하나뿐이라 +// secondaryAction을 주지 않고, 체크 모션이 붙는 완료 화면이라 간격은 8px이다. function WithdrawalCompleteScreen() { const navigate = useNavigate(); - - useScreenHeader( - navigate(EXIT_PATH, { replace: true })} - variant="icon" - > - - - } - variant="normal" - />, - ); + const exit = () => navigate(EXIT_PATH, { replace: true }); return ( -
    - {/* Figma Withdrawal Complete Header(3524:152509): 헤더와 메시지 사이 104px */} -
    - -
    - - 회원 탈퇴가 완료되었어요 - - - 그동안 stream과 함께해 주셔서 감사해요. - -
    -
    - -
    - - {/* WDS는 버튼 세로 padding을 12px(48px)로 주는데 Figma Main Action은 16px(56px)이다 */} - navigate(EXIT_PATH, { replace: true })} - sx={{ padding: "16px 28px" }} - > - 확인 - - - {/* 다른 완료 화면과 같은 이유의 14px — WDS ActionArea 아래 padding 20px에 Home Bar 여백을 더해 34px */} -
    -
    -
    + } + illustrationGap={8} + onClose={exit} + primaryAction={{ label: "확인", onClick: exit }} + title="회원 탈퇴가 완료되었어요" + /> ); } From 318f10ada923484bca1af3dbb34a5e58c28f2562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Tue, 13 Oct 2026 14:27:12 +0900 Subject: [PATCH 11/12] =?UTF-8?q?refactor:=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=ED=99=94=EB=A9=B4=EC=9D=84=20=EC=95=8C?= =?UTF-8?q?=EB=A6=BC=20=EA=B8=B0=EB=8A=A5=20=EC=AA=BD=20=ED=95=9C=20?= =?UTF-8?q?=EB=B2=8C=EB=A1=9C=20=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이 브랜치가 머지해 온 main이 #108(알림 화면) 머지 전이라 작업 중에는 안 보였는데, main에는 이미 같은 화면이 있다. 카테고리 4종의 문구·순서·초기값과 "전체 알림"을 파생시키는 방식까지 그대로 겹친다. Figma도 같은 화면을 설정 섹션(3524:152367)과 알림 섹션(3628:110837)에 복제해 뒀지만 화면은 하나여야 한다. 설정의 "알림 설정" 행이 알림 쪽 /notifications/settings로 들어가게 하고, 이 브랜치가 만든 복제본을 지운다. - NotificationSettingsScreen·NotificationToggleRow 삭제 - /settings/notifications 라우트 삭제 - NOTIFICATION_CATEGORIES 삭제 — 카테고리 문구·초기값은 features/notifications/constants/notifications.ts가 단일 출처다 --- docs/conventions/wds-component-usage.md | 6 +- src/app/router.tsx | 16 +--- .../settings/NotificationSettingsScreen.tsx | 95 ------------------- src/features/settings/SettingsScreen.tsx | 6 +- .../components/NotificationToggleRow.tsx | 52 ---------- src/features/settings/constants/settings.ts | 34 ------- 6 files changed, 13 insertions(+), 196 deletions(-) delete mode 100644 src/features/settings/NotificationSettingsScreen.tsx delete mode 100644 src/features/settings/components/NotificationToggleRow.tsx diff --git a/docs/conventions/wds-component-usage.md b/docs/conventions/wds-component-usage.md index c46a4fa..57e93dc 100644 --- a/docs/conventions/wds-component-usage.md +++ b/docs/conventions/wds-component-usage.md @@ -666,6 +666,8 @@ Figma 초기 상태가 전체 꺼짐 + 빌릴게만 켜짐이라 "전체"는 독 설정 화면 모든 행 끝의 화살표(`445:12456` "Arrow", 8×12)는 `search_design_system`으로 WDS를 뒤져도 같은 이름이 없다(WDS는 `Icon/Normal/Arrow *`·`Icon/Normal/Chevron *` 네이밍). 133번째 줄 "제외됨" 목록의 `Icon/Arrow`와 같은 Stream 전용 아이콘이라 SVG를 받아 `src/assets/icons/settings/arrow-right.svg`로 커밋했다. 색(`#C2C4C8` = `Atomic/Cool Neutral/90`)이 에셋에 구워져 있어 토큰은 새로 추가하지 않았다. -### 알림 설정의 섹션 구분선은 8px 버전 +### 알림 설정 화면은 설정이 아니라 알림 기능이 갖는다 -"`Divider(new)`의 8px 버전은 1px 구분선과 다른 별개 패턴" 절과 같은 케이스다 — `
    `로 직접 그린다. +Figma는 같은 알림 설정 화면을 **설정 섹션(`3524:152367`)과 알림 섹션(`3628:110837`) 두 군데에 복제**해 뒀다(노드 구조·좌표·문구가 완전히 같다). 디자인이 둘이라고 화면을 둘 만들면 안 된다 — 코드는 `features/notifications/NotificationsSettingsScreen`(`/notifications/settings`) 한 벌만 두고, 설정 화면의 "알림 설정" 행과 알림 목록 헤더의 설정 버튼이 **같은 경로로** 들어간다. 카테고리 문구·초기값도 `features/notifications/constants/notifications.ts`가 단일 출처다. + +섹션 구분선(8px `Divider(new)`)과 `Switch` 매핑은 위 "알림" 절에 이미 적어 뒀다. diff --git a/src/app/router.tsx b/src/app/router.tsx index cf2b8ee..d5ccdf4 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -30,7 +30,6 @@ import NoticesListScreen from "@/features/notices/NoticesListScreen"; import NotificationsScreen from "@/features/notifications/NotificationsScreen"; import NotificationsSettingsScreen from "@/features/notifications/NotificationsSettingsScreen"; import SearchScreen from "@/features/search/SearchScreen"; -import NotificationSettingsScreen from "@/features/settings/NotificationSettingsScreen"; import PhoneChangeScreen from "@/features/settings/PhoneChangeScreen"; import SettingsScreen from "@/features/settings/SettingsScreen"; import WithdrawalCompleteScreen from "@/features/settings/WithdrawalCompleteScreen"; @@ -131,22 +130,15 @@ const routes = [ { element: , // 흰 카드가 회색 배경 위에 쌓이는 화면이라 background는 기본값(alternative)을 쓴다. - // 설정은 뒤로가기로만 빠져나가는 흐름이라 Bottom Nav를 안 보여준다(Figma 3013:116135). + // 설정은 뒤로가기로만 빠져나가는 흐름이라 Bottom Nav를 안 보여준다(Figma 3524:152322). + // 설정의 "알림 설정" 행은 알림 화면이 이미 가진 /notifications/settings로 보낸다 — + // Figma는 설정·알림 두 섹션에 같은 화면을 복제해 뒀지만 코드에서는 한 벌만 둔다. handle: { hasBottomNav: false } satisfies ScreenRouteHandle, path: "/settings", }, - { - element: , - // 행이 배경까지 흰 면이고 섹션 구분만 8px 회색 띠로 하는 화면이다(Figma 3013:116182) - handle: { - background: "normal", - hasBottomNav: false, - } satisfies ScreenRouteHandle, - path: "/settings/notifications", - }, { element: , - // 입력칸 하나 + 하단 고정 버튼(Action Area)이 있는 흰 배경 화면(Figma 1799:87220) + // 입력칸 하나 + 하단 고정 버튼(Action Area)이 있는 흰 배경 화면(Figma 3524:152450) handle: { background: "normal", hasBottomNav: false, diff --git a/src/features/settings/NotificationSettingsScreen.tsx b/src/features/settings/NotificationSettingsScreen.tsx deleted file mode 100644 index 4b4a228..0000000 --- a/src/features/settings/NotificationSettingsScreen.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { TopNavigationButton } from "@wanteddev/wds"; -import { IconChevronLeft } from "@wanteddev/wds-icon"; -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; - -import ScreenHeader from "@/components/ui/ScreenHeader"; -import { useScreenHeader } from "@/components/ui/useScreenHeader"; -import NotificationToggleRow from "@/features/settings/components/NotificationToggleRow"; -import { - NOTIFICATION_CATEGORIES, - type NotificationCategoryId, -} from "@/features/settings/constants/settings"; - -// Figma 기본값: 전체 알림 off, 카테고리는 빌릴게만 on (3013:116191, 3013:116199) -const DEFAULT_CATEGORY_STATE: Record = { - bililge: true, - board: false, - events: false, - lockers: false, -}; - -// Figma: 알림 설정 (nodeId 3013:116182) — 전체 알림 행, 8px 두꺼운 구분선, 카테고리 4개. -// 구분선은 Figma `Divider(new)`의 8px 버전이라 1px 헤어라인(WDS Divider)과 다른 패턴이다 -// (wds-component-usage.md "Divider(new)의 8px 버전은 1px 구분선과 다른 별개 패턴" 참고). -// -// 전체 알림은 카테고리를 한 번에 켜고 끄는 마스터 스위치다. 그래서 자체 state를 따로 두지 않고 -// "카테고리가 전부 켜져 있는가"로 계산한다 — 이러면 카테고리를 하나라도 끄는 순간 전체 알림도 -// 같이 꺼져서 두 값이 어긋날 일이 없다. Figma 기본 상태(빌릴게만 on → 전체 알림 off)도 이 -// 계산 결과와 그대로 맞는다. on/off 저장은 API 연동 전이라 화면 state로만 둔다. -function NotificationSettingsScreen() { - const navigate = useNavigate(); - const [categoryEnabled, setCategoryEnabled] = useState( - DEFAULT_CATEGORY_STATE, - ); - const allEnabled = NOTIFICATION_CATEGORIES.every( - (category) => categoryEnabled[category.id], - ); - - const handleGlobalChange = (checked: boolean) => { - setCategoryEnabled((prev) => { - const next = { ...prev }; - for (const category of NOTIFICATION_CATEGORIES) { - next[category.id] = checked; - } - return next; - }); - }; - - useScreenHeader( - navigate(-1)} - variant="icon" - > - - - } - title="알림 설정" - variant="normal" - />, - ); - - return ( -
    - - -
    - -
    - {NOTIFICATION_CATEGORIES.map((category) => ( - - setCategoryEnabled((prev) => ({ - ...prev, - [category.id]: checked, - })) - } - title={category.title} - /> - ))} -
    -
    - ); -} - -export default NotificationSettingsScreen; diff --git a/src/features/settings/SettingsScreen.tsx b/src/features/settings/SettingsScreen.tsx index 10ae40f..91e8120 100644 --- a/src/features/settings/SettingsScreen.tsx +++ b/src/features/settings/SettingsScreen.tsx @@ -6,6 +6,7 @@ import { useNavigate } from "react-router-dom"; import ConfirmModal from "@/components/ui/ConfirmModal"; import ScreenHeader from "@/components/ui/ScreenHeader"; import { useScreenHeader } from "@/components/ui/useScreenHeader"; +import { NOTIFICATIONS_SETTINGS_PATH } from "@/features/notifications/constants/notifications"; import SettingsRow from "@/features/settings/components/SettingsRow"; import SettingsSection from "@/features/settings/components/SettingsSection"; import { @@ -21,6 +22,9 @@ import { // 없어서 SETTINGS_EXTERNAL_LINKS가 빈 문자열이면 아무 동작도 하지 않는다). // 로그아웃은 확인 모달(1799:87183)을 공용 ConfirmModal(tone="negative")로 붙였고, // 회원 탈퇴는 별도 화면(3524:152473)으로 이동한다. +// +// "알림 설정"은 알림 화면이 이미 가진 화면으로 보낸다 — Figma가 설정 섹션(3524:152367)과 알림 +// 섹션(3628:110837)에 같은 화면을 복제해 뒀을 뿐, 진입점만 둘이고 화면은 하나다. function SettingsScreen() { const navigate = useNavigate(); const [logoutOpen, setLogoutOpen] = useState(false); @@ -73,7 +77,7 @@ function SettingsScreen() { navigate("/settings/notifications")} + onClick={() => navigate(NOTIFICATIONS_SETTINGS_PATH)} /> diff --git a/src/features/settings/components/NotificationToggleRow.tsx b/src/features/settings/components/NotificationToggleRow.tsx deleted file mode 100644 index 272845c..0000000 --- a/src/features/settings/components/NotificationToggleRow.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Switch, Typography } from "@wanteddev/wds"; - -interface NotificationToggleRowProps { - title: string; - /** 카테고리 알림에만 있는 설명 — 전체 알림 행은 제목만 쓴다 */ - description?: string; - checked: boolean; - onCheckedChange: (checked: boolean) => void; -} - -// Figma: 알림 설정(nodeId 3013:116182)의 Global Notification Row(3013:116188)와 Category -// Notification Row(3013:116194 등) — 설명 유무만 다르고 나머지(좌우 20px 패딩, 제목 Body2/Bold, -// 스위치)가 같아서 description optional 한 컴포넌트로 합쳤다. -// 스위치는 Figma `Switch/Switch`(679:16080, WDS 공식 문서 링크 확인)라 WDS `Switch`를 그대로 쓴다. -function NotificationToggleRow({ - title, - description, - checked, - onCheckedChange, -}: NotificationToggleRowProps) { - return ( -
    -
    - - {title} - - {description && ( - - {description} - - )} -
    - -
    - ); -} - -export default NotificationToggleRow; diff --git a/src/features/settings/constants/settings.ts b/src/features/settings/constants/settings.ts index 4ae425c..08e6e0e 100644 --- a/src/features/settings/constants/settings.ts +++ b/src/features/settings/constants/settings.ts @@ -16,40 +16,6 @@ export const SETTINGS_EXTERNAL_LINKS = { privacyPolicy: "", }; -// 화면의 on/off state(Record)와 키를 맞추려고 id를 유니온으로 둔다 — 문자열로 두면 양쪽 중 -// 한쪽에 오타가 나도 타입 에러가 안 난다. -export type NotificationCategoryId = "bililge" | "events" | "board" | "lockers"; - -interface NotificationCategory { - id: NotificationCategoryId; - title: string; - description: string; -} - -// Figma: 알림 설정 Category Notification List(nodeId 3013:116193) 순서·문구 그대로 -export const NOTIFICATION_CATEGORIES: NotificationCategory[] = [ - { - description: "대여 진행 상황과 반납 일정을 알려 드려요", - id: "bililge", - title: "빌릴게 알림", - }, - { - description: "진행 중인 행사와 신청 현황을 알려 드려요", - id: "events", - title: "행사 알림", - }, - { - description: "새 공지와 열린피드백 답변을 알려 드려요", - id: "board", - title: "게시판 알림", - }, - { - description: "배정 결과와 이용 기간 만료 임박을 알려 드려요", - id: "lockers", - title: "사물함 알림", - }, -]; - interface WithdrawalNotice { id: string; title: string; From 0b4312f09ab1d5383c95bc7becd07455ab1f91eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=84=9C=EC=A4=80?= <104981505+xeoxxn@users.noreply.github.com> Date: Tue, 13 Oct 2026 14:27:32 +0900 Subject: [PATCH 12/12] =?UTF-8?q?refactor:=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=EA=B2=BD=EB=A1=9C=C2=B7=EC=9D=B4=ED=83=88?= =?UTF-8?q?=20=EA=B2=BD=EB=A1=9C=EB=A5=BC=20=EC=83=81=EC=88=98=EB=A1=9C=20?= =?UTF-8?q?=EB=B9=BC=EA=B3=A0=20=EA=B0=B1=EC=8B=A0=EB=90=9C=20Figma=20?= =?UTF-8?q?=EB=85=B8=EB=93=9C=20ID=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 리뷰에서 나온 것들을 한번에 정리한다. - SETTINGS_PATHS: 설정 화면 경로를 상수로 모은다. 홈 헤더와 설정 화면이 같이 쓰고, 홈 라우팅(#114)에서도 쓸 자리라 알림의 NOTIFICATIONS_PATH와 같은 방식으로 뒀다 - ACCOUNT_EXIT_PATH: 로그아웃과 탈퇴 완료가 나가는 곳이 같은 자리라 한 상수로 합친다. 로그인/온보딩(#110)이 들어오면 이 값만 "/auth"로 바꾸고 세션 정리를 붙이면 된다 - 로그아웃 확인 모달이 모달만 닫고 아무것도 안 하던 걸 실제로 내보내도록 고친다 - 화면을 쌓는 이동에 viewTransition을 켠다(coding-style.md의 화면 전환 규칙) - SettingsRow의 arrow prop 삭제 — 아무 호출부도 넘기지 않는 죽은 prop이고, "화면이 아직 없는 행"이라는 근거도 회원 탈퇴 화면이 생기면서 사라졌다 - 설정 섹션 Figma 노드 ID 갱신: 섹션이 통째로 다시 만들어져 옛 ID(3013:*, 1799:*)가 파일에서 조회되지 않는다. 현재 ID(3524:*)로 바꾸고 이 사실을 컨벤션 문서에 남긴다 - SETTINGS_ACCOUNT 주석이 코드와 달랐던 것(전화번호를 state 초기값으로 쓴다고 적혀 있었지만 상수를 직접 읽는다) 바로잡음 --- docs/conventions/wds-component-usage.md | 22 ++++++++++------- src/features/home/HomeScreen.tsx | 3 ++- src/features/settings/PhoneChangeScreen.tsx | 4 ++-- src/features/settings/SettingsScreen.tsx | 24 +++++++++++++------ .../settings/WithdrawalCompleteScreen.tsx | 8 +++---- src/features/settings/WithdrawalScreen.tsx | 10 ++++++-- .../settings/components/SettingsRow.tsx | 15 ++++-------- .../settings/components/SettingsSection.tsx | 6 ++--- src/features/settings/constants/settings.ts | 20 ++++++++++++++-- 9 files changed, 72 insertions(+), 40 deletions(-) diff --git a/docs/conventions/wds-component-usage.md b/docs/conventions/wds-component-usage.md index 57e93dc..e36e639 100644 --- a/docs/conventions/wds-component-usage.md +++ b/docs/conventions/wds-component-usage.md @@ -650,24 +650,30 @@ Figma는 헤더 배경이 없어서 배너 그림자(`Shadow/Spread/Small`, 60px Figma 초기 상태가 전체 꺼짐 + 빌릴게만 켜짐이라 "전체"는 독립된 값이 아니라 카테고리가 모두 켜졌는지로 파생한다. 누르면 네 카테고리를 같은 값으로 맞추고, 하나라도 끄면 전체가 꺼진다. 값은 화면 state뿐이라 새로고침하면 초기값으로 돌아간다(서버 API 연동 때 교체). -## 설정 화면 (설정 3013:116135 / 알림 설정 3013:116182 / 전화번호 변경 1799:87220) +## 설정 화면 (설정 `3524:152322` / 전화번호 변경 `3524:152450` / 회원 탈퇴 `3524:152473`) + +코드는 `src/features/settings/` 참고. + +### 알림 설정 화면은 설정이 아니라 알림 기능이 갖는다 + +Figma는 같은 알림 설정 화면을 **설정 섹션(`3524:152367`)과 알림 섹션(`3628:110837`) 두 군데에 복제**해 뒀다(노드 구조·좌표·문구가 완전히 같다). 디자인이 둘이라고 화면을 둘 만들면 안 된다 — 코드는 `features/notifications/NotificationsSettingsScreen`(`/notifications/settings`) 한 벌만 두고, 설정 화면의 "알림 설정" 행과 알림 목록 헤더의 설정 버튼이 **같은 경로로** 들어간다. 카테고리 문구·초기값도 `features/notifications/constants/notifications.ts`가 단일 출처다. ### 새로 확정된 WDS 매핑 | Figma 인스턴스 | 코드 | 확정 근거 | |---|---|---| -| `Switch/Switch` | `Switch`(`@wanteddev/wds`) | 메인 컴포넌트 Node ID `679:16080` + [공식 문서](https://montage.wanted.co.kr/docs/components/selection-and-input/switch/design). `size="small"`이 Figma와 같은 39×24(실측 일치) | | `Textinput/Textfield` | `TextField`(`@wanteddev/wds`) | 메인 컴포넌트 Node ID `445:8591` + [공식 문서](https://montage.wanted.co.kr/docs/components/selection-and-input/text-field/design). radius 12px·padding 12px 실측 일치 | -| `Icon/Normal/Setting` | `IconSetting`(`@wanteddev/wds-icon`) | 메인 컴포넌트 Node ID `492:5735`("설정으로 이동할 때 사용합니다") | -**`TextField`는 입력 박스만 그린다** — Figma `Textinput/Textfield`는 라벨(Heading) + 입력칸 + 안내 문구를 한 컴포넌트로 묶고 있지만, 코드 쪽 `TextField`에는 label·helper 슬롯이 없다. 그래서 "새 전화번호" 라벨과 "행사 신청 시 사용돼요." 안내는 Figma 구조대로 형제 요소로 따로 그린다. WDS `Label`은 required `*`만 더해주는 `Typography` 래퍼라 쓸 이유가 없다(행사 신청 폼 절의 선례와 같은 판단). +`Switch/Switch`·`Icon/Normal/Setting`·`Divider(new)` 8px은 위 "알림" 절에서 이미 확정한 매핑을 그대로 쓴다. + +**`TextField`는 입력 박스만 그린다** — Figma `Textinput/Textfield`는 라벨(Heading) + 입력칸 + 안내 문구를 한 컴포넌트로 묶고 있지만, 코드 쪽 `TextField`에는 label·helper 슬롯이 없다. 그래서 "새 전화번호" 라벨과 "행사 신청 시 사용돼요." 안내는 Figma 구조대로 형제 요소로 따로 그린다. WDS `Label`은 required `*`만 더해주는 `Typography` 래퍼라 쓸 이유가 없다(행사 신청 폼 절의 선례와 같은 판단). 값이 있을 때 뜨는 지움(X) 버튼은 `TextField`가 기본으로 그려서 Figma 입력 중 상태(`3524:152465`)와 그대로 맞는다. ### 설정 행의 `Arrow`는 WDS가 아니다 -설정 화면 모든 행 끝의 화살표(`445:12456` "Arrow", 8×12)는 `search_design_system`으로 WDS를 뒤져도 같은 이름이 없다(WDS는 `Icon/Normal/Arrow *`·`Icon/Normal/Chevron *` 네이밍). 133번째 줄 "제외됨" 목록의 `Icon/Arrow`와 같은 Stream 전용 아이콘이라 SVG를 받아 `src/assets/icons/settings/arrow-right.svg`로 커밋했다. 색(`#C2C4C8` = `Atomic/Cool Neutral/90`)이 에셋에 구워져 있어 토큰은 새로 추가하지 않았다. +설정 화면 모든 행 끝의 화살표(`445:12456` "Arrow")는 `search_design_system`으로 WDS를 뒤져도 같은 이름이 없다(WDS는 `Icon/Normal/Arrow *`·`Icon/Normal/Chevron *` 네이밍). 133번째 줄 "제외됨" 목록의 `Icon/Arrow`와 같은 Stream 전용 아이콘이라 SVG를 받아 `src/assets/icons/settings/arrow-right.svg`로 커밋했다. 색(`#C2C4C8` = `Atomic/Cool Neutral/90`)이 에셋에 구워져 있어 토큰은 새로 추가하지 않았다. -### 알림 설정 화면은 설정이 아니라 알림 기능이 갖는다 +**에셋은 12×12인데 슬롯은 8×12다.** Figma도 12×12 뷰박스를 `width: 8px; height: 12px` 자리에 `preserveAspectRatio="none"`으로 넣어 가로를 눌러 쓴다. 내려받은 SVG를 그대로 `className="h-3 w-2"`로 쓰는 게 Figma와 같은 결과다 — 8×12로 다시 내보내거나 종횡비를 맞추려 들면 오히려 어긋난다. -Figma는 같은 알림 설정 화면을 **설정 섹션(`3524:152367`)과 알림 섹션(`3628:110837`) 두 군데에 복제**해 뒀다(노드 구조·좌표·문구가 완전히 같다). 디자인이 둘이라고 화면을 둘 만들면 안 된다 — 코드는 `features/notifications/NotificationsSettingsScreen`(`/notifications/settings`) 한 벌만 두고, 설정 화면의 "알림 설정" 행과 알림 목록 헤더의 설정 버튼이 **같은 경로로** 들어간다. 카테고리 문구·초기값도 `features/notifications/constants/notifications.ts`가 단일 출처다. +### Figma 노드 ID가 통째로 갈린 적이 있다 -섹션 구분선(8px `Divider(new)`)과 `Switch` 매핑은 위 "알림" 절에 이미 적어 뒀다. +설정 섹션은 한 번 통째로 다시 만들어졌다. 옛 ID(`1799:85920` 섹션, 설정 `3013:116135`, 알림 설정 `3013:116182`, 전화번호 변경 `1799:87220`, 로그아웃 모달 `1799:87183`)는 **파일에서 삭제되어 조회되지 않는다.** 탈퇴 계열(`3524:152473`·`3524:152505`·`3524:152517`)만 그대로다. 주석의 nodeId가 `get_design_context`에서 "not found"로 나오면 먼저 섹션이 다시 만들어졌는지 확인한다. diff --git a/src/features/home/HomeScreen.tsx b/src/features/home/HomeScreen.tsx index e298221..ca144ab 100644 --- a/src/features/home/HomeScreen.tsx +++ b/src/features/home/HomeScreen.tsx @@ -17,6 +17,7 @@ import { } from "@/features/home/constants/homeMock"; import { NOTIFICATIONS_PATH } from "@/features/notifications/constants/notifications"; import { getSearchPath } from "@/features/search/constants/search"; +import { SETTINGS_PATHS } from "@/features/settings/constants/settings"; // Figma: 홈 2건 이하 (nodeId 3147:146240), 3건 이상 (3147:146297), empty (3147:146366) // 챗봇 FAB(Figma nodeId 2443:178457 등, 홈 화면 variant마다 우하단에 고정) — @@ -52,7 +53,7 @@ function HomeScreen() { - navigate("/settings", { viewTransition: true }) + navigate(SETTINGS_PATHS.root, { viewTransition: true }) } variant="icon" > diff --git a/src/features/settings/PhoneChangeScreen.tsx b/src/features/settings/PhoneChangeScreen.tsx index 963380f..468d1de 100644 --- a/src/features/settings/PhoneChangeScreen.tsx +++ b/src/features/settings/PhoneChangeScreen.tsx @@ -13,7 +13,7 @@ import ScreenHeader from "@/components/ui/ScreenHeader"; import ScreenToast from "@/components/ui/ScreenToast"; import { useScreenHeader } from "@/components/ui/useScreenHeader"; -// Figma: 전화번호 변경 (nodeId 1799:87220), 입력 중 (1799:87227), 완료 토스트 (1917:93781). +// Figma: 전화번호 변경 (nodeId 3524:152450), 입력 중 (3524:152465), 완료 토스트 (3524:152457). // 입력칸은 Figma `Textinput/Textfield`(445:8591, WDS 공식 문서 링크 확인)라 WDS `TextField`를 // 그대로 쓴다. 다만 WDS `TextField`는 입력 박스만 그려서, 위 라벨("새 전화번호")과 아래 안내 // 문구는 Figma 구조대로 형제 요소로 따로 그린다 — WDS `Label`은 required `*` 처리만 더해주는 @@ -23,7 +23,7 @@ import { useScreenHeader } from "@/components/ui/useScreenHeader"; // 활성 #0066FF). 높이 56px은 ActionAreaButton 기본(48px)과 달라 sx로 세로 padding만 보정한다. // 실제 변경 API가 없어서 누르면 Figma처럼 완료 토스트만 띄운다. // -// Figma 입력 중 상태(1799:87230)가 "010-1234-5"처럼 하이픈이 끼워진 모습이라 입력하는 대로 +// Figma 입력 중 상태(3524:152465)가 "010-1234-5"처럼 하이픈이 끼워진 모습이라 입력하는 대로 // 하이픈을 넣어준다. 유효 기준은 placeholder와 같은 010 + 8자리다. const PHONE_PATTERN = /^010-\d{4}-\d{4}$/; diff --git a/src/features/settings/SettingsScreen.tsx b/src/features/settings/SettingsScreen.tsx index 91e8120..ee04b7e 100644 --- a/src/features/settings/SettingsScreen.tsx +++ b/src/features/settings/SettingsScreen.tsx @@ -10,17 +10,19 @@ import { NOTIFICATIONS_SETTINGS_PATH } from "@/features/notifications/constants/ import SettingsRow from "@/features/settings/components/SettingsRow"; import SettingsSection from "@/features/settings/components/SettingsSection"; import { + ACCOUNT_EXIT_PATH, APP_VERSION_LABEL, SETTINGS_ACCOUNT, SETTINGS_EXTERNAL_LINKS, + SETTINGS_PATHS, } from "@/features/settings/constants/settings"; -// Figma: 설정 (nodeId 3013:116135) — 계정 정보 / 알림 설정 / 서비스 정보 / 로그아웃 네 장의 흰 +// Figma: 설정 (nodeId 3524:152322) — 계정 정보 / 알림 설정 / 서비스 정보 / 로그아웃 네 장의 흰 // 카드가 회색 배경 위에 8px 간격으로 쌓이고, 맨 아래에 Copyright가 깔린다. // // 의견 보내기·개인정보 처리방침은 화면을 따로 만들지 않고 구글폼으로 연결한다(폼 주소가 아직 // 없어서 SETTINGS_EXTERNAL_LINKS가 빈 문자열이면 아무 동작도 하지 않는다). -// 로그아웃은 확인 모달(1799:87183)을 공용 ConfirmModal(tone="negative")로 붙였고, +// 로그아웃은 확인 모달(3524:152426)을 공용 ConfirmModal(tone="negative")로 붙였고, // 회원 탈퇴는 별도 화면(3524:152473)으로 이동한다. // // "알림 설정"은 알림 화면이 이미 가진 화면으로 보낸다 — Figma가 설정 섹션(3524:152367)과 알림 @@ -29,6 +31,9 @@ function SettingsScreen() { const navigate = useNavigate(); const [logoutOpen, setLogoutOpen] = useState(false); + // 설정 아래로 화면을 쌓는 이동이라 슬라이드를 켠다(coding-style.md의 화면 전환 규칙). + const pushScreen = (to: string) => navigate(to, { viewTransition: true }); + useScreenHeader( navigate("/settings/phone")} + onClick={() => pushScreen(SETTINGS_PATHS.phoneChange)} value={SETTINGS_ACCOUNT.phone} variant="account" /> @@ -77,7 +82,7 @@ function SettingsScreen() { navigate(NOTIFICATIONS_SETTINGS_PATH)} + onClick={() => pushScreen(NOTIFICATIONS_SETTINGS_PATH)} /> @@ -105,13 +110,13 @@ function SettingsScreen() { /> navigate("/settings/withdraw")} + onClick={() => pushScreen(SETTINGS_PATHS.withdrawal)} tone="negative" />
    - {/* Copyright는 카드 묶음 아래 남는 공간 맨 끝에 붙는다(Figma 3013:116181) */} + {/* Copyright는 카드 묶음 아래 남는 공간 맨 끝에 붙는다(Figma 3524:152366) */} setLogoutOpen(false)} - onConfirm={() => setLogoutOpen(false)} + // 로그아웃하면 돌아올 화면이 아니라 설정을 히스토리에서 치우고 나간다. + // 실제 세션 정리는 인증이 붙을 때 여기에 함께 붙인다(ACCOUNT_EXIT_PATH 주석 참고). + onConfirm={() => { + setLogoutOpen(false); + navigate(ACCOUNT_EXIT_PATH, { replace: true }); + }} open={logoutOpen} title="정말 로그아웃할까요?" tone="negative" diff --git a/src/features/settings/WithdrawalCompleteScreen.tsx b/src/features/settings/WithdrawalCompleteScreen.tsx index d882f75..f447963 100644 --- a/src/features/settings/WithdrawalCompleteScreen.tsx +++ b/src/features/settings/WithdrawalCompleteScreen.tsx @@ -2,17 +2,15 @@ import { useNavigate } from "react-router-dom"; import CompleteCheck from "@/components/ui/CompleteCheck"; import ResultScreen from "@/components/ui/ResultScreen"; - -// 탈퇴하면 로그아웃된 상태라 로그인/온보딩으로 나가야 하지만 아직 그 화면이 없다 — -// 인증을 붙일 때 이 상수만 바꾼다. 그때까지는 다른 결과 화면과 같이 홈으로 내보낸다. -const EXIT_PATH = "/"; +import { ACCOUNT_EXIT_PATH } from "@/features/settings/constants/settings"; // Figma: 탈퇴 완료 페이지 (nodeId 3524:152505) // 신청 결과 화면과 같은 뼈대라 ResultScreen을 쓴다. 버튼이 "확인" 하나뿐이라 // secondaryAction을 주지 않고, 체크 모션이 붙는 완료 화면이라 간격은 8px이다. +// 나갈 곳은 로그아웃과 같은 자리라 ACCOUNT_EXIT_PATH를 함께 쓴다. function WithdrawalCompleteScreen() { const navigate = useNavigate(); - const exit = () => navigate(EXIT_PATH, { replace: true }); + const exit = () => navigate(ACCOUNT_EXIT_PATH, { replace: true }); return ( setConfirmOpen(false)} // 탈퇴를 확정하면 되돌아올 화면이 아니라 완료 화면으로 치환한다 onConfirm={() => - navigate("/settings/withdraw/complete", { replace: true }) + navigate(SETTINGS_PATHS.withdrawalComplete, { + replace: true, + viewTransition: true, + }) } open={confirmOpen} title="정말 탈퇴하시겠어요?" diff --git a/src/features/settings/components/SettingsRow.tsx b/src/features/settings/components/SettingsRow.tsx index 0f484c0..dc71f99 100644 --- a/src/features/settings/components/SettingsRow.tsx +++ b/src/features/settings/components/SettingsRow.tsx @@ -6,13 +6,8 @@ interface SettingsRowProps { label: string; /** 오른쪽에 붙는 값 — 계정 정보(이름·이메일·전화번호)와 앱 버전에만 있다 */ value?: string; - /** 넘기면 행 전체가 버튼이 된다 */ + /** 넘기면 행 전체가 버튼이 되고 오른쪽에 화살표가 붙는다 */ onClick?: () => void; - /** - * 오른쪽 화살표. 기본값은 onClick 유무를 따르지만, 회원 탈퇴처럼 Figma엔 화살표가 있는데 - * 누른 뒤 화면이 아직 없는 행은 이 값을 직접 켜준다. - */ - arrow?: boolean; /** * account: 계정 정보 행 — 라벨 Regular, 값 Medium·Label/Neutral * menu: 그 외 메뉴·액션 행 — 라벨 Medium, 값 Regular·Label/Alternative(앱 버전) @@ -28,10 +23,11 @@ const TONE_COLOR = { normal: "semantic.label.neutral", } as const; -// Figma: 설정 화면(nodeId 3013:116135)의 행들 — Account Row(3013:116144), Navigation Row -// (3013:116159), Info Row(3013:116174), Action Row(3013:116178)가 같은 높이(20px)·같은 +// Figma: 설정 화면(nodeId 3524:152322)의 행들 — Account Row(3524:152332), Navigation Row +// (3524:152344), Info Row(3524:152356), Action Row(3524:152360)가 같은 높이(20px)·같은 // justify-between 구조라 한 컴포넌트의 variant/tone으로 합쳤다. 넷의 차이는 라벨 굵기(계정 // 정보만 Regular)와 라벨 색(로그아웃 alternative·회원 탈퇴 negative)뿐이다. +// 화살표는 Figma에서 앱 버전 행에만 없는데, 그 행만 누를 수 없어서 onClick 유무로 갈린다. // // 화살표는 Figma `Arrow`(445:12456) — WDS에 같은 이름이 없고(검색 결과 `Icon/Normal/Arrow *`· // `Chevron *`뿐) wds-component-usage.md에도 Stream 전용 아이콘으로 분류돼 있어 SVG로 받아 쓴다. @@ -43,12 +39,11 @@ function SettingsRow({ label, value, onClick, - arrow, variant = "menu", tone = "normal", }: SettingsRowProps) { const isAccount = variant === "account"; - const showArrow = arrow ?? Boolean(onClick); + const showArrow = Boolean(onClick); const content = ( <> diff --git a/src/features/settings/components/SettingsSection.tsx b/src/features/settings/components/SettingsSection.tsx index 02f09ad..b74dafa 100644 --- a/src/features/settings/components/SettingsSection.tsx +++ b/src/features/settings/components/SettingsSection.tsx @@ -9,9 +9,9 @@ interface SettingsSectionProps { children: ReactNode; } -// Figma: 설정 화면(nodeId 3013:116135)의 흰 카드 — Account Section(3013:116141), -// Notification Settings Section(3013:116158), Service Info Section(3013:116162), -// Logout Section(3013:116177) 넷이 같은 배경·radius·padding이라 하나로 합쳤다. +// Figma: 설정 화면(nodeId 3524:152322)의 흰 카드 — Account Section(3524:152329), +// Notification Settings Section(3524:152343), Service Info Section(3524:152347), +// Logout Section(3524:152359) 넷이 같은 배경·radius·padding이라 하나로 합쳤다. // 섹션 라벨 유무와 행 간격만 다르다. function SettingsSection({ title, diff --git a/src/features/settings/constants/settings.ts b/src/features/settings/constants/settings.ts index 08e6e0e..879c483 100644 --- a/src/features/settings/constants/settings.ts +++ b/src/features/settings/constants/settings.ts @@ -1,11 +1,27 @@ -// Figma: 설정 화면 계정 정보(nodeId 3013:116143)·앱 버전(3013:116176) 목데이터 — 실 API 연동 -// 전까지 사용. 전화번호는 변경 화면에서 바꾸면 반영돼야 하는 값이라 화면 state의 초기값으로 쓴다. +// 설정 화면들의 경로 — 홈 헤더·설정 화면이 같이 쓰므로 문자열을 흩지 않고 한곳에 둔다 +// (알림의 `NOTIFICATIONS_PATH`와 같은 방식). +export const SETTINGS_PATHS = { + phoneChange: "/settings/phone", + root: "/settings", + withdrawal: "/settings/withdraw", + withdrawalComplete: "/settings/withdraw/complete", +}; + +// Figma: 설정 화면 계정 정보(nodeId 3524:152331)·앱 버전(3524:152356) 목데이터 — 실 API 연동 +// 전까지 사용. 전화번호 변경 화면은 아직 이 상수를 고치지 않아서, 번호를 바꿔도 설정 화면 +// 값은 그대로다(연동할 때 함께 처리한다). export const SETTINGS_ACCOUNT = { email: "pog454@kookmin.ac.kr", name: "이예담", phone: "010-1234-5678", }; +// 로그아웃·회원 탈퇴로 계정을 떠난 뒤 나갈 곳. 둘 다 로그아웃된 상태라 로그인/온보딩으로 가야 +// 하는데 그 화면이 아직 없어서(#110에서 들어온다) 지금은 홈으로 보낸다. 인증이 붙으면 이 값을 +// "/auth"로 바꾸고, 로그인 표시를 지우는 처리를 두 호출부(SettingsScreen 로그아웃 확인 모달, +// WithdrawalCompleteScreen)에 같이 붙이면 된다 — 지금은 지울 표시 자체가 없다. +export const ACCOUNT_EXIT_PATH = "/"; + // Figma 표기 그대로("26.0.1 · 최신버전") — 최신 여부 판정은 서버 응답을 그대로 보여주는 자리다. export const APP_VERSION_LABEL = "26.0.1 · 최신버전";