Skip to content

[Feat] WTH-234: 마이페이지 API 연결#40

Open
JIN921 wants to merge 38 commits intodevelopfrom
feat/WTH-234-마이페이지-api-연결

Hidden character warning

The head ref may contain hidden characters: "feat/WTH-234-\ub9c8\uc774\ud398\uc774\uc9c0-api-\uc5f0\uacb0"
Open

[Feat] WTH-234: 마이페이지 API 연결#40
JIN921 wants to merge 38 commits intodevelopfrom
feat/WTH-234-마이페이지-api-연결

Conversation

@JIN921
Copy link
Copy Markdown
Collaborator

@JIN921 JIN921 commented Apr 6, 2026

📢 어드민 멤버 페이지 땡겨오느라 변경 사항이 저래 됐습니다.. 마이페이지만 봐주십시오..

✅ PR 유형

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

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

📌 관련 이슈번호

  • Closed #WTH-234

✅ Key Changes

  • 마이페이지 내 정보 조회 API 연결 (GET /clubs/{clubId}/members/me) — MOCK 데이터 제거, 실제 API 데이터로 교체
  • 가입한 동아리 목록 API 연결 (GET /clubs) — 활동정보 섹션에 실제 클럽 데이터 표시
  • 내 정보 수정 API 연결 (PATCH /users + PATCH /clubs/members/me)
    • React Hook Form + Zod 폼 검증 (onTouched 모드)
    • 프로필 이미지: presigned URL 업로드 + URL.createObjectURL 미리보기
    • 이름, 이메일, 연락처, 학번 필수 검증 / 소개글 30자 제한
  • 최초 활동 기수 설정 API 연결 (POST /clubs/{clubId}/members/me/cardinals)
    • useCardinals 쿼리로 기수 목록 조회하여 SetCardinalModal에 전달
  • EditProfileContent 컴포넌트 분리edit/ 폴더
    • ProfileImageEditor — 아바타 + 이미지 편집
    • PersonalInfoFields — 이름, 소개글, 연락처, 이메일
    • SchoolInfoFields — 학교, 학과, 학번
  • 기타 수정
    • SupportListItem 텍스트 왼쪽 정렬 (text-left)
    • MyPageDropdownMenu 탈퇴 문구 서비스 탈퇴로 변경, separator 중복 제거
    • ClubInfoCard에서 availableCardinals prop 제거 (내부에서 API 조회)

📸 스크린샷 or 실행영상


🎸 기타 사항 or 추가 코멘트

훅 폴더 뮤테이션이랑 쿼리 분리 해놧는데 앞으로 작업하실 때 분리해서 해주시믄 짱 좋을 듯... 뮤테이션이랑 쿼리 안에 폴더나 파일 알잘딱 넣어서 써주심 됨니다

🤖 Generated with Claude Code

Summary by CodeRabbit

릴리스 노트

  • New Features

    • 관리자 멤버 관리 확장: 역할 변경, 추방/복구, 동시 선택 작업 지원
    • 프로필 편집 전면 개편: 이미지 업로드/미리보기, 폼 유효성 검사, 학교·학과 자동완성
    • 기수(카디널) 생성 및 관리 기능 추가
  • Improvements

    • 마이페이지와 멤버 목록을 서버 데이터 기반 실시간 조회로 전환
    • 대학·학과 검색/조회 경험 개선
    • 검색 입력 안내문 한국어화
  • Chores

    • 관리자·마이페이지 관련 API 및 쿼리/뮤테이션 공개 및 정리

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Caution

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

⚠️ Outside diff range comments (2)
.claude/settings.local.json (1)

6-23: ⚠️ Potential issue | 🔴 Critical

.claude/settings.local.json에서 Git 머지 충돌 마커를 해소하세요.

6번째, 20번째, 23번째 줄의 충돌 마커(<<<<<<<, =======, >>>>>>>)로 인해 JSON이 유효하지 않습니다. 충돌을 해소하고 하나의 최종 allow 목록만 유지해야 합니다.

해소 예시
-<<<<<<< HEAD
-      "mcp__figma__get_design_context",
-      "Bash(grep -E \"\\\\.tsx$\")",
-      "Bash(find \"D:\\\\project\\\\weeth-client\\\\src\\\\app\\\\\\(private\\)\" -name \"layout.tsx\" -type f)",
-      "Bash(find \"D:\\\\project\\\\weeth-client\\\\src\\\\app\\\\\\(private\\)\\\\\\(main\\)\" -name \"page.tsx\" -type f)",
-      "Bash(grep -r PageNavigation D:projectweeth-clientsrc --include=*.tsx --include=*.ts)",
-      "Bash(ls -la \"D:\\\\project\\\\weeth-client\\\\src\\\\app\\\\\\(private\\)\\\\\\(intro\\)\\\\home\")",
-      "Bash(find \"D:\\\\project\\\\weeth-client\\\\src\\\\app\\\\\\(private\\)\" -type f -name \"layout.tsx\")",
-      "mcp__figma__get_screenshot",
-      "Bash(find D:projectweeth-clientsrccomponentsmypage -type f -name *.tsx -o -name *.ts)",
-      "Bash(ls -la \"D:\\\\project\\\\weeth-client\\\\src\\\\app\\\\\\(private\\)\\\\\\(main\\)\\\\mypage\")",
-      "Bash(ls -la \"D:\\\\project\\\\weeth-client\\\\src\\\\app\\\\\\(private\\)\\\\\\(main\\)\\\\mypage\\\\edit\")",
-      "Bash(find D:projectweeth-clientsrccomponentsui -type f \\\\\\(-name *.tsx -o -name *.ts \\\\\\))",
-      "Bash(find D:/project/weeth-client/src/constants -name *.ts)"
-=======
-      "Bash(ls \"D:/project/weeth-client/src/app/\\(public\\)/\\(landing\\)/\")",
-      "Bash(git fetch:*)"
->>>>>>> feat/WTH-215-멤버-어드민-API-연결
+      "...최종 합의된 항목들..."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/settings.local.json around lines 6 - 23, The JSON contains
unresolved Git merge conflict markers (<<<<<<<, =======, >>>>>>>) around the
"allow" entries (e.g., entries like "mcp__figma__get_design_context", "Bash(ls
\"D:/project/weeth-client/src/app/\\(public\\)/\\(landing\\)/\")", "Bash(git
fetch:*)"); remove the conflict markers, pick and retain the intended final set
of allow entries (merging or deduplicating the two sides as appropriate), ensure
the result is a single valid JSON array/object (no leftover markers, proper
commas/quotes), and save so the file parses as valid JSON.
src/components/admin/member/MemberPageContent.tsx (1)

54-76: ⚠️ Potential issue | 🟠 Major

혼합 상태 선택에서 잘못된 일괄 액션이 실행됩니다.

지금 분기는 allBanned만 보고 결정해서, 활성 멤버와 BANNED 멤버를 함께 선택해도 “유저 추방”이 활성화되고 이미 정지된 멤버에게 banMember를 다시 호출합니다. 상태가 섞인 경우에는 액션을 비활성화하거나, ban/restore 대상을 상태별로 분리해서 호출해야 합니다.

🔧 제안 수정안
+  const bannableMembers = selectedMembers.filter((m) => m.status !== 'BANNED');
+  const restorableMembers = selectedMembers.filter((m) => m.status === 'BANNED');
   const selectedCount = selectedMembers.length;
@@
-  const allBanned = selectedCount > 0 && selectedMembers.every((m) => m.status === 'BANNED');
+  const allBanned = selectedCount > 0 && restorableMembers.length === selectedCount;
@@
-        onBan={() => selectedMembers.forEach((m) => banMember(m.clubMemberId))}
-        onRestore={() => selectedMembers.forEach((m) => restoreMember(m.clubMemberId))}
+        onBan={
+          bannableMembers.length === selectedCount
+            ? () => bannableMembers.forEach((m) => banMember(m.clubMemberId))
+            : undefined
+        }
+        onRestore={
+          restorableMembers.length === selectedCount
+            ? () => restorableMembers.forEach((m) => restoreMember(m.clubMemberId))
+            : undefined
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/member/MemberPageContent.tsx` around lines 54 - 76, The
current logic uses allBanned to decide actions and blindly calls
banMember/restoreMember for every selectedMembers, causing redundant calls when
selection has mixed statuses; update the MemberTopBar handlers so they either
disable the action when selection is mixed (selectedCount>0 && !allBanned &&
!allActive) or, if keeping action enabled, split selectedMembers by m.status and
call banMember only for members with status !== 'BANNED' and restoreMember only
for members with status === 'BANNED'; likewise guard changeMemberRole to only
call changeMemberRole for members whose role actually differs from targetRole;
use the existing symbols selectedMembers, allBanned, selectedCount, targetRole,
banMember, restoreMember, and changeMemberRole to implement the filtering or
disabling behavior.
🧹 Nitpick comments (13)
src/components/mypage/MyPageDropdownMenu.tsx (2)

40-57: 주석 처리된 코드 정리 권장

서비스 탈퇴 기능 관련 코드가 주석 처리되어 있습니다. 나중에 구현할 계획이라면 이 코드를 삭제하고 별도 이슈로 추적하는 것이 코드 가독성과 유지보수에 좋습니다. 주석 처리된 코드는 버전 관리 시스템에서 이력으로 남기 때문에 제거해도 무방합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mypage/MyPageDropdownMenu.tsx` around lines 40 - 57, Remove
the commented-out "서비스 탈퇴" block and the disabled AlertDialog modal code to
improve readability: delete the commented DropdownMenuItem that references
setWithdrawOpen / 서비스 탈퇴 and the AlertDialog block (props open/onOpenChange,
status/title/description, AlertDialogAction/AlertDialogCancel) and any unused
state or imports (e.g., withdrawOpen, setWithdrawOpen) from
MyPageDropdownMenu.tsx; if the feature will be implemented later, create a
tracking issue instead of leaving commented code.

19-19: 사용되지 않는 withdrawOpen 상태 제거 필요

서비스 탈퇴 기능이 주석 처리되면서 withdrawOpen 상태가 선언만 되고 사용되지 않습니다. 불필요한 코드를 제거하세요.

♻️ 제안된 수정
-  const [withdrawOpen, setWithdrawOpen] = useState(false);
   const [logoutOpen, setLogoutOpen] = useState(false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mypage/MyPageDropdownMenu.tsx` at line 19, The declared state
withdrawOpen (and its setter setWithdrawOpen) in MyPageDropdownMenu is unused
because the withdraw feature was commented out; remove the unused useState
declaration (the const [withdrawOpen, setWithdrawOpen] = useState(false) line)
and any related unused imports if they become orphaned (e.g., useState) to clean
up the component and avoid linter warnings.
src/components/mypage/edit/PersonalInfoFields.tsx (1)

1-1: 사용되지 않는 Control 타입 import 제거

Control 타입이 import되었지만 컴포넌트에서 사용되지 않습니다.

♻️ 제안된 수정
-import type { Control, FieldErrors, UseFormRegister, UseFormSetValue } from 'react-hook-form';
+import type { FieldErrors, UseFormRegister, UseFormSetValue } from 'react-hook-form';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mypage/edit/PersonalInfoFields.tsx` at line 1, The import of
the Control type from 'react-hook-form' is unused in PersonalInfoFields.tsx;
remove Control from the import list so the line imports only FieldErrors,
UseFormRegister, and UseFormSetValue, and then run your linter/TS check to
ensure no other references to Control remain (update any code if Control was
intended to be used).
src/lib/schemas/editProfile.ts (1)

10-13: 이메일 검증에 .email() 메서드 사용 권장

refine()과 커스텀 정규식 대신 Zod의 내장 .email() 메서드를 사용하면 더 간결하고 표준적인 검증이 가능합니다.

♻️ 제안된 수정
   email: z
     .string()
     .min(1, '이메일을 입력해주세요')
-    .refine((v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), '올바른 이메일 형식이 아닙니다'),
+    .email('올바른 이메일 형식이 아닙니다'),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/schemas/editProfile.ts` around lines 10 - 13, The email field in the
editProfile Zod schema uses a custom .refine with a regex; replace that with
Zod's built-in .email() to use standard validation: update the email schema (the
email property inside the editProfile schema) to call .email('올바른 이메일 형식이 아닙니다')
and use .nonempty('이메일을 입력해주세요') (or keep the existing .min(1, ...)) instead of
the regex-based .refine to simplify and standardize validation.
src/lib/apis/server.ts (1)

75-77: 개발용 토큰 fallback 조건을 조금 더 제한하는 것이 안전합니다.

Line 75-77 로직은 ACCESS_TOKEN_KEY가 없을 때 항상 DEV_ACCESS_TOKEN을 사용합니다. 이때 REFRESH_TOKEN_KEY가 남아있는 세션에서도 다른 계정 토큰으로 요청이 성공해, 원래 refresh 경로를 타지 못할 수 있습니다. 개발 환경에서도 세션 일관성을 위해 fallback을 더 좁히는 편이 좋습니다.

제안 diff
-  const accessToken =
-    cookieStore.get(ACCESS_TOKEN_KEY)?.value ??
-    (process.env.NODE_ENV === 'development' ? process.env.DEV_ACCESS_TOKEN : undefined);
+  const accessTokenFromCookie = cookieStore.get(ACCESS_TOKEN_KEY)?.value;
+  const refreshTokenFromCookie = cookieStore.get(REFRESH_TOKEN_KEY)?.value;
+  const devAccessToken =
+    process.env.NODE_ENV === 'development' && !refreshTokenFromCookie
+      ? process.env.DEV_ACCESS_TOKEN
+      : undefined;
+  const accessToken = accessTokenFromCookie ?? devAccessToken;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/apis/server.ts` around lines 75 - 77, The current accessToken
fallback uses DEV_ACCESS_TOKEN whenever ACCESS_TOKEN_KEY is missing, which can
hide an existing REFRESH_TOKEN and cause mismatched session behavior; update the
logic around accessToken (the cookieStore.get(ACCESS_TOKEN_KEY) resolution) to
only use process.env.DEV_ACCESS_TOKEN in development when there is no refresh
token present (check cookieStore.get(REFRESH_TOKEN_KEY)?.value) and/or when no
session cookies exist, so the DEV_ACCESS_TOKEN is applied only for truly
session-less dev requests and not when a refresh token is available.
src/hooks/mutations/mypage/useInitCardinalsMutation.ts (1)

14-18: onSuccess 내부의 clubId 체크는 불필요합니다.

mutationFn에서 이미 clubId가 없으면 에러를 throw하므로, onSuccess에 도달했다면 clubId는 항상 truthy입니다. 다만 defensive coding으로 남겨두어도 큰 문제는 없습니다.

♻️ 선택적 개선안
     onSuccess: () => {
       queryClient.invalidateQueries({ queryKey: ['mypage', 'clubs'] });
-      if (clubId) {
-        queryClient.invalidateQueries({ queryKey: ['mypage', 'me', clubId] });
-      }
+      queryClient.invalidateQueries({ queryKey: ['mypage', 'me', clubId] });
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/mutations/mypage/useInitCardinalsMutation.ts` around lines 14 - 18,
In useInitCardinalsMutation's onSuccess handler remove the unnecessary clubId
guard (the mutationFn already throws when clubId is missing) and always call
queryClient.invalidateQueries({ queryKey: ['mypage', 'me', clubId] }) alongside
the existing ['mypage','clubs'] invalidation; update the onSuccess block to
directly invalidate both queries without the if (clubId) check so the code is
simpler and consistent with mutationFn's preconditions.
src/components/mypage/MyPageContent.tsx (1)

94-96: 가로 스크롤 또는 빈 상태 UI를 고려하세요.

clubs가 빈 배열일 때 "활동정보" 섹션이 비어 보일 수 있습니다. 클럽이 없는 경우에 대한 안내 메시지 추가를 고려해볼 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mypage/MyPageContent.tsx` around lines 94 - 96, MyPageContent
renders clubs.map(...) with ClubInfoCard and doesn't handle the empty or
overflow case; update the JSX in the component that uses the clubs array (the
clubs variable and ClubInfoCard list) to conditionally render a clear
empty-state UI when clubs.length === 0 (e.g., a centered message/icon like "가입한
클럽이 없습니다" or a dedicated EmptyState component) and ensure the container styling
prevents unwanted horizontal scrolling (add a wrapping element with
overflow-x-hidden or appropriate flex/wrap behavior around the ClubInfoCard
list).
src/utils/admin/memberMapper.ts (1)

3-7: ROLE_MAP 상수 중복 제거 고려

ROLE_MAPuseAdminMemberMutations.tsROLE_LABEL과 동일한 매핑을 정의하고 있습니다. 공통 상수 파일로 추출하여 중복을 제거하는 것을 권장합니다.

♻️ 제안: 공통 상수로 추출
// src/constants/admin/memberRole.constants.ts
import type { ClubMemberRole } from '@/types/admin/member';

export const ROLE_LABEL: Record<ClubMemberRole, string> = {
  USER: '사용자',
  ADMIN: '관리자',
  LEAD: '리더',
};

그 후 memberMapper.tsuseAdminMemberMutations.ts에서 import하여 사용할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/utils/admin/memberMapper.ts` around lines 3 - 7, ROLE_MAP duplicates
ROLE_LABEL; extract a single shared constant (e.g. export const ROLE_LABEL:
Record<ClubMemberRole,string>) into a common constants module and replace both
ROLE_MAP (in memberMapper.ts) and ROLE_LABEL (in useAdminMemberMutations.ts)
with imports from that module, keeping the ClubMemberRole type and the same
mapping values to avoid behavioral changes.
src/components/mypage/SetCardinalModal/index.tsx (1)

70-70: px-2.5 대신 디자인 토큰 사용 고려

px-2.5는 하드코딩된 값입니다. 프로젝트의 디자인 토큰 패딩 클래스(예: px-200, px-300)로 대체하는 것을 고려해 주세요. As per coding guidelines, "Always use design token classes first — no hardcoded values".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mypage/SetCardinalModal/index.tsx` at line 70, The div in the
SetCardinalModal component uses a hardcoded padding class "px-2.5" which
violates the design-token rule; replace that hardcoded class with the
appropriate design token padding class (e.g., "px-200" or "px-300") in the
className on the element inside SetCardinalModal (look for the div with
className="flex items-center gap-400 px-2.5 pt-200") so the component uses the
project's design token utility classes consistently.
src/hooks/mutations/admin/useAdminMemberMutations.ts (1)

63-63: 상태 값을 타입 상수로 관리 고려

'BANNED''ACTIVE' 문자열이 하드코딩되어 있습니다. MemberStatus 타입과 연계된 상수 객체를 사용하면 타입 안전성과 유지보수성이 향상됩니다.

♻️ 제안
+import { MemberStatus } from '@/types/admin/member';
+
+const STATUS = {
+  ACTIVE: 'ACTIVE',
+  BANNED: 'BANNED',
+} as const satisfies Partial<Record<MemberStatus, MemberStatus>>;

// useBanMember 내부
-        old.map((m) => (m.clubMemberId === clubMemberId ? { ...m, status: 'BANNED' } : m)),
+        old.map((m) => (m.clubMemberId === clubMemberId ? { ...m, status: STATUS.BANNED } : m)),

Also applies to: 91-91

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/mutations/admin/useAdminMemberMutations.ts` at line 63, Replace
hardcoded status strings in the update logic with the project-wide MemberStatus
constant/enum: where old.map((m) => (m.clubMemberId === clubMemberId ? { ...m,
status: 'BANNED' } : m)) and the similar occurrence at line 91 currently use
string literals ('BANNED'/'ACTIVE'); import or reference MemberStatus (or STATUS
constant object) and use MemberStatus.BANNED and MemberStatus.ACTIVE instead to
ensure type safety and consistency across useAdminMemberMutations.ts.
src/components/mypage/edit/EditProfileContent.tsx (1)

116-118: 임의 px 값이 토큰 규칙을 우회합니다.

max-w-[1088px], gap-[35px], pb-[80px], max-w-[640px]는 현재 디자인 토큰 체계를 벗어납니다. 기존 size/spacing 토큰으로 치환하거나 필요한 토큰을 먼저 추가하는 편이 좋겠습니다.

As per coding guidelines "Always use design token classes first (text-, bg-, typo-, p-, gap-*) — no hardcoded values; ask before adding new tokens".

Also applies to: 149-149

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mypage/edit/EditProfileContent.tsx` around lines 116 - 118,
The container in EditProfileContent (the className passed into the cn call
inside the EditProfileContent component) uses hardcoded bracket utilities like
max-w-[1088px], gap-[35px], pb-[80px], px-450 and pt-450 which bypass the design
tokens; replace those with the corresponding existing design token classes
(e.g., max-w-*, gap-*, p-*/px-*/pt-*/pb-* tokens) or, if no appropriate token
exists, request/add the new spacing/size token and then use it, and ensure the
same change is applied to the other occurrence flagged (line ~149) so all
hardcoded values are removed.
src/constants/admin/memberDetailModal.constants.ts (2)

3-8: STATUS_LABEL 값이 한국어로 필요한지 확인이 필요합니다.

현재 라벨이 영문 상태값과 동일합니다 (ACTIVE, WAITING 등). UI에서 한국어 표시가 필요하다면 라벨을 한국어로 변경하는 것을 고려해 주세요.

💡 한국어 라벨 예시
 export const STATUS_LABEL: Record<MemberStatus, string> = {
-  ACTIVE: 'ACTIVE',
-  WAITING: 'WAITING',
-  BANNED: 'BANNED',
-  LEFT: 'LEFT',
+  ACTIVE: '활동중',
+  WAITING: '대기중',
+  BANNED: '추방됨',
+  LEFT: '탈퇴',
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/constants/admin/memberDetailModal.constants.ts` around lines 3 - 8,
STATUS_LABEL currently maps MemberStatus keys to identical English strings; if
the UI requires Korean labels, update the STATUS_LABEL constant to map each
MemberStatus (e.g., ACTIVE, WAITING, BANNED, LEFT) to the appropriate Korean
string equivalents, keeping the keys as the enum names but replacing the values
with Korean labels and ensure any code that consumes STATUS_LABEL (e.g., UI
components rendering member status) continues to use these keys unchanged.

46-54: 인터페이스와 실제 사용이 일치하지 않습니다.

onApprove(line 49)와 onResetPassword(line 51)가 인터페이스에 정의되어 있지만, getFooterActions 함수에서는 주석 처리되어 사용되지 않습니다. 이로 인해 호출자가 불필요한 핸들러를 전달해야 할 수 있습니다.

TODO 항목이므로 당장 삭제하기 어렵다면, 주석으로 명시하거나 인터페이스에서 제거하는 것을 권장합니다.

♻️ 수정 제안
 interface FooterActionHandlers {
   memberRole: ClubMemberRole;
   status: MemberStatus;
-  onApprove?: () => void;
+  // TODO: 가입 승인 API 열리면 추가
+  // onApprove?: () => void;
   onChangeRole?: () => void;
-  onResetPassword?: () => void;
+  // TODO: 비번 변경 API 열리면 추가
+  // onResetPassword?: () => void;
   onBan?: () => void;
   onRestore?: () => void;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/constants/admin/memberDetailModal.constants.ts` around lines 46 - 54, The
FooterActionHandlers interface declares onApprove and onResetPassword while
getFooterActions currently comments them out; synchronize them by either
removing onApprove and onResetPassword from FooterActionHandlers or
reintroducing/using these handlers in getFooterActions so the interface matches
usage—update the FooterActionHandlers declaration (or uncomment and wire the
handlers in getFooterActions) and add a brief comment explaining why the
handlers are present/unused to avoid confusion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/admin/member/MemberPageContent.tsx`:
- Around line 144-155: The onChangeRole handler currently toggles roles by
treating anything not 'ADMIN' as 'USER', causing 'LEAD' to be converted to
'ADMIN'; replace the boolean toggle with an explicit mapping for nextRole (e.g.,
NEXT_ROLE = { ADMIN: 'USER', USER: 'ADMIN', LEAD: 'LEAD' } or block changes for
'LEAD') and use that mapping when computing nextRole, then update
setDetailMember (preserving memberRole and position via ROLE_LABEL[nextRole])
and call changeMemberRole({ clubMemberId: detailMember.clubMemberId, memberRole:
nextRole }) only for allowed transitions; update references to onChangeRole,
detailMember, nextRole, ROLE_LABEL, setDetailMember, and changeMemberRole
accordingly.

In `@src/components/admin/member/MemberTable.tsx`:
- Line 49: The isAllSelected computation incorrectly returns true when members
is an empty array; update the logic in MemberTable so isAllSelected only becomes
true when there are members and all are selected (i.e., require members.length >
0 in the equality check). Locate the isAllSelected declaration and change the
condition that compares selectedIds.size and members.length to also verify
members.length > 0 so the "select all" checkbox is not checked when there are no
members.

In `@src/components/admin/member/modal/MemberDetailModal.tsx`:
- Around line 145-158: The footerActions array can contain items with undefined
handlers causing a runtime error when AlertDialogAction calls onClick={handler};
update the rendering to only render actions with a valid handler or ensure
handlers are always defined: either filter footerActions before mapping (e.g.,
footerActions.filter(a => a.handler)) or change getFooterActions to assign a
no-op function as default for any missing onChangeRole/onBan/onRestore; target
symbols: footerActions, getFooterActions, AlertDialogAction, and the handler
prop used in the map.

In `@src/components/mypage/edit/EditProfileContent.tsx`:
- Around line 34-35: The component currently renders a perpetual "로딩 중..."
whenever me is falsy; adjust EditProfileContent to distinguish loading, success,
and error states by using the query status (from useMyMemberQuery) instead of
only checking me, and by separating update mutation state
(useUpdateProfileMutation's isPending) from query errors; specifically, check
the query's isLoading/isError (or error) and render an error or "no clubId" UI
when appropriate, render the loading indicator only when the query is truly
loading, and keep isPending for showing mutation-in-progress UI while allowing
API error UI to surface when the query fails. Ensure you update any other
similar logic referenced around lines 106-112 that also rely solely on me being
truthy.

In `@src/components/mypage/edit/PersonalInfoFields.tsx`:
- Around line 17-20: handlePhoneChange currently calls telOnChange(e) after
setValue('tel', formatPhone(...), { shouldValidate: true }), which overwrites
the formatted value with the raw event value and breaks the schema regex; remove
the telOnChange(e) invocation from handlePhoneChange so only setValue('tel',
formatPhone(e.target.value), { shouldValidate: true }) runs (leave formatPhone
and setValue usage intact to perform formatting and validation).

In `@src/components/mypage/edit/ProfileImageEditor.tsx`:
- Around line 18-30: The current preview URL cleanup can leak the previous
object URL because useEffect's cleanup runs with the latest previewUrl; modify
handleChange in ProfileImageEditor so it revokes any existing previewUrl before
creating a new one: when a new file is selected in handleChange, if previewUrl
exists call URL.revokeObjectURL(previewUrl) then
setPreviewUrl(URL.createObjectURL(file)) and call onFileChange(file);
alternatively track the previous URL in a ref (e.g., prevPreviewRef) and revoke
the old value there when updating to the new URL.

In `@src/constants/admin/memberDetailModal.constants.ts`:
- Around line 44-45: The import of the type ClubMemberRole is located mid-file;
move the "import type { ClubMemberRole } from '@/types/admin/member';" statement
up into the top import block with the other imports (as a type-only import) and
remove the duplicate/mid-file import occurrence so all imports reside at the top
and the rest of the file references ClubMemberRole normally.

In `@src/constants/admin/memberTable.constants.ts`:
- Line 38: The comparator currently uses parseInt(b.generation, 10) -
parseInt(a.generation, 10) which only considers the first cardinal; update the
comparator in memberTable.constants.ts to split each generation string (e.g.,
"1, 2, 3") into an array of numbers (trim and parse each element) and then
compare those arrays lexicographically (compare element 0, then 1, etc., falling
back to length if all equal). Alternatively, if the intended behavior is to sort
only by the first cardinal, explicitly parse and use only the first element
(e.g., split and parse index 0) and add a comment clarifying that choice;
reference the existing comparator expression to locate where to change.

In `@src/hooks/mutations/admin/useAdminCardinalMutations.ts`:
- Around line 3-4: 현재 파일이 존재하지 않는 CLUB_ID를 import하고 있어 빌드 오류가 발생합니다; 대신
useClubId 훅을 import해 동적으로 clubId를 사용하고 useMutation/useQueryClient 패턴(예:
useInitCardinalsMutation)을 따르세요. 구체적으로는 CLUB_ID import를 제거하고 useClubId를 불러와
clubId를 얻은 뒤 useMutation의 mutationFn에서 cardinalApi.createCardinal(clubId, body)를
호출하고 onSuccess에서 queryClient.invalidateQueries({ queryKey: ['cardinals', clubId]
})를 호출하도록 useCreateCardinal(또는 해당 훅 이름) 구현을 수정하세요.

In `@src/hooks/mutations/mypage/useUpdateProfileMutation.ts`:
- Around line 14-21: The upload flow in useUpdateProfileMutation doesn't guard
against missing presigned data or failed HTTP PUTs: check that
fileApi.getPresignedUrls returned a valid presigned entry (res.data.data[0] /
presigned) and if missing, abort and surface an error; after calling
fetch(presigned.putUrl, ...) verify the response.ok (and response.status) and
throw or return an error when not OK so updateClubProfile is not called with a
broken storageKey; make these checks around the existing
fileApi.getPresignedUrls call and the fetch call to reliably detect and handle
upload failures.

In `@src/types/admin/cardinal.ts`:
- Around line 9-12: The CreateCardinalBody interface is missing fields used by
the mutation; update the CreateCardinalBody type to include year and semester so
it matches the payload used in useAdminCardinalMutations.ts (i.e., define
CreateCardinalBody with cardinalNumber, year, semester, and inProgress), or
alternatively change useAdminCardinalMutations.ts to only send the fields
currently defined on CreateCardinalBody—ensure the symbol CreateCardinalBody and
the mutation functions in useAdminCardinalMutations.ts agree on the same shape.

In `@src/types/mypage.ts`:
- Around line 4-18: Update the MyMember interface so fields that can be absent
or null are typed accordingly: change tel, bio, and profileImageUrl on MyMember
to accept null/undefined (e.g. tel?: string | null, bio?: string | null,
profileImageUrl?: string | null) so usage sites like me.tel?.replace(...),
me.bio ?? '' and conditional checks on profileImageUrl align with the type.
Adjust only the MyMember declaration to reflect these nullable/optional types.

In `@src/utils/admin/memberMapper.ts`:
- Line 15: memberMapper.ts currently sets the mapped object's role field to an
empty string causing blank UI; update the mapper (the function that builds the
mapped member object in memberMapper.ts) to assign role from the appropriate
source (e.g., ROLE_MAP[cm.memberRole] or the same value used for position) or
remove/stop exposing role if unused by getPersonalInfo in
memberDetailModal.constants.ts — locate the mapping that sets "role: ''" and
either replace the empty string with ROLE_MAP[cm.memberRole] (or the correct
member property) or remove the role property and adjust consumer code in
getPersonalInfo accordingly.

---

Outside diff comments:
In @.claude/settings.local.json:
- Around line 6-23: The JSON contains unresolved Git merge conflict markers
(<<<<<<<, =======, >>>>>>>) around the "allow" entries (e.g., entries like
"mcp__figma__get_design_context", "Bash(ls
\"D:/project/weeth-client/src/app/\\(public\\)/\\(landing\\)/\")", "Bash(git
fetch:*)"); remove the conflict markers, pick and retain the intended final set
of allow entries (merging or deduplicating the two sides as appropriate), ensure
the result is a single valid JSON array/object (no leftover markers, proper
commas/quotes), and save so the file parses as valid JSON.

In `@src/components/admin/member/MemberPageContent.tsx`:
- Around line 54-76: The current logic uses allBanned to decide actions and
blindly calls banMember/restoreMember for every selectedMembers, causing
redundant calls when selection has mixed statuses; update the MemberTopBar
handlers so they either disable the action when selection is mixed
(selectedCount>0 && !allBanned && !allActive) or, if keeping action enabled,
split selectedMembers by m.status and call banMember only for members with
status !== 'BANNED' and restoreMember only for members with status === 'BANNED';
likewise guard changeMemberRole to only call changeMemberRole for members whose
role actually differs from targetRole; use the existing symbols selectedMembers,
allBanned, selectedCount, targetRole, banMember, restoreMember, and
changeMemberRole to implement the filtering or disabling behavior.

---

Nitpick comments:
In `@src/components/mypage/edit/EditProfileContent.tsx`:
- Around line 116-118: The container in EditProfileContent (the className passed
into the cn call inside the EditProfileContent component) uses hardcoded bracket
utilities like max-w-[1088px], gap-[35px], pb-[80px], px-450 and pt-450 which
bypass the design tokens; replace those with the corresponding existing design
token classes (e.g., max-w-*, gap-*, p-*/px-*/pt-*/pb-* tokens) or, if no
appropriate token exists, request/add the new spacing/size token and then use
it, and ensure the same change is applied to the other occurrence flagged (line
~149) so all hardcoded values are removed.

In `@src/components/mypage/edit/PersonalInfoFields.tsx`:
- Line 1: The import of the Control type from 'react-hook-form' is unused in
PersonalInfoFields.tsx; remove Control from the import list so the line imports
only FieldErrors, UseFormRegister, and UseFormSetValue, and then run your
linter/TS check to ensure no other references to Control remain (update any code
if Control was intended to be used).

In `@src/components/mypage/MyPageContent.tsx`:
- Around line 94-96: MyPageContent renders clubs.map(...) with ClubInfoCard and
doesn't handle the empty or overflow case; update the JSX in the component that
uses the clubs array (the clubs variable and ClubInfoCard list) to conditionally
render a clear empty-state UI when clubs.length === 0 (e.g., a centered
message/icon like "가입한 클럽이 없습니다" or a dedicated EmptyState component) and ensure
the container styling prevents unwanted horizontal scrolling (add a wrapping
element with overflow-x-hidden or appropriate flex/wrap behavior around the
ClubInfoCard list).

In `@src/components/mypage/MyPageDropdownMenu.tsx`:
- Around line 40-57: Remove the commented-out "서비스 탈퇴" block and the disabled
AlertDialog modal code to improve readability: delete the commented
DropdownMenuItem that references setWithdrawOpen / 서비스 탈퇴 and the AlertDialog
block (props open/onOpenChange, status/title/description,
AlertDialogAction/AlertDialogCancel) and any unused state or imports (e.g.,
withdrawOpen, setWithdrawOpen) from MyPageDropdownMenu.tsx; if the feature will
be implemented later, create a tracking issue instead of leaving commented code.
- Line 19: The declared state withdrawOpen (and its setter setWithdrawOpen) in
MyPageDropdownMenu is unused because the withdraw feature was commented out;
remove the unused useState declaration (the const [withdrawOpen,
setWithdrawOpen] = useState(false) line) and any related unused imports if they
become orphaned (e.g., useState) to clean up the component and avoid linter
warnings.

In `@src/components/mypage/SetCardinalModal/index.tsx`:
- Line 70: The div in the SetCardinalModal component uses a hardcoded padding
class "px-2.5" which violates the design-token rule; replace that hardcoded
class with the appropriate design token padding class (e.g., "px-200" or
"px-300") in the className on the element inside SetCardinalModal (look for the
div with className="flex items-center gap-400 px-2.5 pt-200") so the component
uses the project's design token utility classes consistently.

In `@src/constants/admin/memberDetailModal.constants.ts`:
- Around line 3-8: STATUS_LABEL currently maps MemberStatus keys to identical
English strings; if the UI requires Korean labels, update the STATUS_LABEL
constant to map each MemberStatus (e.g., ACTIVE, WAITING, BANNED, LEFT) to the
appropriate Korean string equivalents, keeping the keys as the enum names but
replacing the values with Korean labels and ensure any code that consumes
STATUS_LABEL (e.g., UI components rendering member status) continues to use
these keys unchanged.
- Around line 46-54: The FooterActionHandlers interface declares onApprove and
onResetPassword while getFooterActions currently comments them out; synchronize
them by either removing onApprove and onResetPassword from FooterActionHandlers
or reintroducing/using these handlers in getFooterActions so the interface
matches usage—update the FooterActionHandlers declaration (or uncomment and wire
the handlers in getFooterActions) and add a brief comment explaining why the
handlers are present/unused to avoid confusion.

In `@src/hooks/mutations/admin/useAdminMemberMutations.ts`:
- Line 63: Replace hardcoded status strings in the update logic with the
project-wide MemberStatus constant/enum: where old.map((m) => (m.clubMemberId
=== clubMemberId ? { ...m, status: 'BANNED' } : m)) and the similar occurrence
at line 91 currently use string literals ('BANNED'/'ACTIVE'); import or
reference MemberStatus (or STATUS constant object) and use MemberStatus.BANNED
and MemberStatus.ACTIVE instead to ensure type safety and consistency across
useAdminMemberMutations.ts.

In `@src/hooks/mutations/mypage/useInitCardinalsMutation.ts`:
- Around line 14-18: In useInitCardinalsMutation's onSuccess handler remove the
unnecessary clubId guard (the mutationFn already throws when clubId is missing)
and always call queryClient.invalidateQueries({ queryKey: ['mypage', 'me',
clubId] }) alongside the existing ['mypage','clubs'] invalidation; update the
onSuccess block to directly invalidate both queries without the if (clubId)
check so the code is simpler and consistent with mutationFn's preconditions.

In `@src/lib/apis/server.ts`:
- Around line 75-77: The current accessToken fallback uses DEV_ACCESS_TOKEN
whenever ACCESS_TOKEN_KEY is missing, which can hide an existing REFRESH_TOKEN
and cause mismatched session behavior; update the logic around accessToken (the
cookieStore.get(ACCESS_TOKEN_KEY) resolution) to only use
process.env.DEV_ACCESS_TOKEN in development when there is no refresh token
present (check cookieStore.get(REFRESH_TOKEN_KEY)?.value) and/or when no session
cookies exist, so the DEV_ACCESS_TOKEN is applied only for truly session-less
dev requests and not when a refresh token is available.

In `@src/lib/schemas/editProfile.ts`:
- Around line 10-13: The email field in the editProfile Zod schema uses a custom
.refine with a regex; replace that with Zod's built-in .email() to use standard
validation: update the email schema (the email property inside the editProfile
schema) to call .email('올바른 이메일 형식이 아닙니다') and use .nonempty('이메일을 입력해주세요') (or
keep the existing .min(1, ...)) instead of the regex-based .refine to simplify
and standardize validation.

In `@src/utils/admin/memberMapper.ts`:
- Around line 3-7: ROLE_MAP duplicates ROLE_LABEL; extract a single shared
constant (e.g. export const ROLE_LABEL: Record<ClubMemberRole,string>) into a
common constants module and replace both ROLE_MAP (in memberMapper.ts) and
ROLE_LABEL (in useAdminMemberMutations.ts) with imports from that module,
keeping the ClubMemberRole type and the same mapping values to avoid behavioral
changes.
🪄 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: 279476c4-e019-4102-af7a-abe36128e8ed

📥 Commits

Reviewing files that changed from the base of the PR and between 70dadf5 and 15d5082.

📒 Files selected for processing (46)
  • .claude/settings.local.json
  • src/app/api/proxy/[...path]/route.ts
  • src/assets/icons/admin/index.ts
  • src/components/admin/member/MemberPageContent.tsx
  • src/components/admin/member/MemberSearchBar.tsx
  • src/components/admin/member/MemberTable.tsx
  • src/components/admin/member/MemberTopBar.tsx
  • src/components/admin/member/modal/MemberDetailModal.tsx
  • src/components/mypage/ClubInfoCard.tsx
  • src/components/mypage/EditProfileContent.tsx
  • src/components/mypage/FormField.tsx
  • src/components/mypage/MyPageContent.tsx
  • src/components/mypage/MyPageDropdownMenu.tsx
  • src/components/mypage/SetCardinalModal/index.tsx
  • src/components/mypage/SetCardinalModal/useCardinalModal.ts
  • src/components/mypage/edit/EditProfileContent.tsx
  • src/components/mypage/edit/PersonalInfoFields.tsx
  • src/components/mypage/edit/ProfileImageEditor.tsx
  • src/components/mypage/edit/SchoolInfoFields.tsx
  • src/components/mypage/edit/index.ts
  • src/components/mypage/index.ts
  • src/constants/admin/memberDetailModal.constants.ts
  • src/constants/admin/memberTable.constants.ts
  • src/constants/admin/memberTopBar.constants.ts
  • src/hooks/mutations/admin/index.ts
  • src/hooks/mutations/admin/useAdminCardinalMutations.ts
  • src/hooks/mutations/admin/useAdminMemberMutations.ts
  • src/hooks/mutations/mypage/useInitCardinalsMutation.ts
  • src/hooks/mutations/mypage/useUpdateProfileMutation.ts
  • src/hooks/queries/admin/index.ts
  • src/hooks/queries/admin/useAdminMemberQueries.ts
  • src/hooks/queries/index.ts
  • src/hooks/queries/mypage/useMyClubsQuery.ts
  • src/hooks/queries/mypage/useMyMemberQuery.ts
  • src/hooks/queries/useCardinals.ts
  • src/lib/apis/adminMember.ts
  • src/lib/apis/cardinal.ts
  • src/lib/apis/index.ts
  • src/lib/apis/mypage.ts
  • src/lib/apis/server.ts
  • src/lib/schemas/editProfile.ts
  • src/stores/useClubStore.ts
  • src/types/admin/cardinal.ts
  • src/types/admin/member.d.ts
  • src/types/mypage.ts
  • src/utils/admin/memberMapper.ts
💤 Files with no reviewable changes (1)
  • src/components/mypage/EditProfileContent.tsx

@JIN921 JIN921 changed the title feat: 마이페이지 API 연결 [Feat] WTH-234: 마이페이지 API 연결 Apr 6, 2026
@JIN921 JIN921 self-assigned this Apr 6, 2026
@JIN921 JIN921 added 🐞 BugFix Something isn't working ✨ Feature 기능 개발 labels Apr 6, 2026
@JIN921 JIN921 requested review from dalzzy, nabbang6 and woneeeee April 6, 2026 12:54
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/api/proxy/[...path]/route.ts

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberPageContent.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberSearchBar.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberTable.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberTopBar.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

PR 테스트 결과

Jest: 통과

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

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

PR 검증 결과

TypeScript: 실패
ESLint: 통과
Prettier: 실패
Build: 실패

⚠️ 일부 검증에 실패했습니다. 확인 후 수정해주세요.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
.claude/settings.local.json (1)

6-28: ⚠️ Potential issue | 🔴 Critical

해결되지 않은 Git 병합 충돌이 있습니다.

이 파일에 Git 병합 충돌 마커(<<<<<<< HEAD, =======, >>>>>>>)가 그대로 남아있어 JSON 파싱이 불가능합니다. 이 상태로는 설정 파일을 읽을 수 없습니다.

충돌을 해결하고 유효한 JSON 형식으로 수정해 주세요. 중첩된 충돌이 있는 것으로 보이므로 주의가 필요합니다:

  • 첫 번째 충돌: Line 6-25 (HEAD vs feat/WTH-215-멤버-어드민-API-연결)
  • 두 번째 충돌: Line 19-28 (중첩된 충돌, commit 70dadf52f3d996e43f43531b2e889a992079f87c)
🐛 충돌 해결 후 예상되는 구조 (필요한 항목 선택 필요)
-<<<<<<< HEAD
       "mcp__figma__get_design_context",
       "Bash(grep -E \"\\\\.tsx$\")",
       ...
-<<<<<<< HEAD
-      "Bash(find D:/project/weeth-client/src/constants -name *.ts)"
-=======
-      "Bash(ls \"D:/project/weeth-client/src/app/\\(public\\)/\\(landing\\)/\")",
-      "Bash(git fetch:*)"
->>>>>>> feat/WTH-215-멤버-어드민-API-연결
-=======
       "Bash(find D:/project/weeth-client/src/constants -name *.ts)",
       "Bash(grep -r \"POSTHOG\\\\|PostHog\" D:projectweeth-client/.env*)"
->>>>>>> 70dadf52f3d996e43f43531b2e889a992079f87c
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.claude/settings.local.json around lines 6 - 28, This file contains
unresolved Git merge conflict markers (<<<<<<< HEAD, =======, >>>>>>>) that
break JSON; remove the conflict markers, pick/merge the desired entries from the
conflicting blocks (e.g., the different "Bash(...)" lines and
"mcp__figma__get_design_context"/"mcp__figma__get_screenshot" entries), ensure
there are no duplicate or malformed entries, and produce a single valid JSON
array/object with proper commas and quoting so the file parses; pay special
attention to the nested conflict between the HEAD block and commits shown
(resolve which of the "Bash(find ...)" and "Bash(ls ...)" / "Bash(git fetch:*)"
/ "Bash(grep -r ...)" variants to keep) and remove any leftover whitespace or
stray characters from the merge markers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/mypage/edit/EditProfileContent.tsx`:
- Around line 151-155: The prop type mismatch occurs because me.profileImageUrl
is string | null but ProfileImageEditor.profileImageUrl expects string |
undefined; fix by normalizing the value before passing it (e.g., convert null to
undefined) or update ProfileImageEditor’s prop type to accept string | null;
locate the usage in EditProfileContent (ProfileImageEditor component call) and
either change the prop expression to pass undefined when me.profileImageUrl is
null or adjust the ProfileImageEditor prop type definition to string | null to
match me.profileImageUrl.

In `@src/components/mypage/edit/ProfileImageEditor.tsx`:
- Around line 26-31: The cleanup effect in ProfileImageEditor captures the
initial previewUrl (null) because it uses an empty dependency array, so when
unmounted the real object URL isn't revoked; fix this by introducing a ref
(e.g., previewUrlRef = useRef<string | null>(null)), ensure
previewUrlRef.current is updated whenever previewUrl changes (or at the point
you call setPreviewUrl), and change the unmount cleanup in the useEffect to call
URL.revokeObjectURL(previewUrlRef.current) if present; keep the unmount effect
without dependencies but read the latest URL from previewUrlRef.current to
safely revoke the object URL on unmount.

---

Outside diff comments:
In @.claude/settings.local.json:
- Around line 6-28: This file contains unresolved Git merge conflict markers
(<<<<<<< HEAD, =======, >>>>>>>) that break JSON; remove the conflict markers,
pick/merge the desired entries from the conflicting blocks (e.g., the different
"Bash(...)" lines and
"mcp__figma__get_design_context"/"mcp__figma__get_screenshot" entries), ensure
there are no duplicate or malformed entries, and produce a single valid JSON
array/object with proper commas and quoting so the file parses; pay special
attention to the nested conflict between the HEAD block and commits shown
(resolve which of the "Bash(find ...)" and "Bash(ls ...)" / "Bash(git fetch:*)"
/ "Bash(grep -r ...)" variants to keep) and remove any leftover whitespace or
stray characters from the merge markers.
🪄 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: 2e32433b-f899-4e91-9840-66282250ed56

📥 Commits

Reviewing files that changed from the base of the PR and between 00ed688 and 1e52b75.

📒 Files selected for processing (8)
  • .claude/settings.local.json
  • src/components/mypage/edit/EditProfileContent.tsx
  • src/components/mypage/edit/PersonalInfoFields.tsx
  • src/components/mypage/edit/ProfileImageEditor.tsx
  • src/hooks/mutations/mypage/useUpdateProfileMutation.ts
  • src/lib/apis/index.ts
  • src/lib/apis/server.ts
  • src/types/mypage.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/lib/apis/server.ts
  • src/hooks/mutations/mypage/useUpdateProfileMutation.ts
  • src/components/mypage/edit/PersonalInfoFields.tsx
  • src/lib/apis/index.ts
  • src/types/mypage.ts

Comment on lines +26 to +31
useEffect(() => {
return () => {
if (previewUrl) URL.revokeObjectURL(previewUrl);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

언마운트 시 Object URL이 해제되지 않습니다.

빈 의존성 배열 []로 인해 cleanup 함수가 마운트 시점의 previewUrl 값(null)을 캡처합니다. 컴포넌트가 언마운트될 때 실제 URL을 해제하지 못해 메모리 누수가 발생합니다.

ref를 사용하여 현재 URL을 추적하는 것이 안전합니다.

🛠️ ref를 사용한 수정 제안
-  const [previewUrl, setPreviewUrl] = useState<string | null>(null);
+  const [previewUrl, setPreviewUrl] = useState<string | null>(null);
+  const previewUrlRef = useRef<string | null>(null);

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

   useEffect(() => {
     return () => {
-      if (previewUrl) URL.revokeObjectURL(previewUrl);
+      if (previewUrlRef.current) URL.revokeObjectURL(previewUrlRef.current);
     };
-    // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mypage/edit/ProfileImageEditor.tsx` around lines 26 - 31, The
cleanup effect in ProfileImageEditor captures the initial previewUrl (null)
because it uses an empty dependency array, so when unmounted the real object URL
isn't revoked; fix this by introducing a ref (e.g., previewUrlRef =
useRef<string | null>(null)), ensure previewUrlRef.current is updated whenever
previewUrl changes (or at the point you call setPreviewUrl), and change the
unmount cleanup in the useEffect to call
URL.revokeObjectURL(previewUrlRef.current) if present; keep the unmount effect
without dependencies but read the latest URL from previewUrlRef.current to
safely revoke the object URL on unmount.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/api/proxy/[...path]/route.ts

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberPageContent.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberSearchBar.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberTable.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberTopBar.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

PR 테스트 결과

Jest: 통과

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

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

PR 검증 결과

TypeScript: 통과
ESLint: 통과
Prettier: 통과
Build: 실패

⚠️ 일부 검증에 실패했습니다. 확인 후 수정해주세요.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
src/components/admin/member/modal/MemberDetailModal.tsx (1)

155-168: ⚠️ Potential issue | 🔴 Critical

handlerundefined일 때 런타임 에러 발생 가능

getFooterActions에서 반환되는 handler는 선택적 속성(onChangeRole?, onBan?, onRestore?)에서 직접 할당되므로 undefined일 수 있습니다. 현재 코드는 .filter() 없이 바로 매핑하여 onClick={handler}를 전달하므로, 핸들러가 없는 상태에서 버튼 클릭 시 undefined()가 호출되어 런타임 에러가 발생합니다.

filter를 다시 추가하거나 handler가 정의된 경우에만 렌더링하도록 수정이 필요합니다.

🔧 제안 수정안
-              {footerActions.map(({ label, title, handler }) => (
+              {footerActions
+                .filter(({ handler }) => handler !== undefined)
+                .map(({ label, title, handler }) => (
                 <AlertDialog
                   key={label}
                   title={title}
                   trigger={
                     <Button variant="secondary" size="lg">
                       {label}
                     </Button>
                   }
                 >
                   <AlertDialogAction onClick={handler}>확인</AlertDialogAction>
                   <AlertDialogCancel>취소</AlertDialogCancel>
                 </AlertDialog>
               ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/member/modal/MemberDetailModal.tsx` around lines 155 -
168, The mapping over footerActions passes potentially undefined handler to
AlertDialogAction's onClick, causing runtime errors when handler is absent;
update the render to only include actions with a defined handler (e.g., filter
footerActions for entries where handler is a function or check handler !==
undefined) before mapping, or conditionally render AlertDialog/AlertDialogAction
only when handler exists; reference getFooterActions for where handlers
originate and AlertDialogAction/handler in MemberDetailModal for the change.
src/components/admin/member/MemberPageContent.tsx (1)

149-162: ⚠️ Potential issue | 🟠 Major

LEAD 역할이 ADMIN으로 잘못 변경됨

현재 로직은 memberRole === 'ADMIN'이 아닌 모든 경우를 'ADMIN'으로 변경합니다. LEAD 역할의 멤버가 "역할 변경" 버튼을 클릭하면 의도치 않게 ADMIN으로 변경됩니다.

LEAD 역할은 별도로 처리하거나 역할 변경을 차단해야 합니다.

🔧 제안 수정안
         onChangeRole={
           detailMember
             ? () => {
+                if (detailMember.memberRole === 'LEAD') {
+                  return;
+                }
                 const nextRole = detailMember.memberRole === 'ADMIN' ? 'USER' : 'ADMIN';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/member/MemberPageContent.tsx` around lines 149 - 162,
The onChangeRole handler for detailMember incorrectly maps any non-ADMIN role
(including LEAD) to 'ADMIN'; fix by explicitly handling roles: if
detailMember.memberRole is 'ADMIN' set nextRole to 'USER', if it's 'USER' set
nextRole to 'ADMIN', and if it's 'LEAD' either return/do nothing or show/block
the change (i.e., do not call setDetailMember or changeMemberRole for 'LEAD').
Update the handler that sets nextRole and uses setDetailMember and
changeMemberRole (referenced symbols: detailMember, onChangeRole,
setDetailMember, changeMemberRole, ROLE_LABEL) so LEAD is preserved or handled
separately.
🧹 Nitpick comments (2)
src/components/mypage/edit/EditProfileContent.tsx (1)

122-128: 하드코딩된 spacing 값을 디자인 토큰으로 대체하세요.

gap-[35px]pb-[80px]는 하드코딩된 값입니다. 코딩 가이드라인에 따라 디자인 토큰 클래스를 우선 사용해야 합니다.

🔧 제안 수정안
     <div
       className={cn(
-        'mx-auto flex w-full max-w-[1088px] flex-col gap-[35px] px-450 pt-450 pb-[80px]',
+        'mx-auto flex w-full max-w-[1088px] flex-col gap-700 px-450 pt-450 pb-900',
         className,
       )}
       {...props}
     >

적절한 토큰이 없다면 디자인 시스템에 새 토큰 추가가 필요한지 확인해주세요. 코딩 가이드라인에 따르면 "no hardcoded values; ask before adding new tokens"입니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mypage/edit/EditProfileContent.tsx` around lines 122 - 128,
EditProfileContent is using hardcoded spacing classes gap-[35px] and pb-[80px];
replace them with the appropriate design-token utility classes (e.g., the
project's spacing tokens) inside the className on the root div in
EditProfileContent; if suitable tokens don't exist, open a design-system request
to add tokens matching 35px/80px and then update the className to use those new
token classes, avoiding any remaining bracketed hardcoded values.
src/components/admin/member/MemberPageContent.tsx (1)

72-81: 대량 뮤테이션 시 부분 실패 처리 및 UX 고려 필요

forEach로 여러 멤버에 대해 동시에 뮤테이션을 호출하면:

  • 일부만 실패해도 사용자에게 명확한 피드백이 없음
  • 로딩 상태 표시 없이 여러 API 호출이 병렬로 진행됨

현재 구현이 단순한 케이스에서는 동작하지만, 멤버 수가 많아지면 에러 핸들링과 로딩 상태 관리를 개선하는 것이 좋습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/member/MemberPageContent.tsx` around lines 72 - 81, The
current handlers (onChangeRole, onBan, onRestore) call
changeMemberRole/banMember/restoreMember inside selectedMembers.forEach which
fires many parallel mutations with no loading state or partial-failure handling;
change each handler to run the per-member mutations using Promise.allSettled (or
sequentially with for..of + await if rate-limiting is desired) over
selectedMembers.map(m => changeMemberRole({ clubMemberId: m.clubMemberId,
memberRole: targetRole })) (and similarly for banMember/restoreMember), set and
expose a loading state while requests are in-flight, collect successes and
failures from the results, and surface a summarized UX message (e.g., X
succeeded, Y failed with error details) so partial failures are visible to the
user.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/admin/member/MemberPageContent.tsx`:
- Around line 133-148: The local detailMember is being optimistically updated
before calling banMember/restoreMember, but those mutations may fail and React
Query's rollback doesn't revert this component state; update the component to
either perform the UI update inside the mutation onSuccess handlers or capture
the previousDetail (e.g., prev = detailMember) before optimistic set and revert
it in the mutation onError/onSettled callbacks; specifically modify the calls
around setDetailMember, banMember, and restoreMember so that setDetailMember({
...detailMember, status: 'BANNED' }) / setDetailMember({ ...detailMember,
status: 'ACTIVE' }) is moved into the corresponding mutation onSuccess or add
logic to restore prev via the mutation's onError for both banMember and
restoreMember.

In `@src/components/admin/member/modal/MemberDetailModal.tsx`:
- Around line 106-108: member.generation is parsed with parseInt in
MemberDetailModal which can produce NaN and render "NaN기"; guard against invalid
values by validating the parsed number (e.g., use Number.isFinite or isNaN
checks) and fall back to a safe display (empty string, "-" or "Unknown") when
parseInt(member.generation, 10) yields NaN; update the rendering logic around
parseInt(member.generation, 10) to compute a safeGeneration variable and use
that in the JSX.

---

Duplicate comments:
In `@src/components/admin/member/MemberPageContent.tsx`:
- Around line 149-162: The onChangeRole handler for detailMember incorrectly
maps any non-ADMIN role (including LEAD) to 'ADMIN'; fix by explicitly handling
roles: if detailMember.memberRole is 'ADMIN' set nextRole to 'USER', if it's
'USER' set nextRole to 'ADMIN', and if it's 'LEAD' either return/do nothing or
show/block the change (i.e., do not call setDetailMember or changeMemberRole for
'LEAD'). Update the handler that sets nextRole and uses setDetailMember and
changeMemberRole (referenced symbols: detailMember, onChangeRole,
setDetailMember, changeMemberRole, ROLE_LABEL) so LEAD is preserved or handled
separately.

In `@src/components/admin/member/modal/MemberDetailModal.tsx`:
- Around line 155-168: The mapping over footerActions passes potentially
undefined handler to AlertDialogAction's onClick, causing runtime errors when
handler is absent; update the render to only include actions with a defined
handler (e.g., filter footerActions for entries where handler is a function or
check handler !== undefined) before mapping, or conditionally render
AlertDialog/AlertDialogAction only when handler exists; reference
getFooterActions for where handlers originate and AlertDialogAction/handler in
MemberDetailModal for the change.

---

Nitpick comments:
In `@src/components/admin/member/MemberPageContent.tsx`:
- Around line 72-81: The current handlers (onChangeRole, onBan, onRestore) call
changeMemberRole/banMember/restoreMember inside selectedMembers.forEach which
fires many parallel mutations with no loading state or partial-failure handling;
change each handler to run the per-member mutations using Promise.allSettled (or
sequentially with for..of + await if rate-limiting is desired) over
selectedMembers.map(m => changeMemberRole({ clubMemberId: m.clubMemberId,
memberRole: targetRole })) (and similarly for banMember/restoreMember), set and
expose a loading state while requests are in-flight, collect successes and
failures from the results, and surface a summarized UX message (e.g., X
succeeded, Y failed with error details) so partial failures are visible to the
user.

In `@src/components/mypage/edit/EditProfileContent.tsx`:
- Around line 122-128: EditProfileContent is using hardcoded spacing classes
gap-[35px] and pb-[80px]; replace them with the appropriate design-token utility
classes (e.g., the project's spacing tokens) inside the className on the root
div in EditProfileContent; if suitable tokens don't exist, open a design-system
request to add tokens matching 35px/80px and then update the className to use
those new token classes, avoiding any remaining bracketed hardcoded values.
🪄 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: bc35fffd-3ef0-468e-b848-1db3846bc51a

📥 Commits

Reviewing files that changed from the base of the PR and between 1e52b75 and dbe025d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • src/components/admin/member/MemberPageContent.tsx
  • src/components/admin/member/modal/MemberDetailModal.tsx
  • src/components/mypage/MyPageContent.tsx
  • src/components/mypage/edit/EditProfileContent.tsx
  • src/hooks/mutations/admin/useAdminCardinalMutations.ts
  • src/hooks/mutations/admin/useAdminMemberMutations.ts
  • src/lib/apis/mypage.ts
✅ Files skipped from review due to trivial changes (1)
  • src/lib/apis/mypage.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/hooks/mutations/admin/useAdminCardinalMutations.ts
  • src/components/mypage/MyPageContent.tsx
  • src/hooks/mutations/admin/useAdminMemberMutations.ts

Comment on lines +133 to +148
onBan={
detailMember
? () => {
setDetailMember({ ...detailMember, status: 'BANNED' });
banMember(detailMember.clubMemberId);
}
: undefined
}
onRestore={
detailMember
? () => {
setDetailMember({ ...detailMember, status: 'ACTIVE' });
restoreMember(detailMember.clubMemberId);
}
: undefined
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

뮤테이션 실패 시 로컬 상태 롤백 누락

banMemberrestoreMember 호출 전에 setDetailMember로 낙관적 업데이트를 수행하고 있습니다. 뮤테이션 훅의 onError는 React Query 캐시만 롤백하므로, 실패 시 detailMember 로컬 상태는 잘못된 상태로 남아 모달 UI와 서버 데이터가 불일치하게 됩니다.

뮤테이션의 onError 또는 onSettled 콜백에서 로컬 상태도 동기화하거나, 낙관적 업데이트 대신 onSuccess에서 상태를 업데이트하는 방식을 고려하세요.

🔧 onSuccess 기반 업데이트 예시
         onBan={
           detailMember
             ? () => {
-                setDetailMember({ ...detailMember, status: 'BANNED' });
-                banMember(detailMember.clubMemberId);
+                banMember(detailMember.clubMemberId, {
+                  onSuccess: () => setDetailMember({ ...detailMember, status: 'BANNED' }),
+                });
               }
             : undefined
         }
         onRestore={
           detailMember
             ? () => {
-                setDetailMember({ ...detailMember, status: 'ACTIVE' });
-                restoreMember(detailMember.clubMemberId);
+                restoreMember(detailMember.clubMemberId, {
+                  onSuccess: () => setDetailMember({ ...detailMember, status: 'ACTIVE' }),
+                });
               }
             : undefined
         }
📝 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
onBan={
detailMember
? () => {
setDetailMember({ ...detailMember, status: 'BANNED' });
banMember(detailMember.clubMemberId);
}
: undefined
}
onRestore={
detailMember
? () => {
setDetailMember({ ...detailMember, status: 'ACTIVE' });
restoreMember(detailMember.clubMemberId);
}
: undefined
}
onBan={
detailMember
? () => {
banMember(detailMember.clubMemberId, {
onSuccess: () => setDetailMember({ ...detailMember, status: 'BANNED' }),
});
}
: undefined
}
onRestore={
detailMember
? () => {
restoreMember(detailMember.clubMemberId, {
onSuccess: () => setDetailMember({ ...detailMember, status: 'ACTIVE' }),
});
}
: undefined
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/member/MemberPageContent.tsx` around lines 133 - 148,
The local detailMember is being optimistically updated before calling
banMember/restoreMember, but those mutations may fail and React Query's rollback
doesn't revert this component state; update the component to either perform the
UI update inside the mutation onSuccess handlers or capture the previousDetail
(e.g., prev = detailMember) before optimistic set and revert it in the mutation
onError/onSettled callbacks; specifically modify the calls around
setDetailMember, banMember, and restoreMember so that setDetailMember({
...detailMember, status: 'BANNED' }) / setDetailMember({ ...detailMember,
status: 'ACTIVE' }) is moved into the corresponding mutation onSuccess or add
logic to restore prev via the mutation's onError for both banMember and
restoreMember.

Comment on lines +106 to +108
<span className="typo-h3 text-text-strong">
{parseInt(member.generation, 10)}기
</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

parseInt 결과가 NaN이 될 수 있음

member.generation이 숫자로 변환할 수 없는 문자열인 경우 NaN기로 표시됩니다. 서버 데이터가 항상 유효한 숫자 문자열임이 보장된다면 문제없으나, 방어적으로 처리하는 것이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/admin/member/modal/MemberDetailModal.tsx` around lines 106 -
108, member.generation is parsed with parseInt in MemberDetailModal which can
produce NaN and render "NaN기"; guard against invalid values by validating the
parsed number (e.g., use Number.isFinite or isNaN checks) and fall back to a
safe display (empty string, "-" or "Unknown") when parseInt(member.generation,
10) yields NaN; update the rendering logic around parseInt(member.generation,
10) to compute a safeGeneration variable and use that in the JSX.

useAdminMemberMutations에서 @/lib/apis barrel import 대신
@/lib/apis/adminMember 직접 import로 변경하여
클라이언트 번들에 next/headers가 포함되는 문제 해결

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

PR 테스트 결과

Jest: 통과

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

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

🤖 Claude 테스트 제안

모델: claude-sonnet-4-6 | 토큰: 0 입력 / 0 출력

변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.

src/app/api/proxy/[...path]/route.ts

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberPageContent.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberSearchBar.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberTable.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


src/components/admin/member/MemberTopBar.tsx

오류: Your credit balance is too low to access the Anthropic API. Please go to Plans & Billing to upgrade or purchase credits.


이 코멘트는 Claude API를 통해 자동 생성되었습니다. 반드시 검토 후 사용하세요.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

PR 검증 결과

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

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

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 6, 2026

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

Copy link
Copy Markdown
Member

@woneeeee woneeeee left a comment

Choose a reason for hiding this comment

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

수고하셨습니당!!! 드디어 유저 부분은 끝이 보이네요... 하핫

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 +26 to +30
useEffect(() => {
return () => {
if (previewUrl) URL.revokeObjectURL(previewUrl);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
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.

요 부분 eslint-disable 처리하지말고 previewUrlRef로 해서 useRef로 최신값 추적하게 하는건 어떨까욤?

<p className="typo-body1 text-state-error">내 정보를 불러올 수 없습니다.</p>
</div>
);
}
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.

이 부분이 src/components/mypage/edit/EditProfileContent.tsx 여기에도 있었는데 useMyMemberQuery쿼리에서 적용해둔건 어떨까욤?

@@ -102,9 +35,7 @@ export const SORT_LABEL: Record<SortBy, string> = {
export function sortMembers(members: Member[], sortBy: SortBy): Member[] {
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.

여기sortMembers는 유틸함수인데 상수 파일에 들어와있는 것 같슴니닷!!

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.

엇 어드민 커밋이엇네...

profileImageFile?: File | null;
}

async function uploadProfileImage(file: File) {
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 +13 to +23
interface SchoolsResponse {
code: number;
message: string;
data: School[];
}

interface MajorsResponse {
code: number;
message: string;
data: Major[];
}
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.

요거 공통 ApiResponse 사용해도 좋을 것 같아욤

USER: '사용자',
ADMIN: '관리자',
LEAD: '리더',
};
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.

이거 src/utils/admin/memberMapper.ts에 그대로 똑같이 있어서 그거 사용해줘도 될 것 같아욤

Copy link
Copy Markdown
Collaborator

@nabbang6 nabbang6 left a comment

Choose a reason for hiding this comment

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

고생하셧습니다 !!!!
쿼리랑 뮤테이션 분리해두니까 깔끔하고 좋네용... 저도 앞으로 작업하면서 반영해두겟습니닷 🥕

Comment on lines +44 to +54
await mypageApi.updateUser(user);

const profileImage = profileImageFile
? await uploadProfileImage(profileImageFile)
: undefined;

await mypageApi.updateClubProfile({
bio: clubProfile.bio,
...(profileImage && { profileImage }),
});
},
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가 순차적으로 호출되고 잇는 것 같은데, updateUser, updateClubProfile은 병렬 처리 해주면 어떨까 싶습니다!
profileImage 업로드는 updateClubProfile에 필요하니까 고것만 먼저 처리해줌 좋을 것 같아용

  const [, profileImage] = await Promise.all([
    mypageApi.updateUser(user),
    profileImageFile ? uploadProfileImage(profileImageFile) : undefined,
  ]);
  await mypageApi.updateClubProfile({
    bio: clubProfile.bio,
    ...(profileImage && { profileImage }),
  });

이런 느낌으로,,

Comment on lines +38 to +74
const {
register,
handleSubmit,
setValue,
reset,
control,
formState: { errors },
} = useForm<EditProfileFormData>({
resolver: zodResolver(editProfileSchema),
mode: 'onBlur',
defaultValues: {
name: '',
bio: '',
tel: '',
email: '',
school: '',
department: '',
studentId: '',
},
});

useEffect(() => {
if (me) {
reset(
{
name: me.name,
bio: me.bio ?? '',
tel: me.tel ? formatPhone(me.tel) : '',
email: me.email,
school: me.school,
department: me.department,
studentId: me.studentId,
},
{ keepDirtyValues: true },
);
}
}, [me, reset]);
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.

요기 useEffect로 me 데이터를 reset 해주고 잇는 것 같은데, defaultValues를 비동기로 설정해주면 effect 없이 처리해줄 수 잇을 것 같습니다!

  const { ... } = useForm({
    resolver: zodResolver(editProfileSchema),
    mode: 'onBlur',
    values: me ? {
      name: me.name,
      bio: me.bio ?? '',
      tel: me.tel ? formatPhone(me.tel) : '',
      email: me.email,
      school: me.school,
      department: me.department,
      studentId: me.studentId,
    } : undefined,
  });

React Hook Form values 옵션이 자동으로 폼 동기화를 시켜준다고 하네용

Comment on lines +6 to +20
export function useCardinals() {
const clubId = useClubId();

return useQuery({
queryKey: ['cardinals', clubId],
queryFn: async () => {
const res = await cardinalApi.getCardinals(clubId!);
const data = res.data.data;
return data;
},
enabled: !!clubId,
staleTime: 30 * 60 * 1000,
gcTime: 60 * 60 * 1000,
});
}
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.

별건 아니지만,,, ㅎ__ㅎ useCardinals도 다른 query 훅들처럼 useCardinalsQuery로 통일해줘도 좋을 것 같습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🐞 BugFix Something isn't working ✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants