-
Notifications
You must be signed in to change notification settings - Fork 1
feat: 모임 현황 페이지 API 연동 완료 #25
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
- 시간, 인원에 따른 알림 UI 구축 - 실시간 사용자 추가 기능 구축
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Walkthrough미팅 생성 페이지의 참여자 및 마감일 상한을 각각 10명/180일로 제한하고, 미정 참가자 용량을 30→10으로 변경했으며, MeetingInfoSection 컴포넌트와 useCountdown/useIsLoggedIn 훅을 추가해 미팅 정보, 카운트다운, 공유 흐름을 도입했습니다. Changes
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 분 Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 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: 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;
| {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} | ||
| /> |
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:
#!/bin/bash
# Check for MeetingStatusData type and related fields
rg -n "MeetingStatusData|deadlineFlexible|participantUndecided" --type=tsRepository: 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 -20Repository: 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=tsRepository: 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 -50Repository: SWYP-mingling/Frontend
Length of output: 2007
API 응답에서 isDeadlineFlexible과 isParticipantUndecided 필드 누락
현재 MeetingStatusData 타입에는 isDeadlineFlexible과 isParticipantUndecided 필드가 포함되어 있지 않습니다. 이 값들은 모임 생성 시(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.
| 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; | ||
| }; |
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.
파이프라인 실패 수정 및 유효하지 않은 날짜 처리 추가
- 파이프라인 오류:
useEffect내 동기setState호출 문제를 lazy initializer로 해결할 수 있습니다. targetDateISO가 유효하지 않은 문자열일 경우NaN이 발생할 수 있습니다.- 매직 넘버
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.
| 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; | ||
| }; |
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.
파이프라인 실패 수정: 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.
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
🤖 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'; | |||
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.
사용하지 않는 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.
| 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).
🚀 feat: 모임 현황 페이지 API 연동 완료
📝 변경사항
✅ 체크리스트
📸 스크린샷
💬 리뷰어 전달사항
Summary by CodeRabbit