Skip to content

[FEAT] WTH-421: 마이페이지 개선뷰 UI 구현#137

Merged
woneeeee merged 100 commits into
developfrom
feat/WTH-421-마이페이지-개선뷰-UI-구현
Jul 6, 2026

Hidden character warning

The head ref may contain hidden characters: "feat/WTH-421-\ub9c8\uc774\ud398\uc774\uc9c0-\uac1c\uc120\ubdf0-UI-\uad6c\ud604"
Merged

[FEAT] WTH-421: 마이페이지 개선뷰 UI 구현#137
woneeeee merged 100 commits into
developfrom
feat/WTH-421-마이페이지-개선뷰-UI-구현

Conversation

@woneeeee

@woneeeee woneeeee commented Jul 2, 2026

Copy link
Copy Markdown
Member

✅ PR 유형

어떤 변경 사항이 있었나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드에 영향을 주지 않는 변경사항(오타 수정, 탭 사이즈 변경, 변수명 변경)
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

📌 관련 이슈번호

  • Closed #421

✅ Key Changes

마이페이지 공통 구조

  • 마이페이지 공통 레이아웃(layout.tsx) 및 네비게이션(MyPageNav) 구현
  • 헤더가 필요 없는 하위 경로(프로필 수정 등)에서 헤더 숨김 처리

하위 페이지 추가

  • 활동정보(activity), 내가 쓴 글(posts), 출석 세션(sessions), 서비스 설정(settings), 프로필 관리(profiles) 페이지 추가
  • 화면 모드 선택 모바일 전용 페이지(settings/theme) 추가
  • 각 페이지에 loading.tsx(스켈레톤) 연동

프로필 관리

  • 프로필 선택 모달(ProfileSelectModal), 프로필 카드(ProfileCard) 구현
  • 프로필 추가 2-step 모달(AddProfileModal) 및 모바일 전용 페이지(AddProfilePageContent) 구현
  • 프로필 수정 모달(EditProfileModal) 및 모바일 전용 페이지(EditProfilePageContent) 구현
  • 프로필 삭제 Dialog(DeleteProfileDialog) 분리
  • 프로필 배경 이미지 편집 컴포넌트(ProfileBackgroundImageEditor) 추가

화면 모드 선택

  • 공용 ThemeModeSelector 컴포넌트 구현 (auto/light/dark 이미지 에셋 포함)
  • 모달(ThemeModeModal) / 모바일 페이지(ThemeModePageContent) 두 진입점 지원

활동 동아리 & 서비스 설정

  • 활동 동아리 카드 목록(ActiveClubList) 구현 (모바일 가로 스크롤 / 태블릿 그리드 반응형)
  • 서비스 설정 고객지원 리스트(SupportListItem), 로그아웃/탈퇴 확인 Dialog 분리

스켈레톤 & 반응형

  • 활동정보·출석 세션·프로필 관리·서비스 설정용 스켈레톤 컴포넌트 추가
  • 전 페이지 모바일(360px) / 태블릿(696px) 뷰 분기 적용

기타 공통 개선

  • ButtonprimarySoft variant 추가 및 disabled 스타일 보정
  • Avatar 스타일 보정
  • 아이콘 에셋 5종 추가 (add_round, calendar, mail, phone, setting)
  • useResponsiveGridColumns 훅, date 포맷 유틸 분리
  • 마이페이지 라우트 상수(constants/mypage/routes.ts) 추가

📸 스크린샷 or 실행영상


🎸 기타 사항 or 추가 코멘트

Summary by CodeRabbit

  • New Features
    • 마이페이지에서 프로필, 활동, 게시글, 출석, 설정 화면을 제공하며 프로필 추가/수정/삭제, 로그아웃·탈퇴 확인 흐름과 테마 모드 선택 UI가 추가되었습니다.
    • 프로필 관리 흐름(프로필 선택/추가 단계)과 각 영역용 로딩 스켈레톤, 화면에 맞춘 반응형 레이아웃이 도입되었습니다.
  • Bug Fixes
    • 마이페이지 경로에 따라 모바일 헤더 표시가 개선되었고, 게시글이 없을 때 문구가 필터 결과 기준으로 정확히 표시됩니다.
    • 버튼/지원 항목/아바타 등 UI 옵션이 더 일관되게 동작하도록 보완되었습니다.

woneeeee added 30 commits July 1, 2026 14:54
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@woneeeee, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5c724486-9e7e-47d3-9d29-4b2b62afc1b7

📥 Commits

Reviewing files that changed from the base of the PR and between 8a2ab7e and 133c931.

📒 Files selected for processing (1)
  • src/hooks/mypage/useImagePreview.ts
📝 Walkthrough

Walkthrough

마이페이지 하위 라우트와 레이아웃이 추가되고, 프로필 요약·관리·편집, 게시글/세션, 설정/테마, 공용 토큰과 지원 컴포넌트가 함께 정리됐다. 프로필 중심 화면과 관련된 훅, 스켈레톤, 내비게이션, 헤더 동작도 같이 바뀌었다.

Changes

마이페이지 재구성

Layer / File(s) Summary
라우트와 레이아웃
src/app/(private)/[clubId]/(main)/mypage/**, src/constants/mypage/routes.ts
activity/posts/profiles/sessions/settings/theme 라우트와 loading 컴포넌트, MyPageLayout, MyPageNav, 서브경로 판별 함수가 추가됐다.
프로필 요약과 활동
src/components/mypage/ProfileSection.tsx, MyPageContent.tsx, ActiveClubList.tsx, MyPageActivityContent.tsx, edit/ProfileBackgroundImageEditor.tsx, edit/ProfileImageEditor.tsx, types/mypage.ts, hooks/useResponsiveGridColumns.ts
프로필 섹션이 이미지 에디터와 활동 버튼을 포함하도록 바뀌고, 상단 마이페이지 레이아웃과 활동 카드 그리드가 재구성됐다.
프로필 관리와 편집
ProfileManagementContent.tsx, ProfileCard.tsx, ProfileSelectModal.tsx, DeleteProfileDialog.tsx, AddProfileModal*, EditProfile*.tsx, CharacterCountRow.tsx, hooks/mypage/*, skeleton/ProfileManagementSkeleton.tsx, skeleton/EditProfileSkeleton.tsx
프로필 목록, 추가, 선택, 수정, 삭제 흐름과 관련 폼 훅, 편집 폼, 스켈레톤이 추가됐다.
게시글과 세션
MyPagePostsContent.tsx, MyPageSessionsContent.tsx, BoardContent.tsx, utils/shared/date.ts, skeleton/MyPageSessionsSkeleton.tsx
내가 쓴 글과 출석 세션 화면이 추가되고, 게시글 필터링과 세션 날짜 포맷이 확장됐다.
설정과 테마
MyPageSettingsContent.tsx, ThemeMode*.tsx, SupportListItem.tsx, LogoutConfirmDialog.tsx, WithdrawConfirmDialog.tsx, LeaveClubDropdownMenu.tsx, MyPageDropdownMenu.tsx, skeleton/MyPageSettingsSkeleton.tsx, src/types/theme.ts, src/stores/theme-store.ts, src/components/mypage/ThemeToggle.tsx
서비스 설정 화면, 테마 선택 UI, 로그아웃/탈퇴 다이얼로그, 지원 항목, 테마 타입 연결이 추가됐다.
지원 컴포넌트와 토큰
Header.tsx, ClubInfoCard.tsx, ui/Button.tsx, ui/avatar.tsx, globals.css, assets/icons/index.ts, FormFieldWrapper.tsx, mypage/index.ts, hooks/index.ts, hooks/mypage/index.ts, skeleton/index.ts
모바일 헤더 숨김, 버튼/아바타/색상 토큰, 아이콘 재노출, 폼 필드 라벨 타입, 배럴 export가 갱신됐다.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • Team-Weeth/weeth-client#33: BoardContent의 작성자 필터링/빈 상태 렌더링과 같은 파일의 기존 확장 작업이 겹친다.
  • Team-Weeth/weeth-client#65: ProfileSection, ThemeToggle, useMyPageQueries 중심의 마이페이지 컴포넌트 변경과 직접적으로 연결된다.
  • Team-Weeth/weeth-client#79: EditProfileContent와 편집용 하위 컴포넌트 변경이 같은 마이페이지 편집 흐름에 속한다.

Suggested labels: 🎨 Html&css

Suggested reviewers: dalzzy, nabbang6

Poem

🐰 마이페이지에 새 길이 났네,
프로필은 반짝, 세션은 또각또각,
설정엔 달빛 모드가 살포시,
스켈레톤은 포근한 당근 이불,
토끼는 오늘도 굴 속을 반짝이며 뛰어요 ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 마이페이지 개선뷰 UI 구현이라는 핵심 변경을 직접적으로 요약합니다.
Description check ✅ Passed PR 유형, 이슈번호, Key Changes가 채워져 있어 템플릿의 핵심 요구사항을 대부분 충족합니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/WTH-421-마이페이지-개선뷰-UI-구현

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR E2E 테스트 결과

Playwright: 통과

🎉 E2E 테스트를 통과했습니다!

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-o0tacr1tf-weethsite-4975s-projects.vercel.app

@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/mypage/LeaveClubDropdownMenu.tsx (1)

1-42: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

파일명과 내보내는 컴포넌트명이 불일치: LeaveClubDropdownMenu.tsxMyPageDropdownMenu를 export

PR 스택 아웃라인에는 LeaveClubDropdownMenu.tsxMyPageDropdownMenu.tsx가 서로 다른 컴포넌트를 담은 별도 파일로 명시되어 있습니다. 그런데 실제로는 LeaveClubDropdownMenu.tsx 파일 안에 MyPageDropdownMenu 컴포넌트(탈퇴 확인 다이얼로그를 여는 드롭다운)가 구현되어 있고, LeaveClubDropdownMenu라는 이름의 컴포넌트는 이 파일에 존재하지 않습니다. 파일이 잘못된 위치에 있거나, 두 컴포넌트가 병합/누락된 것으로 보입니다.

파일 내용이 MyPageDropdownMenu.tsx로 옮겨져야 하는지, 혹은 실제로 LeaveClubDropdownMenu 컴포넌트가 별도로 존재하는지 확인 부탁드립니다.

🤖 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 `@src/components/mypage/LeaveClubDropdownMenu.tsx` around lines 1 - 42, The
dropdown component name and file purpose are mismatched: this file currently
defines and exports MyPageDropdownMenu, but the review expects
LeaveClubDropdownMenu here. Verify the intended split between
LeaveClubDropdownMenu and MyPageDropdownMenu, then either rename this
component/export to LeaveClubDropdownMenu or move it into the correct
MyPageDropdownMenu file and keep this file for the leave-club flow. Use the
MyPageDropdownMenu function and its WithdrawConfirmDialog usage to locate the
affected component.
🟡 Minor comments (6)
src/components/ui/avatar.tsx-14-20 (1)

14-20: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

새로 추가된 100/36 사이즈에 대한 fallback 텍스트 크기 규칙 누락

avatarVariants에는 size: 100, size: 36이 추가됐지만, AvatarFallback의 텍스트 크기 클래스는 group-data-[size="24"], 40, 64, 128만 정의되어 있습니다. 새 사이즈로 Avatar를 사용하면 fallback 텍스트가 아무 크기 규칙도 매칭되지 않아 브라우저 기본 크기로 렌더링됩니다.

🐛 제안
         'group-data-[type=round]/avatar:rounded-full',
         'group-data-[type=square]/avatar:rounded-md',
         'group-data-[size="24"]/avatar:text-xs',
         'group-data-[size="40"]/avatar:text-sm',
+        'group-data-[size="36"]/avatar:text-sm',
         'group-data-[size="64"]/avatar:text-sm',
+        'group-data-[size="100"]/avatar:text-base',
         'group-data-[size="128"]/avatar:text-base',

Also applies to: 124-156

🤖 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 `@src/components/ui/avatar.tsx` around lines 14 - 20, `avatarVariants`에 새로 추가된
`size: 100`과 `size: 36`이 `AvatarFallback`의 텍스트 크기 규칙에 연결되지 않아 기본 크기로 렌더링됩니다.
`AvatarFallback`에서 사용하는 size별 class 매핑에 `group-data-[size="100"]`과
`group-data-[size="36"]`를 추가하고, 기존 `group-data-[size="24"]`, `40`, `64`, `128`
규칙과 같은 방식으로 적절한 텍스트 크기를 지정해 `Avatar`와 `AvatarFallback`이 일관되게 보이도록 수정하세요.
src/components/auth/hub/FormFieldWrapper.tsx-11-11 (1)

11-11: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

label이 없을 때 빈 FieldLabel이 렌더링됨

label을 optional로 바꿨지만 FieldLabel은 조건 없이 항상 렌더링되어, label이 없으면 빈 <span>gap-200 레이아웃 공간을 차지하게 됩니다.

🐛 제안
   return (
     <div className="flex flex-col gap-200">
-      <FieldLabel>{label}</FieldLabel>
+      {label && <FieldLabel>{label}</FieldLabel>}
       {children}
       <FieldError message={error} />
     </div>
   );

Also applies to: 16-24

🤖 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 `@src/components/auth/hub/FormFieldWrapper.tsx` at line 11,
`FormFieldWrapper`에서 `label`이 optional인 경우에도 `FieldLabel`이 항상 렌더링되어 빈 공간을 차지하는
문제입니다. `FormFieldWrapper`와 `FieldLabel` 사용부를 확인해 `label`이 있을 때만 라벨 영역을 렌더링하도록
조건부 분기하고, `children` 레이아웃은 `label` 유무에 따라 자연스럽게 유지되도록 정리하세요.
src/components/mypage/edit/ProfileBackgroundImageEditor.tsx-37-61 (1)

37-61: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

언마운트 시 미리보기 objectURL이 해제되지 않아 메모리 누수가 발생할 수 있습니다.

previewUrlRef의 blob URL은 handleChange(새 파일 선택 시) 또는 handleReset 호출 시에만 URL.revokeObjectURL로 해제됩니다. 사용자가 이미지를 선택한 뒤 초기화 없이 모달을 닫거나 다른 페이지로 이동하면 URL이 해제되지 않고 남습니다.

🔧 제안
   const [previewUrl, setPreviewUrl] = useState<string | null>(null);
   const [isReset, setIsReset] = useState(false);
+
+  useEffect(() => {
+    return () => {
+      if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current);
+    };
+  }, []);

(useEffectreact에서 추가로 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 `@src/components/mypage/edit/ProfileBackgroundImageEditor.tsx` around lines 37
- 61, The preview objectURL in ProfileBackgroundImageEditor is only revoked in
handleChange and handleReset, so it can leak when the component unmounts. Add a
useEffect cleanup in ProfileBackgroundImageEditor that revokes
previewUrlRef.current on unmount (and clears the ref), and make sure useEffect
is imported from react alongside useRef/useState.
src/components/mypage/ProfileSection.tsx-39-55 (1)

39-55: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

프로필 섹션의 편집 UI를 숨기거나 저장 흐름에 연결하세요. ProfileSection은 조회 화면인데 카메라 버튼이 노출되고, 여기서는 변경값을 저장할 onFileChange/onResetImage나 배경 이미지 소스가 전달되지 않아 로컬 미리보기로만 끝납니다. inline 편집 의도가 없다면 이 화면에서는 편집 트리거를 제거하고, 의도한 거라면 useUpdateProfileMutation 쪽 저장 경로를 붙여야 합니다.

🤖 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 `@src/components/mypage/ProfileSection.tsx` around lines 39 - 55,
ProfileSection is showing edit affordances without a real save flow, so either
hide the camera/edit triggers in this read-only view or wire them to
persistence. Update the ProfileSection composition around
ProfileBackgroundImageEditor and ProfileImageEditor so they only render editing
UI when inline editing is intended, and if editing should stay, pass the missing
onFileChange/onResetImage handlers and background image source through to
connect the changes to useUpdateProfileMutation instead of leaving them as local
preview-only state.
src/components/mypage/ProfileCard.tsx-58-63 (1)

58-63: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

하드코딩된 색상값 사용

text-[#CECFCF]는 디자인 토큰이 아닌 임의 색상값입니다. 프로젝트의 디자인 토큰 클래스(text-icon-alternative 등 기존 사용 패턴)로 대체해야 합니다.

As per coding guidelines, **/*.{ts,tsx,css}: "Never hardcode design token values; always use token classes from design tokens".

🎨 제안 수정
-            <Icon src={InfoCircleIcon} size={18} className="text-[`#CECFCF`]" />
+            <Icon src={InfoCircleIcon} size={18} className="text-icon-alternative" />
🤖 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 `@src/components/mypage/ProfileCard.tsx` around lines 58 - 63, In ProfileCard,
the empty-club state is using a hardcoded Tailwind color on the InfoCircleIcon
wrapper (`text-[`#CECFCF`]`), which should be replaced with the matching
design-token class used elsewhere in the app. Update the JSX in the clubs.length
=== 0 branch to use the existing token pattern such as `text-icon-alternative`
instead of an arbitrary hex color, keeping the rest of the empty-state markup
unchanged.

Source: Coding guidelines

src/components/mypage/ProfileManagementContent.tsx-35-41 (1)

35-41: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

뒤로가기 버튼에 접근성 라벨이 없습니다.

아이콘만 있는 버튼에 텍스트나 aria-label이 없어 스크린 리더 사용자가 버튼의 목적을 알 수 없습니다. 같은 PR의 AddProfileModalHeader 닫기 버튼은 aria-label="프로필 추가 닫기"를 사용하고 있어 일관성도 떨어집니다.

🛠️ 제안
-        <button
-          type="button"
-          onClick={() => router.back()}
-          className="flex items-center justify-center p-1"
-        >
+        <button
+          type="button"
+          onClick={() => router.back()}
+          className="flex items-center justify-center p-1"
+          aria-label="뒤로 가기"
+        >
🤖 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 `@src/components/mypage/ProfileManagementContent.tsx` around lines 35 - 41, The
back button in ProfileManagementContent is icon-only and missing an accessible
name, so add an aria-label to the button in the same way AddProfileModalHeader
does for its close button. Update the button wrapping Icon and router.back() to
include a descriptive aria-label that tells screen readers it is a back action.
🧹 Nitpick comments (17)
src/app/(private)/[clubId]/(main)/mypage/layout.tsx (1)

9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

pb-[80px] 임의값 대신 spacing 토큰 사용 검토.

같은 클래스 문자열 내에서 px-450, gap-500 등은 프로젝트 spacing 토큰을 사용하는데, pb-[80px]만 임의값(arbitrary value)입니다. 해당 여백에 대응하는 spacing 토큰이 존재한다면 일관성을 위해 토큰 클래스로 교체하는 것이 좋습니다.

♻️ 예시 (해당 토큰이 존재하는 경우)
-<div className="desktop:px-16 tablet:pt-450 mx-auto flex w-full items-start gap-500 px-450 pb-[80px]">
+<div className="desktop:px-16 tablet:pt-450 mx-auto flex w-full items-start gap-500 px-450 pb-800">

As per coding guidelines, **/*.{ts,tsx,css}: "Never hardcode design token values; always use token classes from design tokens".

🤖 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 `@src/app/`(private)/[clubId]/(main)/mypage/layout.tsx at line 9, The
`pb-[80px]` class in the layout wrapper is an arbitrary spacing value and should
be replaced with a design token class for consistency with the existing `px-450`
and `gap-500` usage. Check the spacing tokens available in the same
layout/component styling system and update the `layout.tsx` wrapper className to
use the matching token instead of hardcoded padding, keeping the change
localized to the element with the current `pb-[80px]` value.

Source: Coding guidelines

src/components/mypage/MyPageNav.tsx (1)

20-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

w-[304px] 임의값 - 토큰화 여부 확인 필요.

nav 너비가 임의값으로 하드코딩되어 있습니다. 디자인 시스템에 대응하는 width 토큰이 있다면 해당 토큰 클래스로 교체하는 것을 권장합니다.

As per coding guidelines, **/*.{ts,tsx,css}: "Never hardcode design token values; always use token classes from design tokens".

🤖 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 `@src/components/mypage/MyPageNav.tsx` at line 20, The MyPageNav width is
hardcoded with w-[304px], which should be replaced with the matching design
token class if one exists. Update the className in MyPageNav to use the
appropriate token-based width utility from the design system instead of the
arbitrary value, and keep the existing cn composition intact.

Source: Coding guidelines

src/components/mypage/skeleton/MyPageContentSkeletons.tsx (1)

9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

ring-white가 프로젝트 디자인 토큰인지 확인 필요.

주변 요소들은 bg-container-neutral, bg-container-neutral-alternative 등 프로젝트 정의 토큰을 사용하는 반면, 이 라인은 Tailwind 기본 팔레트 색상인 white를 그대로 사용합니다. 다크모드 등 테마 전환 시 하드코딩된 흰색이 배경과 충돌할 수 있습니다. globals.css에 대응하는 토큰(예: 컨테이너 배경색 관련 토큰)이 있다면 이를 사용하는 것이 좋습니다.

As per coding guidelines, **/*.{ts,tsx,css}: "Never hardcode design token values; always use token classes from design tokens".

🤖 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 `@src/components/mypage/skeleton/MyPageContentSkeletons.tsx` at line 9, The
MyPage skeleton avatar is using a hardcoded Tailwind color class instead of a
design token, so replace the `ring-white` usage in `MyPageContentSkeletons` with
the appropriate token-based ring/background class defined in the project tokens.
Check the surrounding skeleton styles in `MyPageContentSkeletons` and the
available token classes in `globals.css` so the ring matches theme changes and
does not rely on the default `white` palette.

Source: Coding guidelines

src/app/(private)/[clubId]/(main)/mypage/settings/loading.tsx (1)

1-1: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Import 경로 일관성.

profiles/loading.tsx, sessions/loading.tsx는 스켈레톤을 개별 파일 경로에서 직접 import하는데, 이 파일만 barrel(@/components/mypage/skeleton)을 사용합니다. 기능에는 영향이 없으나 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 `@src/app/`(private)/[clubId]/(main)/mypage/settings/loading.tsx at line 1,
Import style is inconsistent in MyPage loading skeletons. Update this
`loading.tsx` to match the sibling `profiles/loading.tsx` and
`sessions/loading.tsx` import pattern by importing `MyPageSettingsSkeleton` from
its direct skeleton file path instead of the barrel
`@/components/mypage/skeleton`. Keep the component usage unchanged; only align
the import source for consistency.
src/components/mypage/ClubInfoCard.tsx (1)

97-109: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

하드코딩된 색상값 bg-[#9095991A] 사용.

새로 추가된 "미설정" 태그에 임의값 hex/alpha 색상이 하드코딩되어 있습니다. 동일 패턴이 MyPageSessionsContent.tsx의 Tag 컴포넌트에도 반복 사용되고 있어, 디자인 토큰 대신 매직 컬러가 여러 곳에 중복되는 상태입니다. 토큰화된 Tag variant(예: variant="muted")로 추출하는 것을 권장합니다.

As per coding guidelines, **/*.{ts,tsx,css}: "Never hardcode design token values; always use token classes from design tokens".

🤖 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 `@src/components/mypage/ClubInfoCard.tsx` around lines 97 - 109, The new “미설정”
Tag is using a hardcoded hex/alpha background class, and the same pattern is
duplicated in MyPageSessionsContent; replace these ad hoc color values with a
tokenized Tag variant such as variant="muted" or an equivalent design-system
class. Update the Tag usage in ClubInfoCard and any matching Tag instances so
the styling comes from the shared Tag component API instead of inline magic
colors.

Source: Coding guidelines

src/components/mypage/index.ts (1)

13-13: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

EditProfilePageContent만 Props 타입 export가 누락됨.

다른 컨텐츠 컴포넌트들(ActiveClubList, AddProfilePageContent, MyPageActivityContent 등)은 모두 컴포넌트와 함께 ...Props 타입도 같이 export하는데, EditProfilePageContent만 타입 없이 export되어 barrel 구성이 비일관적입니다. 소비 측에서 props 타입이 필요하면 별도 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 `@src/components/mypage/index.ts` at line 13, `EditProfilePageContent`만 컴포넌트
export에 대응하는 Props 타입 export가 빠져 있어 barrel 구성이 다른 컨텐츠들과 일관되지 않습니다.
`EditProfilePageContent`의 정의 파일에서 함께 선언된 props 타입(예:
`EditProfilePageContentProps`)을 export하고, `src/components/mypage/index.ts`의
barrel에서도 해당 타입을 같이 re-export하도록 정리해 소비자가 한 곳에서 컴포넌트와 타입을 모두 가져갈 수 있게 해주세요.
src/app/(private)/[clubId]/(main)/mypage/sessions/page.tsx (1)

17-22: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

에러를 조용히 흡수하고 있어 디버깅이 어렵습니다.

catch 블록에서 실제 에러 객체를 로깅하지 않고 고정 메시지만 세팅합니다. 운영 환경에서 출석 조회 실패 원인(네트워크/인증/서버 오류 등)을 추적할 수 없게 됩니다.

♻️ 제안 diff
   } catch {
+    // eslint-disable-next-line no-console
+    console.error('[MyPageSessionsPage] attendanceServerApi.getDetail failed', error);
     errorMessage = '출석 기록을 불러오지 못했습니다.';
   }
🤖 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 `@src/app/`(private)/[clubId]/(main)/mypage/sessions/page.tsx around lines 17 -
22, The catch block in the sessions page is swallowing the real failure by only
setting a fixed error message. Update the try/catch around
attendanceServerApi.getDetail in the page.tsx session loader to capture the
thrown error object and log it with enough context before setting errorMessage,
so failures can be traced in production.
src/components/ui/Button.tsx (1)

10-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

disabled 배경 토큰 불일치

primary/secondary/primarySoftdisabled:bg-button-disabled로 변경됐지만 danger/kakao/apple은 여전히 disabled:bg-button-neutral을 사용합니다. 두 토큰이 의미상 분리된 상태라면 나머지 variant도 일관되게 bg-button-disabled로 맞추는 것이 좋습니다.

♻️ 제안
         danger:
-          'bg-state-error text-text-inverse hover:opacity-90 active:opacity-80 disabled:bg-button-neutral disabled:text-text-disabled',
+          'bg-state-error text-text-inverse hover:opacity-90 active:opacity-80 disabled:bg-button-disabled disabled:text-text-disabled',
         kakao:
-          'bg-[var(--kakao-bg)] text-[var(--kakao-text)] hover:opacity-90 active:opacity-80 disabled:bg-button-neutral disabled:text-text-disabled',
+          'bg-[var(--kakao-bg)] text-[var(--kakao-text)] hover:opacity-90 active:opacity-80 disabled:bg-button-disabled disabled:text-text-disabled',
         apple:
-          'bg-black text-white dark:bg-white dark:text-black hover:opacity-90 active:opacity-80 disabled:bg-button-neutral disabled:text-text-disabled',
+          'bg-black text-white dark:bg-white dark:text-black hover:opacity-90 active:opacity-80 disabled:bg-button-disabled disabled:text-text-disabled',
🤖 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 `@src/components/ui/Button.tsx` around lines 10 - 22, The disabled background
token is inconsistent across button variants in Button.tsx:
primary/secondary/primarySoft already use the dedicated disabled token, but
danger/kakao/apple still fall back to the neutral background. Update the variant
class definitions in the Button component so all disabled states use the same
disabled background token, keeping the styling consistent across every variant.
src/components/mypage/MyPageSettingsContent.tsx (1)

36-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

테마 이동 경로를 상수/헬퍼로 빼서 재사용하세요.
/${clubId}/mypage/settings/theme가 이 파일에만 직접 들어가 있어 경로 변경 시 불일치가 생길 수 있습니다. src/constants/mypage/routes.ts에 전용 export를 추가해 여기서도 그 값을 쓰도록 통일하면 됩니다.

🤖 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 `@src/components/mypage/MyPageSettingsContent.tsx` around lines 36 - 42, The
theme navigation path is hardcoded in the MyPageSettingsContent handler, which
can lead to route drift if it changes later. Move the
`/${clubId}/mypage/settings/theme` value into a shared export in
`src/constants/mypage/routes.ts`, then update the `router.push` call in
`MyPageSettingsContent` to use that constant/utility so the route is reused
consistently.
src/components/mypage/ThemeModeSelector.tsx (2)

11-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ThemeMode 타입이 theme-store.ts와 중복 정의됨

컨텍스트 스니펫에서 src/stores/theme-store.ts가 이미 동일한 type ThemeMode = 'auto' | 'light' | 'dark';를 정의하고 있습니다. 이 파일에서 다시 별도로 선언하면, 향후 한쪽만 값이 추가/변경될 경우 타입 불일치가 발생할 수 있습니다. store에서 export하여 재사용하는 편이 안전합니다.

🤖 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 `@src/components/mypage/ThemeModeSelector.tsx` at line 11, `ThemeMode` is
duplicated in `ThemeModeSelector`, so the component can drift from `theme-store`
over time. Reuse the single source of truth by exporting the `ThemeMode` type
from `theme-store.ts` and importing it into `ThemeModeSelector` instead of
redefining it locally. Update the component’s type references to use the shared
`ThemeMode` symbol so both files stay consistent.

41-44: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Tailwind 클래스 중복(tablet:tablet:) 오타

tablet:tablet:h-[72px]처럼 tablet: variant가 두 번 중첩되어 있습니다. 결과적으로 동작은 유지되지만(중첩 variant가 동일 조건이라 무의미하게 재적용됨) 의도한 클래스가 아닌 것으로 보입니다.

🧹 제안
-          'tablet:tablet:h-[72px] tablet:w-[104px] h-[110px] w-[160px] overflow-hidden rounded-[10px] transition-colors',
+          'tablet:h-[72px] tablet:w-[104px] h-[110px] w-[160px] overflow-hidden rounded-[10px] transition-colors',
🤖 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 `@src/components/mypage/ThemeModeSelector.tsx` around lines 41 - 44, The
className in ThemeModeSelector has a duplicated Tailwind variant prefix, so fix
the typo by removing the extra tablet: from the h-[72px] utility. Update the cn
call in ThemeModeSelector so the responsive height class uses the intended
single tablet variant and keep the rest of the selected ring styling unchanged.
src/components/mypage/ThemeModePageContent.tsx (1)

34-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

뒤로가기 버튼에 aria-label 누락

ThemeModeModal.tsx의 닫기 버튼은 aria-label="화면 모드 닫기"를 갖고 있는데(컨텍스트 스니펫), 동일한 기능을 하는 이 뒤로가기 버튼에는 접근 가능한 이름이 없습니다. 스크린리더 사용자에게 버튼의 의미가 전달되지 않습니다.

♿ 제안
 <button
   type="button"
   onClick={() => router.back()}
   className="flex items-center justify-center p-1"
+  aria-label="뒤로 가기"
 >
🤖 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 `@src/components/mypage/ThemeModePageContent.tsx` around lines 34 - 42, The
back button in ThemeModePageContent lacks an accessible name, so add an
aria-label to the button that uses router.back() and keep it consistent with the
close button pattern used elsewhere (for example, ThemeModeModal’s labeled close
action). Update the button element near the BackIcon so screen readers can
announce its purpose clearly, while preserving the existing click behavior and
styling.
src/components/mypage/ProfileSection.tsx (1)

105-135: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

라우트 경로 하드코딩 대신 마이페이지 라우트 상수 사용을 권장합니다.

/${clubId}/mypage/posts, /${clubId}/mypage/sessions가 문자열로 하드코딩되어 있습니다. PR 요약에 따르면 src/constants/mypage/routes.ts에 마이페이지 라우트 상수가 별도로 추가되었으므로, 해당 상수를 재사용하면 경로 변경 시 여러 곳을 동시에 수정하지 않아도 됩니다.

🤖 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 `@src/components/mypage/ProfileSection.tsx` around lines 105 - 135, The
ProfileSection navigation paths are hardcoded instead of using the shared mypage
route constants. Update the onClick handlers in ProfileSection to reference the
route symbols from the new mypage routes module, replacing the literal
`/${clubId}/mypage/posts` and `/${clubId}/mypage/sessions` strings with the
corresponding constants so route changes stay centralized.
src/components/mypage/edit/ProfileBackgroundImageEditor.tsx (1)

1-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

ProfileImageEditor와 파일 선택/미리보기/초기화 로직이 거의 동일하게 중복됩니다.

fileInputRef, previewUrlRef, isReset 상태 관리와 handleChange/handleReset 로직이 src/components/mypage/edit/ProfileImageEditor.tsx와 사실상 동일합니다. 공통 훅(예: useImageUploadPreview)으로 추출하면 두 컴포넌트 간 동작 불일치(예: cleanup 누락)를 한 곳에서 관리할 수 있습니다.

🤖 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 `@src/components/mypage/edit/ProfileBackgroundImageEditor.tsx` around lines 1 -
119, `ProfileBackgroundImageEditor` is duplicating the same file input, preview
URL, reset state, and cleanup logic already used by `ProfileImageEditor`.
Extract the shared behavior into a reusable hook such as `useImageUploadPreview`
that owns `fileInputRef`, `previewUrlRef`, `isReset`, `handleChange`, and
`handleReset`, then have both editors consume it. Keep the component-specific UI
in `ProfileBackgroundImageEditor` but delegate all upload/preview/reset state
transitions to the shared hook to prevent divergence and ensure consistent
cleanup.
src/components/mypage/MyPageActivityContent.tsx (1)

10-11: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

하드코딩된 314 값을 TABLET_CARD_WIDTH 상수로 통일 권장.

TABLET_CARD_WIDTH 상수가 정의되어 있음에도 그리드 템플릿(line 38)과 mobileCardClassName(line 24)에서 동일 값 314가 별도로 하드코딩되어, 상수 값 변경 시 두 곳이 서로 불일치할 위험이 있습니다.

♻️ 제안 리팩터
           style={{
             gridTemplateColumns: isSingleColumn
               ? 'minmax(0, 1fr)'
-              : `repeat(${columnCount}, 314px)`,
+              : `repeat(${columnCount}, ${TABLET_CARD_WIDTH}px)`,
           }}

Also applies to: 21-24, 35-39

🤖 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 `@src/components/mypage/MyPageActivityContent.tsx` around lines 10 - 11, The
hardcoded 314 value is duplicated even though TABLET_CARD_WIDTH already exists,
so update MyPageActivityContent to use TABLET_CARD_WIDTH consistently in
mobileCardClassName and the grid template logic instead of inline literals. Keep
the layout calculations centralized around TABLET_CARD_WIDTH (and
TABLET_CARD_GAP where applicable) so future width changes only need one update.
src/components/mypage/ProfileCard.tsx (1)

25-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

라우트 경로 하드코딩 대신 상수 사용 권장

/${clubId}/mypage/profiles/${profile.id}/edit 경로를 직접 문자열로 작성했습니다. 마이페이지 전용 라우트 상수 파일(src/constants/mypage/routes.ts)이 이번 PR 스택에 추가된 것으로 보이는데, 해당 상수를 재사용하면 경로 변경 시 여러 파일을 동시에 수정해야 하는 부담을 줄일 수 있습니다.

🤖 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 `@src/components/mypage/ProfileCard.tsx` around lines 25 - 32,
`handleOpenEditProfile` in `ProfileCard` is using a hardcoded edit route string;
replace `/${clubId}/mypage/profiles/${profile.id}/edit` with the existing mypage
route constant from `src/constants/mypage/routes.ts` so routing stays
centralized. Update the `router.push` call to build the path through that
constant and the relevant `clubId`/`profile.id` params, keeping the modal
behavior unchanged for non-tablet screens.
src/components/mypage/edit/EditProfileContent.tsx (1)

33-34: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

죽은 이미지 상태 변수 제거 selectedFile/resetToDefault가 이 화면에서 쓰이지 않아 hasChanges와 submit payload의 이미지 분기(33-34, 75, 96-97)가 불필요합니다. 제거해 주세요.

🤖 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 `@src/components/mypage/edit/EditProfileContent.tsx` around lines 33 - 34, The
image state in EditProfileContent is dead code: selectedFile and resetToDefault
are never used, so remove these unused variables and the related image branching
in hasChanges and the submit payload. Update EditProfileContent to rely only on
the actual editable fields, and clean up any dependent logic that references
selectedFile/resetToDefault so the form state and submit handling stay
consistent.
🤖 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 `@src/components/mypage/AddProfileModal.tsx`:
- Line 36: The AddProfileModal Dialog is wired directly to the parent setter, so
closing via outside click or Escape bypasses resetFlow and leaves stale
step/input/club state. Update the Dialog onOpenChange handling in
AddProfileModal to wrap the existing setter and call resetFlow whenever the
dialog transitions to closed, while still preserving the open-state update
behavior for the parent.

In `@src/components/mypage/AddProfileModal/StepOneContent.tsx`:
- Around line 42-43: The Avatar styling in StepOneContent currently hardcodes
the hex color class bg-[`#EFF1F1`], which should be replaced with an existing
design token class. Update the avatarClassName and fallbackClassName props to
use the appropriate token-based background class (for example, a neutral
container token if that matches the design system) and keep the change localized
to the Avatar usage in StepOneContent.
- Around line 14-21: `StepOneContent` and `useAddProfileFlow` only handle
`name`/`bio`, so selected profile/background images are dropped before
submission. Extend `useAddProfileFlow` to store the chosen files and pass those
handlers/state through to `StepOneContent`, then wire
`ProfileBackgroundImageEditor` and `ProfileImageEditor` via their `onFileChange`
props so the selected images persist to the next step and final submit.

In `@src/components/mypage/AddProfileModal/StepTwoContent.tsx`:
- Around line 7-12: `StepTwoContent` is still rendering the hardcoded
`MOCK_CLUBS` list instead of the user’s real clubs. Update `StepTwoContent` to
consume the club data returned by `useMyPageQueries` and render that data
directly, using the existing club identifiers and names from the query response.
Also remove the fixed `selectedClubIds` defaults in `useAddProfileFlow` and
initialize selection from the fetched club list so the add-profile step reflects
the current user’s actual clubs.

In `@src/components/mypage/EditProfileModal.tsx`:
- Around line 26-40: `handleClose` and `handleDelete` in `EditProfileModal` only
reset state and close the modal, so the desktop profile edit flow never persists
changes or performs a real delete. Wire `useUpdateProfileMutation` into the save
path so `완료` triggers the profile update submit before closing, and split
deletion into a separate delete handler/flow instead of reusing `handleClose`;
keep the existing `resetToProfile`, `onOpenChange`, and `handleOpenDeleteDialog`
behavior only for cancel/close transitions.

In `@src/components/mypage/EditProfilePageContent.tsx`:
- Around line 28-37: `EditProfilePageContent`의 `handleClose`/`handleDelete`는 현재
UI만 닫고 `router.back()`만 해서 서버 변경이 반영되지 않습니다. `완료` 동작에는 실제 프로필 업데이트 뮤테이션을, `삭제`
동작에는 실제 삭제 뮤테이션을 연결하고, 성공 후에만 닫기/이동하도록 바꾸세요. `handleClose`, `handleDelete`, 그리고
이 컴포넌트에서 사용하는 저장/삭제 처리 함수(또는 관련 mutation hook) 흐름을 찾아 호출하도록 수정하면 됩니다.

In `@src/components/mypage/MyPageContent.tsx`:
- Around line 20-80: The `MyPageContent` render logic duplicates the same `me ?
ProfileSection : ProfileSectionSkeleton` branch and `ActiveClubList` in both
desktop/tablet and mobile blocks. Extract that shared JSX into a local variable
or small helper within `MyPageContent`, then reuse it in both layouts so changes
to `ProfileSection` props or `ActiveClubList` stay in sync.

In `@src/components/mypage/MyPageSessionsContent.tsx`:
- Line 65: The Tag styling in MyPageSessionsContent is using a hardcoded
hex+alpha background value instead of a design token class. Replace
bg-[`#9095991A`] with the appropriate existing token-based background/overlay
class used elsewhere in the design system, and apply the same fix to the other
Tag occurrence referenced in MyPageSessionsContent so both instances rely on
tokens rather than inline color values.

In `@src/components/mypage/MyPageSettingsContent.tsx`:
- Line 120: WithdrawConfirmDialog in MyPageSettingsContent is missing the actual
confirm action, so confirming only closes the modal instead of triggering
withdrawal. Update the MyPageSettingsContent component to pass the real 탈퇴
handler into WithdrawConfirmDialog via onConfirm, wiring it to the existing
withdraw API/hook logic used in this page, while keeping onOpenChange for modal
state control.

In `@src/components/mypage/ProfileCard.tsx`:
- Around line 86-96: The ProfileSelectModal in ProfileCard is missing the
profile-switch confirmation callback, so confirming only closes the modal
without changing the profile. Update the ProfileCard component to pass the
appropriate onConfirm handler into ProfileSelectModal, using the existing
selectedClub/currentProfileId/availableProfiles state and the modal’s existing
onOpenChange flow so the switch action is executed when the user confirms.

In `@src/components/mypage/ProfileManagementContent.tsx`:
- Around line 65-121: The mock ProfileCard entries labeled as TODO: mock are
being rendered unconditionally in ProfileManagementContent, so real users will
always see fake profiles alongside the actual club list. Remove the mock-empty
and mock-multi ProfileCard blocks from the production render path in
ProfileManagementContent and keep only the real clubs.map(...) output; if you
still need empty/multi state checks, move those cases into Storybook or tests
using the same ProfileCard component.

In `@src/constants/mypage/routes.ts`:
- Around line 10-32: `shouldHideMobileHeaderOnMyPage` and
`MOBILE_HEADER_HIDDEN_MYPAGE_PATHS` contain redundant and unreachable checks.
Remove the dead `/mypage/profiles/add` and the `new RegExp(...)` branch from
`shouldHideMobileHeaderOnMyPage`, and verify whether `/mypage/edit` in
`MOBILE_HEADER_HIDDEN_MYPAGE_PATHS` is a valid route or leftover code; keep the
logic centered on the existing `MYPAGE_PROFILE_SUB_PATHS` and
`MYPAGE_SETTINGS_SUB_PATHS` symbols so matching remains correct and simpler.

In `@src/hooks/mypage/useAddProfileFlow.ts`:
- Around line 8-59: useAddProfileFlow currently only manages local step state
and selections, but the profile create mutation is never invoked. Update
useAddProfileFlow and the handleConfirm flow in AddProfileModal.tsx and
AddProfilePageContent.tsx so the collected name, bio, and selectedClubIds are
passed into the add-profile mutation before calling resetFlow() and closing.
Keep the existing step/selection logic, but ensure the confirm action actually
persists the profile data.

In `@src/hooks/mypage/useEditProfileForm.ts`:
- Around line 8-11: `useEditProfileForm` currently only tracks text fields, and
the edit flow is not wired to persist image changes or form submission. Update
`useEditProfileForm` and the `EditProfileFormContent` integration so the image
editor exposes and forwards `onFileChange` into form state, and make the `완료`
action trigger the actual submit/save path instead of only closing the dialog.
Use the existing `EditProfileFormValuesSource` and related submit handlers to
keep the image, name, and bio values in sync through save.

In `@src/types/mypage.ts`:
- Around line 20-28: Profile background image is missing from the response and
render flow, so add backgroundImageUrl to ProfileData as well as the MyMember
and getMe response types. Then update MyPageContent and the ProfileSection data
path so the background image is passed through to ProfileBackgroundImageEditor
and persists after refresh. Use the existing ProfileData, MyMember, getMe,
MyPageContent, ProfileSection, and ProfileBackgroundImageEditor symbols to wire
the new field through consistently.

---

Outside diff comments:
In `@src/components/mypage/LeaveClubDropdownMenu.tsx`:
- Around line 1-42: The dropdown component name and file purpose are mismatched:
this file currently defines and exports MyPageDropdownMenu, but the review
expects LeaveClubDropdownMenu here. Verify the intended split between
LeaveClubDropdownMenu and MyPageDropdownMenu, then either rename this
component/export to LeaveClubDropdownMenu or move it into the correct
MyPageDropdownMenu file and keep this file for the leave-club flow. Use the
MyPageDropdownMenu function and its WithdrawConfirmDialog usage to locate the
affected component.

---

Minor comments:
In `@src/components/auth/hub/FormFieldWrapper.tsx`:
- Line 11: `FormFieldWrapper`에서 `label`이 optional인 경우에도 `FieldLabel`이 항상 렌더링되어 빈
공간을 차지하는 문제입니다. `FormFieldWrapper`와 `FieldLabel` 사용부를 확인해 `label`이 있을 때만 라벨 영역을
렌더링하도록 조건부 분기하고, `children` 레이아웃은 `label` 유무에 따라 자연스럽게 유지되도록 정리하세요.

In `@src/components/mypage/edit/ProfileBackgroundImageEditor.tsx`:
- Around line 37-61: The preview objectURL in ProfileBackgroundImageEditor is
only revoked in handleChange and handleReset, so it can leak when the component
unmounts. Add a useEffect cleanup in ProfileBackgroundImageEditor that revokes
previewUrlRef.current on unmount (and clears the ref), and make sure useEffect
is imported from react alongside useRef/useState.

In `@src/components/mypage/ProfileCard.tsx`:
- Around line 58-63: In ProfileCard, the empty-club state is using a hardcoded
Tailwind color on the InfoCircleIcon wrapper (`text-[`#CECFCF`]`), which should be
replaced with the matching design-token class used elsewhere in the app. Update
the JSX in the clubs.length === 0 branch to use the existing token pattern such
as `text-icon-alternative` instead of an arbitrary hex color, keeping the rest
of the empty-state markup unchanged.

In `@src/components/mypage/ProfileManagementContent.tsx`:
- Around line 35-41: The back button in ProfileManagementContent is icon-only
and missing an accessible name, so add an aria-label to the button in the same
way AddProfileModalHeader does for its close button. Update the button wrapping
Icon and router.back() to include a descriptive aria-label that tells screen
readers it is a back action.

In `@src/components/mypage/ProfileSection.tsx`:
- Around line 39-55: ProfileSection is showing edit affordances without a real
save flow, so either hide the camera/edit triggers in this read-only view or
wire them to persistence. Update the ProfileSection composition around
ProfileBackgroundImageEditor and ProfileImageEditor so they only render editing
UI when inline editing is intended, and if editing should stay, pass the missing
onFileChange/onResetImage handlers and background image source through to
connect the changes to useUpdateProfileMutation instead of leaving them as local
preview-only state.

In `@src/components/ui/avatar.tsx`:
- Around line 14-20: `avatarVariants`에 새로 추가된 `size: 100`과 `size: 36`이
`AvatarFallback`의 텍스트 크기 규칙에 연결되지 않아 기본 크기로 렌더링됩니다. `AvatarFallback`에서 사용하는
size별 class 매핑에 `group-data-[size="100"]`과 `group-data-[size="36"]`를 추가하고, 기존
`group-data-[size="24"]`, `40`, `64`, `128` 규칙과 같은 방식으로 적절한 텍스트 크기를 지정해
`Avatar`와 `AvatarFallback`이 일관되게 보이도록 수정하세요.

---

Nitpick comments:
In `@src/app/`(private)/[clubId]/(main)/mypage/layout.tsx:
- Line 9: The `pb-[80px]` class in the layout wrapper is an arbitrary spacing
value and should be replaced with a design token class for consistency with the
existing `px-450` and `gap-500` usage. Check the spacing tokens available in the
same layout/component styling system and update the `layout.tsx` wrapper
className to use the matching token instead of hardcoded padding, keeping the
change localized to the element with the current `pb-[80px]` value.

In `@src/app/`(private)/[clubId]/(main)/mypage/sessions/page.tsx:
- Around line 17-22: The catch block in the sessions page is swallowing the real
failure by only setting a fixed error message. Update the try/catch around
attendanceServerApi.getDetail in the page.tsx session loader to capture the
thrown error object and log it with enough context before setting errorMessage,
so failures can be traced in production.

In `@src/app/`(private)/[clubId]/(main)/mypage/settings/loading.tsx:
- Line 1: Import style is inconsistent in MyPage loading skeletons. Update this
`loading.tsx` to match the sibling `profiles/loading.tsx` and
`sessions/loading.tsx` import pattern by importing `MyPageSettingsSkeleton` from
its direct skeleton file path instead of the barrel
`@/components/mypage/skeleton`. Keep the component usage unchanged; only align
the import source for consistency.

In `@src/components/mypage/ClubInfoCard.tsx`:
- Around line 97-109: The new “미설정” Tag is using a hardcoded hex/alpha
background class, and the same pattern is duplicated in MyPageSessionsContent;
replace these ad hoc color values with a tokenized Tag variant such as
variant="muted" or an equivalent design-system class. Update the Tag usage in
ClubInfoCard and any matching Tag instances so the styling comes from the shared
Tag component API instead of inline magic colors.

In `@src/components/mypage/edit/EditProfileContent.tsx`:
- Around line 33-34: The image state in EditProfileContent is dead code:
selectedFile and resetToDefault are never used, so remove these unused variables
and the related image branching in hasChanges and the submit payload. Update
EditProfileContent to rely only on the actual editable fields, and clean up any
dependent logic that references selectedFile/resetToDefault so the form state
and submit handling stay consistent.

In `@src/components/mypage/edit/ProfileBackgroundImageEditor.tsx`:
- Around line 1-119: `ProfileBackgroundImageEditor` is duplicating the same file
input, preview URL, reset state, and cleanup logic already used by
`ProfileImageEditor`. Extract the shared behavior into a reusable hook such as
`useImageUploadPreview` that owns `fileInputRef`, `previewUrlRef`, `isReset`,
`handleChange`, and `handleReset`, then have both editors consume it. Keep the
component-specific UI in `ProfileBackgroundImageEditor` but delegate all
upload/preview/reset state transitions to the shared hook to prevent divergence
and ensure consistent cleanup.

In `@src/components/mypage/index.ts`:
- Line 13: `EditProfilePageContent`만 컴포넌트 export에 대응하는 Props 타입 export가 빠져 있어
barrel 구성이 다른 컨텐츠들과 일관되지 않습니다. `EditProfilePageContent`의 정의 파일에서 함께 선언된 props
타입(예: `EditProfilePageContentProps`)을 export하고,
`src/components/mypage/index.ts`의 barrel에서도 해당 타입을 같이 re-export하도록 정리해 소비자가 한
곳에서 컴포넌트와 타입을 모두 가져갈 수 있게 해주세요.

In `@src/components/mypage/MyPageActivityContent.tsx`:
- Around line 10-11: The hardcoded 314 value is duplicated even though
TABLET_CARD_WIDTH already exists, so update MyPageActivityContent to use
TABLET_CARD_WIDTH consistently in mobileCardClassName and the grid template
logic instead of inline literals. Keep the layout calculations centralized
around TABLET_CARD_WIDTH (and TABLET_CARD_GAP where applicable) so future width
changes only need one update.

In `@src/components/mypage/MyPageNav.tsx`:
- Line 20: The MyPageNav width is hardcoded with w-[304px], which should be
replaced with the matching design token class if one exists. Update the
className in MyPageNav to use the appropriate token-based width utility from the
design system instead of the arbitrary value, and keep the existing cn
composition intact.

In `@src/components/mypage/MyPageSettingsContent.tsx`:
- Around line 36-42: The theme navigation path is hardcoded in the
MyPageSettingsContent handler, which can lead to route drift if it changes
later. Move the `/${clubId}/mypage/settings/theme` value into a shared export in
`src/constants/mypage/routes.ts`, then update the `router.push` call in
`MyPageSettingsContent` to use that constant/utility so the route is reused
consistently.

In `@src/components/mypage/ProfileCard.tsx`:
- Around line 25-32: `handleOpenEditProfile` in `ProfileCard` is using a
hardcoded edit route string; replace
`/${clubId}/mypage/profiles/${profile.id}/edit` with the existing mypage route
constant from `src/constants/mypage/routes.ts` so routing stays centralized.
Update the `router.push` call to build the path through that constant and the
relevant `clubId`/`profile.id` params, keeping the modal behavior unchanged for
non-tablet screens.

In `@src/components/mypage/ProfileSection.tsx`:
- Around line 105-135: The ProfileSection navigation paths are hardcoded instead
of using the shared mypage route constants. Update the onClick handlers in
ProfileSection to reference the route symbols from the new mypage routes module,
replacing the literal `/${clubId}/mypage/posts` and `/${clubId}/mypage/sessions`
strings with the corresponding constants so route changes stay centralized.

In `@src/components/mypage/skeleton/MyPageContentSkeletons.tsx`:
- Line 9: The MyPage skeleton avatar is using a hardcoded Tailwind color class
instead of a design token, so replace the `ring-white` usage in
`MyPageContentSkeletons` with the appropriate token-based ring/background class
defined in the project tokens. Check the surrounding skeleton styles in
`MyPageContentSkeletons` and the available token classes in `globals.css` so the
ring matches theme changes and does not rely on the default `white` palette.

In `@src/components/mypage/ThemeModePageContent.tsx`:
- Around line 34-42: The back button in ThemeModePageContent lacks an accessible
name, so add an aria-label to the button that uses router.back() and keep it
consistent with the close button pattern used elsewhere (for example,
ThemeModeModal’s labeled close action). Update the button element near the
BackIcon so screen readers can announce its purpose clearly, while preserving
the existing click behavior and styling.

In `@src/components/mypage/ThemeModeSelector.tsx`:
- Line 11: `ThemeMode` is duplicated in `ThemeModeSelector`, so the component
can drift from `theme-store` over time. Reuse the single source of truth by
exporting the `ThemeMode` type from `theme-store.ts` and importing it into
`ThemeModeSelector` instead of redefining it locally. Update the component’s
type references to use the shared `ThemeMode` symbol so both files stay
consistent.
- Around line 41-44: The className in ThemeModeSelector has a duplicated
Tailwind variant prefix, so fix the typo by removing the extra tablet: from the
h-[72px] utility. Update the cn call in ThemeModeSelector so the responsive
height class uses the intended single tablet variant and keep the rest of the
selected ring styling unchanged.

In `@src/components/ui/Button.tsx`:
- Around line 10-22: The disabled background token is inconsistent across button
variants in Button.tsx: primary/secondary/primarySoft already use the dedicated
disabled token, but danger/kakao/apple still fall back to the neutral
background. Update the variant class definitions in the Button component so all
disabled states use the same disabled background token, keeping the styling
consistent across every variant.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ed2a2567-d275-481e-8e7f-04ff5d465bea

📥 Commits

Reviewing files that changed from the base of the PR and between 5d79a1e and 370938e.

⛔ Files ignored due to path filters (8)
  • src/assets/icons/add_round.svg is excluded by !**/*.svg
  • src/assets/icons/calendar.svg is excluded by !**/*.svg
  • src/assets/icons/mail.svg is excluded by !**/*.svg
  • src/assets/icons/phone.svg is excluded by !**/*.svg
  • src/assets/icons/setting.svg is excluded by !**/*.svg
  • src/assets/image/theme/theme_auto.png is excluded by !**/*.png
  • src/assets/image/theme/theme_dark.png is excluded by !**/*.png
  • src/assets/image/theme/theme_light.png is excluded by !**/*.png
📒 Files selected for processing (74)
  • src/app/(private)/[clubId]/(main)/mypage/activity/loading.tsx
  • src/app/(private)/[clubId]/(main)/mypage/activity/page.tsx
  • src/app/(private)/[clubId]/(main)/mypage/layout.tsx
  • src/app/(private)/[clubId]/(main)/mypage/posts/page.tsx
  • src/app/(private)/[clubId]/(main)/mypage/profiles/[profileId]/edit/page.tsx
  • src/app/(private)/[clubId]/(main)/mypage/profiles/add/page.tsx
  • src/app/(private)/[clubId]/(main)/mypage/profiles/loading.tsx
  • src/app/(private)/[clubId]/(main)/mypage/profiles/page.tsx
  • src/app/(private)/[clubId]/(main)/mypage/sessions/loading.tsx
  • src/app/(private)/[clubId]/(main)/mypage/sessions/page.tsx
  • src/app/(private)/[clubId]/(main)/mypage/settings/loading.tsx
  • src/app/(private)/[clubId]/(main)/mypage/settings/page.tsx
  • src/app/(private)/[clubId]/(main)/mypage/settings/theme/page.tsx
  • src/app/globals.css
  • src/assets/icons/index.ts
  • src/components/auth/hub/FormFieldWrapper.tsx
  • src/components/board/BoardContent.tsx
  • src/components/board/CategorySelector.tsx
  • src/components/layout/header/Header.tsx
  • src/components/mypage/ActiveClubList.tsx
  • src/components/mypage/AddProfileModal.tsx
  • src/components/mypage/AddProfileModal/Header.tsx
  • src/components/mypage/AddProfileModal/StepOneContent.tsx
  • src/components/mypage/AddProfileModal/StepTwoContent.tsx
  • src/components/mypage/AddProfilePageContent.tsx
  • src/components/mypage/CharacterCountRow.tsx
  • src/components/mypage/ClubInfoCard.tsx
  • src/components/mypage/DeleteProfileDialog.tsx
  • src/components/mypage/EditProfileFormContent.tsx
  • src/components/mypage/EditProfileModal.tsx
  • src/components/mypage/EditProfilePageContent.tsx
  • src/components/mypage/InfoSection.tsx
  • src/components/mypage/LeaveClubDropdownMenu.tsx
  • src/components/mypage/LogoutConfirmDialog.tsx
  • src/components/mypage/MyPageActivityContent.tsx
  • src/components/mypage/MyPageContent.tsx
  • src/components/mypage/MyPageDropdownMenu.tsx
  • src/components/mypage/MyPageNav.tsx
  • src/components/mypage/MyPagePostsContent.tsx
  • src/components/mypage/MyPageSessionsContent.tsx
  • src/components/mypage/MyPageSettingsContent.tsx
  • src/components/mypage/ProfileCard.tsx
  • src/components/mypage/ProfileManagementContent.tsx
  • src/components/mypage/ProfileSection.tsx
  • src/components/mypage/ProfileSelectModal.tsx
  • src/components/mypage/SetCardinalModal/index.tsx
  • src/components/mypage/SupportListItem.tsx
  • src/components/mypage/ThemeModeModal.tsx
  • src/components/mypage/ThemeModePageContent.tsx
  • src/components/mypage/ThemeModeSelector.tsx
  • src/components/mypage/WithdrawConfirmDialog.tsx
  • src/components/mypage/edit/EditProfileContent.tsx
  • src/components/mypage/edit/PersonalInfoFields.tsx
  • src/components/mypage/edit/ProfileBackgroundImageEditor.tsx
  • src/components/mypage/edit/ProfileImageEditor.tsx
  • src/components/mypage/index.ts
  • src/components/mypage/skeleton/EditProfileSkeleton.tsx
  • src/components/mypage/skeleton/MyPageActivitySkeleton.tsx
  • src/components/mypage/skeleton/MyPageContentSkeletons.tsx
  • src/components/mypage/skeleton/MyPageSessionsSkeleton.tsx
  • src/components/mypage/skeleton/MyPageSettingsSkeleton.tsx
  • src/components/mypage/skeleton/ProfileManagementSkeleton.tsx
  • src/components/mypage/skeleton/index.ts
  • src/components/ui/Button.tsx
  • src/components/ui/avatar.tsx
  • src/constants/mypage/profile.ts
  • src/constants/mypage/routes.ts
  • src/hooks/index.ts
  • src/hooks/mypage/index.ts
  • src/hooks/mypage/useAddProfileFlow.ts
  • src/hooks/mypage/useEditProfileForm.ts
  • src/hooks/useResponsiveGridColumns.ts
  • src/types/mypage.ts
  • src/utils/shared/date.ts
💤 Files with no reviewable changes (3)
  • src/components/mypage/MyPageDropdownMenu.tsx
  • src/components/board/CategorySelector.tsx
  • src/components/mypage/edit/PersonalInfoFields.tsx

};

return (
<Dialog open={open} onOpenChange={onOpenChange}>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Dialog를 외부 클릭/Escape로 닫으면 플로우가 리셋되지 않습니다.

onOpenChange에 부모의 원본 setter가 그대로 연결되어 있어, 닫기 버튼이 아닌 외부 클릭이나 Escape로 닫힐 경우 resetFlow()가 호출되지 않습니다. 이후 모달을 다시 열면 이전 step, 입력값, 선택된 club이 그대로 남아 있게 됩니다.

🐛 제안
-    <Dialog open={open} onOpenChange={onOpenChange}>
+    <Dialog
+      open={open}
+      onOpenChange={(next) => (next ? onOpenChange(next) : handleClose())}
+    >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Dialog open={open} onOpenChange={onOpenChange}>
<Dialog
open={open}
onOpenChange={(next) => (next ? onOpenChange(next) : handleClose())}
>
🤖 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 `@src/components/mypage/AddProfileModal.tsx` at line 36, The AddProfileModal
Dialog is wired directly to the parent setter, so closing via outside click or
Escape bypasses resetFlow and leaves stale step/input/club state. Update the
Dialog onOpenChange handling in AddProfileModal to wrap the existing setter and
call resetFlow whenever the dialog transitions to closed, while still preserving
the open-state update behavior for the parent.

Comment on lines +14 to +21
interface StepOneContentProps {
control: Control<Pick<EditProfileFormData, 'name' | 'bio'>>;
errors: FieldErrors<Pick<EditProfileFormData, 'name' | 'bio'>>;
onCancel: () => void;
onNext: () => void;
cancelAsDialogClose?: boolean;
mobileFixedFooter?: boolean;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## files\n'
git ls-files | rg 'src/components/mypage/AddProfileModal/StepOneContent\.tsx|useAddProfileFlow\.ts|ProfileBackgroundImageEditor|ProfileImageEditor'

printf '\n## references\n'
rg -n "ProfileBackgroundImageEditor|ProfileImageEditor|onFileChange|useAddProfileFlow|StepOneContent" src

Repository: Team-Weeth/weeth-client

Length of output: 4453


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map likely related files if the first search is too broad.
fd -a "StepOneContent.tsx|useAddProfileFlow.ts|ProfileBackgroundImageEditor|ProfileImageEditor" src

Repository: Team-Weeth/weeth-client

Length of output: 442


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## StepOneContent\n'
sed -n '1,220p' src/components/mypage/AddProfileModal/StepOneContent.tsx

printf '\n## useAddProfileFlow\n'
sed -n '1,220p' src/hooks/mypage/useAddProfileFlow.ts

printf '\n## AddProfilePageContent\n'
sed -n '1,220p' src/components/mypage/AddProfilePageContent.tsx

printf '\n## AddProfileModal\n'
sed -n '1,220p' src/components/mypage/AddProfileModal.tsx

Repository: Team-Weeth/weeth-client

Length of output: 9831


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## StepOneContent outline\n'
ast-grep outline src/components/mypage/AddProfileModal/StepOneContent.tsx --view expanded

printf '\n## useAddProfileFlow outline\n'
ast-grep outline src/hooks/mypage/useAddProfileFlow.ts --view expanded

printf '\n## editors outline\n'
ast-grep outline src/components/mypage/edit/ProfileImageEditor.tsx --view expanded
ast-grep outline src/components/mypage/edit/ProfileBackgroundImageEditor.tsx --view expanded

Repository: Team-Weeth/weeth-client

Length of output: 1857


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## step two\n'
sed -n '1,260p' src/components/mypage/AddProfileModal/StepTwoContent.tsx

printf '\n## search add profile submit/image fields\n'
rg -n "selectedClubIds|createProfile|addProfile|profileImageUrl|backgroundImageUrl|backgroundImage|profileImage|onFileChange" src/components src/hooks src/lib src/app

Repository: Team-Weeth/weeth-client

Length of output: 14040


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## apis/mypage\n'
sed -n '1,240p' src/lib/apis/mypage.ts

printf '\n## search profile creation hooks\n'
rg -n "createProfile|addProfile|profileImageFile|backgroundImageFile|profileImage:|backgroundImage:" src/hooks src/lib src/components

Repository: Team-Weeth/weeth-client

Length of output: 1949


추가된 프로필 이미지/배경은 이 단계에서 버려집니다.
StepOneContent에서 ProfileBackgroundImageEditor/ProfileImageEditoronFileChange를 넘기지 않고, useAddProfileFlowname/bio만 관리합니다. 이 흐름으로는 사용자가 고른 이미지가 다음 단계나 최종 제출로 전달되지 않으니, 이미지 업로드/전달 로직을 useAddProfileFlow까지 연결해야 합니다.

🤖 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 `@src/components/mypage/AddProfileModal/StepOneContent.tsx` around lines 14 -
21, `StepOneContent` and `useAddProfileFlow` only handle `name`/`bio`, so
selected profile/background images are dropped before submission. Extend
`useAddProfileFlow` to store the chosen files and pass those handlers/state
through to `StepOneContent`, then wire `ProfileBackgroundImageEditor` and
`ProfileImageEditor` via their `onFileChange` props so the selected images
persist to the next step and final submit.

Comment on lines +42 to +43
avatarClassName="bg-[#EFF1F1]"
fallbackClassName="bg-[#EFF1F1]"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

하드코딩된 색상값(bg-[#EFF1F1])은 디자인 토큰 클래스로 대체해야 합니다.

As per coding guidelines, **/*.{ts,tsx,css}: "Never hardcode design token values; always use token classes from design tokens". avatarClassName/fallbackClassName에 임의 hex 값이 직접 지정되어 있습니다. 해당 색상에 대응하는 토큰(예: bg-container-neutral-alternative 등 정의된 토큰)이 있는지 확인 후 교체가 필요합니다.

🎨 제안 방향
-          avatarClassName="bg-[`#EFF1F1`]"
-          fallbackClassName="bg-[`#EFF1F1`]"
+          avatarClassName="bg-container-neutral-alternative"
+          fallbackClassName="bg-container-neutral-alternative"

(실제 디자인 토큰 값 확인 후 정확한 클래스명으로 교체하세요.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
avatarClassName="bg-[#EFF1F1]"
fallbackClassName="bg-[#EFF1F1]"
avatarClassName="bg-container-neutral-alternative"
fallbackClassName="bg-container-neutral-alternative"
🤖 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 `@src/components/mypage/AddProfileModal/StepOneContent.tsx` around lines 42 -
43, The Avatar styling in StepOneContent currently hardcodes the hex color class
bg-[`#EFF1F1`], which should be replaced with an existing design token class.
Update the avatarClassName and fallbackClassName props to use the appropriate
token-based background class (for example, a neutral container token if that
matches the design system) and keep the change localized to the Avatar usage in
StepOneContent.

Source: Coding guidelines

Comment on lines +7 to +12
const MOCK_CLUBS = [
{ id: '1', name: '가천대 검도부', memberCount: 368 },
{ id: '2', name: '가천대 테니스부', memberCount: 368 },
{ id: '3', name: '가천대 산악부', memberCount: 368 },
{ id: '4', name: '가천대 종이접기부', memberCount: 368 },
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the relevant files and symbols.
git ls-files 'src/components/mypage/**' 'src/hooks/**' 'src/**' | rg 'StepTwoContent\.tsx|useAddProfileFlow\.ts|ProfileManagementContent|useMyPageQueries'

printf '\n--- StepTwoContent outline ---\n'
ast-grep outline src/components/mypage/AddProfileModal/StepTwoContent.tsx --view expanded

printf '\n--- useAddProfileFlow outline ---\n'
ast-grep outline src/hooks/useAddProfileFlow.ts --view expanded

printf '\n--- search for MOCK_CLUBS / selectedClubIds / useMyPageQueries ---\n'
rg -n "MOCK_CLUBS|selectedClubIds|useMyPageQueries|AddProfileModal|AddProfilePageContent|ProfileManagementContent" src

Repository: Team-Weeth/weeth-client

Length of output: 6131


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,220p' src/components/mypage/AddProfileModal/StepTwoContent.tsx
printf '\n===== useAddProfileFlow =====\n'
sed -n '1,220p' src/hooks/mypage/useAddProfileFlow.ts
printf '\n===== useMyPageQueries =====\n'
sed -n '1,220p' src/hooks/queries/mypage/useMyPageQueries.ts
printf '\n===== AddProfileModal =====\n'
sed -n '1,220p' src/components/mypage/AddProfileModal.tsx
printf '\n===== AddProfilePageContent =====\n'
sed -n '1,220p' src/components/mypage/AddProfilePageContent.tsx

Repository: Team-Weeth/weeth-client

Length of output: 9348


실제 동아리 목록으로 연결 필요

StepTwoContentMOCK_CLUBS를 그대로 렌더링하고 있고, useAddProfileFlowselectedClubIds 초기값도 '1', '2'로 고정돼 있습니다.
이 상태로는 프로필 추가 단계에서 실제 사용자의 동아리 목록과 무관한 4개 항목만 보여집니다. useMyPageQueries로 가져온 동아리 데이터를 연결해야 합니다.

🤖 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 `@src/components/mypage/AddProfileModal/StepTwoContent.tsx` around lines 7 -
12, `StepTwoContent` is still rendering the hardcoded `MOCK_CLUBS` list instead
of the user’s real clubs. Update `StepTwoContent` to consume the club data
returned by `useMyPageQueries` and render that data directly, using the existing
club identifiers and names from the query response. Also remove the fixed
`selectedClubIds` defaults in `useAddProfileFlow` and initialize selection from
the fetched club list so the add-profile step reflects the current user’s actual
clubs.

Comment on lines +26 to +40
const handleClose = () => {
resetToProfile();
setIsDeleteDialogOpen(false);
onOpenChange(false);
};

const handleOpenDeleteDialog = () => {
onOpenChange(false);
setIsDeleteDialogOpen(true);
};

const handleDelete = () => {
setIsDeleteDialogOpen(false);
handleClose();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== file list ==\n'
git ls-files 'src/components/mypage/EditProfileModal.tsx' 'src/hooks/mypage/useEditProfileForm.ts' 'src/**/mypage/**' | sed 's#^`#-` #'

printf '\n== outline: EditProfileModal.tsx ==\n'
ast-grep outline src/components/mypage/EditProfileModal.tsx --view expanded || true

printf '\n== outline: useEditProfileForm.ts ==\n'
ast-grep outline src/hooks/mypage/useEditProfileForm.ts --view expanded || true

printf '\n== EditProfileModal.tsx (selected lines) ==\n'
sed -n '1,220p' src/components/mypage/EditProfileModal.tsx | cat -n

printf '\n== useEditProfileForm.ts (selected lines) ==\n'
sed -n '1,260p' src/hooks/mypage/useEditProfileForm.ts | cat -n

printf '\n== search for edit profile / delete profile mutations ==\n'
rg -n "useEditProfileForm|handleDelete|delete.*profile|profile.*delete|update.*profile|mutat|save.*profile|resetToProfile|onOpenChange" src -g '!**/*.map'

Repository: Team-Weeth/weeth-client

Length of output: 46514


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== EditProfileModal usages ==\n'
rg -n "EditProfileModal" src

printf '\n== EditProfilePageContent selected lines ==\n'
sed -n '1,220p' src/components/mypage/EditProfilePageContent.tsx | cat -n

printf '\n== ProfileCard selected lines ==\n'
sed -n '1,180p' src/components/mypage/ProfileCard.tsx | cat -n

printf '\n== useUpdateProfileMutation selected lines ==\n'
sed -n '1,220p' src/hooks/mutations/useUpdateProfileMutation.ts | cat -n

printf '\n== edit page selected lines ==\n'
sed -n '1,220p' src/app/'(private)'/'[clubId]'/'(main)'/mypage/profiles/'[profileId]'/edit/page.tsx | cat -n

Repository: Team-Weeth/weeth-client

Length of output: 13992


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== EditProfileFormContent selected lines ==\n'
sed -n '1,220p' src/components/mypage/EditProfileFormContent.tsx | cat -n

printf '\n== edit/EditProfileContent selected lines ==\n'
sed -n '1,320p' src/components/mypage/edit/EditProfileContent.tsx | cat -n

printf '\n== DeleteProfileDialog selected lines ==\n'
sed -n '1,220p' src/components/mypage/DeleteProfileDialog.tsx | cat -n

printf '\n== mypage API selected lines ==\n'
sed -n '1,220p' src/lib/apis/mypage.ts | cat -n

Repository: Team-Weeth/weeth-client

Length of output: 13945


데스크톱 프로필 수정 모달의 저장/삭제 동작 연결 필요 handleClose/handleDelete가 모두 reset 후 닫기만 수행해서, 완료를 눌러도 변경 사항이 저장되지 않고 프로필 삭제하기도 실제 삭제 흐름이 없습니다. useUpdateProfileMutation를 연결해 완료에서 submit하고, 삭제는 별도 삭제 처리로 분리해야 합니다.

🤖 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 `@src/components/mypage/EditProfileModal.tsx` around lines 26 - 40,
`handleClose` and `handleDelete` in `EditProfileModal` only reset state and
close the modal, so the desktop profile edit flow never persists changes or
performs a real delete. Wire `useUpdateProfileMutation` into the save path so
`완료` triggers the profile update submit before closing, and split deletion into
a separate delete handler/flow instead of reusing `handleClose`; keep the
existing `resetToProfile`, `onOpenChange`, and `handleOpenDeleteDialog` behavior
only for cancel/close transitions.

Comment on lines +65 to +121
{/* TODO: mock — 빈 프로필 empty state 확인용 */}
<ProfileCard
profile={{
id: 'mock-empty',
name: '프로필 2',
schoolName: '',
description: '소개글입니다.',
profileImageUrl: '',
memberCount: 0,
cardinals: [],
memberRole: 'USER',
memberStatus: 'ACTIVE',
}}
clubs={[]}
clubId={clubId}
availableProfiles={clubs}
/>
{/* TODO: mock — 클럽 2개 확인용 */}
<ProfileCard
profile={{
id: 'mock-multi',
name: '프로필 4',
schoolName: '',
description: '반가워용',
profileImageUrl: '',
memberCount: 0,
cardinals: [],
memberRole: 'USER',
memberStatus: 'ACTIVE',
}}
clubs={[
{
id: 'mock-c1',
name: '가천대 검도부',
schoolName: '가천대',
description: '',
profileImageUrl: '',
memberCount: 0,
cardinals: [],
memberRole: 'USER',
memberStatus: 'ACTIVE',
},
{
id: 'mock-c2',
name: '가천대 테니스부',
schoolName: '가천대',
description: '',
profileImageUrl: '',
memberCount: 0,
cardinals: [],
memberRole: 'USER',
memberStatus: 'ACTIVE',
},
]}
clubId={clubId}
availableProfiles={clubs}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

목(mock) 프로필 카드가 실제 렌더링 트리에 포함되어 있습니다.

TODO: mock 주석이 달린 mock-empty, mock-multi 프로필 카드가 조건 없이 항상 clubs.map(...) 뒤에 렌더링됩니다. 이 상태로 배포되면 모든 사용자가 실제 프로필 목록과 함께 가짜 프로필("프로필 2", "프로필 4")을 보게 됩니다.

🐛 제거 제안
-          {/* TODO: mock — 빈 프로필 empty state 확인용 */}
-          <ProfileCard
-            profile={{
-              id: 'mock-empty',
-              ...
-            }}
-            clubs={[]}
-            clubId={clubId}
-            availableProfiles={clubs}
-          />
-          {/* TODO: mock — 클럽 2개 확인용 */}
-          <ProfileCard
-            profile={{
-              id: 'mock-multi',
-              ...
-            }}
-            clubs={[...]}
-            clubId={clubId}
-            availableProfiles={clubs}
-          />

Empty/multi state 확인이 필요하다면 Storybook이나 테스트 코드로 분리하는 것을 권장합니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* TODO: mock — 빈 프로필 empty state 확인용 */}
<ProfileCard
profile={{
id: 'mock-empty',
name: '프로필 2',
schoolName: '',
description: '소개글입니다.',
profileImageUrl: '',
memberCount: 0,
cardinals: [],
memberRole: 'USER',
memberStatus: 'ACTIVE',
}}
clubs={[]}
clubId={clubId}
availableProfiles={clubs}
/>
{/* TODO: mock — 클럽 2개 확인용 */}
<ProfileCard
profile={{
id: 'mock-multi',
name: '프로필 4',
schoolName: '',
description: '반가워용',
profileImageUrl: '',
memberCount: 0,
cardinals: [],
memberRole: 'USER',
memberStatus: 'ACTIVE',
}}
clubs={[
{
id: 'mock-c1',
name: '가천대 검도부',
schoolName: '가천대',
description: '',
profileImageUrl: '',
memberCount: 0,
cardinals: [],
memberRole: 'USER',
memberStatus: 'ACTIVE',
},
{
id: 'mock-c2',
name: '가천대 테니스부',
schoolName: '가천대',
description: '',
profileImageUrl: '',
memberCount: 0,
cardinals: [],
memberRole: 'USER',
memberStatus: 'ACTIVE',
},
]}
clubId={clubId}
availableProfiles={clubs}
/>
🤖 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 `@src/components/mypage/ProfileManagementContent.tsx` around lines 65 - 121,
The mock ProfileCard entries labeled as TODO: mock are being rendered
unconditionally in ProfileManagementContent, so real users will always see fake
profiles alongside the actual club list. Remove the mock-empty and mock-multi
ProfileCard blocks from the production render path in ProfileManagementContent
and keep only the real clubs.map(...) output; if you still need empty/multi
state checks, move those cases into Storybook or tests using the same
ProfileCard component.

Comment on lines +10 to +32
const MOBILE_HEADER_HIDDEN_MYPAGE_PATHS = [
'/mypage/profiles',
'/mypage/posts',
'/mypage/sessions',
'/mypage/edit',
'/mypage/settings/theme',
'/mypage/profiles/add',
] as const;

function isMyPageProfileSubPath(pathname: string, clubId: string) {
return MYPAGE_PROFILE_SUB_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}`));
}

function isMyPageSettingsSubPath(pathname: string, clubId: string) {
return MYPAGE_SETTINGS_SUB_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}/`));
}

function shouldHideMobileHeaderOnMyPage(pathname: string, clubId: string) {
return (
MOBILE_HEADER_HIDDEN_MYPAGE_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}`)) ||
new RegExp(`^/${clubId}/mypage/profiles/[^/]+/edit$`).test(pathname)
);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

MOBILE_HEADER_HIDDEN_MYPAGE_PATHS의 중복/사각 항목 정리 필요.

'/mypage/profiles' 항목이 이미 startsWith 방식으로 매칭되므로, '/mypage/profiles/add'(line 16)와 line 30의 정규식(^/${clubId}/mypage/profiles/[^/]+/edit$)은 모두 도달 불가능한 dead code입니다.

  • /1/mypage/profiles/add, /1/mypage/profiles/123/edit 모두 이미 /1/mypage/profiles로 시작하므로 line 29의 .some(...)에서 true가 나온 뒤 line 30은 절대 평가/기여하지 않습니다.
  • 정적분석 도구가 지적한 "변수로부터 생성된 정규식" 경고도 이 dead code를 제거하면 자연히 해소됩니다. [^/]+ 패턴 자체는 ReDoS 위험은 없지만(오탐), clubId를 이스케이프 없이 삽입하는 방식은 불필요한 위험 요소이므로 제거하는 편이 안전합니다.
  • 또한 '/mypage/edit'(line 5, 14) 항목은 제공된 라우트 파일 목록(activity, posts, profiles, profiles/add, profiles/[profileId]/edit, sessions, settings, settings/theme) 어디에도 대응하는 실제 경로가 없어 보입니다. 오타이거나 leftover 코드로 의심됩니다.
♻️ 제안: dead code 제거
 const MOBILE_HEADER_HIDDEN_MYPAGE_PATHS = [
   '/mypage/profiles',
   '/mypage/posts',
   '/mypage/sessions',
-  '/mypage/edit',
   '/mypage/settings/theme',
-  '/mypage/profiles/add',
 ] as const;

 ...

 function shouldHideMobileHeaderOnMyPage(pathname: string, clubId: string) {
-  return (
-    MOBILE_HEADER_HIDDEN_MYPAGE_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}`)) ||
-    new RegExp(`^/${clubId}/mypage/profiles/[^/]+/edit$`).test(pathname)
-  );
+  return MOBILE_HEADER_HIDDEN_MYPAGE_PATHS.some((path) =>
+    pathname.startsWith(`/${clubId}${path}`),
+  );
 }

'/mypage/edit'가 실제 의도된 경로라면(예: 향후 추가 예정 라우트), 어떤 라우트를 가리키는지 확인 부탁드립니다.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const MOBILE_HEADER_HIDDEN_MYPAGE_PATHS = [
'/mypage/profiles',
'/mypage/posts',
'/mypage/sessions',
'/mypage/edit',
'/mypage/settings/theme',
'/mypage/profiles/add',
] as const;
function isMyPageProfileSubPath(pathname: string, clubId: string) {
return MYPAGE_PROFILE_SUB_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}`));
}
function isMyPageSettingsSubPath(pathname: string, clubId: string) {
return MYPAGE_SETTINGS_SUB_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}/`));
}
function shouldHideMobileHeaderOnMyPage(pathname: string, clubId: string) {
return (
MOBILE_HEADER_HIDDEN_MYPAGE_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}`)) ||
new RegExp(`^/${clubId}/mypage/profiles/[^/]+/edit$`).test(pathname)
);
}
const MOBILE_HEADER_HIDDEN_MYPAGE_PATHS = [
'/mypage/profiles',
'/mypage/posts',
'/mypage/sessions',
'/mypage/settings/theme',
] as const;
function isMyPageProfileSubPath(pathname: string, clubId: string) {
return MYPAGE_PROFILE_SUB_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}`));
}
function isMyPageSettingsSubPath(pathname: string, clubId: string) {
return MYPAGE_SETTINGS_SUB_PATHS.some((path) => pathname.startsWith(`/${clubId}${path}/`));
}
function shouldHideMobileHeaderOnMyPage(pathname: string, clubId: string) {
return MOBILE_HEADER_HIDDEN_MYPAGE_PATHS.some((path) =>
pathname.startsWith(`/${clubId}${path}`),
);
}
🧰 Tools
🪛 ast-grep (0.44.0)

[warning] 29-29: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^/${clubId}/mypage/profiles/[^/]+/edit$)
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)

🤖 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 `@src/constants/mypage/routes.ts` around lines 10 - 32,
`shouldHideMobileHeaderOnMyPage` and `MOBILE_HEADER_HIDDEN_MYPAGE_PATHS` contain
redundant and unreachable checks. Remove the dead `/mypage/profiles/add` and the
`new RegExp(...)` branch from `shouldHideMobileHeaderOnMyPage`, and verify
whether `/mypage/edit` in `MOBILE_HEADER_HIDDEN_MYPAGE_PATHS` is a valid route
or leftover code; keep the logic centered on the existing
`MYPAGE_PROFILE_SUB_PATHS` and `MYPAGE_SETTINGS_SUB_PATHS` symbols so matching
remains correct and simpler.

Source: Linters/SAST tools

Comment on lines +8 to +59
function useAddProfileFlow() {
const [step, setStep] = useState(1);
const [selectedClubIds, setSelectedClubIds] = useState<string[]>(['1', '2']);
const stepOneSchema = editProfileSchema.pick({ name: true, bio: true });

const {
control,
reset,
trigger,
formState: { errors },
} = useForm<Pick<EditProfileFormData, 'name' | 'bio'>>({
resolver: zodResolver(stepOneSchema),
mode: 'onChange',
reValidateMode: 'onChange',
defaultValues: {
name: '',
bio: '',
},
});

const resetFlow = () => {
setStep(1);
reset({ name: '', bio: '' });
setSelectedClubIds(['1', '2']);
};

const handleToggleClub = (clubId: string) => {
setSelectedClubIds((prev) =>
prev.includes(clubId) ? prev.filter((id) => id !== clubId) : [...prev, clubId],
);
};

const handleNext = async () => {
const isValid = await trigger();
if (!isValid) return false;
setStep(2);
return true;
};

return {
step,
setStep,
selectedClubIds,
control,
errors,
resetFlow,
handleToggleClub,
handleNext,
};
}

export { useAddProfileFlow };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## Files\n'
git ls-files 'src/hooks/mypage/*' 'src/components/*AddProfile*' 'src/app/*AddProfile*' 'src/**/StepTwoContent.tsx' 'src/**/StepOneContent.tsx' | sed 's#^`#-` #'

printf '\n## Search for hook and confirm handlers\n'
rg -n "useAddProfileFlow|handleConfirm|resetFlow|selectedClubIds|MOCK_CLUBS|mutation|createProfile|addProfile|saveProfile" src --glob '!**/*.map'

printf '\n## Candidate file outlines\n'
for f in \
  src/hooks/mypage/useAddProfileFlow.ts \
  src/components/mypage/AddProfileModal.tsx \
  src/components/mypage/AddProfilePageContent.tsx \
  src/components/mypage/StepTwoContent.tsx \
  src/components/mypage/StepOneContent.tsx
do
  if [ -f "$f" ]; then
    echo "--- $f"
    ast-grep outline "$f" --view expanded 2>/dev/null || true
  fi
done

Repository: Team-Weeth/weeth-client

Length of output: 12758


🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  src/hooks/mypage/useAddProfileFlow.ts \
  src/components/mypage/AddProfileModal.tsx \
  src/components/mypage/AddProfilePageContent.tsx \
  src/components/mypage/StepTwoContent.tsx
do
  if [ -f "$f" ]; then
    echo "===== $f ====="
    wc -l "$f"
    sed -n '1,220p' "$f"
  fi
done

Repository: Team-Weeth/weeth-client

Length of output: 5750


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "handleConfirm|resetFlow\\(|useAddProfileFlow\\(" src/components src/hooks src/app --glob '!**/*.map' -A 4 -B 4

Repository: Team-Weeth/weeth-client

Length of output: 12705


프로필 생성 mutation을 연결해야 합니다.
useAddProfileFlowname/bioselectedClubIds만 수집하고, AddProfileModal.tsxAddProfilePageContent.tsxhandleConfirmresetFlow() 후 닫기만 해서 입력값이 실제로 저장되지 않습니다.

🤖 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 `@src/hooks/mypage/useAddProfileFlow.ts` around lines 8 - 59, useAddProfileFlow
currently only manages local step state and selections, but the profile create
mutation is never invoked. Update useAddProfileFlow and the handleConfirm flow
in AddProfileModal.tsx and AddProfilePageContent.tsx so the collected name, bio,
and selectedClubIds are passed into the add-profile mutation before calling
resetFlow() and closing. Keep the existing step/selection logic, but ensure the
confirm action actually persists the profile data.

Comment thread src/hooks/mypage/useEditProfileForm.ts
Comment thread src/types/mypage.ts
Comment on lines +20 to +28
export interface ProfileData {
name: string;
bio?: string;
profileImageUrl?: string;
tel?: string;
email?: string;
school?: string;
department?: string;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "backgroundImageUrl" --type=ts -C2

Repository: Team-Weeth/weeth-client

Length of output: 161


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## files containing ProfileData or mypage refs\n'
rg -n "ProfileData|mypage|ProfileBackgroundImageEditor|backgroundImage" src --type=ts --type=tsx -C 2 || true

printf '\n## candidate files\n'
fd -a "mypage.ts|ProfileSection.tsx|MyPageContent.tsx|ProfileBackgroundImageEditor" src

Repository: Team-Weeth/weeth-client

Length of output: 536


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n## search me response / profile api shape\n'
rg -n "me\b|MyPageContent|ProfileSection|profileImageUrl|backgroundImageUrl|background image" src --type=ts --type=tsx -C 2 || true

Repository: Team-Weeth/weeth-client

Length of output: 232


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '## src/types/mypage.ts'
sed -n '1,220p' src/types/mypage.ts | cat -n

echo
echo '## src/lib/apis/mypage.ts'
sed -n '1,260p' src/lib/apis/mypage.ts | cat -n

echo
echo '## src/components/mypage/MyPageContent.tsx'
sed -n '1,260p' src/components/mypage/MyPageContent.tsx | cat -n

echo
echo '## src/components/mypage/ProfileSection.tsx'
sed -n '1,260p' src/components/mypage/ProfileSection.tsx | cat -n

echo
echo '## src/components/mypage/edit/ProfileBackgroundImageEditor.tsx'
sed -n '1,220p' src/components/mypage/edit/ProfileBackgroundImageEditor.tsx | cat -n

Repository: Team-Weeth/weeth-client

Length of output: 17182


배경 이미지 필드를 프로필 응답과 렌더링 경로에 추가해야 합니다. ProfileData만이 아니라 MyMember/getMe 응답 타입에도 backgroundImageUrl을 포함하고, MyPageContent.tsx에서 ProfileSectionProfileBackgroundImageEditor로 내려줘야 업로드한 배경이 새로고침 후에도 유지됩니다.

🤖 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 `@src/types/mypage.ts` around lines 20 - 28, Profile background image is
missing from the response and render flow, so add backgroundImageUrl to
ProfileData as well as the MyMember and getMe response types. Then update
MyPageContent and the ProfileSection data path so the background image is passed
through to ProfileBackgroundImageEditor and persists after refresh. Use the
existing ProfileData, MyMember, getMe, MyPageContent, ProfileSection, and
ProfileBackgroundImageEditor symbols to wire the new field through consistently.

@dalzzy dalzzy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

확인했습니당!! 수고하셨어요 👍🏻 마이페이지가 할 게 많았네요 ,,,,,

Comment on lines +51 to +74
{isRow ? (
variant === 'copy' ? (
<span className="bg-button-neutral typo-button2 text-text-strong inline-flex shrink-0 items-center justify-center rounded-sm px-300 py-200">
복사하기
</span>
) : (
<Icon
src={ArrowRightIcon}
size={12}
className="text-icon-normal"
alt="페이지 이동버튼"
/>
)
) : (
<span className="text-icon-alternative absolute top-3.75 right-300">
{variant === 'link' && (
<Icon
src={ArrowRightIcon}
size={12}
className="text-icon-normal"
alt="페이지 이동버튼"
/>
)}
</span>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

중첩 삼항으로 조건이 겹쳐있어서 가독성이 좀 떨어지는 것 같은데 renderAction 함수로 분리해서 isRow, variant별 렌더링을 명시하면 가독성이 좋아질 것 같아요!!

const renderAction = () => {
  if (isRow && variant === 'copy') {
    return (
      <span className="bg-button-neutral typo-button2 text-text-strong inline-flex shrink-0 items-center justify-center rounded-sm px-300 py-200">
        복사하기
      </span>
    );
  }

  if (variant === 'link') {
    return (
      <Icon
        src={ArrowRightIcon}
        size={12}
        className="text-icon-normal"
        alt="페이지 이동버튼"
      />
    );
  }

  return null;
};

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

오호 그렇네요... 반영했습니당!!

Comment on lines +106 to +134
<button
type="button"
className="flex flex-1 flex-col items-center justify-center gap-2 py-200"
onClick={() => router.push(`/${clubId}/mypage/posts`)}
>
<span className="tablet:typo-sub3 typo-button2 text-text-alternative flex items-center gap-2">
내가 쓴 글
<div className="bg-icon-alternative flex size-[18px] items-center justify-center rounded-full">
<Icon src={ArrowRightIcon} alt="" size={8} className="text-icon-inverse pl-[1px]" />
</div>
</span>
<span className="tablet:typo-h3 typo-sub1 text-text-strong">{postCount}개</span>
</button>

<div className="bg-button-neutral-interaction h-[80px] w-px" />

<button
type="button"
className="flex flex-1 flex-col items-center justify-center gap-2 py-200"
onClick={() => router.push(`/${clubId}/mypage/sessions`)}
>
<span className="tablet:typo-sub3 typo-button2 text-text-alternative flex items-center gap-2">
출석한 세션
<div className="bg-icon-alternative flex size-[18px] items-center justify-center rounded-full">
<Icon src={ArrowRightIcon} alt="" size={8} className="text-icon-inverse pl-[1px]" />
</div>
</span>
<span className="tablet:typo-h3 typo-sub1 text-text-strong">{sessionCount}개</span>
</button>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

동일한 버튼 구조가 반복되고 있어서 [{ label, count, href }] 형태의 객체 배열로 분리해 map 렌더링하면 가독성과 유지보수성이 좋아질 것 같습니당

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

오호 좋습니당!! 반영해둘게요!!

@JIN921 JIN921 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

짱 많다.... 고생하셧습니다 ㅜ.ㅜ!!! 마이페이지 수정 사항이 굉장하네요... api도 다 다시 달아야 하다니 우악... 화이팅!!!

<button
type="button"
onClick={() => router.back()}
className="flex items-center justify-center p-1"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

커서 포인터 추가!!

<div className="bg-container-neutral-alternative mt-5 flex h-[112px] items-center justify-center overflow-hidden rounded-md">
<button
type="button"
className="flex flex-1 flex-col items-center justify-center gap-2 py-200"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

요기두 커서 포인터 추가용!!

</Link>
<Link
href={`/${clubId}/mypage/profiles`}
className="bg-container-neutral-alternative tablet:hidden tablet:h-[128px] tablet:w-[140px] flex shrink-0 items-center justify-center gap-3 rounded-md px-400 py-300"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

hover 시 색상 변경 넣어주면 좋을 거 같애용!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

container-neutral-alternative-interaction으로 넣어두겟습니당!!

Comment on lines +34 to +43
<div className="tablet:items-start flex items-center gap-1 py-300">
<button
type="button"
onClick={() => router.back()}
className="flex items-center justify-center p-1"
>
<Icon src={BackIcon} size={21} className="text-icon-normal p-1" />
</button>
<div className="flex flex-col">
<h1 className="tablet:typo-h3 typo-sub1 text-text-normal">프로필 관리</h1>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

요기는 따로 묶어서... MyPageHeader...?로 써도 유지보수 하기 좋을 거 같애요! 밑에 설명은 걍 따로 달구..

Comment thread src/components/mypage/ClubInfoCard.tsx Outdated
</p>
)}
<div className="text-text-alternative mb-300 flex items-center gap-100">
<Icon src={PeopleIcon} size={16} className="text-icon-altenative" />

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

오타낫어용! text-icon-alternative

import { AdminMeatballIcon } from '@/assets/icons/admin';
import { WithdrawConfirmDialog } from './WithdrawConfirmDialog';

function MyPageDropdownMenu() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

요거 파일명 말고 이름도 LeaveClubDropdownMenu로 바꾸면 좋을 거 같습니다! MyPageDropdownMenu는 이미 잇으니까...!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

어머나 MyPageDropdownMenu랑 기능이 비슷해서 가져오다가 함수명을 까먹고 안바꿨네요... ㅎㅎㅎㅎ 감사합니당!!!

@nabbang6 nabbang6 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

고생하셧습니다!!! 👍👍 바뀐 마이페이지 넘 예쁘네용.... 짱이닷...

Comment on lines +37 to +63
const fileInputRef = useRef<HTMLInputElement>(null);
const previewUrlRef = useRef<string | null>(null);
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [isReset, setIsReset] = useState(false);

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current);
const url = URL.createObjectURL(file);
previewUrlRef.current = url;
setPreviewUrl(url);
setIsReset(false);
onFileChange?.(file);
};

const handleReset = () => {
if (previewUrlRef.current) {
URL.revokeObjectURL(previewUrlRef.current);
previewUrlRef.current = null;
}
setPreviewUrl(null);
setIsReset(true);
onResetImage?.();
};

const displayUrl = isReset ? null : (previewUrl ?? backgroundImageUrl ?? null);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

요기 fileInputRef ~ isReset 상태랑 핸들러, displayUrl 계산 로직 부분이 ProfileImageEditor 에도 동일하게 작성되어 잇는 것 같습니다! useImagePreview 같은 훅으로 추출해줌 조을 것 같아용

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

오호 좋습니당! 반영해둘게용

import { Icon } from '@/components/ui';
import { cn } from '@/lib/cn';

type ThemeMode = 'auto' | 'light' | 'dark';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

요 ThemeMode MypageSettingsContent 쪽에서도 동일하게 선언되어 잇어서 types/에 옮겨두어도 좋을 것 같아요!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ThemeMode를 각각 선언해둔 파일들이 많아서 다 types/ 로 옮겨서 import 하도록 수정했습니당 감사합니당!

Comment on lines +68 to +72
<Image
src={displayUrl}
alt=""
fill
unoptimized

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

unoptimized는 로컬 이미지 미리보기에만 적용해주어도 충분할 것 같아서... unoptimized={!!previewUrl}로 작성해두는 건 어떨까용? 서버에서 업로드된 이미지엔 기존 최적화 기능 사용하게 해주는 게 좋을 것 같슴니닷

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

넵 좋습니당!! 말씀해주신 리뷰대로 useImagePreview 훅에서 isPreview를 내려주는 방식으로 적용해둘게용!!

Comment on lines +12 to +14
interface ProfileSectionProps extends React.HTMLAttributes<HTMLDivElement>, ProfileData {
postCount?: number;
sessionCount?: number;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

요기 priority prop 추가해서 ProfileImageEditor, ProfileBackgroundImageEditor 이미지에 priority 적용해주면 어떨까요??

마이페이지 진입했을 때 화면 상단에 바로 보이는 이미지라 LCP 대상이기도 해서... 초기 렌더링 성능 측면에서 좋을 것 같습니닷 👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

오호 좋습니다! 근데 현재 저희가 공통 컴포넌트로 사용 중인 ProfileImageEditor는 Radix AvatarImage 기반이라 priority 직접 사용은 어려워서 추후에 리팩토링을 진행해야 할 것 같아욤 ㅜㅜ

<div className="flex flex-col items-center gap-2">
<div
className={cn(
'tablet:tablet:h-[72px] tablet:w-[104px] h-[110px] w-[160px] overflow-hidden rounded-[10px] transition-colors',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

요기...오타난 것 같습니당 tablet:tablet:h-[72px] 요 부분 tablet이 두 번 들어간 것 같아용

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

흐억 그렇네요... 매의 눈... 감사합니당!

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-iul0vtjin-weethsite-4975s-projects.vercel.app

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR E2E 테스트 결과

Playwright: 통과

🎉 E2E 테스트를 통과했습니다!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/mypage/ClubInfoCard.tsx (1)

97-101: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

임의 hex 컬러 대신 디자인 토큰 클래스 사용 필요

bg-[#9095991A]는 디자인 토큰이 아닌 임의의 하드코딩된 hex+alpha 값입니다. 프로젝트에 등록된 토큰(bg-neutral-*, bg-container-neutral*, bg-background 등) 중 일치하는 옅은 회색 계열 토큰이 있는지 확인 후 교체가 필요합니다.

As per coding guidelines, **/*.{ts,tsx,css}: "Never hardcode design token values; always use token classes from design tokens".

🤖 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 `@src/components/mypage/ClubInfoCard.tsx` around lines 97 - 101, Replace the
hardcoded `bg-[`#9095991A`]` on the `Tag` in `ClubInfoCard` with an existing
design token class that matches the same light gray/neutral translucent
background. Check the available token classes such as `bg-neutral-*`,
`bg-container-neutral*`, or `bg-background`, and use the closest registered
option instead of a custom hex value. Keep the rest of the `Tag` styling
unchanged.

Source: Coding guidelines

🤖 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 `@src/hooks/mypage/useImagePreview.ts`:
- Around line 25-38: `useImagePreview`의 `handleChange`는 파일을 읽은 뒤에도 input value를
비우지 않아 같은 파일 재선택 시 `onChange`가 다시 발생하지 않습니다. `handleChange`에서 `e.target.files`를
처리한 뒤 `input` 값을 초기화하도록 수정하고, 이를 통해 `previewUrlRef`, `setPreviewUrl`,
`setIsReset`, `onFileChange` 흐름이 같은 파일 재선택에도 다시 실행되게 하세요.

---

Outside diff comments:
In `@src/components/mypage/ClubInfoCard.tsx`:
- Around line 97-101: Replace the hardcoded `bg-[`#9095991A`]` on the `Tag` in
`ClubInfoCard` with an existing design token class that matches the same light
gray/neutral translucent background. Check the available token classes such as
`bg-neutral-*`, `bg-container-neutral*`, or `bg-background`, and use the closest
registered option instead of a custom hex value. Keep the rest of the `Tag`
styling unchanged.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df64f7da-e0ed-4079-9252-18924f0280b6

📥 Commits

Reviewing files that changed from the base of the PR and between 370938e and 8a2ab7e.

📒 Files selected for processing (20)
  • src/assets/icons/index.ts
  • src/components/mypage/ActiveClubList.tsx
  • src/components/mypage/ClubInfoCard.tsx
  • src/components/mypage/LeaveClubDropdownMenu.tsx
  • src/components/mypage/MyPageContent.tsx
  • src/components/mypage/MyPagePostsContent.tsx
  • src/components/mypage/MyPageSettingsContent.tsx
  • src/components/mypage/ProfileSection.tsx
  • src/components/mypage/SupportListItem.tsx
  • src/components/mypage/ThemeModeModal.tsx
  • src/components/mypage/ThemeModePageContent.tsx
  • src/components/mypage/ThemeModeSelector.tsx
  • src/components/mypage/ThemeToggle.tsx
  • src/components/mypage/edit/ProfileBackgroundImageEditor.tsx
  • src/components/mypage/edit/ProfileImageEditor.tsx
  • src/hooks/index.ts
  • src/hooks/mypage/index.ts
  • src/hooks/mypage/useImagePreview.ts
  • src/stores/theme-store.ts
  • src/types/theme.ts
✅ Files skipped from review due to trivial changes (3)
  • src/types/theme.ts
  • src/hooks/index.ts
  • src/stores/theme-store.ts
🚧 Files skipped from review as they are similar to previous changes (12)
  • src/hooks/mypage/index.ts
  • src/components/mypage/MyPagePostsContent.tsx
  • src/components/mypage/MyPageSettingsContent.tsx
  • src/components/mypage/ThemeModeModal.tsx
  • src/components/mypage/ThemeModeSelector.tsx
  • src/assets/icons/index.ts
  • src/components/mypage/ThemeModePageContent.tsx
  • src/components/mypage/MyPageContent.tsx
  • src/components/mypage/ActiveClubList.tsx
  • src/components/mypage/edit/ProfileImageEditor.tsx
  • src/components/mypage/SupportListItem.tsx
  • src/components/mypage/ProfileSection.tsx

Comment thread src/hooks/mypage/useImagePreview.ts
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR 테스트 결과

Jest: 통과

🎉 모든 테스트를 통과했습니다!

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 통과

🎉 모든 검증을 통과했습니다!

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

구현한 기능 Preview: https://weeth-aq5rdcd0z-weethsite-4975s-projects.vercel.app

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

PR E2E 테스트 결과

Playwright: 통과

🎉 E2E 테스트를 통과했습니다!

@woneeeee woneeeee merged commit 74f6df9 into develop Jul 6, 2026
5 checks passed
@woneeeee woneeeee deleted the feat/WTH-421-마이페이지-개선뷰-UI-구현 branch July 6, 2026 10:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants