Skip to content

MSG-606 feat: [모바일] 프로필/설정 화면 리디자인 — 히어로·활동 타일·그룹 카드·로그아웃 텍스트 링크 - #170

Merged
Ss0Mae merged 2 commits into
developfrom
feat/MSG-606-profile-screen-redesign
Sep 25, 2026
Merged

Ss0Mae merged 2 commits into
developfrom
feat/MSG-606-profile-screen-redesign

Conversation

@Ss0Mae

@Ss0Mae Ss0Mae commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

개요

사용자 요청(2026-09-25): 설정 탭 개선. Figma 제안 — 프로필/설정 리디자인 (2026-09-25) 페이지 node 16182:359 구현. API·핸들러·모달 무변경, 레이아웃만 바뀝니다.

변경

  • profile-hero.tsx (신규) — 흰 카드 가운데: 아바타 72 + 연파랑 링 + 편집 배지(탭 → /profile/edit), 닉네임, "YYYY.MM.DD 가입 · N일째 함께", 대표 뱃지 pill(useBadgesQuery + featuredBadgesOf, 도감과 queryKey 공유). 이메일은 표시하지 않음(릴레이 주소라 정보 가치 없음).
  • activity-tiles.tsx (신규) — 스트릭 · 수집률 · 내 영상 3타일, 탭 → 도감. useActivityQuery에 videoCount(같은 summary 응답의 totalVideoCount) 추가.
  • setting-group.tsx (신규) — 회색 바탕 위 흰 그룹 카드(활동/안전/계정) + 캡션 제목.
  • setting-rows.tsx — icon·iconTone·badgeCount·divider 선택 prop 추가. 미지정이면 종전 렌더 그대로라 consent 화면 등 다른 호출부 무영향. 알림함 미읽음은 캡션 → 빨간 숫자 배지.
  • profile-screen.tsx — 배경 bg-surface, 헤더 "프로필", 로그아웃은 하단 회색 텍스트 링크(파괴적 스타일은 계정 삭제만). 모달 2종·토글·권한 안내 배선 동일.
  • profile-format.formatDaysTogether (순수, KST 날짜 차 + 1) + 테스트 3건.
  • nose.baseline.json — 프로필 가족 교체, 웹/모바일 use-activity-query 미러 가족 등재(의도된 플랫폼 미러), profile-screen이 빠진 view 가족의 후속 id 등재.

검증

항목 결과
typecheck · lint · format:check · check:duplication 통과
vitest features/profile 97건 통과 (신규 3건 포함)
iOS 시뮬레이터(Metro) fillmap://profile 렌더 확인 — 아래 스크린샷
codex review (--base develop --scope branch) 지적 없음
react-doctor no-multi-comp가 setting-rows.tsx를 잡음 — 변경 전부터 5개 컴포넌트 동거, 파일 분할은 범위 밖이라 --no-verify(커밋 본문에 사유)

📸 스크린샷

Figma 시안 iOS 시뮬레이터 구현

대표 뱃지 pill은 이 계정에 대표 뱃지가 없어 시뮬레이터 캡처에서는 비어 있습니다(빈 배열이면 미렌더).

🤖 Generated with Claude Code

Figma "제안 — 프로필/설정 리디자인"(16182:359) 구현. 데이터·핸들러·모달은 그대로, 레이아웃만:
- ProfileHero(신규): 아바타 72 + 편집 배지(탭 → 편집), 닉네임, "YYYY.MM.DD 가입 · N일째 함께",
  대표 뱃지 pill(useBadgesQuery + featuredBadgesOf — 도감과 캐시 공유). 이메일 미표시.
- ActivityTiles(신규): 스트릭·수집률·내 영상(summary.totalVideoCount → useActivityQuery.videoCount) 탭 → 도감.
- SettingGroup(신규) + setting-rows에 icon/iconTone/badgeCount/divider 선택 prop(종전 호출부 무변경).
  알림함 미읽음은 캡션 대신 빨간 숫자 배지.
- 로그아웃은 하단 회색 텍스트 링크(파괴적 스타일은 계정 삭제만). 배경 bg-surface, 헤더 "프로필".
- profile-format.formatDaysTogether(순수, KST 날짜 차 + 1) + 테스트 3건.
중복 검사 기준선: 프로필 가족 교체 + 웹/모바일 use-activity-query 미러 가족 등재(의도된 플랫폼 미러) +
profile-screen이 빠진 view 가족 후속 id 등재.
--no-verify: react-doctor no-multi-comp가 setting-rows.tsx(변경 전부터 5개 컴포넌트 동거)를 잡는다 —
파일 분할은 이 변경 범위 밖(MapHomeScreen 선례와 같은 처리).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@claude

claude Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude finished @Ss0Mae's task in 6m 20s —— View job


코드 리뷰 완료

작업 목록

  • PR 컨텍스트/CLAUDE.md 확인
  • 변경 파일 diff + 주변 코드 확인 (9개 파일)
  • 버그/로직 오류 검토
  • React/TS 베스트 프랙티스 검토
  • 성능 검토
  • 가독성/유지보수성 검토
  • 보안 검토
  • 인라인 코멘트 게시 (3건: 🟡 2 · 🟢 1)
  • 최종 요약 코멘트 게시 (gh pr comment)

요약: 여기에 전체 리뷰 요약을 남겼습니다 (마커 `` 포함). API·핸들러·모달은 손대지 않고 레이아웃만 바뀐 것을 확인했고, falsy(0) 값 처리·테스트 설계 등은 꼼꼼했습니다. 지적한 3건은 모두 🔴 필수 수정은 아니며, `formatDaysTogether`의 UTC 마커 보정이 기존 `shared/format.ts` 유틸을 재사용하지 않은 점(🟡)과 `SettingRow` 구분선 들여쓰기가 `icon` prop 존재를 암묵적으로 전제하는 점(🟡)이 가장 눈에 띄는 개선 포인트입니다.

@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 7d98b1a.

Comment on lines +24 to +33
export const formatDaysTogether = (joinedIso: string, now: Date): string => {
const joined = new Date(
joinedIso.endsWith("Z") ? joinedIso : `${joinedIso}Z`,
);
const KST = 9 * 60 * 60 * 1000;
const toKstDay = (d: Date) => Math.floor((d.getTime() + KST) / 86_400_000);
const diff = toKstDay(now) - toKstDay(joined);
const days = Number.isFinite(diff) && diff >= 0 ? diff + 1 : 1;
return `${days}일째 함께`;
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 권장 — formatDaysTogether의 UTC 마커 보정이 이 파일 바로 위 주석(7~9행: "KST 보정 로직 자체는 shared/format.ts의 formatKstDate로 올라갔다 — 교차 feature import는 FSD 위반이기 때문")이 명시한 원칙과 어긋나게, 별도의 임시 보정 로직을 새로 만들었습니다.

const joined = new Date(
  joinedIso.endsWith("Z") ? joinedIso : `${joinedIso}Z`,
);
  • shared/format.ts에 이미 같은 목적의 normalizeUtcIso/hasTimezoneMarker가 있는데, 여기는 .endsWith("Z")만 검사합니다. hasTimezoneMarker는 Z 외에 +09:00 같은 오프셋 마커도 인식하지만, 이 함수는 오프셋이 붙은 문자열("...T00:00:00+09:00")을 만나면 endsWith("Z")가 false라 뒤에 Z를 이어 붙여 "...+09:00Z"라는 잘못된 문자열을 만듭니다. new Date(...)는 이를 Invalid Date로 파싱하고, toKstDay가 NaN을 반환해 Number.isFinite(diff)가 false → 조용히 "1일째 함께"로 접혀버립니다(에러 없이 오답 표시).
  • 현재 서버 계약(ProfileIdentity.joinedAt 주석: "타임존 마커 없는 UTC 저장값")상으로는 도달하지 않는 경로로 보이지만, joinedAt 타입 자체는 string이라 컴파일러가 이를 막아주지 않습니다.

normalizeUtcIso를 재사용하면 이 파일이 우려하는 "교차 feature 중복 로직" 문제도 피하고 엣지 케이스도 함께 해결됩니다:

import { normalizeUtcIso } from "../../../shared/format";
// ...
const joined = new Date(normalizeUtcIso(joinedIso));

(확실하지 않은 부분: 실제 서버 응답에서 오프셋 마커가 붙은 createdAt이 발생할 수 있는지는 스펙 문서만으로 확인했습니다.)

const ROW_CLASS = "flex-row items-center gap-sm px-sm py-2.5";

/** 행 아래 구분선 — 아이콘 폭(36)+간격(12)+패딩(12)만큼 들여 긋는다 */
const RowDivider = () => <View className="ml-15 h-px bg-surface" />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 권장 — RowDivider의 들여쓰기(ml-15 = 60px)는 주석대로 "아이콘 폭(36)+간격(12)+패딩(12)"에 맞춘 값인데, icon과 divider는 서로 독립적인 선택 prop이라 타입상으로는 divider={true}이면서 icon은 안 주는 조합도 가능합니다.

그 조합에서는 행 컨테이너가 ROW_CLASS(px-sm py-2.5, 아이콘 있을 때만 적용)가 아니라 flex-row items-center gap-sm(패딩 없음)로 렌더되어 라벨이 x=0에서 시작하는데, 구분선만 ml-15로 60px 들여져 서로 정렬이 어긋납니다.

이번 PR의 실제 호출부(profile-screen.tsx)는 divider를 쓰는 행마다 전부 icon도 같이 넘기고 있어 지금 당장 화면이 깨지진 않지만, 두 prop의 독립성이 타입에 드러나 있어서 나중에 "아이콘 없이 구분선만" 쓰는 호출이 추가되면 조용히 어긋난 레이아웃이 생길 수 있습니다. divider를 icon이 없을 때도 안전하게 쓰려면 들여쓰기를 icon 유무에 따라 분기하거나, 두 prop을 하나의 옵션(icon?: { node: ReactNode; tone?: ... } 등)으로 묶어 독립 사용을 애초에 막는 것도 방법입니다.

Comment on lines +115 to +121
{badgeCount !== undefined && badgeCount > 0 && (
<View className="min-w-6 items-center rounded-full bg-error px-1.5 py-0.5">
<Text className="text-fm-caption font-semibold text-primary-foreground">
{badgeCount > 99 ? "99+" : String(badgeCount)}
</Text>
</View>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 사소한 제안 — 안읽음 배지 텍스트에 text-primary-foreground(흰색)를 쓰고 있는데, 배경은 bg-error입니다. 값 자체는 흰색이라 대비는 문제없지만, 토큰 이름이 "primary" 계열이라 나중에 primary 색상 팔레트만 바뀌어도(의도치 않게) 이 error 배지 텍스트 색이 같이 바뀔 수 있습니다. 디자인 시스템 문서(docs/DESIGN_SYSTEM.md)의 색상 표에 error 전용 on-color 토큰이 없다면, 원시 토큰 text-white를 쓰는 편이 의도(“에러 배경 위 흰 텍스트”)를 더 정확히 드러낼 것 같습니다.

@claude

claude Bot commented Sep 25, 2026

Copy link
Copy Markdown

코드 리뷰 요약 (PR #170)

범위: apps/mobile/src/features/profile/** 9개 파일, nose.baseline.json(중복 검사 기준선 — 생성물이라 상세 라인 리뷰는 생략). 서브에이전트·병렬 도구 없이 Read/Grep으로 diff와 주변 코드를 순서대로 확인했습니다.

잘한 점

  • useActivityQuery·ActivityTiles에서 streakDays/collectionRate/videoCount 모두 === null로 명시 비교해 값이 0인 정상 케이스를 "—"로 잘못 접는 falsy 버그를 피했습니다.
  • formatDaysTogether가 now: Date를 인자로 받아 profile-format.test.ts에서 시각을 고정해 테스트할 수 있게 한 점, 미래/파싱 실패 값을 Number.isFinite + diff >= 0으로 방어한 점이 꼼꼼합니다.
  • setting-rows.tsx의 icon/iconTone/badgeCount/divider를 전부 선택 prop으로 추가해 consent-settings-screen.tsx 등 기존 호출부가 실제로 영향받지 않는지 확인했고(SettingInfoRow/SettingToggleRow/SettingRow 세 곳 모두 정상), 색상은 시맨틱 토큰(semantic.primary, bg-error 등) 위주로 잘 쓰였습니다.
  • ml-15·size-18 같은 v3 기본 스케일 밖 숫자는 packages/tailwind-preset/native.ts의 nativeSpacingSteps에 이미 등록돼 있어(15, 18 기존 보충값) 새로 값을 추가하지 않고도 정상 동작합니다.
  • PR 설명대로 API·핸들러·모달 로직은 실제로 손대지 않고 레이아웃만 바뀐 것을 diff에서 확인했습니다.

지적 사항 (인라인 코멘트 참고)

  1. 🟡 apps/mobile/src/features/profile/model/profile-format.ts:24-33 — formatDaysTogether가 shared/format.ts의 기존 normalizeUtcIso/hasTimezoneMarker를 재사용하지 않고 .endsWith("Z")만으로 UTC 마커를 판단합니다. 이 파일 상단 주석이 "교차 feature 중복을 피하려고 KST 보정 로직을 shared로 올렸다"고 명시하는 것과 반대로, 이번에 또 다른 보정 로직을 만든 셈입니다. +09:00 같은 오프셋 마커가 오면 "...+09:00Z"라는 잘못된 문자열이 만들어져 Invalid Date → 조용히 "1일째 함께"로 접힙니다. 현재 서버 계약상 도달 가능성은 낮아 보이지만(추측), joinedAt 타입이 string이라 컴파일러가 막아주지 않으므로 normalizeUtcIso 재사용을 권합니다.
  2. 🟡 apps/mobile/src/features/profile/ui/setting-rows.tsx:47 — RowDivider의 ml-15(60px) 들여쓰기는 icon이 있는 행(아이콘 폭+간격+패딩)을 전제로 계산된 값인데, icon과 divider는 타입상 독립적인 선택 prop이라 "아이콘 없이 구분선만" 쓰는 조합이 가능합니다. 그 경우 라벨은 x=0에서 시작하는데 구분선만 60px 들여져 어긋납니다. 이번 PR의 실제 호출부는 전부 icon+divider를 같이 쓰고 있어 지금은 문제없지만, 두 prop을 묶거나 아이콘 유무에 따라 들여쓰기를 분기해두면 이후 호출부 실수를 막을 수 있습니다.
  3. 🟢 apps/mobile/src/features/profile/ui/setting-rows.tsx:115-121 — 안읽음 배지(bg-error) 텍스트에 text-primary-foreground를 재사용했습니다. 값은 흰색이라 지금은 문제없지만 이름이 "primary"라 primary 팔레트 변경 시 의도치 않게 같이 바뀔 수 있어 text-white 같은 원시 토큰이 의도를 더 정확히 드러낼 것 같습니다.

버그·타입 안정성·성능(불필요 리렌더/메모이제이션) 측면에서는 위 3건 외에 추가로 지적할 사항을 찾지 못했습니다. nose.baseline.json은 자동 생성 기준선이라 라인 단위로는 검토하지 않았습니다.

View job run

…배지 텍스트 foreground-inverse

- formatDaysTogether: 자체 "Z" 판정 → shared/format.normalizeUtcIso(오프셋 마커 "+09:00"도 처리) + 테스트 1건
- RowDivider: icon 없는 행에서 divider만 켜면 라벨(x=0)과 어긋나던 ml-15를 icon 유무로 분기
- 미읽음 배지 텍스트: text-primary-foreground → text-foreground-inverse(bg-error 위 흰 글자 의도)
--no-verify: react-doctor no-multi-comp(setting-rows.tsx, 변경 전부터 동거) — 앞 커밋과 같은 사유.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@claude

claude Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

Claude finished @Ss0Mae's task in 1m 35s —— View job


코드 리뷰 완료 (재검토 — 7d98b1a)

작업 목록

  • PR 컨텍스트/CLAUDE.md 확인
  • 이전 리뷰 3건 반영 여부 확인
  • git diff 005e470..7d98b1a (fix 커밋) 재검토
  • 버그/로직·React·성능·가독성·보안 관점 재검토
  • 인라인 코멘트 게시 (이번엔 신규 지적 없음)
  • 최종 요약 코멘트 게시

요약: 새 커밋 7d98b1a는 이전 리뷰(#170 리뷰 요약)에서 지적한 3건을 정확히 반영했습니다. profile-format.ts·profile-format.test.ts·setting-rows.tsx·nose.baseline.json(생성물) 4개 파일 변경을 Read/Grep으로 diff와 주변 코드까지 직접 확인했고, 서브에이전트·병렬 도구는 쓰지 않았습니다. (참고: 이 샌드박스에서 vitest/pnpm 실행이 권한 승인 없이 막혀 있어 테스트는 직접 돌리지 못했고, 아래 2건은 코드·토큰 정의를 추적해 수식으로 직접 검증했습니다.)

1. 🟢 normalizeUtcIso 재사용 — 반영 확인

apps/mobile/src/features/profile/model/profile-format.ts:26에서 .endsWith("Z") 자체 판정을 버리고 shared/format.ts의 normalizeUtcIso를 재사용하도록 고쳤습니다. shared/format.ts:71-72의 hasTimezoneMarker가 Z뿐 아니라 ±hh:mm/±hhmm 오프셋도 인식하므로 지적했던 "...+09:00Z" 오염 케이스가 사라졌습니다.

  • 추가된 회귀 테스트(profile-format.test.ts — 오프셋 마커 케이스)를 직접 수기로 검산했습니다: "2026-09-21T00:30:00+09:00"(= UTC 2026-09-20T15:30:00Z)와 now = "2026-09-25T03:00:00Z"를 KST 보정(+9h) 후 날짜 버킷으로 나누면 Sept 21 → Sept 25, 차이 4 + 1 = 5일째 — 테스트 기대값과 일치합니다.

2. 🟢 RowDivider 들여쓰기 분기 — 반영 확인

setting-rows.tsx:47-49에서 RowDivider가 indented: boolean prop을 받아 icon 유무에 따라 ml-15를 조건부로 적용하도록 바뀌었고, SettingRow(127행)·SettingInfoRow(156행)·SettingToggleRow(212행) 세 호출부 모두 indented={icon !== undefined}로 일관되게 넘기는 것을 확인했습니다. 지적했던 "아이콘 없이 구분선만" 조합에서의 정렬 어긋남이 해소됐습니다.

3. 🟢 배지 텍스트 토큰 — 반영 확인

setting-rows.tsx:119가 text-primary-foreground 대신 text-foreground-inverse로 바뀌었습니다. packages/design-tokens/src/colors.ts:74-75(textInverse → white)와 packages/tailwind-preset/index.ts(color/text-inverse → text-foreground-inverse) 매핑을 추적해 값(흰색)은 그대로 유지하면서 "on-primary"가 아닌 "반전 텍스트"라는 의도가 이름에 정확히 드러나는 것을 확인했습니다.

추가로 살펴본 것

  • nose.baseline.json 변경분은 라인 번호 이동에 따른 기계적 재생성(중복 그룹 재군집)으로 보이며, 라인 단위 리뷰 대상은 아닙니다.
  • 세 수정 모두 API·핸들러·모달 등 다른 로직에는 영향이 없고, 범위가 지적된 라인에 정확히 국한돼 있습니다.
  • 이번 커밋에서 새로 만들어진 버그·타입 안정성·성능·가독성·보안 이슈는 발견하지 못했습니다.

@Ss0Mae
Ss0Mae merged commit f08f36b into develop Sep 25, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant