Skip to content

[fix] 모바일 기본정보 수정 리뷰 반영 - #1796

Merged
seongwon030 merged 4 commits into
develop-fefrom
fix/admin-mobile-edit-review
Jul 1, 2026
Merged

[fix] 모바일 기본정보 수정 리뷰 반영#1796
seongwon030 merged 4 commits into
develop-fefrom
fix/admin-mobile-edit-review

Conversation

@seongwon030

@seongwon030 seongwon030 commented Jul 1, 2026

Copy link
Copy Markdown
Member

개요

PR #1794 리뷰(수동 + gemini-code-assist) 중 유효한 지적을 반영한 후속 수정입니다. 관리자 기본 정보 수정 모바일 뷰 대상.

변경 사항

  • fix(admin): 모바일 기본정보 입력에 20자 제한 추가
    • 데스크톱 InputField는 동아리명·한줄소개에 maxLength=20이 걸려 있으나 모바일 TextField엔 제한이 없어 20자 초과 입력·저장이 가능했습니다(데이터 정합성 리스크).
    • TextFieldmaxLength prop 추가 후 모바일 두 필드에 20 연결.
  • fix(admin): 링크 필드 지우기 후 입력 포커스 유지
    • 형제 컴포넌트 TextField와 동일하게 clear 후 입력 포커스 유지(gemini 리뷰 반영).
  • fix(admin): 링크·자유태그 저장 상태 동기화를 onSuccess로 이동
    • 요청 전 동기적으로 initialValues를 갱신하던 것을 onSuccess 콜백으로 이동. 저장 실패 시 변경분이 저장된 것으로 오인돼 버튼이 비활성화되던 문제 방어.

반영하지 않은 리뷰

  • gemini의 훅 내부 queryClient.invalidateQueries 추가 제안 → useUpdateClubDetail이 이미 queryKeys.club.detail(id)를 무효화하고 outlet context가 해당 쿼리로 backing되어 중복이며, ['clubDetail', id] 하드코딩은 쿼리키 중앙관리 컨벤션 위반이라 제외.

확인

  • CI typecheck / lint
  • 모바일 기본정보 수정 화면 동작 확인

Summary by CodeRabbit

  • 새 기능

    • 입력 필드에 글자 수 제한을 설정할 수 있게 되었습니다.
    • 모바일 클럽 정보 편집에서 동아리명과 동아리소개 입력이 20자로 제한됩니다.
  • 버그 수정

    • 입력 내용을 지운 뒤에도 커서가 다시 입력칸에 유지되도록 개선했습니다.
    • 편집 중인 내용이 서버 갱신으로 덮어써지지 않도록 동기화 동작을 개선했습니다.

데스크톱 InputField는 동아리명·한줄소개에 maxLength 20이 적용돼 있으나
모바일 TextField에는 제한이 없어 20자 초과 입력·저장이 가능했다.
TextField에 maxLength prop을 추가하고 모바일 두 필드에 20을 연결한다.
형제 컴포넌트 TextField와 동일하게, 지우기 버튼 클릭 후에도
입력 필드 포커스를 유지해 바로 이어서 입력할 수 있도록 한다.
기존에는 요청 전에 initialValues를 동기적으로 갱신해, 저장이 실패해도
변경분이 저장된 것으로 간주돼 메인 저장 버튼이 비활성화되는 문제가 있었다.
setInitialValues를 updateClub onSuccess 콜백으로 옮겨 성공 시에만 반영한다.
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
moadong Ready Ready Preview, Comment Jul 1, 2026 3:20pm

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

TextField 컴포넌트에 maxLength 프롭을 추가하고 동아리명/동아리소개 필드에 20자 제한을 적용했습니다. LinkField는 클리어 후 input에 다시 포커스하도록 변경되었고, useClubInfoEdit 훅은 isDirty 기반으로 폼 상태 동기화 로직과 업데이트 성공 시 initialValues 갱신 흐름을 수정했습니다.

Changes

폼 입력 제한 및 상태 동기화 개선

Layer / File(s) Summary
TextField maxLength 프롭 추가 및 적용
frontend/src/pages/AdminPage/components/editFields/TextField/TextField.tsx, frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTabMobile.tsx
TextFieldProps에 maxLength를 추가하고 Styled.Input에 전달하며, 동아리명/동아리소개 필드에 각각 20자 제한을 적용했다.
LinkField 클리어 후 포커스 유지
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/mobile/LinkEditPage/LinkField.tsx
useRef로 input 참조를 만들고, handleClear에서 onClear 호출 후 해당 input에 다시 포커스를 준다.
useClubInfoEdit 상태 동기화 로직 변경
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/hooks/useClubInfoEdit.ts
isDirty를 계산해 편집 중일 때 서버 값이 폼 상태를 덮어쓰지 않도록 하고, 클럽/소셜링크/자유태그 업데이트 성공 시 onSuccess에서 initialValues를 갱신하도록 로직을 이동했다.

Estimated code review effort: 2 (Simple) | ~12 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant useClubInfoEdit
  participant API
  User->>useClubInfoEdit: 정보/링크/태그 업데이트 요청
  useClubInfoEdit->>API: mutation 호출
  API-->>useClubInfoEdit: onSuccess 응답
  useClubInfoEdit->>useClubInfoEdit: initialValues 갱신 (isDirty 아닐 때만 서버값 반영)
Loading

Possibly related PRs

Suggested labels: 🛠Fix

Suggested reviewers: lepitaaar

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 모바일 기본정보 수정 화면의 리뷰 반영과 관련된 변경사항을 잘 요약한 제목입니다.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/admin-mobile-edit-review

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 added the 💻 FE Frontend label Jul 1, 2026
@reviewer-harry

reviewer-harry Bot commented Jul 1, 2026

Copy link
Copy Markdown

Claude finished @seongwon030's task in 1m 10s —— View job


harry 리뷰 결과 🐹

  • .github/harry-review-rules.md 기준 로드
  • PR 전체 diff 분석 (origin/develop-fe...HEAD)
  • 변경 파일 4개 리뷰

리뷰 기준 대비 위반 사항 없이 깔끔한 후속 수정이에요. 👍

좋았던 점

  • TextField에 optional maxLength prop을 추가한 방식이 backward-compatible하고, 데스크톱 InputField(20자)와 정합성을 맞춰 준 점이 좋아요. 공통 컴포넌트 재사용 원칙도 잘 지켰어요.
  • LinkField의 clear 후 포커스 유지가 형제 컴포넌트 TextFieldhandleClear 패턴과 동일해서 일관성이 좋아요.
  • setInitialValues를 요청 전 동기 호출에서 onSuccess로 옮긴 건 저장 실패 시 dirty 상태가 깨지던 문제를 정확히 방어하는 좋은 수정이에요. 수동 메모이제이션·불필요한 useEffect 추가도 없고 변경 범위가 surgical해요.

가벼운 제안 (선택)

  • useClubInfoEdithandleUpdateClubWithLinks / handleUpdateClubWithTags는 "성공 시에만 initialValues 갱신"이라는 회귀에 민감한 로직이라, 여유가 되면 성공/실패 각 케이스에 대한 테스트를 한 번 걸어두면 안심이 될 것 같아요. 이번 PR에서 꼭 반영할 필요는 없어요.

반영하지 않은 gemini 리뷰(invalidateQueries 중복 · 쿼리키 하드코딩 회피)에 대한 판단도 타당해 보여요. 통과예요! 🎉

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

🎨 UI 변경사항을 확인해주세요

변경된 스토리를 Chromatic에서 확인해주세요.

구분 링크
🔍 변경사항 리뷰 https://www.chromatic.com/build?appId=67904e61c16daa99a63b44a7&number=682
📖 Storybook https://67904e61c16daa99a63b44a7-qnbbaoppts.chromatic.com/

13개 스토리 변경 · 전체 74개 스토리 · 29개 컴포넌트

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces maximum length constraints on club name and introduction fields, improves the UX of the link field by focusing it upon clearing, and refactors useClubInfoEdit to update initial values only after successful API calls. Feedback was provided regarding a potential bug in useClubInfoEdit.ts where unsaved input data could be lost when clubDetail is refetched after partial updates; a fix was suggested to only synchronize state if there are no unsaved changes.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@suhyun113 suhyun113 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.

testField 최대 길이 제한 놓친 부분인데, 빠르게 수정해주셔서 감사합니다!

배너/로고 업로드 뮤테이션이 clubDetail 쿼리를 무효화하면 refetch가 일어나
useEffect가 무조건 서버 값으로 로컬 상태를 덮어써, 수정 중이던 동아리명·소개가
유실되던 문제가 있었다.

- useEffect에 isDirty 가드를 추가해 편집 중에는 서버 값으로 덮어쓰지 않는다.
- 가드로 인해 refetch 기반 리셋이 막히므로, handleUpdateClub 성공 시
  initialValues를 현재 값으로 동기화해 저장 버튼이 정상 비활성화되게 한다.
- deps는 clubDetail만 유지(isDirty 제외)해 재동기화 루프를 방지한다.
@reviewer-harry

reviewer-harry Bot commented Jul 1, 2026

Copy link
Copy Markdown

Claude finished @seongwon030's task in 1m 21s —— View job


harry 리뷰 결과 🐹 (synchronize)

  • .github/harry-review-rules.md 기준 로드
  • 직전 리뷰 이후 새 커밋 diff 분석 (c77d1e90...22284bd0)
  • 변경 파일 1개 리뷰 (useClubInfoEdit.ts)

이번 추가 커밋(편집 중 clubDetail refetch 시 미저장 입력 유실 방지)은 gemini가 지적한 유실 버그를 정확히 방어한 좋은 수정이에요. 👍

좋았던 점

  • useEffect!isDirty 가드를 걸어, 배너/로고 업로드 등으로 clubDetail이 refetch돼도 편집 중이면 서버 값으로 덮어쓰지 않도록 한 접근이 적절해요. 새 useEffect를 추가한 게 아니라 기존 sync 이펙트에 조건만 얹은 surgical한 변경이라 룰에도 부합해요.
  • isDirtyuseEffect deps에서 제외한 판단과 그 이유(재동기화 루프 방지)를 주석 + eslint-disable로 명시해 둔 점이 좋아요. isDirty가 매 렌더 새로 계산되고 이펙트 콜백도 매 렌더 재생성되므로, refetch 시점의 최신 isDirty를 보게 돼 stale closure 문제도 없어요.
  • handleUpdateClub 성공 시 initialValues를 현재 입력값으로 동기화해 준 덕분에, 저장 성공 → invalidate → refetch 흐름에서도 isDirtyfalse로 떨어져 서버 값과 일관되게 재동기화돼요. 형제 핸들러(WithLinks/WithTags)의 onSuccess 동기화 패턴과도 결이 맞아요.

참고 (이번 PR에서 조치 불필요)

  • isDirty 비교에는 clubPresidentName/telephoneNumber가 빠져 있어, 이 두 필드만 편집한 상태에서 refetch가 나면 여전히 서버 값으로 덮일 여지가 있어요. 다만 이건 이번 커밋에서 새로 생긴 게 아니라 기존 isDirty 정의의 범위 문제라, 이번 PR 스코프 밖으로 봐요. 나중에 편집폼 전체를 dirty 추적 대상으로 넓힐 때 함께 보면 좋을 것 같아요.
  • 직전 리뷰에서 언급한 성공/실패 케이스 테스트는 이번에도 선택 사항이에요.

룰 위반 없이 깔끔해요. 통과예요! 🎉

@seongwon030
seongwon030 merged commit 9e4762a into develop-fe Jul 1, 2026
8 of 9 checks passed
@seongwon030
seongwon030 deleted the fix/admin-mobile-edit-review branch July 1, 2026 15:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/hooks/useClubInfoEdit.ts (1)

188-207: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

링크/태그 저장 성공 시 initialValues 부분 동기화로 인한 dirty 상태 불일치

handleUpdateClubWithLinkshandleUpdateClubWithTagsupdateClub 호출 시 clubName, selectedDivision, selectedCategory, introduction 등 폼의 모든 현재 값을 페이로드에 실어 서버에 저장합니다(189-201, 219-230). 하지만 onSuccess에서는 socialLinks(203-207) 또는 clubTags(232-236)만 initialValues에 반영하고 나머지 필드는 갱신하지 않습니다.

이로 인해 사용자가 다른 필드(예: 동아리명)를 수정한 뒤 저장하지 않고 링크/태그 페이지에서 저장하면, 서버에는 해당 필드까지 이미 반영되었음에도 initialValues가 갱신되지 않아 isDirty가 계속 true로 남습니다. ClubInfoEditTabMobile.tsx의 저장 버튼은 disabled={!isDirty}로 제어되므로(Context snippet 2), 이미 저장된 상태인데도 저장 버튼이 계속 활성 상태로 남는 등 상태 불일치가 발생합니다.

handleUpdateClubonSuccess(163-174)처럼 전체 필드를 동기화하도록 수정하는 것을 권장합니다.

🔧 제안 수정
       {
-        onSuccess: () => {
-          setInitialValues((prev) =>
-            prev ? { ...prev, socialLinks: mergedLinks } : null,
-          );
-        },
+        onSuccess: () => {
+          setInitialValues({
+            clubName,
+            introduction,
+            selectedDivision,
+            selectedCategory,
+            clubTags,
+            socialLinks: mergedLinks,
+          });
+        },
         onError: (error) => {
           alert(`링크 저장에 실패했습니다: ${error.message}`);
         },
       },
       {
-        onSuccess: () => {
-          setInitialValues((prev) =>
-            prev ? { ...prev, clubTags: newTags } : null,
-          );
-        },
+        onSuccess: () => {
+          setInitialValues({
+            clubName,
+            introduction,
+            selectedDivision,
+            selectedCategory,
+            clubTags: newTags,
+            socialLinks,
+          });
+        },
         onError: (error) => {
           alert(`자유태그 저장에 실패했습니다: ${error.message}`);
         },
       },

Also applies to: 218-236

🤖 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 `@frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/hooks/useClubInfoEdit.ts`
around lines 188 - 207, `handleUpdateClubWithLinks` and
`handleUpdateClubWithTags` only sync part of `initialValues` after a successful
`updateClub`, which leaves `isDirty` out of sync even though the server already
saved the current form state. Update the `onSuccess` logic in `useClubInfoEdit`
to refresh `initialValues` the same way `handleUpdateClub` does, including all
fields that are sent in the payload such as `clubName`, `selectedCategory`,
`selectedDivision`, `clubTags`, `introduction`, `clubPresidentName`,
`telephoneNumber`, `socialLinks`, and `description`, so the dirty state is
recalculated from the saved values.
🧹 Nitpick comments (1)
frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTabMobile.tsx (1)

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

20자 제한 안내 표시 고려.

자유태그 필드는 라벨에 (5자이내) 힌트가 있지만, 동아리명/동아리소개 필드는 maxLength={20}이 적용되었음에도 사용자에게 글자수 제한이나 남은 글자 수를 안내하는 UI가 없습니다. 사용자가 입력이 막히는 이유를 알기 어려울 수 있습니다.

🤖 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 `@frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTabMobile.tsx`
around lines 108 - 130, The ClubInfoEditTabMobile text inputs for club name and
introduction enforce maxLength={20} but do not surface that limit to users,
unlike the free-tag field. Update the TextField usage in ClubInfoEditTabMobile
so the 20-character constraint is visible in the UI, either by adding a clear
label hint such as “(20자이내)” or by showing remaining/used characters for
clubName and introduction. Keep the behavior tied to the existing TextField
components and their maxLength/onChange handlers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/hooks/useClubInfoEdit.ts`:
- Around line 188-207: `handleUpdateClubWithLinks` and
`handleUpdateClubWithTags` only sync part of `initialValues` after a successful
`updateClub`, which leaves `isDirty` out of sync even though the server already
saved the current form state. Update the `onSuccess` logic in `useClubInfoEdit`
to refresh `initialValues` the same way `handleUpdateClub` does, including all
fields that are sent in the payload such as `clubName`, `selectedCategory`,
`selectedDivision`, `clubTags`, `introduction`, `clubPresidentName`,
`telephoneNumber`, `socialLinks`, and `description`, so the dirty state is
recalculated from the saved values.

---

Nitpick comments:
In `@frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTabMobile.tsx`:
- Around line 108-130: The ClubInfoEditTabMobile text inputs for club name and
introduction enforce maxLength={20} but do not surface that limit to users,
unlike the free-tag field. Update the TextField usage in ClubInfoEditTabMobile
so the 20-character constraint is visible in the UI, either by adding a clear
label hint such as “(20자이내)” or by showing remaining/used characters for
clubName and introduction. Keep the behavior tied to the existing TextField
components and their maxLength/onChange handlers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6587757d-984c-418b-a945-f230e2850025

📥 Commits

Reviewing files that changed from the base of the PR and between 9ac0b5e and 22284bd.

📒 Files selected for processing (4)
  • frontend/src/pages/AdminPage/components/editFields/TextField/TextField.tsx
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/ClubInfoEditTabMobile.tsx
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/components/mobile/LinkEditPage/LinkField.tsx
  • frontend/src/pages/AdminPage/tabs/ClubInfoEditTab/hooks/useClubInfoEdit.ts

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

Labels

💻 FE Frontend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants