[Feat] WTH-235: 어드민 네비게이션 UI 변경#44
Conversation
PR 테스트 결과✅ Jest: 통과 🎉 모든 테스트를 통과했습니다! |
🤖 Claude 테스트 제안
변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.
|
PR 검증 결과❌ TypeScript: 실패 |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (5)
✅ Files skipped from review due to trivial changes (3)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthrough관리자 사이드바(LNB)를 컴포지션 기반으로 재구성하고 축소/확장 토글 및 서브컴포넌트들을 추가했습니다. 클럽 조회 API와 훅이 도입되었고 Club 타입·아바타 참조가 Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant LNB as LNB (container)
participant State as React State
participant LNBHeader as LNBHeader
participant LNBClubInfo as LNBClubInfo
participant NavItem as NavItem
User->>LNBHeader: 클릭(토글)
LNBHeader->>State: onToggle()
State->>LNB: collapsed 값 갱신
LNB->>LNBClubInfo: 전달(collapsed)
LNB->>NavItem: 전달(collapsed, isActive)
NavItem->>User: 툴팁 또는 라벨 표시
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested labels
Suggested reviewers
시
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 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: 8
🧹 Nitpick comments (1)
src/hooks/queries/admin/useAdminClubQuery.ts (1)
10-11:clubId!대신 queryFn 내부 가드를 두는 쪽이 더 안전합니다.Line 10의 non-null assertion은 런타임 방어가 약합니다.
enabled와 별개로 queryFn에서 명시적으로 검증해 주세요.Based on learnings: "Applies to src/**/*.{ts,tsx} : Use tanstack query for data fetching and Zustand for client state management"수정 예시
return useQuery({ queryKey: ['admin', 'club', clubId], - queryFn: () => adminClubApi.getDetail(clubId!).then((res) => res.data.data), + queryFn: async () => { + if (!clubId) throw new Error('clubId is required'); + const res = await adminClubApi.getDetail(clubId); + return res.data.data; + }, enabled: !!clubId, staleTime: 30 * 60 * 1000, gcTime: 60 * 60 * 1000, });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/hooks/queries/admin/useAdminClubQuery.ts` around lines 10 - 11, The queryFn currently uses a non-null assertion (clubId!) which is unsafe; modify the queryFn in useAdminClubQuery to explicitly check clubId at runtime and either throw an error or return a rejected Promise when clubId is missing, instead of relying solely on enabled, and remove the non-null assertion. Locate the query configuration that calls adminClubApi.getDetail(clubId!) and replace it with a guarded implementation that verifies clubId (the clubId variable and the queryFn calling adminClubApi.getDetail) before invoking the API.
🤖 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/layout/LNB.tsx`:
- Around line 90-104: The ThemeModeSelector currently renders directly under the
moveNavItems inside NavSection so it flows with content instead of sticking to
the sidebar bottom; extract ThemeModeSelector out of the NavSection and render
it after the NavSection in its own wrapper that applies a bottom-sticky layout
(e.g., a wrapper with class "mt-auto" or equivalent CSS), and ensure the sidebar
root container (the parent around NavSection and the new wrapper) is a
full-height flex column (flex flex-col h-full) so the mt-auto spacer pushes
ThemeModeSelector to the bottom; keep CollapsedDivider/NavItem rendering
unchanged and place CollapsedDivider before the new wrapper so the divider
remains above the bottom selector.
In `@src/components/admin/layout/LNBHeader.tsx`:
- Around line 19-24: The icon-only toggle button in LNBHeader.tsx lacks
accessible name/state and an explicit button type; update the <button> that
renders the Icon (using NavToggleIcon and onToggle) to include type="button", an
aria-label (e.g., "Toggle navigation"), and aria-expanded bound to the
component's open state (for example aria-expanded={isOpen} or
aria-expanded={navOpen} depending on the prop/state used by LNBHeader); if the
component currently doesn't expose a boolean open prop, add or derive one (e.g.,
isOpen/navOpen) so aria-expanded reflects the current collapsed/expanded state.
In `@src/components/admin/layout/NavItem.tsx`:
- Around line 49-69: The icon-only nav items lack accessible labels; update
NavItem.tsx to add aria-label={label} to the interactive elements (the button
created in the window.open branch and both Link elements in the external and
internal branches) so assistive tech can read the hidden text—use the existing
variables (label, collapsed, cls, path, external, iconEl, el) and conditionally
or unconditionally set aria-label={label} on those elements when the visible
text is hidden.
- Around line 7-13: The import for PeopleIcon should be a regular value import,
not a type-only import; replace "import type { PeopleIcon }" with a normal
import so the value exported from "@/assets/icons" is available at runtime,
keeping the NavItemProps interface and its "icon: typeof PeopleIcon" type query
unchanged; update the import statement that provides PeopleIcon to a standard
import to ensure the SVG component value is imported while leaving the rest of
NavItemProps intact.
In `@src/components/admin/layout/ThemeModeSelector.tsx`:
- Around line 39-55: The component currently only stores isDark in useThemeStore
so selecting "auto" only applies once; modify the theme store (useThemeStore) to
also persist a ThemeMode (e.g., mode) and update ThemeModeSelector to read/write
that mode instead of only local state (replace the local useState<ThemeMode>
initialization with the store's mode and setMode). In handleSelect, save the
selected mode to the store (not just call setDark) and when mode === 'auto'
attach a window.matchMedia('(prefers-color-scheme: dark)') change listener that
calls setDark(matcher.matches) to keep the UI in sync; remove that listener when
mode changes away from 'auto' and ensure the store restores mode on reload so
"auto" remains active across refreshes.
In `@src/lib/apis/adminClub.ts`:
- Around line 6-7: The getDetail API call embeds clubId raw into the path which
can break for special characters; update the getDetail implementation (the
apiClient.get call in adminClub's getDetail) to URL-encode clubId with
encodeURIComponent before constructing the `/admin/clubs/${...}` path so the
request target is always a valid URL.
In `@src/proxy.ts`:
- Line 7: The hardcoded PRE_LAUNCH constant should be replaced with an
environment-driven flag: read an env var (e.g., process.env.PRE_LAUNCH or
process.env.PRE_LAUNCH_ENABLED) and parse it into a boolean with a safe default
(false) so different deployments can toggle the gate; update the declaration of
PRE_LAUNCH in src/proxy.ts (replace the const PRE_LAUNCH = false) to compute its
value from the env var and accept common truthy values like "true" or "1".
In `@src/types/club.ts`:
- Around line 4-11: The shared Club type has removed logoUrl but consumers still
reference club.logoUrl (notably in ClubSearchDropdown.tsx reading club.logoUrl),
causing type errors; either restore a backward-compatible optional logoUrl field
on the Club interface or update all consumers in this PR to use profileImageUrl
instead—specifically modify the Club type (add logoUrl?: string) or replace
occurrences of club.logoUrl in components like ClubSearchDropdown.tsx to read
club.profileImageUrl and adjust any UI/prop names to match the new field.
---
Nitpick comments:
In `@src/hooks/queries/admin/useAdminClubQuery.ts`:
- Around line 10-11: The queryFn currently uses a non-null assertion (clubId!)
which is unsafe; modify the queryFn in useAdminClubQuery to explicitly check
clubId at runtime and either throw an error or return a rejected Promise when
clubId is missing, instead of relying solely on enabled, and remove the non-null
assertion. Locate the query configuration that calls
adminClubApi.getDetail(clubId!) and replace it with a guarded implementation
that verifies clubId (the clubId variable and the queryFn calling
adminClubApi.getDetail) before invoking the API.
🪄 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: abc14900-d89b-4abe-ad51-c3105f9564d4
⛔ Files ignored due to path filters (12)
src/assets/icons/admin/ic_admin_attendance.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_calendar.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_due.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_fileout.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_forum.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_light.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_manual.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_penalty.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_service_transfer.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_setting.svgis excluded by!**/*.svgsrc/assets/icons/admin/ic_admin_user.svgis excluded by!**/*.svgsrc/assets/icons/nav_toggle.svgis excluded by!**/*.svg
📒 Files selected for processing (15)
src/assets/icons/admin/index.tssrc/assets/icons/index.tssrc/components/admin/layout/CollapsedDivider.tsxsrc/components/admin/layout/LNB.tsxsrc/components/admin/layout/LNBClubInfo.tsxsrc/components/admin/layout/LNBHeader.tsxsrc/components/admin/layout/NavItem.tsxsrc/components/admin/layout/NavSection.tsxsrc/components/admin/layout/ThemeModeSelector.tsxsrc/components/ui/avatar.tsxsrc/hooks/queries/admin/useAdminClubQuery.tssrc/lib/apis/adminClub.tssrc/lib/apis/index.tssrc/proxy.tssrc/types/club.ts
| <NavSection label="이동" collapsed={collapsed}> | ||
| {moveNavItems.map(({ id, icon, label, path, external, openInWindow }) => ( | ||
| <NavItem | ||
| key={id} | ||
| icon={icon} | ||
| label={label} | ||
| path={path} | ||
| collapsed={collapsed} | ||
| external={external} | ||
| openInWindow={openInWindow} | ||
| /> | ||
| ))} | ||
| <CollapsedDivider collapsed={collapsed} /> | ||
| <ThemeModeSelector collapsed={collapsed} /> | ||
| </NavSection> |
There was a problem hiding this comment.
테마 셀렉터가 아직 "하단 고정"되지 않습니다.
지금 구조에서는 이동 메뉴 바로 아래에 이어서 렌더링될 뿐이라, 남는 높이가 있어도 sidebar bottom에 붙지 않습니다. ThemeModeSelector는 mt-auto가 걸린 별도 wrapper로 빼서 항상 하단에 고정하는 편이 요구사항과 맞습니다.
가능한 정리 예시
<NavSection label="이동" collapsed={collapsed}>
{moveNavItems.map(({ id, icon, label, path, external, openInWindow }) => (
<NavItem
key={id}
icon={icon}
label={label}
path={path}
collapsed={collapsed}
external={external}
openInWindow={openInWindow}
/>
))}
- <CollapsedDivider collapsed={collapsed} />
- <ThemeModeSelector collapsed={collapsed} />
</NavSection>
+ <div className="mt-auto">
+ <CollapsedDivider collapsed={collapsed} />
+ <ThemeModeSelector collapsed={collapsed} />
+ </div>📝 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.
| <NavSection label="이동" collapsed={collapsed}> | |
| {moveNavItems.map(({ id, icon, label, path, external, openInWindow }) => ( | |
| <NavItem | |
| key={id} | |
| icon={icon} | |
| label={label} | |
| path={path} | |
| collapsed={collapsed} | |
| external={external} | |
| openInWindow={openInWindow} | |
| /> | |
| ))} | |
| <CollapsedDivider collapsed={collapsed} /> | |
| <ThemeModeSelector collapsed={collapsed} /> | |
| </NavSection> | |
| <NavSection label="이동" collapsed={collapsed}> | |
| {moveNavItems.map(({ id, icon, label, path, external, openInWindow }) => ( | |
| <NavItem | |
| key={id} | |
| icon={icon} | |
| label={label} | |
| path={path} | |
| collapsed={collapsed} | |
| external={external} | |
| openInWindow={openInWindow} | |
| /> | |
| ))} | |
| </NavSection> | |
| <div className="mt-auto"> | |
| <CollapsedDivider collapsed={collapsed} /> | |
| <ThemeModeSelector collapsed={collapsed} /> | |
| </div> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/admin/layout/LNB.tsx` around lines 90 - 104, The
ThemeModeSelector currently renders directly under the moveNavItems inside
NavSection so it flows with content instead of sticking to the sidebar bottom;
extract ThemeModeSelector out of the NavSection and render it after the
NavSection in its own wrapper that applies a bottom-sticky layout (e.g., a
wrapper with class "mt-auto" or equivalent CSS), and ensure the sidebar root
container (the parent around NavSection and the new wrapper) is a full-height
flex column (flex flex-col h-full) so the mt-auto spacer pushes
ThemeModeSelector to the bottom; keep CollapsedDivider/NavItem rendering
unchanged and place CollapsedDivider before the new wrapper so the divider
remains above the bottom selector.
| const [mode, setMode] = useState<ThemeMode>(() => { | ||
| if (typeof window === 'undefined') return 'light'; | ||
| return useThemeStore.getState().isDark ? 'dark' : 'light'; | ||
| }); | ||
|
|
||
| const handleSelect = (value: ThemeMode) => { | ||
| setMode(value); | ||
|
|
||
| if (value === 'light') { | ||
| setDark(false); | ||
| } else if (value === 'dark') { | ||
| setDark(true); | ||
| } else { | ||
| const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; | ||
| setDark(prefersDark); | ||
| } | ||
| }; |
There was a problem hiding this comment.
auto 모드가 실제로는 자동 추적되지 않습니다.
지금 구현은 store에 isDark만 저장하고, auto를 선택해도 현재 OS 테마 값을 한 번 반영하는 데서 끝납니다. 그래서 새로고침하면 선택 상태가 auto로 복원되지 않고, 이후 시스템 테마가 바뀌어도 UI가 따라가지 않습니다. mode 자체를 Zustand에 저장하고, auto일 때만 matchMedia의 change 이벤트로 setDark를 동기화해야 합니다.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/admin/layout/ThemeModeSelector.tsx` around lines 39 - 55, The
component currently only stores isDark in useThemeStore so selecting "auto" only
applies once; modify the theme store (useThemeStore) to also persist a ThemeMode
(e.g., mode) and update ThemeModeSelector to read/write that mode instead of
only local state (replace the local useState<ThemeMode> initialization with the
store's mode and setMode). In handleSelect, save the selected mode to the store
(not just call setDark) and when mode === 'auto' attach a
window.matchMedia('(prefers-color-scheme: dark)') change listener that calls
setDark(matcher.matches) to keep the UI in sync; remove that listener when mode
changes away from 'auto' and ensure the store restores mode on reload so "auto"
remains active across refreshes.
src/lib/apis/adminClub.ts
Outdated
| getDetail: (clubId: string) => | ||
| apiClient.get<ApiResponse<Club>>(`/admin/clubs/${clubId}`), |
There was a problem hiding this comment.
clubId는 URL 인코딩 후 경로에 넣어 주세요.
Line 7처럼 raw 문자열을 경로에 넣으면 특수문자 포함 시 잘못된 endpoint로 요청될 수 있습니다.
수정 예시
export const adminClubApi = {
getDetail: (clubId: string) =>
- apiClient.get<ApiResponse<Club>>(`/admin/clubs/${clubId}`),
+ apiClient.get<ApiResponse<Club>>(`/admin/clubs/${encodeURIComponent(clubId)}`),
};📝 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.
| getDetail: (clubId: string) => | |
| apiClient.get<ApiResponse<Club>>(`/admin/clubs/${clubId}`), | |
| getDetail: (clubId: string) => | |
| apiClient.get<ApiResponse<Club>>(`/admin/clubs/${encodeURIComponent(clubId)}`), |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/apis/adminClub.ts` around lines 6 - 7, The getDetail API call embeds
clubId raw into the path which can break for special characters; update the
getDetail implementation (the apiClient.get call in adminClub's getDetail) to
URL-encode clubId with encodeURIComponent before constructing the
`/admin/clubs/${...}` path so the request target is always a valid URL.
|
|
||
| // TODO: 런칭 후 PRE_LAUNCH 플래그 및 관련 분기 제거 | ||
| const PRE_LAUNCH = true; | ||
| const PRE_LAUNCH = false; |
There was a problem hiding this comment.
런치 게이트를 하드코딩 값이 아닌 환경변수로 전환해 주세요.
Line 7에서 PRE_LAUNCH = false 고정은 모든 배포 환경에서 즉시 게이트를 해제합니다. 운영 안정성을 위해 환경별로 제어 가능해야 합니다.
제안 변경안
-const PRE_LAUNCH = false;
+const PRE_LAUNCH = process.env.PRE_LAUNCH === 'true';📝 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.
| const PRE_LAUNCH = false; | |
| const PRE_LAUNCH = process.env.PRE_LAUNCH === 'true'; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/proxy.ts` at line 7, The hardcoded PRE_LAUNCH constant should be replaced
with an environment-driven flag: read an env var (e.g., process.env.PRE_LAUNCH
or process.env.PRE_LAUNCH_ENABLED) and parse it into a boolean with a safe
default (false) so different deployments can toggle the gate; update the
declaration of PRE_LAUNCH in src/proxy.ts (replace the const PRE_LAUNCH = false)
to compute its value from the env var and accept common truthy values like
"true" or "1".
…충돌 해결 - logoUrl을 profileImageUrl로 일괄 변경 - Radix AvatarProps의 color와 cva color variant 충돌 → colorScheme으로 rename Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
🤖 Claude 테스트 제안
변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.
|
PR 테스트 결과✅ Jest: 통과 🎉 모든 테스트를 통과했습니다! |
PR 검증 결과✅ TypeScript: 통과 🎉 모든 검증을 통과했습니다! |
|
구현한 기능 Preview: https://weeth-2lrt5mphk-weethsite-4975s-projects.vercel.app |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
src/components/admin/layout/LNB.tsx (1)
96-110:⚠️ Potential issue | 🟡 Minor테마 셀렉터를
NavSection밖으로 빼서 하단에 고정해주세요.지금 구조에서는
ThemeModeSelector가 이동 메뉴의 마지막 아이템처럼 흐르기 때문에, 사이드바 높이가 남아도 하단에 붙지 않습니다.nav는 이미flex h-full flex-col이므로, 별도 wrapper에mt-auto를 주는 방식으로 분리하는 편이 맞습니다.가능한 수정 예시
<NavSection label="이동" collapsed={collapsed}> {moveNavItems.map(({ id, icon, label, path, external, openInWindow }) => ( <NavItem key={id} icon={icon} label={label} path={path} collapsed={collapsed} external={external} openInWindow={openInWindow} /> ))} - <CollapsedDivider collapsed={collapsed} /> - <ThemeModeSelector collapsed={collapsed} /> </NavSection> + <div className="mt-auto"> + <CollapsedDivider collapsed={collapsed} /> + <ThemeModeSelector collapsed={collapsed} /> + </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/admin/layout/LNB.tsx` around lines 96 - 110, Move ThemeModeSelector out of the NavSection so it’s fixed to the bottom: remove ThemeModeSelector from inside the NavSection block (the map over moveNavItems and CollapsedDivider) and instead render it after the closing </NavSection> wrapped in a container with mt-auto (e.g., <div className="mt-auto"><ThemeModeSelector collapsed={collapsed} /></div>). Keep NavSection (and CollapsedDivider) as-is; rely on the parent nav being flex h-full flex-col so the new mt-auto wrapper will push ThemeModeSelector to the bottom.
🧹 Nitpick comments (1)
src/components/ui/avatar.tsx (1)
15-19:size=40추가가 하위 컴포넌트 사이즈 매핑까지는 아직 안 맞춰졌습니다.
AvatarFallback은 40 사이즈를 지원하지만AvatarBadge,AvatarGroupCount는 24/64/128만 정의되어 있어size={40}조합에서 비주얼 불일치가 생길 수 있습니다.변경 예시
function AvatarBadge({ className, ...props }: React.ComponentProps<'span'>) { return ( <span @@ className={cn( 'bg-primary text-primary-foreground ring-background absolute right-0 bottom-0 z-10 inline-flex items-center justify-center rounded-full ring-2 select-none', 'group-data-[size="24"]/avatar:size-2 group-data-[size="24"]/avatar:[&>svg]:hidden', + 'group-data-[size="40"]/avatar:size-2.5 group-data-[size="40"]/avatar:[&>svg]:size-2', 'group-data-[size="64"]/avatar:size-2.5 group-data-[size="64"]/avatar:[&>svg]:size-2', 'group-data-[size="128"]/avatar:size-3 group-data-[size="128"]/avatar:[&>svg]:size-2', className, )} @@ function AvatarGroupCount({ className, ...props }: React.ComponentProps<'div'>) { @@ className={cn( 'ring-background bg-container-neutral text-text-alternative relative flex shrink-0 items-center justify-center rounded-full ring-2', 'group-has-data-[size="24"]/avatar-group:size-6 group-has-data-[size="24"]/avatar-group:text-xs', + 'group-has-data-[size="40"]/avatar-group:size-10 group-has-data-[size="40"]/avatar-group:text-sm', 'group-has-data-[size="64"]/avatar-group:size-16 group-has-data-[size="64"]/avatar-group:text-sm', 'group-has-data-[size="128"]/avatar-group:size-32 group-has-data-[size="128"]/avatar-group:text-base', className, )}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/ui/avatar.tsx` around lines 15 - 19, Avatar components have inconsistent size mappings: AvatarFallback supports size=40 but AvatarBadge and AvatarGroupCount only map 24/64/128, causing visual mismatch for size={40}. Update the size-to-class mappings used by AvatarBadge and AvatarGroupCount to include the 40 key (matching the class used by AvatarFallback), and ensure any centralized sizeMap/sizeClass utility (or constants in AvatarBadge, AvatarGroupCount) includes 40 so all avatar subcomponents render consistent classes for size={40}.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/components/admin/layout/LNB.tsx`:
- Around line 96-110: Move ThemeModeSelector out of the NavSection so it’s fixed
to the bottom: remove ThemeModeSelector from inside the NavSection block (the
map over moveNavItems and CollapsedDivider) and instead render it after the
closing </NavSection> wrapped in a container with mt-auto (e.g., <div
className="mt-auto"><ThemeModeSelector collapsed={collapsed} /></div>). Keep
NavSection (and CollapsedDivider) as-is; rely on the parent nav being flex
h-full flex-col so the new mt-auto wrapper will push ThemeModeSelector to the
bottom.
---
Nitpick comments:
In `@src/components/ui/avatar.tsx`:
- Around line 15-19: Avatar components have inconsistent size mappings:
AvatarFallback supports size=40 but AvatarBadge and AvatarGroupCount only map
24/64/128, causing visual mismatch for size={40}. Update the size-to-class
mappings used by AvatarBadge and AvatarGroupCount to include the 40 key
(matching the class used by AvatarFallback), and ensure any centralized
sizeMap/sizeClass utility (or constants in AvatarBadge, AvatarGroupCount)
includes 40 so all avatar subcomponents render consistent classes for size={40}.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dbde6570-c45a-459d-8e32-bcf149da73eb
📒 Files selected for processing (10)
.claude/settings.local.jsonsrc/components/admin/layout/LNB.tsxsrc/components/admin/layout/LNBClubInfo.tsxsrc/components/admin/layout/LNBHeader.tsxsrc/components/auth/hub/ClubSearchDropdown.tsxsrc/components/auth/hub/ClubSelectedCard.tsxsrc/components/auth/invite/ClubAccessPage.tsxsrc/components/auth/invite/ClubConfirmCard.tsxsrc/components/ui/avatar.tsxsrc/lib/apis/adminClub.ts
✅ Files skipped from review due to trivial changes (6)
- src/components/auth/hub/ClubSelectedCard.tsx
- src/components/auth/invite/ClubConfirmCard.tsx
- src/lib/apis/adminClub.ts
- src/components/auth/hub/ClubSearchDropdown.tsx
- .claude/settings.local.json
- src/components/auth/invite/ClubAccessPage.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
- src/components/admin/layout/LNBHeader.tsx
- src/components/admin/layout/LNBClubInfo.tsx
| code: string; | ||
| schoolName: string; | ||
| description: string; | ||
| logoUrl?: string; |
There was a problem hiding this comment.
이거 동아리 정보 api dto 보니까 profileImageUrl 이길래.. 이걸로 설정햇습니다,,,
woneeeee
left a comment
There was a problem hiding this comment.
수고하셨습니당!! 현재 Tooltip 저는 괜찮은 것 같은데 나중에 예진님한테도 한 번 여쭤보면 좋을 것 같아욤
There was a problem hiding this comment.
피그마에서 사용되고 있는 width랑 현재 구현되어있는 width가 다른 것 같습니닷 ㅜㅜ 확인 부탁드려욤
dalzzy
left a comment
There was a problem hiding this comment.
수고하셨습니당!! 피그마랑 타이포,아이콘 다른 부분만 수정해주시면 좋을 것 같습니다!!
nabbang6
left a comment
There was a problem hiding this comment.
수고하셨습니다~~!! ㅠ.ㅠ 리뷰가 늦어 죄송합니다,,,
다른 분들이 말씀해주신 것처럼 피그마랑 상이한 부분만 수정해주심 좋을 것 같슴니당!! 👍 최고최고,,
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
src/components/admin/layout/ThemeModeSelector.tsx (1)
39-55:⚠️ Potential issue | 🟠 Major
auto모드가 실제 모드로 저장되지 않습니다.지금 구현은
auto를 고르면 현재 시스템 테마로setDark()만 한 번 호출하고, 선택한 모드 자체는 로컬 state에만 남깁니다. 그래서 새로고침 후에는 persisted 된isDark값만 보고dark/light로 복원되고, 이후 OS 테마 변경도 따라가지 않습니다.src/stores/theme-store.ts:1-19에mode를 persisted 상태로 올리고,src/providers/theme-provider.tsx:1-14같은 루트 effect에서auto일 때만matchMedia변경을 구독하는 구조로 바꾸는 편이 안전합니다.#!/bin/bash # 기대 결과: # 1) src/stores/theme-store.ts 에 mode / setMode 가 없으면 auto 선택이 복원되지 않습니다. # 2) src/providers/theme-provider.tsx 또는 동등한 루트 effect에 matchMedia change 구독이 없으면 OS 테마 변경을 추적하지 못합니다. rg -n -C2 '\b(isDark|setDark|mode|setMode|matchMedia)\b' \ src/stores/theme-store.ts \ src/providers/theme-provider.tsx \ src/components/admin/layout/ThemeModeSelector.tsx🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/admin/layout/ThemeModeSelector.tsx` around lines 39 - 55, The selector only persists isDark, so choosing "auto" is lost on refresh and OS theme changes aren't tracked; update the theme store (e.g., add persisted mode and setMode in useThemeStore) so the selected ThemeMode (light|dark|auto) is saved, modify ThemeModeSelector (mode state and handleSelect) to call setMode(value) on selection instead of only setDark, and add a root effect in the theme provider (e.g., in theme-provider.tsx) that, when mode === 'auto', subscribes to window.matchMedia('(prefers-color-scheme: dark)') changes to call setDark(true/false) so OS changes are followed.src/components/admin/layout/LNB.tsx (1)
96-112:⚠️ Potential issue | 🟡 Minor테마 셀렉터가 아직 사이드바 하단에 고정되지 않습니다.
nav가flex-col h-full인 건 맞지만, 이 블록 자체에는mt-auto가 없어 화면 높이가 남는 경우 "이동" 섹션 바로 아래에서 멈춥니다. 요구사항대로 항상 하단에 붙이려면ThemeModeSelector를 별도 wrapper로 빼서 밀어주는 편이 안전합니다.정리 예시
- <CollapsedDivider collapsed={collapsed} /> - <NavSection label="모드" collapsed={collapsed}> - <ThemeModeSelector collapsed={collapsed} /> - </NavSection> + <div className="mt-auto"> + <CollapsedDivider collapsed={collapsed} /> + <NavSection label="모드" collapsed={collapsed}> + <ThemeModeSelector collapsed={collapsed} /> + </NavSection> + </div>🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/components/admin/layout/LNB.tsx` around lines 96 - 112, The ThemeModeSelector is not pinned to the bottom because the block lacks a flex spacer; wrap ThemeModeSelector (currently inside the NavSection with label "모드") in a dedicated wrapper that applies a bottom-pushing style (e.g., className "mt-auto" or a flex spacer) so it always sits at the bottom of the flex-col container; update the JSX around NavSection/CollapsedDivider/ThemeModeSelector (referencing NavSection, CollapsedDivider, ThemeModeSelector and the collapsed prop) to move ThemeModeSelector into that wrapper so it is pushed to the bottom when the sidebar has extra height.
🤖 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/layout/ThemeModeSelector.tsx`:
- Around line 60-77: The trigger button becomes icon-only when collapsed and
lacks an accessible name; update the DropdownMenuTrigger's button in
ThemeModeSelector (the trigger constant) to include an aria-label (or
aria-labelledby) using the current label text from TRIGGER_LABELS[mode] whenever
collapsed is true (or simply always) so assistive tech can announce the control;
ensure the same fix is applied to the similar button instance used in the menu
(the code around the other DropdownMenuTrigger / button usage referenced in the
comment).
---
Duplicate comments:
In `@src/components/admin/layout/LNB.tsx`:
- Around line 96-112: The ThemeModeSelector is not pinned to the bottom because
the block lacks a flex spacer; wrap ThemeModeSelector (currently inside the
NavSection with label "모드") in a dedicated wrapper that applies a bottom-pushing
style (e.g., className "mt-auto" or a flex spacer) so it always sits at the
bottom of the flex-col container; update the JSX around
NavSection/CollapsedDivider/ThemeModeSelector (referencing NavSection,
CollapsedDivider, ThemeModeSelector and the collapsed prop) to move
ThemeModeSelector into that wrapper so it is pushed to the bottom when the
sidebar has extra height.
In `@src/components/admin/layout/ThemeModeSelector.tsx`:
- Around line 39-55: The selector only persists isDark, so choosing "auto" is
lost on refresh and OS theme changes aren't tracked; update the theme store
(e.g., add persisted mode and setMode in useThemeStore) so the selected
ThemeMode (light|dark|auto) is saved, modify ThemeModeSelector (mode state and
handleSelect) to call setMode(value) on selection instead of only setDark, and
add a root effect in the theme provider (e.g., in theme-provider.tsx) that, when
mode === 'auto', subscribes to window.matchMedia('(prefers-color-scheme: dark)')
changes to call setDark(true/false) so OS changes are followed.
🪄 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: 2bc7850c-08cc-4efe-b552-9e0a615a574d
📒 Files selected for processing (8)
src/app/(private)/admin/board/page.tsxsrc/app/(private)/admin/club-info/page.tsxsrc/app/(private)/admin/schedule/page.tsxsrc/app/globals.csssrc/components/admin/layout/LNB.tsxsrc/components/admin/layout/LNBHeader.tsxsrc/components/admin/layout/NavItem.tsxsrc/components/admin/layout/ThemeModeSelector.tsx
✅ Files skipped from review due to trivial changes (1)
- src/components/admin/layout/LNBHeader.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/admin/layout/NavItem.tsx
| const trigger = ( | ||
| <DropdownMenuTrigger asChild> | ||
| <button | ||
| className={cn( | ||
| 'text-text-normal flex h-12 w-full cursor-pointer items-center border-none transition-colors', | ||
| 'hover:bg-container-neutral-interaction', | ||
| collapsed ? 'justify-center px-200' : 'gap-300 px-300', | ||
| )} | ||
| > | ||
| <TriggerIcon className="h-6 w-6 shrink-0" /> | ||
| {!collapsed && ( | ||
| <> | ||
| <span className="typo-button2 flex-1 text-left">{TRIGGER_LABELS[mode]}</span> | ||
| <ChevronDown className="h-5 w-5 shrink-0" /> | ||
| </> | ||
| )} | ||
| </button> | ||
| </DropdownMenuTrigger> |
There was a problem hiding this comment.
접힌 상태의 트리거 버튼에 접근 가능한 이름이 없습니다.
collapsed일 때는 텍스트가 빠져서 사실상 아이콘-only 버튼이 되는데, 현재 버튼에 aria-label이 없어 보조기기에서는 용도를 알기 어렵습니다. 툴팁은 대체 이름이 아니니 현재 모드 기반 label을 버튼에 직접 넣어 주세요.
간단한 보완 예시
<DropdownMenuTrigger asChild>
<button
+ type="button"
+ aria-label={TRIGGER_LABELS[mode]}
className={cn(
'text-text-normal flex h-12 w-full cursor-pointer items-center border-none transition-colors',
'hover:bg-container-neutral-interaction',Also applies to: 82-85
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/components/admin/layout/ThemeModeSelector.tsx` around lines 60 - 77, The
trigger button becomes icon-only when collapsed and lacks an accessible name;
update the DropdownMenuTrigger's button in ThemeModeSelector (the trigger
constant) to include an aria-label (or aria-labelledby) using the current label
text from TRIGGER_LABELS[mode] whenever collapsed is true (or simply always) so
assistive tech can announce the control; ensure the same fix is applied to the
similar button instance used in the menu (the code around the other
DropdownMenuTrigger / button usage referenced in the comment).
🤖 Claude 테스트 제안
변경된 컴포넌트에 대해 Claude가 생성한 테스트 코드입니다. 검토 후 적합한 부분만 사용하세요.
|
PR 테스트 결과✅ Jest: 통과 🎉 모든 테스트를 통과했습니다! |
PR 검증 결과✅ TypeScript: 통과 🎉 모든 검증을 통과했습니다! |
|
구현한 기능 Preview: https://weeth-7rhgnll14-weethsite-4975s-projects.vercel.app |

✅ PR 유형
어떤 변경 사항이 있었나요?
📌 관련 이슈번호
✅ Key Changes
w-56↔w-14), 접힌 상태에서 아이콘만 표시 + 툴팁LNBHeader,LNBClubInfo,NavItem,NavSection,CollapsedDivider,ThemeModeSelector6개 컴포넌트로 분리useAdminClubQuery+adminClubApi추가, LNBClubInfo에서 동아리 정보 표시size={40}variant,colorvariant (default/primary/secondary) 추가currentColor로 통일📸 스크린샷 or 실행영상
🎸 기타 사항 or 추가 코멘트
clubId는 로그인 시 스토어에 세팅되는 구조이므로, 로그인을 타지 않은 개발 환경에서는 localStorage에 수동 세팅 필요:localStorage.setItem('clubId', JSON.stringify({ state: { clubId: "ID값" }, version: 0 }))-> 요게 이전 pr인 [Feat] WTH-249 : 출석 api 연결 #43 에서 클럽 아이디 이슈 해결 되면.. 없애도 되는 이슈입니다...
툴팁 같은 경우는 디자인이랑 조금 다른데 네비 토글 툴팁을 디자인이랑 맞추면 다른 아이콘 툴팁이랑 조금 달라져서.. 그냥 임의로 맞췃습니다.. 너무 구리면 알려주세요옹
Summary by CodeRabbit
새로운 기능
버그 수정 / 변경
스타일