-
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| 'use client'; | ||
|
|
||
| import Image from 'next/image'; | ||
| import { useCountdown } from '@/hooks/useCountdown'; | ||
|
|
||
| interface MeetingInfoProps { | ||
| deadline: string; | ||
| isDeadlineFlexible?: boolean; | ||
| totalCapacity: number; | ||
| currentParticipants: number; | ||
| isParticipantUndecided?: boolean; | ||
| onShare: (e: React.MouseEvent<HTMLButtonElement>) => void; | ||
| } | ||
|
|
||
| export default function MeetingInfoSection({ | ||
| deadline, | ||
| isDeadlineFlexible = false, | ||
| totalCapacity, | ||
| currentParticipants, | ||
| isParticipantUndecided = false, | ||
| onShare, | ||
| }: MeetingInfoProps) { | ||
| const { days, hours, minutes, isExpired } = useCountdown(deadline); | ||
|
|
||
| // 남은 인원 (음수 방지) | ||
| const pendingCount = Math.max(0, totalCapacity - currentParticipants); | ||
|
|
||
| // 1. 시간 렌더링 여부: (기한 유연 아님) AND (59일 미만) | ||
| const isTimeSet = !isDeadlineFlexible && days < 59; | ||
|
|
||
| // 2. 인원 렌더링 여부: (인원 미정 아님) AND (남은 사람이 있음) | ||
| // 👉 !isParticipantUndecided 덕분에 "인원 선택 안 함" 상태면 false가 되어 숨겨집니다. | ||
| const isCapacitySet = !isParticipantUndecided && pendingCount > 0; | ||
|
|
||
| return ( | ||
| <div className="px-5 pt-10 md:p-0"> | ||
| <div className="flex items-start justify-between"> | ||
| <div className="text-[22px] leading-[1.364] font-semibold tracking-[-1.948%] break-keep"> | ||
| {/* --- [타이틀 영역] --- */} | ||
| <h2 className="text-gray-9"> | ||
| {isTimeSet ? ( | ||
| // Case: 시간이 설정됨 (시간만 입력 or 둘 다 입력) | ||
| <> | ||
| 투표 마감 시간 | ||
| <br /> | ||
| {isExpired ? ( | ||
| <span className="text-gray-400">마감되었습니다</span> | ||
| ) : ( | ||
| <> | ||
| <span className="text-blue-5"> | ||
| {days > 0 && `${days}일 `} | ||
| {hours}시간 {minutes}분 | ||
| </span> | ||
| {' 남았습니다'} | ||
| </> | ||
| )} | ||
| </> | ||
| ) : ( | ||
| // Case: 시간이 유연함 (참여자만 입력 or 둘 다 안 함) | ||
| '투표에 참여해주세요' | ||
| )} | ||
| </h2> | ||
|
|
||
| {/* --- [인원 텍스트 영역] --- */} | ||
| {/* isCapacitySet이 false면 아예 렌더링되지 않음 */} | ||
| {isCapacitySet && ( | ||
| <p className="text-gray-5 mt-2 text-[15px] font-normal"> | ||
| <span>아직 입력 안 한 모임원 {pendingCount}명</span> | ||
| </p> | ||
| )} | ||
| </div> | ||
|
|
||
| <button | ||
| className="text-blue-5 bg-blue-1 hover:bg-blue-2 flex h-6 w-fit shrink-0 cursor-pointer items-center gap-0.5 rounded px-3 py-1.5 text-[11px] font-semibold whitespace-nowrap transition-colors" | ||
| type="button" | ||
| onClick={onShare} | ||
| > | ||
| <Image src="/icon/share.svg" alt="공유 아이콘" width={12} height={12} /> | ||
| 참여 링크 공유하기 | ||
| </button> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,45 @@ | ||||||
| import { useState, useEffect, useCallback } from 'react'; | ||||||
|
Check warning on line 1 in hooks/useCountdown.ts
|
||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용하지 않는
🧹 수정 제안-import { useState, useEffect, useCallback } from 'react';
+import { useState, useEffect } from 'react';📝 Committable suggestion
Suggested change
🧰 Tools🪛 GitHub Check: build[warning] 1-1: 🤖 Prompt for AI Agents |
||||||
|
|
||||||
| // 1. 계산 로직을 훅 내부(useEffect 밖) 또는 파일 최상단으로 분리 | ||||||
| const calculateTimeLeft = (targetDateISO: string) => { | ||||||
| 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, | ||||||
| }; | ||||||
| }; | ||||||
|
|
||||||
| export const useCountdown = (targetDateISO: string) => { | ||||||
| // 2. [수정] useState 안에서 함수를 호출하여 '초기값'을 바로 세팅합니다. | ||||||
| // 이렇게 하면 useEffect에서 setTimeLeft를 또 호출할 필요가 없습니다. | ||||||
| const [timeLeft, setTimeLeft] = useState(() => calculateTimeLeft(targetDateISO)); | ||||||
|
|
||||||
| useEffect(() => { | ||||||
| // 3. 기한 없음(59일 이상) 체크를 여기서 수행 | ||||||
| // (초기값이 이미 계산되어 있으므로 timeLeft.days를 바로 확인 가능) | ||||||
| if (timeLeft.days >= 59) { | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
| // 만약 초기값 계산 시점과 mount 시점의 차이를 보정하고 싶다면 | ||||||
| // 여기서 한 번 더 계산할 수도 있지만, 위에서 초기화를 했으므로 바로 인터벌을 돌려도 됩니다. | ||||||
|
|
||||||
| const timer = setInterval(() => { | ||||||
| setTimeLeft(calculateTimeLeft(targetDateISO)); | ||||||
| }, 1000); | ||||||
|
|
||||||
| return () => clearInterval(timer); | ||||||
| }, [targetDateISO, timeLeft.days]); // timeLeft.days 의존성 추가 | ||||||
|
|
||||||
| return timeLeft; | ||||||
| }; | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| 'use client'; | ||
|
|
||
| import { useState, useEffect } from 'react'; | ||
|
|
||
| // 커스텀 이벤트로 로그인 상태 변경 알림 | ||
| export const notifyLoginStateChange = () => { | ||
| window.dispatchEvent(new Event('loginStateChange')); | ||
| }; | ||
|
|
||
| export const useIsLoggedIn = () => { | ||
| const [isLogin, setIsLogin] = useState(() => { | ||
| // 초기값을 lazy initialization으로 설정 | ||
| if (typeof window !== 'undefined') { | ||
| return document.cookie.includes('accessToken'); | ||
| } | ||
| return false; | ||
| }); | ||
|
|
||
| useEffect(() => { | ||
| const checkLoginState = () => { | ||
| setIsLogin(document.cookie.includes('accessToken')); | ||
| }; | ||
|
|
||
| // 커스텀 이벤트 리스너 등록 | ||
| window.addEventListener('loginStateChange', checkLoginState); | ||
|
|
||
| return () => { | ||
| window.removeEventListener('loginStateChange', checkLoginState); | ||
| }; | ||
| }, []); | ||
|
|
||
| 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.
🧩 Analysis chain
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 202
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 477
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 1337
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 498
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 2668
🏁 Script executed:
Repository: SWYP-mingling/Frontend
Length of output: 2007
API 응답에서
isDeadlineFlexible과isParticipantUndecided필드 누락현재
MeetingStatusData타입에는isDeadlineFlexible과isParticipantUndecided필드가 포함되어 있지 않습니다. 이 값들은 모임 생성 시(app/create/page.tsx)에는 상태로 관리되지만, API 응답에는 반영되지 않고 있습니다.모임의 의미 있는 속성이므로 API에서 이 필드들을 반환하도록 업데이트하고, 그 후 여기서 동적으로 전달하는 것이 좋습니다.
🤖 Prompt for AI Agents