Skip to content

Api(client): 카테고리별 아티클 조회 API 연결 & 아티클 없는 경우 UI 분기 추가#91

Merged
constantly-dev merged 13 commits intodevelopfrom
api/#87/article-for-category-api
Sep 13, 2025
Merged

Api(client): 카테고리별 아티클 조회 API 연결 & 아티클 없는 경우 UI 분기 추가#91
constantly-dev merged 13 commits intodevelopfrom
api/#87/article-for-category-api

Conversation

@constantly-dev
Copy link
Member

@constantly-dev constantly-dev commented Sep 13, 2025

📌 Related Issues

관련된 Issue를 태그해주세요. (e.g. - close #25)

📄 Tasks

  • 카테고리별 아티클 조회 API 연결
  • 아티클 없는 경우 UI 분기 추가

⭐ PR Point (To Reviewer)

크게 추가된 것은 없는데 카테고리별 아티클 조회 API 연결하고 아티클 없는 경우 각 fallback UI 추가했습니다!
그리고 카테고리별 아티클 조회도 전체/안 읽음 이 두개 상태에 따라서 다른 데이터가 와야하는데 아직 서버에 반영이 안 됐다고 해서 서버 수정 및 배포 되면 새로 이슈 파서 그 부분 적용하겠습니다!

그래서 categoryArticles를 에러 안 뜨게 console에 임시로 넣어놨습니다! 그리고 정민님이 작업하신 수정/삭제 popup UI 충돌나서 rebase로 해결하고 올렸습니다.

📷 Screenshot

2025-09-13.8.06.29.mov

Summary by CodeRabbit

  • New Features
    • 북마크: 카테고리별 보기(주소창 파라미터로 category 처리), 헤더에 선택 카테고리 표시, 카드에 카테고리·날짜 노출, 빈 결과용 NoArticles 컴포넌트 추가, 카테고리 기반 API/훅(useGetCategoryBookmarkArticles) 추가.
    • 리마인드: 읽음/안읽음 뱃지 전환에 따른 별도 리스트 렌더링 및 빈 상태용 NoReadArticles/NoUnreadArticles 컴포넌트 추가.
    • 사이드바/네비: 카테고리 선택 시 이름을 포함한 쿼리로 이동하도록 변경.
    • 디자인 시스템: 아이콘 목록 재정렬 및 'black' 색상 옵션 추가.

@coderabbitai
Copy link

coderabbitai bot commented Sep 13, 2025

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

📥 Commits

Reviewing files that changed from the base of the PR and between 633efc1 and 317454f.

📒 Files selected for processing (2)
  • apps/client/src/layout/Layout.tsx (1 hunks)
  • packages/design-system/src/components/card/BaseCard.tsx (1 hunks)
 ____________________________________________
< Forcing bugs to surrender unconditionally. >
 --------------------------------------------
  \
   \   \
        \ /\
        ( )
      .( o ).

Tip

CodeRabbit can suggest fixes for GitHub Check annotations.

Configure reviews.tools.github-checks in your project's settings in CodeRabbit to adjust the time to wait for GitHub Checks to complete.

Walkthrough

카테고리별 북마크 조회 API/훅/타입을 추가하고 MyBookmark/Remind 페이지에 카테고리 및 읽음 상태 기반 UI 분기와 빈 상태 컴포넌트를 도입했다. 사이드바의 selectCategory 시그니처와 라우팅 쿼리 형식이 변경되었고, 디자인 시스템의 아이콘 목록 및 Icon 색상 타입이 업데이트됐다.

Changes

Cohort / File(s) Summary
MyBookmark UI 업데이트
apps/client/src/pages/myBookmark/MyBookmark.tsx, apps/client/src/pages/myBookmark/components/NoArticles/NoArticles.tsx
카테고리 쿼리 파라미터 사용, 카테고리별 쿼리 호출·conditional 렌더링(전체/언리드/카테고리), Card에 category prop 전달, 빈 상태 컴포넌트 추가 및 레이아웃 조정.
MyBookmark API/쿼리/타입 확장
apps/client/src/pages/myBookmark/apis/axios.ts, apps/client/src/pages/myBookmark/apis/queries.ts, apps/client/src/pages/myBookmark/types/api.ts
getCategoryBookmarkArticles 함수와 useGetCategoryBookmarkArticles 훅 추가, 타입에 Category 필드·응답 속성 확장 및 CategoryBookmarkArticleResponse alias 추가.
Remind 페이지 분기/빈 상태 UI
apps/client/src/pages/remind/Remind.tsx, apps/client/src/pages/remind/components/noReadArticles/NoReadArticles.tsx, apps/client/src/pages/remind/components/noUnreadArticles/NoUnreadArticles.tsx
배지 타입을 `'read'
Remind API 파라미터 수정
apps/client/src/pages/remind/apis/axios.ts
리마인드 API 쿼리 파라미터 이름을 readStatusread-status로 변경.
사이드바 내비게이션 변경
apps/client/src/shared/components/sidebar/Sidebar.tsx, apps/client/src/shared/hooks/useSidebarNav.ts
selectCategory(id, name) 시그니처로 확장, 라우팅 쿼리를 /my-bookmarks?id={id}&category={name}로 변경(이전 categoryId 쿼리 구조 변경).
레이아웃 import 정리
apps/client/src/layout/Layout.tsx
Sidebar import를 경로 별칭(@shared/components/sidebar/Sidebar)으로 변경.
디자인 시스템 아이콘/타입
packages/design-system/src/icons/iconNames.ts, packages/design-system/src/icons/components/icon.tsx
iconNames 배열 내용 및 순서 변경(예: ic_extension, main_header_logo 위치 조정 등) 및 IconColor 타입에 black 추가.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User as 사용자
  participant Sidebar as 사이드바
  participant NavHook as useSidebarNav
  participant Router as Router
  participant MyBookmark as MyBookmark 페이지
  participant Query as ReactQuery
  participant API as /api

  User->>Sidebar: 카테고리 클릭(id, name)
  Sidebar->>NavHook: selectCategory(id, name)
  NavHook->>Router: push /my-bookmarks?id={id}&category={name}
  Router-->>MyBookmark: 쿼리파라미터 전달(id, category)
  MyBookmark->>Query: useGetCategoryBookmarkArticles(id, page, size)
  Query->>API: GET /api/v1/articles/category?categoryId={id}&page={page}&size={size}
  API-->>Query: CategoryBookmarkArticleResponse
  Query-->>MyBookmark: categoryArticles
  alt 결과 있음
    MyBookmark-->>User: 카드 그리드(카테고리 표기)
  else 비어있음
    MyBookmark-->>User: NoArticles 표시
  end
Loading
sequenceDiagram
  autonumber
  actor User as 사용자
  participant Remind as Remind 페이지
  participant Query as ReactQuery
  participant API as /api
  participant UI as 빈상태컴포넌트

  User->>Remind: 배지 선택(read / notRead)
  alt read
    Remind->>Query: useGetRemindArticles('read')
    Query->>API: GET /remind?read-status=read
  else notRead
    Remind->>Query: useGetRemindArticles('notRead')
    Query->>API: GET /remind?read-status=notRead
  end
  API-->>Query: { articles }
  Query-->>Remind: data
  alt articles 존재
    Remind-->>User: 리스트 렌더
  else 없음
    Remind->>UI: NoReadArticles / NoUnreadArticles
    UI-->>User: 빈 상태 표시
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

Suggested reviewers

  • jllee000
  • jjangminii

Poem

폴짝폴짝 토끼가 말하네, 카테고리 따라 북마크 꽃길로 🐇
빈 숲이면 칩피와 인사하고 ✨
읽음·안읽음 배지로 길을 가르고
아이콘 한 줄 더해 반짝임을 더하고
오늘도 북마크 숲에서 즐겁게 폴짝! 🎶

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch api/#87/article-for-category-api

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.

Pre-merge checks

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Linked Issues Check ⚠️ Warning 이 PR은 링크된 이슈들 중 [#87]의 요구사항(카테고리별 아티클 API 연동 및 빈 결과 UI 처리)을 axios 함수, react-query 훅, MyBookmark 뷰 수정 및 NoArticles 컴포넌트 추가로 구현하여 충족했지만 링크에 포함된 [#25](디자인 시스템 Progress 컴포넌트 구현)은 본 PR에서 구현되지 않았고 관련 파일 변경도 보이지 않으므로 해당 요구사항은 미충족입니다; 따라서 모든 연결 이슈의 요구사항을 완전히 만족하지 않으므로 연관 이슈 준수 검사 결과는 실패입니다. 해결 방안: PR에 실제로 연관된 이슈([#87])만 연결하도록 정리하거나, [#25]의 Progress 컴포넌트를 포함하려면 별도 PR로 분리해 제출하고 두 작업을 분리된 검토 단위로 관리하세요.
Out of Scope Changes Check ⚠️ Warning PR 목적(#[87] 카테고리별 아티클 API 연동 및 빈 상태 UI)과 무관한 변경들이 PR에 포함되어 있습니다; 구체적으로 packages/design-system/src/icons/iconNames.ts 및 packages/design-system/src/icons/components/icon.tsx의 아이콘/타입 변경, Remind 페이지 관련 신규 컴포넌트 및 렌더링 로직(apps/client/src/pages/remind/...), 그리고 Layout의 import 경로 변경 등이 본 이슈 핵심 범위를 벗어나는 것으로 보입니다; shared/hooks/useSidebarNav.ts와 shared/components/sidebar/Sidebar.tsx의 selectCategory 시그니처 변경은 카테고리 이름 전달을 위한 연관 변경으로 이해되나 공용 API 영향 범위를 면밀히 검토해야 합니다. 해결 방안: 비연관 변경(packages/design-system의 아이콘 변경, Remind 관련 UI, Layout 리팩토링)은 본 PR에서 제거하거나 별도 PR로 분리하고, shared API 변경은 영향 범위(사용처 목록 및 마이그레이션 지침)를 PR 본문에 명확히 문서화한 뒤 병합하세요.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed 현재 PR 제목 "Api(client): 카테고리별 아티클 조회 API 연결 & 아티클 없는 경우 UI 분기 추가"은 변경사항의 핵심(카테고리별 아티클 API 연동 및 빈 결과에 대한 UI 처리)을 명확하게 요약하고 있어 PR 목록을 스캔하는 동료가 의도를 빠르게 파악할 수 있습니다.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Description Check ✅ Passed 현재 PR 설명은 리포지토리의 템플릿 구조(관련 이슈, 작업 항목, 리뷰 포인트, 스크린샷)를 충실히 따르고 있으며 관련 이슈(#87), 구현된 작업 목록, 리뷰어 참고사항 및 스크린샷 링크를 포함하여 기본 요구사항을 충족합니다. 또한 서버 미반영 상태와 임시 console.log 삽입 같은 구현 한계도 명시되어 있어 리뷰어가 현재 상태를 명확히 이해할 수 있습니다. 다만 변경된 주요 파일 목록이나 머지 전 수행해야 할 간단한 테스트 항목을 추가하면 설명이 더 완전해집니다.

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

🧹 Nitpick comments (16)
apps/client/src/pages/remind/apis/axios.ts (2)

10-11: 문자열 보간 대신 axios params 사용으로 안전한 인코딩/유지보수성 향상

쿼리스트링을 수동으로 조합하면 인코딩 누락 및 파라미터 변경 시 실수가 잦습니다. 아래처럼 params 옵션을 사용해주세요.

-  const { data } = await apiRequest.get(
-    `/api/v1/articles/remind?now=${nowDate}&read-status=${readStatus}&page=${page}&size=${size}`
-  );
+  const { data } = await apiRequest.get('/api/v1/articles/remind', {
+    params: { now: nowDate, 'read-status': readStatus, page, size },
+  });

3-8: 반환 타입 명시 및 응답 제네릭 도입 제안

API 응답 스키마가 정해져 있다면 반환 타입을 명시하고 axios 제네릭을 사용해 타입 안정성을 높이는 것을 권장합니다.

-export const getRemindArticles = async (
+export const getRemindArticles = async (
   nowDate: string,
   readStatus: boolean,
   page: number,
   size: number
-) => {
+): Promise<RemindArticlesResponse> => {
-  const { data } = await apiRequest.get('/api/v1/articles/remind', {
+  const { data } = await apiRequest.get<ApiResponse<RemindArticlesResponse>>('/api/v1/articles/remind', {
     params: { now: nowDate, 'read-status': readStatus, page, size },
   });
   return data.data;
 }

(참고: RemindArticlesResponse, ApiResponse 타입은 프로젝트 공용 타입 위치에 정의되어 있어야 합니다.)

apps/client/src/pages/remind/components/noReadArticles/NoReadArticles.tsx (2)

6-11: 복수의 상태에 맞는 카피/대체 텍스트로 정확도 및 접근성 개선

읽은 아티클이 없을 때 노출되므로 복수의 문구와 alt를 구체화하면 UX가 개선됩니다.

-      <img src={chippiNoArticles} alt="No Articles" />
+      <img src={chippiNoArticles} alt="읽은 아티클이 없어요" />

-      <p className="body1-m text-font-gray-3 mt-[0.8rem]">
-        저장된 정보가 없어요
+      <p className="body1-m text-font-gray-3 mt-[0.8rem]">
+        아직 읽은 아티클이 없어요

8-8: 의미론적 태그 사용 제안: 제목은 <h2>

스크린리더 구조 인식 향상을 위해 제목은 heading 요소를 권장합니다.

-      <p className="head2 mt-[1.6rem]">앗..</p>
+      <h2 className="head2 mt-[1.6rem]">앗..</h2>
apps/client/src/pages/remind/components/noUnreadArticles/NoUnreadArticles.tsx (1)

1-1: 변수명과 alt 정교화로 가독성/접근성 개선

컴포넌트 목적에 맞게 변수명을 NoUnread로, alt도 구체적으로 수정 제안드립니다.

-import chippiNoRemindArticles from '@assets/chippi_remindx.svg';
+import chippiNoUnreadArticles from '@assets/chippi_remindx.svg';
...
-      <img src={chippiNoRemindArticles} alt="No Articles" />
+      <img src={chippiNoUnreadArticles} alt="읽지 않은 아티클이 없어요" />

Also applies to: 6-6

apps/client/src/pages/remind/Remind.tsx (2)

41-57: 중복 렌더링 블록 제거로 DRY 적용

두 분기에서 동일한 카드 리스트를 중복 렌더링합니다. 한 블록으로 합치고 placeholder만 분기하세요.

-      {activeBadge === 'read' &&
-        (data?.articles && data.articles.length > 0 ? (
-          <div className="scrollbar-hide mt-[2.6rem] flex max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">
-            {data.articles.map((article) => (
-              <Card
-                key={article.articleId}
-                type="remind"
-                title={article.url}
-                content={article.memo}
-                timeRemaining={article.remindAt}
-                category={article.category.categoryName}
-              />
-            ))}
-          </div>
-        ) : (
-          <NoReadArticles />
-        ))}
-
-      {activeBadge === 'notRead' &&
-        (data?.articles && data.articles.length > 0 ? (
-          <div className="scrollbar-hide mt-[2.6rem] flex max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">
-            {data.articles.map((article) => (
-              <Card
-                key={article.articleId}
-                type="remind"
-                title={article.url}
-                content={article.memo}
-                timeRemaining={article.remindAt}
-                category={article.category.categoryName}
-              />
-            ))}
-          </div>
-        ) : (
-          <NoUnreadArticles />
-        ))}
+      {data?.articles && data.articles.length > 0 ? (
+        <div className="scrollbar-hide mt-[2.6rem] flex max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">
+          {data.articles.map((article) => (
+            <Card
+              key={article.articleId}
+              type="remind"
+              title={article.url}
+              content={article.memo}
+              timeRemaining={article.remindAt}
+              category={article.category.categoryName}
+            />
+          ))}
+        </div>
+      ) : activeBadge === 'read' ? (
+        <NoReadArticles />
+      ) : (
+        <NoUnreadArticles />
+      )}

Also applies to: 59-75


12-17: 로딩/에러 시 빈 상태 깜빡임 방지

현재 로딩 중에도 빈 상태 컴포넌트가 먼저 보일 수 있습니다. isLoading/isError를 활용해 UX를 다듬는 것을 권장합니다.

-  const { data } = useGetRemindArticles(
+  const { data, isLoading, isError } = useGetRemindArticles(
     formattedDate,
     activeBadge === 'read',
     1,
     10
   );
+  if (isLoading) {
+    return <div className="flex flex-col py-[5.2rem] pl-[8rem]">로딩 중…</div>;
+  }
+  if (isError) {
+    return <div className="flex flex-col py-[5.2rem] pl-[8rem]">데이터를 불러오지 못했습니다.</div>;
+  }

Also applies to: 41-75

apps/client/src/pages/myBookmark/types/api.ts (2)

1-5: 내부 Category 타입 네이밍 충돌/혼동 방지

동일 리포 내 apps/client/src/shared/types/api.ts에도 Category가 존재합니다. 현재 파일의 Category는 필드명이 다르므로 혼동을 줄이기 위해 별도 이름(예: BookmarkCategory)으로 분리하는 것을 권장합니다.

-interface Category {
+interface BookmarkCategory {
   categoryId: number;
   categoryName: string;
   categoryColor: string;
 }
 ...
-  category: Category;
+  category: BookmarkCategory;

Also applies to: 13-13


31-31: 카테고리 응답 타입을 전용 인터페이스로 분리하고 totalUnreadArticle을 optional로 변경하세요.

MyBookmark.tsx가 articles?.totalUnreadArticle || 0로 사용하므로 optional로 변경해도 호환되며, alias는 백엔드 스펙 변경에 취약합니다.
수정 위치: apps/client/src/pages/myBookmark/types/api.ts (현재 31행). 사용처: apps/client/src/pages/myBookmark/MyBookmark.tsx:60

-export type CategoryBookmarkArticleResponse = UnreadBookmarkArticleResponse;
+export interface CategoryBookmarkArticleResponse {
+  totalArticle: number;
+  // 카테고리 API가 제공하는 경우에만 사용
+  totalUnreadArticle?: number;
+  articles: BookmarkArticle[];
+}
packages/design-system/src/icons/components/icon.tsx (1)

107-107: CSS 변수 미정의 시 안전한 폴백 추가 제안

토큰이 누락되어도 현재 텍스트 색상(currentColor)을 사용하도록 폴백을 추가하세요.

-      style={color ? { color: `var(--color-${color})` } : undefined}
+      style={color ? { color: `var(--color-${color}, currentColor)` } : undefined}
apps/client/src/pages/myBookmark/components/NoArticles/NoArticles.tsx (2)

7-7: 대체 텍스트 현지화 제안

시각적 맥락에 맞춰 alt를 한국어로 제공하세요.

-      <img src={chippiNoArticles} alt="No Articles" />
+      <img src={chippiNoArticles} alt="저장된 북마크 없음" />

10-16: gap 클래스 적용 위치 수정

gap은 flex 컨테이너에 적용해야 효력이 있습니다. 현재 <p>에 적용되어 무의미합니다. 컨테이너로 이동하세요.

-      <div className="mt-[0.8rem] flex items-center">
-        <p className="body1-m text-font-gray-3 gap-[0.4rem]">
+      <div className="mt-[0.8rem] flex items-center gap-[0.4rem]">
+        <p className="body1-m text-font-gray-3">
           원하는 페이지에서
         </p>
         <div className="flex items-center text-center">
           <Icon name="ic_extension" width={28} height={28} />
           <p className="body1-m text-font-gray-3">아이콘을 눌러주세요.</p>
         </div>
       </div>
apps/client/src/pages/myBookmark/apis/queries.ts (1)

34-44: React Query 옵션 보완 제안 (UX 안정화)

페이지/사이즈 변경 시 스피너 점멸을 줄이기 위해 캐싱 파라미터를 보완하는 것을 권장합니다.

다음과 같이 staleTime/gcTime과 placeholder를 추가해 보세요 (v5 기준):

 export const useGetCategoryBookmarkArticles = (
   categoryId: string | null,
   page: number,
   size: number
 ): UseQueryResult<CategoryBookmarkArticleResponse, AxiosError> => {
   return useQuery({
     queryKey: ['categoryBookmarkArticles', categoryId, page, size],
     queryFn: () => getCategoryBookmarkArticles(categoryId, page, size),
     enabled: !!categoryId,
+    staleTime: 30_000,
+    gcTime: 5 * 60_000,
+    placeholderData: (prev) => prev, // 페이지 전환 시 이전 데이터 유지
   });
 };
apps/client/src/pages/myBookmark/MyBookmark.tsx (3)

26-27: 디버그 콘솔 제거

불필요한 콘솔은 머지 전 제거해주세요.

-// 임시 콘솔
-console.log('categoryArticles', categoryArticles);

84-100: 카테고리 필터 적용 시 '안 읽음' 섹션 동작 스펙 확인 필요

현재 '안 읽음'은 전역 미읽음 목록을 그대로 사용합니다. 카테고리 필터 선택 상태에서 미읽음도 해당 카테고리로 제한해야 하는지 요구사항 확인이 필요합니다.

원한다면 다음 두 가지 중 하나를 구현해야 합니다.

  • 서버: "카테고리별 미읽음" API와 훅 추가.
  • 클라이언트: unreadArticles.articlesarticle.category.categoryId === categoryId로 필터링 (페이징 정확도는 떨어짐).

필요 시 API/훅 스캐폴딩 또는 클라이언트 필터 버전을 빠르게 제안드릴게요.


34-49: 중첩 h-screen로 인한 이중 스크롤 가능성

루트 컨테이너와 리스트 컨테이너 모두 h-screen을 사용 중입니다. 레이아웃에 따라 이중 스크롤이 발생할 수 있습니다.

루트에 min-h-screen + 내부에 flex-1 overflow-y-auto를 권장합니다.

-<div className="flex h-screen flex-col py-[5.2rem] pl-[8rem]">
+<div className="flex min-h-screen flex-col py-[5.2rem] pl-[8rem]">
...
-  <div className="scrollbar-hide mt-[2.6rem] flex h-screen max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">
+  <div className="scrollbar-hide mt-[2.6rem] flex flex-1 max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">

Also applies to: 66-69, 86-87

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d3a7096 and bbe8f1f.

⛔ Files ignored due to path filters (3)
  • apps/client/src/assets/chippi_remindx.svg is excluded by !**/*.svg
  • apps/client/src/assets/chippi_x.svg is excluded by !**/*.svg
  • packages/design-system/src/icons/source/ic_extension.svg is excluded by !**/*.svg
📒 Files selected for processing (14)
  • apps/client/src/layout/Layout.tsx (1 hunks)
  • apps/client/src/pages/myBookmark/MyBookmark.tsx (1 hunks)
  • apps/client/src/pages/myBookmark/apis/axios.ts (1 hunks)
  • apps/client/src/pages/myBookmark/apis/queries.ts (2 hunks)
  • apps/client/src/pages/myBookmark/components/NoArticles/NoArticles.tsx (1 hunks)
  • apps/client/src/pages/myBookmark/types/api.ts (1 hunks)
  • apps/client/src/pages/remind/Remind.tsx (3 hunks)
  • apps/client/src/pages/remind/apis/axios.ts (1 hunks)
  • apps/client/src/pages/remind/components/noReadArticles/NoReadArticles.tsx (1 hunks)
  • apps/client/src/pages/remind/components/noUnreadArticles/NoUnreadArticles.tsx (1 hunks)
  • apps/client/src/shared/components/sidebar/Sidebar.tsx (1 hunks)
  • apps/client/src/shared/hooks/useSidebarNav.ts (1 hunks)
  • packages/design-system/src/icons/components/icon.tsx (1 hunks)
  • packages/design-system/src/icons/iconNames.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
🧬 Code graph analysis (4)
apps/client/src/pages/myBookmark/apis/queries.ts (2)
apps/client/src/pages/myBookmark/types/api.ts (1)
  • CategoryBookmarkArticleResponse (31-31)
apps/client/src/pages/myBookmark/apis/axios.ts (1)
  • getCategoryBookmarkArticles (17-26)
apps/client/src/pages/remind/Remind.tsx (1)
packages/design-system/src/components/card/Card.stories.tsx (1)
  • Remind (42-53)
apps/client/src/pages/myBookmark/MyBookmark.tsx (2)
apps/client/src/pages/myBookmark/apis/queries.ts (3)
  • useGetBookmarkArticles (14-22)
  • useGetBookmarkUnreadArticles (24-32)
  • useGetCategoryBookmarkArticles (34-44)
packages/design-system/src/icons/components/icon.tsx (1)
  • Icon (71-114)
apps/client/src/pages/myBookmark/types/api.ts (1)
apps/client/src/shared/types/api.ts (1)
  • Category (1-5)
🔇 Additional comments (14)
apps/client/src/pages/remind/apis/axios.ts (1)

10-10: 백엔드 쿼리 키 확인 필요: read-status vs readStatus
현재 apps/client/src/pages/remind/apis/axios.ts (라인 10)에서 쿼리키를 read-status로 요청합니다; 서버가 하이픈 표기를 기대하는지 확인하고 백엔드와 합의된 표기법으로 일관화하세요.

apps/client/src/pages/remind/components/noUnreadArticles/NoUnreadArticles.tsx (1)

1-1: 확인 — 에셋 파일 존재(Import 유효)

apps/client/src/assets/chippi_remindx.svg 파일이 리포지토리에 존재하므로 import 경로는 유효합니다.

apps/client/src/pages/remind/Remind.tsx (3)

9-9: 유니온 타입으로 상호배타 상태 모델링: 굿

'read' | 'notRead' 유니온과 핸들러 시그니처가 명확합니다. 상태 오타를 방지합니다.

Also applies to: 19-21


28-36: 검증 완료 — API 필드명(readArticleCount, unreadArticleCount) 일치

apps/client/src/pages/remind/types/api.ts의 ArticleListResponse에 두 필드가 정의되어 있으며 apps/client/src/pages/remind/Remind.tsx에서 동일 필드를 사용하고 있어 카운트가 0으로 고정될 위험은 없습니다.


48-52: Card props — title / timeRemaining 전달 방식 확인 필요

  • packages/design-system/src/components/card/Card.tsx: CardProps = RemindProps | BookmarkProps이며 timeRemaining?: never로 선언되어 있어, Remind에서 넘기는 article.remindAt이 현재 타입에 맞지 않거나 허용되지 않을 가능성 있음.
  • apps/client/src/pages/remind/Remind.tsx (48–52, 66–70): title={article.url} 대신 article.title ?? article.url로 폴백 적용. remindAt은 Card가 기대하는 형식(예: '남은 시간' 문자열)으로 포맷해 전달하거나 Card의 RemindProps 타입을 수정해 일치시키기.
  • 참고: 레포 검색에서 Article/Remind 응답 타입 정의는 발견되지 않음 — 도메인 타입(필드명·타입)을 직접 확인해 변경 영향 검증 필요.
apps/client/src/pages/myBookmark/types/api.ts (1)

20-20: isNewUser 필드 도입 검증 필요
타입 선언이 추가되었으나 코드베이스에서 사용처가 없고 선언부만 존재합니다 (apps/client/src/pages/myBookmark/types/api.ts:20). /api/v1/articles 응답에 isNewUser가 실제 포함되는지 확인하세요. 미포함이면 타입을 optional로 변경하거나 응답 파싱 시 기본값을 할당하세요.

packages/design-system/src/icons/iconNames.ts (1)

13-19: 아이콘 등록 확인 — 스프라이트/빌드 포함 여부만 검증 필요

  • 확인: packages/design-system/src/icons/iconNames.ts에 'ic_extension'(13행), 'main_header_logo'(17행), 'main_logo'(18행) 등록됨; 중복 없음.
  • 조치: 빌드 산출물(또는 아이콘 스프라이트)에서 해당 심볼(id)이 실제로 포함되는지 확인(예: 출력 스프라이트에서 등 검색).
packages/design-system/src/icons/components/icon.tsx (1)

30-30: 검증 완료: --color-black CSS 변수 존재 확인됨

packages/tailwind-config/shared-styles.css:176에 --color-black: #000000; 정의되어 있으므로 'black' 토큰 추가 시 의도한 색상이 적용됩니다.

apps/client/src/layout/Layout.tsx (1)

2-2: 경로 별칭 사용 전환 LGTM

@shared 경로 별칭 사용으로 일관성/가독성 개선되었습니다. tsconfig/webpack 설정과 ESLint import-resolver가 이미 정합하면 문제 없습니다.

apps/client/src/shared/components/sidebar/Sidebar.tsx (1)

119-119: 카테고리 선택 시 name 전달 변경 LGTM

selectCategory(id, category.name)로 시그니처 변경 반영이 적절합니다. URL 인코딩은 훅 내부에서 처리하도록 해 두셨는지 확인만 부탁드립니다(별도 코멘트 참조).

apps/client/src/pages/myBookmark/apis/queries.ts (2)

3-7: 신규 API 의존성 추가 적절

getCategoryBookmarkArticles 추가 및 기존 import 구성과 일관성이 유지됩니다.


10-10: 타입 별칭 import OK

CategoryBookmarkArticleResponse 도입이 반환 타입과 맞물려 명확합니다.

apps/client/src/pages/myBookmark/MyBookmark.tsx (2)

6-7: 신규 훅 의존성 추가 적절

카테고리 전용 훅 도입이 PR 목적과 부합합니다.


14-17: 쿼리 파라미터 중복 및 명명 혼선: category vs categoryId

File: apps/client/src/pages/myBookmark/MyBookmark.tsx (Lines 14-17)
동일한 쿼리 키를 두 변수에 중복 할당하고 있어 혼선 발생. API는 id를, UI는 이름을 기대할 가능성이 있으므로 분리하세요.

-const [searchParams] = useSearchParams();
-const category = searchParams.get('category');
-const categoryId = searchParams.get('category');
+const [searchParams] = useSearchParams();
+const categoryId = searchParams.get('categoryId'); // API 호출용
+const categoryName = searchParams.get('category'); // 헤더 표시용

헤더(표시) 영역과 axios 호출(쿼리 키)도 함께 갱신하세요.
백엔드가 실제로 categoryId 키를 기대하는지 확인. 백엔드가 category를 기대하면 axios 쿼리 키를 그에 맞춰 유지/조정하세요.

Comment on lines +17 to +26
export const getCategoryBookmarkArticles = async (
categoryId: string | null,
page: number,
size: number
) => {
const { data } = await apiRequest.get(
`/api/v1/articles/category?categoryId=${categoryId}&page=${page}&size=${size}`
);
return data.data;
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

null 카테고리 방지 및 쿼리스트링 조합 방식 개선 제안

  • categoryId가 null일 때 ...categoryId=null로 호출될 수 있습니다. 안전하게 가드하고, 템플릿 문자열 대신 axios params를 사용해 인코딩/누락 처리 이점을 얻는 편이 좋습니다.

다음 diff 적용 제안:

 export const getCategoryBookmarkArticles = async (
   categoryId: string | null,
   page: number,
   size: number
 ) => {
-  const { data } = await apiRequest.get(
-    `/api/v1/articles/category?categoryId=${categoryId}&page=${page}&size=${size}`
-  );
+  if (!categoryId) {
+    throw new Error('categoryId is required');
+  }
+  const { data } = await apiRequest.get('/api/v1/articles/category', {
+    params: { categoryId, page, size },
+  });
   return data.data;
 };

추가로, 반환 타입을 명확히 하려면 API envelope 타입을 도입하거나 캐스팅을 통해 CategoryBookmarkArticleResponse를 지정하는 것도 고려해주세요.

📝 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 getCategoryBookmarkArticles = async (
categoryId: string | null,
page: number,
size: number
) => {
const { data } = await apiRequest.get(
`/api/v1/articles/category?categoryId=${categoryId}&page=${page}&size=${size}`
);
return data.data;
};
export const getCategoryBookmarkArticles = async (
categoryId: string | null,
page: number,
size: number
) => {
if (!categoryId) {
throw new Error('categoryId is required');
}
const { data } = await apiRequest.get('/api/v1/articles/category', {
params: { categoryId, page, size },
});
return data.data;
};
🤖 Prompt for AI Agents
In apps/client/src/pages/myBookmark/apis/axios.ts around lines 17-26, the
current implementation interpolates categoryId into the querystring which can
produce categoryId=null; change to call apiRequest.get with axios params (e.g.,
{ params: { page, size, ...(categoryId ? { categoryId } : {}) } }) so null is
omitted and axios handles encoding; also annotate the function return type by
mapping the response envelope to CategoryBookmarkArticleResponse (or cast
data.data) before returning.

💡 Verification agent

🧩 Analysis chain

URL 파라미터 네이밍 일관성 확인 필요 (id vs categoryId)

라우팅에서는 /my-bookmarks?id=...를 사용하고, API에서는 categoryId를 기대합니다. 코드베이스 전반의 파라미터 키가 혼용되지 않는지 점검 부탁드립니다.

다음 스크립트로 사용처를 빠르게 확인할 수 있습니다:


🏁 Script executed:

#!/bin/bash
# id / categoryId 파라미터 사용 현황 점검
rg -n --type ts --type tsx -C2 '\bcreateSearchParams\(|useSearchParams\(|categoryId\b|\bid='

Length of output: 27


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# TS/TSX 파일에서 createSearchParams, useSearchParams, categoryId, id= 사용 현황 재검색
echo "검색 시작: createSearchParams, useSearchParams, categoryId, id= (ts/tsx/js/jsx)..."

rg -n -C2 -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' '\b(createSearchParams\(|useSearchParams\(|categoryId\b|\bid=)' || true

# 문자열로 포함된 쿼리 파라미터도 확인
echo "검색 추가: 파일 전체에서 '?id=' 포함 여부 확인..."
rg -n -C2 -g '!**/node_modules/**' '\?id=' || true

Length of output: 9237


쿼리 파라미터 키 불일치 — 긴급 수정 필요

라우팅은 id 및 category(name)을 쿼리로 보내는데, MyBookmark는 'category'를 categoryId로 사용하고 API는 'categoryId'를 기대합니다. 이로 인해 올바른 카테고리 ID가 API에 전달되지 않습니다.

  • 발견 위치:

    • apps/client/src/shared/hooks/useSidebarNav.ts — navigate(/my-bookmarks?id=${id}&category=${name})
    • apps/client/src/pages/myBookmark/MyBookmark.tsx — searchParams.get('category')를 categoryId로 사용
    • apps/client/src/pages/myBookmark/apis/axios.ts — getCategoryBookmarkArticles가 ?categoryId=${categoryId}로 호출
    • apps/client/src/pages/myBookmark/apis/queries.ts — queryKey / enabled: !!categoryId 사용
  • 권장 조치:

    • 일관화(택1): MyBookmark에서 searchParams.get('id')로 실제 ID를 읽어 API에 전달하거나, useSidebarNav에서 쿼리 키를 categoryId로 변경.
    • API 전송 전 string → number 변환 확인 및 enabled 조건이 의도대로 동작하는지 검증.

Comment on lines 18 to 41
const { data: articles } = useGetBookmarkArticles(1, 10);
const { data: unreadArticles } = useGetBookmarkUnreadArticles(1, 10);
const { data: categoryArticles } = useGetCategoryBookmarkArticles(
categoryId,
1,
10
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

카테고리 API를 호출하지만 UI에 반영되지 않음

categoryArticles를 가져오지만 렌더링·카운트에 사용하지 않아 필터 효과가 없습니다. 카테고리 존재 시 목록/카운트를 카테고리 응답으로 스위칭하세요.

아래 코멘트(라인 66-82, 51-64)에 JSX와 카운트 반영용 diff를 제안했습니다.

Comment on lines +38 to +66
{category && (
<Icon
name="ic_arrow_down_disable"
width={24}
height={24}
rotate={270}
color="black"
/>
)}
</div>
<p className="head3 text-main500">{category || ''}</p>
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

헤더 카테고리 표시 변수 교체

표시용은 categoryName으로 교체해 의미를 명확히 하세요.

-          {category && (
+          {categoryName && (
             <Icon
               name="ic_arrow_down_disable"
               width={24}
               height={24}
               rotate={270}
               color="black"
             />
           )}
-        <p className="head3 text-main500">{category || ''}</p>
+        <p className="head3 text-main500">{categoryName || ''}</p>
📝 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
{category && (
<Icon
name="ic_arrow_down_disable"
width={24}
height={24}
rotate={270}
color="black"
/>
)}
</div>
<p className="head3 text-main500">{category || ''}</p>
</div>
{categoryName && (
<Icon
name="ic_arrow_down_disable"
width={24}
height={24}
rotate={270}
color="black"
/>
)}
</div>
<p className="head3 text-main500">{categoryName || ''}</p>
</div>
🤖 Prompt for AI Agents
In apps/client/src/pages/myBookmark/MyBookmark.tsx around lines 38 to 49, the
header is using the prop/state variable category for display; change both the
conditional render (the Icon block) and the paragraph text to use categoryName
instead of category, keeping the existing fallback (empty string) for the
paragraph and preserving the Icon render condition to only show when
categoryName is truthy.

Comment on lines 51 to 81
<div className="mt-[3rem] flex gap-[2.4rem]">
<Badge
text="전체보기"
countNum={readArticles?.totalArticle || 0}
countNum={articles?.totalArticle || 0}
onClick={() => handleBadgeClick('all')}
isActive={activeBadge === 'all'}
/>
<Badge
text="안 읽음"
countNum={readArticles?.totalUnreadArticle || 0}
countNum={articles?.totalUnreadArticle || 0}
onClick={() => handleBadgeClick('notRead')}
isActive={activeBadge === 'notRead'}
/>
</div>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

뱃지 카운트 소스 잘못 지정 및 카테고리 연동 필요

  • "안 읽음" 뱃지 카운트가 articles에서 오고 있어 잘못된 값입니다.
  • 카테고리 필터가 있을 때 "전체보기" 카운트는 categoryArticles를 사용해야 일관됩니다.
-<Badge
-  text="전체보기"
-  countNum={articles?.totalArticle || 0}
+<Badge
+  text="전체보기"
+  countNum={(categoryId ? categoryArticles?.totalArticle : articles?.totalArticle) || 0}
   onClick={() => handleBadgeClick('all')}
   isActive={activeBadge === 'all')}
 />
 <Badge
   text="안 읽음"
-  countNum={articles?.totalUnreadArticle || 0}
+  countNum={unreadArticles?.totalArticle || 0}
   onClick={() => handleBadgeClick('notRead')}
   isActive={activeBadge === 'notRead')}
 />
📝 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
<div className="mt-[3rem] flex gap-[2.4rem]">
<Badge
text="전체보기"
countNum={readArticles?.totalArticle || 0}
countNum={articles?.totalArticle || 0}
onClick={() => handleBadgeClick('all')}
isActive={activeBadge === 'all'}
/>
<Badge
text="안 읽음"
countNum={readArticles?.totalUnreadArticle || 0}
countNum={articles?.totalUnreadArticle || 0}
onClick={() => handleBadgeClick('notRead')}
isActive={activeBadge === 'notRead'}
/>
</div>
<div className="mt-[3rem] flex gap-[2.4rem]">
<Badge
text="전체보기"
countNum={(categoryId ? categoryArticles?.totalArticle : articles?.totalArticle) || 0}
onClick={() => handleBadgeClick('all')}
isActive={activeBadge === 'all'}
/>
<Badge
text="안 읽음"
countNum={unreadArticles?.totalArticle || 0}
onClick={() => handleBadgeClick('notRead')}
isActive={activeBadge === 'notRead'}
/>
</div>
🤖 Prompt for AI Agents
In apps/client/src/pages/myBookmark/MyBookmark.tsx around lines 51-64, the badge
count sources are wrong: the "안 읽음" badge is using articles instead of the
unread count from the currently active data set, and "전체보기" should show counts
from categoryArticles when a category filter is applied. Update the countNum
props so both badges derive their values from categoryArticles when a category
is selected (e.g., categoryArticles?.totalArticle and
categoryArticles?.totalUnreadArticle) otherwise fall back to
articles?.totalArticle and articles?.totalUnreadArticle, and keep null-safe
fallbacks to 0.

Comment on lines 66 to 119
{activeBadge === 'all' &&
(articles?.articles && articles.articles.length > 0 ? (
<div className="scrollbar-hide mt-[2.6rem] flex h-screen max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">
{articles.articles.map((article) => (
<Card
key={article.articleId}
type="bookmark"
title={article.url}
content={article.memo}
category={article.category.categoryName}
date={new Date(article.createdAt).toLocaleDateString('ko-KR')}
/>
))}
</div>
) : (
<NoArticles />
))}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

"전체보기" 렌더링에 카테고리 결과 적용

카테고리 존재 시 categoryArticles를 사용하도록 전환하세요.

컴포넌트 상단(쿼리 호출 직후)에 파생 값을 하나 두고:

+// 파생 리스트 (카테고리 유무에 따라 전환)
+const listAll = categoryId ? categoryArticles?.articles : articles?.articles;

렌더링 분기는 다음처럼 수정:

-{activeBadge === 'all' &&
-  (articles?.articles && articles.articles.length > 0 ? (
+{activeBadge === 'all' &&
+  (listAll && listAll.length > 0 ? (
     <div className="scrollbar-hide mt-[2.6rem] flex h-screen max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">
-      {articles.articles.map((article) => (
+      {listAll.map((article) => (
         <Card
           key={article.articleId}
           type="bookmark"
           title={article.url}
           content={article.memo}
           category={article.category.categoryName}
           date={new Date(article.createdAt).toLocaleDateString('ko-KR')}
         />
       ))}
     </div>
   ) : (
     <NoArticles />
   ))}
📝 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
{activeBadge === 'all' &&
(articles?.articles && articles.articles.length > 0 ? (
<div className="scrollbar-hide mt-[2.6rem] flex h-screen max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">
{articles.articles.map((article) => (
<Card
key={article.articleId}
type="bookmark"
title={article.url}
content={article.memo}
category={article.category.categoryName}
date={new Date(article.createdAt).toLocaleDateString('ko-KR')}
/>
))}
</div>
) : (
<NoArticles />
))}
// 파생 리스트 (카테고리 유무에 따라 전환)
const listAll = categoryId ? categoryArticles?.articles : articles?.articles;
{activeBadge === 'all' &&
(listAll && listAll.length > 0 ? (
<div className="scrollbar-hide mt-[2.6rem] flex h-screen max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">
{listAll.map((article) => (
<Card
key={article.articleId}
type="bookmark"
title={article.url}
content={article.memo}
category={article.category.categoryName}
date={new Date(article.createdAt).toLocaleDateString('ko-KR')}
/>
))}
</div>
) : (
<NoArticles />
))}
🤖 Prompt for AI Agents
In apps/client/src/pages/myBookmark/MyBookmark.tsx around lines 66 to 82, the
"all" view always renders from articles.articles even when a category filter is
present; create a derived variable (e.g. const displayedArticles =
categoryArticles?.articles?.length ? categoryArticles : articles) right after
the query returns and then update the JSX branch to use displayedArticles (check
length and map displayedArticles.articles) so the "전체보기" view shows category
results when available and falls back to the full articles list otherwise.

Comment on lines +26 to 31
(id: number, name: string) => {
setActiveTab('mybookmark');
setSelectedCategoryId(id);
navigate(`/my-bookmarks?categoryId=${id}`);
navigate(`/my-bookmarks?id=${id}&category=${name}`);
},
[navigate]
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

카테고리 이름 쿼리스트링 인코딩 필요

한글/공백/특수문자를 포함한 카테고리명이 있을 수 있어 URL 인코딩이 필요합니다.

-      navigate(`/my-bookmarks?id=${id}&category=${name}`);
+      navigate(`/my-bookmarks?id=${id}&category=${encodeURIComponent(name)}`);

또는 createSearchParams 사용을 권장합니다:

navigate({
  pathname: '/my-bookmarks',
  search: createSearchParams({ id: String(id), category: name }).toString(),
});
🤖 Prompt for AI Agents
In apps/client/src/shared/hooks/useSidebarNav.ts around lines 26 to 31, the code
constructs the category query string without URL-encoding which will break for
Korean, spaces or special characters; update the navigate call to URL-encode the
category value (e.g., use encodeURIComponent(String(name))) or use
createSearchParams to build the search string (ensuring id is stringified) so
the resulting URL is safe; keep setActiveTab and setSelectedCategoryId as-is and
pass the encoded search string to navigate.

💡 Verification agent

🧩 Analysis chain

URL 파라미터 키 일관성 검토 (id ↔ categoryId)

현재 쿼리 키가 id인데, API는 categoryId를 사용합니다. 페이지에서 useSearchParams로 읽어오는 키와 훅/axios에 넘기는 키가 일치하는지 한 번 더 확인해주세요.


🏁 Script executed:

#!/bin/bash
# MyBookmark 관련 파일에서 파라미터 키 점검
rg -n -C2 --type ts --type tsx 'useSearchParams|categoryId\b|\bid=' apps/client/src/pages/myBookmark

Length of output: 27


추가 확인을 위해 아래 스크립트를 실행합니다.


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "추가 검색: useSearchParams, categoryId, id=, my-bookmarks 관련 항목을 apps/client에서 찾습니다..."
if ! command -v rg >/dev/null 2>&1; then
  echo "rg 미설치: grep으로 대체 검색 실행"
  grep -R -n -E --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' 'useSearchParams|categoryId\b|\bid=|\bmy-bookmark|/my-bookmarks' apps/client || true
  exit 0
fi
echo "rg 버전: $(rg --version | head -n1 || true)"
rg -n -C2 'useSearchParams|categoryId\b|\bid=|\bmy-bookmark|/my-bookmarks' apps/client -g '**/*.ts' -g '**/*.tsx' -g '**/*.js' -g '**/*.jsx' -S || true

Length of output: 10212


쿼리 파라미터 키 통일 — categoryId 사용 권장

useSidebarNav가 /my-bookmarks?id=${id}&category=${name}로 네비게이트하는 반면 MyBookmark는 searchParams.get('category')로 id를 읽고, API는 ?categoryId= 를 기대합니다. 쿼리 키를 일관되게 정리하세요.

  • 수정 제안: apps/client/src/shared/hooks/useSidebarNav.ts — navigate(/my-bookmarks?categoryId=${id}&category=${name})로 변경.
  • 앱 측 정리: apps/client/src/pages/myBookmark/MyBookmark.tsx — 카테고리 ID는 searchParams.get('categoryId')에서 읽고, 이름은 searchParams.get('category')에서 읽도록 분리(현재 둘 다 'category'로 읽음).
  • API: apps/client/src/pages/myBookmark/apis/axios.ts는 categoryId를 사용하므로 변경 불필요; 변경 후 categoryId로 전달되는 값이 숫자 ID인지 확인.
🤖 Prompt for AI Agents
In apps/client/src/shared/hooks/useSidebarNav.ts around lines 26-31, the
navigation query keys are inconsistent with MyBookmark and the API — change the
navigate call to use categoryId for the numeric id
(navigate(`/my-bookmarks?categoryId=${id}&category=${name}`)) so the URL
contains both categoryId and category name; then update
apps/client/src/pages/myBookmark/MyBookmark.tsx to read the id from
searchParams.get('categoryId') and the name from searchParams.get('category'),
and ensure the value sent to the API
(apps/client/src/pages/myBookmark/apis/axios.ts) uses the numeric categoryId
(parse/validate as number) so keys and types are consistent across the app.

@constantly-dev constantly-dev self-assigned this Sep 13, 2025
@constantly-dev constantly-dev added the api api 연결 label Sep 13, 2025
@constantly-dev constantly-dev force-pushed the api/#87/article-for-category-api branch from bbe8f1f to 08d37d8 Compare September 13, 2025 11:47
@github-actions
Copy link

github-actions bot commented Sep 13, 2025

✅ Storybook chromatic 배포 확인:
🐿️ storybook

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

♻️ Duplicate comments (5)
apps/client/src/pages/myBookmark/MyBookmark.tsx (5)

14-14: 대소문자 경로 수정 확인 (CI용 리눅스 호환성 보장)

components/NoArticles/NoArticles로 교정된 점 좋습니다. 이전 코멘트 반영되어 import 실패 리스크 해소되었습니다.


55-66: 헤더 카테고리 표시 변수 교체

표시는 categoryName을 사용하는 편이 명확합니다. 이전 제안과 동일합니다.

-          {category && (
+          {categoryName && (
             <Icon
               name="ic_arrow_down_disable"
               width={24}
               height={24}
               rotate={270}
               color="black"
             />
           )}
-        <p className="head3 text-main500">{category || ''}</p>
+        <p className="head3 text-main500">{categoryName || ''}</p>

71-80: 뱃지 카운트 소스 수정 (카테고리/안 읽음 일관성)

  • 전체보기: 카테고리 선택 시 categoryArticles의 총계를 사용해야 합니다.
  • 안 읽음: articles?.totalUnreadArticle 대신 unreadArticles?.totalArticle을 사용하세요.
 <Badge
   text="전체보기"
-  countNum={articles?.totalArticle || 0}
+  countNum={allCount}
   onClick={() => handleBadgeClick('all')}
   isActive={activeBadge === 'all'}
 />
 <Badge
   text="안 읽음"
-  countNum={articles?.totalUnreadArticle || 0}
+  countNum={unreadArticles?.totalArticle || 0}
   onClick={() => handleBadgeClick('notRead')}
   isActive={activeBadge === 'notRead'}
 />

83-120: "전체보기" 렌더링 데이터 소스 전환 (카테고리 반영)

카테고리 선택 시에도 항상 articles.articles를 사용하고 있어 필터가 UI에 반영되지 않습니다. 파생 리스트 listAll을 사용하도록 바꾸세요.

-{activeBadge === 'all' &&
-  (articles?.articles && articles.articles.length > 0 ? (
+{activeBadge === 'all' &&
+  (listAll && listAll.length > 0 ? (
     <div className="scrollbar-hide mt-[2.6rem] flex h-screen max-w-[104rem] flex-wrap gap-[1.6rem] overflow-y-auto scroll-smooth">
-      {articles.articles.map((article) => (
+      {listAll.map((article) => (
         <Card
           key={article.articleId}
           type="bookmark"
           title={article.url}
           content={article.memo}
           category={article.category.categoryName}
           date={new Date(article.createdAt).toLocaleDateString('ko-KR')}
           onClick={() => {}}
           onOptionsClick={(e) =>
             openMenu(article.articleId, e.currentTarget)
           }
         />
       ))}

35-41: 카테고리 API 응답을 실제 렌더·카운트에 연결하세요

현재 useGetCategoryBookmarkArticles를 호출하지만 렌더/카운트에서 사용하지 않아 필터 효과가 없습니다. 파생 값을 두고 UI 전환하세요.

   const { data: articles } = useGetBookmarkArticles(1, 10);
   const { data: unreadArticles } = useGetBookmarkUnreadArticles(1, 10);
   const { data: categoryArticles } = useGetCategoryBookmarkArticles(
     categoryId,
     1,
     10
   );
+
+  // 카테고리 선택 시 카테고리 결과로 전환
+  const listAll = categoryId ? categoryArticles?.articles : articles?.articles;
+  const allCount = (categoryId ? categoryArticles?.totalArticle : articles?.totalArticle) ?? 0;
🧹 Nitpick comments (3)
apps/client/src/pages/myBookmark/MyBookmark.tsx (3)

19-21: URL 파라미터 네이밍 명확화 (category → categoryName) 제안

표시 전용 문자열임이 드러나도록 categoryName으로 명시하면 가독성이 좋아집니다. 아래 헤더 사용처도 함께 교체 권장합니다.

-  const category = searchParams.get('category');
+  const categoryName = searchParams.get('category');
   const categoryId = searchParams.get('id');

43-44: 임시 콘솔 제거

배포 전 불필요한 콘솔은 삭제하세요.

-  // 임시 콘솔
-  console.log('categoryArticles', categoryArticles);
+  // TODO: 필요 시 개발 환경에서만 디버깅 로깅

83-120: 로딩 중 빈 상태 깜빡임 방지

초기 로딩 시 데이터 없음으로 판단해 <NoArticles />가 먼저 보일 수 있습니다. isLoading/isFetching으로 분기하거나 keepPreviousData를 활용하세요.

예:

-const { data: articles } = useGetBookmarkArticles(1, 10);
+const { data: articles, isLoading: isAllLoading } = useGetBookmarkArticles(1, 10);
 ...
-{activeBadge === 'all' &&
-  (listAll && listAll.length > 0 ? ( ... ) : ( <NoArticles /> ))}
+{activeBadge === 'all' &&
+  (isAllLoading ? <SkeletonGrid /> : (listAll && listAll.length > 0 ? ( ... ) : ( <NoArticles /> )))}

Also applies to: 121-157

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bbe8f1f and 08d37d8.

⛔ Files ignored due to path filters (3)
  • apps/client/src/assets/chippi_remindx.svg is excluded by !**/*.svg
  • apps/client/src/assets/chippi_x.svg is excluded by !**/*.svg
  • packages/design-system/src/icons/source/ic_extension.svg is excluded by !**/*.svg
📒 Files selected for processing (14)
  • apps/client/src/layout/Layout.tsx (1 hunks)
  • apps/client/src/pages/myBookmark/MyBookmark.tsx (2 hunks)
  • apps/client/src/pages/myBookmark/apis/axios.ts (1 hunks)
  • apps/client/src/pages/myBookmark/apis/queries.ts (2 hunks)
  • apps/client/src/pages/myBookmark/components/NoArticles/NoArticles.tsx (1 hunks)
  • apps/client/src/pages/myBookmark/types/api.ts (1 hunks)
  • apps/client/src/pages/remind/Remind.tsx (4 hunks)
  • apps/client/src/pages/remind/apis/axios.ts (1 hunks)
  • apps/client/src/pages/remind/components/noReadArticles/NoReadArticles.tsx (1 hunks)
  • apps/client/src/pages/remind/components/noUnreadArticles/NoUnreadArticles.tsx (1 hunks)
  • apps/client/src/shared/components/sidebar/Sidebar.tsx (1 hunks)
  • apps/client/src/shared/hooks/useSidebarNav.ts (1 hunks)
  • packages/design-system/src/icons/components/icon.tsx (1 hunks)
  • packages/design-system/src/icons/iconNames.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
  • apps/client/src/pages/remind/apis/axios.ts
  • apps/client/src/pages/myBookmark/apis/queries.ts
  • apps/client/src/pages/remind/components/noReadArticles/NoReadArticles.tsx
  • apps/client/src/pages/myBookmark/types/api.ts
  • packages/design-system/src/icons/iconNames.ts
  • packages/design-system/src/icons/components/icon.tsx
  • apps/client/src/shared/hooks/useSidebarNav.ts
  • apps/client/src/pages/remind/components/noUnreadArticles/NoUnreadArticles.tsx
  • apps/client/src/layout/Layout.tsx
  • apps/client/src/pages/myBookmark/apis/axios.ts
  • apps/client/src/pages/myBookmark/components/NoArticles/NoArticles.tsx
  • apps/client/src/shared/components/sidebar/Sidebar.tsx
  • apps/client/src/pages/remind/Remind.tsx
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
📚 Learning: 2025-07-17T09:18:13.818Z
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#102
File: apps/extension/src/components/modalPop/ModalPop.tsx:166-172
Timestamp: 2025-07-17T09:18:13.818Z
Learning: In apps/extension/src/components/modalPop/ModalPop.tsx, the categories array should include "안 읽은 정보" (Unread Information) as the first default category that cannot be deleted. This default category is used consistently across the client-side dashboard and should be protected from deletion in the extension as well.

Applied to files:

  • apps/client/src/pages/myBookmark/MyBookmark.tsx
🧬 Code graph analysis (1)
apps/client/src/pages/myBookmark/MyBookmark.tsx (3)
apps/client/src/pages/myBookmark/apis/queries.ts (3)
  • useGetBookmarkArticles (14-22)
  • useGetBookmarkUnreadArticles (24-32)
  • useGetCategoryBookmarkArticles (34-44)
packages/design-system/src/icons/components/icon.tsx (1)
  • Icon (71-114)
apps/client/src/shared/components/sidebar/OptionsMenuPortal.tsx (1)
  • OptionsMenuPortal (15-45)
🔇 Additional comments (4)
apps/client/src/pages/myBookmark/MyBookmark.tsx (4)

6-8: 카테고리/라우터 훅 import 추가 적절

카테고리별 조회와 URL 파라미터 연동을 위한 훅 추가 방향 좋습니다.


15-15: Icon import 사용 OK

디자인시스템 아이콘 별도 import로 분리한 점 문제 없습니다.


55-63: Icon color 토큰 확인 필요

color="black"은 내부에서 var(--color-black)을 참조합니다. 디자인 토큰에 --color-black이 존재하는지 확인해주세요. 없다면 className="text-neutral900" 등 토큰 기반 색상으로 교체하세요.


100-115: OptionsMenuPortal 네이밍·사용 의도 불일치 — 확인 필요

문제: 포털 Props(categoryId, getCategoryName)는 사이드바 카테고리 편집용으로 보이지만, MyBookmark에서 articleId를 menu.categoryId로 전달해 아티클 옵션으로 사용하고 있습니다. 네이밍·도메인 혼동이 큽니다.

위치: apps/client/src/pages/myBookmark/MyBookmark.tsx (약 100–115, 138–153)

권장(간결):

  • 아티클 전용 포털 분리(ArticleOptionsMenuPortal) 또는 useAnchoredMenu의 categoryId → targetId 등으로 일반화하고 포털 Props 제네릭화.
  • REMIND_MOCK_DATA 기반 getBookmarkTitle 의존 제거 — 실제 아티클 데이터에서 라벨 계산하거나 ID 전달 방식으로 수정.

검증 참고: 이전 rg 실행이 "unrecognized file type: tsx"로 실패했습니다. 코드베이스 전반에서 OptionsMenuPortal 사용 여부를 type 필터 없이 재검색해 확인하세요 (예: rg -nC3 'OptionsMenuPortal').

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

🧹 Nitpick comments (3)
apps/client/src/pages/remind/Remind.tsx (3)

10-11: 빈 상태 컴포넌트는 지연 로딩(lazy) 고려

초기 번들을 줄여야 한다면 React.lazy + Suspense로 두 컴포넌트를 지연 로딩하는 것을 검토해주세요. 영향이 작다면 현 상태도 무방합니다.


40-41: 네이밍/표현 미세 개선

여기 값은 컴포넌트 타입이 아니라 JSX 엘리먼트입니다. EmptyStateComponent 대신 emptyState 정도로 이름을 바꾸거나, else 분기에서 바로 삼항을 인라인 렌더링하면 가독성이 좋아집니다.


79-80: 로딩 상태와 빈 상태 분리 제안

초기 로딩에 빈 상태가 깜빡 보일 수 있습니다. useGetRemindArticlesisLoading(또는 isFetching)을 노출받아 로딩/빈/컨텐츠 3-way 분기를 고려해주세요.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08d37d8 and 633efc1.

📒 Files selected for processing (2)
  • apps/client/src/pages/myBookmark/MyBookmark.tsx (2 hunks)
  • apps/client/src/pages/remind/Remind.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/client/src/pages/myBookmark/MyBookmark.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.
🧬 Code graph analysis (1)
apps/client/src/pages/remind/Remind.tsx (1)
apps/client/src/shared/components/sidebar/OptionsMenuPortal.tsx (1)
  • OptionsMenuPortal (15-45)
⏰ 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: storybook
🔇 Additional comments (4)
apps/client/src/pages/remind/Remind.tsx (4)

8-8: 경로 별칭(import) 정리 좋습니다.

모듈 경로가 명확해져 유지보수성이 좋아졌습니다.


36-38: 배지 타입을 리터럴 유니온으로 좁힌 점 좋습니다.

클릭 핸들러 시그니처도 일관되어 타입 안정성이 향상됐습니다.


61-63: 데이터 가드 후 그리드 렌더링 전환 👍

빈 배열/undefined 안전성이 개선되었습니다.


67-69: Card prop 매핑(타입/의도) 확인 필요

  • packages/design-system/src/components/card/Card.tsx (RemindProps)와 packages/design-system/src/components/card/RemindCard.tsx를 보면 remind 타입은 title: string, timeRemaining: string(스토리/RemindCard는 '3시간 25분' 같은 포맷의 문자열을 기대)입니다.
  • apps/client/src/pages/remind/Remind.tsx (줄 66–69) 현재: title={article.url} content={article.memo} timeRemaining={article.remindAt}
    • article.url 대신 article.title을 써야 하는지 확인하세요.
    • article.remindAt이 이미 사용자용 "남은 시간" 문자열인지(예: "3시간 25분") 아니면 ISO/timestamp인지 확인하고, ISO/timestamp면 표시 전에 포맷(remaining-time 문자열)으로 변환하세요.

Comment on lines 24 to +26
const getItemTitle = (id: number | null) =>
id == null ? '' : (REMIND_MOCK_DATA.find((d) => d.id === id)?.title ?? '');
const [activeBadge, setActiveBadge] = useState('notRead');
const [activeBadge, setActiveBadge] = useState<'read' | 'notRead'>('notRead');
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

카테고리명 조회가 목데이터에 의존 — 실제 API 데이터로 교체 필요

getItemTitleREMIND_MOCK_DATA를 사용하고 있어 실제 categoryId와 매칭되지 않을 가능성이 큽니다. 옵션 메뉴에 잘못된 이름(혹은 빈 문자열)이 전달될 수 있습니다. API 응답(data.articles) 기반으로 조회하도록 교체해주세요.

적용 예시:

- const getItemTitle = (id: number | null) =>
-   id == null ? '' : (REMIND_MOCK_DATA.find((d) => d.id === id)?.title ?? '');
+ const getCategoryName = (id: number | null) =>
+   id == null
+     ? ''
+     : data?.articles?.find((a) => a.category.categoryId === id)?.category
+         .categoryName ?? '';

추가로, 상단의 REMIND_MOCK_DATA import는 제거해주세요.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/client/src/pages/remind/Remind.tsx around lines 24-26, getItemTitle
currently looks up REMIND_MOCK_DATA which can mismatch real categoryIds; change
it to look up the title from the API response (data.articles or the correct
articles/categories array available in this component) using the passed id,
return empty string when not found, and remove the REMIND_MOCK_DATA import at
the top; ensure you handle the case where data is undefined (safe optional
chaining) and keep the function signature the same.

Comment on lines +82 to +97
<OptionsMenuPortal
open={menu.open}
style={style ?? undefined}
containerRef={containerRef}
categoryId={menu.categoryId}
getCategoryName={getItemTitle}
onEdit={() => {
setIsEditOpen(true);
closeMenu();
}}
onDelete={(id) => {
console.log('delete', id);
closeMenu();
}}
onClose={closeMenu}
/>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

옵션 메뉴에 실제 카테고리명 전달 + style 전달 단순화

상단 수정에 맞춰 getCategoryName을 넘기고, style ?? undefined는 불필요합니다.

-      <OptionsMenuPortal
-        open={menu.open}
-        style={style ?? undefined}
+      <OptionsMenuPortal
+        open={menu.open}
+        style={style}
         containerRef={containerRef}
         categoryId={menu.categoryId}
-        getCategoryName={getItemTitle}
+        getCategoryName={getCategoryName}
📝 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
<OptionsMenuPortal
open={menu.open}
style={style ?? undefined}
containerRef={containerRef}
categoryId={menu.categoryId}
getCategoryName={getItemTitle}
onEdit={() => {
setIsEditOpen(true);
closeMenu();
}}
onDelete={(id) => {
console.log('delete', id);
closeMenu();
}}
onClose={closeMenu}
/>
<OptionsMenuPortal
open={menu.open}
style={style}
containerRef={containerRef}
categoryId={menu.categoryId}
getCategoryName={getCategoryName}
onEdit={() => {
setIsEditOpen(true);
closeMenu();
}}
onDelete={(id) => {
console.log('delete', id);
closeMenu();
}}
onClose={closeMenu}
/>
🤖 Prompt for AI Agents
In apps/client/src/pages/remind/Remind.tsx around lines 82 to 97, the
OptionsMenuPortal should receive the actual category name and the style prop
should be simplified; replace the getCategoryName={getItemTitle} prop with
categoryName={getItemTitle(menu.categoryId)} (or the equivalent call to resolve
the name before passing) and change style={style ?? undefined} to simply
style={style} (or remove the prop when style is falsy) so the component gets a
string name and a direct style value.

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

🧹 Nitpick comments (2)
packages/design-system/src/components/card/BaseCard.tsx (2)

7-7: border 전환에 따른 클래스 유효성·접근성·레이아웃 영향 확인

  • 토큰: border-gray200가 Tailwind 테마에 실제 정의돼 있는지 확인 필요. 미정의면 기본색(현재컬러)로 렌더링됩니다. 필요 시 border-gray-200로 교정하세요.
  • 접근성: outline→border 전환으로 포커스 표시가 사라질 수 있습니다(카드가 클릭/포커스가능한 경우). 포커스 링을 추가하는 편이 안전합니다.
  • 레이아웃: border는 콘텐츠 영역을 1px 줄입니다(글로벌 box-sizing이 border-box라도 내부 영역 감소). 카드 내부 텍스트/줄바꿈에 영향 없는지 확인 부탁드립니다.

대안 A(색상 토큰 교정):

-<div className="border-gray200 w-[24.8rem] overflow-hidden rounded-[1.2rem] border bg-white">
+<div className="w-[24.8rem] overflow-hidden rounded-[1.2rem] bg-white border border-gray-200">

대안 B(레이아웃 영향 최소화: ring 사용, 포커스 가시성 확보):

-<div className="border-gray200 w-[24.8rem] overflow-hidden rounded-[1.2rem] border bg-white">
+<div className="w-[24.8rem] overflow-hidden rounded-[1.2rem] bg-white ring-1 ring-gray-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500">

1-3: BaseCard 확장성·타입 개선 제안(className/DOM props 패스스루)

디자인 시스템 컴포넌트로서 외부에서 className/aria/tabIndex 등 전달이 필요할 수 있습니다. 다음처럼 타입·시그니처를 확장하면 재사용성이 좋아집니다.

+import type { HTMLAttributes, ReactNode } from 'react';
-
-interface BaseCardProps {
-  children: React.ReactNode;
-}
+interface BaseCardProps extends HTMLAttributes<HTMLDivElement> {
+  children: ReactNode;
+  className?: string;
+}

-const BaseCard = ({ children }: BaseCardProps) => {
+const BaseCard = ({ children, className, ...props }: BaseCardProps) => {
   return (
-    <div className="border-gray200 w-[24.8rem] overflow-hidden rounded-[1.2rem] border bg-white">
+    <div
+      className={`w-[24.8rem] overflow-hidden rounded-[1.2rem] bg-white border border-gray-200 ${className ?? ''}`}
+      {...props}
+    >
       {children}
     </div>
   );
};

Also applies to: 5-5, 7-7

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 633efc1 and 317454f.

📒 Files selected for processing (2)
  • apps/client/src/layout/Layout.tsx (1 hunks)
  • packages/design-system/src/components/card/BaseCard.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/client/src/layout/Layout.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: constantly-dev
PR: Pinback-Team/pinback-client#2
File: pnpm-workspace.yaml:3-3
Timestamp: 2025-08-18T13:48:59.065Z
Learning: constantly-dev는 docs 디렉터리를 컨벤션 문서 추가용으로 사용할 예정이라고 명시했습니다.

Copy link
Collaborator

@jjangminii jjangminii left a comment

Choose a reason for hiding this comment

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

아티클 없을 때 분기처리까지 좋네요 👍 👍 고생하셨습니다

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api api 연결

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Api] 카테고리별 아티클 조회 API 연결 & 아티클 없는 경우 UI 분기 추가

2 participants