Skip to content

Conversation

@kangdy25
Copy link
Collaborator

@kangdy25 kangdy25 commented Feb 3, 2026

🚀 feat: 모임 현황 페이지 API 연동 완료

📝 변경사항

  • 시간, 인원에 따른 알림 UI 구축
  • 실시간 사용자 추가 기능 구축

✅ 체크리스트

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

📸 스크린샷

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

💬 리뷰어 전달사항

  • 아직 사용자 출발지 삭제 기능은 구현이 안된상태입니다.
  • 세션 상태에 따른 조건부 렌더링 기능도 나중에 추가하려구 합니다
  • 일단 태우님도 작업을 할 수 있도록 먼저 PR을 올려드립니다!!

Summary by CodeRabbit

  • 새로운 기능
    • 회의 생성 시 참여자 수를 최대 10명으로, 마감일을 최대 180일로 제한했습니다.
    • 회의 상세에 마감 카운트다운 타이머와 남은 정원 표시가 추가되었습니다.
    • 회의 정보 섹션에 공유 버튼과 참여현황 표시가 추가되어 공유 모달을 열 수 있습니다.
  • 리팩터
    • 상세 화면 레이아웃을 정리해 정보 섹션을 먼저 노출하고 참여 목록을 뒤이어 표시합니다.

- 시간, 인원에 따른 알림 UI 구축
- 실시간 사용자 추가 기능 구축
@kangdy25 kangdy25 requested a review from kim3360 February 3, 2026 13:10
@vercel
Copy link

vercel bot commented Feb 3, 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 3, 2026 1:24pm

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

Walkthrough

미팅 생성 페이지의 참여자 및 마감일 상한을 각각 10명/180일로 제한하고, 미정 참가자 용량을 30→10으로 변경했으며, MeetingInfoSection 컴포넌트와 useCountdown/useIsLoggedIn 훅을 추가해 미팅 정보, 카운트다운, 공유 흐름을 도입했습니다.

Changes

Cohort / File(s) Summary
참여자·마감일 제약
app/create/page.tsx
참여자 상한을 10명으로, 마감일 상한을 180일로 제한. 미정(undecided)일 때 용량 매핑을 30→10으로 변경. 관련 UI 버튼 비활성화 조건 업데이트.
미팅 상세 UI
app/meeting/[id]/page.tsx
좌측 패널에 MeetingInfoSection 통합, 기존 인라인 타이머/공유 버튼 제거, handleShareClick 추가로 SHARE 모달을 meetingId와 함께 엶. 참여자 수 표시는 currentParticipantCount 기반으로 변경.
새 컴포넌트
components/meeting/MeetingInfoSection.tsx
새 클라이언트 컴포넌트 추가: 마감 카운트다운 표시(유연성 조건 포함), 남은 용량 표시, 공유 버튼(onShare 콜백). useCountdown 사용.
훅들
hooks/useCountdown.ts, hooks/useIsLoggedIn.ts
useCountdown: 목표 ISO 날짜에 대한 days/hours/minutes/seconds/isExpired 반환 훅 추가. useIsLoggedInnotifyLoginStateChange 추가: 쿠키 기반 로그인 상태 추적 및 이벤트 발행/구독.

Sequence Diagram

sequenceDiagram
    participant User as User
    participant Info as MeetingInfoSection
    participant Page as Meeting Page
    participant Modal as Modal Store

    User->>Info: 공유 버튼 클릭
    activate Info
    Info->>Page: onShare(meetingId)
    deactivate Info
    activate Page
    Page->>Modal: openModal(type: SHARE, props:{ meetingId })
    activate Modal
    Modal-->>User: 공유 모달 표시
    deactivate Modal
    deactivate Page
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 분

Possibly related PRs

Suggested reviewers

  • kim3360
🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
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.
Description check ❓ Inconclusive PR 설명이 변경사항과 체크리스트를 포함하지만, 구체적인 구현 세부사항이 부족하고 체크리스트 항목이 모두 미완료 상태입니다. 각 변경사항에 대한 구체적인 설명을 추가하고, 완료된 체크리스트 항목에는 체크 표시를 해주시기 바랍니다.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 모임 현황 페이지 API 연동이라는 주요 변경사항을 명확하게 반영하고 있습니다.

✏️ 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/meeting-info-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.

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: 3

🤖 Fix all issues with AI agents
In `@app/meeting/`[id]/page.tsx:
- Around line 128-136: The API response and MeetingStatusData type are missing
the boolean fields isDeadlineFlexible and isParticipantUndecided, so update the
backend API to return these two fields and extend the MeetingStatusData
type/interface to include them, then pass those properties dynamically into the
MeetingInfoSection (currently called with hardcoded false values) — locate the
MeetingStatusData type definition and the API response mapping used to build
meetingData, add isDeadlineFlexible and isParticipantUndecided there, and
replace the hardcoded props in the page component (where MeetingInfoSection is
rendered) to use meetingData.data.isDeadlineFlexible and
meetingData.data.isParticipantUndecided; also ensure app/create/page.tsx uses
the same field names so creation and read models align.

In `@hooks/useCountdown.ts`:
- Around line 3-49: The hook useCountdown should (1) avoid synchronous setState
inside useEffect by initializing state with a lazy initializer (pass a function
to useState that computes initial timeLeft), (2) validate targetDateISO by
parsing new Date(targetDateISO).getTime() and treating NaN as an expired/invalid
date result, and (3) replace the magic number 59 with a named constant (e.g.,
MAX_COUNTDOWN_DAYS) used both in useCountdown (replace the if (initialTime.days
>= 59) check) and kept consistent with MeetingInfoSection; move or share the
time calculation logic (calculateTimeLeft) so the lazy initializer and the
interval use the same function and ensure the interval is only started when the
parsed target is valid and days < MAX_COUNTDOWN_DAYS.

In `@hooks/useIsLoggedIn.ts`:
- Around line 5-21: Replace the runtime setState in useEffect by computing
initial login state in useState's lazy initializer inside useIsLoggedIn; remove
the useEffect and setIsLogin call. In the initializer, compute presence of the
exact cookie name (avoid document.cookie.includes) by parsing document.cookie or
using a regex that checks for "(^|;\\s*)accessToken=" so you only match the
exact cookie key, then return that boolean as the initial state for isLogin.
🧹 Nitpick comments (2)
app/create/page.tsx (1)

44-46: 매직 넘버를 상수로 추출하는 것을 권장합니다.

10(최대 참여자 수), 180(최대 마감일), 60(유연한 마감일 기본값) 등의 값이 여러 곳에 분산되어 있습니다. 상수로 추출하면 유지보수성이 향상됩니다.

♻️ 상수 추출 예시

파일 상단에 상수를 정의:

const MAX_PARTICIPANTS = 10;
const MAX_DEADLINE_DAYS = 180;
const DEFAULT_FLEXIBLE_DEADLINE_DAYS = 60;
components/meeting/MeetingInfoSection.tsx (1)

23-29: useCountdown에서 seconds를 구조 분해하지 않아도 됩니다.

seconds 값은 사용되지 않으므로 구조 분해에서 제외하면 의도가 더 명확해집니다. 또한, 59라는 매직 넘버가 useCountdown.ts와 여기서 중복 사용되고 있으니, 공유 상수로 추출하는 것을 권장합니다.

♻️ 개선 제안
-  const { days, hours, minutes, isExpired } = useCountdown(deadline);
+  const { days, hours, minutes, isExpired } = useCountdown(deadline);

상수 공유 예시 (constants/meeting.ts 등):

export const NO_TIMER_THRESHOLD_DAYS = 59;

Comment on lines +128 to +136
{meetingData?.data && (
<MeetingInfoSection
deadline={meetingData.data.deadlineAt} // "2026-01-23T18:00:00"
totalCapacity={meetingData.data.totalParticipantCount} // 10
currentParticipants={meetingData.data.currentParticipantCount} // 2
isDeadlineFlexible={false}
isParticipantUndecided={false}
onShare={handleShareClick}
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for MeetingStatusData type and related fields
rg -n "MeetingStatusData|deadlineFlexible|participantUndecided" --type=ts

Repository: SWYP-mingling/Frontend

Length of output: 202


🏁 Script executed:

# Also explore the file structure to understand the context better
fd --type f -e "tsx" -e "ts" | grep -E "(type|api|meeting)" | head -20

Repository: SWYP-mingling/Frontend

Length of output: 477


🏁 Script executed:

# Check the specific file mentioned in the review
cat -n app/meeting/[id]/page.tsx | sed -n '120,145p'

Repository: SWYP-mingling/Frontend

Length of output: 1337


🏁 Script executed:

# View the MeetingStatusData interface definition
cat -n types/api.ts | sed -n '55,75p'

Repository: SWYP-mingling/Frontend

Length of output: 498


🏁 Script executed:

# Search more broadly for any flexible or undecided related fields
rg -n "flexible|Flexible|undecided|Undecided" --type=ts

Repository: SWYP-mingling/Frontend

Length of output: 2668


🏁 Script executed:

# Check the MeetingInfoSection component to understand what props it expects
cat -n components/meeting/MeetingInfoSection.tsx | head -50

Repository: SWYP-mingling/Frontend

Length of output: 2007


API 응답에서 isDeadlineFlexibleisParticipantUndecided 필드 누락

현재 MeetingStatusData 타입에는 isDeadlineFlexibleisParticipantUndecided 필드가 포함되어 있지 않습니다. 이 값들은 모임 생성 시(app/create/page.tsx)에는 상태로 관리되지만, API 응답에는 반영되지 않고 있습니다.

모임의 의미 있는 속성이므로 API에서 이 필드들을 반환하도록 업데이트하고, 그 후 여기서 동적으로 전달하는 것이 좋습니다.

🤖 Prompt for AI Agents
In `@app/meeting/`[id]/page.tsx around lines 128 - 136, The API response and
MeetingStatusData type are missing the boolean fields isDeadlineFlexible and
isParticipantUndecided, so update the backend API to return these two fields and
extend the MeetingStatusData type/interface to include them, then pass those
properties dynamically into the MeetingInfoSection (currently called with
hardcoded false values) — locate the MeetingStatusData type definition and the
API response mapping used to build meetingData, add isDeadlineFlexible and
isParticipantUndecided there, and replace the hardcoded props in the page
component (where MeetingInfoSection is rendered) to use
meetingData.data.isDeadlineFlexible and meetingData.data.isParticipantUndecided;
also ensure app/create/page.tsx uses the same field names so creation and read
models align.

Comment on lines 3 to 49
export const useCountdown = (targetDateISO: string) => {
const [timeLeft, setTimeLeft] = useState({
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
isExpired: false,
});

useEffect(() => {
const calculateTimeLeft = () => {
const now = new Date().getTime();
const target = new Date(targetDateISO).getTime();
const difference = target - now;

if (difference <= 0) {
return { days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: true };
}

return {
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
minutes: Math.floor((difference / 1000 / 60) % 60),
seconds: Math.floor((difference / 1000) % 60),
isExpired: false,
};
};

// 1. 먼저 한 번 계산해서 현재 상태를 업데이트합니다.
const initialTime = calculateTimeLeft();
setTimeLeft(initialTime);

// 2. 기한 없음 상태)면 타이머(setInterval)를 아예 시작하지 않고 여기서 끝냅니다.
if (initialTime.days >= 59) {
return;
}

// 3. 59일 미만일 때만 1초마다 갱신합니다.
const timer = setInterval(() => {
setTimeLeft(calculateTimeLeft());
}, 1000);

return () => clearInterval(timer);
}, [targetDateISO]);

return timeLeft;
};
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

파이프라인 실패 수정 및 유효하지 않은 날짜 처리 추가

  1. 파이프라인 오류: useEffect 내 동기 setState 호출 문제를 lazy initializer로 해결할 수 있습니다.
  2. targetDateISO가 유효하지 않은 문자열일 경우 NaN이 발생할 수 있습니다.
  3. 매직 넘버 59를 상수로 추출하면 MeetingInfoSection.tsx와 일관성을 유지하기 좋습니다.
🐛 파이프라인 오류 및 유효성 검증 수정 제안
 import { useState, useEffect } from 'react';

+const NO_TIMER_THRESHOLD_DAYS = 59;
+
+const calculateTimeLeft = (targetDateISO: string) => {
+  const now = new Date().getTime();
+  const target = new Date(targetDateISO).getTime();
+
+  if (Number.isNaN(target)) {
+    return { days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: true };
+  }
+
+  const difference = target - now;
+
+  if (difference <= 0) {
+    return { days: 0, hours: 0, minutes: 0, seconds: 0, isExpired: true };
+  }
+
+  return {
+    days: Math.floor(difference / (1000 * 60 * 60 * 24)),
+    hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
+    minutes: Math.floor((difference / 1000 / 60) % 60),
+    seconds: Math.floor((difference / 1000) % 60),
+    isExpired: false,
+  };
+};
+
 export const useCountdown = (targetDateISO: string) => {
-  const [timeLeft, setTimeLeft] = useState({
-    days: 0,
-    hours: 0,
-    minutes: 0,
-    seconds: 0,
-    isExpired: false,
-  });
+  const [timeLeft, setTimeLeft] = useState(() => calculateTimeLeft(targetDateISO));

   useEffect(() => {
-    const calculateTimeLeft = () => {
-      // ... existing logic
-    };
-
-    const initialTime = calculateTimeLeft();
-    setTimeLeft(initialTime);
-
-    if (initialTime.days >= 59) {
+    // 초기 계산은 useState lazy initializer에서 수행됨
+    const currentTime = calculateTimeLeft(targetDateISO);
+    
+    if (currentTime.days >= NO_TIMER_THRESHOLD_DAYS) {
       return;
     }

     const timer = setInterval(() => {
-      setTimeLeft(calculateTimeLeft());
+      setTimeLeft(calculateTimeLeft(targetDateISO));
     }, 1000);

     return () => clearInterval(timer);
   }, [targetDateISO]);

   return timeLeft;
 };
🧰 Tools
🪛 GitHub Actions: CI

[error] 33-33: Calling setState synchronously within an effect. Avoid calling setState directly within useEffect

🤖 Prompt for AI Agents
In `@hooks/useCountdown.ts` around lines 3 - 49, The hook useCountdown should (1)
avoid synchronous setState inside useEffect by initializing state with a lazy
initializer (pass a function to useState that computes initial timeLeft), (2)
validate targetDateISO by parsing new Date(targetDateISO).getTime() and treating
NaN as an expired/invalid date result, and (3) replace the magic number 59 with
a named constant (e.g., MAX_COUNTDOWN_DAYS) used both in useCountdown (replace
the if (initialTime.days >= 59) check) and kept consistent with
MeetingInfoSection; move or share the time calculation logic (calculateTimeLeft)
so the lazy initializer and the interval use the same function and ensure the
interval is only started when the parsed target is valid and days <
MAX_COUNTDOWN_DAYS.

Comment on lines 5 to 21
export const useIsLoggedIn = () => {
const [isLogin, setIsLogin] = useState(false);

useEffect(() => {
// 1. 쿠키에서 확인하는 경우 (가장 추천, 아까 쿠키 세팅 하셨으니까요!)
// 'accessToken'이나 'JSESSIONID' 등 실제 사용하는 쿠키 이름이 포함되어 있는지 확인
const hasCookie = document.cookie.includes('accessToken');

// 2. 혹은 로컬스토리지/세션스토리지에서 확인하는 경우
// const hasToken = !!localStorage.getItem('accessToken');
// const hasSession = !!sessionStorage.getItem('accessToken');

setIsLogin(hasCookie); // 조건에 따라 true/false 설정
}, []);

return isLogin;
};
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

파이프라인 실패 수정: useEffect 내 동기 setState 호출 문제

파이프라인 오류에서 지적된 대로, useEffect 내에서 직접 setState를 호출하는 것은 불필요한 리렌더링을 발생시킵니다. useState의 lazy initializer를 사용하면 마운트 시점에 즉시 올바른 값을 설정할 수 있습니다.

또한, document.cookie.includes('accessToken')는 부분 문자열 매칭으로 'myaccessToken' 같은 다른 쿠키도 매칭될 수 있습니다.

🐛 파이프라인 오류 및 쿠키 체크 로직 수정 제안
 'use client';

-import { useState, useEffect } from 'react';
+import { useState } from 'react';
+
+const hasAccessTokenCookie = (): boolean => {
+  if (typeof document === 'undefined') return false;
+  return document.cookie
+    .split(';')
+    .some((c) => c.trim().startsWith('accessToken='));
+};

 export const useIsLoggedIn = () => {
-  const [isLogin, setIsLogin] = useState(false);
-
-  useEffect(() => {
-    // 1. 쿠키에서 확인하는 경우 (가장 추천, 아까 쿠키 세팅 하셨으니까요!)
-    // 'accessToken'이나 'JSESSIONID' 등 실제 사용하는 쿠키 이름이 포함되어 있는지 확인
-    const hasCookie = document.cookie.includes('accessToken');
-
-    // 2. 혹은 로컬스토리지/세션스토리지에서 확인하는 경우
-    // const hasToken = !!localStorage.getItem('accessToken');
-    // const hasSession = !!sessionStorage.getItem('accessToken');
-
-    setIsLogin(hasCookie); // 조건에 따라 true/false 설정
-  }, []);
+  const [isLogin] = useState(hasAccessTokenCookie);

   return isLogin;
 };
🧰 Tools
🪛 GitHub Actions: CI

[error] 17-17: Calling setState synchronously within an effect. Avoid calling setState directly inside useEffect

🤖 Prompt for AI Agents
In `@hooks/useIsLoggedIn.ts` around lines 5 - 21, Replace the runtime setState in
useEffect by computing initial login state in useState's lazy initializer inside
useIsLoggedIn; remove the useEffect and setIsLogin call. In the initializer,
compute presence of the exact cookie name (avoid document.cookie.includes) by
parsing document.cookie or using a regex that checks for "(^|;\\s*)accessToken="
so you only match the exact cookie key, then return that boolean as the initial
state for isLogin.

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

🤖 Fix all issues with AI agents
In `@hooks/useCountdown.ts`:
- Line 1: The import list in hooks/useCountdown.ts includes an unused symbol
useCallback; remove useCallback from the import statement (leave useState and
useEffect) and run lint/type-check to ensure no other references to useCallback
remain (look for any functions named useCountdown or internal callbacks if
present).

@@ -0,0 +1,45 @@
import { useState, useEffect, useCallback } from 'react';
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

사용하지 않는 useCallback import 제거 필요

useCallback이 import되었지만 실제로 사용되지 않습니다.

🧹 수정 제안
-import { useState, useEffect, useCallback } from 'react';
+import { useState, 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.

Suggested change
import { useState, useEffect, useCallback } from 'react';
import { useState, useEffect } from 'react';
🧰 Tools
🪛 GitHub Check: build

[warning] 1-1:
'useCallback' is defined but never used

🤖 Prompt for AI Agents
In `@hooks/useCountdown.ts` at line 1, The import list in hooks/useCountdown.ts
includes an unused symbol useCallback; remove useCallback from the import
statement (leave useState and useEffect) and run lint/type-check to ensure no
other references to useCallback remain (look for any functions named
useCountdown or internal callbacks if present).

@kangdy25 kangdy25 merged commit cd41f3c into main Feb 3, 2026
5 checks passed
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