[feat] 동아리 등록 요청 플로우 구현#331
Hidden character warning
Conversation
Walkthrough동아리 등록 및 정보 수정 요청 기능의 완전한 구현입니다. 먼저 API 스키마에서 Possibly related PRs
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
apps/web/src/apis/clubInformationUpdate/mutations.ts (1)
6-10: ⚡ Quick winReact Query 키는 팩토리 패턴으로 분리해주세요.
mutationKey를 인라인 배열로 두면 키 재사용/일관성 관리가 어려워집니다. 키 팩토리로 분리해submit에서 참조하도록 맞추는 게 좋습니다.예시 diff
+const clubInformationUpdateMutationKeys = { + all: ['clubInformationUpdate'] as const, + submit: () => [...clubInformationUpdateMutationKeys.all, 'submit'] as const, +}; + export const clubInformationUpdateMutations = { submit: () => mutationOptions({ - mutationKey: ['clubInformationUpdate', 'submit'], + mutationKey: clubInformationUpdateMutationKeys.submit(), mutationFn: ({ clubId, body }: SubmitClubInformationUpdateRequestParams) => submitClubInformationUpdateRequest(clubId, body), }), };As per coding guidelines, "Use query key factory pattern for React Query key organization".
🤖 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 `@apps/web/src/apis/clubInformationUpdate/mutations.ts` around lines 6 - 10, Currently the React Query key is inlined in clubInformationUpdateMutations.submit (mutationKey: ['clubInformationUpdate','submit']), which makes reuse and consistency hard; refactor by creating a key factory (e.g., clubInformationUpdateKeys or getClubInformationUpdateKey) that exposes a submit() method/field returning the array, then replace the inline mutationKey in submit within clubInformationUpdateMutations to call that factory (use the new key factory wherever this key is needed) while keeping mutationOptions and mutationFn unchanged.apps/web/src/apis/clubRegistration/mutations.ts (1)
6-10: ⚡ Quick win여기도 mutationKey를 키 팩토리로 통일하는 게 좋습니다.
현재 인라인 키 배열이라 확장 시 중복/불일치 위험이 있습니다. 키 팩토리로 맞춰주세요.
As per coding guidelines, "Use query key factory pattern for React Query key organization".
🤖 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 `@apps/web/src/apis/clubRegistration/mutations.ts` around lines 6 - 10, The mutation currently uses an inline array for mutationKey in clubRegistrationMutations.submit inside the mutationOptions call; replace the inline ['clubRegistration','submit'] with your query key factory call (e.g., clubRegistrationKeys.submit() or clubRegistrationKeyFactory.submit()) to follow the query key factory pattern and avoid duplication, leaving the rest of the mutationFn (SubmitClubRegistrationRequestParams -> submitClubRegistrationRequest) unchanged.apps/web/src/App.tsx (1)
6-8: ⚡ Quick win내부 페이지 import는
@/*별칭을 사용하는 쪽으로 맞춰주세요.Line 6-8은 현재 상대경로인데, 이 PR에서 추가된 import는
@/pages/...로 통일하면 규칙 일관성이 좋아집니다.As per coding guidelines, "Use path alias
@/*for internal app imports (preferred over relative paths)".🤖 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 `@apps/web/src/App.tsx` around lines 6 - 8, Update the three internal imports in App.tsx to use the project path alias rather than relative paths: replace the current relative imports of ClubInformationUpdate, ClubRegistration, and ClubRequestComplete with alias imports using "`@/pages/`..." so they align with the codebase convention and keep all internal page imports consistent.apps/web/src/pages/RegisterClub/index.tsx (2)
37-37: ⚡ Quick win텍스트 크기 유틸을 시맨틱 타이포그래피로 통일해주세요.
Line 37, 41, 50-52의
text-[24px],text-[20px],text-[14px]는text-h*/text-body*계열로 맞추는 편이 좋습니다.As per coding guidelines, "Prioritize semantic typography utilities (e.g.,
text-h1,text-body1) over generic utilities".Also applies to: 41-41, 50-52
🤖 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 `@apps/web/src/pages/RegisterClub/index.tsx` at line 37, The components in RegisterClub with utility classes text-[24px], text-[20px], and text-[14px] should be replaced with the semantic typography utilities; update the div using text-[24px] to text-h3 (or your project's matching heading size), replace text-[20px] occurrences with text-body1, and replace text-[14px] occurrences with text-body2 (or the equivalent semantic tokens your design system uses) so all typography uses semantic classes; locate these in the RegisterClub component (the outer rounded div and the elements around lines showing text-[20px] and text-[14px]) and swap the generic pixel utilities for the corresponding text-h*/text-body* classes.
76-76: ⚡ Quick win하드코딩 색상 대신 디자인 토큰 기반 클래스를 사용해주세요.
Line 76의
bg-[#ffffff]는 토큰 기반 스타일(bg-white또는 토큰 매핑 클래스)로 바꾸는 게 규칙에 맞습니다.As per coding guidelines, "Use CSS export from
@konect/design-tokensfor design tokens, prioritized over inline values".🤖 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 `@apps/web/src/pages/RegisterClub/index.tsx` at line 76, The class string containing the hardcoded color `bg-[`#ffffff`]` should be replaced with the design-token based class (e.g. `bg-white` or the specific token class exported from `@konect/design-tokens`) wherever that class string is used in the RegisterClub component; import the token CSS or class mapping from `@konect/design-tokens` and swap `bg-[`#ffffff`]` for the corresponding token class in the same JSX/variable (the long class string that includes 'border-text-100 ... bg-[`#ffffff`] ...'); ensure you remove the hex token and use the token name so the component conforms to design-token rules.apps/web/src/pages/ClubRequestComplete/index.tsx (1)
17-18: ⚡ Quick win시맨틱 타이포그래피 유틸로 교체해주세요.
Line 17-18의
text-[28px],sm:text-[36px],sm:text-[20px]는 디자인 시스템 일관성을 깨기 쉬워서text-h*,text-body*계열로 맞추는 게 좋습니다.As per coding guidelines, "Prioritize semantic typography utilities (e.g.,
text-h1,text-body1) over generic utilities".🤖 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 `@apps/web/src/pages/ClubRequestComplete/index.tsx` around lines 17 - 18, Replace the raw pixel utilities on the heading and paragraph in ClubRequestComplete/index.tsx with the design-system semantic typography classes: update the <h1> (currently using text-[28px] and sm:text-[36px]) to the appropriate heading token (e.g., text-h* per your design system) and update the <p> (currently using sm:text-[20px]) to the appropriate body token (e.g., text-body*); locate the className on the <h1> and <p> elements and swap the generic size utilities for the corresponding semantic utilities (text-hX for the heading and text-bodyY for the paragraph) so they follow the typography scale.apps/web/src/pages/ClubInformationUpdate/index.tsx (1)
55-58: ⚡ Quick win
clubQuery를 디바운스해 키 입력마다 발생하는 요청을 줄이세요.
onChange에서 매 키 입력마다setClubQuery(value.trim())을 호출하고 해당 쿼리가enabled상태라, 동아리명을 한 글자씩 칠 때마다 목록 조회 요청이 발생합니다.@konect/utils/use-debounced-callback로clubQuery갱신을 디바운스하는 것을 권장합니다.♻️ 제안
+ const updateClubQuery = useDebouncedCallback((value: string) => { + setClubQuery(value.trim()); + }); + ... onChange={(event) => { const value = event.target.value; setClubName(value); - setClubQuery(value.trim()); + updateClubQuery(value); setFormMessage(''); }}Also applies to: 202-207
🤖 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 `@apps/web/src/pages/ClubInformationUpdate/index.tsx` around lines 55 - 58, clubQuery를 매타이핑마다 setClubQuery로 즉시 업데이트해 useQuery가 잦은 요청을 보냅니다; import된 useDebouncedCallback(`@konect/utils/use-debounced-callback`)를 사용해 setClubQuery 호출을 디바운스하고, useQuery의 enabled와 쿼리 인자로는 디바운스된 값(예: debouncedClubQuery)을 사용하도록 변경하세요; 구체적으로 onChange 핸들러에서 setClubQuery 대신 debounced setter(예: debouncedSetClubQuery)를 호출하고, universityClubQueries.list 호출 및 useQuery(enabled)에서 clubQuery 대신 debouncedClubQuery를 참조하도록 수정하며 같은 패턴을 다른 clubQuery 설정 위치에도 적용하세요.
🤖 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 `@apps/web/src/pages/ClubRequest/components.tsx`:
- Around line 180-188: The component uses useSuspenseQuery for the autocomplete
which causes fallback flicker when the debounced query (state managed by
query/updateQuery) changes; replace useSuspenseQuery(homeQueries.detail(query ?
{ query } : {})) with react-query's useQuery so you can pass placeholderData (or
keep previousData) to avoid UI fallback—call useQuery with the same query key
(homeQueries.detail(query ? { query } : {})) and set placeholderData: previous
result (e.g., from a ref or react-query's keepPreviousData option) or use
keepPreviousData: true; alternatively, if you prefer minimal change, wrap the
state update that causes the query key change (updateQuery or the setQuery call)
in startTransition to smooth UI updates—adjust code around updateQuery, query,
useSuspenseQuery -> useQuery, and homeQueries.detail accordingly.
---
Nitpick comments:
In `@apps/web/src/apis/clubInformationUpdate/mutations.ts`:
- Around line 6-10: Currently the React Query key is inlined in
clubInformationUpdateMutations.submit (mutationKey:
['clubInformationUpdate','submit']), which makes reuse and consistency hard;
refactor by creating a key factory (e.g., clubInformationUpdateKeys or
getClubInformationUpdateKey) that exposes a submit() method/field returning the
array, then replace the inline mutationKey in submit within
clubInformationUpdateMutations to call that factory (use the new key factory
wherever this key is needed) while keeping mutationOptions and mutationFn
unchanged.
In `@apps/web/src/apis/clubRegistration/mutations.ts`:
- Around line 6-10: The mutation currently uses an inline array for mutationKey
in clubRegistrationMutations.submit inside the mutationOptions call; replace the
inline ['clubRegistration','submit'] with your query key factory call (e.g.,
clubRegistrationKeys.submit() or clubRegistrationKeyFactory.submit()) to follow
the query key factory pattern and avoid duplication, leaving the rest of the
mutationFn (SubmitClubRegistrationRequestParams ->
submitClubRegistrationRequest) unchanged.
In `@apps/web/src/App.tsx`:
- Around line 6-8: Update the three internal imports in App.tsx to use the
project path alias rather than relative paths: replace the current relative
imports of ClubInformationUpdate, ClubRegistration, and ClubRequestComplete with
alias imports using "`@/pages/`..." so they align with the codebase convention and
keep all internal page imports consistent.
In `@apps/web/src/pages/ClubInformationUpdate/index.tsx`:
- Around line 55-58: clubQuery를 매타이핑마다 setClubQuery로 즉시 업데이트해 useQuery가 잦은 요청을
보냅니다; import된 useDebouncedCallback(`@konect/utils/use-debounced-callback`)를 사용해
setClubQuery 호출을 디바운스하고, useQuery의 enabled와 쿼리 인자로는 디바운스된 값(예:
debouncedClubQuery)을 사용하도록 변경하세요; 구체적으로 onChange 핸들러에서 setClubQuery 대신 debounced
setter(예: debouncedSetClubQuery)를 호출하고, universityClubQueries.list 호출 및
useQuery(enabled)에서 clubQuery 대신 debouncedClubQuery를 참조하도록 수정하며 같은 패턴을 다른
clubQuery 설정 위치에도 적용하세요.
In `@apps/web/src/pages/ClubRequestComplete/index.tsx`:
- Around line 17-18: Replace the raw pixel utilities on the heading and
paragraph in ClubRequestComplete/index.tsx with the design-system semantic
typography classes: update the <h1> (currently using text-[28px] and
sm:text-[36px]) to the appropriate heading token (e.g., text-h* per your design
system) and update the <p> (currently using sm:text-[20px]) to the appropriate
body token (e.g., text-body*); locate the className on the <h1> and <p> elements
and swap the generic size utilities for the corresponding semantic utilities
(text-hX for the heading and text-bodyY for the paragraph) so they follow the
typography scale.
In `@apps/web/src/pages/RegisterClub/index.tsx`:
- Line 37: The components in RegisterClub with utility classes text-[24px],
text-[20px], and text-[14px] should be replaced with the semantic typography
utilities; update the div using text-[24px] to text-h3 (or your project's
matching heading size), replace text-[20px] occurrences with text-body1, and
replace text-[14px] occurrences with text-body2 (or the equivalent semantic
tokens your design system uses) so all typography uses semantic classes; locate
these in the RegisterClub component (the outer rounded div and the elements
around lines showing text-[20px] and text-[14px]) and swap the generic pixel
utilities for the corresponding text-h*/text-body* classes.
- Line 76: The class string containing the hardcoded color `bg-[`#ffffff`]` should
be replaced with the design-token based class (e.g. `bg-white` or the specific
token class exported from `@konect/design-tokens`) wherever that class string is
used in the RegisterClub component; import the token CSS or class mapping from
`@konect/design-tokens` and swap `bg-[`#ffffff`]` for the corresponding token
class in the same JSX/variable (the long class string that includes
'border-text-100 ... bg-[`#ffffff`] ...'); ensure you remove the hex token and use
the token name so the component conforms to design-token rules.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9728cf2a-452a-4d95-b415-0bca6e5ab081
⛔ Files ignored due to path filters (2)
apps/web/src/assets/image/complete-cat.jpgis excluded by!**/*.jpg,!apps/*/src/assets/**and included by**apps/web/src/assets/svg/arrow_drop_down-icon.svgis excluded by!**/*.svg,!apps/*/src/assets/**and included by**
📒 Files selected for processing (21)
apps/web/src/App.tsxapps/web/src/apis/clubDetail/entity.tsapps/web/src/apis/clubInformationUpdate/entity.tsapps/web/src/apis/clubInformationUpdate/index.tsapps/web/src/apis/clubInformationUpdate/mutations.tsapps/web/src/apis/clubRegistration/entity.tsapps/web/src/apis/clubRegistration/index.tsapps/web/src/apis/clubRegistration/mutations.tsapps/web/src/apis/recentClub/entity.tsapps/web/src/apis/universityClub/entity.tsapps/web/src/apis/upload/entity.tsapps/web/src/apis/upload/index.tsapps/web/src/components/RecentClubCard/index.tsxapps/web/src/pages/ClubDetail/index.tsxapps/web/src/pages/ClubInformationUpdate/index.tsxapps/web/src/pages/ClubRegistration/index.tsxapps/web/src/pages/ClubRequest/components.tsxapps/web/src/pages/ClubRequest/utils.tsapps/web/src/pages/ClubRequestComplete/index.tsxapps/web/src/pages/RegisterClub/index.tsxapps/web/src/pages/UniversityClubList/index.tsx
| const [query, setQuery] = useState(''); | ||
| const [isOpen, setIsOpen] = useState(false); | ||
| const listboxId = useId(); | ||
| const updateQuery = useDebouncedCallback((value: string) => { | ||
| setQuery(value.trim()); | ||
| }); | ||
|
|
||
| const { data: homeData } = useSuspenseQuery(homeQueries.detail(query ? { query } : {})); | ||
| const universityOptions = homeData.universities ?? []; |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
React Query v5 useSuspenseQuery suspend on query key change input focus loss placeholderData
💡 Result:
In TanStack Query v5, the useSuspenseQuery hook is designed specifically for React Suspense integration [1][2]. Because it manages loading and error states via React's native Suspense and Error Boundary mechanisms, it intentionally deviates from the behavior of the standard useQuery hook in several ways relevant to your query [1][3][2]: 1. placeholderData Is Not Supported: The placeholderData option is not available in useSuspenseQuery [3][4]. When the query key changes, useSuspenseQuery will trigger the nearest Suspense fallback instead of keeping the previous data on screen [1][5][6]. 2. Handling Transitions to Avoid UI Flickering: To prevent the UI from being replaced by a Suspense fallback when the query key changes (which can cause input focus loss or jarring UI transitions), you should wrap the update to the query key in React's startTransition [1][5][6]. Using startTransition tells React to treat the state update as a non-urgent transition, allowing the previous UI to remain visible while the new data fetches in the background [6]. 3. Input Focus Loss: Input focus is often lost when a component suspends because the component unmounts or re-renders in a way that destroys the DOM node holding focus [1][6]. By using startTransition, you keep the current component mounted with its existing data while the new data is fetched, which helps maintain the input's focus and prevents the UI from resetting [1][6]. In summary, because useSuspenseQuery does not support placeholderData, you must rely on React's transition APIs (startTransition) to manage the UI state during query key changes [1][5]. If you require the specific behavior provided by placeholderData (like keeping previous data visible without triggering a suspense fallback), consider using the standard useQuery hook instead, as it provides finer control over these states [6]. Top Results: [1][5][6]
Citations:
- 1: https://tanstack.com/query/v5/docs/framework/react/guides/suspense
- 2: https://beyondthecode.medium.com/declarative-data-fetching-using-usesuspensequery-in-react-query-6a8d3a277de9
- 3: https://tanstack-query.mintlify.app/api/react/use-suspense-query
- 4: https://tanstack.com/query/v5/docs/framework/react/reference/useSuspenseQueries
- 5: https://tanstack.com/query/latest/docs/framework/react/guides/suspense
- 6: I found useSuspenseQuery placeholderData have been removed in v5.24.2,how to use it without breaking the UI? TanStack/query#7013
검색 자동완성 콤보박스에 useSuspenseQuery 사용은 부적합합니다.
useSuspenseQuery는 placeholderData를 지원하지 않아 디바운스된 query로 쿼리 키가 바뀔 때 Suspense fallback이 뜨면서 입력 포커스/드롭다운이 깜빡일 수 있습니다. 검색형 입력에는 useQuery + placeholderData(이전 결과 유지)로 처리하거나, 최소한 쿼리 키 변경을 startTransition으로 감싸 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 `@apps/web/src/pages/ClubRequest/components.tsx` around lines 180 - 188, The
component uses useSuspenseQuery for the autocomplete which causes fallback
flicker when the debounced query (state managed by query/updateQuery) changes;
replace useSuspenseQuery(homeQueries.detail(query ? { query } : {})) with
react-query's useQuery so you can pass placeholderData (or keep previousData) to
avoid UI fallback—call useQuery with the same query key
(homeQueries.detail(query ? { query } : {})) and set placeholderData: previous
result (e.g., from a ref or react-query's keepPreviousData option) or use
keepPreviousData: true; alternatively, if you prefer minimal change, wrap the
state update that causes the query key change (updateQuery or the setQuery call)
in startTransition to smooth UI updates—adjust code around updateQuery, query,
useSuspenseQuery -> useQuery, and homeQueries.detail accordingly.
✨ 요약
😎 해결한 이슈
Summary by CodeRabbit
릴리스 노트
New Features
Style