-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 모달 데이터 전달 시스템 리팩토링 및 공유 기능 API 연동 #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- useMeeting을 useCreateMeeting으로 이름 변경 및 폴더 구조 정리 - create/page.tsx의 tailwind 스타일명 수정
1. Global Modal 시스템 개선 - `useModalStore`: 모달 오픈 시 `data`(예: meetingId)를 전달받도록 상태 구조 확장 - `useOpenModal`: (type, data, event) 시그니처로 변경하여 데이터 전달 파이프라인 구축 - `GlobalModal`: Store의 데이터를 하위 모달(ShareModal 등)에 Props로 주입 2. 공유 기능 훅(useShareMeeting) 리팩토링 및 최적화 - `ShareModal`의 비즈니스 로직을 커스텀 훅으로 분리 - `useSyncExternalStore`를 도입하여 `window.location.origin` 접근 시 발생하는 Hydration Mismatch 및 ESLint(`setState-in-effect`) 에러 해결 - 불필요한 `useState`, `useEffect` 제거로 렌더링 성능 최적화
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough모달 시스템에 meetingId를 전달하기 위해 데이터 페이로드 메커니즘을 추가하고, 공유 URL 생성 및 클립보드 관리 로직을 새로운 Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant Page as Page Component
participant Modal as Global Modal
participant Store as Modal Store
participant Hook as useShareMeeting Hook
participant API as API Server
User->>Page: Click Share/Nudge
Page->>Store: openModal(type, {meetingId: id})
Store->>Store: Store meetingId in data
Store->>Modal: Update modal state
Modal->>Modal: Render ShareModal/NudgeModal
Modal->>Hook: Pass meetingId prop
Hook->>API: Fetch meeting (if exists)
API-->>Hook: Validate meeting
Hook->>Hook: Build shareUrl (origin/join/{id})
Hook-->>Modal: Return {shareUrl, isLoading, isError}
Modal-->>User: Display share link in input
User->>Modal: Click Copy
Hook->>Hook: Copy to clipboard
Hook-->>User: Show toast (success/failure)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
components/modal/globalModal.tsx (1)
10-22:⚠️ Potential issue | 🟡 MinormeetingId 누락 시 빈 문자열로 렌더링되는 점을 가드하세요.
SHARE/NUDGE는 meetingId가 필수이므로
''fallback은 잘못된 요청/빈 링크로 이어질 수 있습니다. 누락 시 모달을 닫거나 에러 UI로 처리하는 편이 안전합니다.🛡️ 예시 가드
case 'SHARE': - return <ShareModal isOpen={isOpen} onClose={onClose} meetingId={data?.meetingId || ''} />; + if (!data?.meetingId) return null; + return <ShareModal isOpen={isOpen} onClose={onClose} meetingId={data.meetingId} />; case 'NUDGE': - return <NudgeModal isOpen={isOpen} onClose={onClose} meetingId={data?.meetingId || ''} />; + if (!data?.meetingId) return null; + return <NudgeModal isOpen={isOpen} onClose={onClose} meetingId={data.meetingId} />;
🤖 Fix all issues with AI agents
In `@app/result/page.tsx`:
- Around line 6-15: The Page currently reads an id via useParams() which returns
undefined because /result is a static route; update Page to obtain meetingId
from the query using useSearchParams() (e.g., const search = useSearchParams();
const meetingId = search.get('id')) or change the route to a dynamic segment
(/result/[id]/page.tsx) so useParams() works; also update the SHARE modal
invocation (openModal / any share handler) to pass meetingId only when present
and disable or prevent clicks on the SHARE button when meetingId is
null/undefined/empty to avoid calling the link-generation API with an invalid
id.
🧹 Nitpick comments (6)
hooks/api/query/useShareMeeting.ts (1)
29-35: 쿼리 에러 시 shareUrl이 여전히 유효한 URL을 반환함
useQuery가 에러를 반환하더라도shareUrl은 여전히 유효한 값을 가집니다. 존재하지 않는 모임에 대한 공유 링크가 복사될 수 있습니다.handleCopyLink에서isError상태도 체크하는 것이 좋습니다.🛡️ 에러 상태 체크 추가 제안
const handleCopyLink = async () => { - if (!shareUrl) return; + if (!shareUrl || isError) return; try { await navigator.clipboard.writeText(shareUrl); show(); } catch (err) { console.error(err); alert('복사 실패'); } };components/modal/shareModal.tsx (1)
62-69: 비활성화된 버튼에 시각적 피드백 추가 권장
disabled상태일 때 버튼의 시각적 변화가 없어 사용자가 비활성화 상태를 인지하기 어렵습니다.disabled:opacity-50또는disabled:cursor-not-allowed클래스 추가를 권장합니다.💅 비활성화 스타일 추가 제안
<button type="button" onClick={handleCopyLink} disabled={isLoading || isError || !shareUrl} - className="bg-gray-1 text-gray-6 border-gray-1 hover:bg-gray-2 cursor-pointer rounded-r-sm border px-3.5 py-3 text-sm font-semibold whitespace-nowrap transition-colors" + className="bg-gray-1 text-gray-6 border-gray-1 hover:bg-gray-2 cursor-pointer rounded-r-sm border px-3.5 py-3 text-sm font-semibold whitespace-nowrap transition-colors disabled:opacity-50 disabled:cursor-not-allowed" >app/share/[id]/page.tsx (2)
14-15: 로딩 상태에서 빈 화면 대신 스켈레톤이나 스피너 표시 권장현재
isLoading상태에서null을 반환하면 사용자에게 빈 화면이 보여 UX가 좋지 않을 수 있습니다. 로딩 인디케이터나 스켈레톤 UI 표시를 고려해 보세요.💡 로딩 상태 UI 개선 예시
if (isError) notFound(); - if (isLoading) return null; + if (isLoading) { + return ( + <div className="flex flex-col items-center justify-center bg-white px-5 py-10 md:py-25"> + <p className="text-gray-6">로딩 중...</p> + </div> + ); + }
34-46: hydration 전 shareUrl이 빈 문자열일 수 있음
useSyncExternalStore의 서버 스냅샷이 빈 문자열을 반환하므로, 초기 렌더링 시 input에 빈 값이 표시될 수 있습니다. ShareModal처럼displayValue패턴을 적용하거나, 빈 값일 때 플레이스홀더를 표시하는 것을 고려해 보세요.components/modal/nudgeModal.tsx (2)
29-75: ShareModal과 공유 링크 UI 중복ShareModal과 NudgeModal의 주소창 복사 영역(input + button + Toast)이 거의 동일합니다. 현재는 두 개의 모달뿐이므로 허용 가능하지만, 향후 유사한 UI가 추가된다면 공통 컴포넌트로 추출하는 것을 고려해 보세요.
63-70: 비활성화된 버튼에 시각적 피드백 추가 권장ShareModal과 마찬가지로,
disabled상태일 때 시각적 변화가 없습니다.disabled:opacity-50 disabled:cursor-not-allowed클래스 추가를 권장합니다.💅 비활성화 스타일 추가 제안
<button type="button" onClick={handleCopyLink} disabled={isLoading || isError || !shareUrl} - className="bg-gray-1 text-gray-6 border-gray-1 hover:bg-gray-2 cursor-pointer rounded-r-sm border px-3.5 py-3 text-sm font-semibold whitespace-nowrap transition-colors" + className="bg-gray-1 text-gray-6 border-gray-1 hover:bg-gray-2 cursor-pointer rounded-r-sm border px-3.5 py-3 text-sm font-semibold whitespace-nowrap transition-colors disabled:opacity-50 disabled:cursor-not-allowed" >
| import { useParams, useRouter } from 'next/navigation'; | ||
| import KakaoMapLine from '@/components/map/kakaoMapLine'; | ||
| import { MOCK_LOCATION_RESULTS } from '@/mock/mockData'; | ||
|
|
||
| export default function Page() { | ||
| const openModal = useOpenModal(); | ||
| const router = useRouter(); | ||
| const params = useParams(); | ||
| const id = params?.id as string; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find app/result -type f -name "*.tsx" -o -name "*.ts" | head -20Repository: SWYP-mingling/Frontend
Length of output: 85
🏁 Script executed:
cd app && find . -path "*result*" -type f | head -20Repository: SWYP-mingling/Frontend
Length of output: 83
🏁 Script executed:
cat -n app/result/page.tsx 2>/dev/null | head -60Repository: SWYP-mingling/Frontend
Length of output: 2601
🏁 Script executed:
ls -la app/result/Repository: SWYP-mingling/Frontend
Length of output: 234
🏁 Script executed:
find app/result -type d | sortRepository: SWYP-mingling/Frontend
Length of output: 76
🏁 Script executed:
rg "result/\[" --type tsx --type ts | head -20Repository: SWYP-mingling/Frontend
Length of output: 93
🏁 Script executed:
rg "result" app --type js --type jsx -A 2 -B 2 | grep -E "(push|href|navigate|router\.|/result)" | head -30Repository: SWYP-mingling/Frontend
Length of output: 93
🏁 Script executed:
rg "SHARE" app --type js --type jsx -A 3 | head -40Repository: SWYP-mingling/Frontend
Length of output: 93
🏁 Script executed:
rg "useParams" app --type js --type jsx -B 2 -A 5 | head -60Repository: SWYP-mingling/Frontend
Length of output: 93
🏁 Script executed:
rg "result" app -A 2 -B 2 | grep -E "(push|href|navigate|router\.|/result|meetingId)" | head -50Repository: SWYP-mingling/Frontend
Length of output: 2494
🏁 Script executed:
rg "openModal" app -A 3 | head -80Repository: SWYP-mingling/Frontend
Length of output: 1711
🏁 Script executed:
rg "/result" . -A 2 -B 2 | head -60Repository: SWYP-mingling/Frontend
Length of output: 623
🏁 Script executed:
rg "SHARE" hooks -A 10 | head -60Repository: SWYP-mingling/Frontend
Length of output: 48
🏁 Script executed:
find . -name "*.tsx" -o -name "*.ts" | xargs grep -l "SHARE" | grep -i modalRepository: SWYP-mingling/Frontend
Length of output: 125
🏁 Script executed:
rg "useOpenModal" hooks -A 20 | head -80Repository: SWYP-mingling/Frontend
Length of output: 628
🏁 Script executed:
cat -n components/modal/globalModal.tsx | head -100Repository: SWYP-mingling/Frontend
Length of output: 1198
🏁 Script executed:
cat -n store/useModalStore.tsRepository: SWYP-mingling/Frontend
Length of output: 976
🏁 Script executed:
rg "meetingId" components/modal -A 5 -B 5Repository: SWYP-mingling/Frontend
Length of output: 2823
🌐 Web query:
Next.js App Router useParams() static route behavior empty segment
💡 Result:
In the App Router, useParams() only includes active dynamic segments. So:
- If the current route has no dynamic segments,
useParams()returns{}. (nextjs.org) - For an optional catch-all like
app/shop/[[...slug]]/page.tsx:- Visiting
/shopmeans the optional segment is not present, so in practice you should expect noslugkey (i.e.useParams()is{}), and handle it as optional. (nextjs.org) - Visiting
/shop/a/bmeansslugis active and you get{ slug: ['a','b'] }. (nextjs.org)
- Visiting
Typical handling pattern:
'use client'
import { useParams } from 'next/navigation'
export default function Page() {
const params = useParams<{ slug?: string[] }>()
const slug = params.slug ?? [] // treat “missing” as empty
// ...
}If you’re seeing null on the first render, that’s a Pages Router static optimization behavior (not App Router): useParams() can be null during pre-rendering and then fill after hydration. (nextjs.org)
Citations:
- 1: https://nextjs.org/docs/app/api-reference/functions/use-params?utm_source=openai
- 2: https://nextjs.org/docs/app/api-reference/functions/use-params?utm_source=openai
- 3: https://nextjs.org/docs/14/app/building-your-application/routing/dynamic-routes?utm_source=openai
- 4: https://nextjs.org/docs/pages/api-reference/functions/use-params?utm_source=openai
🏁 Script executed:
rg "useSearchParams" app -B 2 -A 5 | head -40Repository: SWYP-mingling/Frontend
Length of output: 48
meetingId가 undefined로 전달되어 SHARE 모달 기능이 작동하지 않습니다.
/result는 동적 세그먼트가 없는 정적 라우트이므로 useParams().id는 항상 undefined를 반환합니다. 이는 SHARE 모달에 빈 문자열 또는 undefined로 전달되어 링크 생성 API가 실패합니다. /result/[id]로 라우팅을 변경하거나 useSearchParams()로 쿼리 파라미터를 사용해 주세요. 또한 meetingId가 없을 때 SHARE 버튼을 비활성화하거나 클릭을 방지해야 합니다.
🔧 예시 수정안 (쿼리 기반)
-import { useParams, useRouter } from 'next/navigation';
+import { useSearchParams, useRouter } from 'next/navigation';
-const params = useParams();
-const id = params?.id as string;
+const searchParams = useSearchParams();
+const id = searchParams.get('meetingId');
- onClick={(e) => openModal('SHARE', { meetingId: id }, e)}
+ onClick={(e) => {
+ if (!id) return;
+ openModal('SHARE', { meetingId: id }, e);
+ }}또는 라우트를 동적 세그먼트로 변경: /app/result/[id]/page.tsx
🤖 Prompt for AI Agents
In `@app/result/page.tsx` around lines 6 - 15, The Page currently reads an id via
useParams() which returns undefined because /result is a static route; update
Page to obtain meetingId from the query using useSearchParams() (e.g., const
search = useSearchParams(); const meetingId = search.get('id')) or change the
route to a dynamic segment (/result/[id]/page.tsx) so useParams() works; also
update the SHARE modal invocation (openModal / any share handler) to pass
meetingId only when present and disable or prevent clicks on the SHARE button
when meetingId is null/undefined/empty to avoid calling the link-generation API
with an invalid id.
🚀 feat: 모달 데이터 전달 시스템 리팩토링 및 공유 기능 API 연동
📝 변경사항
useMeetinghooks를useCreateMeeting으로 변경✅ 체크리스트
📸 스크린샷
💬 리뷰어 전달사항
Summary by CodeRabbit
릴리스 노트
새로운 기능
UI 개선
리팩토링
✏️ Tip: You can customize this high-level summary in your review settings.