Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough이 PR은 모임 생성, 참여, 결과 조회 페이지 및 관련 컴포넌트들의 UI/UX를 개선합니다. 폰트 크기, 레이아웃 조정, 결과 조회 버튼 상태 관리, 로딩 상태 처리, 에러 핸들링을 개선하고 카테고리 데이터를 정규화합니다. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. 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 |
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 (2)
app/result/[id]/page.tsx (1)
105-164:⚠️ Potential issue | 🟡 Minor
getLineColor함수 내 기본 반환값이 일관되지 않습니다Line 128의 숫자 호선
default는'bg-gray-400'을 반환하고, Line 162의 이름 기반default는'bg-gray-4'를 반환합니다. Tailwind v4에서gray-400과gray-4는 다른 색상입니다. 일관되게 하나로 통일하세요.♻️ 수정 제안
default: - return 'bg-gray-400'; + return 'bg-gray-4'; } }components/join/joinForm.tsx (1)
78-79:⚠️ Potential issue | 🟡 Minor
@ts-ignore주석을 제거하세요
@ts-ignore는 불필요합니다.useEnterParticipant훅의 반환 타입과mutateAsync호출 파라미터가 올바르게 일치합니다.mutateAsync는{ meetingId: string; data: ParticipantEnterRequest }타입을 기대하며, 코드에서 전달하는 인자가 정확히 이를 만족하므로 해당 주석을 제거해도 타입 오류가 발생하지 않습니다.
🤖 Fix all issues with AI agents
In `@app/meeting/`[id]/page.tsx:
- Around line 302-307: The wrapper div using class "group relative right-5
left-5 mb-10 md:absolute md:right-0 md:bottom-0 md:left-0 md:mb-0" applies both
left and right offsets in a relative context (causing an unintended 1.25rem
shift on mobile); remove the positional offsets and replace them with a negative
horizontal margin to counter parent padding (use "-mx-5" instead of "right-5
left-5") while keeping "relative" and the md:absolute/md:* classes intact so
desktop absolute positioning still works.
In `@components/join/joinForm.tsx`:
- Around line 28-40: The current useState initializers for meetingTypeVal and
categoryVal read searchParams/localStorage and return '' on server which can
cause structural hydration mismatches when the client sets values; change both
to client-only initialization: keep initial state as '' (e.g., const
[meetingTypeVal, setMeetingTypeVal] and const [categoryVal, setCategoryVal]) and
use a useEffect that runs on mount to read searchParams.get('meetingType') /
searchParams.get('category') and
localStorage.getItem(`meeting_${meetingId}_meetingType`/`category`) and call the
corresponding setters; optionally add an isClient/mounted flag state to guard
the conditional rendering that depends on (meetingTypeVal || categoryVal) so
rendering only changes after client hydration.
- Line 91: The code is reading result.data?.message but ParticipantEnterData is
defined as an empty Record so that property will always be undefined; update the
typing and usage: change ParticipantEnterData to include an optional message?:
string (or a proper response interface that contains message) so
result.data?.message is valid, and in the participant join flow ensure you
setErrorMessage from the catch path using error.data?.message (or fallback)
instead of relying on result when an exception occurs; adjust references in
joinForm.tsx around setErrorMessage, the try/catch handling, and the
ParticipantEnterData type so the message field is correctly typed and used.
In `@components/map/kakaoMapRecommend.tsx`:
- Around line 55-57: The comment above the meetingType === '회의' branch is
inconsistent with the filter values: update the comment to match the code's
category id format ('스터디 카페' with a space) in the block that returns
ALL_CATEGORIES.filter((cat) => cat.id === '스터디 카페' || cat.id === '장소 대여'), so
the comment text reads "회의: 스터디 카페, 장소 대여".
🧹 Nitpick comments (5)
components/meeting/stationSearch.tsx (1)
16-23:stationsprop이 필수로 선언되어 있어 기본값이 적용되지 않습니다.
StationSearchProps에서stations: Station[]은 optional이 아니므로 TypeScript가 호출 시 항상 전달을 요구합니다. 의도대로 생략 가능하게 하려면 optional로 변경하세요.📝 수정 제안
interface StationSearchProps { - stations: Station[]; + stations?: Station[]; selectedStation: string | null; onSelect: (stationName: string | null) => void; }components/modal/transferModal.tsx (1)
46-117:getLineBadgeStyle이app/result/[id]/page.tsx의getLineColor와 거의 동일하게 중복됩니다두 함수 모두 호선명을 받아 배경색 클래스를 반환하는 동일한 switch 로직입니다. 공통 유틸리티로 추출하면 유지보수성이 크게 향상됩니다.
또한, 일부 주석의 색상 설명이 실제 색상과 일치하지 않습니다 (예: Line 59
'3'→ "파랑"이라고 되어 있지만#ED6C00은 주황색, Line 65'6'→ "빨강"이라고 되어 있지만#7C4932는 갈색).app/result/[id]/page.tsx (2)
194-214: 모바일/데스크탑 맵 렌더링 IIFE가 동일한 로직으로 중복됩니다
selectedResult계산 및KakaoMapLine렌더링 로직이 모바일(Line 196-214)과 데스크탑(Line 317-333)에서 거의 동일합니다.selectedResult를 변수로 추출하고, 맵 props를 공유하면 중복을 줄일 수 있습니다.♻️ 수정 제안 (예시)
locationResultsuseMemo 아래에 selectedResult를 미리 계산:const selectedResult = useMemo(() => { if (locationResults.length === 0) return null; return locationResults.find((r) => r.id === selectedResultId) || locationResults[0]; }, [locationResults, selectedResultId]);그 후 모바일/데스크탑 영역에서 직접 사용:
- {locationResults.length > 0 && - (() => { - const selectedResult = - locationResults.find((r) => r.id === selectedResultId) || locationResults[0]; - return ( - <div className="relative h-93.5 w-full max-w-[100vw] overflow-hidden md:hidden"> - <KakaoMapLine ... /> - </div> - ); - })()} + {selectedResult && ( + <div className="relative h-93.5 w-full max-w-[100vw] overflow-hidden md:hidden"> + <KakaoMapLine + className="bg-gray-1 relative block h-93.5 md:hidden" + endStation={{ name: selectedResult.endStation, latitude: selectedResult.latitude, longitude: selectedResult.longitude }} + userRoutes={selectedResult.userRoutes} + meetingId={id} + purposes={meetingData?.data?.purposes} + /> + </div> + )}Also applies to: 316-334
219-220: 스크롤 영역에 고정 높이h-125가 적용되어 다양한 화면 크기에서 문제가 될 수 있습니다
h-125(31.25rem)는 리스트 항목 수와 관계없이 고정 높이를 갖습니다. 작은 화면에서는 버튼이 뷰포트 밖으로 밀릴 수 있고, 큰 화면에서는 불필요한 빈 공간이 생길 수 있습니다.max-h와flex를 조합하면 더 유연하게 대응할 수 있습니다.app/meeting/[id]/page.tsx (1)
303-308:hidden+group-hover:block과opacity전환의 조합이 부드러운 애니메이션을 생성하지 않습니다
hidden(display: none)에서group-hover:block(display: block)으로의 전환은 CSS에서 애니메이션이 불가능합니다.transition-opacity가 적용되어 있지만,display변경과 동시에 발생하므로 페이드인 효과가 나타나지 않습니다.부드러운 페이드인이 필요하면
hidden대신invisible(또는pointer-events-none)을 사용하세요:♻️ 수정 제안
- <div className="bg-gray-9 absolute bottom-full left-1/2 mb-2 hidden w-max -translate-x-1/2 rounded px-3 py-1.5 text-xs text-white opacity-0 transition-opacity group-hover:block group-hover:opacity-100"> + <div className="bg-gray-9 pointer-events-none absolute bottom-full left-1/2 mb-2 w-max -translate-x-1/2 rounded px-3 py-1.5 text-xs text-white opacity-0 transition-opacity group-hover:pointer-events-auto group-hover:opacity-100">
| <div className="group relative right-5 left-5 mb-10 md:absolute md:right-0 md:bottom-0 md:left-0 md:mb-0"> | ||
| {!isResultEnabled && ( | ||
| <div className="bg-gray-9 absolute bottom-full left-1/2 mb-2 hidden w-max -translate-x-1/2 rounded px-3 py-1.5 text-xs text-white opacity-0 transition-opacity group-hover:block group-hover:opacity-100"> | ||
| 모든 팀원이 참여해야 결과를 확인할 수 있어요 | ||
| <div className="border-t-gray-9 absolute top-full left-1/2 -translate-x-1/2 border-4 border-transparent"></div> | ||
| </div> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n app/meeting/[id]/page.tsx | sed -n '295,320p'Repository: SWYP-mingling/Frontend
Length of output: 1539
🏁 Script executed:
# Check recent git history for this file
git log --oneline -n 10 -- "app/meeting/[id]/page.tsx"Repository: SWYP-mingling/Frontend
Length of output: 92
🏁 Script executed:
# Check the diff from previous commit to see what changed
git diff HEAD~1..HEAD -- "app/meeting/[id]/page.tsx" 2>/dev/null | head -150Repository: SWYP-mingling/Frontend
Length of output: 48
🏁 Script executed:
# Get more context around the parent container structure
cat -n app/meeting/[id]/page.tsx | sed -n '240,320p'Repository: SWYP-mingling/Frontend
Length of output: 4568
🏁 Script executed:
# Check if there's any CSS or styling context that might explain the positioning
git log --all -p -- "app/meeting/[id]/page.tsx" | grep -A 5 -B 5 "relative right-5 left-5" | head -40Repository: SWYP-mingling/Frontend
Length of output: 733
🏁 Script executed:
# Let's also check for any similar patterns in other files to understand the intent
rg "relative right-\d left-\d" --type tsxRepository: SWYP-mingling/Frontend
Length of output: 93
🏁 Script executed:
# Check what previous versions of this button container looked like
git log --all -p --reverse -- "app/meeting/[id]/page.tsx" | grep -A 15 -B 5 "결과보기" | head -100Repository: SWYP-mingling/Frontend
Length of output: 1411
🏁 Script executed:
# Check if there are any tailwind config files that might affect spacing
fd -e "tailwind.config" -e "tailwind.config.js" -e "tailwind.config.ts"Repository: SWYP-mingling/Frontend
Length of output: 48
🏁 Script executed:
# Let me check the entire git history to see when this wrapper was added
git log --all --oneline -- "app/meeting/[id]/page.tsx"Repository: SWYP-mingling/Frontend
Length of output: 162
🏁 Script executed:
# Check the specific commit that added the wrapper div
git show 98bb381:app/meeting/[id]/page.tsx | sed -n '295,320p'Repository: SWYP-mingling/Frontend
Length of output: 1357
🏁 Script executed:
# Let's check the commit that introduced the wrapper to understand the intent
git show 98bb381 --statRepository: SWYP-mingling/Frontend
Length of output: 7503
🏁 Script executed:
# Check if there are other similar positioning patterns in the codebase
rg "relative.*left-\d.*right-\d|relative.*right-\d.*left-\d" --type tsx -A 2 -B 2Repository: SWYP-mingling/Frontend
Length of output: 93
🏁 Script executed:
# Let me check how positioning utilities behave in Tailwind by looking at the actual CSS output or config
cat tailwind.config.ts 2>/dev/null || cat tailwind.config.js 2>/dev/nullRepository: SWYP-mingling/Frontend
Length of output: 48
🏁 Script executed:
# Let's also look at the parent container and understand the flex layout better
cat -n app/meeting/[id]/page.tsx | sed -n '200,250p'Repository: SWYP-mingling/Frontend
Length of output: 2528
🏁 Script executed:
# Let me trace through how flex layout works with the positioning
# Check if there's any visible layout issue by seeing how the button is supposed to align
cat -n app/meeting/[id]/page.tsx | sed -n '309,318p'Repository: SWYP-mingling/Frontend
Length of output: 538
🏁 Script executed:
# Check if there are any other buttons or elements that use similar negative margin patterns
rg "\-mx-\d|\-ml-\d|\-mr-\d" app/meeting/Repository: SWYP-mingling/Frontend
Length of output: 48
🏁 Script executed:
# Let me check the actual CSS behavior by examining how the button would be rendered
# Check if there's a previous version without the wrapper to see the original intent
git log --all --oneline -20 -- "app/meeting/[id]/page.tsx" | head -5Repository: SWYP-mingling/Frontend
Length of output: 162
🏁 Script executed:
# Get the diff between the previous button implementation and current
git show f0cc36b:app/meeting/[id]/page.tsx 2>/dev/null | grep -A 10 "결과보기" | head -20Repository: SWYP-mingling/Frontend
Length of output: 403
🏁 Script executed:
# Let me understand the flex layout better and check if there are any design specifications
# Check the parent's flex properties to see how children are laid out
cat -n app/meeting/[id]/page.tsx | sed -n '206,210p'Repository: SWYP-mingling/Frontend
Length of output: 514
🏁 Script executed:
# Look at the button's w-full property and understand how it interacts with the wrapper
cat -n app/meeting/[id]/page.tsx | sed -n '302,318p'Repository: SWYP-mingling/Frontend
Length of output: 1176
🏁 Script executed:
# Let me trace the actual CSS behavior: on a relative element with both left and right set
# Check if there's any CSS custom property or Tailwind config that might explain this
fd "globals.css|tailwind.css" app/Repository: SWYP-mingling/Frontend
Length of output: 81
🏁 Script executed:
# Check the globals.css to understand any custom styling
cat app/globals.cssRepository: SWYP-mingling/Frontend
Length of output: 2130
🏁 Script executed:
# Let me also verify the previous version more thoroughly to compare
git diff f0cc36b..98bb381 -- "app/meeting/[id]/page.tsx" | grep -A 20 -B 5 "relative right-5 left-5"Repository: SWYP-mingling/Frontend
Length of output: 1434
모바일에서 relative right-5 left-5의 위치 지정이 의도와 다릅니다
Line 302의 wrapper에서 relative right-5 left-5를 사용합니다. position: relative 요소에 left와 right가 동시에 설정되면, LTR 환경에서 left만 적용되어 요소가 1.25rem 오른쪽으로 밀립니다.
부모의 px-5 패딩을 상쇄하려는 의도라면 음수 마진(-mx-5)을 사용하세요:
- <div className="group relative right-5 left-5 mb-10 md:absolute md:right-0 md:bottom-0 md:left-0 md:mb-0">
+ <div className="group relative -mx-5 mb-10 md:absolute md:right-0 md:bottom-0 md:left-0 md:mb-0">🤖 Prompt for AI Agents
In `@app/meeting/`[id]/page.tsx around lines 302 - 307, The wrapper div using
class "group relative right-5 left-5 mb-10 md:absolute md:right-0 md:bottom-0
md:left-0 md:mb-0" applies both left and right offsets in a relative context
(causing an unintended 1.25rem shift on mobile); remove the positional offsets
and replace them with a negative horizontal margin to counter parent padding
(use "-mx-5" instead of "right-5 left-5") while keeping "relative" and the
md:absolute/md:* classes intact so desktop absolute positioning still works.
| const [meetingTypeVal] = useState(() => { | ||
| if (typeof window === 'undefined') return ''; | ||
| const param = searchParams.get('meetingType'); | ||
| if (param) return param; | ||
| return localStorage.getItem(`meeting_${meetingId}_meetingType`) || ''; | ||
| }); | ||
|
|
||
| const [categoryVal] = useState(() => { | ||
| if (typeof window === 'undefined') return ''; | ||
| const param = searchParams.get('category'); | ||
| if (param) return param; | ||
| return localStorage.getItem(`meeting_${meetingId}_category`) || ''; | ||
| }); |
There was a problem hiding this comment.
meetingTypeVal/categoryVal의 하이드레이션 불일치 가능성
useState 초기화 함수에서 typeof window === 'undefined' 체크를 통해 서버에서는 ''을 반환하고, 클라이언트에서는 searchParams/localStorage에서 값을 읽습니다. 이 경우 서버 렌더링 결과와 클라이언트 하이드레이션 결과가 구조적으로 달라질 수 있습니다 (Line 125의 조건부 렌더링 {(meetingTypeVal || categoryVal) && (...)}).
suppressHydrationWarning는 해당 요소의 텍스트 콘텐츠 불일치만 억제하며, 자식 노드의 존재 여부가 달라지는 구조적 불일치는 해결하지 못합니다.
권장 접근법: useEffect를 사용하여 클라이언트에서만 값을 설정하면 하이드레이션 불일치를 완전히 방지할 수 있습니다.
♻️ 수정 제안
- const [meetingTypeVal] = useState(() => {
- if (typeof window === 'undefined') return '';
- const param = searchParams.get('meetingType');
- if (param) return param;
- return localStorage.getItem(`meeting_${meetingId}_meetingType`) || '';
- });
-
- const [categoryVal] = useState(() => {
- if (typeof window === 'undefined') return '';
- const param = searchParams.get('category');
- if (param) return param;
- return localStorage.getItem(`meeting_${meetingId}_category`) || '';
- });
+ const [meetingTypeVal, setMeetingTypeVal] = useState('');
+ const [categoryVal, setCategoryVal] = useState('');
+
+ useEffect(() => {
+ const mtParam = searchParams.get('meetingType');
+ setMeetingTypeVal(mtParam || localStorage.getItem(`meeting_${meetingId}_meetingType`) || '');
+ const catParam = searchParams.get('category');
+ setCategoryVal(catParam || localStorage.getItem(`meeting_${meetingId}_category`) || '');
+ }, [searchParams, meetingId]);🤖 Prompt for AI Agents
In `@components/join/joinForm.tsx` around lines 28 - 40, The current useState
initializers for meetingTypeVal and categoryVal read searchParams/localStorage
and return '' on server which can cause structural hydration mismatches when the
client sets values; change both to client-only initialization: keep initial
state as '' (e.g., const [meetingTypeVal, setMeetingTypeVal] and const
[categoryVal, setCategoryVal]) and use a useEffect that runs on mount to read
searchParams.get('meetingType') / searchParams.get('category') and
localStorage.getItem(`meeting_${meetingId}_meetingType`/`category`) and call the
corresponding setters; optionally add an isClient/mounted flag state to guard
the conditional rendering that depends on (meetingTypeVal || categoryVal) so
rendering only changes after client hydration.
| router.push(`/meeting/${meetingId}`); | ||
| } else { | ||
| setErrorMessage(result.data.message || '모임 참여에 실패했습니다. 다시 시도해주세요.'); | ||
| setErrorMessage(result.data?.message || '모임 참여에 실패했습니다. 다시 시도해주세요.'); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Search for ParticipantEnterResponse type definition
rg -n "ParticipantEnterResponse" --type ts --type tsx -C 5Repository: SWYP-mingling/Frontend
Length of output: 93
🏁 Script executed:
#!/bin/bash
# Search for ParticipantEnterResponse type definition
rg -n "ParticipantEnterResponse" --type ts -C 5Repository: SWYP-mingling/Frontend
Length of output: 1711
🏁 Script executed:
#!/bin/bash
# Search for ApiResponse type definition
rg -n "type ApiResponse|interface ApiResponse" --type ts -C 8Repository: SWYP-mingling/Frontend
Length of output: 408
🏁 Script executed:
#!/bin/bash
# Look at joinForm.tsx around line 91
cat -n components/join/joinForm.tsx | sed -n '80,100p'Repository: SWYP-mingling/Frontend
Length of output: 779
result.data?.message 접근이 불가능함 - ParticipantEnterData 타입이 빈 객체로 정의됨
ParticipantEnterData가 Record<string, never> (속성 없는 빈 객체)로 정의되어 있어 result.data?.message는 항상 undefined로 평가됩니다. 따라서 폴백 메시지만 표시됩니다. 에러 메시지는 try-catch 블록의 error.data?.message에서 처리하는 것이 맞습니다.
🤖 Prompt for AI Agents
In `@components/join/joinForm.tsx` at line 91, The code is reading
result.data?.message but ParticipantEnterData is defined as an empty Record so
that property will always be undefined; update the typing and usage: change
ParticipantEnterData to include an optional message?: string (or a proper
response interface that contains message) so result.data?.message is valid, and
in the participant join flow ensure you setErrorMessage from the catch path
using error.data?.message (or fallback) instead of relying on result when an
exception occurs; adjust references in joinForm.tsx around setErrorMessage, the
try/catch handling, and the ParticipantEnterData type so the message field is
correctly typed and used.
| if (meetingType === '회의') { | ||
| // 회의: 스터디카페, 장소 대여 | ||
| return ALL_CATEGORIES.filter((cat) => cat.id === '스터디카페' || cat.id === '장소 대여'); | ||
| return ALL_CATEGORIES.filter((cat) => cat.id === '스터디 카페' || cat.id === '장소 대여'); |
There was a problem hiding this comment.
주석과 코드가 불일치합니다.
Line 56 주석에 "스터디카페"(공백 없음)로 되어 있지만, Line 57 코드에서는 '스터디 카페'(공백 있음)를 사용합니다. 주석을 코드와 일치시켜 주세요.
📝 수정 제안
if (meetingType === '회의') {
- // 회의: 스터디카페, 장소 대여
+ // 회의: 스터디 카페, 장소 대여
return ALL_CATEGORIES.filter((cat) => cat.id === '스터디 카페' || cat.id === '장소 대여');📝 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.
| if (meetingType === '회의') { | |
| // 회의: 스터디카페, 장소 대여 | |
| return ALL_CATEGORIES.filter((cat) => cat.id === '스터디카페' || cat.id === '장소 대여'); | |
| return ALL_CATEGORIES.filter((cat) => cat.id === '스터디 카페' || cat.id === '장소 대여'); | |
| if (meetingType === '회의') { | |
| // 회의: 스터디 카페, 장소 대여 | |
| return ALL_CATEGORIES.filter((cat) => cat.id === '스터디 카페' || cat.id === '장소 대여'); |
🤖 Prompt for AI Agents
In `@components/map/kakaoMapRecommend.tsx` around lines 55 - 57, The comment above
the meetingType === '회의' branch is inconsistent with the filter values: update
the comment to match the code's category id format ('스터디 카페' with a space) in
the block that returns ALL_CATEGORIES.filter((cat) => cat.id === '스터디 카페' ||
cat.id === '장소 대여'), so the comment text reads "회의: 스터디 카페, 장소 대여".
🚀 refactor: 전체 폰트 사이즈 수정 및 소소한 편의성 개선
📝 변경사항
✅ 체크리스트
📸 스크린샷
💬 리뷰어 전달사항
Summary by CodeRabbit
릴리스 노트
New Features
Bug Fixes
Style