Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough타입 변경(cheer: imageUrl → images[], tags 추가), 상점 API·쿼리에 tag·location 필터 도입, RecentCheers 컴포넌트의 태그 표시 및 이미지 소스 변경, 홈에 신규 StoreList 섹션 추가, 스토어 리스트 필터링 로직 적용 및 이미지 렌더 단순화, 일부 스타일 조정 및 정적 상수 삭제. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant UI as StoreList (홈)
participant Query as storesQueryOptions
participant API as getStores
participant Server as HTTP Server
User->>UI: 카테고리/필터 선택
UI->>Query: queryKey(size, category, tag[], location[])
Query->>API: getStores({ size, category, tag, location })
API->>Server: GET /shops?size=&category=&tag=csv&location=csv
Server-->>API: StoresResponse
API-->>Query: StoresResponse
Query-->>UI: 데이터 렌더
sequenceDiagram
participant RecentCheers as RecentCheers
participant Data as Cheer
participant Tags as ALL_TAGS
Data-->>RecentCheers: { images[], tags[] }
RecentCheers->>Tags: 태그 이름 매칭(ALL_TAGS)
Tags-->>RecentCheers: 선택된 태그(최대 2 + 나머지 수)
Note over RecentCheers: 첫 이미지(url) 사용해 썸네일 표시
sequenceDiagram
participant StoreListComp as StoreList (스토어 영역)
participant Filter as useChipFilter
participant Query as storesQueryOptions
StoreListComp->>Filter: 선택 필터 읽기
Filter-->>StoreListComp: { tag[], location[], ... }
StoreListComp->>Query: options({ tag, location, category })
Note over StoreListComp: 이미지 1장만 표시, 더보기 제거
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
|
🎨 Storybook Preview: https://685a32a1c0bbd269fdb67af4-qnybxncrjs.chromatic.com/ |
wkdtnqls0506
left a comment
There was a problem hiding this comment.
ㅋㅋㅋㅋㅋㅋㅋㅋㅋ빠르시다!! 저는 준환님 덕분에 FilterBottomSheet 편안히 썼슴다 ㅎㅎ
수고하셨어요~!!
| import { at, chunk, compact } from "es-toolkit"; | ||
| import { isEmpty } from "es-toolkit/compat"; |
There was a problem hiding this comment.
신기하고 편리한 것들이 정말 많군유 @@
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (19)
src/components/ui/FoodCategory/FoodCategory.css.ts (1)
11-12: 하단 보더 추가 LGTM. 다만 의미 토큰 사용과 스크롤바 숨김의 크로스 브라우저 보완을 제안합니다.
- 디자인 토큰 일관성: colors.coolNeutral 대신 semantic.border.grayLight 사용이 더 일관적입니다.
- 스크롤바 숨김: WebKit 전용 처리만 있어 Firefox/구형 Edge(Trident)에서 스크롤바가 노출될 수 있습니다.
다음 diff로 테마 일관성을 맞출 수 있습니다(선택 사항):
- borderBottom: `1px solid ${colors.coolNeutral[97]}`, + borderBottom: `1px solid ${semantic.border.grayLight}`,선택적으로, 아래 속성들을 container 스타일에 추가해 크로스 브라우저 스크롤바 숨김을 보완하세요(선택 사항):
// container 스타일 객체 상단 레벨에 추가 msOverflowStyle: "none", // IE/Legacy Edge scrollbarWidth: "none", // Firefoxsrc/app/(home)/_components/RecentCheers/RecentCheers.css.ts (1)
63-63: objectFit: cover 적용 LGTM썸네일 크롭 일관성 향상에 적절합니다. 필요 시 중앙 크롭을 보장하려면 objectPosition: "center" 추가를 고려해도 됩니다(선택).
src/app/(home)/_api/shop/shop.api.ts (2)
21-23: toCSV 중복 호출 제거로 가독성/미세 성능 개선같은 값에 대해 toCSV를 두 번 호출하고 있습니다. 한 번만 계산해 재사용하면 명확합니다.
- const toCSV = (v?: string | string[]) => - Array.isArray(v) ? v.filter(Boolean).join(",") : v || undefined; + const toCSV = (v?: string | string[]) => + Array.isArray(v) ? v.filter(Boolean).join(",") : v || undefined; return await http .get("api/shops", { searchParams: { size, ...(category ? { category } : {}), - ...(toCSV(tag) ? { tag: toCSV(tag) } : {}), - ...(toCSV(location) ? { location: toCSV(location) } : {}), + ...(() => { + const tagCSV = toCSV(tag); + return tagCSV ? { tag: tagCSV } : {}; + })(), + ...(() => { + const locationCSV = toCSV(location); + return locationCSV ? { location: locationCSV } : {}; + })(), }, })Also applies to: 29-31
13-20: JSDoc 파라미터 주석 업데이트 필요함수 시그니처에 tag/location이 추가되었으나 상단 주석에 반영되지 않았습니다. 공개 API인 만큼 주석 동기화를 권장합니다.
src/app/(home)/_api/shop/shop.queries.ts (2)
7-13: 쿼리 키 안정성 향상: 필터 배열 정렬/정규화하여 캐시 키 난립 방지동일 의미의 필터라도 순서가 다르면 다른 queryKey가 생성됩니다. tag/location을 정렬/정규화해서 키 안정성을 높이세요.
export const storesQueryKeys = { all: ["stores"] as const, - size: ( - size: number, - category?: string, - tag?: string[], - location?: string[] - ) => [...storesQueryKeys.all, size, category, tag, location] as const, + size: ( + size: number, + category?: string, + tag?: string[], + location?: string[] + ) => { + const norm = (a?: string[]) => (a && a.length ? [...a].filter(Boolean).sort() : undefined); + return [...storesQueryKeys.all, size, category, norm(tag), norm(location)] as const; + }, };
27-29: API 호출 파라미터도 정렬/정규화하여 서버 캐시/로그 일관성 확보queryKey만 정규화하면 캐시 일관성은 좋아지지만, 서버에는 순서가 다른 CSV가 전달될 수 있습니다. API 호출에도 동일 정규화를 적용해두면 운영 관점에서 유리합니다.
- queryOptions({ - queryKey: storesQueryKeys.size(size, category, tag, location), - queryFn: () => getStores({ size, category, tag, location }), - }); + queryOptions({ + queryKey: storesQueryKeys.size(size, category, tag, location), + queryFn: () => { + const norm = (a?: string[]) => (a && a.length ? [...a].filter(Boolean).sort() : undefined); + return getStores({ size, category, tag: norm(tag), location: norm(location) }); + }, + });src/app/(home)/page.tsx (1)
23-28: 구분선은 Spacer 스타일 대신 전용 컴포넌트(Separator/Divider) 사용 고려시맨틱/재사용성 측면에서 색 있는 12px 바를 Spacer 인라인 스타일로 만드는 것보다, 얇은 구분선 전용 컴포넌트(예:
<Divider height={12} color={...} />)를 두는 편이 의도 전달과 유지보수에 유리합니다. 현 구조로도 문제는 없으나, 디자인 토큰/테마 변경 시 일괄치환이 쉬운 형태로 추상화를 제안합니다.src/app/(home)/_components/StoreList/StoreList.tsx (1)
29-31: 서스펜스 fallback 추가로 로딩 UX 개선 제안현재
<Suspense>의fallback이 없어 초기 로딩 동안 공백이 발생합니다. 간단한 여백/스켈레톤을 추가하면 체감 UX가 좋아집니다.다음처럼 최소 fallback을 지정할 수 있습니다.
- <Suspense> + <Suspense fallback={<Spacer size={16} />}> <StoreListComponent category={selectedCategory.name} /> </Suspense>src/app/(store)/stores/(list)/_components/StoreList/StoreList.tsx (2)
41-49: 대량 렌더링에 대한 페이징/가상화 고려
size: 50으로 한 번에 렌더링합니다. 디바이스/네트워크 상황에 따라 이미지 서브쿼리(N개)와 맞물려 비용이 커질 수 있어, 무한 스크롤(페이지네이션) 또는 리스트 가상화 도입을 장기적으로 고려해 주세요.
31-37: 정렬된 배열로 캐시 키 안정성 보장 권장
useSuspenseQuery호출부에 전달하는tag/location배열은 선택 순서에 따라 달라질 수 있어, 동일한 필터 조합임에도 서로 다른queryKey가 생성되어 캐시 미스나 불필요한 리패치가 발생할 수 있습니다. 호출 직전에 배열을 정렬하여 순서 독립성을 보장하는 것을 권장합니다.• 대상 위치
- 파일:
src/app/(store)/stores/(list)/_components/StoreList/StoreList.tsx
라인 31–37 (useSuspenseQuery인자)• 수정 제안 (옵션)
} = useSuspenseQuery( storesQueryOptions({ size: 50, category, - tag: [...selectedFilters.atmosphereTags, ...selectedFilters.utilityTags], - location: selectedFilters.locations, + tag: [ + ...selectedFilters.atmosphereTags, + ...selectedFilters.utilityTags, + ].sort(), + location: [...selectedFilters.locations].sort(), }) );• 추가 확인 사항
storesQueryKeys.size(size, category, tag, location)내부에서는 현재 전달된 배열을 그대로 사용하고 별도 정렬/정규화 로직은 없습니다.
– 필요하다면storesQueryKeys.size구현부에서 내부 정렬을 추가하거나, 호출부에서 정렬된 배열을 전달하는 방안 중 선택하세요.shop.api.ts내부toCSV유틸(로컬 화살표 함수)은 배열을 필터링 후join(",")만 수행하므로, 호출부에서 정렬된 배열을 넘기면 쿼리 문자열에도 정렬 순서가 유지됩니다.위 변경은 필수는 아니지만, 캐시 키 안정성을 높이고 불필요한 refetch를 방지하는 좋은 관례입니다.
src/app/(home)/_components/RecentCheers/RecentCheers.tsx (6)
5-6: at/compact/isEmpty 의존 제거하고 slice/length로 단순화하세요가시 태그 2개만 추리는 목적이라면
slice(0, 2)와length체크가 가장 읽기 쉽고 타입 안정적입니다. 번들 크기도 약간 줄어듭니다.적용 예시:
- import { at, chunk, compact } from "es-toolkit"; - import { isEmpty } from "es-toolkit/compat"; + import { chunk } from "es-toolkit"; - const selectedTags = ALL_TAGS.filter(tag => tags?.includes(tag.name)); - - const visibleTags = compact(at(selectedTags, [0, 1])); + const selectedTags = ALL_TAGS.filter(tag => tags.includes(tag.name)); + const visibleTags = selectedTags.slice(0, 2); - {isEmpty(selectedTags) ? null : ( + {selectedTags.length === 0 ? null : (Also applies to: 114-117, 165-166
77-78:tags타입과 사용 방식이 일치하지 않습니다
RecentSupportCardProps에서는tags: string[]로 필수값인데, 필터링에서는tags?.includes(...)로 옵셔널처럼 다루고 있습니다. 둘 중 하나로 일관성 있게 맞춰 주세요.옵션 A(권장: 서버에서 빈 배열 보장):
- const selectedTags = ALL_TAGS.filter(tag => tags?.includes(tag.name)); + const selectedTags = ALL_TAGS.filter(tag => tags.includes(tag.name));옵션 B(API가 undefined를 반환할 수 있다면):
- tags: string[]; + tags?: string[];그리고 호출부에서
- tags={cheer.tags} + tags={cheer.tags ?? []}Also applies to: 105-112, 114-114
82-90: 스켈레톤과 라우팅/문구가 불일치합니다본 컴포넌트는 '/cheer' + "응원 전체보기"로 변경되었지만, 스켈레톤은 여전히 '/stores' + "가게 전체보기"를 가리킵니다. UX 혼선을 막기 위해 스켈레톤도 동일하게 맞춰 주세요.
스켈레톤 수정 예시(파일 하단, 안내용 추가 코드 블록):
- <Link href='/stores'> + <Link href='/cheer'> ... - 가게 전체보기 + 응원 전체보기
43-45: 빈 데이터일 때 refresh에서 NaN 인덱스가 발생할 수 있습니다
chunkedList.length === 0면% 0으로NaN이 되고, 이후 인덱싱이 전부undefined가 됩니다. 데이터가 없을 때는 버튼을 비활성화하거나 no-op 처리하세요.const handleRefresh = () => { - setCurrentIndex((currentIndex + 1) % chunkedList.length); + if (chunkedList.length <= 1) return; + setCurrentIndex((currentIndex + 1) % chunkedList.length); };또는 렌더 시
chunkedList.length <= 1이면 새로고침 버튼을disabled로 두는 것도 방법입니다.
168-171: 태그 아이콘 Image의 도메인/경로 확인 필요
tag.iconUrl이 외부 도메인이라면next.config.js의images.domains에 등록되어야 합니다. 내부 정적 자원이라면next/image대신 정적 import(svg) 또는<img>사용이 더 가볍습니다(14px 아이콘).대안:
- 외부 호스트:
next.config.js에 호스트 추가.- 내부 아이콘:
import Icon from "@/assets/foo.svg"; <Icon .../>또는<img width={14} height={14} src={...} alt={...} />
165-175: ‘+N’ 더보기 칩에 접근성 힌트 추가 제안화면 리더 사용자에게 의미를 전달하려면
aria-label이나title을 추가하는 것이 좋습니다. 예: “태그 3개 더 있음”.- {selectedTags.length > 2 && <Tag>+{selectedTags.length - 2}</Tag>} + {selectedTags.length > 2 && ( + <Tag aria-label={`태그 ${selectedTags.length - 2}개 더 있음`} title={`태그 ${selectedTags.length - 2}개 더 있음`}> + +{selectedTags.length - 2} + </Tag> + )}src/app/(store)/stores/(list)/_components/StoreList/StoreList.css.ts (3)
24-27:storeImagesContainer의position: relative필요성 확인자식에서 absolute 포지셔닝을 사용하지 않는다면 불필요할 수 있습니다. 의미 없는 stacking context 생성을 피하려면 제거를 고려해 주세요.
35-39: 이미지 라운딩/클리핑은 래퍼에 위임하세요
Next/Image를fill또는 고정 크기로 사용할 때는 래퍼가overflow: hidden과borderRadius를 가져가는 편이 안전합니다. 현재는storeImage에borderRadius가 있어 중복/미클리핑 이슈가 생길 수 있습니다.권장 변경:
export const storeImageWrapper = style({ position: "relative", width: "100%", height: "168px", + overflow: "hidden", + borderRadius: radius[160], }); export const storeImage = style({ objectFit: "cover", - overflow: "hidden", flexShrink: 0, - - borderRadius: radius[160], + // 필요 시: width/height 100% 또는 Next/Image의 fill 사용 });또한
storeImage의overflow: "hidden"은 불필요합니다(자식 클리핑은 래퍼가 담당).Also applies to: 46-47
54-56: 빈 이미지 폭 고정(126px)로 인한 불균형 가능성래퍼가
width: 100%인 구조라면emptyImage도 동일하게 맞추는 편이 카드 간 레이아웃 왜곡을 줄입니다.참고 변경(파일 외 영역 수정 예시):
- width: "126px", + width: "100%",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (13)
src/app/(home)/_api/cheer/cheer.types.ts(1 hunks)src/app/(home)/_api/shop/shop.api.ts(1 hunks)src/app/(home)/_api/shop/shop.queries.ts(1 hunks)src/app/(home)/_components/RecentCheers/RecentCheers.css.ts(2 hunks)src/app/(home)/_components/RecentCheers/RecentCheers.tsx(5 hunks)src/app/(home)/_components/StoreList/StoreList.tsx(1 hunks)src/app/(home)/_components/StoreList/index.ts(1 hunks)src/app/(home)/_components/index.ts(1 hunks)src/app/(home)/page.tsx(2 hunks)src/app/(store)/constants/storeCategory.constants.ts(0 hunks)src/app/(store)/stores/(list)/_components/StoreList/StoreList.css.ts(3 hunks)src/app/(store)/stores/(list)/_components/StoreList/StoreList.tsx(3 hunks)src/components/ui/FoodCategory/FoodCategory.css.ts(1 hunks)
💤 Files with no reviewable changes (1)
- src/app/(store)/constants/storeCategory.constants.ts
🧰 Additional context used
📓 Path-based instructions (3)
{src/app/**/_components/**/*.tsx,src/components/**/*.tsx}
📄 CodeRabbit inference engine (.cursor/rules/nextjs-folder-structure.mdc)
Component files must use PascalCase naming (e.g.,
Button.tsx,DomainLayout.tsx).
Files:
src/app/(store)/stores/(list)/_components/StoreList/StoreList.tsxsrc/app/(home)/_components/StoreList/StoreList.tsxsrc/app/(home)/_components/RecentCheers/RecentCheers.tsx
src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/nextjs-folder-structure.mdc)
All files must use TypeScript.
Files:
src/app/(store)/stores/(list)/_components/StoreList/StoreList.tsxsrc/components/ui/FoodCategory/FoodCategory.css.tssrc/app/(home)/_components/RecentCheers/RecentCheers.css.tssrc/app/(home)/_components/index.tssrc/app/(home)/_components/StoreList/StoreList.tsxsrc/app/(home)/_api/cheer/cheer.types.tssrc/app/(home)/_components/StoreList/index.tssrc/app/(home)/_api/shop/shop.queries.tssrc/app/(store)/stores/(list)/_components/StoreList/StoreList.css.tssrc/app/(home)/page.tsxsrc/app/(home)/_api/shop/shop.api.tssrc/app/(home)/_components/RecentCheers/RecentCheers.tsx
{src/styles/**/*.css.ts,src/app/**/_components/**/*.css.ts,src/components/**/*.css.ts}
📄 CodeRabbit inference engine (.cursor/rules/nextjs-folder-structure.mdc)
Style files must use camelCase naming with the
.css.tsextension (e.g.,Button.css.ts,theme.css.ts).
Files:
src/components/ui/FoodCategory/FoodCategory.css.tssrc/app/(home)/_components/RecentCheers/RecentCheers.css.tssrc/app/(store)/stores/(list)/_components/StoreList/StoreList.css.ts
🧬 Code graph analysis (7)
src/app/(store)/stores/(list)/_components/StoreList/StoreList.tsx (1)
src/app/(home)/_api/shop/shop.queries.ts (1)
storesQueryOptions(15-29)
src/app/(home)/_components/StoreList/StoreList.tsx (1)
src/hooks/useFoodCategory.ts (1)
useFoodCategory(38-79)
src/app/(home)/_api/cheer/cheer.types.ts (1)
src/types/image.types.ts (1)
ImageResponse(11-11)
src/app/(home)/_api/shop/shop.queries.ts (1)
src/app/(home)/_api/shop/shop.api.ts (1)
getStores(10-34)
src/app/(home)/page.tsx (2)
src/app/(home)/_components/index.ts (2)
RecentCheers(1-1)StoreList(4-4)src/app/(home)/_components/StoreList/StoreList.tsx (1)
StoreList(11-34)
src/app/(home)/_api/shop/shop.api.ts (2)
src/app/(home)/_api/shop/shop.types.ts (1)
StoresResponse(10-12)src/lib/api/client.ts (1)
http(69-74)
src/app/(home)/_components/RecentCheers/RecentCheers.tsx (1)
src/constants/tag.constants.ts (1)
ALL_TAGS(84-84)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (13)
src/app/(home)/_api/cheer/cheer.types.ts (1)
1-2: Cheer.tags 하위 호환성 검증 필요
Cheer타입에서tags: string[]를 필수로 유지하면, 백엔드가 빈 배열을 반환하지 않거나tags필드를 누락했을 때 런타임 에러가 발생할 수 있습니다. 아래 사항을 확인 및 적용해주세요.– cheer.types.ts
• 위치:src/app/(home)/_api/cheer/cheer.types.ts
• 현재 정의:
ts export type Cheer = { // … tags: string[]; };–
cheer.tags직접 사용 위치
• RecentCheers 컴포넌트
src/app/(home)/_components/RecentCheers/RecentCheers.tsx:77—tags={cheer.tags}
• CheerCardTags 컴포넌트
src/app/(cheer)/cheer/_components/CheerCard/CheerCard.tsx:134—<CheerCardTags tags={cheer.tags} />추천 옵션(백엔드가 빈 배열을 항상 보장하지 않는 경우):
타입을 optional로 변경
export type Cheer = { // … - tags: string[]; + tags?: string[]; };adapter 레이어에서 기본값 매핑
function normalizeCheer(raw: RawCheer): Cheer { return { ...raw, tags: raw.tags ?? [], }; }위 두 옵션 중 하나를 적용하거나, 백엔드 스펙(빈 배열 보장 여부)을 우선 확인 후 진행해주세요.
src/app/(home)/_components/RecentCheers/RecentCheers.css.ts (1)
9-11: 카드 높이 동적화 + overflow 처리 LGTMmin/maxHeight와 overflow: hidden 적용으로 내용 길이 편차 대응이 좋아졌습니다. 내부 포커스 가능한 요소가 있다면 포커스 링이 잘리지 않는지만 한 번만 확인해 주세요.
src/app/(home)/_components/index.ts (1)
4-4: StoreList 배럴 노출 추가 LGTM홈 컴포넌트 퍼블릭 API에 StoreList를 노출한 선택이 합리적입니다. 경로
./StoreList는 내부 배럴(index.ts)을 바라보므로 트리셰이킹/경로 안정성 모두 괜찮습니다.src/app/(home)/_components/StoreList/index.ts (1)
1-1: 단일 엔트리 배럴 구성 적절StoreList 하위 모듈을
export *로 재노출하는 패턴이 상위 배럴과 잘 맞습니다. 추후 서브 컴포넌트가 추가되어도 import 경로가 안정적으로 유지됩니다.src/app/(home)/page.tsx (2)
30-30: 홈 섹션에 StoreList 통합 👍홈 탭에서 RecentCheers 아래에 스토어 리스트를 배치한 구성과 여백 조정이 자연스럽습니다. 상위 페이지는 클라이언트 컴포넌트이므로 서브트리의 서스펜스/쿼리 훅과도 호환됩니다.
6-6: colors.coolNeutral[98] 및 [96] 토큰 존재 확인 완료colors.css.ts 파일 내에서
coolNeutral스케일에 99, 98, 96 키가 정의되어 있어colors.coolNeutral[98]및[96]호출 시 런타임 오류가 없습니다. 해당 리뷰 코멘트는 해결되었습니다.src/app/(home)/_components/StoreList/StoreList.tsx (1)
12-14: 빈 카테고리 문자열 처리 확인(캐시 키/쿼리 파라미터 영향)
selectedCategory.name이 빈 문자열일 수 있습니다. 이 값이 그대로 하위StoreListComponent→storesQueryOptions에 전달되면,
- 캐시 키에
""가 포함되어 불필요한 변형 키가 생길 수 있고,- API 레이어에서 빈 문자열이 쿼리스트링에 실리면 서버에서 "전체"가 아닌 "빈 문자열"로 해석될 가능성이 있습니다.
현 구현이 “빈 문자열=전체”로 명시적으로 처리됨을 보장하는지 확인 부탁드립니다. 필요 시 아래 둘 중 하나로 정리하는 것을 권장합니다.
- 하위 컴포넌트 prop을 선택형으로 완화:
- export const StoreList = ({ category }: { category: string }) => { + export const StoreList = ({ category }: { category?: string }) => {
- 혹은 상위에서 undefined로 정규화:
- <StoreListComponent category={selectedCategory.name} /> + <StoreListComponent category={selectedCategory.name || undefined} />두 방법 중 하나만 택해 일관성 있게 적용해 주세요. 선택 2를 택하면 하위 타입도 함께 조정되어야 합니다.
Also applies to: 29-31
src/app/(store)/stores/(list)/_components/StoreList/StoreList.tsx (3)
117-117: 썸네일 선택 로직 간결화 👍
first(imageUrls)로 대표 이미지를 선택하고 Empty 상태를 분기하는 방식이 단순하고 명확합니다.
145-149: Empty 상태 컬러 토큰 검증
colors.coolNeutral[96]토큰 존재 여부도 함께 확인 부탁드립니다(상위 파일에서 98 인덱스와 동일 이슈).
124-138:unoptimized플래그 제거 가능
프로젝트의next.config.ts에 이미images.remotePatterns로 원격 이미지 도메인이 등록되어 있으므로,unoptimized옵션을 제거하고 기본 이미지 최적화를 그대로 사용할 수 있습니다.검토 대상:
- 파일:
src/app/(store)/stores/(list)/_components/StoreList/StoreList.tsx- 위치: 124–138번째 줄
제거할 코드:
- // TODO: 추후 제거 - unoptimized추가 안내:
priority={false}는 Next/Image의 기본값이므로 별도로 명시하지 않아도 무방합니다.src/app/(home)/_components/RecentCheers/RecentCheers.tsx (2)
72-73: 새 스키마(images[0]?.url) 적용 👍 — 안전장치도 적절합니다빈 문자열 처리 전에 조건부 렌더링으로 fallback을 제공하고 있어 런타임 경고를 잘 회피합니다. 이 변경은 문제 없어 보입니다.
17-17: 태그 시스템 연동을 위한 컴포넌트/상수 import 추가 LGTMUI 구성요소와 상수 분리 방향 좋습니다. 추후 태그 체계 확장에도 유연합니다.
Also applies to: 20-20
src/app/(store)/stores/(list)/_components/StoreList/StoreList.css.ts (1)
61-62: emptyImage 라운딩 통일 👍이미지와 동일한 radius로 UI 일관성이 좋아졌습니다.
| @@ -0,0 +1,34 @@ | |||
| import { Suspense } from "react"; | |||
There was a problem hiding this comment.
Client 전용 훅 사용 파일에 'use client' 누락 — 빌드 에러 가능
이 컴포넌트는 useFoodCategory를 통해 useRouter/useSearchParams/usePathname(Client 전용)를 사용합니다. 파일 최상단에 "use client" 지시어가 없으면 Server Component로 취급되어 Next.js 빌드 시 에러가 납니다.
아래 패치를 적용해 주세요(Line 1 상단에 추가):
+ "use client";
import { Suspense } from "react";📝 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.
| import { Suspense } from "react"; | |
| "use client"; | |
| import { Suspense } from "react"; |
🤖 Prompt for AI Agents
In src/app/(home)/_components/StoreList/StoreList.tsx around line 1, this
component uses client-only hooks (useRouter/useSearchParams/usePathname via
useFoodCategory) but lacks the "use client" directive; add the "use client"
directive as the very first line of the file so Next.js treats the file as a
Client Component and prevents build-time errors.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/app/(store)/stores/(list)/_components/StoreListGNB/StoreListGNB.tsx (1)
3-5: 타이틀 하드코딩 대신 props로 일반화해 재사용성/카피 변경 대응성 향상 제안향후 i18n/A-B 테스트/카피 변경에 유연하게 대응하려면 기본값을 두고 title을 외부에서 주입하는 편이 좋습니다.
-export const StoreListGNB = () => { - return <GNB title='가게 모아보기' />; -}; +type StoreListGNBProps = { title?: string }; +export const StoreListGNB = ({ title = "가게 모아보기" }: StoreListGNBProps) => { + return <GNB title={title} />; +};src/app/(store)/stores/(list)/page.tsx (1)
9-9: Spacer 임포트 OK. 다만 간격 스케일 일관성 확인 권장현재 사용처(라인 32)에서 size={12}를 쓰고 있는데, 디자인 토큰이 4/8 스케일 기반이라면 12가 예외값일 수 있습니다. 팀 스케일과 맞는지 한 번만 확인 부탁드립니다.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
src/app/(store)/stores/(list)/_components/StoreListGNB/StoreListGNB.tsx(1 hunks)src/app/(store)/stores/(list)/page.tsx(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
src/**/*.{ts,tsx}
📄 CodeRabbit inference engine (.cursor/rules/nextjs-folder-structure.mdc)
All files must use TypeScript.
Files:
src/app/(store)/stores/(list)/page.tsxsrc/app/(store)/stores/(list)/_components/StoreListGNB/StoreListGNB.tsx
{src/app/**/_components/**/*.tsx,src/components/**/*.tsx}
📄 CodeRabbit inference engine (.cursor/rules/nextjs-folder-structure.mdc)
Component files must use PascalCase naming (e.g.,
Button.tsx,DomainLayout.tsx).
Files:
src/app/(store)/stores/(list)/_components/StoreListGNB/StoreListGNB.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test
🔇 Additional comments (4)
src/app/(store)/stores/(list)/_components/StoreListGNB/StoreListGNB.tsx (2)
4-4: 간결화된 GNB 적용, 문제 없어 보입니다
- 컴포넌트/파일 네이밍이 PascalCase를 준수하고, TSX 사용 요건도 충족합니다.
- 서버 컴포넌트로서 문자열 props만 내려 안정적입니다.
4-4: StoreListGNB 내비게이션 액션 누락 확인 요청
src/app/(store)/stores/(list)/_components/StoreListGNB/StoreListGNB.tsx에서처럼 left/right addon 없이 GNB가 렌더링되고 있어, 뒤로가기 버튼(왼쪽)이나 프로필 진입(오른쪽)이 제거된 상태입니다.return <GNB title='가게 모아보기' />;- 동일한 GNB 컴포넌트가 쓰이는 다른 화면들(SettingGNB, StoreRegisterGNB, StoreDetailGNB 등)에는
leftAddon·rightAddon으로 내비게이션 액션이 제공되고 있음을 확인했습니다.- 스토어 리스트 페이지가 루트 레이아웃의 BottomNavigation(하단 탭)을 통해 진입되는 화면인지, 아니면 독립적인 페이지인지 검토 부탁드립니다.
- 만약 BottomNavigation이 적용되어 스토어 리스트 진입 경로가 탭 내에 있다면 프로필 진입은 하단 탭을 통해 여전히 보장됩니다.
- BottomNavigation이 없는 구조라면, 별도의 뒤로가기 또는 프로필 버튼이 필요할 수 있습니다.
위 사항을 확인하신 후, 필요 시
StoreListGNB에leftAddon·rightAddon을 추가하거나, 레이아웃 차원에서 하단 네비게이션 노출 여부를 검토해 주시면 감사하겠습니다.src/app/(store)/stores/(list)/page.tsx (2)
5-5: ChipFilter 도입 자체는 좋습니다.공유 컴포넌트로 분리해 재사용성을 높인 점 👍
31-35: 위 스크립트가--type tsx를 인식하지 못해 실행 결과가 출력되지 않았습니다. 아래와 같이 파일 확장자 글로브(-g)를 사용해서 다시 한 번 확인 후 결과를 공유 부탁드립니다.#!/bin/bash # 1) Suspense import 원본 확인 rg -nP -C1 'from\s+"@suspensive/react"|from\s+"react"' -g '*.tsx' # 2) ChipFilter 훅/프로바이더/컨텍스트 사용 여부 확인 rg -nP -C2 'useChipFilter\(|ChipFilterProvider|ChipFilterContext' -g '*.{ts,tsx}' # 3) StoreList 컴포넌트 시그니처 확인 (category가 optional string인지) rg -nP '^(export\s+)?(function|const)\s+StoreList' -C2 -g '*.{ts,tsx}'위 결과를 토대로 리뷰 코멘트를 최종 확정하겠습니다.
✅ 이슈 번호
close x
🪄 작업 내용 (변경 사항)
📸 스크린샷
💡 설명
🗣️ 리뷰어에게 전달 사항
덕분에 편하게 만들었어유,,
📍 트러블 슈팅
Summary by CodeRabbit
신기능
스타일
기타