๐API & โจFeat : ์ด๋ฒคํธ ํ์ด์ง api ์ฐ๊ฒฐ#123
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough์ด PR์ ์ด๋ฒคํธ ๊ธฐ๋ฅ์ ์ ์ ๋ฐ์ดํฐ์์ API ๊ธฐ๋ฐ ํ (์กฐํยท์์ธยท๋ถ๋งํฌ)๊ณผ ํ์ ยท๋งคํผยท์ ํธ๋ก ์ ํํ๊ณ , EventCard์ ๋ถ๋งํฌ/๋ก๊ทธ์ธ ํ๋ฆ์ ํตํฉํ๋ฉฐ Next.js ์ด๋ฏธ์ง remotePatterns ๋ฐ ์ผ๋ถ UI z-index๋ฅผ ์กฐ์ ํฉ๋๋ค. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant Page as "ํ์ด์ง (๋ชฉ๋ก/์์ธ)"
participant Hook as "useEvents / useEventDetail"
participant RQ as "React Query"
participant API as "Events API"
participant BK as "useBookmark"
User->>Page: ์ ๊ทผ (date ๋๋ eventId ํฌํจ)
Page->>Hook: ์์ฒญ (date ๋๋ eventId)
Hook->>RQ: useQuery ์คํ
RQ->>API: fetchEvents / fetchEventDetail ํธ์ถ
API-->>RQ: ์๋ต ๋ฐํ
RQ-->>Hook: ๋ฐ์ดํฐ ์ ๋ฌ
Hook-->>Page: ์ด๋ฒคํธ ๋ฐ์ดํฐ ์ ๊ณต
Page->>Page: ๋ ๋ (Image, EventCard, ๊ด๋ จ ์ด๋ฒคํธ)
alt ๋ถ๋งํฌ ํ ๊ธ
User->>Page: ๋ถ๋งํฌ ํด๋ฆญ
Page->>BK: toggleBookmark()
BK->>API: postBookmark / deleteBookmark
API-->>BK: ์๋ต
BK->>RQ: ์บ์ ๋ฌดํจํ ['events'], ['eventDetail', id], ['myPage']
RQ->>API: ์ฌ์์ฒญ
API-->>RQ: ์ต์ ์๋ต
RQ-->>Page: UI ์
๋ฐ์ดํธ
end
Estimated code review effort๐ฏ 4 (Complex) | โฑ๏ธ ~45๋ถ ์ฃผ์๊ฐ ํ์ํ ํ์ผ/์์ญ:
Possibly related issues
Possibly related PRs
Suggested labels
Suggested reviewers
Poem
Pre-merge checks and finishing touchesโ Failed checks (1 warning, 1 inconclusive)
โ Passed checks (1 passed)
โจ 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 |
|
๐ท๏ธ Labeler has automatically applied labels based on your PR title, branch name, or commit message. |
|
๐ท๏ธ Labeler has automatically applied labels based on your PR title, branch name, or commit message. |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and canโt be posted inline due to platform limitations.
โ ๏ธ Outside diff range comments (1)
src/shared/components/container/LocationCard.tsx (1)
24-28: ์ฌ์ฉ๋์ง ์๋ ์ํ ์ ๊ฑฐ ํ์
liked์ํ์handleLikeClickํธ๋ค๋ฌ๊ฐ ์ ์๋์ด ์์ง๋ง ๋ ์ด์ ๋ ๋๋ง๋ ์์์ ์ฐ๊ฒฐ๋์ด ์์ง ์์ต๋๋ค(Lines 90-103์ ์์ด์ฝ์ด ์ฃผ์ ์ฒ๋ฆฌ๋จ). ์ฌ์ฉํ์ง ์๋ ์ฝ๋๋ ์ ๊ฑฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค.- const [liked, setLiked] = useState(false); - - const handleLikeClick = () => { - setLiked((prev) => !prev); - };
๐งน Nitpick comments (11)
src/shared/components/set/PopupSet.tsx (1)
27-27: LGTM! Overlay์์ z-index ๊ณ์ธต ๊ตฌ์กฐ๊ฐ ์ฌ๋ฐ๋ฆ ๋๋คPopupSet(z-100)์ด Overlay(z-99) ์์ ๋ ๋๋ง๋๋๋ก z-index๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ์กฐ์ ๋์์ต๋๋ค. ํ์ง๋ง ์ ์ง๋ณด์์ฑ์ ์ํด Overlay.tsx์ ๋์ผํ z-index ์์๋ฅผ ๊ณต์ ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
์์:
// ๊ณต์ ์์ ํ์ผ์์ import { Z_INDEX } from '@/shared/constants/z-index'; // PopupSet์์ ์ฌ์ฉ className={cn( `fixed inset-0 z-[${Z_INDEX.POPUP}]`, 'flex items-center justify-center', )}src/shared/overlay/Overlay.tsx (1)
12-12: z-index ๊ตฌ์กฐ ๊ฐ์ ๋ฐ ์์ํ ๊ถ์ฅz-index๊ฐ 99๋ก ๋ณ๊ฒฝ๋์ด PopupSet(z-100)๋ณด๋ค ์๋์ ์์นํ๋ ๊ฒ์ด ํ์ธ๋์์ต๋๋ค. ๋ค๋ง ๋ค์ ์ฌํญ์ ๊ฐ์ ํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค:
- Header์ PopupSet ์ถฉ๋: ๋ ์ปดํฌ๋ํธ ๋ชจ๋ z-[100]์ ์ฌ์ฉํ๊ณ ์์ด, ๊ฐ์ ์คํ ์ปจํ ์คํธ์์ ์๊ธฐ์น ์์ ๋ ์ด์ด๋ง ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
- ์์ํ ๋ถ์ฌ: ํ์ฌ z-index ๊ฐ๋ค์ด ์ฌ๋ฌ ํ์ผ์ ํ๋์ฝ๋ฉ๋์ด ์์ต๋๋ค(
src/shared/components/header/Header.tsx,src/shared/components/set/PopupSet.tsx,src/shared/components/set/LocationBubbleSet.tsx๋ฑ). ์ผ๊ด์ฑ ์๋ ๊ด๋ฆฌ๋ฅผ ์ํด ๋ค์๊ณผ ๊ฐ์ด ์์ ํ์ผ์ ์์ฑํ๊ธฐ๋ฅผ ๊ถ์ฅํฉ๋๋ค:// src/shared/constants/zIndex.ts export const Z_INDEX = { OVERLAY: 99, POPUP: 100, HEADER: 101, CONTROL_BAR: 50, LOCATION_BUBBLE: 50, } as const;์ดํ ๊ฐ ์ปดํฌ๋ํธ์์ ํ๋์ฝ๋ฉ๋ ๊ฐ ๋์ ์์๋ฅผ ์ฐธ์กฐํ๋๋ก ์ ๋ฐ์ดํธํ์ธ์.
src/shared/components/container/LocationCard.tsx (1)
90-103: ๋ถ๋งํฌ ๊ธฐ๋ฅ ํตํฉ ๊ณ ๋ ค ํ์ํํธ ์์ด์ฝ์ด ์ฃผ์ ์ฒ๋ฆฌ๋์ด ์์ต๋๋ค. PR์ ๋ค๋ฅธ ๋ณ๊ฒฝ์ฌํญ์ ๋ณด๋ฉด
EventCard์์useBookmarkํ ์ ์ฌ์ฉํ์ฌ ๋ถ๋งํฌ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ณ ์๋๋ฐ,LocationCard์๋ ๋์ผํ ํจํด์ ์ ์ฉํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์. PR ์ค๋ช ์ "๋ถ๋งํฌ UI ๋๊ธฐํ ํ์ธ (todo)"์ ๊ด๋ จ์ด ์๋ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
EventCard์ ๋์ผํ ๋ฐฉ์์ผ๋ก ๋ถ๋งํฌ ๊ธฐ๋ฅ์ ํตํฉํ๋ ์ฝ๋๋ฅผ ์์ฑํด๋๋ฆด๊น์?src/shared/hooks/events/useEventDetail.ts (1)
10-13: ์๋ฌ ์ฒ๋ฆฌ ์ถ๊ฐ ๊ถ์ฅAPI ํธ์ถ ์คํจ ์ ๋ ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ํด ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
const fetchEventDetail = async (eventId: number): Promise<EventDetail> => { try { const res = await apiWithToken.get<EventDetailResponse>(`/api/events/${eventId}`); return res.data.data; } catch (error) { console.error('Failed to fetch event detail:', error); throw error; } };src/shared/hooks/events/useEvents.ts (1)
12-12: Non-null assertion ์ฌ์ฉLine 12์์
formattedDate!์ non-null assertion์ ์ฌ์ฉํ๊ณ ์์ต๋๋ค.enabled: !!formattedDate๊ฐ๋ ๋๋ถ์ ์์ ํ์ง๋ง, ๋ ๋ช ์์ ์ธ ํ์ ์ฒดํฌ๋ฅผ ์ ํธํ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์์ ํ ์ ์์ต๋๋ค.const { data, isLoading, isError } = useQuery({ queryKey: ['events', formattedDate], - queryFn: () => fetchEvents(formattedDate!), + queryFn: () => formattedDate ? fetchEvents(formattedDate) : Promise.resolve({ data: [] }), enabled: !!formattedDate, });next.config.ts (1)
19-26:domains์ค์ ์remotePatterns๋ก ๋ง์ด๊ทธ๋ ์ด์ ๊ถ์ฅNext.js 14๋ถํฐ
domains์ต์ ์ด deprecated๋์์ผ๋ฉฐ,remotePatterns๋ protocol, hostname, port, path๋ฅผ ๋ชจ๋ ์ ํํ ์ ์์ด ๋ ์์ ํฉ๋๋ค. ์ผ๊ด์ฑ๊ณผ ๋ณด์์ ์ํด ๋ชจ๋ ์ด๋ฏธ์ง ๋๋ฉ์ธ์remotePatterns๋ก ๋ง์ด๊ทธ๋ ์ด์ ํ์ธ์.images: { - domains: ['maps.googleapis.com'], remotePatterns: [ + { protocol: 'https', hostname: 'maps.googleapis.com' }, { protocol: 'https', hostname: 'mblogthumb-phinf.pstatic.net' }, { protocol: 'https', hostname: 'blogfiles.pstatic.net' }, { protocol: 'https', hostname: 'postfiles.pstatic.net' }, ], },src/shared/types/eventtypes.ts (1)
1-40: EventResponse โ EventData ํ๋ ๋ค์ด๋ฐ/ํ์ ์ฌ๋ถ ์ ํฉ์ฑ ํ์ธ ์ ์
EventResponse์EventData์ฌ์ด์ ์๋์ ๊ฐ์ด ์๋ฏธ๊ฐ ๊ฐ์๋ฐ ์ด๋ฆ/ํ์ ์ฌ๋ถ๊ฐ ๋ค๋ฅธ ํ๋๋ค์ด ์์ต๋๋ค (Line 1โ21):
titleโnamebodyโdescriptionisBookmarked?โliked(ํ์)address?,startDate?,endDate?โaddress,startDate,endDate(ํ์)๋งคํ ์ ํธ(์:
mapEvent)์์ ์ด ์ฐจ์ด๋ฅผ ๋ชจ๋ ์๋์ ์ผ๋ก ์ฒ๋ฆฌํ๊ณ ์๋์ง ํ ๋ฒ๋ง ๋ ํ์ธํด ์ฃผ์ธ์. ํนํ API์์ ํ๋๊ฐ ๋๋ฝ๋ ์ ์๋ ๊ฒฝ์ฐ(์ฃผ์/๋ ์ง/๋ถ๋งํฌ ์ฌ๋ถ ๋ฑ)์ ํ๋ก ํธ์์ ์์ ํ๊ฒ ๊ธฐ๋ณธ๊ฐ์ ๋ฃ๊ณ ์๋์ง ์ฒดํฌํด ๋๋ฉด ์ถํ ๋ฐํ์ ์๋ฌ๋ฅผ ์ค์ผ ์ ์์ต๋๋ค.src/pages/events/index.tsx (1)
38-48: DatePicker ์ ๊ทผ์ฑ: label ์ฐ๋ ์ฌ๋ถ ํ์ธ ํ์Line 40โ47์์ SR-only
<label htmlFor='event-date'>๋ฅผ ๋๊ณ ์๋๋ฐ,DatePicker์๋id๋inputId๊ฐ์ prop์ด ์ ๋ฌ๋์ง ์๊ณ ์์ต๋๋ค.DatePicker๋ด๋ถ์์ ์ค์ <input>์id๊ฐ'event-date'๋ก ์ค์ ๋์ง ์๋๋ค๋ฉด ์ด ๋ผ๋ฒจ์ ์คํฌ๋ฆฐ ๋ฆฌ๋์ ์ฐ๋๋์ง ์์ต๋๋ค.
DatePicker๊ฐid๋ฅผ ๋ฐ๋๋ก ๋์ด ์๋ค๋ฉด ์ด ํ์ด์ง์์id='event-date'๋ฅผ ๋๊ฒจ ์ฃผ์ธ์.- ๊ทธ๋ ์ง ์๋ค๋ฉด
aria-labelledby๋๋aria-label๋ง์ผ๋ก ์ฒ๋ฆฌํ๋ ์ชฝ์ผ๋ก ๊ตฌ์กฐ๋ฅผ ์ ๋ฆฌํ๋ ๊ฒ์ด ์ข์ต๋๋ค.์ ๊ทผ์ฑ ๋ณด์ฅ์ ์ํด ํ ๋ฒ ํ์ธ ๋ถํ๋๋ฆฝ๋๋ค.
src/shared/hooks/events/useBookmark.ts (1)
1-36: ๋ถ๋งํฌ ํ ์ํ ๋๊ธฐํ ๋ฐ ํด๋ผ์ด์ธํธ ์ง์์ด ๊ด๋ จ ์ ์
- Line 1์
'use client'๋ ์ด ํ ์ด App Router ์๋ฒ ์ปดํฌ๋ํธ์์ ์ง์ import๋๋ ์ํฉ์ด ์๋๋ผ๋ฉด ํ์ ์์ต๋๋ค. ํ์ฌ ํ๋ก์ ํธ๊ฐ Pages Router ๊ธฐ๋ฐ์ด๋ผ๋ฉด ์ ๊ฑฐํด๋ ๋ฌด๋ฐฉํด ๋ณด์ ๋๋ค.- Line 8โ10์์
initialState๋กisBookmarked๋ฅผ ์ด๊ธฐํํ์ง๋ง, ๋ถ๋ชจ์์ ๋ด๋ ค์ฃผ๋liked๊ฐ์ด ๋์ค์ ๋ฐ๋์ด๋ ๋ด๋ถ ์ํ๋ ํ ๋ฒ ์ ํด์ง๋ฉด ๊ทธ๋๋ก ์ ์ง๋ฉ๋๋ค. ๋์ผ ์ด๋ฒคํธ๋ฅผ ๋ค๋ฅธ ํ๋ฉด์์ ํ ๊ธํ์ ๋, ์๋ก๊ณ ์นจ ์์ด ์ด ์ปดํฌ๋ํธ๊ฐ ๋ค์ ๋ง์ดํธ๋์ง ์๋๋ค๋ฉด ์๋ฒ ์ํ์ UI ์ฌ์ด์ ๋ถ์ผ์น๊ฐ ์๊ธธ ์ ์์ต๋๋ค.์๋ฅผ ๋ค์ด ์๋์ฒ๋ผ
initialState๋ณ๊ฒฝ ์ ๋๊ธฐํํ๋useEffect๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉ์์ ๊ณ ๋ คํด ๋ณผ ์ ์์ต๋๋ค.useEffect(() => { setIsBookmarked(initialState); }, [initialState]);
- Line 28โ29์์ React Query์
invalidateQueries๋ก['events'],['eventDetail', eventId]๋ฅผ ๋ฌดํจํํ๋ ์ ๊ทผ์useEvents/useEventDetail์ฟผ๋ฆฌ ํค ๊ตฌ์ฑ๊ณผ ์ ๋ง์ ๋ณด์ ๋๋ค. ๋ค๋ง, ํ ๊ธ ์คํจ ์ ์ฌ์ฉ์์๊ฒ ํผ๋๋ฐฑ์ด ์๋ ์ํ๋ผ ํ์ํ๋ค๋ฉด ํ์ ์์ (TODO) ์ ๋๋ ๋จ๊ฒจ ๋์ด๋ ์ข๊ฒ ์ต๋๋ค.src/pages/events/[id].tsx (1)
31-39: ํค๋ ํ์ดํ ๋ฐ ๋ก๋ฉ/์๋ฌ ์ํ UX ๊ฐ์ ์ ์
- Line 35โ37์์
Header์title์ ํญ์'ํ์ฌ๋ช '์ผ๋ก ๋๊ธฐ๊ณ ์์ต๋๋ค. ์ด๋ฏธ Line 28โ29์์title์ ๊ตฌ์กฐ ๋ถํดํ๊ณ ์์ผ๋ฏ๋ก, ์ค์ ํ์ฌ๋ช ์ ๋ณด์ฌ์ฃผ๋ ค๋ฉดtitle={title}๋ก ์ฌ์ฉํ๋ ํธ์ด ์์ฐ์ค๋ฌ์ธ ๊ฒ ๊ฐ์ต๋๋ค.- ํ์ฌ๋
if (!eventId) return null;๋ฐif (isError || !eventDetail) return null;๋ก ์ธํด ๋ก๋ฉ/์๋ฌ ์ํฉ์์ ํ๋ฉด์ ์๋ฌด๊ฒ๋ ๋ ๋๋ง๋์ง ์์ต๋๋ค. ์ด๋ฏธuseEffect๋ก/events๋ก ๋ฆฌ๋ค์ด๋ ํธ๋ ํ๊ณ ์์ง๋ง, ๋คํธ์ํฌ๊ฐ ๋๋ฆฐ ๊ฒฝ์ฐ ์ฌ์ฉ์๋ ๋น ํ๋ฉด๋ง ์ ๊น ๋ณด๊ฒ ๋ฉ๋๋ค.
๊ฐ๋จํ ๋ก๋ฉ ์คํผ๋๋ โํ์ฌ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค๋ ์ค์ ๋๋คโ ์ ๋์ ํ ์คํธ๋ฅผ ์ถ๊ฐํด ์ฃผ๋ฉด UX๊ฐ ์กฐ๊ธ ๋ ์ข์์ง ๊ฒ ๊ฐ์ต๋๋ค.Also applies to: 79-97
src/shared/api/events/event.ts (1)
14-25: ๋ชฉ๋ก ์กฐํ ์ ์ด๋ฒคํธ๋ณ ์์ธ API N+1 ํธ์ถ๋ก ์ธํ ์ฑ๋ฅ ์ด์ ๊ฐ๋ฅ์ฑํ์ฌ
fetchEvents์์res.data.data์ ๊ฐ ์์ดํ ๋ง๋ค/api/events/{eventId}๋ฅผ ๋ค์ ํธ์ถํ๊ณ ์์ด, ์ด๋ฒคํธ ์๊ฐ ๋ง์ ๋ N+1 ํจํด์ผ๋ก ๋คํธ์ํฌ ๋ถํ๊ฐ ์ปค์ง ์ ์์ต๋๋ค. ํ์ํimageUrl,isBookmarked๋ฅผ ๋ชฉ๋ก API์์ ํจ๊ป ๋ด๋ ค๋ฐ๊ฑฐ๋, ์๋ฒ์ ๋ฐฐ์น/์์ฝ์ฉ ์๋ํฌ์ธํธ๋ฅผ ์ถ๊ฐํ๋ ๋ฐฉํฅ๋ ํ ๋ฒ ๊ณ ๋ คํด ๋ณด์๋ฉด ์ข๊ฒ ์ต๋๋ค.
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (17)
next.config.ts(1 hunks)src/pages/events/[id].tsx(2 hunks)src/pages/events/index.tsx(2 hunks)src/shared/api/events/event.ts(1 hunks)src/shared/api/instance.ts(1 hunks)src/shared/components/container/EventCard.tsx(2 hunks)src/shared/components/container/LocationCard.tsx(2 hunks)src/shared/components/datePicker/DatePicker.tsx(3 hunks)src/shared/components/set/PopupSet.tsx(1 hunks)src/shared/constants/events/eventsData.ts(0 hunks)src/shared/hooks/events/EventMapper.ts(1 hunks)src/shared/hooks/events/useBookmark.ts(1 hunks)src/shared/hooks/events/useEventDetail.ts(1 hunks)src/shared/hooks/events/useEvents.ts(1 hunks)src/shared/overlay/Overlay.tsx(1 hunks)src/shared/types/eventtypes.ts(1 hunks)src/shared/utils/buildNextEventList.ts(1 hunks)
๐ค Files with no reviewable changes (1)
- src/shared/constants/events/eventsData.ts
๐งฐ Additional context used
๐ง Learnings (1)
๐ Learning: 2025-10-31T17:34:04.352Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.352Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/events/index.tsxsrc/pages/events/[id].tsx
๐งฌ Code graph analysis (11)
src/shared/utils/buildNextEventList.ts (1)
src/shared/types/eventtypes.ts (1)
RelatedEvent(23-27)
src/shared/components/container/EventCard.tsx (5)
src/shared/hooks/events/useBookmark.ts (1)
useBookmark(8-36)src/pages/mypage/components/LoginRequired.tsx (1)
LoginRequired(10-21)src/shared/components/container/Card.tsx (1)
Card(32-48)src/shared/lib/utils.ts (1)
cn(71-73)src/shared/icons/components/icon.tsx (1)
Icon(58-130)
src/shared/api/instance.ts (1)
src/pages/auth/callback.tsx (2)
res(19-35)AuthCallbackPage(7-41)
src/shared/hooks/events/useBookmark.ts (2)
src/shared/utils/token.ts (1)
getAccessToken(8-9)src/shared/api/events/event.ts (2)
deleteBookmark(54-59)postBookmark(46-51)
src/shared/components/datePicker/DatePicker.tsx (1)
src/shared/icons/components/icon.tsx (1)
Icon(58-130)
src/shared/hooks/events/useEventDetail.ts (3)
src/shared/api/events/event.ts (1)
fetchEventDetail(34-43)src/shared/types/eventtypes.ts (2)
EventDetail(37-40)EventDetailResponse(29-35)src/shared/api/instance.ts (1)
apiAuth(20-23)
src/shared/api/events/event.ts (4)
src/shared/types/authtypes.ts (1)
ApiResponse(1-6)src/shared/types/eventtypes.ts (2)
EventData(12-21)EventResponse(1-11)src/shared/api/instance.ts (1)
apiWithToken(14-17)src/shared/hooks/events/EventMapper.ts (1)
mapEvent(3-12)
src/pages/events/index.tsx (1)
src/shared/hooks/events/useEvents.ts (1)
useEvents(7-21)
src/shared/hooks/events/useEvents.ts (2)
src/shared/utils/date.ts (1)
formatDateToISO(1-6)src/shared/api/events/event.ts (1)
fetchEvents(7-31)
src/shared/hooks/events/EventMapper.ts (1)
src/shared/types/eventtypes.ts (2)
EventResponse(1-11)EventData(12-21)
src/pages/events/[id].tsx (4)
src/shared/hooks/events/useEventDetail.ts (1)
useEventDetail(15-21)src/shared/utils/buildNextEventList.ts (1)
buildNextEventList(3-11)src/shared/lib/utils.ts (1)
cn(71-73)src/pages/events/components/DateTag.tsx (1)
DateTag(24-44)
โฐ 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: build-and-deploy
๐ Additional comments (8)
src/shared/api/instance.ts (1)
95-95: ์ธ์ฆ ์คํจ ์ ๋ฆฌ๋ค์ด๋ ํธ ๊ฒฝ๋ก ๋ณ๊ฒฝ ํ์ธ๋จํ ํฐ ๊ฐฑ์ ์คํจ ์
/auth๋ก ๋ฆฌ๋ค์ด๋ ํธํ๋๋ก ๋ณ๊ฒฝํ ๊ฒ์ด auth callback ํ์ด์ง์ ์๋ฌ ์ฒ๋ฆฌ์ ์ผ์นํฉ๋๋ค.src/shared/components/datePicker/DatePicker.tsx (2)
14-14: ์ ๊ทผ์ฑ ๊ฐ์
ariaLabelprop ์ถ๊ฐ๋ก ์ ๊ทผ์ฑ์ด ํฅ์๋์์ต๋๋ค.
88-89: ๊ณผ๊ฑฐ ๋ ์ง ์ ํ ๊ฐ๋ฅ ์ฌ๋ถ ํ์ธ ํ์๋ ์ง ์ ํ(
disabled,fromDate)์ด ์ฃผ์ ์ฒ๋ฆฌ๋์ด ์ด์ ๊ณผ๊ฑฐ ๋ ์ง ์ ํ์ด ๊ฐ๋ฅํฉ๋๋ค. ์ด๋ฒคํธ ๊ฒ์ ์๋๋ฆฌ์ค์์ ๊ณผ๊ฑฐ ์ด๋ฒคํธ ์กฐํ๊ฐ ํ์ํ ๊ฒฝ์ฐ๋ผ๋ฉด ์๋๋ ๋ณ๊ฒฝ์ด์ง๋ง, ๊ทธ๋ ์ง ์๋ค๋ฉด ์ ํ์ ์ ์งํ๋ ๊ฒ์ด ์ข์ต๋๋ค.src/shared/hooks/events/EventMapper.ts (1)
3-12: LGTM!EventResponse๋ฅผ EventData๋ก ๋ณํํ๋ ๊น๋ํ ๋งคํ ํจ์์ ๋๋ค. nullish coalescing์ ์ฌ์ฉํ ๊ธฐ๋ณธ๊ฐ ์ฒ๋ฆฌ๋ ์ ์ ํฉ๋๋ค.
src/pages/events/index.tsx (1)
1-4: Pages Router ํ๊ฒฝ์์'use client'๋ฐ ์ด๊ธฐ ์ํ UX ์ ๊ฒ ์ ์
- Line 1์
'use client'๋ Next.js Pages Router ํ์ด์ง์์๋ ์๋ฏธ๊ฐ ์๊ณ , ๊ธฐ์กด ํ์ต ์ปจํ ์คํธ(์ด ํ๋ก์ ํธ๋ Pages Router์์use client๋ฅผ ์ฌ์ฉํ์ง ์์)์๋ ๋ง์ง ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ์ ๊ฑฐํด๋ ๋์์๋ ์ํฅ์ด ์์ผ๋, ์ปจ๋ฒค์ ์ ๋ง๊ฒ ์ ๋ฆฌํ๋ ๊ฒ์ ์ถ์ฒ๋๋ฆฝ๋๋ค.- Line 18โ20์์
const filteredEvents = events;๋ก ๋ณ๋ ๋ณ์๋ง ๋๊ณ ์์ด ์ถ๊ฐ ํํฐ๋ง์ด ์๋ค๋ฉด ๋ฐ๋กevents๋ฅผ ์ฌ์ฉํ๋ ํธ์ด ๊ฐ๊ฒฐํฉ๋๋ค.- ํ์ฌ ๋ก์ง์์๋ ๋ ์ง๋ฅผ ์์ง ์ ํํ์ง ์์ ์ฒซ ์ง์ ์์๋
events๊ฐ ๋น ๋ฐฐ์ด์ด๋ผ ๊ณง๋ฐ๋ก โํ์ฌ๊ฐ ์์ด์!โ ๋น ์ํ UI(Line 81โ95)๊ฐ ํ์๋ฉ๋๋ค. UX ์ โ๋ ์ง๋ฅผ ๋จผ์ ์ ํํด ์ฃผ์ธ์โ์ ๊ฐ์ ์๋ด๋ฅผ ๋ณด์ฌ์ฃผ๊ฑฐ๋, ๋ ์ง๊ฐ ์ ํ๋์์ ๋๋ง ๋น ์ํ๋ฅผ ๋ณด์ฌ์ฃผ๋ ์กฐ๊ฑด(date์กด์ฌ ์ฌ๋ถ)์ ์ถ๊ฐํ ์ง ํ ๋ฒ ๊ณ ๋ คํด ๋ณด์ ๋ ์ข๊ฒ ์ต๋๋ค.Also applies to: 13-21
src/pages/events/[id].tsx (2)
55-77: ์ด๋ฏธ์ง fallback ๋ฒ๊ทธ ๋ฐ ๊ด๋ จ ํ์ฌ ์์ดํ ํ์ ์์ ์ฑ
์ด๋ฏธ์ง fallback์์ ์ ์๋์ง ์์ ๋ณ์ ์ฌ์ฉ
Line 71โ75์์:
aria-label={`${name} ์ด๋ฏธ์ง๊ฐ ์ ๊ณต๋์ง ์์ต๋๋ค.`}๋ฅผ ์ฌ์ฉํ๊ณ ์๋๋ฐ, ์ด ์ค์ฝํ์๋
name๋ณ์๊ฐ ์์ต๋๋ค. TypeScript ๊ธฐ์ค์ผ๋ก๋ ์ปดํ์ผ ์๋ฌ, JS ๊ธฐ์ค์ผ๋ก๋ReferenceError๊ฐ ๋ฐ์ํฉ๋๋ค.title์ ์ฌ์ฉํ๋ ค๋ ์๋๋ก ๋ณด์ด๋ฏ๋ก ์๋์ ๊ฐ์ด ์์ ํด์ผ ํ ๊ฒ ๊ฐ์ต๋๋ค.
- aria-label={
${name} ์ด๋ฏธ์ง๊ฐ ์ ๊ณต๋์ง ์์ต๋๋ค.}
- aria-label={
${title} ์ด๋ฏธ์ง๊ฐ ์ ๊ณต๋์ง ์์ต๋๋ค.}
nextList์์ดํ ์isEmptyํ๋กํผํฐ ์ฌ์ฉ
Line 104โ121์์
nextList.map๋ด๋ถ์์item.isEmpty๋ก ๋ถ๊ธฐํ๊ณ ์์ต๋๋ค.buildNextEventList๊ตฌํ์ ๋ณด๋ฉด ์ค์ ์ด๋ฒคํธ(RelatedEvent)์{ isEmpty: true }๋ฅผ ์์ด์ ๋ฐํํ๊ณ ์๊ธฐ ๋๋ฌธ์, ํ์ ์์ผ๋ก๋RelatedEvent | { isEmpty: boolean }์ ๋์จ์ด ๋ฉ๋๋ค.ํ์ฌ์ฒ๋ผ ๋จ์ํ
item.isEmpty์ ์ ๊ทผํ๋ฉด TypeScript์์๋"Property 'isEmpty' does not exist on type 'RelatedEvent'"์๋ฌ๊ฐ ๋ ์ ์์ต๋๋ค.
ํ์ ์ ์๋์ฒ๋ผ ์ ๋ฆฌํ๋ฉด ๋ ์์ ํฉ๋๋ค.type NextEventItem = RelatedEvent & { isEmpty?: boolean }; export const buildNextEventList = (nextEvents: RelatedEvent[]): NextEventItem[] => { const MAX = 2; if (nextEvents.length >= MAX) return nextEvents.slice(0, MAX); const emptyItems: NextEventItem[] = Array(MAX - nextEvents.length).fill({ isEmpty: true } as NextEventItem); return [...nextEvents, ...emptyItems]; };๊ทธ๋ฆฌ๊ณ ์ฌ์ฉ์ฒ์์๋
item.isEmpty๋ฅผ ๊ทธ๋๋ก ์ฐ๋ ํ์ฌ ์ฝ๋๊ฐ ํ์ ์์ผ๋ก๋ ์์ ํด์ง๋๋ค.Also applies to: 87-122
1-3: ๋ฐ์ดํฐ ํ์น/๋ฆฌ๋ค์ด๋ ํธ ๋ก์ง ๋ฐ'use client'์ฌ์ฉ ์ ๊ฒ
Line 1์
'use client'๋ ์ด ํ์ผ์ด Pages Router์ ํ์ด์ง ์ปดํฌ๋ํธ์ธ ์ ์ ๊ณ ๋ คํ๋ฉด ์ค์ง์ ์ธ ํจ๊ณผ๊ฐ ์์ต๋๋ค. ๊ธฐ์กด ํ์ต ๋ด์ฉ(์ด ๋ ํฌ์์๋ Pages Router์์use client๋ฅผ ์ฌ์ฉํ์ง ์์)์ ๋ฐ๋ฅด๋ ค๋ฉด ์ ๊ฑฐํ๋ ํธ์ด ์ผ๊ด๋ ๊ฒ ๊ฐ์ต๋๋ค.Line 16โ18์์
eventId = Number(id)๋ก ๋ณํํ ๋คuseEventDetail(eventId)๋ฅผ ํธ์ถํ๊ณ ์์ต๋๋ค.useEventDetail๋ด๋ถ ๊ตฌํ ์fetchEventDetail์ด API ๋ํผ ์ ์ฒด({ success, code, message, data, timestamp })๋ฅผ ๋ฐํํ๋์ง, ์๋๋ฉดdata๋ง ๋ฐํํ๋์ง์ ๋ฐ๋ผ Line 28โ29์ ๊ตฌ์กฐ ๋ถํด:const { title, body, address, startDate, endDate, imageUrl, nextEvents } = eventDetail;๊ฐ ์ ์ ๋์ํ ์๋ ์๊ณ , ์ ๋ถ
undefined๊ฐ ๋ ์๋ ์์ต๋๋ค.useEvents์์data?.data || []๋ฅผ ์ฐ๋ ํจํด์ ๋ณด๋ฉดfetchEventDetail๋ ์ ์ฌํ ๋ํผ๋ฅผ ์ธ ๊ฐ๋ฅ์ฑ์ด ๋์ผ๋ฏ๋ก, ์ด ๋ถ๋ถ์ ์ค์ ๋ฐํ ํ์ ์ ํ ๋ฒ ๋ ํ์ธํด ์ฃผ์ธ์ (eventDetail.data์์ ๊ตฌ์กฐ ๋ถํดํด์ผ ํ ์๋ ์์ต๋๋ค).Line 19โ23์
useEffect์์!isLoading && (isError || !eventDetail)์ด๋ฉด/events๋กreplaceํ๋๋ฐ,eventId๊ฐ ์ซ์๋ก ์ ๋๋ก ํ์ฑ๋์ง ๋ชปํ ๊ฒฝ์ฐ(NaN)์๋enabled: !!eventId๋๋ฌธ์ ์ฟผ๋ฆฌ๊ฐ ๋นํ์ฑํ๋๊ณ , ๊ทธ ๊ฒฐ๊ณผeventDetail์ด ํญ์undefined๋ผ ๊ณง๋ฐ๋ก ๋ฆฌ์คํธ ํ์ด์ง๋ก ํ๊ธธ ์ ์์ต๋๋ค.
ํ์ํ๋ค๋ฉดid๊ฐ ์ ํจํ ์ ์์ธ์ง ๋จผ์ ๊ฒ์ฆํ ๋ค(if (!id || Array.isArray(id) || Number.isNaN(eventId))) ๋ฆฌ๋ค์ด๋ ํธ๋ฅผ ์ํํ๋ ๋ฐฉ์์ ๊ณ ๋ คํด ๋ณด์ ๋ ์ข๊ฒ ์ต๋๋ค.Also applies to: 8-27
src/shared/api/events/event.ts (1)
45-67: ๋ถ๋งํฌ ๊ด๋ จ API ๋ํผ ๊ตฌํ์ ๋ช ํํ๊ณ ์ผ๊ด์ ์ ๋๋ค
postBookmark,deleteBookmark,fetchBookmarkStatus๋ชจ๋ REST ๊ฒฝ๋ก์ HTTP ๋ฉ์๋๊ฐ ์ง๊ด์ ์ผ๋ก ์ ๋งคํ๋์ด ์๊ณ , ๋ฐํ ํ์ ๋ApiResponse๋ก ํต์ผ๋์ด ์์ด ์ฌ์ฉ์ฑ์ด ์ข์ ๋ณด์ ๋๋ค.
| export const fetchEventDetail = async ( | ||
| eventId: number, | ||
| ): Promise<ApiResponse<EventData>> => { | ||
| const res = await apiWithToken.get(`/api/events/${eventId}`); | ||
|
|
||
| return { | ||
| ...res.data, | ||
| data: res.data.data, | ||
| }; |
There was a problem hiding this comment.
ํ์ฌ ์์ธ ์๋ต์ EventData๋ก ๋งคํํ์ง ์์ ํ์ /ํ๋ ๋ถ์ผ์น ๊ฐ๋ฅ์ฑ
EventResponse์ EventData ํ์
์ ์๋ฅผ ๋ณด๋ฉด ๋ชฉ๋ก ์กฐํ์์๋ mapEvent๋ก ํ๋๋ช
์ ๋ณํํ์ง๋ง, ์์ธ ์กฐํ์์๋ res.data.data๋ฅผ ๊ทธ๋๋ก EventData๋ก ๋ฐํํ๊ณ ์์ต๋๋ค. ๋ฐฑ์๋์์ ์ด๋ฏธ EventData ๋ชจ์์ผ๋ก ๋ด๋ ค์ฃผ์ง ์๋๋ค๋ฉด, ์์ธ ํ๋ฉด์์ id, name ๋ฑ์ ํ๋๋ฅผ ๊ธฐ๋ํ ๋ eventId, title๋ง ์กด์ฌํด ๊นจ์ง ์ ์์ต๋๋ค.
์๋์ฒ๋ผ ์์ธ ์๋ต๋ mapEvent๋ฅผ ๊ฑฐ์น๋๋ก ์์ ํ๋ ์ชฝ์ด ๋ ์์ ํด ๋ณด์
๋๋ค.
export const fetchEventDetail = async (
eventId: number,
): Promise<ApiResponse<EventData>> => {
const res = await apiWithToken.get(`/api/events/${eventId}`);
return {
- ...res.data,
- data: res.data.data,
+ ...res.data,
+ data: mapEvent(res.data.data as EventResponse),
};
};๐ 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.
| export const fetchEventDetail = async ( | |
| eventId: number, | |
| ): Promise<ApiResponse<EventData>> => { | |
| const res = await apiWithToken.get(`/api/events/${eventId}`); | |
| return { | |
| ...res.data, | |
| data: res.data.data, | |
| }; | |
| export const fetchEventDetail = async ( | |
| eventId: number, | |
| ): Promise<ApiResponse<EventData>> => { | |
| const res = await apiWithToken.get(`/api/events/${eventId}`); | |
| return { | |
| ...res.data, | |
| data: mapEvent(res.data.data as EventResponse), | |
| }; | |
| }; |
๐ค Prompt for AI Agents
In src/shared/api/events/event.ts around lines 34 to 42, the event detail
response returns res.data.data directly which can mismatch EventData fields;
update the function to map the returned event through mapEvent before returning
(i.e., import mapEvent if missing and replace data: res.data.data with data:
mapEvent(res.data.data)) so the detail response has the same normalized shape as
the list endpoint.
| const EventCard = ({ | ||
| eventId, | ||
| name, | ||
| address, | ||
| description, | ||
| variant = 'gray', | ||
| size = 'medium', | ||
| imageSrc = '', | ||
| liked = false, | ||
| }: EventCardProps) => { | ||
| const [liked, setLiked] = useState(false); | ||
| const { isBookmarked, toggleBookmark, requireLogin, setRequireLogin } = | ||
| useBookmark(eventId, liked); | ||
|
|
||
|
|
There was a problem hiding this comment.
๋ถ๋งํฌ ๋ฒํผ ์ ๊ทผ์ฑ(a11y) ๋ณด์ ํ์
์ค๊ฐ/๋ํ ์นด๋์์ ํํธ ์์ด์ฝ ๋ฒํผ(Line 104โ123, 150โ169)์ ์๊ฐ์ ์ผ๋ก๋ ๋ช
ํํ์ง๋ง, <button> ์์ ํ
์คํธ๊ฐ ์๊ณ aria-label๋ ์์ด ์คํฌ๋ฆฐ ๋ฆฌ๋์์๋ ๋จ์ํ โ๋ฒํผโ์ผ๋ก๋ง ์ฝํ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค. ํ์ฌ Icon ์ปดํฌ๋ํธ ๊ธฐ๋ณธ๊ฐ์ด ariaHidden์ธ ์ ์ ๊ณ ๋ คํ๋ฉด ์ด๋ฆ ์๋ ๋ฒํผ์ด ๋ฉ๋๋ค.
- ๋ฒํผ์
aria-label์ ๋ถ์ฌํ๊ฑฐ๋, - ํน์ ๋ฒํผ ๋ด๋ถ์
sr-onlyํ ์คํธ(๋ถ๋งํฌ ์ถ๊ฐ,๋ถ๋งํฌ ํด์ ๋ฑ)๋ฅผ ์ถ๊ฐํด ์๋ฏธ๋ฅผ ์ ๋ฌํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
์์:
<button
className="cursor-pointer p-1"
onClick={(e) => { e.stopPropagation(); toggleBookmark(); }}
aria-label={isBookmarked ? '๋ถ๋งํฌ ํด์ ' : '๋ถ๋งํฌ ์ถ๊ฐ'}
>
<Icon
name="HeartStraight"
size={20}
color={isBookmarked ? 'red-400' : variant === 'mint' ? 'mint-400' : 'gray-300'}
fillColor={isBookmarked ? 'red-300' : undefined}
/>
</button>๋ํ ๋์ผํ ํํธ ๋ฒํผ ๋ก์ง์ด medium/large ์นด๋์ ์ค๋ณต๋์ด ์์ผ๋ฏ๋ก, ์๊ท๋ชจ๋ผ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ BookmarkButton ์๋ธ์ปดํฌ๋ํธ๋ก ๋ถ๋ฆฌํ๋ฉด ์ ์ง๋ณด์๊ฐ ๋ ์ฌ์์ง ๊ฒ ๊ฐ์ต๋๋ค.
Also applies to: 48-124, 137-180
๐ค Prompt for AI Agents
In src/shared/components/container/EventCard.tsx around lines 22 to 35 (and
apply same changes at 48-124, 137-180), the heart bookmark buttons lack
accessible names because the Icon is aria-hidden and the button has no
aria-label or visible text; update the button to include an appropriate
aria-label that reflects state (e.g., aria-label={isBookmarked ? '๋ถ๋งํฌ ํด์ ' : '๋ถ๋งํฌ
์ถ๊ฐ'}) or add an sr-only text node inside the button, preserve existing onClick
behavior (stopPropagation then toggleBookmark), and refactor the duplicated
bookmark button markup into a small reusable BookmarkButton subcomponent that
accepts isBookmarked, onToggle, variant props and handles aria-label/sr-only and
styling so both medium and large card variants use the same accessible
implementation.
| export const useEvents = (date?: Date) => { | ||
| const formattedDate = formatDateToISO(date); | ||
|
|
||
| const { data, isLoading, isError } = useQuery({ | ||
| queryKey: ['events', formattedDate], | ||
| queryFn: () => fetchEvents(formattedDate!), | ||
| enabled: !!formattedDate, | ||
| }); | ||
|
|
||
| return { | ||
| events: data?.data || [], | ||
| isLoading, | ||
| isError, | ||
| }; | ||
| }; |
There was a problem hiding this comment.
๐งฉ Analysis chain
fetchEvents์ N+1 ์ฟผ๋ฆฌ ๋ฌธ์ ํ์ธ ํ์
๊ด๋ จ ์ฝ๋(src/shared/api/events/event.ts)๋ฅผ ๋ณด๋ฉด fetchEvents๊ฐ ๊ฐ ์ด๋ฒคํธ๋ง๋ค ๊ฐ๋ณ์ ์ผ๋ก ์์ธ ์ ๋ณด๋ฅผ ์์ฒญํ๊ณ ์์ต๋๋ค(Lines 13-16). ์ด๋ N+1 ์ฟผ๋ฆฌ ๋ฌธ์ ๋ฅผ ์ผ๊ธฐํ์ฌ ์ฑ๋ฅ ์ ํ๋ฅผ ์ผ์ผํฌ ์ ์์ต๋๋ค.
๋ฐฑ์๋ API๋ฅผ ์์ ํ์ฌ ์ด๋ฒคํธ ๋ชฉ๋ก ์กฐํ ์ ํ์ํ ๋ชจ๋ ์ ๋ณด(imageUrl, isBookmarked ํฌํจ)๋ฅผ ํ ๋ฒ์ ๋ฐํํ๋๋ก ํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๋๋ GraphQL์ด๋ ๋ฐฐ์น API๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.
๋ค์ ์คํฌ๋ฆฝํธ๋ก fetchEvents์ ๊ตฌํ์ ํ์ธํ ์ ์์ต๋๋ค:
๐ Script executed:
#!/bin/bash
# fetchEvents ๊ตฌํ ํ์ธ
rg -A 20 "export const fetchEvents" --type tsLength of output: 1201
fetchEvents์์ N+1 ์ฟผ๋ฆฌ ๋ฌธ์ ํ์ธ๋จ
ํ์ธ๋ ๋ฐ์ ๊ฐ์ด src/shared/api/events/event.ts์ fetchEvents ํจ์๋ ์ด๋ฒคํธ ๋ชฉ๋ก์ ์กฐํํ ํ, ๊ฐ ์ด๋ฒคํธ๋ง๋ค ์์ธ ์ ๋ณด(imageUrl, isBookmarked)๋ฅผ ๊ฐ๋ณ์ ์ผ๋ก ์์ฒญํ๊ณ ์์ต๋๋ค. ์ด๋ N๊ฐ์ ์ด๋ฒคํธ๊ฐ ์์ ๋ ์ด N+1๊ฐ์ API ํธ์ถ์ ๋ฐ์์ํค๋ ์ฑ๋ฅ ๋ฌธ์ ์
๋๋ค.
๋ฐฑ์๋ API๋ฅผ ์์ ํ์ฌ ์ด๊ธฐ ์ด๋ฒคํธ ๋ชฉ๋ก ์กฐํ ์๋ต์ ํ์ํ ๋ชจ๋ ์ ๋ณด๋ฅผ ํฌํจํ๊ฑฐ๋, ๋ฐฐ์น ์กฐํ ์๋ํฌ์ธํธ๋ฅผ ์ ๊ณตํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค.
๐ค Prompt for AI Agents
In src/shared/hooks/events/useEvents.ts around lines 7-21, useQuery currently
calls fetchEvents which (per review) issues per-event detail requests causing an
N+1 API call problem; fix this by changing fetchEvents
(src/shared/api/events/event.ts) to return all required fields in one network
call (either by adding an includeDetails flag to the events endpoint or by
introducing a batch-details endpoint that accepts an array of event IDs), update
the queryFn here to call that new/updated fetchEvents signature (passing
formattedDate and the include/details flag or performing a single batch-details
request inside fetchEvents), and ensure the returned data structure already
contains imageUrl and isBookmarked so useEvents no longer triggers per-event
requests.
There was a problem hiding this comment.
Actionable comments posted: 1
โป๏ธ Duplicate comments (1)
src/pages/mypage/postcard/[id].tsx (1)
3-5: ์ฝ์ ์์ธ ํ์ด์ง๊ฐ ํ๋ ์ด์คํ๋๋ก ๋์ฒด๋์์ต๋๋ค.
PostcardPage์ปดํฌ๋ํธ๊ฐ ํ๋ ์ด์คํ๋ ํ ์คํธ๋ง ๋ ๋๋งํ์ฌ/mypage/postcard/[id]๋ผ์ฐํธ๊ฐ ์ ๋๋ก ์๋ํ์ง ์์ต๋๋ค. ์ด์ ๋ฆฌ๋ทฐ์์๋ ์ง์ ๋ ๊ฒ์ฒ๋ผ, ๋ค์ ์ค ํ๋๋ฅผ ๊ถ์ฅํฉ๋๋ค:
- ์ ๋ฏผ๋์ ๊ตฌํ์ ๋ฐ๊ธฐ ์ ๊น์ง ์ด ํ์ผ ๋ณ๊ฒฝ์ฌํญ์ PR์์ ์ ์ธ
- ์์๋ก "์ค๋น์ค" ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ฃผ๋ ์ ์ ํ UI ์ปดํฌ๋ํธ ๊ตฌํ
- ํด๋น ๋ผ์ฐํธ๋ก์ ์ ๊ทผ์ ๋ง๋ ๋ก์ง ์ถ๊ฐ
์์ ๊ตฌํ ์์:
export default function PostcardPage() { - return <div>์ ๋ฏผ์ด๊ฑฐ ๋ฐ์์ค๊ธฐ</div>; + return ( + <div className="flex items-center justify-center min-h-screen"> + <p className="text-gray-500">์ค๋น์ค์ ๋๋ค.</p> + </div> + ); }
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (1)
src/pages/mypage/postcard/[id].tsx(1 hunks)
๐งฐ Additional context used
๐ง Learnings (1)
๐ Learning: 2025-10-31T17:34:04.352Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.352Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/mypage/postcard/[id].tsx
โฐ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: build-and-deploy
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and canโt be posted inline due to platform limitations.
โ ๏ธ Outside diff range comments (1)
src/pages/events/index.tsx (1)
1-1: Pages Router์์๋ 'use client' ์ง์์๊ฐ ๋ถํ์ํฉ๋๋ค.ํ๋ก์ ํธ๊ฐ Next.js Pages Router๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฏ๋ก 'use client' ์ง์์๋ฅผ ์ ๊ฑฐํด์ผ ํฉ๋๋ค.
Based on learnings
-'use client'; - import { useState } from 'react';
โป๏ธ Duplicate comments (1)
src/shared/api/events/event.ts (1)
41-50: ์ด์ ๋ฆฌ๋ทฐ ์ง์ ์ฌํญ: fetchEventDetail์์ mapEvent๋ฅผ ์ฌ์ฉํ์ง ์์ ํ์ ๋ถ์ผ์น ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
fetchEventDetail์ดEventData๋ฅผ ๋ฐํํ๋๋ก ์ ์ธ๋์ด ์์ง๋งmapEvent๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ์์ต๋๋ค. ๋ฐฑ์๋๊ฐEventDataํ์์ ์ง์ ๋ฐํํ์ง ์๋๋ค๋ฉด,eventId/title๊ฐ์ ํ๋๊ฐid/name์ผ๋ก ๋ณํ๋์ง ์์ ํ์ ๋ถ์ผ์น๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.export const fetchEventDetail = async ( eventId: number, ): Promise<ApiResponse<EventData>> => { const res = await apiWithToken.get(`/api/events/${eventId}`); return { ...res.data, - data: res.data.data, + data: mapEvent(res.data.data as EventResponse), }; };
๐งน Nitpick comments (2)
src/shared/hooks/events/useBookmark.ts (1)
13-15: ๋ถํ์ํ useEffect๋ฅผ ์ ๊ฑฐํ์ธ์.
useState(initialState)๊ฐ ์ด๋ฏธ ์ด๊ธฐ๊ฐ์ ์ค์ ํ๋ฏ๋ก,useEffect๋ก ๋ค์ ๋๊ธฐํํ๋ ๊ฒ์ ์ค๋ณต์ ๋๋ค.initialState๊ฐ ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ์๋ง ํ์ํ์ง๋ง, ์ผ๋ฐ์ ์ผ๋ก props๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์ปดํฌ๋ํธ๊ฐ ์ฌ๋ง์ดํธ๋ฉ๋๋ค.- useEffect(() => { - setIsBookmarked(initialState); - }, [initialState]);src/pages/events/index.tsx (1)
21-21: ๋ถํ์ํ ๋ณ์ ํ ๋น์ ์ ๊ฑฐํ์ธ์.
filteredEvents๊ฐ ๋จ์ํevents๋ฅผ ์ฐธ์กฐํ๊ณ ์์ผ๋ฉฐ ์ค์ ํํฐ๋ง ๋ก์ง์ด ์์ต๋๋ค. ์ง์ events๋ฅผ ์ฌ์ฉํ๊ฑฐ๋ ํฅํ ํํฐ๋ง ๋ก์ง์ ์ถ๊ฐํ ์์ ์ด๋ผ๋ฉด TODO ์ฃผ์์ ๋จ๊ธฐ์ธ์.- const filteredEvents = events;๊ทธ๋ฆฌ๊ณ ์๋์์
events๋ฅผ ์ง์ ์ฌ์ฉ:- {filteredEvents.length > 0 ? ( + {events.length > 0 ? ( <section aria-label='์ด๋ฒคํธ ๋ชฉ๋ก' className={cn( 'grid w-full mt-[1.4rem]', 'grid-cols-2 gap-x-[1.4rem] gap-y-[1.4rem]', )} > - {filteredEvents.map((event: EventData) => ( + {events.map((event: EventData) => (
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (8)
next.config.ts(1 hunks)src/pages/events/[id].tsx(2 hunks)src/pages/events/index.tsx(2 hunks)src/shared/api/events/event.ts(1 hunks)src/shared/hooks/events/EventMapper.ts(1 hunks)src/shared/hooks/events/useBookmark.ts(1 hunks)src/shared/types/eventtypes.ts(1 hunks)src/shared/utils/buildNextEventList.ts(1 hunks)
๐ง Files skipped from review as they are similar to previous changes (3)
- src/shared/hooks/events/EventMapper.ts
- next.config.ts
- src/shared/utils/buildNextEventList.ts
๐งฐ Additional context used
๐ง Learnings (1)
๐ Learning: 2025-10-31T17:34:04.352Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.352Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/events/index.tsxsrc/pages/events/[id].tsx
๐งฌ Code graph analysis (4)
src/shared/hooks/events/useBookmark.ts (2)
src/shared/utils/token.ts (1)
getAccessToken(8-9)src/shared/api/events/event.ts (2)
deleteBookmark(61-66)postBookmark(53-58)
src/pages/events/index.tsx (3)
src/shared/hooks/events/useEvents.ts (1)
useEvents(7-21)src/shared/components/datePicker/DatePicker.tsx (1)
DatePicker(17-97)src/shared/types/eventtypes.ts (1)
EventData(12-21)
src/pages/events/[id].tsx (4)
src/shared/types/eventtypes.ts (1)
RelatedEventOrEmpty(42-42)src/shared/hooks/events/useEventDetail.ts (1)
useEventDetail(15-21)src/shared/utils/buildNextEventList.ts (1)
buildNextEventList(5-17)src/pages/events/components/DateTag.tsx (1)
DateTag(24-44)
src/shared/api/events/event.ts (3)
src/shared/types/authtypes.ts (1)
ApiResponse(1-6)src/shared/types/eventtypes.ts (2)
EventData(12-21)EventResponse(1-11)src/shared/api/instance.ts (1)
apiWithToken(14-17)
โฐ 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: build-and-deploy
๐ Additional comments (2)
src/shared/api/events/event.ts (1)
4-4: mapEvent๋ฅผ importํ์ง๋ง fetchEventDetail์์ ์ฌ์ฉํ์ง ์์ต๋๋ค.
mapEvent๋ฅผ importํ์ง๋งfetchEventDetailํจ์์์๋ ์ฌ์ฉํ์ง ์๊ณ ์์ต๋๋ค. ์์ fetchEventDetail ์์ ์ฌํญ์ ์ ์ฉํ๋ฉด ์ด import๊ฐ ํ์ฉ๋ฉ๋๋ค.src/shared/types/eventtypes.ts (1)
1-42: ํ์ ์ ์์ ๋งคํ ๊ตฌํ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๋์ด ์์ต๋๋ค.๊ฒ์ฆ ๊ฒฐ๊ณผ,
mapEventํจ์๊ฐEventResponse์ optional ํ๋๋ค์EventData์ required ํ๋๋ค๋ก ์ ํํ๊ฒ ๋ณํํ๊ณ ์์ต๋๋ค:
address,startDate,endDate: ๋น ๋ฌธ์์ด ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์์ ํ๊ฒ ์ฒ๋ฆฌimageSrc:thumbnailUrl์ ์ฐ์ ์ผ๋ก, ์์ ๊ฒฝ์ฐimageUrl์ฌ์ฉ (๋ช ํํ ํด๋ฐฑ ๋ก์ง)liked:false๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ผ๊ด์ฑ ์๊ฒ ์ฒ๋ฆฌํ์ ๊ตฌ์กฐ์ ๊ตฌํ ๋ก์ง์ด ์ผ๊ด์ฑ ์๊ณ ์์ ํฉ๋๋ค. ์ถ๊ฐ ์์ ์ด ํ์ํ์ง ์์ต๋๋ค.
| imageSrc={item.imageUrl} | ||
| variant="gray" | ||
| size="small" | ||
| liked={false} |
There was a problem hiding this comment.
๐งฉ Analysis chain
๊ด๋ จ ํ์ฌ์ ๋ถ๋งํฌ ์ํ ํ์ธ์ด ํ์ํฉ๋๋ค.
๊ด๋ จ ํ์ฌ์ liked ์์ฑ์ด false๋ก ํ๋์ฝ๋ฉ๋์ด ์์ต๋๋ค. PR ๋ชฉํ์ "๋ถ๋งํฌ UI ๋๊ธฐํ ํ์ธ (todo)"๊ฐ ์ธ๊ธ๋์ด ์์ผ๋ฏ๋ก, ์ถํ ์์
์ผ๋ก ์ค์ ๋ถ๋งํฌ ์ํ๋ฅผ ๋ฐ์ํด์ผ ํฉ๋๋ค.
ํ์ฌ RelatedEvent ํ์
์ isBookmarked ํ๋๊ฐ ์๋์ง ํ์ธํ์ธ์:
๐ Script executed:
#!/bin/bash
# RelatedEvent ํ์
์ ์ ํ์ธ
ast-grep --pattern $'interface RelatedEvent {
$$$
}'Length of output: 259
๐ Script executed:
#!/bin/bash
# Check the actual code context around line 123 in [id].tsx
sed -n '115,130p' src/pages/events/[id].tsxLength of output: 445
๐ Script executed:
#!/bin/bash
# Search for RelatedEvent component usage and bookmark-related props
rg -n "liked\s*=" src/pages/events/[id].tsx -A 2 -B 2Length of output: 313
๐ Script executed:
#!/bin/bash
# Check if there's an Event type with bookmark state that should be mapped to RelatedEvent
rg -n "interface Event|type Event" src/shared/types/ -A 10Length of output: 1937
RelatedEvent ํ์
์ isBookmarked ํ๋๋ฅผ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
๊ฒ์ฆ ๊ฒฐ๊ณผ:
RelatedEvent์ธํฐํ์ด์ค์isBookmarkedํ๋๊ฐ ์ค์ ๋ก ์์ (src/shared/types/eventtypes.ts:23-27)- ๋ฉ์ธ ํ์ฌ๋
eventDetail.isBookmarked๋ก ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌ (line 100) - ๊ด๋ จ ํ์ฌ๋
liked={false}๋ก ํ๋์ฝ๋ฉ๋จ (line 123)
ํ์ํ ์์ :
- src/shared/types/eventtypes.ts์
RelatedEvent์ธํฐํ์ด์ค์isBookmarked?: booleanํ๋ ์ถ๊ฐ - API ์๋ต์์ ๊ด๋ จ ํ์ฌ์ ๋ถ๋งํฌ ์ํ ๋ฐ์ดํฐ ํฌํจ ํ์ธ
- src/pages/events/[id].tsx:123์
liked={false}๋ฅผliked={item.isBookmarked ?? false}๋ก ๋ณ๊ฒฝ
์ด๋ PR ๋ชฉํ์ธ "๋ถ๋งํฌ UI ๋๊ธฐํ ํ์ธ (todo)"๋ฅผ ์๋ฃํ๋ ๋ฐ ํ์์ ๋๋ค.
๐ค Prompt for AI Agents
In src/pages/events/[id].tsx around line 123 and src/shared/types/eventtypes.ts
around lines 23-27: RelatedEvent is missing an isBookmarked field and the
related-event component is hardcoding liked to false; add an optional boolean
isBookmarked? field to the RelatedEvent interface in
src/shared/types/eventtypes.ts, ensure the API response for related events
includes the bookmark state (update backend/serialization or mapper as needed),
and replace the hardcoded liked={false} in src/pages/events/[id].tsx with a prop
that uses the related itemโs isBookmarked value with a safe fallback (use
item.isBookmarked if present, otherwise false).
| const mapped = await Promise.all( | ||
| res.data.data.map(async (item: EventResponse) => { | ||
| const detail = await apiWithToken.get(`/api/events/${item.eventId}`); | ||
| const detailData = detail.data.data; | ||
|
|
||
| return { | ||
| id: item.eventId, | ||
| name: item.title, | ||
| description: item.body, | ||
| address: item.address ?? '', | ||
| startDate: item.startDate ?? '', | ||
| endDate: item.endDate ?? '', | ||
| imageSrc: detailData.imageUrl ?? '', | ||
| liked: item.isBookmarked ?? false, | ||
| } satisfies EventData; | ||
| }), | ||
| ); |
There was a problem hiding this comment.
์ฌ๊ฐํ N+1 ์ฟผ๋ฆฌ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค.
๊ฐ ์ด๋ฒคํธ๋ง๋ค ๊ฐ๋ณ์ ์ผ๋ก ์์ธ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ํด ์ถ๊ฐ API ํธ์ถ์ ์ํํฉ๋๋ค. ์ด๋ฒคํธ๊ฐ N๊ฐ๋ผ๋ฉด ์ด N+1๋ฒ์ API ํธ์ถ์ด ๋ฐ์ํ์ฌ ์ฌ๊ฐํ ์ฑ๋ฅ ์ ํ๋ฅผ ์ด๋ํฉ๋๋ค.
์์: ์ด๋ฒคํธ 50๊ฐ โ 51๋ฒ์ API ํธ์ถ (1๋ฒ ๋ชฉ๋ก + 50๋ฒ ์์ธ)
๋ฐฑ์๋ ํ๊ณผ ํ์ํ์ฌ ๋ค์ ์ค ํ๋๋ฅผ ๊ตฌํํ์ธ์:
- ๋ชฉ๋ก API ์๋ต์
imageUrl๊ณผisBookmarkedํฌํจ - ์ผ๊ด ์์ธ ์กฐํ API ์ถ๊ฐ (์:
POST /api/events/batch) - GraphQL ๋๋ ํ๋ ์ ํ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ์ง์
์์ ํด๊ฒฐ์ฑ ์ผ๋ก ํด๋ผ์ด์ธํธ ์ธก ์บ์ฑ์ด๋ ๋ฐฐ์น ์์ฒญ์ ๊ณ ๋ คํ ์ ์์ง๋ง, ๊ทผ๋ณธ์ ์ธ ํด๊ฒฐ์ ๋ฐฑ์๋ API ๊ฐ์ ์ด ํ์ํฉ๋๋ค.
// ์์ ํด๊ฒฐ์ฑ
์์ (๋ฐฑ์๋ ๋ณ๊ฒฝ์ด ์ฐ์ )
export const fetchEvents = async (
date: string,
): Promise<ApiResponse<EventData[]>> => {
const res = await apiWithToken.get('/api/events', {
params: { date },
});
// ์์ธ ์ ๋ณด๊ฐ ์ด๋ฏธ ํฌํจ๋์ด ์๋ค๊ณ ๊ฐ์
const mapped = res.data.data.map((item: EventResponse) => ({
id: item.eventId,
name: item.title,
description: item.body,
address: item.address ?? '',
startDate: item.startDate ?? '',
endDate: item.endDate ?? '',
imageSrc: item.imageUrl ?? '', // ๋ชฉ๋ก API์์ ์ ๊ณต
liked: item.isBookmarked ?? false,
}));
return {
...res.data,
data: mapped,
};
};There was a problem hiding this comment.
Actionable comments posted: 2
๐งน Nitpick comments (1)
src/shared/types/membertypes.ts (1)
5-13: ํ์ ์์ ์ฑ ๊ฐ์ ์ ์๋ชจ๋ ํ๋(eventId ์ ์ธ)๊ฐ optional๋ก ์ ์๋์ด ์์ด ํ์ ์์ ์ฑ์ด ์ฝํ๋ฉ๋๋ค. API ์๋ต์์ ํญ์ ์ ๊ณต๋๋ ํ๋๋ required๋ก ์ง์ ํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
๋ํ, PR ์์ฝ์์ ์ธ๊ธ๋ "๋ถ๋งํฌ UI ๋๊ธฐํ"๋ฅผ ์ํด์๋
liked๋๋isBookmarkedํ๋๊ฐ ํ์ํ ์ ์์ต๋๋ค.API ์๋ต ์คํ์ ํ์ธํ ํ, ๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํ๋ ๊ฒ์ ๊ณ ๋ คํ์ธ์:
bookmarkedEvents: { eventId: number; name: string; // ํญ์ ์ ๊ณต๋๋ฉด required๋ก address: string; description: string; imageSrc?: string; // ์ ํ์ ์ผ๋ก ์ ๊ณต๋๋ฉด optional startDate?: string; endDate?: string; isBookmarked?: boolean; // ๋ถ๋งํฌ ์ํ ์ถ์ ์ฉ }[];
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (4)
src/pages/mypage/index.tsx(3 hunks)src/shared/api/member.ts(0 hunks)src/shared/hooks/events/useBookmark.ts(1 hunks)src/shared/types/membertypes.ts(1 hunks)
๐ค Files with no reviewable changes (1)
- src/shared/api/member.ts
๐ง Files skipped from review as they are similar to previous changes (2)
- src/shared/hooks/events/useBookmark.ts
- src/pages/mypage/index.tsx
๐งฐ Additional context used
๐งฌ Code graph analysis (1)
src/shared/types/membertypes.ts (3)
src/shared/constants/events/eventsData.ts (1)
EventData(1-9)src/shared/components/container/EventCard.tsx (2)
EventCardProps(7-16)EventCardProps(18-164)src/pages/events/index.tsx (2)
EventPage(16-98)event(59-74)
โฐ 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: build-and-deploy
| bookmarkedEvents: { | ||
| eventId: number; | ||
| eventName: string; | ||
| eventImageUrl: string; | ||
| title?: string; | ||
| body?: string; | ||
| address?: string; | ||
| imageUrl?: string; | ||
| eventName?: string; | ||
| eventImageUrl?: string; | ||
| }[]; |
There was a problem hiding this comment.
์ค๋ณต๋๋ ํ๋๋ช ์ผ๋ก ์ธํ ํผ๋ ๊ฐ๋ฅ์ฑ
๋์ผํ ๊ฐ๋ ์ ๋ํ๋ด๋ ํ๋๊ฐ ์ค๋ณต ์ ์๋์ด ์์ต๋๋ค:
title๊ณผeventName(lines 7, 11)imageUrl๊ณผeventImageUrl(lines 10, 12)
์ด๋ ์๋นํ๋ ์ฝ๋์์ ์ด๋ค ํ๋๋ฅผ ์ฌ์ฉํด์ผ ํ ์ง ํผ๋์ ์ผ๊ธฐํ๊ณ ๋ฒ๊ทธ์ ์์ธ์ด ๋ ์ ์์ต๋๋ค.
๋ค์ diff๋ฅผ ์ ์ฉํ์ฌ ์ค๋ณต ํ๋๋ฅผ ์ ๊ฑฐํ๊ณ EventData ์ธํฐํ์ด์ค์ ์ผ๊ด์ฑ์ ์ ์งํ์ธ์:
bookmarkedEvents: {
eventId: number;
- title?: string;
- body?: string;
+ name?: string;
+ description?: string;
address?: string;
- imageUrl?: string;
- eventName?: string;
- eventImageUrl?: string;
+ imageSrc?: string;
+ startDate?: string;
+ endDate?: string;
}[];๐ 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.
| bookmarkedEvents: { | |
| eventId: number; | |
| eventName: string; | |
| eventImageUrl: string; | |
| title?: string; | |
| body?: string; | |
| address?: string; | |
| imageUrl?: string; | |
| eventName?: string; | |
| eventImageUrl?: string; | |
| }[]; | |
| bookmarkedEvents: { | |
| eventId: number; | |
| name?: string; | |
| description?: string; | |
| address?: string; | |
| imageSrc?: string; | |
| startDate?: string; | |
| endDate?: string; | |
| }[]; |
๐ค Prompt for AI Agents
In src/shared/types/membertypes.ts around lines 5 to 13, the bookmarkedEvents
item defines duplicated fields (title vs eventName and imageUrl vs
eventImageUrl) causing ambiguity; remove the duplicate fields so the shape
matches the canonical EventData interface (pick the canonical names used
elsewhere โ e.g., eventName and eventImageUrl), keep eventId, address, body and
optional canonical fields, and update any consuming code/tests to use the chosen
names to maintain consistency.
๐งฉ Analysis chain
EventData ์ธํฐํ์ด์ค์์ ๋ถ์ผ์น
bookmarkedEvents์ ํ๋๋ช
์ด src/shared/constants/events/eventsData.ts์ EventData ์ธํฐํ์ด์ค์ ์ผ์นํ์ง ์์ต๋๋ค:
bodyโdescriptionimageUrl/eventImageUrlโimageSrceventIdโidtitle/eventNameโname
๋ํ EventData์ ์๋ startDate์ endDate ํ๋๊ฐ ๋๋ฝ๋์ด ์์ต๋๋ค. ์ด ํ๋๋ค์ src/pages/events/index.tsx์ ์ด๋ฒคํธ ํํฐ๋ง ๋ก์ง์์ ์ฌ์ฉ๋๋ฏ๋ก, ๋ถ๋งํฌ๋ ์ด๋ฒคํธ๋ฅผ ๋์ผํ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ ์ ์๊ฒ ๋ฉ๋๋ค.
๋ค์ ์คํฌ๋ฆฝํธ๋ฅผ ์คํํ์ฌ EventData ํ์
์ ์ ์์ ์ฌ์ฉ์ฒ๋ฅผ ํ์ธํ์ธ์:
๐ Script executed:
#!/bin/bash
# EventData ์ธํฐํ์ด์ค ์ ์ ํ์ธ
ast-grep --pattern 'interface EventData {
$$$
}'
# bookmarkedEvents ์ฌ์ฉ์ฒ ํ์ธ
rg -nP --type=ts --type=tsx -C3 'bookmarkedEvents'Length of output: 548
Based on my verification of the EventData interface in the codebase, I can confirm the findings. The review comment correctly identifies field name mismatches, though it references the wrong file location. Here's the rewritten comment:
bookmarkedEvents ํ์
์ด EventData์ ํธํ๋์ง ์์
EventData ์ธํฐํ์ด์ค(src/shared/types/eventtypes.ts)์ ํ๋ ๊ตฌ์กฐ์ bookmarkedEvents์ ํ๋๋ช
์ด ์ผ์นํ์ง ์์ต๋๋ค. TypeScript์ ๊ตฌ์กฐ์ ํ์ดํ์ ๋ฐ๋ฅด๋ฉด ํ๋๋ช
์ด ์ ํํ ์ผ์นํด์ผ ํ๋ฏ๋ก ๋ค์ ํ๋๋ค์ ์์ ํด์ผ ํฉ๋๋ค:
eventIdโidtitle/eventNameโnamebodyโdescriptionimageUrl/eventImageUrlโimageSrc- ๋๋ฝ:
startDate,endDate,liked
src/shared/types/membertypes.ts์ bookmarkedEvents ํ์
์ EventData ์ธํฐํ์ด์ค์ ์ผ์นํ๋๋ก ์ ๋ ฌํ์ธ์.
๐ค Prompt for AI Agents
In src/shared/types/membertypes.ts around lines 5 to 13, the bookmarkedEvents
type does not match the EventData interface: rename eventId โ id, title and
eventName โ name, body โ description, imageUrl and eventImageUrl โ imageSrc; add
the missing fields startDate, endDate, and liked with the same types as in
EventData; ensure the resulting bookmarkedEvents array type exactly mirrors the
EventData interface (field names and types) defined in
src/shared/types/eventtypes.ts.
There was a problem hiding this comment.
Actionable comments posted: 2
โป๏ธ Duplicate comments (5)
src/pages/mypage/index.tsx (1)
78-78: ํ๋ ์ด์คํ๋ ์ด๋ฒคํธ ID ์ถฉ๋ ๊ฐ๋ฅ์ฑ์ด์ ๋ฆฌ๋ทฐ์์ ์ด๋ฏธ ์ง์ ๋ ์ฌํญ์ ๋๋ค. ํ๋์ฝ๋ฉ๋ ID(99, 98)๊ฐ ์ค์ API ์ด๋ฒคํธ ID์ ์ถฉ๋ํ ์ ์์ผ๋ฏ๋ก ์์ ์์ ์ฌ์ฉ์ ๊ถ์ฅํฉ๋๋ค.
src/shared/components/container/EventCard.tsx (1)
107-126: ๋ถ๋งํฌ ๋ฒํผ ์ ๊ทผ์ฑ ๋ฐ ์ค๋ณต ์ฝ๋์ด์ ๋ฆฌ๋ทฐ์์ ์ง์ ๋ ์ ๊ทผ์ฑ ๋ฌธ์ ๊ฐ ์ฌ์ ํ ๋จ์์์ต๋๋ค. ๋ถ๋งํฌ ๋ฒํผ์
aria-label์ด ์์ด ์คํฌ๋ฆฐ ๋ฆฌ๋ ์ฌ์ฉ์์๊ฒ ๋ถ๋ช ํํฉ๋๋ค.์ถ๊ฐ๋ก, medium(Lines 107-126)๊ณผ large(Lines 153-172) ์นด๋์ ๋ถ๋งํฌ ๋ฒํผ ๋ก์ง์ด ๊ฑฐ์ ๋์ผํ๊ฒ ์ค๋ณต๋์ด ์์ต๋๋ค. ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ
BookmarkButton์ปดํฌ๋ํธ๋ก ์ถ์ถํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค:const BookmarkButton = ({ isBookmarked, variant, onToggle }: { isBookmarked: boolean; variant: 'gray' | 'mint'; onToggle: () => void; }) => ( <button className="cursor-pointer p-1" onClick={(e) => { e.stopPropagation(); onToggle(); }} aria-label={isBookmarked ? '๋ถ๋งํฌ ํด์ ' : '๋ถ๋งํฌ ์ถ๊ฐ'} > <Icon name="HeartStraight" size={20} color={ isBookmarked ? 'red-400' : variant === 'mint' ? 'mint-400' : 'gray-300' } fillColor={isBookmarked ? 'red-300' : undefined} /> </button> );src/pages/mypage/events/[id].tsx (3)
16-20: ๋ฆฌ๋ค์ด๋ ํธ๋ก ์ธํ UI ๊น๋นก์์ด์ ๋ฆฌ๋ทฐ์์ ์ง์ ๋ ์ฌํญ์ ๋๋ค. ๋ก๋ฉ ์ค ๋ฆฌ๋ค์ด๋ ํธ ๋ก์ง์ด ์ด๊ธฐ ๋ ๋๋ง ์ ๊น๋นก์์ ์ ๋ฐํ ์ ์์ต๋๋ค. ๋ก๋ฉ ์ํ์ผ ๋ ์ค์ผ๋ ํค UI๋ฅผ ํ์ํ๋ ๊ฒ์ด ๋ ๋์ UX๋ฅผ ์ ๊ณตํฉ๋๋ค.
55-61: Image ์ปดํฌ๋ํธ์ sizes ์์ฑ ๋๋ฝ์ด์ ๋ฆฌ๋ทฐ์์ ์ง์ ๋ ์ฌํญ์ ๋๋ค.
fill์์ฑ์ ์ฌ์ฉํ๋ Image ์ปดํฌ๋ํธ์sizes์์ฑ์ด ์์ด ์ด๋ฏธ์ง ์ต์ ํ๊ฐ ์ ๋๋ก ์ด๋ฃจ์ด์ง์ง ์์ต๋๋ค.<Image src={imageUrl} alt={`${title} ์ด๋ฏธ์ง`} fill + sizes="(max-width: 354px) 100vw, 354px" className='object-cover rounded-[2rem]' />
86-86: ๋ถ๋งํฌ ์ํ ํด๋ฐฑ ๋ก์ง ์ฌ๊ฒํ ํ์์ด์ ๋ฆฌ๋ทฐ์์ ์ง์ ๋ ์ฌํญ์ ๋๋ค.
eventDetail.isBookmarked ?? true๋isBookmarked๊ฐ undefined์ผ ๋ true๋ก ํด๋ฐฑํ๋ฏ๋ก, ์ ์ฅํ ํ์ฌ ํ์ด์ง๋ผ๋ฉด ํญ์ true๋ก ๊ณ ์ ํ๊ฑฐ๋ false๋ก ํด๋ฐฑํ๋ ๊ฒ์ด ๋ ์์ ํฉ๋๋ค.
๐งน Nitpick comments (1)
src/shared/components/container/EventCard.tsx (1)
1-1: ์ฌ์ฉ๋์ง ์๋ useEffect import ์ ๊ฑฐ
useEffect๊ฐ import๋์์ง๋ง ์ปดํฌ๋ํธ์์ ์ฌ์ฉ๋์ง ์์ต๋๋ค. ๋ถํ์ํ import๋ ์ ๊ฑฐํ๋ ๊ฒ์ด ์ข์ต๋๋ค.-import { useState, useEffect } from 'react'; +import { useState } from 'react';
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (3)
src/pages/mypage/events/[id].tsx(4 hunks)src/pages/mypage/index.tsx(5 hunks)src/shared/components/container/EventCard.tsx(2 hunks)
๐งฐ Additional context used
๐ง Learnings (1)
๐ Learning: 2025-10-31T17:34:04.352Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.352Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/mypage/events/[id].tsxsrc/pages/mypage/index.tsx
๐งฌ Code graph analysis (2)
src/shared/components/container/EventCard.tsx (4)
src/shared/hooks/events/useBookmark.ts (1)
useBookmark(8-47)src/pages/mypage/components/LoginRequired.tsx (1)
LoginRequired(10-21)src/shared/components/container/Card.tsx (1)
Card(32-48)src/shared/icons/components/icon.tsx (1)
Icon(58-130)
src/pages/mypage/events/[id].tsx (3)
src/shared/hooks/events/useEventDetail.ts (1)
useEventDetail(15-21)src/pages/events/index.tsx (2)
EventPage(16-98)event(59-74)src/pages/events/[id].tsx (3)
router(8-111)router(25-25)e(12-12)
โฐ 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: build-and-deploy
๐ Additional comments (4)
src/shared/components/container/EventCard.tsx (2)
34-35: ๋ถ๋งํฌ ํ ํตํฉ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.
useBookmarkํ ์ ํตํ ๋ถ๋งํฌ ์ํ ๊ด๋ฆฌ๊ฐ ์ ์ ํ๊ฒ ๊ตฌํ๋์์ต๋๋ค.eventId์likedprops๋ฅผ ์ ๋ฌํ๊ณ ํ์ํ ์ํ ๋ฐ ํจ์๋ฅผ ์ ํํ ์ถ์ถํ๊ณ ์์ต๋๋ค.
40-41: ๋ก๊ทธ์ธ ํ์ ์ฒ๋ฆฌ๊ฐ ์ ์ ํ๊ฒ ๊ตฌํ๋์์ต๋๋ค.๋ถ๋งํฌ ์ก์ ์ ๋ํ ๋ก๊ทธ์ธ ๊ฒ์ดํ ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.
requireLogin์ํ์ ๋ฐ๋ผLoginRequired์ปดํฌ๋ํธ๋ฅผ ์กฐ๊ฑด๋ถ ๋ ๋๋งํ๊ณ , ๋ซ๊ธฐ ์ ์ํ๋ฅผ ์ด๊ธฐํํ๋ ๋ก์ง์ด ์ ์ ํฉ๋๋ค.src/pages/mypage/events/[id].tsx (2)
13-14: API ํ ํตํฉ์ด ์ฌ๋ฐ๋ฅด๊ฒ ๊ตฌํ๋์์ต๋๋ค.
useEventDetailํ ์ ์ฌ์ฉํ ์ด๋ฒคํธ ์์ธ ์ ๋ณด ์กฐํ๊ฐ ์ ์ ํ๊ฒ ๊ตฌํ๋์์ต๋๋ค. ๋ผ์ฐํฐ ์ฟผ๋ฆฌ์์eventId๋ฅผ ์ถ์ถํ๊ณ ํ ์ ์ ๋ฌํ๋ ๋ก์ง์ด ์ ํํฉ๋๋ค.
25-25: ์ด๋ฒคํธ ์์ธ ๋ฐ์ดํฐ ๋งคํ์ด ์ ์ ํฉ๋๋ค.API ์๋ต์์ ํ์ํ ํ๋๋ค์ ์ฌ๋ฐ๋ฅด๊ฒ ์ถ์ถํ๊ณ ์์ผ๋ฉฐ, ์ปดํฌ๋ํธ ์ ๋ฐ์ ๊ฑธ์ณ ์ผ๊ด๋๊ฒ ์ฌ์ฉ๋๊ณ ์์ต๋๋ค.
| @@ -1,6 +1,7 @@ | |||
| 'use client'; | |||
There was a problem hiding this comment.
Pages Router์์๋ 'use client' ์ง์๋ฌธ์ด ํ์ํ์ง ์์ต๋๋ค.
์ด ํ๋ก์ ํธ๋ Next.js Pages Router๋ฅผ ์ฌ์ฉํ๋ฏ๋ก 'use client' ์ง์๋ฌธ์ ์ ๊ฑฐํด์ผ ํฉ๋๋ค. ์ด ์ง์๋ฌธ์ App Router ์ ์ฉ์ ๋๋ค.
Based on learnings
๋ค์๊ณผ ๊ฐ์ด ์์ ํ์ธ์:
-'use client';
-
import React, { useEffect } from 'react';๐ Committable suggestion
โผ๏ธ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| 'use client'; | |
| import React, { useEffect } from 'react'; |
๐ค Prompt for AI Agents
src/pages/mypage/index.tsx around lines 1 to 1: remove the top-level "'use
client';" directive because this file is in the Next.js Pages Router where the
directive is not needed; delete that line so the file does not include the App
Routerโspecific client directive.
| eventId={event.eventId} | ||
| name={event.title ?? event.eventName ?? ''} | ||
| address={event.address ?? ''} | ||
| description={event.body ?? ''} | ||
| variant='gray' | ||
| size='large' | ||
| imageSrc={event.eventImageUrl} | ||
| imageSrc={event.imageUrl} | ||
| liked={true} | ||
| onClick={() => router.push(`/mypage/events/${event.eventId}`)} |
There was a problem hiding this comment.
๐งฉ Analysis chain
๋ถ๋งํฌ๋ ์ด๋ฒคํธ ๋ฐ์ดํฐ ๋งคํ ํ์ธ
EventCard์ ์ ๋ฌ๋๋ props๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ๋งคํ๋์์ต๋๋ค. ๋ค๋ง Line 104์ event.title ?? event.eventName ?? '' ํด๋ฐฑ ์ฒด์ธ์ API ์๋ต์ ํ๋๋ช
์ด ์ผ๊ด๋์ง ์์ ๊ฐ๋ฅ์ฑ์ ์์ฌํฉ๋๋ค.
API ์๋ต ๊ตฌ์กฐ๋ฅผ ํ์ธํ์ฌ title๊ณผ eventName ํ๋๊ฐ ๋ชจ๋ ํ์ํ์ง ๊ฒ์ฆํ์ธ์:
๐ Script executed:
#!/bin/bash
# ๋ถ๋งํฌ ์ด๋ฒคํธ API ์๋ต ํ์
์ ์ ํ์ธ
rg -nP --type=ts "bookmarkedEvents|BookmarkedEvent" -A5 -B2Length of output: 1728
event.eventName ํ๋๊ฐ ํ์
์ ์์ ์กด์ฌํ์ง ์์
src/shared/types/membertypes.ts์ BookmarkedEvent ํ์
์ ์์์ eventName ํ๋๊ฐ ์์ต๋๋ค. Line 104์ ํด๋ฐฑ ์ฒด์ธ์์ event.eventName์ ์ฐธ์กฐํ๋ ๊ฒ์ ๋ถํ์ํ ๋ฐฉ์ด ์ฝ๋ฉ์
๋๋ค.
ํ์
์ ์์ ๋ฐ๋ฅด๋ฉด title ํ๋๋ง ์กด์ฌํ๋ฏ๋ก, ๋ค์๊ณผ ๊ฐ์ด ๋จ์ํํ์ธ์:
name={event.title ?? ''}๐ค Prompt for AI Agents
In src/pages/mypage/index.tsx around lines 103 to 111, the fallback chain
references event.eventName which is not defined in the BookmarkedEvent type;
replace the name prop expression with only the typed field (name={event.title ??
''}) and remove the unnecessary event.eventName fallback to match the type
definition.
There was a problem hiding this comment.
Actionable comments posted: 2
โป๏ธ Duplicate comments (3)
src/pages/events/[id].tsx (3)
1-12: Pages Router์์๋'use client'๊ฐ ๋ถํ์ํฉ๋๋ค.์ด ํ์ผ์
pages๋ผ์ฐํฐ์ฉ ํ์ด์ง๋ผ์ Line 1์'use client';์ง์์๋ ์๋ฏธ๊ฐ ์๊ณ ํผ๋๋ง ์ค ์ ์์ต๋๋ค. ๋จ์ํ ์ ๊ฑฐํด๋ ๋์์๋ ์ํฅ์ด ์์ต๋๋ค.-'use client'; - import { useEffect } from 'react';
53-57:startDate!,endDate!non-null ๋จ์ธ์ ์ ๊ฑฐํ๋ ํธ์ด ์์ ํฉ๋๋ค.
eventDetail์startDate,endDate๊ฐ ์ค์ ๋ก optional์ผ ์ ์๋๋ฐ, non-null ๋จ์ธ(!)์ ์ฐ๋ฉด ํ์ ์ฒดํฌ์์ ๋๋ฝ๋ ์ผ์ด์ค๋ฅผ ์จ๊ธธ ์ ์์ต๋๋ค.DateTag๋ ์ด๋ฏธundefined๋ฅผ ์ฒ๋ฆฌํ๊ณ , ๋ ๋ค ์์ผ๋ฉดnull์ ๋ฐํํ๋๋ก ๊ตฌํ๋์ด ์์ผ๋ฏ๋ก(๊ด๋ จ ํ์ผ ์ฐธ๊ณ ), ๋จ์ธ ์์ด ๊ทธ๋๋ก ๋๊ฒจ๋ ๋ฌด๋ฐฉํฉ๋๋ค. ๊ธฐ์กด ๋ฆฌ๋ทฐ์์ ์ง์ ๋ ๋ด์ฉ๊ณผ ๋์ผํฉ๋๋ค.- <DateTag startDate={startDate!} endDate={endDate!} /> + <DateTag startDate={startDate} endDate={endDate} />
109-137: ๊ด๋ จ ํ์ฌ ์นด๋: ๋ถ๋งํฌ/ํค/ํด๋ฆญ ์ฒ๋ฆฌ ๊ฐ์ ์ฌ์ง๊ด๋ จ ํ์ฌ ๋ ๋๋ง ์ ๋ฐ์ ์ ๋์ํ๊ฒ ์ง๋ง, ๋ช ๊ฐ์ง ๋ณด์ ํฌ์ธํธ๊ฐ ์์ต๋๋ค.
๋ถ๋งํฌ ์ํ ํ๋์ฝ๋ฉ
- ํ์ฌ ๊ด๋ จ ํ์ฌ ์นด๋์์
liked={false}๋ก ๊ณ ์ ๋์ด ์์ด ์ค์ ๋ถ๋งํฌ ์ํ๊ฐ UI์ ๋ฐ์๋์ง ์์ต๋๋ค.- PR ์ค๋ช ์๋ โ๋ถ๋งํฌ UI ๋๊ธฐํ ํ์ธ: ๋ฏธ์๋ฃโ๊ฐ ์์ด์, ์ถํ์๋ ๊ด๋ จ ํ์ฌ ํ์ /์๋ต์ ๋ถ๋งํฌ ํ๋๋ฅผ ์ถ๊ฐํ๊ณ ์ฌ๊ธฐ์๋
liked={item.isBookmarked ?? false}ํํ๋ก ์ฐ๊ฒฐํ๋ ํ์ ์์ ์ด ํ์ํด ๋ณด์ ๋๋ค. (๊ธฐ์กด ๋ฆฌ๋ทฐ์์ ์ธ๊ธ๋ ๋ด์ฉ๊ณผ ๊ฐ์ ๋ฐฉํฅ์ ๋๋ค.)placeholder ์นด๋์ onClick
- ๋น ์ฌ๋กฏ์ฉ ์นด๋์
onClick={() => null}์ ๋๊ธฐ๊ณ ์์ด, ๊ฒ์ผ๋ก๋ ํด๋ฆญ ๊ฐ๋ฅํด ๋ณด์ด๋๋ฐ ์๋ฌด ๋์์ ํ์ง ์์ต๋๋ค.- ์์
onClick์ ์ ๋ฌํ์ง ์๊ฑฐ๋, ํด๋ฆญ์ ๋นํ์ฑํํ๋ ์คํ์ผ์ ์ฃผ๋ ํธ์ด ๋ ๋ช ํํ ์ ์์ต๋๋ค.index ๊ธฐ๋ฐ key
- ํ์ฌ
key={idx}๋ฅผ ์ฌ์ฉํ๊ณ ์๋๋ฐ, ๋ฆฌ์คํธ ํฌ๊ธฐ๊ฐ ๊ณ ์ (2๊ฐ)๋ผ ์ค์ ๋ฌธ์ ๋ ํฌ์ง ์์ง๋ง, ๊ฐ๋ฅํ๋ค๋ฉดisEmpty์ฌ๋ถ์eventId๋ฅผ ์กฐํฉํ ํค๋ฅผ ์ฐ๋ฉด ๋ฆฌ๋ ๋๋ง ์์ ์ฑ์ด ์กฐ๊ธ ๋ ์ข์์ง๋๋ค.์์:
- {nextList.map((item: RelatedEventOrEmpty, idx) => ( - <div key={idx} className={cn('w-[17rem]')}> + {nextList.map((item: RelatedEventOrEmpty, idx) => ( + <div + key={isEmptyItem(item) ? `empty-${idx}` : item.eventId} + className={cn('w-[17rem]')} + > ... - liked={false} - onClick={() => null} + liked={false}
๐งน Nitpick comments (4)
src/pages/events/[id].tsx (2)
16-31: ์๋ชป๋idํ๋ผ๋ฏธํฐ์ ๋ํ ์ฒ๋ฆฌ ๋ณด์์ ๊ณ ๋ คํด๋ณด์ธ์.ํ์ฌ ๋ก์ง์์๋:
const eventId = Number(id);if (!eventId) return null;๋ก ์ฒ๋ฆฌํ๊ณ ์์ด,
/events/abc์ฒ๋ผ ์ซ์๊ฐ ์๋id๋ก ์ ๊ทผํ๋ฉด ํ๋ฉด์ด ๊ทธ๋ฅ ๋น์ด ์๋ ์ํ(null ๋ ๋๋ง)๋ก ๋จ์ต๋๋ค. ์ฌ์ฉ์ฑ์ ์ํด ์ด๋ฐ ๊ฒฝ์ฐ์๋/events๋ก ๋ฆฌ๋ค์ด๋ ํธํ๊ฑฐ๋ ์๋ฌ ์ํ๋ฅผ ๋ ธ์ถํ๋ ์ชฝ์ด ๋ ๋ซ์ต๋๋ค.์๋ฅผ ๋ค์ด:
- const eventId = Number(id); + const eventId = Number(id); + + if (!router.isReady) return null; + if (!Number.isFinite(eventId) || eventId <= 0) { + router.replace('/events'); + return null; + } - - if (!eventId) return null; - if (isError || !eventDetail) return null; - if (!router.isReady) return null; + if (isError || !eventDetail) return null;์ฒ๋ผ
router.isReady์eventId์ ํจ์ฑ์ ๋จผ์ ๊ฒ์ฌํ๋ ํจํด์ ๊ณ ๋ คํด๋ณผ ์ ์์ต๋๋ค.
39-43: ํค๋ ํ์ดํ์ ์ค์ ํ์ฌ๋ช ์ผ๋ก ๊ต์ฒดํ๋ ๊ฒ์ ๊ณ ๋ คํด๋ณด์ธ์.ํ์ฌ ํค๋
title์ด ํ๋์ฝ๋ฉ๋'ํ์ฌ๋ช '์ด๋ผ, ์์ธ์์ ์ค์ ํ์ฌ๋ช ์ด ๋ณด์ด์ง ์์ต๋๋ค. Line 33์์title์ ์ด๋ฏธ ๊ตฌ์กฐ ๋ถํดํ๊ณ ์์ผ๋, ๋ค์์ฒ๋ผ ๋ฐ๊พธ๋ฉด UX๊ฐ ๋ ์์ฐ์ค๋ฌ์ธ ๊ฒ ๊ฐ์ต๋๋ค.- <Header - title='ํ์ฌ๋ช ' + <Header + title={title} onClick={() => router.push(`/events${date ? `?date=${date}` : ''}`)}src/pages/events/index.tsx (2)
1-14: Pages Router์์๋use client์ง์์ด๊ฐ ๋ถํ์ํฉ๋๋ค.์ด ํ์ผ์
src/pagesํ์์ Pages Router ํ์ด์ง๋ผ ๊ธฐ๋ณธ์ ์ผ๋ก ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ก ์ฒ๋ฆฌ๋๋ฏ๋ก, ์ต์๋จ์'use client';๋ ์ ๊ฑฐํด๋ ๋์์ ๋ณํ๊ฐ ์์ต๋๋ค. Pages Router ์ฝ๋์๋ง ์ด ์ง์์ด๊ฐ ์์ฌ ์์ผ๋ฉด ๋์ค์ App Router ๋์ ์ ํผ๋์ ์ค ์ ์์ด ๊ฐ๊ธ์ ์ ๋ฆฌํ๋ ํธ์ ๊ถ์ฅํฉ๋๋ค.useRouter๋ฅผnext/router์์ importํ๋ ๋ถ๋ถ๊ณผ ์๋ก ์ถ๊ฐ๋useEvents/EventDataimport ๊ตฌ์ฑ์ ํ์ฌ ํ๋ก์ ํธ ์ปจ๋ฒค์ ๊ณผ ์ ๋ง์ต๋๋ค.Based on learnings
67-69:router.push์์selectedDateString์ด ๋น์ด ์์ ๋๋ฅผ ๋ฐฉ์ดํด ๋๋ฉด ๋ ์์ ํฉ๋๋ค.ํ์ฌ ๊ตฌํ์์๋ ํญ์
/events/${event.id}?date=${selectedDateString}ํํ๋ก ์ด๋ํฉ๋๋ค. ์ง๊ธ ๊ตฌ์กฐ์ ์นด๋๊ฐ ๋ ธ์ถ๋๋ ์์ ์๋date๊ฐ ์ค์ ๋์ด ์์ด ์ค์ ๋กdate=๋ง ๋น์ด ์๋ ๊ฒฝ์ฐ๋ ๊ฑฐ์ ์๊ฒ ์ง๋ง, ์ถํ ๊ธฐ๋ณธ ์ด๋ฒคํธ ๋ชฉ๋ก์ ๋ก๋ํ๋๋ก ๋ณ๊ฒฝ๋ ๊ฒฝ์ฐ ๋น ์ฟผ๋ฆฌ(?date=)๊ฐ ๋ด๋ ค๊ฐ๋ฉด์ ์์ธ ํ์ด์ง์์dateํ์ฑ ์ edge case๊ฐ ์๊ธธ ์ฌ์ง๊ฐ ์์ต๋๋ค.๊ฐ๋จํ ํด๋ฆญ ํธ๋ค๋ฌ ์์์ ๋ ์ง๊ฐ ์์ ๋๋ง ์ฟผ๋ฆฌ๋ฅผ ๋ถ์ด๋ ์์ผ๋ก ๋ฐฉ์ดํด ๋๋ ๊ฑธ ์ ์๋๋ฆฝ๋๋ค.
- onClick={() => - router.push(`/events/${event.id}?date=${selectedDateString}`) - } + onClick={() => { + if (selectedDateString) { + router.push( + `/events/${event.id}?date=${selectedDateString}`, + ); + } else { + router.push(`/events/${event.id}`); + } + }}ํ์ ์์ ์ ์๋์ง๋ง, ์ดํ ์๊ตฌ์ฌํญ ๋ณ๊ฒฝ์ ์กฐ๊ธ ๋ ๊ฒฌ๊ณ ํ๊ฒ ๋์ํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค.
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (2)
src/pages/events/[id].tsx(1 hunks)src/pages/events/index.tsx(3 hunks)
๐งฐ Additional context used
๐ง Learnings (1)
๐ Learning: 2025-10-31T17:34:04.352Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.352Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/events/index.tsxsrc/pages/events/[id].tsx
๐งฌ Code graph analysis (2)
src/pages/events/index.tsx (4)
src/shared/hooks/events/useEvents.ts (1)
useEvents(7-21)src/shared/components/datePicker/DatePicker.tsx (1)
DatePicker(17-97)src/shared/types/eventtypes.ts (1)
EventData(12-21)src/shared/components/container/EventCard.tsx (1)
EventCardProps(18-164)
src/pages/events/[id].tsx (5)
src/shared/types/eventtypes.ts (1)
RelatedEventOrEmpty(42-42)src/shared/hooks/events/useEventDetail.ts (1)
useEventDetail(15-21)src/shared/utils/buildNextEventList.ts (1)
buildNextEventList(5-17)src/pages/events/components/DateTag.tsx (1)
DateTag(24-44)src/shared/components/container/EventCard.tsx (3)
EventCardProps(18-164)EventCardProps(7-16)prev(29-29)
โฐ 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: build-and-deploy
๐ Additional comments (5)
src/pages/events/[id].tsx (4)
13-14:isEmptyItemํ์ ๊ฐ๋ ๊ตฌํ ์ข์ต๋๋ค.
RelatedEventOrEmpty์์{ isEmpty: true }์ผ์ด์ค๋ฅผ'isEmpty' in item์ผ๋ก ์ขํ๋ ๋ฐฉ์์ด ๋ช ํํ๊ณ , ์ดํ JSX ๋ถ๊ธฐ์์๋ ์ ํ์ฉ๋๊ณ ์์ต๋๋ค. ๋ณ๋ค๋ฅธ ์์ ์๊ฒฌ ์์ต๋๋ค.
33-37:eventDetail๊ตฌ์กฐ ๋ถํด์buildNextEventList์ฌ์ฉ์ ์ผ๊ด์ฑ ์๊ฒ ์ ์ ๋ฆฌ๋์์ต๋๋ค.์์ธ ์ ๋ณด์์ ํ์ํ ํ๋๋ง ๊ตฌ์กฐ ๋ถํดํ ๋ค,
buildNextEventList๋ก ๊ด๋ จ ํ์ฌ ๋ฆฌ์คํธ๋ฅผ 2๊ฐ ๊ณ ์ ์ผ๋ก ๋ง์ถ๋ ๊ตฌ์กฐ๊ฐ ๋ช ํํ๊ณ ์ฌ์ฌ์ฉ์ฑ๋ ์ข์ ๋ณด์ ๋๋ค. ์ด ๋ถ๋ถ์ ๊ทธ๋๋ก ์ ์งํด๋ ์ข๊ฒ ์ต๋๋ค.
61-82: ๋ํ ์ด๋ฏธ์ง ์น์ ์ ์ ๊ทผ์ฑ ์ฒ๋ฆฌ ์ข์ต๋๋ค.์ด๋ฏธ์ง๊ฐ ์์ ๋๋
alt={${title} ์ด๋ฏธ์ง}๋ก, ์์ ๋๋role="img"์aria-label๋ก ๋์ฒด ํ ์คํธ๋ฅผ ์ ๊ณตํ๋ ํจํด์ด ๋ช ํํ๊ณ ์ ๊ทผ์ฑ ์ธก๋ฉด์์๋ ์ ์ ํฉ๋๋ค.Image์ปดํฌ๋ํธ์ fallback div ๋์ด/๋น์จ๋ ์ผ๊ด๋์ด UX๊ฐ ์์ ์ ์ ๋๋ค.
92-101: ๋ฉ์ธ ํ์ฌ ์นด๋์ ๋ถ๋งํฌ ์ํ๋ฅผ ์ฐ๊ฒฐํ ๋ถ๋ถ ์ข์ต๋๋ค.
eventId,name,description์ ๋ํดliked={eventDetail.isBookmarked ?? false}๋ก ์์ธ์ ๋ถ๋งํฌ ์ํ๋ฅผ ๋ฐ์ํ๊ณ ์์ด, ๋ถ๋งํฌ UI์ ๋ฐ์ดํฐ๊ฐ ์ ์ฑํฌ๋ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค. ์ด ๋ถ๋ถ์ ์๊ตฌ์ฌํญ์ ์ ๋ง๋ ๊ตฌํ ๊ฐ์ต๋๋ค.src/pages/events/index.tsx (1)
38-38: ์๋จControlBar๋ฅผfixed๋ก ์ฌ๋ฆฐ ๋ณ๊ฒฝ์ UX ์ธก๋ฉด์์ ์ข์ ๋ณด์ ๋๋ค.์คํฌ๋กค ์์๋ ์ปจํธ๋กค ๋ฐ๊ฐ ํญ์ ๋ณด์ด๋๋ก ๋์ด ์ด๋ฒคํธ ๋ ์ง/ํํฐ ์กฐ์์ด ์ฌ์์ง ๊ฒ ๊ฐ๊ณ , z-index์ padding ์ค์ ๋ ๋ค๋ฅธ ์์๋ค๊ณผ ์ถฉ๋ ์์ด ๋ณด์ ๋๋ค.
There was a problem hiding this comment.
Actionable comments posted: 1
โป๏ธ Duplicate comments (4)
src/pages/events/index.tsx (4)
57-102: ๋ก๋ฉ/์๋ฌ/๋ ์ง ๋ฏธ์ ํ ์ํ๊ฐ ๋น ๋ฐ์ดํฐ์ ๊ตฌ๋ถ๋์ง ์์ต๋๋ค.์ด์ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ์์ ์ง์ ๋ ๋ด์ฉ์ด ์์ง ๋ฐ์๋์ง ์์์ต๋๋ค. ํ์ฌ
filteredEvents.length > 0์กฐ๊ฑด๋ง์ผ๋ก ๋ถ๊ธฐํ๊ณ ์์ด์ ๋ค์ ์ํฉ๋ค์ด ๋ชจ๋ ๋์ผํ๊ฒ "์ ํํ์ ๋ ์ ํ์ฌ๊ฐ ์์ด์!" ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ค๋๋ค:
- API ํธ์ถ ์ค(๋ก๋ฉ)
- API ํธ์ถ ์คํจ(์๋ฌ)
- ๋ ์ง๋ฅผ ์์ง ์ ํํ์ง ์์
- ์ค์ ๋ก ํด๋น ๋ ์ง์ ํ์ฌ๊ฐ ์์
20๋ฒ ๋ผ์ธ์์
isLoading,isError๋ฅผ ํจ๊ป ๊ตฌ์กฐ ๋ถํดํ๊ณ , ๋ ๋๋ง ๋ก์ง์ ๋ค์๊ณผ ๊ฐ์ด ๊ฐ์ ํด ์ฃผ์ธ์:{/* ํ์ฌ์นด๋ & ๋นํ๋ฉด */} - {filteredEvents.length > 0 ? ( + {isLoading ? ( + <div className='flex items-center justify-center mt-[15rem]'> + <p className='text-label-lg text-gray-500'>ํ์ฌ ๋ชฉ๋ก์ ๋ถ๋ฌ์ค๋ ์ค์ ๋๋ค...</p> + </div> + ) : isError ? ( + <div className='flex flex-col items-center justify-center text-center mt-[15rem]'> + <p className='text-label-lg text-gray-700'> + ํ์ฌ ์ ๋ณด๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ด์. + <br /> + ์ ์ ํ ๋ค์ ์๋ํด ์ฃผ์ธ์. + </p> + </div> + ) : date && filteredEvents.length > 0 ? ( <section aria-label='์ด๋ฒคํธ ๋ชฉ๋ก' className={cn( @@ -83,7 +95,7 @@ ))} </section> - ) : ( + ) : date ? ( <div className='flex flex-col items-center justify-center text-center mt-[15rem]' role='status' @@ -99,6 +111,7 @@ ํ์ฌ๊ฐ ์์ด์! </p> </div> + ) : null} )}๋ํ 19๋ฒ ๋ผ์ธ์
date์ด๊ธฐ๊ฐ์new Date()๋ก ์ค์ ํ๋ฉด ์ฌ์ฉ์๊ฐ ์ฒ์ ์ง์ ํ ๋ ์ค๋ ๋ ์ง์ ํ์ฌ๋ฅผ ๋ฐ๋ก ๋ณผ ์ ์์ด UX๊ฐ ๋ ์ข์์ง๋๋ค.
19-21: ๋ก๋ฉ/์๋ฌ ์ํ๋ฅผ ๊ตฌ๋ ํ์ง ์๊ณ ์์ต๋๋ค.์ด์ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ์์ ์ง์ ๋ ๋ด์ฉ์ด ์์ง ๋ฐ์๋์ง ์์์ต๋๋ค.
useEventsํ ์isLoading๊ณผisError๋ฅผ ๋ฐํํ๋๋ฐ, ํ์ฌ๋events๋ง ๊ตฌ์กฐ ๋ถํดํ๊ณ ์์ด์ ๋ก๋ฉ ์ค์ด๊ฑฐ๋ ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋๋ ๋์ผํ๊ฒ ๋น ์ํ UI๊ฐ ๋ ธ์ถ๋ฉ๋๋ค.๋ค์๊ณผ ๊ฐ์ด ์์ ํด ์ฃผ์ธ์:
- const { events } = useEvents(date); + const { events, isLoading, isError } = useEvents(date);๊ทธ๋ฆฌ๊ณ ๋ ๋๋ง ๋ก์ง์์ ์ด ์ํ๋ค์ ํ์ฉํด์ผ ํฉ๋๋ค(์๋ ๋ณ๋ ์ฝ๋ฉํธ ์ฐธ์กฐ).
46-53:label์htmlFor๊ฐ ์ค์ DOM ์์์ ๋งค์นญ๋์ง ์์ต๋๋ค.์ด์ ๋ฆฌ๋ทฐ ์ฝ๋ฉํธ์์ ์ง์ ๋ ๋ด์ฉ์ด ์์ง ๋ฐ์๋์ง ์์์ต๋๋ค.
htmlFor='event-date'๋ก ์ง์ ๋์ด ์์ง๋ง,DatePicker๋idprop์ ๋ฐ์ง ์์์ ์ค์ DOM์id="event-date"์ธ ์์๊ฐ ์กด์ฌํ์ง ์์ต๋๋ค.๋ค์ ์ค ํ๋๋ฅผ ์ ํํด ์ฃผ์ธ์:
- ๊ฐ๋จํ ๋ฐฉ๋ฒ:
label์ ์ ๊ฑฐํ๊ณariaLabel๋ง ์ ์ง- <label htmlFor='event-date' className='sr-only'> - ํ์ฌ ๋ ์ง ์ ํ - </label> <DatePicker ariaLabel='ํ์ฌ ๋ ์ง ์ ํ'
- ์์ ํ ๋ฐฉ๋ฒ:
DatePicker์ปดํฌ๋ํธ๋ฅผ ํ์ฅํดidprop์ ๋ฐ๋๋ก ์์ ํ๊ณ , ์ฌ๊ธฐ์id='event-date'๋ฅผ ์ ๋ฌ
24-28: ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก๋ถํฐ ํ์ฑํ ๋ ์ง์ ์ ํจ์ฑ ๊ฒ์ฆ์ด ์์ต๋๋ค.
dateQuery๋ฅผnew Date(String(dateQuery))๋ก ๋ณํํ ๋, ์๋ชป๋ ๋ ์ง ๋ฌธ์์ด์ด ๋ค์ด์ค๋ฉด Invalid Date ๊ฐ์ฒด๊ฐ ์์ฑ๋ ์ ์์ต๋๋ค. ์ด ๊ฒฝ์ฐ UI๊ฐ ์๋์น ์๊ฒ ๋์ํ ์ ์์ต๋๋ค.๋ ์ง ์ ํจ์ฑ์ ๊ฒ์ฆํ๋๋ก ๊ฐ์ ํด ์ฃผ์ธ์:
useEffect(() => { if (router.isReady && dateQuery) { - setDate(new Date(String(dateQuery))); + const parsed = new Date(String(dateQuery)); + if (!Number.isNaN(parsed.getTime())) { + setDate(parsed); + } } }, [router.isReady, dateQuery]);
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (1)
src/pages/events/index.tsx(3 hunks)
๐งฐ Additional context used
๐ง Learnings (1)
๐ Learning: 2025-10-31T17:34:04.352Z
Learnt from: jjangminii
Repo: geulDa/FE PR: 64
File: src/pages/map/result/components/ResultMap.tsx:3-3
Timestamp: 2025-10-31T17:34:04.352Z
Learning: The geulDa/FE project uses Next.js Pages Router (not App Router), so components should import useRouter from 'next/router' and should not use the 'use client' directive.
Applied to files:
src/pages/events/index.tsx
๐งฌ Code graph analysis (1)
src/pages/events/index.tsx (4)
src/shared/hooks/events/useEvents.ts (1)
useEvents(7-21)src/shared/utils/date.ts (1)
formatDateToISO(1-6)src/shared/components/datePicker/DatePicker.tsx (1)
DatePicker(17-97)src/shared/types/eventtypes.ts (1)
EventData(12-21)
โฐ 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: build-and-deploy
๐ Additional comments (1)
src/pages/events/index.tsx (1)
39-39: z-50 z-index ๊ฐ์ ์ ์ ํฉ๋๋ค.๊ฒ์ฆ ๊ฒฐ๊ณผ, ํ๋ก์ ํธ์ z-index ๊ณ์ธต ๊ตฌ์กฐ๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
- z-50: ControlBar ๋ฐ ๊ณ ์ ํค๋ (์๋๋ ๋ฐฐ์น)
- z-[99]: Overlay ์ปดํฌ๋ํธ
- z-[100]: PopupSet, Header (์ต์์)
ControlBar์ z-50์ ๋ชจ๋ฌ๊ณผ ํ์ (z-[99], z-[100])๋ณด๋ค ์๋์ ์์นํ๋๋ก ์๋์ ์ผ๋ก ์ค์ ๋์์ผ๋ฉฐ, ์ด๋ ๊ณ ์ ํค๋์ ์ฌ๋ฐ๋ฅธ ๋์์ ๋๋ค. UI ์์ ๊ฐ ๋ ์ด์ด ์ถฉ๋์ด ์์ต๋๋ค.
| @@ -1,6 +1,6 @@ | |||
| 'use client'; | |||
There was a problem hiding this comment.
Pages Router์์๋ 'use client' ์ง์์ด๊ฐ ํ์ํ์ง ์์ต๋๋ค.
ํ์ฌ ํ๋ก์ ํธ๋ Next.js Pages Router๋ฅผ ์ฌ์ฉํ๊ณ ์๋๋ฐ, 'use client' ์ง์์ด๋ App Router ์ ์ฉ์
๋๋ค. Pages Router์ pages/ ๋๋ ํ ๋ฆฌ ๋ด ํ์ผ๋ค์ ๊ธฐ๋ณธ์ ์ผ๋ก ํด๋ผ์ด์ธํธ ์ธก์์ ํ์ด๋๋ ์ด์
๋๋ฏ๋ก ์ด ์ง์์ด๊ฐ ๋ถํ์ํ๋ฉฐ, ์คํ๋ ค ํผ๋์ ์ค ์ ์์ต๋๋ค.
๋ค์๊ณผ ๊ฐ์ด ์ ๊ฑฐํด ์ฃผ์ธ์:
-'use client';
-
import { useState, useEffect } from 'react';Based on learnings
๐ 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.
| 'use client'; | |
| import { useState, useEffect } from 'react'; |
๐ค Prompt for AI Agents
In src/pages/events/index.tsx lines 1-1, remove the top-level 'use client'
directive (delete that first line). If this file relies on client-only hooks or
browser APIs, move those parts into a separate client component under
pages/events or convert the specific components to client components (with 'use
client') inside a subfile; otherwise simply delete the directive so the page
uses the Pages Router default behavior.
There was a problem hiding this comment.
Actionable comments posted: 0
โป๏ธ Duplicate comments (1)
src/shared/components/container/EventCard.tsx (1)
105-124: ๋ถ๋งํฌ ๋ฒํผ a11y ๋ณด์ ๋ฐ ์ค๋ณต ์ฝ๋ ์ ๋ฆฌ ๊ถ์ฅ (์ด์ ๋ฆฌ๋ทฐ์ ๋์ผ ์ด์)Line [105]
[124], [151][170]์ ํํธ ๋ถ๋งํฌ ๋ฒํผ์ ์๊ฐ์ ์ผ๋ก๋ ๋ช ํํ์ง๋ง, ํ์ฌ<button>์aria-label์ด ์๊ณ , ๋ด๋ถIcon์ปดํฌ๋ํธ๋ ๊ธฐ๋ณธ์ ์ผ๋กariaHidden์ด๊ธฐ ๋๋ฌธ์ ์คํฌ๋ฆฐ ๋ฆฌ๋์์ โ์ด ๋ฒํผ์ด ๋ฌด์์ ํ๋์งโ ์ ์ ์์ต๋๋ค. ์ด์ ๋ฆฌ๋ทฐ์์๋ ์ง์ ๋์๋ ๋ถ๋ถ์ธ๋ฐ ์์ง ๋ฐ์๋์ง ์์ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
- ์ ๊ทผ์ฑ(a11y) ์ธก๋ฉด ํ์ ์์ ์ ์
๊ฐ ๋ฒํผ์ ์ํ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง๋
aria-label์ ๋ถ์ฌํ๋ฉด ์คํฌ๋ฆฐ ๋ฆฌ๋์์ ์๋ฏธ๋ฅผ ํ์ ํ ์ ์์ต๋๋ค. ๋์์type='button'์ ๋ช ์ํด ์ถํ form ๋ด์ ๋ค์ด๊ฐ ๊ฐ๋ฅ์ฑ๊น์ง ๋ฐฉ์ดํ๋ ํธ์ด ์์ ํฉ๋๋ค.์์ diff:
- <button - className='cursor-pointer p-1' - onClick={(e) => { - e.stopPropagation(); - toggleBookmark(); - }} - > + <button + type='button' + className='cursor-pointer p-1' + onClick={(e) => { + e.stopPropagation(); + toggleBookmark(); + }} + aria-label={isBookmarked ? '๋ถ๋งํฌ ํด์ ' : '๋ถ๋งํฌ ์ถ๊ฐ'} + > @@ - <Icon + <Icon name='HeartStraight' size={20} color={ isBookmarked ? 'red-400' : variant === 'mint' ? 'mint-400' : 'gray-300' } fillColor={isBookmarked ? 'red-300' : undefined} /> </button>Large ์นด๋ ์ชฝ ๋ฒํผ(Line [151]~[170])์๋ ๋์ผํ๊ฒ
type='button'๊ณผaria-label์ ์ถ๊ฐํด ์ฃผ์๋ฉด ์ข๊ฒ ์ต๋๋ค.
- ์ค๋ณต๋ ๋ฒํผ ๋งํฌ์ ์ ์๊ท๋ชจ ์๋ธ์ปดํฌ๋ํธ๋ก ์ถ์ถ ๊ถ์ฅ
medium/large ์นด๋์์ ๋ฒํผ ๋งํฌ์ ๊ณผ ์์ ๋ก์ง์ด ๊ฑฐ์ ๋์ผํ๊ฒ ๋ฐ๋ณต๋๊ณ ์์ด, ์ถํ ์คํ์ผ ๋ณ๊ฒฝ ์ ์ค๋ณต ์์ ์ด ํ์ํฉ๋๋ค. ํ์๋ ์๋์ง๋ง, ์๋ฅผ ๋ค์ด
BookmarkButton๊ฐ์ ์๊ท๋ชจ ์๋ธ์ปดํฌ๋ํธ๋ก ๋ฝ์์
isBookmarked,variant,onToggle์ ๋๋ฅผ props๋ก ๋ฐ๊ณ- ๋ด๋ถ์์
stopPropagation,aria-label์ฒ๋ฆฌ์ ์์/Icon๋ ๋๋ง์ ๊ณตํตํํ๋ ํํ๋ก ์ ๋ฆฌํ๋ฉด ์ ์ง๋ณด์๊ฐ ๋ ์์ํด์ง ๊ฒ ๊ฐ์ต๋๋ค.
Also applies to: 151-170
๐งน Nitpick comments (1)
src/shared/components/container/EventCard.tsx (1)
49-75: small ์นด๋์์ ๋ถ๋งํฌ ๋ฏธ๋ ธ์ถ์ด ๊ธฐํ๊ณผ ๋ง๋์ง๋ง ํ์ธ ๋ถํ๋๋ฆฝ๋๋คLine [49]~[75]์ small ์นด๋ ๋ ์ด์์์๋ ์ด๋ฏธ์ง์ ์ ๋ชฉ๋ง ์๊ณ , medium/large ์นด๋์ ์๋ ํํธ(๋ถ๋งํฌ) ๋ฒํผ์ ๋ฐฐ์น๋์ด ์์ง ์์ต๋๋ค. ๊ธฐํ์ small ์นด๋๋ ๋ถ๋งํฌ๋ฅผ ์ง์ํ์ง ์๋ ๋์์ธ์ด๋ผ๋ฉด ๊ทธ๋๋ก ๋์ด๋ ๋์ง๋ง, ์นด๋ ์ฌ์ด์ฆ์ ๊ด๊ณ์์ด ์ผ๊ด๋ ๋ถ๋งํฌ ๊ฒฝํ์ ์ํ๋ ์๊ตฌ์ฌํญ์ด๋ผ๋ฉด ์ด ๋ถ๋ถ๋ ์ถํ ํ์ฅ ๋์์ด ๋ ์ ์์ต๋๋ค.
ํ์ฌ PR ๋ชฉ์ (์ด๋ฒคํธ ํ์ด์ง API ์ฐ๋) ๊ด์ ์์๋ blocker๋ ์๋์ง๋ง, ๊ธฐํ/๋์์ธ๊ณผ ์๋๋ ๋์์ด ๋ง๋์ง๋ง ํ ๋ฒ ํ์ธํด ๋ณด์๋ฉด ์ข๊ฒ ์ต๋๋ค.
๐ Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
๐ Files selected for processing (1)
src/shared/components/container/EventCard.tsx(3 hunks)
๐งฐ Additional context used
๐งฌ Code graph analysis (1)
src/shared/components/container/EventCard.tsx (4)
src/shared/hooks/events/useBookmark.ts (1)
useBookmark(8-47)src/pages/mypage/components/LoginRequired.tsx (1)
LoginRequired(10-21)src/shared/components/container/Card.tsx (1)
Card(32-48)src/shared/icons/components/icon.tsx (1)
Icon(58-130)
โฐ 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: build-and-deploy
๐ Additional comments (3)
src/shared/components/container/EventCard.tsx (3)
9-35: EventCard props ํ์ฅ ๋ฐuseBookmark์ฐ๋ ๊ตฌ์กฐ๋ ๋ฌด๋ํฉ๋๋คLine [9]~[35]์์
eventId,liked,onClick๋ฅผ props๋ก ํ์ฅํ๊ณ ,useBookmark(eventId, liked)๋ก ๋ถ๋งํฌ ์ํ๋ฅผ ์์ํ ๊ตฌ์กฐ๊ฐ ์ ์ฒด ์ด๋ฒคํธ/์์ธ/๋ง์ดํ์ด์ง ์บ์ ์ ๋ต๊ณผ ์ ๋ง์ต๋๋ค.liked๊ธฐ๋ณธ๊ฐ์false๋ก ๋ ๊ฒ๋ API ์๋ต์ด ์ง์ฐ๋๊ฑฐ๋ ๋๋ฝ๋ ๋์ ์ด๊ธฐ ๋ ๋ ์์ ์ฑ ์ธก๋ฉด์์ ๊ด์ฐฎ์ ๋ณด์ ๋๋ค.๋ณ๋ ์ด์๋ ์์ด ๋ณด์ ๋๋ค.
38-48: ๋ก๊ทธ์ธ ์๊ตฌ ํ๋ก์ฐ์ Card ๋ํ ๋ฐฉ์ ์ ์ Line [38]~[48]์์
requireLogin์ดtrue์ผ ๋LoginRequired๋ฅผ ๋ ๋ํ๊ณ ,onClose={() => setRequireLogin(false)}๋ก ์ํ๋ฅผ ์ ๋ฆฌํ ๋คCard๋ฅผ ๊ฐ์ธ๋ ๊ตฌ์กฐ๋ ๊น๋ํฉ๋๋ค.
Card์onClick์ ๊ทธ๋๋ก ๋๊ธฐ๋, ๋ถ๋งํฌ ์์ด์ฝ ์ชฝ์์stopPropagation์ ์ฐ๊ณ ์์ด์ ์นด๋ ์ ์ฒด ํด๋ฆญ ์ก์ ๊ณผ ๋ถ๋งํฌ ํ ๊ธ์ด ์ ๋ถ๋ฆฌ๋ ๊ฒ์ผ๋ก ๋ณด์ ๋๋ค.
41-47:Card์size/customHeight์กฐํฉ์์ small ์ฒ๋ฆฌ๋ง ํ ๋ฒ ํ์ธ ํ์Line [41]~[47]์์
size === 'small' ? undefined : size๋กCard์size๋ฅผ ๋๊ธฐ์ง ์๊ณ , ๋์customHeight๋ก๋ง ๋์ด๋ฅผ ์ ์ดํ๊ณ ์์ต๋๋ค.Card๋ด๋ถ์์size๊ฐundefined์ผ ๋ ์ด๋ค ์คํ์ผ์ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ฌ์ฉํ๋์ง์ ๋ฐ๋ผ small ์นด๋๊ฐ medium ์คํ์ผ์ ์ผ๋ถ ๋ฌผ๋ ค๋ฐ์ ์๋ ์์ผ๋, ๋์์ธ ์๋์ ์ค์ ์นด๋ ์คํ์ผ์ด ์ผ์นํ๋์ง๋ง ํ ๋ฒ ๋์ผ๋ก ํ์ธํด ์ฃผ์ธ์.๊ธฐ๋ฅ์ ์ธ ๋ฌธ์ ๋ ์์ด ๋ณด์ด๊ณ , ํ์ํ๋ค๋ฉด
Card์ชฝcardStyle์ค์ ๋ง ์ฌ๊ฒํ ํ๋ฉด ๋ ๊ฒ ๊ฐ์ต๋๋ค.
๐ฅ ์์ ๋ด์ฉ
๐ค ์ถํ ์์ ์ฌํญ
๐ย ์ด์
PR Point (To Reviewer)
ํ์ฌ ํ์ด์ง api ์ฐ๊ฒฐ
ํ์ฌ ํ์ด์ง & ์ ์ฅํ ํ์ฌ ์ฐ๊ฒฐ ์๋ฃํ์ต๋๋ค.
์์ธ ํ์ฌ ํ์ด์ง์์๋ ๋์๊ฐ๋ฉด ์กฐํํ๋ ๋ ์ง๋ก ๋์๊ฐ๋๋ก ํด๋จ๊ณ
๊ทธ์ธ ๊ด๋ จ ๋ค์ ํ์ฌ ๋ฑ ๋ฐ์์์ต๋๋ค. ์์ ํ์ฌ ์นด๋ ๋๋ฅด๋ฉด ๊ทธ ํ์ฌ ์์ธ ํ์ด์ง๋ก ๋์ด๊ฐ๋๋ค
๋ค๋ง.. ์ง๊ธ ์์๋ณด์๋ฉด ์๋๋ค..
์์ธ ํ์ด์ง์์ ๋ถ๋งํฌ UI๋ฅผ ๋ฐ์ํ์ง ๋ชปํฉ๋๋ค.. (๋ง์ดํ์ด์ง๋ ๋๋๋ฐ)
๊ทธ๋์ ์ด ๋ถ๋ถ ์์ ์ด ์ข ํ์ํด๋ณด์ด๊ณ ๋๋จธ์ง๋..์ ์ ์๋ํฉ๋๋ค.
๐ธย ํผ๊ทธ๋ง ์คํฌ๋ฆฐ์ท or ๊ธฐ๋ฅ GIF
-.Clipchamp.6.mp4
Summary by CodeRabbit
์๋ก์ด ๊ธฐ๋ฅ
์คํ์ผ
๋ฆฌํฉํ ๋ง
๊ธฐํ