Conversation
읽고 있는 책, 내 약속 리스트, 즐겨찾는 모임 3개 섹션 구현. - BookCarousel: 호버 시 좌우 화살표, 스크롤 감지 캐러셀 - MyMeetingsSection: 커서 기반 페이지네이션, 펼치기/접기 - FavoriteGatheringsSection: 3열 그리드 즐겨찾기 모임 카드 - 각 섹션 스켈레톤 UI 적용 (Figma 디자인 규칙 기반) - useDeferredLoading 훅: 지연 표시 + 최소 표시 시간으로 깜빡임 방지
Walkthrough홈 페이지 섹션 추가(ReadingBooks, MyMeetings, FavoriteGatherings), BookCarousel 컴포넌트 도입, 개인 미팅 관련 타입·엔드포인트·API·훅 및 useDeferredLoading 훅과 PAGE_SIZES.MY_MEETINGS 상수 추가, Tooltip 임포트 경로 통합, 생성 후 툴팁 상태 처리 로직 추가. Changes
Sequence DiagramsequenceDiagram
participant User as 사용자
participant MyMeetingsSection as MyMeetingsSection
participant Hook as useMyMeetings Hook
participant API as Meetings API
participant Card as HomeMeetingCard
User->>MyMeetingsSection: 페이지 진입 / 탭 선택
MyMeetingsSection->>Hook: 쿼리 요청(myMeetingQueryKeys.list(filter))
Hook->>API: getMyMeetings(filter, startDateTime?, meetingId?)
API-->>Hook: 응답({ items, nextCursor, hasNext })
Hook-->>MyMeetingsSection: 데이터, isLoading, hasNextPage
MyMeetingsSection->>Card: 항목 렌더링(최대 표시 개수)
User->>MyMeetingsSection: '펼치기' 클릭
MyMeetingsSection->>Hook: fetchNextPage()
Hook->>API: 다음 페이지 요청(nextCursor)
API-->>Hook: 추가 항목 반환
Hook-->>MyMeetingsSection: 새로운 페이지 데이터
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
최근 4개 커밋만 리뷰해줘 |
🧩 Analysis chain
🏁 Script executed: Length of output: 152
해당 커밋들에 대한 리뷰를 시작하겠습니다. ✅ Actions performedReview triggered.
|
haruyam15
left a comment
There was a problem hiding this comment.
로딩 상태의 깜빡임을 방지하는 훅 멋지네여
| function formatMeetingDate(dateStr: string) { | ||
| const date = new Date(dateStr) | ||
| const yy = String(date.getFullYear()).slice(2) | ||
| const mm = String(date.getMonth() + 1).padStart(2, '0') | ||
| const dd = String(date.getDate()).padStart(2, '0') | ||
| const day = DAY_NAMES[date.getDay()] | ||
| const hh = String(date.getHours()).padStart(2, '0') | ||
| const min = String(date.getMinutes()).padStart(2, '0') | ||
| return `${yy}.${mm}.${dd}(${day}) ${hh}:${min}` | ||
| } | ||
|
|
||
| function getDDay(startDateTime: string): string { | ||
| const now = new Date() | ||
| const start = new Date(startDateTime) | ||
| const diffTime = start.getTime() - now.getTime() | ||
| const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) | ||
| if (diffDays === 0) return 'D-Day' | ||
| if (diffDays > 0) return `D-${diffDays}` | ||
| return '' | ||
| } | ||
|
|
There was a problem hiding this comment.
사용중인 date-fns 라이브러리 활용하면 짧게 할수있어요
(바꾸라는 말은 아님)
예시)
import { ko } from 'date-fns/locale'
function formatMeetingDate(dateStr: string) {
return format(new Date(dateStr), 'yy.MM.dd(eee) HH:mm', {
locale: ko,
})
}
function getDDay(startDateTime: string): string {
const diffDays = differenceInCalendarDays(
new Date(startDateTime),
new Date()
)
if (diffDays === 0) return 'D-Day'
if (diffDays > 0) return `D-${diffDays}`
return ''
}
| import type { MyMeetingFilter, MyMeetingListResponse } from '../meetings.types' | ||
| import { myMeetingQueryKeys } from './myMeetingQueryKeys' | ||
|
|
||
| const MY_MEETINGS_PAGE_SIZE = 4 |
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/features/meetings/hooks/index.ts (1)
1-15:⚠️ Potential issue | 🔴 CriticalCI 파이프라인 실패 — export 정렬 오류를 수정해야 합니다.
useMyMeetings와useMyMeetingTabCounts가 알파벳 순서를 위반하여 ESLintsimple-import-sort/exports규칙 오류가 발생합니다.useMeetingForm과usePlaceSearch사이로 이동해야 합니다.🔧 수정 제안
export * from './meetingQueryKeys' export * from './myMeetingQueryKeys' export * from './useCancelJoinMeeting' export * from './useConfirmMeeting' export * from './useCreateMeeting' export * from './useDeleteMeeting' export * from './useJoinMeeting' export * from './useMeetingApprovals' export * from './useMeetingDetail' export * from './useMeetingForm' +export * from './useMyMeetings' +export * from './useMyMeetingTabCounts' export * from './usePlaceSearch' -export * from './useMyMeetings' -export * from './useMyMeetingTabCounts' export * from './useRejectMeeting' export * from './useUpdateMeeting'또는
npx eslint --fix src/features/meetings/hooks/index.ts로 자동 수정할 수 있습니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/meetings/hooks/index.ts` around lines 1 - 15, The exports in the meetings hooks barrel are out-of-order and violate simple-import-sort/exports: move the exports for useMyMeetings and useMyMeetingTabCounts so they appear after useMeetingForm and before usePlaceSearch (i.e., reorder the lines exporting useMyMeetings and useMyMeetingTabCounts in src/features/meetings/hooks/index.ts), or run npx eslint --fix to auto-fix; ensure the exported symbol order now follows alphabetical order including useMeetingForm, useMyMeetings, useMyMeetingTabCounts, usePlaceSearch.
🧹 Nitpick comments (8)
src/pages/Home/components/FavoriteGatheringsSection.tsx (1)
53-53:onFavoriteToggle래퍼 제거 가능합니다.
mutate시그니처가(id: number) => void로onFavoriteToggle과 동일하므로 인라인 화살표 함수가 불필요합니다.♻️ 리팩터 제안
- onFavoriteToggle={(id) => toggleFavorite(id)} + onFavoriteToggle={toggleFavorite}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Home/components/FavoriteGatheringsSection.tsx` at line 53, The inline arrow wrapper around onFavoriteToggle is unnecessary because toggleFavorite already matches the signature (id: number) => void; replace onFavoriteToggle={(id) => toggleFavorite(id)} with onFavoriteToggle={toggleFavorite} in FavoriteGatheringsSection.tsx to remove the redundant wrapper and simplify the prop passing.src/pages/Home/components/MyMeetingsSection.tsx (2)
22-22:4매직 넘버가MY_MEETINGS_PAGE_SIZE와 분리됨
allItems.slice(0, 4)의4는useMyMeetings.ts의MY_MEETINGS_PAGE_SIZE와 동일한 값이어야 하지만, 해당 상수가 export되지 않아 각자 독립적으로 하드코딩되어 있습니다.MY_MEETINGS_PAGE_SIZE변경 시 이 값도 수동으로 변경해야 합니다.Line 60의
[...Array(4).keys()](스켈레톤 아이템 수)도 동일하게 해당 상수를 참조해야 합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Home/components/MyMeetingsSection.tsx` at line 22, Replace the hardcoded page-size literals in MyMeetingsSection (the slice call allItems.slice(0, 4) and the skeleton generator [...Array(4).keys()]) with the shared constant MY_MEETINGS_PAGE_SIZE: export MY_MEETINGS_PAGE_SIZE from useMyMeetings.ts (or wherever it’s defined), import it into MyMeetingsSection.tsx, and use MY_MEETINGS_PAGE_SIZE for both the isCollapsed slice (displayItems) and the skeleton array length so the page size is maintained from a single source of truth.
24-27:value as MyMeetingFilter타입 캐스트 개선 여지
onValueChange의value: string을MyMeetingFilter로 캐스팅합니다. 현재는TabsTriggervalue가 정확히'ALL' | 'UPCOMING' | 'DONE'으로 하드코딩되어 있어 런타임 안전하지만, 탭 값과 타입 간 정적 연결이 없습니다.♻️ 간단한 개선 예시
+const MY_MEETING_FILTERS = ['ALL', 'UPCOMING', 'DONE'] as const satisfies MyMeetingFilter[] + const handleTabChange = (value: string) => { - setActiveTab(value as MyMeetingFilter) + if ((MY_MEETING_FILTERS as readonly string[]).includes(value)) { + setActiveTab(value as MyMeetingFilter) + } setIsCollapsed(false) }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Home/components/MyMeetingsSection.tsx` around lines 24 - 27, handleTabChange currently casts a string to MyMeetingFilter (setActiveTab(value as MyMeetingFilter)), which is brittle; change the handler and its caller so the tab value is typed as MyMeetingFilter instead of string (e.g., declare handleTabChange(value: MyMeetingFilter) and update the Tabs/TabsTrigger onValueChange typing) or explicitly validate/map the incoming string to a MyMeetingFilter before calling setActiveTab. Update references: handleTabChange, setActiveTab, MyMeetingFilter, TabsTrigger, and the onValueChange usage so the value is statically typed and no runtime cast is required.src/features/meetings/hooks/useMyMeetings.ts (1)
7-7:MY_MEETINGS_PAGE_SIZE를 export하거나 공유 상수로 이동 권장이 상수가
MyMeetingsSection.tsx의slice(0, 4)및Array(4)와 값을 공유해야 하지만, 현재 모듈 내부에만 있어 두 곳에서4가 각자 하드코딩됩니다. 페이지 크기 변경 시 둘 다 수동으로 바꿔야 하는 유지보수 리스크가 생깁니다.♻️ 제안: 상수 export
-const MY_MEETINGS_PAGE_SIZE = 4 +export const MY_MEETINGS_PAGE_SIZE = 4
MyMeetingsSection.tsx(및 skeleton Array 생성)에서 import해서 사용:+import { MY_MEETINGS_PAGE_SIZE } from '@/features/meetings/hooks/useMyMeetings' ... -const displayItems = isCollapsed ? allItems.slice(0, 4) : allItems +const displayItems = isCollapsed ? allItems.slice(0, MY_MEETINGS_PAGE_SIZE) : allItems ... -{[...Array(4).keys()].map((i) => ( +{[...Array(MY_MEETINGS_PAGE_SIZE).keys()].map((i) => (🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/meetings/hooks/useMyMeetings.ts` at line 7, 현재 모듈 내부에 하드코딩된 MY_MEETINGS_PAGE_SIZE(값 4)를 export하거나 공통 상수 모듈로 옮겨 MyMeetingsSection.tsx에서 slice(0, 4)와 Array(4) 대신 해당 상수를 import해 사용하도록 변경하세요; 구체적으로 useMyMeetings.ts의 MY_MEETINGS_PAGE_SIZE를 export하거나 src/constants 등 공통 파일로 이동한 후 MyMeetingsSection.tsx의 slice 및 스켈레톤 생성 부분에서 4를 MY_MEETINGS_PAGE_SIZE로 대체해 일관된 단일 소스에서 페이지 크기를 관리하도록 수정합니다.src/pages/Home/components/HomeMeetingCard.tsx (1)
128-132:isDone버튼 클릭 시 아무 동작 없음 (TODO 미구현)"개인 회고 작성하기" 버튼이
handleActionClick을 호출하지만, 핸들러 내부에서isDone분기가 없어stopPropagation만 실행되고 네비게이션이 발생하지 않습니다. 사용자 입장에서는 반응 없는 버튼처럼 보입니다.개인 회고 작성 페이지 라우트가 확정되면 연결하는 코드를 생성해 드릴 수 있습니다. 새 이슈로 추적하길 원하시면 알려주세요.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Home/components/HomeMeetingCard.tsx` around lines 128 - 132, The button labeled "개인 회고 작성하기" calls handleActionClick but currently only stopPropagation is implemented; update handleActionClick in HomeMeetingCard to handle the isDone case by performing navigation to the personal retrospective page (e.g., calling your router/navigation utility such as router.push or useNavigate) when isDone is true, ensuring you still call event.stopPropagation() first and pass any needed meeting identifiers (meetingId/meeting) from the component props so the target route can load the correct context.src/features/meetings/meetings.types.ts (1)
204-204: 같은 파일에 유사한ProgressStatus타입 두 개 공존
MeetingProgressStatus = 'PRE' | 'ONGOING' | 'POST'(line 204)와MyMeetingProgressStatus = 'UPCOMING' | 'ONGOING' | 'DONE' | 'UNKNOWN'(line 262)가'ONGOING'을 공유하면서도 서로 다른 값 집합을 가집니다. 백엔드 컨텍스트(상세 조회 vs 대시보드 목록)가 달라 분리가 필요함을 이해하지만, 타입 이름만으로는 둘의 차이가 명확하지 않아 혼동을 유발할 수 있습니다. 짧은 구분 주석 추가를 권장합니다.♻️ 구분 주석 추가 예시
+/** 약속 상세 조회(GetMeetingDetailResponse)용 진행 상태 */ export type MeetingProgressStatus = 'PRE' | 'ONGOING' | 'POST' // ... +/** 메인페이지 대시보드 목록(MyMeetingListItem)용 진행 상태 — 서버 시간 기준 */ export type MyMeetingProgressStatus = 'UPCOMING' | 'ONGOING' | 'DONE' | 'UNKNOWN'Also applies to: 262-262
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/meetings/meetings.types.ts` at line 204, Add a short distinguishing comment above each of the two similar types to clarify their intended context and value meanings: for MeetingProgressStatus ('PRE' | 'ONGOING' | 'POST') note it represents backend detail-level lifecycle stages (pre-meeting, in-progress, post-meeting), and for MyMeetingProgressStatus ('UPCOMING' | 'ONGOING' | 'DONE' | 'UNKNOWN') indicate it’s used for dashboard/listing views (upcoming vs completed vs unknown); reference the type names MeetingProgressStatus and MyMeetingProgressStatus so future readers understand why both exist and how their shared 'ONGOING' differs semantically.src/features/book/components/BookCarousel.tsx (1)
49-52:scroll-smoothCSS 클래스 중복 설정
scrollBy의behavior옵션에'smooth'를 명시적으로 전달하면 CSSscroll-behavior값은 무시됩니다. 화살표 버튼 클릭은 모두scrollBy({ behavior: 'smooth' })를 사용하므로, 컨테이너에 적용된scroll-smoothTailwind 클래스는 실질적으로 효과가 없습니다. 이 캐러셀 안에는 앵커 링크도 없어 CSS 클래스가 동작할 다른 경로도 없습니다.♻️ 제안
- <div ref={scrollRef} className="flex gap-large overflow-x-auto scrollbar-hide scroll-smooth"> + <div ref={scrollRef} className="flex gap-large overflow-x-auto scrollbar-hide">🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/features/book/components/BookCarousel.tsx` around lines 49 - 52, The carousel container in BookCarousel currently includes the Tailwind class 'scroll-smooth' which is redundant because all programmatic scrolling uses scrollBy({ behavior: 'smooth' }); remove the 'scroll-smooth' class from the div with ref={scrollRef} (the element rendering children in BookCarousel) to avoid duplicate/unused CSS; ensure you only alter the className string (leave cn('group/carousel relative', className) and the scrollRef/children usage unchanged.src/pages/Home/components/ReadingBooksSection.tsx (1)
15-15:activeTab상태가 데이터 패칭에 연결되지 않음 — 탭 활성화 시 필터링 무동작
activeTab이useBooks호출에 전달되지 않습니다. 현재pre/post탭이disabled라 사용자가 선택할 수 없지만, 향후 탭을 활성화할 때 상태는 바뀌나 데이터가 변하지 않는 버그가 잠재합니다. 지금 당장 수정이 어렵다면 TODO 주석으로 명시해두는 것을 권장합니다.♻️ 향후 탭 필터 연결 시 구조 참고
- const { data, isLoading } = useBooks({ status: 'READING' }) + // TODO: pre/post 탭 활성화 시 filter 파라미터 연결 필요 + // const bookFilter = activeTab === 'pre' ? 'BEFORE_MEETING' : activeTab === 'post' ? 'AFTER_MEETING' : undefined + const { data, isLoading } = useBooks({ status: 'READING' /*, filter: bookFilter */ })Also applies to: 31-43
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Home/components/ReadingBooksSection.tsx` at line 15, activeTab state is not passed into the data fetching hook, so changing tabs won't update results; update the useBooks call to accept activeTab (or its derived filter) so the hook/query refetches when activeTab changes, e.g., wire activeTab into the dependency/parameter of useBooks used in ReadingBooksSection (reference activeTab, setActiveTab, and useBooks), or if you can't implement it now add a clear TODO comment next to the useState declaration explaining that activeTab must be forwarded to useBooks to enable tab-based filtering in future.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/Home/components/FavoriteGatheringsSection.tsx`:
- Around line 25-58: The empty-state is flashing during the 0–200ms loading gap
because showSkeleton can be false while the fetched data is still undefined,
causing gatherings to be treated as an empty array and the "즐겨찾기한 모임이 없어요."
message to render; update the render branches in FavoriteGatheringsSection to
explicitly check for undefined data (the variable used to derive gatherings) and
return null when data === undefined (i.e., before rendering either skeleton or
empty state), and apply the same fix to MyMeetingsSection and
ReadingBooksSection so they also skip rendering until the data is received.
In `@src/pages/Home/components/HomeMeetingCard.tsx`:
- Around line 25-33: getDDay currently uses Math.ceil on millisecond diff which
makes same-day future meetings show as D-1; change getDDay to compare calendar
dates instead: create midnight-truncated Date objects for now and start (e.g.,
zero out hours/mins/secs or use start.toDateString()/now.toDateString()),
compute diffDays = (startMidnight.getTime() - nowMidnight.getTime()) /
(1000*60*60*24) and then return 'D-Day' when diffDays === 0, 'D-{diffDays}' when
diffDays > 0, otherwise ''. Keep this logic inside getDDay to preserve behavior
elsewhere.
In `@src/pages/Home/components/HomeSectionHeader.tsx`:
- Line 10: The build fails because TypeScript references React.ReactNode without
importing React; update the components that declare children?: React.ReactNode
(notably HomeSectionHeader and Card) to import the type from React and use it
directly — e.g., add import type { ReactNode } from 'react' and change the prop
type to children?: ReactNode (or alternatively add import * as React from
'react' if you prefer the namespace import) so the compiler finds the React
type.
In `@src/pages/Home/HomePage.tsx`:
- Around line 13-16: The greeting renders an empty name while useUserProfile is
loading; update the HomePage component to check the loading/ready state from
useUserProfile (e.g., isLoading or user being undefined) and render a
placeholder or skeleton instead of using user?.nickname ?? '' in the h1;
specifically change the h1 that currently uses user?.nickname to conditionally
render `안녕하세요, ${user.nickname}님!` only when user exists (or show a Skeleton
component) and otherwise render a neutral placeholder or nothing until
useUserProfile indicates loaded.
---
Outside diff comments:
In `@src/features/meetings/hooks/index.ts`:
- Around line 1-15: The exports in the meetings hooks barrel are out-of-order
and violate simple-import-sort/exports: move the exports for useMyMeetings and
useMyMeetingTabCounts so they appear after useMeetingForm and before
usePlaceSearch (i.e., reorder the lines exporting useMyMeetings and
useMyMeetingTabCounts in src/features/meetings/hooks/index.ts), or run npx
eslint --fix to auto-fix; ensure the exported symbol order now follows
alphabetical order including useMeetingForm, useMyMeetings,
useMyMeetingTabCounts, usePlaceSearch.
---
Nitpick comments:
In `@src/features/book/components/BookCarousel.tsx`:
- Around line 49-52: The carousel container in BookCarousel currently includes
the Tailwind class 'scroll-smooth' which is redundant because all programmatic
scrolling uses scrollBy({ behavior: 'smooth' }); remove the 'scroll-smooth'
class from the div with ref={scrollRef} (the element rendering children in
BookCarousel) to avoid duplicate/unused CSS; ensure you only alter the className
string (leave cn('group/carousel relative', className) and the
scrollRef/children usage unchanged.
In `@src/features/meetings/hooks/useMyMeetings.ts`:
- Line 7: 현재 모듈 내부에 하드코딩된 MY_MEETINGS_PAGE_SIZE(값 4)를 export하거나 공통 상수 모듈로 옮겨
MyMeetingsSection.tsx에서 slice(0, 4)와 Array(4) 대신 해당 상수를 import해 사용하도록 변경하세요;
구체적으로 useMyMeetings.ts의 MY_MEETINGS_PAGE_SIZE를 export하거나 src/constants 등 공통 파일로
이동한 후 MyMeetingsSection.tsx의 slice 및 스켈레톤 생성 부분에서 4를 MY_MEETINGS_PAGE_SIZE로 대체해
일관된 단일 소스에서 페이지 크기를 관리하도록 수정합니다.
In `@src/features/meetings/meetings.types.ts`:
- Line 204: Add a short distinguishing comment above each of the two similar
types to clarify their intended context and value meanings: for
MeetingProgressStatus ('PRE' | 'ONGOING' | 'POST') note it represents backend
detail-level lifecycle stages (pre-meeting, in-progress, post-meeting), and for
MyMeetingProgressStatus ('UPCOMING' | 'ONGOING' | 'DONE' | 'UNKNOWN') indicate
it’s used for dashboard/listing views (upcoming vs completed vs unknown);
reference the type names MeetingProgressStatus and MyMeetingProgressStatus so
future readers understand why both exist and how their shared 'ONGOING' differs
semantically.
In `@src/pages/Home/components/FavoriteGatheringsSection.tsx`:
- Line 53: The inline arrow wrapper around onFavoriteToggle is unnecessary
because toggleFavorite already matches the signature (id: number) => void;
replace onFavoriteToggle={(id) => toggleFavorite(id)} with
onFavoriteToggle={toggleFavorite} in FavoriteGatheringsSection.tsx to remove the
redundant wrapper and simplify the prop passing.
In `@src/pages/Home/components/HomeMeetingCard.tsx`:
- Around line 128-132: The button labeled "개인 회고 작성하기" calls handleActionClick
but currently only stopPropagation is implemented; update handleActionClick in
HomeMeetingCard to handle the isDone case by performing navigation to the
personal retrospective page (e.g., calling your router/navigation utility such
as router.push or useNavigate) when isDone is true, ensuring you still call
event.stopPropagation() first and pass any needed meeting identifiers
(meetingId/meeting) from the component props so the target route can load the
correct context.
In `@src/pages/Home/components/MyMeetingsSection.tsx`:
- Line 22: Replace the hardcoded page-size literals in MyMeetingsSection (the
slice call allItems.slice(0, 4) and the skeleton generator [...Array(4).keys()])
with the shared constant MY_MEETINGS_PAGE_SIZE: export MY_MEETINGS_PAGE_SIZE
from useMyMeetings.ts (or wherever it’s defined), import it into
MyMeetingsSection.tsx, and use MY_MEETINGS_PAGE_SIZE for both the isCollapsed
slice (displayItems) and the skeleton array length so the page size is
maintained from a single source of truth.
- Around line 24-27: handleTabChange currently casts a string to MyMeetingFilter
(setActiveTab(value as MyMeetingFilter)), which is brittle; change the handler
and its caller so the tab value is typed as MyMeetingFilter instead of string
(e.g., declare handleTabChange(value: MyMeetingFilter) and update the
Tabs/TabsTrigger onValueChange typing) or explicitly validate/map the incoming
string to a MyMeetingFilter before calling setActiveTab. Update references:
handleTabChange, setActiveTab, MyMeetingFilter, TabsTrigger, and the
onValueChange usage so the value is statically typed and no runtime cast is
required.
In `@src/pages/Home/components/ReadingBooksSection.tsx`:
- Line 15: activeTab state is not passed into the data fetching hook, so
changing tabs won't update results; update the useBooks call to accept activeTab
(or its derived filter) so the hook/query refetches when activeTab changes,
e.g., wire activeTab into the dependency/parameter of useBooks used in
ReadingBooksSection (reference activeTab, setActiveTab, and useBooks), or if you
can't implement it now add a clear TODO comment next to the useState declaration
explaining that activeTab must be forwarded to useBooks to enable tab-based
filtering in future.
- hooks/index.ts: simple-import-sort 위반 수정 (useMyMeetings, useMyMeetingTabCounts 순서) - HomeMeetingCard: date-fns로 날짜 포맷 변경, getDDay 캘린더 기준 계산으로 수정 - pagination.ts: MY_MEETINGS 페이지 사이즈 상수 추가 - useMyMeetings: 로컬 상수 제거 후 PAGE_SIZES.MY_MEETINGS 사용 - HomeSectionHeader: ReactNode import type 추가 - BookCarousel: ReactNode import type 추가, 중복 scroll-smooth 제거 - HomePage: 로딩 중 닉네임 빈 문자열 노출 방지 - FavoriteGatheringsSection: data 미로드 시 null 반환, toggleFavorite 불필요한 래퍼 제거 - MyMeetingsSection: 하드코딩된 숫자를 PAGE_SIZES.MY_MEETINGS로 교체
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/pages/Home/components/HomeSectionHeader.tsx (1)
29-37: [선택] 장식용 아이콘에aria-hidden추가 권장
ChevronRight는 장식용 아이콘이지만aria-hidden이 없어 스크린 리더가 아이콘 이름을 함께 읽을 수 있습니다.♻️ 제안 수정
- <ChevronRight className="size-4" /> + <ChevronRight className="size-4" aria-hidden="true" />🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Home/components/HomeSectionHeader.tsx` around lines 29 - 37, ChevronRight is a decorative icon so add aria-hidden="true" (and if applicable focusable="false") on the ChevronRight element in the HomeSectionHeader component to prevent screen readers from announcing it; update the JSX where ChevronRight is rendered (the ChevronRight component usage inside the Link) to pass aria-hidden="true" so the icon is ignored by assistive tech.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/Home/components/HomeMeetingCard.tsx`:
- Around line 57-63: In HomeMeetingCard update the incomplete handleActionClick
in the HomeMeetingCard component so clicks on the "isDone" / 개인 회고 작성하기 button
don't block card navigation without doing anything: either (A) make the isDone
branch set the button to disabled (or return early) so the click only calls
e.stopPropagation() and shows the button as disabled, or (B) remove the button
from render until the 회고 page implementation exists; locate the logic in
handleActionClick and the button render near the isDone-related branches (also
applied at the duplicate location around lines where the other isDone handling
exists) and ensure UX indicates the action is unavailable rather than silently
swallowing clicks.
---
Duplicate comments:
In `@src/pages/Home/components/HomeMeetingCard.tsx`:
- Around line 19-24: Update the getDDay implementation to use
differenceInCalendarDays (already done) instead of the prior Math.ceil approach:
in function getDDay(startDateTime: string) call differenceInCalendarDays(new
Date(startDateTime), new Date()), return 'D-Day' when diffDays === 0, return
`D-${diffDays}` when diffDays > 0, and return an empty string otherwise; ensure
getDDay is the single source of truth for D-Day formatting and remove any
remaining Math.ceil-based logic elsewhere.
In `@src/pages/Home/components/HomeSectionHeader.tsx`:
- Around line 1-5: The previous compile error caused by missing React.ReactNode
is resolved by adding the explicit import line "import type { ReactNode } from
'react'" in HomeSectionHeader (and using ReactNode directly), so no code changes
are required—confirm the import of ReactNode remains and keep using ReactNode in
HomeSectionHeader.tsx.
---
Nitpick comments:
In `@src/pages/Home/components/HomeSectionHeader.tsx`:
- Around line 29-37: ChevronRight is a decorative icon so add aria-hidden="true"
(and if applicable focusable="false") on the ChevronRight element in the
HomeSectionHeader component to prevent screen readers from announcing it; update
the JSX where ChevronRight is rendered (the ChevronRight component usage inside
the Link) to pass aria-hidden="true" so the icon is ignored by assistive tech.
- BookCarousel.tsx: Prettier 포맷 수정 (CI format:check 실패 원인) - HomeMeetingCard: 개인 회고 페이지 미연결 상태에서 isDone 버튼 disabled 처리 (클릭 시 stopPropagation만 호출되어 카드 네비게이션을 막는 문제 수정)
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/pages/Home/components/HomeMeetingCard.tsx (1)
15-17:new Date()래핑 제거 가능 (date-fns v4)date-fns v4에서는
format(),differenceInCalendarDays()가 문자열 argument를 직접 지원하므로:function formatMeetingDate(dateStr: string) { - return format(new Date(dateStr), 'yy.MM.dd(eee) HH:mm', { locale: ko }) + return format(dateStr, 'yy.MM.dd(eee) HH:mm', { locale: ko }) } function getDDay(startDateTime: string): string { - const diffDays = differenceInCalendarDays(new Date(startDateTime), new Date()) + const diffDays = differenceInCalendarDays(startDateTime, new Date()) }실제
startDateTime은'2026-02-01T14:00:00'형식(Z suffix 없음)이므로 현재 타임존 처리는 의도대로 동작합니다.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/Home/components/HomeMeetingCard.tsx` around lines 15 - 17, formatMeetingDate currently wraps the string in new Date before calling format; in date-fns v4 you can pass the ISO string directly, so remove the new Date(...) wrapper in formatMeetingDate and pass dateStr straight to format (keep the 'yy.MM.dd(eee) HH:mm' pattern and { locale: ko } locale). Also, if you have other date-fns usages like differenceInCalendarDays elsewhere that take startDateTime (e.g., '2026-02-01T14:00:00'), update them similarly to accept the string directly so timezone handling remains consistent.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@src/pages/Home/components/HomeMeetingCard.tsx`:
- Around line 119-124: The Button rendered when isDone currently has disabled
set to true as a temporary guard; when you implement navigation to the personal
review page, remove the disabled prop and add an onClick handler that navigates
to the personal review route (or triggers the open-review flow) from
HomeMeetingCard.tsx — update the Button component instance and the surrounding
TODO comment (remove or replace with TODO: implement navigation) so the Button
no longer blocks interaction once onClick is wired.
- Around line 19-24: The D-Day calculation previously showed "D-1" for same-day
events; confirm the getDDay function uses differenceInCalendarDays to compare
new Date(startDateTime) with new Date() and returns 'D-Day' when diffDays === 0,
'D-{diffDays}' when diffDays > 0, and '' otherwise—no further changes required
beyond this implementation in getDDay.
---
Nitpick comments:
In `@src/pages/Home/components/HomeMeetingCard.tsx`:
- Around line 15-17: formatMeetingDate currently wraps the string in new Date
before calling format; in date-fns v4 you can pass the ISO string directly, so
remove the new Date(...) wrapper in formatMeetingDate and pass dateStr straight
to format (keep the 'yy.MM.dd(eee) HH:mm' pattern and { locale: ko } locale).
Also, if you have other date-fns usages like differenceInCalendarDays elsewhere
that take startDateTime (e.g., '2026-02-01T14:00:00'), update them similarly to
accept the string directly so timezone handling remains consistent.
🚀 풀 리퀘스트 제안
📋 작업 내용
로그인한 사용자의 대시보드 역할을 하는 메인페이지를 구현했습니다.
읽고 있는 책, 내 약속 리스트, 즐겨찾는 모임 3개 섹션으로 구성됩니다.
🔧 변경 사항
📸 스크린샷 (선택 사항)
📄 기타
Summary by CodeRabbit
새로운 기능
사용성 개선