Skip to content

[Feat] 그룹 리스트 컴포넌트 UI 구현#138

Merged
jeongyou merged 21 commits into
mainfrom
feature/groups-list-#137
May 19, 2026
Merged

[Feat] 그룹 리스트 컴포넌트 UI 구현#138
jeongyou merged 21 commits into
mainfrom
feature/groups-list-#137

Conversation

@jeongyou
Copy link
Copy Markdown
Collaborator

@jeongyou jeongyou commented May 15, 2026

What is this PR? 🔍

그룹 회고 페이지의 그룹 리스트 컴포넌트를 구현.

Changes 📝

  • GroupList 컴포넌트 구현 (가로 스크롤, 선택 상태 오버레이)
  • GroupTab 컴포넌트 구현 (친구/캐릭터 탭 전환)
  • 그룹 조회 API 쿼리 훅 추가 (useGroupListQuery)
  • Storybook 배포 자동화를 위한 Chromatic CI 워크플로우 추가

스토리북이 배포되어 있으면 UI 리뷰가 편할 것 같아서 Chromatic으로 배포했습니다.
PR을 열면 GitHub Actions가 자동으로 Storybook을 빌드해 Chromatic에 배포합니다.
변경된 UI는 PR 하단 Checks 탭의 Chromatic 항목에서 확인할 수 있습니다.
image

main에 머지되면 아래 링크에도 자동으로 반영됩니다.
🔗 배포된 Storybook: https://6a0747043a4480833f4697f4-nyrdwztrox.chromatic.com

추가 참고사항

app/groups/page.tsx는 컴포넌트 통합 확인용 임시 파일입니다.
나중에 페이지 작업 시 새로 작성하면 되니 무시하셔도 됩니다.

그룹 선택할 떄 화살표 같이 움직이는 부분은 페이지 전체 합칠 때 추가해야 할 것 같습니다.
그래서 일단은 아이템 수가 많아지면 스크롤바가 노출되는 방식으로 구현했습니다.

Chromatic에 팀으로 초대해드려야 해서 말씀해주시면 링크드릴게요!

Related Issues 🔗

Screenshot 📷

PR Checks 탭의 Chromatic 링크에서 배포된 Storybook 확인 가능


Check List

  • 테스트가 전부 통과되었나요?
  • 모든 commit이 push 되었나요?
  • merge할 branch를 확인했나요?
  • Assignee를 지정했나요?
  • Label을 지정했나요?
  • 저장소에 공개되면 안 되는 파일이 커밋되진 않았나요?

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a groups management page with tabbed switching and a selectable group list.
    • Added a compact tab component and group list UI with visual selection states.
  • Style

    • Improved horizontal scrollbar styling for a cleaner, transparent track.
  • Documentation

    • Updated contributor guidance and routes in the project documentation.
  • Chores

    • Added Chromatic publishing workflow and a script to run it.

Review Change Stack

@jeongyou jeongyou requested a review from baegyeong May 15, 2026 17:04
@jeongyou jeongyou self-assigned this May 15, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

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

Project Deployment Actions Updated (UTC)
timo Ready Ready Preview, Comment May 19, 2026 8:53am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 15, 2026

📝 Walkthrough

Walkthrough

Adds a Groups management feature: GroupTab and GroupList UI, a Zod-validated React Query hook for groups, Storybook stories, global scrollbar styling, a Chromatic npm script and devDependency, a Chromatic GitHub Actions workflow, and CLAUDE.md updates for pnpm and the /groups route.

Changes

Groups Management UI

Layer / File(s) Summary
Tab Component
src/components/features/groups/GroupTab/GroupTab.tsx
TabType union and GroupTabProps interface define the tab contract; TABS constant provides Korean labels; GroupTab renders buttons for each tab with active/inactive styling via cn.
List Component
src/components/features/groups/GroupList/GroupList.tsx
GroupItem and GroupListProps types shape the data contract; GroupList renders a horizontally-scrollable row of group cards using next/image with fallback images and conditional highlighting based on selectedId.
Query Infrastructure
src/components/features/groups/constants/queryKey.ts, src/components/features/groups/constants/url.ts, src/components/features/groups/queries/useGroupListQuery.ts
groupKeys object provides React Query key factories, GROUP_ENDPOINT constant defines the /groups API path, and useSuspenseGroupListQuery hook fetches and Zod-validates groups list using the established keys and endpoint.
Groups Page
app/groups/page.tsx
Client component manages activeTab and selectedId state, filters mock friend/character group datasets, and wires GroupTab and GroupList together with state handlers for tab switching and group selection.
Component Stories
src/components/features/groups/GroupTab/GroupTab.stories.tsx, src/components/features/groups/GroupList/GroupList.stories.tsx
Storybook metadata and Default stories document both components with interactive state management and sample datasets for visual testing and developer reference.
Supporting Infrastructure
src/styles/globals.css, package.json, .github/workflows/chromatic.yml, CLAUDE.md
Horizontal scrollbar styling added via .scrollbar-x-transparent-track class; package.json adds a chromatic script and chromatic devDependency; new GitHub Actions workflow runs Chromatic on main and pull requests; CLAUDE.md updated with pnpm command guidance and /groups route documentation.

Sequence Diagram

sequenceDiagram
  participant GroupsPage
  participant GroupTab
  participant GroupList
  participant useSuspenseGroupListQuery
  participant API

  GroupsPage->>useSuspenseGroupListQuery: mount / request group list (groupKeys.list)
  useSuspenseGroupListQuery->>API: GET /groups (validated by Zod)
  API-->>useSuspenseGroupListQuery: groups array
  useSuspenseGroupListQuery->>GroupsPage: return cached/validated data

  GroupsPage->>GroupTab: pass activeTab, onTabChange
  GroupTab-->>GroupsPage: onTabChange(tab)
  GroupsPage->>GroupList: pass filtered groups and selectedId
  GroupList-->>GroupsPage: onSelect(groupId)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🐰 A new groups page hops into view,
With tabs that switch 'tween friends and crew,
Scrolling lists dance side-to-side,
React Query brings data as guide,
Storybook's stage makes testing true!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Out of Scope Changes check ❓ Inconclusive While most changes align with #137, the PR includes Chromatic CI workflow and chromatic package setup which may be considered infrastructure/automation scope beyond the core component implementation requirement. Clarify whether Chromatic workflow setup was explicitly requested in #137 or is a supporting change for development workflow improvement.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed All coding requirements from issue #137 are met: GroupList component with horizontal layout and selection overlay [#137], GroupTab component for friend/character filtering [#137], and query hook implementation [#137].
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The pull request title clearly and accurately summarizes the main change: implementing a group list component UI for the groups retrospective page.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 feature/groups-list-#137

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 and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

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 implements the foundation for the Group Reflection feature, including a new groups page, UI components for tabs and lists, and a data fetching hook. Additionally, it updates repository documentation to use pnpm and adds Chromatic for visual testing. The review identifies a security vulnerability involving a hardcoded token in package.json, a potential runtime error in the groups page when handling empty data, and an improvement for type safety in the Zod schema.

Comment thread package.json Outdated
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
"build-storybook": "storybook build",
"chromatic": "npx chromatic --project-token=chpt_4fbd06fd5628810"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-critical critical

Chromatic 프로젝트 토큰이 package.json에 하드코딩되어 있습니다. 이는 민감한 정보가 소스 코드에 노출되는 심각한 보안 취약점입니다. 이 파일이 공개 저장소에 푸시되면 누구나 토큰을 사용하여 Chromatic 프로젝트에 접근할 수 있습니다.

이 토큰은 GitHub Actions Secrets과 같은 보안 저장소에 환경 변수로 저장하고, CI 워크플로우에서 secrets.CHROMATIC_PROJECT_TOKEN과 같이 참조하여 사용해야 합니다.

스크립트에서는 환경 변수를 사용하도록 수정해주세요.

Suggested change
"chromatic": "npx chromatic --project-token=chpt_4fbd06fd5628810"
"chromatic": "npx chromatic --project-token=$CHROMATIC_PROJECT_TOKEN"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

이거 토큰 새로운걸로 바로 reset 했습니다!! 죄송합니다🙇‍♀️

Comment thread app/groups/page.tsx Outdated

const GroupsPage = () => {
const [activeTab, setActiveTab] = useState<TabType>('friend');
const [selectedId, setSelectedId] = useState(MOCK_FRIENDS_GROUPS[0].id);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

MOCK_FRIENDS_GROUPS 배열이 비어있을 경우, MOCK_FRIENDS_GROUPS[0].id에 접근 시 런타임 에러가 발생하여 페이지가 크래시될 수 있습니다. Mock 데이터라 할지라도, 실제 API 연동 시 데이터가 없는 경우를 대비하여 안전하게 처리하는 것이 좋습니다. 옵셔널 체이닝(?.)과 nullish coalescing operator(??)를 사용하여 초기값을 안전하게 설정할 수 있습니다.

selectedId의 타입이 string으로 유지되어야 GroupListonSelect prop과 타입이 일치하므로, fallback 값으로 빈 문자열 등을 제공하는 것을 고려해볼 수 있습니다.

Suggested change
const [selectedId, setSelectedId] = useState(MOCK_FRIENDS_GROUPS[0].id);
const [selectedId, setSelectedId] = useState(MOCK_FRIENDS_GROUPS[0]?.id ?? '');

id: z.string(),
code: z.string(),
name: z.string(),
type: z.string(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

ResponseSchematype 필드가 z.string()으로 관대하게 정의되어 있습니다. src/components/features/groups/GroupList/GroupList.tsx 에서는 이 type'friend' | 'character'로 제한하여 사용하고 있습니다. Zod의 enum을 사용하여 API 응답의 type 값이 예상된 값 중 하나인지 명확하게 검증하는 것이 좋습니다. 이렇게 하면 타입 안정성을 높이고 예기치 않은 데이터에 대한 방어적인 코드를 작성할 수 있습니다.

Suggested change
type: z.string(),
type: z.enum(['friend', 'character']),

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: 6

🧹 Nitpick comments (3)
src/components/features/groups/GroupTab/GroupTab.stories.tsx (1)

22-28: ⚡ Quick win

Wire args into render to keep controls/docs consistent.

Default declares args but render currently ignores them, so controls for activeTab are not actually reflected in the rendered state. Initialize state from args to keep story behavior aligned with controls.

Suggested diff
 export const Default: Story = {
-  render: () => {
-    const [activeTab, setActiveTab] = useState<TabType>('friend');
+  render: (args) => {
+    const [activeTab, setActiveTab] = useState<TabType>(args.activeTab);
     return <GroupTab activeTab={activeTab} onTabChange={setActiveTab} />;
   },
   args: { activeTab: 'friend' as const, onTabChange: () => {} },
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/features/groups/GroupTab/GroupTab.stories.tsx` around lines 22
- 28, The story's render ignores Storybook args so controls don't affect the
rendered component; update Default.render to accept args and initialize local
state from args.activeTab (e.g., const [activeTab, setActiveTab] =
useState<TabType>(args.activeTab)) and pass args through to GroupTab while
overriding onTabChange with setActiveTab (e.g., <GroupTab {...args}
activeTab={activeTab} onTabChange={setActiveTab} />) so the story's controls and
docs remain consistent with Default.args.
src/styles/globals.css (1)

149-156: 💤 Low value

Consider adding thumb border/background-clip for visual consistency.

The horizontal scrollbar thumb lacks the border and background-clip: content-box properties present in the vertical version (lines 131-132). This creates visual inconsistency between horizontal and vertical scrollbars.

♻️ Proposed adjustment for consistency
 .scrollbar-x-transparent-track::-webkit-scrollbar-thumb {
   background-color: color-mix(
     in srgb,
     var(--color-g-0) 25%,
     transparent
   );
   border-radius: 999px;
+  border: 1px solid transparent;
+  background-clip: content-box;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/styles/globals.css` around lines 149 - 156, The horizontal scrollbar
thumb selector .scrollbar-x-transparent-track::-webkit-scrollbar-thumb is
missing the border and background-clip: content-box properties that the vertical
thumb uses; update that selector to include a matching border (same width and
color used in the vertical thumb) and add background-clip: content-box so the
color-mix background renders consistently with
.scrollbar-y-transparent-track::-webkit-scrollbar-thumb.
src/components/features/groups/GroupList/GroupList.tsx (1)

37-40: ⚡ Quick win

rounded-[10px] 대신 디자인 토큰 기반 radius 유틸을 사용해 주세요.

하드코딩된 arbitrary value보다 토큰 클래스(예: rounded-*)로 맞추는 편이 일관성과 유지보수에 유리합니다.

As per coding guidelines src/**/*.{ts,tsx}: "Use only design tokens from src/styles/globals.css (@theme block) and src/styles/typography.css for colors, typography, and spacing instead of arbitrary 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 `@src/components/features/groups/GroupList/GroupList.tsx` around lines 37 - 40,
The component uses a hardcoded radius class "rounded-[10px]" in the GroupList
rendering (the element with className="rounded-[10px]" and the overlay div shown
when !isSelected); replace both occurrences with the appropriate design-token
radius utility (e.g., rounded-sm/rounded-md/rounded-lg as defined in
src/styles/globals.css `@theme`) so the component uses the project's radius token
instead of an arbitrary value, and confirm the chosen token exists in the theme
and visually matches the previous 10px radius.
🤖 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 `@app/groups/page.tsx`:
- Around line 31-38: When switching tabs the previous selectedId is retained and
can point to an invalid ID for the new list; add logic in the component that
watches activeTab (e.g., a useEffect) and calls setSelectedId to the new tab's
first item's id or undefined when activeTab changes. Use the same arrays used
for GroupList (MOCK_FRIENDS_GROUPS and MOCK_CHARACTERS_GROUPS) to determine the
first id, and ensure you handle empty arrays by setting selectedId to undefined;
update only in response to activeTab changes so GroupTab/GroupList props remain
unchanged.

In `@package.json`:
- Line 15: The package.json "chromatic" script currently exposes a hardcoded
project token; update the "chromatic" script (referencing the "chromatic" npm
script name) to avoid embedding the token by reading it from an environment
variable (e.g., use CHROMATIC_PROJECT_TOKEN) or remove the script entirely and
rely on your CI's secrets (secrets.CHROMATIC_PROJECT_TOKEN) for publishes;
ensure local developers are instructed to set CHROMATIC_PROJECT_TOKEN in their
.env.local for local runs if you keep the script.

In `@src/components/features/groups/GroupList/GroupList.tsx`:
- Around line 26-30: The clickable item is currently a div which prevents native
keyboard activation and harms accessibility; replace the outer div in
GroupList.tsx with a semantic button element (keeping key={item.id}, className,
and the onClick={() => onSelect?.(item.id)} handler), add type="button" to avoid
form submission behavior, and remove any non-button ARIA/role hacks so the
element gains native keyboard focus and activation.

In `@src/components/features/groups/GroupTab/GroupTab.tsx`:
- Around line 19-21: The button elements rendered in GroupTab (the <button
key={tab.value} onClick={() => onTabChange(tab.value)} /> instances) lack an
explicit type, so they default to type="submit" and may submit enclosing forms;
update the JSX for these buttons to include type="button" to prevent accidental
form submissions when clicking tabs (locate the button rendering in GroupTab.tsx
near the onTabChange usage and add the type="button" attribute).

In `@src/components/features/groups/queries/useGroupListQuery.ts`:
- Around line 24-28: Add and export a suspense variant of the existing hook so
it matches the project's "standard + suspense" query hook pattern: create and
export useGroupListQuerySuspense which calls useQuery with the same queryKey
(groupKeys.list()) and queryFn (fetchGroups) but passes { suspense: true } (and
any other shared options) so the suspense behavior is enabled; keep the original
useGroupListQuery unchanged and ensure the new hook name follows the
useGroupListQuerySuspense convention.
- Around line 14-16: The schema currently uses z.string() for the group "type"
which permits any string; update the Zod schema in useGroupListQuery.ts to use
z.enum(['friend','character']) (or z.union of literals) for the type field so it
only accepts 'friend' or 'character', and then export both the normal and
suspense variants of the query hook (e.g., export useGroupListQuery and export
useGroupListQuerySuspense) following the existing project hook pattern so
consumers can import either the standard or suspense-enabled hook.

---

Nitpick comments:
In `@src/components/features/groups/GroupList/GroupList.tsx`:
- Around line 37-40: The component uses a hardcoded radius class
"rounded-[10px]" in the GroupList rendering (the element with
className="rounded-[10px]" and the overlay div shown when !isSelected); replace
both occurrences with the appropriate design-token radius utility (e.g.,
rounded-sm/rounded-md/rounded-lg as defined in src/styles/globals.css `@theme`) so
the component uses the project's radius token instead of an arbitrary value, and
confirm the chosen token exists in the theme and visually matches the previous
10px radius.

In `@src/components/features/groups/GroupTab/GroupTab.stories.tsx`:
- Around line 22-28: The story's render ignores Storybook args so controls don't
affect the rendered component; update Default.render to accept args and
initialize local state from args.activeTab (e.g., const [activeTab,
setActiveTab] = useState<TabType>(args.activeTab)) and pass args through to
GroupTab while overriding onTabChange with setActiveTab (e.g., <GroupTab
{...args} activeTab={activeTab} onTabChange={setActiveTab} />) so the story's
controls and docs remain consistent with Default.args.

In `@src/styles/globals.css`:
- Around line 149-156: The horizontal scrollbar thumb selector
.scrollbar-x-transparent-track::-webkit-scrollbar-thumb is missing the border
and background-clip: content-box properties that the vertical thumb uses; update
that selector to include a matching border (same width and color used in the
vertical thumb) and add background-clip: content-box so the color-mix background
renders consistently with
.scrollbar-y-transparent-track::-webkit-scrollbar-thumb.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8f60a052-7acc-474b-862c-a004319918bf

📥 Commits

Reviewing files that changed from the base of the PR and between 447b36f and 20b08c5.

⛔ Files ignored due to path filters (2)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • public/images/default-group.svg is excluded by !**/*.svg
📒 Files selected for processing (12)
  • .github/workflows/chromatic.yml
  • CLAUDE.md
  • app/groups/page.tsx
  • package.json
  • src/components/features/groups/GroupList/GroupList.stories.tsx
  • src/components/features/groups/GroupList/GroupList.tsx
  • src/components/features/groups/GroupTab/GroupTab.stories.tsx
  • src/components/features/groups/GroupTab/GroupTab.tsx
  • src/components/features/groups/constants/queryKey.ts
  • src/components/features/groups/constants/url.ts
  • src/components/features/groups/queries/useGroupListQuery.ts
  • src/styles/globals.css

Comment thread app/groups/page.tsx
Comment on lines +31 to +38
<GroupTab activeTab={activeTab} onTabChange={setActiveTab} />
<GroupList
groups={
activeTab === 'friend' ? MOCK_FRIENDS_GROUPS : MOCK_CHARACTERS_GROUPS
}
selectedId={selectedId}
onSelect={setSelectedId}
/>
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 | ⚡ Quick win

탭 전환 시 selectedId를 해당 탭의 첫 항목(또는 undefined)으로 동기화해 주세요.

지금은 이전 탭의 선택값이 유지되어, 실제 데이터 ID가 달라지면 전환 직후 선택 상태가 깨질 수 있습니다.

Proposed fix
-      <GroupTab activeTab={activeTab} onTabChange={setActiveTab} />
+      <GroupTab
+        activeTab={activeTab}
+        onTabChange={(nextTab) => {
+          setActiveTab(nextTab);
+          const nextGroups =
+            nextTab === 'friend' ? MOCK_FRIENDS_GROUPS : MOCK_CHARACTERS_GROUPS;
+          setSelectedId(nextGroups[0]?.id);
+        }}
+      />
🤖 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 `@app/groups/page.tsx` around lines 31 - 38, When switching tabs the previous
selectedId is retained and can point to an invalid ID for the new list; add
logic in the component that watches activeTab (e.g., a useEffect) and calls
setSelectedId to the new tab's first item's id or undefined when activeTab
changes. Use the same arrays used for GroupList (MOCK_FRIENDS_GROUPS and
MOCK_CHARACTERS_GROUPS) to determine the first id, and ensure you handle empty
arrays by setting selectedId to undefined; update only in response to activeTab
changes so GroupTab/GroupList props remain unchanged.

Comment thread package.json Outdated
Comment thread src/components/features/groups/GroupList/GroupList.tsx Outdated
Comment thread src/components/features/groups/GroupTab/GroupTab.tsx
Comment on lines +14 to +16
type: z.string(),
image: z.string(),
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/features/groups/queries/useGroupListQuery.ts

Repository: dnd-side-project/dnd-14th-5-frontend

Length of output: 925


🏁 Script executed:

# Search for other query hooks to see the suspense pattern
fd -t f "queries.*\.ts$" src/components/features --exec head -30 {} \; | head -100

Repository: dnd-side-project/dnd-14th-5-frontend

Length of output: 62


🏁 Script executed:

# Look for any type field usage in the groups feature
rg "type.*friend|character" src/components/features/groups -A 2 -B 2

Repository: dnd-side-project/dnd-14th-5-frontend

Length of output: 2718


🏁 Script executed:

# Check for z.enum patterns in other query schemas
rg "z\.enum" src/components/features -A 1 -B 1 | head -50

Repository: dnd-side-project/dnd-14th-5-frontend

Length of output: 2178


🏁 Script executed:

# Check if suspense variants exist in other query hooks
rg "Suspense" src/components/features/*/queries/ -t ts

Repository: dnd-side-project/dnd-14th-5-frontend

Length of output: 1640


🏁 Script executed:

# Look for any hook exported as both standard and suspense versions
rg "export.*Query.*Suspense|useSuspense" src/components/features --type ts | head -20

Repository: dnd-side-project/dnd-14th-5-frontend

Length of output: 2357


🏁 Script executed:

# Check the structure of other query hooks to see if they export suspense variants
fd -t f "queries.*\.ts$" src/components/features -exec grep -l "export" {} \; | head -10 | xargs -I {} bash -c 'echo "=== {} ===" && tail -10 {}'

Repository: dnd-side-project/dnd-14th-5-frontend

Length of output: 281


Add enum validation for type field and implement suspense hook variant

The type field should validate against specific enum values ('friend' | 'character') rather than accepting arbitrary strings. Additionally, the query hook pattern requires exporting both standard and suspense versions.

Proposed changes
+import { useSuspenseQuery } from '@tanstack/react-query';
+
-const ResponseSchema = z.array(
-  z.object({
-    id: z.string(),
-    code: z.string(),
-    name: z.string(),
-    type: z.string(),
+const ResponseSchema = z.array(
+  z.object({
+    id: z.string(),
+    code: z.string(),
+    name: z.string(),
+    type: z.enum(['friend', 'character']),
     image: z.string(),
   }),
 );

 export const useGroupListQuery = () =>
   useQuery({
     queryKey: groupKeys.list(),
     queryFn: fetchGroups,
   });
+
+export const useSuspenseGroupListQuery = () =>
+  useSuspenseQuery({
+    queryKey: groupKeys.list(),
+    queryFn: fetchGroups,
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/features/groups/queries/useGroupListQuery.ts` around lines 14
- 16, The schema currently uses z.string() for the group "type" which permits
any string; update the Zod schema in useGroupListQuery.ts to use
z.enum(['friend','character']) (or z.union of literals) for the type field so it
only accepts 'friend' or 'character', and then export both the normal and
suspense variants of the query hook (e.g., export useGroupListQuery and export
useGroupListQuerySuspense) following the existing project hook pattern so
consumers can import either the standard or suspense-enabled hook.

Comment on lines +24 to +28
export const useGroupListQuery = () =>
useQuery({
queryKey: groupKeys.list(),
queryFn: fetchGroups,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

쿼리 훅 패턴에 맞춰 suspense 버전도 함께 export해 주세요.

현재 표준 훅만 있어, 팀 규약(standard + suspense 동시 제공)과 불일치합니다.

As per coding guidelines src/components/features/*/queries/*.{ts,tsx}: "Implement query hook pattern: define responseSchema with Zod, create fetch function, and export both standard and suspense versions of the hook".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/features/groups/queries/useGroupListQuery.ts` around lines 24
- 28, Add and export a suspense variant of the existing hook so it matches the
project's "standard + suspense" query hook pattern: create and export
useGroupListQuerySuspense which calls useQuery with the same queryKey
(groupKeys.list()) and queryFn (fetchGroups) but passes { suspense: true } (and
any other shared options) so the suspense behavior is enabled; keep the original
useGroupListQuery unchanged and ensure the new hook name follows the
useGroupListQuerySuspense convention.

Copy link
Copy Markdown
Collaborator

@baegyeong baegyeong left a comment

Choose a reason for hiding this comment

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

스토리북 배포 해주셔서 감사해요! 작업하시느라 고생 많으셨습니다 👍👍

백엔드와 그룹 이름 글자수를 몇 자로 제한할 지 논의해도 좋을 것 같아요..! 그룹 이름이 차지하는 칸이 작다보니 제한하는 게 좋을 것 같습니다!

@jeongyou
Copy link
Copy Markdown
Collaborator Author

넵 좋아요 일단은 줄바꿈 되도록 처리 해놓긴 했는데 아예 그룹 이름 글자 수를 제한 하는 것도 좋을 것 같아요 👍

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.

♻️ Duplicate comments (2)
src/components/features/groups/queries/useGroupListQuery.ts (2)

1-1: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Export a regular query hook in addition to the suspense hook.

This file only exports the suspense variant, which breaks the project query-hook contract for reusable consumers.

🔧 Proposed fix
-import { useSuspenseQuery } from '`@tanstack/react-query`';
+import { useQuery, useSuspenseQuery } from '`@tanstack/react-query`';
@@
+export const useGroupListQuery = () =>
+  useQuery({
+    queryKey: groupKeys.list(),
+    queryFn: fetchGroups,
+  });
+
 export const useSuspenseGroupListQuery = () =>
   useSuspenseQuery({
     queryKey: groupKeys.list(),
     queryFn: fetchGroups,
   });

As per coding guidelines src/components/features/*/queries/**/*.{ts,tsx}: "Implement query hooks with useQuery and useSuspenseQuery … then export both regular and suspense variants".

Also applies to: 24-28

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/features/groups/queries/useGroupListQuery.ts` at line 1, The
file only provides the suspense variant; add and export a non-suspense
counterpart that uses useQuery so consumers can call the regular hook. Create a
new exported hook (e.g., useGroupListQuery) that wraps the same query key and
fetcher currently used by the suspense hook but calls useQuery instead of
useSuspenseQuery, keep the existing exported suspense hook (e.g.,
useGroupListQuerySuspense or existing default) unchanged, and ensure both hooks
share the same query key/fetch function and typings so they remain
interchangeable for consumers.

14-14: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Tighten type validation to allowed variants only.

Line 14 currently accepts any string, so invalid server values pass validation and can break downstream group-type logic.

🔧 Proposed fix
-    type: z.string(),
+    type: z.enum(['friend', 'character']),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/features/groups/queries/useGroupListQuery.ts` at line 14, The
`type: z.string()` validator in useGroupListQuery is too permissive; replace it
with a strict validator that only allows the server-known group type variants
(e.g. use z.enum([...allowedVariants...]) or z.union of z.literal(...) values)
so invalid values fail validation; update the schema entry for the `type` field
inside the useGroupListQuery schema (where `type` is currently defined) to use
z.enum or z.literal variants that exactly match the server-side group type
values.
🤖 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.

Duplicate comments:
In `@src/components/features/groups/queries/useGroupListQuery.ts`:
- Line 1: The file only provides the suspense variant; add and export a
non-suspense counterpart that uses useQuery so consumers can call the regular
hook. Create a new exported hook (e.g., useGroupListQuery) that wraps the same
query key and fetcher currently used by the suspense hook but calls useQuery
instead of useSuspenseQuery, keep the existing exported suspense hook (e.g.,
useGroupListQuerySuspense or existing default) unchanged, and ensure both hooks
share the same query key/fetch function and typings so they remain
interchangeable for consumers.
- Line 14: The `type: z.string()` validator in useGroupListQuery is too
permissive; replace it with a strict validator that only allows the server-known
group type variants (e.g. use z.enum([...allowedVariants...]) or z.union of
z.literal(...) values) so invalid values fail validation; update the schema
entry for the `type` field inside the useGroupListQuery schema (where `type` is
currently defined) to use z.enum or z.literal variants that exactly match the
server-side group type values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1ba6a6f0-14a1-49bc-a3ee-2c4829be0201

📥 Commits

Reviewing files that changed from the base of the PR and between 20b08c5 and 609624f.

📒 Files selected for processing (6)
  • app/groups/page.tsx
  • package.json
  • src/components/features/groups/GroupList/GroupList.stories.tsx
  • src/components/features/groups/GroupList/GroupList.tsx
  • src/components/features/groups/GroupTab/GroupTab.tsx
  • src/components/features/groups/queries/useGroupListQuery.ts
✅ Files skipped from review due to trivial changes (1)
  • src/components/features/groups/GroupList/GroupList.stories.tsx
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/components/features/groups/GroupTab/GroupTab.tsx
  • app/groups/page.tsx
  • package.json

@jeongyou jeongyou changed the title [Feat] 그룹 리스트 컴포넌트 구현 [Feat] 그룹 리스트 컴포넌트 UI 구현 May 19, 2026
@jeongyou jeongyou merged commit ae89e02 into main May 19, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

그룹 리스트 컴포넌트 구현

2 participants