Skip to content

Conversation

@kangdy25
Copy link
Collaborator

@kangdy25 kangdy25 commented Feb 2, 2026

🚀 feat: 모달 데이터 전달 시스템 리팩토링 및 공유 기능 API 연동

📝 변경사항

  • 중요 폴더 구조 변경 (api 폴더 하위에 query, mutation 폴더를 두었습니다.)
  • useMeeting hooks를 useCreateMeeting 으로 변경
  • Global Modal 시스템 개선 (데이터 전달 구조 도입)
  • 공유 기능 로직 분리 및 최적화 (useShareMeeting)

✅ 체크리스트

  • 코드 리뷰를 받았습니다
  • 테스트를 완료했습니다
  • 린터 에러가 없습니다
  • 타입 에러가 없습니다
  • 브라우저에서 테스트를 완료했습니다
  • 모바일에서 테스트를 완료했습니다 (해당되는 경우)

📸 스크린샷

UI 변경 사항이 있다면 이미지를 드래그해서 넣어주세요!

💬 리뷰어 전달사항

  • 이번 PR에서는 변경사항이 많습니다. 태우님이 작업 중인 부분과 충돌이 날 수 있으니 확인 부탁드려요!!

Summary by CodeRabbit

릴리스 노트

  • 새로운 기능

    • 미팅 공유 기능이 개선되어 모달에서 미팅 ID를 안정적으로 전달합니다.
    • 공유 및 알림 모달이 미팅 정보를 정확하게 처리합니다.
  • UI 개선

    • 버튼과 컨테이너의 크기, 간격, 모서리 반경을 정렬하여 일관성 있는 디자인을 제공합니다.
  • 리팩토링

    • 공유 기능 로직이 최적화되어 더 효율적으로 작동합니다.

✏️ Tip: You can customize this high-level summary in your review settings.

- 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` 제거로 렌더링 성능 최적화
@kangdy25 kangdy25 requested a review from kim3360 February 2, 2026 02:34
@vercel
Copy link

vercel bot commented Feb 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mingling-frontend Ready Ready Preview, Comment Feb 2, 2026 2:34am

@coderabbitai
Copy link

coderabbitai bot commented Feb 2, 2026

Walkthrough

모달 시스템에 meetingId를 전달하기 위해 데이터 페이로드 메커니즘을 추가하고, 공유 URL 생성 및 클립보드 관리 로직을 새로운 useShareMeeting 훅으로 중앙화합니다. 공유/누지 모달, 라우팅 페이지 및 관련 컴포넌트들을 이에 맞게 업데이트합니다.

Changes

Cohort / File(s) Summary
모달 시스템 확장
store/useModalStore.ts, hooks/useOpenModal.ts, components/modal/globalModal.tsx
ModalData 인터페이스 추가, onOpen 시그니처 확장, 모달 스토어에 data 필드 추가, GlobalModal에서 data 읽기 및 SHARE/NUDGE 모달에 meetingId 전달
공유/누지 모달 리팩토링
components/modal/shareModal.tsx, components/modal/nudgeModal.tsx
meetingId prop 추가, useShareMeeting 훅 사용으로 로컬 상태 제거, 클립보드/토스트 로직 간소화
새 공유 미팅 훅
hooks/api/query/useShareMeeting.ts
meetingId를 기반으로 공유 URL 생성, 클립보드 복사 처리, 로딩/에러 상태 제공
페이지 라우팅 업데이트
app/meeting/[id]/page.tsx, app/result/page.tsx, app/share/[id]/page.tsx
useParams로 id 추출, 모달 열기 시 meetingId 전달, app/share/[id]/page.tsx에서 useShareMeeting 훅 사용
모달 호출 업데이트
components/footer.tsx, components/header.tsx
openModal 호출에 undefined 데이터 인수 추가
페이지 스타일링
app/create/page.tsx
Tailwind 클래스 간소화: rounded 변형, 높이/너비 토큰 정규화, 간격/정렬 조정

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)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • kim3360
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경사항의 주요 내용을 정확하게 반영하고 있습니다. 모달 데이터 전달 시스템 리팩토링과 공유 기능 API 연동이라는 핵심 변경사항을 명확하게 설명합니다.
Description check ✅ Passed PR 설명이 템플릿의 주요 섹션을 포함하고 있으며, 변경사항, 체크리스트, 리뷰어 전달사항이 모두 작성되어 있습니다. 스크린샷 섹션은 UI 변경이 미미하여 포함되지 않은 것으로 보입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/share-link-modal-api

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kangdy25 kangdy25 changed the title Feature/share link modal api feat: 모달 데이터 전달 시스템 리팩토링 및 공유 기능 API 연동 Feb 2, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 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 | 🟡 Minor

meetingId 누락 시 빈 문자열로 렌더링되는 점을 가드하세요.

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"
            >

Comment on lines +6 to 15
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;

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find app/result -type f -name "*.tsx" -o -name "*.ts" | head -20

Repository: SWYP-mingling/Frontend

Length of output: 85


🏁 Script executed:

cd app && find . -path "*result*" -type f | head -20

Repository: SWYP-mingling/Frontend

Length of output: 83


🏁 Script executed:

cat -n app/result/page.tsx 2>/dev/null | head -60

Repository: 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 | sort

Repository: SWYP-mingling/Frontend

Length of output: 76


🏁 Script executed:

rg "result/\[" --type tsx --type ts | head -20

Repository: 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 -30

Repository: SWYP-mingling/Frontend

Length of output: 93


🏁 Script executed:

rg "SHARE" app --type js --type jsx -A 3 | head -40

Repository: SWYP-mingling/Frontend

Length of output: 93


🏁 Script executed:

rg "useParams" app --type js --type jsx -B 2 -A 5 | head -60

Repository: 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 -50

Repository: SWYP-mingling/Frontend

Length of output: 2494


🏁 Script executed:

rg "openModal" app -A 3 | head -80

Repository: SWYP-mingling/Frontend

Length of output: 1711


🏁 Script executed:

rg "/result" . -A 2 -B 2 | head -60

Repository: SWYP-mingling/Frontend

Length of output: 623


🏁 Script executed:

rg "SHARE" hooks -A 10 | head -60

Repository: SWYP-mingling/Frontend

Length of output: 48


🏁 Script executed:

find . -name "*.tsx" -o -name "*.ts" | xargs grep -l "SHARE" | grep -i modal

Repository: SWYP-mingling/Frontend

Length of output: 125


🏁 Script executed:

rg "useOpenModal" hooks -A 20 | head -80

Repository: SWYP-mingling/Frontend

Length of output: 628


🏁 Script executed:

cat -n components/modal/globalModal.tsx | head -100

Repository: SWYP-mingling/Frontend

Length of output: 1198


🏁 Script executed:

cat -n store/useModalStore.ts

Repository: SWYP-mingling/Frontend

Length of output: 976


🏁 Script executed:

rg "meetingId" components/modal -A 5 -B 5

Repository: 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 /shop means the optional segment is not present, so in practice you should expect no slug key (i.e. useParams() is {}), and handle it as optional. (nextjs.org)
    • Visiting /shop/a/b means slug is active and you get { slug: ['a','b'] }. (nextjs.org)

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:


🏁 Script executed:

rg "useSearchParams" app -B 2 -A 5 | head -40

Repository: 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.

@kangdy25 kangdy25 merged commit f7cbcb1 into main Feb 2, 2026
5 checks passed
@kangdy25 kangdy25 deleted the feature/share-link-modal-api branch February 2, 2026 11:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants