diff --git a/app/create/page.tsx b/app/create/page.tsx index 4a2cacf..5ca407e 100644 --- a/app/create/page.tsx +++ b/app/create/page.tsx @@ -41,7 +41,7 @@ export default function Page() { }; const handleIncreaseParticipants = () => { - if (!isParticipantUndecided) { + if (!isParticipantUndecided && participantCount < 10) { setParticipantCount((prev) => prev + 1); } }; @@ -53,7 +53,7 @@ export default function Page() { }; const handleIncreaseDeadline = () => { - if (!isDeadlineFlexible) { + if (!isDeadlineFlexible && deadlineDays < 180) { setDeadlineDays((prev) => prev + 1); } }; @@ -124,7 +124,7 @@ export default function Page() { const purposes = getPurposes(); // capacity 처리: "아직 안정해졌어요" 체크 시 30으로 설정 - const capacity = isParticipantUndecided ? 30 : participantCount || 1; + const capacity = isParticipantUndecided ? 10 : participantCount || 1; const requestData: MeetingCreateRequest = { meetingName, @@ -308,7 +308,7 @@ export default function Page() { - - + {meetingData?.data && ( + + )} {/* 모바일 전용 지도 영역 */} 참여현황 {/* 전체 인원 수 동적 반영 (API) */} - - {meetingData?.data.totalParticipantCount ?? 0}명 - - 이 참여 중 + {meetingData?.data.currentParticipantCount}명이 + 참여 중 diff --git a/components/meeting/MeetingInfoSection.tsx b/components/meeting/MeetingInfoSection.tsx new file mode 100644 index 0000000..1fad764 --- /dev/null +++ b/components/meeting/MeetingInfoSection.tsx @@ -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) => 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 ( +
+
+
+ {/* --- [타이틀 영역] --- */} +

+ {isTimeSet ? ( + // Case: 시간이 설정됨 (시간만 입력 or 둘 다 입력) + <> + 투표 마감 시간 +
+ {isExpired ? ( + 마감되었습니다 + ) : ( + <> + + {days > 0 && `${days}일 `} + {hours}시간 {minutes}분 + + {' 남았습니다'} + + )} + + ) : ( + // Case: 시간이 유연함 (참여자만 입력 or 둘 다 안 함) + '투표에 참여해주세요' + )} +

+ + {/* --- [인원 텍스트 영역] --- */} + {/* isCapacitySet이 false면 아예 렌더링되지 않음 */} + {isCapacitySet && ( +

+ 아직 입력 안 한 모임원 {pendingCount}명 +

+ )} +
+ + +
+
+ ); +} diff --git a/hooks/useCountdown.ts b/hooks/useCountdown.ts new file mode 100644 index 0000000..aa95d43 --- /dev/null +++ b/hooks/useCountdown.ts @@ -0,0 +1,45 @@ +import { useState, useEffect, useCallback } from 'react'; + +// 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; +}; diff --git a/hooks/useIsLoggedIn.ts b/hooks/useIsLoggedIn.ts new file mode 100644 index 0000000..bc5fe83 --- /dev/null +++ b/hooks/useIsLoggedIn.ts @@ -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; +};