Skip to content

Comments

feat: 가게 상세 페이지 태그 추가, 응원 카드 변경#156

Merged
Seojunhwan merged 6 commits intomainfrom
feature/PRODUCT-274
Aug 22, 2025
Merged

feat: 가게 상세 페이지 태그 추가, 응원 카드 변경#156
Seojunhwan merged 6 commits intomainfrom
feature/PRODUCT-274

Conversation

@Seojunhwan
Copy link
Member

@Seojunhwan Seojunhwan commented Aug 21, 2025

✅ 이슈 번호

close x


🪄 작업 내용 (변경 사항)

  • 가게 상세 페이지 태그 추가
  • 응원 카드 디자인 변경

📸 스크린샷

14056

💡 설명


🗣️ 리뷰어에게 전달 사항


📍 트러블 슈팅

Summary by CodeRabbit

  • New Features
    • 매장 정보에 태그 표시(아이콘 포함) 및 추가 태그 개수(+N) 노출
    • 응원 카드에 태그 표시 및 내용 확장/접기 인터랙션 추가
    • 매장 등록 버튼(CTA) 추가 및 스켈레톤에도 반영
  • Style
    • 응원 카드 전면 리디자인: 테마 색상(옐로/핑크/블루), 헤더/콘텐츠 영역 분리, 아바타/레이아웃 개선
    • 태그 pill 스타일(라운드, 보더, 아이콘 크기) 적용
  • Refactor
    • 매장 정보와 태그 동시 로딩으로 초기 표시 경험 개선
  • Revert
    • 응원 영역의 기존 “가게 응원하기” 버튼 제거

@coderabbitai
Copy link

coderabbitai bot commented Aug 21, 2025

Walkthrough

가게 태그 조회 기능을 추가하고, 쿼리 키/옵션을 확장했다. StoreCheers 컴포넌트는 테마/태그 중심으로 UI를 재구성했고, 관련 CSS와 유틸을 신설했다. StoreInfo는 상세+태그를 동시 조회하고 태그 표시 및 등록 버튼을 추가했다. 타입에 tags 필드와 응답 타입을 도입했다.

Changes

Cohort / File(s) Change summary
API Types
src/app/(store)/_api/shop/shop.types.ts
StoreCheerstags: string[] 추가. export type StoreTagsResponse { tags: string[] } 신설.
API Client
src/app/(store)/_api/shop/shop.api.ts
StoreTagsResponse 임포트. getStoreTags(storeId) GET api/shops/${storeId}/tags 추가(응답 JSON 반환).
Query Layer
src/app/(store)/_api/shop/shop.queries.ts
storeQueryKeys.tags(storeId)storeTagsQueryOptions(storeId) 추가. getStoreTags 연동.
StoreCheers UI/Styles
.../StoreCheers/StoreCheers.tsx, .../StoreCheers/StoreCheers.css.ts, .../StoreCheers/types.ts, .../StoreCheers/utils.ts
카드 헤더/콘텐츠 테마 배경 도입, 태그 렌더링(+N 표시), 확장 애니메이션, 아바타/레이아웃 갱신. CSS에 카드/헤더/아바타/태그 스타일 추가 및 구 스타일 제거. Theme 타입 및 테마 유틸 3종 추가.
StoreInfo Update
.../StoreInfo/StoreInfo.tsx
useSuspenseQueries로 상세+태그 동시 조회. 첫 태그 및 추가 개수 표시. StoreRegisterButton 도입(콘텐츠/스켈레톤 반영). 레이아웃/아이콘 정리.

Sequence Diagram(s)

sequenceDiagram
  actor UI as StoreInfo UI
  participant Q as React Query (useSuspenseQueries)
  participant API as shop.api
  participant S as Server

  UI->>Q: trigger storeDetailQuery + storeTagsQuery
  Q->>API: getStoreDetail(storeId)
  Q->>API: getStoreTags(storeId)
  API->>S: GET /api/shops/{id}
  API->>S: GET /api/shops/{id}/tags
  S-->>API: store detail JSON
  S-->>API: { tags: string[] }
  API-->>Q: parsed results
  Q-->>UI: {detail, tags}
  UI->>UI: render title/intro + Tag(1st) + "+N" if any
Loading
sequenceDiagram
  actor UI as StoreCheers List
  participant Q as React Query Cache
  participant Data as StoreCheers Data
  participant Utils as Theme Utils

  UI->>Q: read cheers (includes tags[])
  Q-->>UI: cheers[]
  loop per cheer
    UI->>Utils: getHeaderBackgroundColor(theme)
    Utils-->>UI: hex color
    UI->>Utils: getContentBackgroundColor(theme)
    Utils-->>UI: hex color
    UI->>UI: render Tag icons (+N), expandable text
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

꼬닥꼬닥 귀를 세워 태그를 주웠지, 🥕
노랑 분홍 파랑으로 카드가 물들었지.
+N 뒤에 숨은 이야기, 살짝 펼쳐 보기—
두 쿼리 함께 달려와 상점이 웃었지.
등록 버튼 토닥토닥, 길도 함께 열렸지.
오늘의 깃발은 “tags!” 폴짝, 폴짝—띠링!

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 Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/PRODUCT-274

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot requested a review from wkdtnqls0506 August 21, 2025 15:10
@vercel
Copy link
Contributor

vercel bot commented Aug 21, 2025

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

Project Deployment Preview Comments Updated (UTC)
26th-web-team-1-fe Ready Ready Preview Comment Aug 21, 2025 3:10pm

@github-actions
Copy link

🎨 Storybook Preview: https://685a32a1c0bbd269fdb67af4-slgnlfcwou.chromatic.com/
🔗 Chromatic Build: https://www.chromatic.com/build?appId=685a32a1c0bbd269fdb67af4&number=244
🕖 Updated at: 2025년 08월 22일 00시 10분 52초

Copy link

@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: 3

🧹 Nitpick comments (14)
src/app/(store)/stores/[storeId]/_components/StoreCheers/types.ts (1)

1-1: 테마 상수 추출 및 타입 파생 제안

Theme 유니온 타입과 ["yellow", "pink", "blue"] 배열이 아래 세 곳에서 중복 정의되어 있어 변경 시 누락·불일치 위험이 있습니다. 단일 소스(상수 배열)에서 관리하고, 타입과 사용 로직을 모두 파생하도록 리팩터링을 제안드립니다.

· 중복 위치

  • src/app/(store)/stores/[storeId]/_components/StoreCheers/types.ts
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts

· 리팩터링 방안

  1. types.ts에 THEMES 상수를 정의 및 내보내기
  2. Theme 타입을 typeof THEMES[number]로 파생
  3. StoreCheers.tsx와 utils.ts에서 직접 하드코딩된 배열/유니온 대신 THEMESTheme을 import
--- a/src/app/(store)/stores/[storeId]/_components/StoreCheers/types.ts
+++ b/src/app/(store)/stores/[storeId]/_components/StoreCheers/types.ts
-export type Theme = "yellow" | "pink" | "blue";
+export const THEMES = ["yellow", "pink", "blue"] as const;
+export type Theme = typeof THEMES[number];
--- a/src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx
- const THEMES = ["yellow", "pink", "blue"] as const;
- type Theme = (typeof THEMES)[number];
+ import { THEMES, Theme } from "./types";
--- a/src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts
- export const getCheerCardTheme = (memberId: number) => {
-   return ["yellow", "pink", "blue"][memberId % 3];
- };
+ export const getCheerCardTheme = (memberId: number) =>
+   THEMES[memberId % THEMES.length];

- export const getHeaderBackgroundColor = (colorName: Theme) => {
+ import { THEMES, Theme } from "./types";
+ export const getHeaderBackgroundColor = (colorName: Theme) => {
    switch (colorName) {
      case "yellow":
        return "#fceb9c";
      /* … */

이렇게 하면 색상 목록이 변경돼도 한 군데만 수정하면 되므로 중복·드리프트를 방지할 수 있습니다.

src/app/(store)/_api/shop/shop.types.ts (2)

18-19: StoreCheers에 tags 추가는 합리적 — 런타임 안전성(빈 배열 보장)만 확인해 주세요

타입상 string[]로 강제하면 사용 측에서 map/filter 시 깔끔합니다. 다만 BE가 해당 필드를 누락하거나 null을 반환하는 경우가 있는지 확인이 필요합니다. 그런 케이스가 가능하다면 API 계층에서 tags ?? []로 normalize 하거나 타입을 tags?: string[]로 완화 후 UI에서 기본값을 보장하는 방식을 권장합니다. 또한 불변 의도를 드러내려면 readonly string[] 도 고려해 볼 수 있습니다.

불변 표현을 원하신다면 다음과 같이 변경 가능합니다:

-  tags: string[];
+  readonly tags: string[];

가능하다면 getStoreCheers 응답 파싱 시 다음과 같이 기본값을 보장해 주세요(참고용, 파일 외 코드):

// ex) api 계층에서
json.cheers = json.cheers.map(c => ({ ...c, tags: c.tags ?? [] }));

39-41: StoreTagsResponse 정의 적절 — 향후 확장성을 위해 주석/스키마 명시 고려

{ tags: string[] } 심플하고 명확합니다. 추후 태그의 정렬 기준, 총 개수(total) 등이 필요하면 API/타입 확장을 쉽게 할 수 있도록 JSDoc으로 스키마 의도를 남겨 두면 좋겠습니다.

src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.css.ts (3)

16-21: 헤더 레이아웃 OK — 텍스트/아이콘 대비는 테마 색상에서만 확인

Flex 정렬/패딩 구성 무난합니다. 테마 배경 위에서의 텍스트/아이콘 대비는 실제 테마 색상 적용 시 한 번만 점검해 주세요.


33-44: 긴 단어 줄바꿈 방지 보완 제안

-webkit-line-clamp 처리 외에 긴 토큰(해시/URL/연속문자)로 인한 오버플로우를 막기 위해 overflowWrap/wordBreak를 추가해 두면 안전합니다.

 export const cheerCardContentText = style({
   width: "100%",
+  overflowWrap: "anywhere",
+  wordBreak: "break-word",
   selectors: {
     "&[data-long-text='true'][data-expanded='false']": {
       display: "-webkit-box",
       WebkitLineClamp: 4,
       WebkitBoxOrient: "vertical",
       overflow: "hidden",
       textOverflow: "ellipsis",
     },
   },
 });

46-54: 색상 하드코딩(#fff, #f0f0f0) 대신 디자인 토큰/변수 사용 권장

다크 모드/테마 확장 시 유지보수가 어려워질 수 있습니다. 가능하다면 @/styles의 색상 토큰 또는 CSS 변수(semantic tokens)로 치환해 주세요.

src/app/(store)/_api/shop/shop.queries.ts (1)

49-53: 태그 쿼리 옵션 기본형 완성 — 캐시 전략 옵션(선택)을 고려해보세요

태그 변동 빈도가 낮다면 staleTime/gcTime을 부여해 네트워크 호출을 줄일 수 있습니다. 또한 방어적으로 enabled: storeId > 0도 고려 가능합니다.

 export const storeTagsQueryOptions = (storeId: number) =>
   queryOptions({
     queryKey: storeQueryKeys.tags(storeId),
     queryFn: () => getStoreTags(storeId),
+    // 선택: 태그 변동이 잦지 않다면 캐시 기간을 늘려 네트워크 절약
+    staleTime: 5 * 60 * 1000, // 5분
+    gcTime: 10 * 60 * 1000,   // 10분
+    // 선택: 잘못된 storeId(0 등) 방지
+    enabled: storeId > 0,
   });
src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts (1)

8-19: switch 문을 매핑 상수로 단순화하고 팔레트 단일 소스화 고려

동일한 패턴의 switch 문 두 개를 상수 매핑으로 단순화하면 가독성과 변경 용이성이 좋아집니다. 더 나아가 디자인 토큰(예: CSS 변수, theme 객체)을 사용해 팔레트를 단일 소스에서 관리하는 것도 권장합니다.

-export const getHeaderBackgroundColor = (colorName: Theme) => {
-  switch (colorName) {
-    case "yellow":
-      return "#fceb9c";
-    case "pink":
-      return "#fabdb8";
-    case "blue":
-      return "#b2dfff";
-    default:
-      return "#fceb9c";
-  }
-};
-
-export const getContentBackgroundColor = (colorName: Theme) => {
-  switch (colorName) {
-    case "yellow":
-      return "#fef8dd";
-    case "pink":
-      return "#fde5e3";
-    case "blue":
-      return "#e0f2ff";
-    default:
-      return "#fef8dd";
-  }
-};
+const HEADER_BG: Record<Theme, string> = {
+  yellow: "#fceb9c",
+  pink: "#fabdb8",
+  blue: "#b2dfff",
+};
+const CONTENT_BG: Record<Theme, string> = {
+  yellow: "#fef8dd",
+  pink: "#fde5e3",
+  blue: "#e0f2ff",
+};
+
+export const getHeaderBackgroundColor = (colorName: Theme) =>
+  HEADER_BG[colorName];
+
+export const getContentBackgroundColor = (colorName: Theme) =>
+  CONTENT_BG[colorName];

Also applies to: 21-32

src/app/(store)/stores/[storeId]/_components/StoreInfo/StoreInfo.tsx (3)

53-56: 중복 key 제거 및 안정적인 key 사용

동일한 index 기반 key가 래퍼 div와 Image 둘 다에 지정되어 있습니다. 내부 Image의 key는 불필요하며, 가능하면 URL 자체를 key로 사용하는 것이 더 안정적입니다.

-        {imageUrls.map((image, index) => (
-          <div key={index} className={styles.storeInfoImageWrapper}>
-            <Image
-              key={index}
+        {imageUrls.map((image, index) => (
+          <div key={image || index} className={styles.storeInfoImageWrapper}>
+            <Image
               className={styles.storeInfoImage}
               src={image}
               fill

71-80: useSuspenseQueries 결과 튜플 타입 보존

queries 배열에 as const를 붙여 결과 배열의 튜플 타입을 보존하면 구조 분해 시 타입 안정성이 향상됩니다.

-  ] = useSuspenseQueries({
-    queries: [storeDetailQueryOptions(storeId), storeTagsQueryOptions(storeId)],
-  });
+  ] = useSuspenseQueries({
+    queries: [storeDetailQueryOptions(storeId), storeTagsQueryOptions(storeId)] as const,
+  });

82-85: 표시 태그 개수 계산 로직 보정: 존재하지 않는 태그까지 카운트하는 문제

+{tags.length - 1}는 ALL_TAGS에 존재하지 않는 태그명까지 포함해 개수를 표시할 수 있습니다. 매칭된 태그 기준으로 개수를 계산하세요.

-  const firstTag = head(ALL_TAGS.filter(tag => tags.includes(tag.name)));
-
-  const showAdditionalTags = tags.length > 1;
+  const matchedTags = ALL_TAGS.filter(tag => tags.includes(tag.name));
+  const firstTag = head(matchedTags);
+  const additionalTagsCount = Math.max(0, matchedTags.length - (firstTag ? 1 : 0));
+  const showAdditionalTags = additionalTagsCount > 0;
-              <Tag variant='primaryLow'>
-                <Text as='span' typo='label1Sb' color='transparent'>
-                  +{tags.length - 1}
-                </Text>
-              </Tag>
+              <Tag variant='primaryLow'>
+                <Text as='span' typo='label1Sb' color='transparent'>
+                  +{additionalTagsCount}
+                </Text>
+              </Tag>

Also applies to: 136-141

src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx (3)

5-6: 불필요한 라이브러리 유틸 의존도 축소: native slice 사용

간단한 슬라이싱에 es-toolkit/compatslice는 과합니다. 네이티브 Array.prototype.slice로 대체하여 번들 크기와 의존성을 줄여주세요.

-import { slice } from "es-toolkit/compat";
@@
-  const visibleTags = isExpanded ? selectedTags : slice(selectedTags, 0, 2);
+  const visibleTags = isExpanded ? selectedTags : selectedTags.slice(0, 2);

Also applies to: 115-116


42-44: Theme/THEMES 중복 정의 제거로 단일 소스화 권장

Theme 타입이 ./types에 존재하고, 유사한 상수/로직이 utils.ts에도 있습니다. 현재 파일의 THEMES/로컬 Theme 정의는 중복이며 드리프트 위험이 있습니다. 공통 상수/타입을 한 곳(예: types.ts 또는 utils.ts)에서 export해 사용하도록 리팩터링을 제안합니다. 또한 카드 테마 산정은 getCheerCardTheme로 통일하면 좋습니다.

-const THEMES = ["yellow", "pink", "blue"] as const;
-type Theme = (typeof THEMES)[number];
+// 권장: ./types 에서 Theme, ./utils 에서 getCheerCardTheme 재사용
+// import { type Theme } from "./types";
+// import { getCheerCardTheme } from "./utils";

또한 아래 매핑에서도 동일 상수를 직접 접근하기보다는 유틸을 호출하도록 변경을 권장합니다.

-            theme={THEMES[index % THEMES.length] as Theme}
+            theme={getCheerCardTheme(index)}

137-141: Avatar memberId 계산 단순화

findIndex 대신 indexOf가 의도를 더 명확히 드러냅니다. (향후 THEMES를 외부 상수로 단일화 시 함께 적용)

-        <Avatar
-          // TODO: 추후 theme 지정 가능하게끔 수정
-          memberId={THEMES.findIndex(t => t === theme)}
-          className={styles.cheerCardAvatar}
-        />
+        <Avatar
+          // TODO: 추후 theme 지정 가능하게끔 수정
+          memberId={THEMES.indexOf(theme)}
+          className={styles.cheerCardAvatar}
+        />
📜 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.

📥 Commits

Reviewing files that changed from the base of the PR and between bb06fb3 and d7a1479.

📒 Files selected for processing (8)
  • src/app/(store)/_api/shop/shop.api.ts (2 hunks)
  • src/app/(store)/_api/shop/shop.queries.ts (3 hunks)
  • src/app/(store)/_api/shop/shop.types.ts (2 hunks)
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.css.ts (2 hunks)
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx (5 hunks)
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/types.ts (1 hunks)
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts (1 hunks)
  • src/app/(store)/stores/[storeId]/_components/StoreInfo/StoreInfo.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/nextjs-folder-structure.mdc)

All files must use TypeScript.

Files:

  • src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts
  • src/app/(store)/_api/shop/shop.types.ts
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/types.ts
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx
  • src/app/(store)/stores/[storeId]/_components/StoreInfo/StoreInfo.tsx
  • src/app/(store)/_api/shop/shop.api.ts
  • src/app/(store)/_api/shop/shop.queries.ts
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.css.ts
{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/[storeId]/_components/StoreCheers/StoreCheers.tsx
  • src/app/(store)/stores/[storeId]/_components/StoreInfo/StoreInfo.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.ts extension (e.g., Button.css.ts, theme.css.ts).

Files:

  • src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.css.ts
🧬 Code graph analysis (5)
src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts (1)
src/app/(store)/stores/[storeId]/_components/StoreCheers/types.ts (1)
  • Theme (1-1)
src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx (4)
src/app/(store)/_api/shop/shop.queries.ts (1)
  • storeCheersQueryOptions (27-31)
src/constants/tag.constants.ts (1)
  • ALL_TAGS (84-84)
src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.css.ts (1)
  • tag (46-54)
src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts (2)
  • getHeaderBackgroundColor (8-19)
  • getContentBackgroundColor (21-32)
src/app/(store)/stores/[storeId]/_components/StoreInfo/StoreInfo.tsx (2)
src/app/(store)/_api/shop/shop.queries.ts (2)
  • storeDetailQueryOptions (21-25)
  • storeTagsQueryOptions (49-53)
src/constants/tag.constants.ts (1)
  • ALL_TAGS (84-84)
src/app/(store)/_api/shop/shop.api.ts (2)
src/app/(store)/_api/shop/shop.types.ts (1)
  • StoreTagsResponse (39-41)
src/lib/api/client.ts (1)
  • http (69-74)
src/app/(store)/_api/shop/shop.queries.ts (1)
src/app/(store)/_api/shop/shop.api.ts (1)
  • getStoreTags (79-83)
⏰ 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 (12)
src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.css.ts (5)

10-14: 카드 컨테이너 스타일 구성 합리적입니다

풀폭, radius, overflow 설정이 컴포넌트 구조와 맞습니다.


23-26: 아바타 사이징 무난합니다

일관된 정사각 크기 지정으로 레이아웃이 안정적입니다.


28-31: 컨텐츠 패딩 조정 좋습니다

내부 여백이 카드 구조와 잘 어울립니다.


56-58: 아이콘 폰트 사이즈 지정 문제 없습니다

필요 시 상수화(토큰화)만 고려해 주세요.


3-3: radius[160] 토큰 정의 확인 완료
src/styles/radius.css.ts에서 160: "16px" 항목이 명시되어 있어 radius[160] 사용 시 타입 안전성이나 런타임 에러 우려 없습니다.

src/app/(store)/_api/shop/shop.api.ts (2)

8-9: 타입 임포트 추가 적절

응답 타입과 구현이 정합합니다.


74-83: 가게 태그 API 추가 — JSDoc @param 표기 및 인증 여부 확인 요청

간결하게 잘 구현해 주셨습니다. 아래 두 가지만 확인 부탁드립니다:

  • JSDoc 표기: @params → 표준 @param으로 수정
  • 현재 getStoreTagshttp.get(비인증) 사용 중입니다. 해당 엔드포인트가 인증 API라면 authHttp.get으로 변경해 주세요. 백엔드 API 명세를 참고해 인증 요구 여부를 확인 부탁드립니다.

수정 예시:

 /**
  * 가게별 태그 조회 API
- * @params storeId 조회할 가게 ID
+ * @param storeId 조회할 가게 ID
  * @returns 가게별 태그 정보
  */
 export const getStoreTags = async (
   storeId: number
 ): Promise<StoreTagsResponse> => {
-  return await http.get(`api/shops/${storeId}/tags`).json<StoreTagsResponse>();
+  return await http.get(`api/shops/${storeId}/tags`).json<StoreTagsResponse>();
 };
src/app/(store)/_api/shop/shop.queries.ts (2)

8-9: getStoreTags 임포트 OK

쿼리 옵션에서 올바르게 사용되고 있습니다.


18-18: 쿼리 키(tags) 구성 적절

["store", storeId, "tags"] 형태로 다른 키들과 일관적입니다.

src/app/(store)/stores/[storeId]/_components/StoreInfo/StoreInfo.tsx (2)

151-159: CTA 컴포넌트 추가 LGTM

등록 버튼 컴포넌트를 분리해 재사용성과 가독성이 좋아졌습니다. 라우팅 쿼리도 명확합니다.


125-131: Next/Image 원격 아이콘 도메인 허용 설정 검토

next.config.tsimages.remotePatterns에 이미 다음 S3 호스트들이 등록되어 있습니다.

  • eatda-storage-dev.s3.ap-northeast-2.amazonaws.com
  • eatda-storage-prod.s3.ap-northeast-2.amazonaws.com

src/app/(store)/stores/[storeId]/_components/StoreInfo/StoreInfo.tsx (125–131라인)에서 firstTag.iconUrl을 사용하고 있으므로, 해당 URL이 위에 등록된 도메인을 사용하고 있는지 확인해 주세요.
만약 다른 외부 도메인의 URL이 전달될 가능성이 있다면, next.config.tsimages.remotePatterns 또는 images.domains에 누락된 호스트를 추가해야 런타임 이미지 차단을 방지할 수 있습니다.

src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx (1)

22-23: motion/react vs framer-motion 패키지 호환성 및 whileTap 지원 여부 확인 필요

검토 결과

  • package.json에 "motion": "^12.23.11" 의존성이 선언되어 있고,
  • src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx 에서 import { motion } from "motion/react"; 을 사용 중입니다.

따라서

  1. Motion One(motion/react) React 패키지가 <motion.div> 등의 컴포넌트에서 whileTap 프로퍼티를 지원하는지 공식 문서를 통해 반드시 확인해주세요.
  2. 만약 whileTap 사용이 필요하고 Motion One에서 지원되지 않는다면, framer-motion 패키지로 교체가 필요합니다.
    • 이 경우 npm install framer-motion 또는 yarn add framer-motion 후, import를 from "framer-motion" 으로 변경
    • 변경 후 런타임 경고 없이 정상 동작하는지 테스트 바랍니다.

Comment on lines +166 to +173
<TextButton
size='small'
variant='assistive'
onClick={() => setIsExpanded(!isExpanded)}
>
{isExpanded ? "접기" : "더보기"}
</TextButton>
)}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

카드 전체 onClick과 “더보기/접기” 버튼 onClick이 중복 호출되어 토글이 무력화됨

부모 motion.div와 자식 TextButton이 모두 토글을 수행하여 버튼 클릭 시 두 번 토글(원상복귀)됩니다. 자식 클릭에서 이벤트 전파를 중단하고, 키보드 접근성도 함께 보강해주세요.

-    <motion.div
+    <motion.div
       className={styles.cheerCard}
-      onClick={() => setIsExpanded(!isExpanded)}
+      onClick={() => setIsExpanded(prev => !prev)}
       role='button'
       tabIndex={0}
+      aria-expanded={isExpanded}
+      onKeyDown={e => {
+        if (e.key === "Enter" || e.key === " ") {
+          e.preventDefault();
+          setIsExpanded(prev => !prev);
+        }
+      }}
       transition={{ duration: 0.3 }}
       whileTap={{ scale: 0.99 }}
     >
@@
-            <TextButton
+            <TextButton
               size='small'
               variant='assistive'
-              onClick={() => setIsExpanded(!isExpanded)}
+              onClick={e => {
+                e.stopPropagation();
+                setIsExpanded(prev => !prev);
+              }}
             >

Also applies to: 123-131

🤖 Prompt for AI Agents
In src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx
around lines 166-173 (and also apply the same fix at 123-131), the card wrapper
and the TextButton both toggle expansion so clicking the button triggers both
and cancels the toggle; modify the TextButton handlers to accept the event and
call event.stopPropagation() in onClick, and add an onKeyDown handler that
prevents default and stops propagation while toggling on Enter/Space so keyboard
users work correctly; ensure the toggle logic is invoked only once from the
button handler and keep the parent card click untouched.

Comment on lines +3 to +5
export const getCheerCardTheme = (memberId: number) => {
return ["yellow", "pink", "blue"][memberId % 3];
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Theme 반환값이 undefined가 될 수 있는 여지 제거 + 명시적 반환 타입 지정

memberId % 3가 음수이거나 정수가 아닐 경우 인덱싱 결과가 undefined가 될 수 있습니다. 또한 반환 타입이 암묵적이라 호출부 타입 안정성이 떨어집니다. 안전한 인덱스 계산과 Theme 반환 타입을 명시해 주세요.

-export const getCheerCardTheme = (memberId: number) => {
-  return ["yellow", "pink", "blue"][memberId % 3];
-};
+export const getCheerCardTheme = (memberId: number): Theme => {
+  const themes: Theme[] = ["yellow", "pink", "blue"];
+  const safeIndex = Math.abs(Math.trunc(memberId)) % themes.length;
+  return themes[safeIndex];
+};
📝 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.

Suggested change
export const getCheerCardTheme = (memberId: number) => {
return ["yellow", "pink", "blue"][memberId % 3];
};
export const getCheerCardTheme = (memberId: number): Theme => {
const themes: Theme[] = ["yellow", "pink", "blue"];
const safeIndex = Math.abs(Math.trunc(memberId)) % themes.length;
return themes[safeIndex];
};
🤖 Prompt for AI Agents
In src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts around
lines 3 to 5, the function can return undefined for non-integer or negative
memberId and lacks an explicit return type; ensure you validate and normalize
memberId to a safe 0..2 index (e.g. coerce to a finite integer with Math.floor,
then compute ((index % 3) + 3) % 3 to handle negatives), use that index to pick
from the themes array, add an explicit return type Theme (import or declare the
Theme union if needed), and provide a fallback theme as a last-resort default so
the function never returns undefined.

💡 Verification agent

❓ Verification inconclusive

유틸이 실제로 사용되는지 확인 필요

이번 PR에서 getCheerCardTheme가 사용되지 않는 것으로 보입니다. 미사용 유틸은 제거하거나 실제 사용처(예: StoreCheers.tsx)로 연결해 주세요.

다음 스크립트로 사용 여부를 확인할 수 있습니다.


🏁 Script executed:

#!/bin/bash
# getCheerCardTheme 사용처 검색
rg -nP '\bgetCheerCardTheme\s*\(' -S --type ts --type tsx -C2

Length of output: 91


getCheerCardTheme의 실제 사용처를 glob 옵션으로 다시 확인해 보겠습니다.
다음 스크립트를 실행해 주세요.


🏁 Script executed:

#!/bin/bash
# getCheerCardTheme 사용처 검색 (glob)
rg -nP '\bgetCheerCardTheme\s*\(' -n --glob '*.ts' --glob '*.tsx' -C2

Length of output: 71


getCheerCardTheme 사용처 없음 – 제거 또는 연결 필요

현재 src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts에서 정의된 getCheerCardTheme 함수가 코드베이스 내 어디에서도 호출되지 않습니다.
불필요한 dead code 방지를 위해 아래 중 하나를 선택해 주세요:

  • 유틸이 더 이상 필요하지 않다면 해당 함수 및 임포트를 삭제
  • 실제로 사용하려는 컴포넌트(예: StoreCheers.tsx)에서 호출하도록 연결

대상 위치
• src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts:3-5 (export const getCheerCardTheme …)

🤖 Prompt for AI Agents
In src/app/(store)/stores/[storeId]/_components/StoreCheers/utils.ts lines 3-5,
the exported getCheerCardTheme function is not used; either remove it and any
related imports/exports to eliminate dead code, or wire it into the StoreCheers
component (e.g., import getCheerCardTheme in
src/app/(store)/stores/[storeId]/_components/StoreCheers/StoreCheers.tsx and
call getCheerCardTheme(memberId) to compute the card theme and apply it to the
card’s className/props where member cards are rendered). Ensure after the change
there are no unused exports or linter errors.

Copy link
Contributor

@wkdtnqls0506 wkdtnqls0506 left a comment

Choose a reason for hiding this comment

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

너무너무 숙오하셨읍니다~!
2차 스프린트 마무리가 눈 앞에..✨✨✨

Comment on lines +46 to +54
export const tag = style({
display: "flex",
alignItems: "center",
gap: "0.4rem",
padding: "0.6rem 0.8rem",
backgroundColor: "#ffffff",
borderRadius: "999px",
border: "1px solid #f0f0f0",
});
Copy link
Contributor

Choose a reason for hiding this comment

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

P3:
옹! 뭔가 사용안하는 스타일인거 같아서 제거해도 될 거 같은 느낌!

<VStack gap={12}>
<HStack align='center' gap={8}>
<Avatar memberId={memberId} className={styles.cheerCardProfileImage} />
<motion.div
Copy link
Contributor

Choose a reason for hiding this comment

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

motion까쥐 🤩🤩

Copy link
Contributor

Choose a reason for hiding this comment

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

오 배경 색상 지정하는 함수 꽤나 쓰이는거 같은데, 이것도 분리해서 함께 사용하면 좋을 거 같네유 ~ . ~

@Seojunhwan Seojunhwan merged commit 5d1c82f into main Aug 22, 2025
9 checks passed
@Seojunhwan Seojunhwan deleted the feature/PRODUCT-274 branch August 22, 2025 13:44
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