From 025e2133f36180ee47bef929766da49bc4f7808e Mon Sep 17 00:00:00 2001 From: Shineast Date: Mon, 2 Feb 2026 14:52:42 +0900 Subject: [PATCH 1/6] =?UTF-8?q?fix:=20:=20=ED=99=88=20=ED=99=94=EB=A9=B4?= =?UTF-8?q?=20=EB=AC=B4=ED=95=9C=20=EB=A1=A4=EB=A7=81=20=EC=95=A0=EB=8B=88?= =?UTF-8?q?=EB=A9=94=EC=9D=B4=EC=85=98=20=EB=81=8A=EA=B9=80=20=ED=98=84?= =?UTF-8?q?=EC=83=81=20=EC=88=98=EC=A0=95=20-=20=EB=B6=88=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20globals.css=20=EB=82=B4=EC=9A=A9=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20-=20=EB=AC=B4=ED=95=9C=20=EB=A1=A4?= =?UTF-8?q?=EB=A7=81=20=EC=95=A0=EB=8B=88=EB=A9=94=EC=9D=B4=EC=85=98=20?= =?UTF-8?q?=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/globals.css | 19 +++++++++---------- app/page.tsx | 24 ++++++++++++++---------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/app/globals.css b/app/globals.css index 4424318..09468ea 100644 --- a/app/globals.css +++ b/app/globals.css @@ -79,16 +79,6 @@ --ring: oklch(0.708 0 0); } -@layer base { -} - -/* 부드러운 무한 스크롤 애니메이션 */ -@layer utilities { - .animate-slide-smooth { - animation: slide-smooth 30s linear infinite; - } -} - @keyframes slide-smooth { 0% { transform: translateX(0); @@ -97,3 +87,12 @@ transform: translateX(-50%); } } + +.animate-slide-smooth { + animation: slide-smooth 30s linear infinite; + will-change: transform; +} + +.animate-slide-smooth:hover { + animation-play-state: paused; +} diff --git a/app/page.tsx b/app/page.tsx index 68ecaf0..2919b0c 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -131,16 +131,20 @@ const PainPointsSection = () => ( {/* Tooltip Slider */}
-
-
- {PAIN_POINTS.map((text, index) => ( - - ))} - - {/* 두 번째 세트 (이어달리기용) */} - {PAIN_POINTS.map((text, index) => ( - - ))} +
+
+ {/* 첫 번째 세트 */} +
+ {PAIN_POINTS.map((text, index) => ( + + ))} +
+ {/* 두 번째 세트 */} +
+ {PAIN_POINTS.map((text, index) => ( + + ))} +
From b59131d812d5bde78f3dc1cd3c1216da8822b1df Mon Sep 17 00:00:00 2001 From: Shineast Date: Mon, 2 Feb 2026 20:46:09 +0900 Subject: [PATCH 2/6] =?UTF-8?q?chore:=20=ED=83=80=EC=9D=B4=ED=8B=80,=20?= =?UTF-8?q?=EB=94=94=EC=8A=A4=ED=81=AC=EB=A6=BD=EC=85=98=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/layout.tsx | 5 +++-- app/page.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 69d12e3..f4273a8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -24,8 +24,9 @@ const pretendard = localFont({ }); export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app', + title: '밍글링 - 어디서 만날지, 고민 시간을 줄여드려요', + description: + '퇴근 후 모임, 주말 약속까지. 서울 어디서든 모두가 비슷하게 도착하는 마법의 장소를 찾아드려요.', }; export default function RootLayout({ diff --git a/app/page.tsx b/app/page.tsx index 2919b0c..e23f578 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -132,7 +132,7 @@ const PainPointsSection = () => (
-
+
{/* 첫 번째 세트 */}
{PAIN_POINTS.map((text, index) => ( From 5475c9b92e5302f8091e1ecfdd6f9eddcf25af7c Mon Sep 17 00:00:00 2001 From: Kangdy Date: Mon, 2 Feb 2026 21:42:59 +0900 Subject: [PATCH 3/6] =?UTF-8?q?refactor:=20=ED=95=A8=EC=88=98=EB=AA=85/?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD=20(useEnterPar?= =?UTF-8?q?ticipant)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/join/[id]/page.tsx | 4 ++-- .../useEnterParticipant.ts} | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) rename hooks/api/{useParticipant.ts => mutation/useEnterParticipant.ts} (66%) diff --git a/app/join/[id]/page.tsx b/app/join/[id]/page.tsx index 41dbf3a..bbda034 100644 --- a/app/join/[id]/page.tsx +++ b/app/join/[id]/page.tsx @@ -2,7 +2,7 @@ import { useRouter, useParams } from 'next/navigation'; import { useState } from 'react'; -import { useParticipantEnter } from '@/hooks/api/useParticipant'; +import { useEnterParticipant } from '@/hooks/api/mutation/useEnterParticipant'; import { useToast } from '@/hooks/useToast'; import Toast from '@/components/ui/toast'; @@ -14,7 +14,7 @@ export default function Page() { const [isRemembered, setIsRemembered] = useState(true); const [errorMessage, setErrorMessage] = useState(''); const router = useRouter(); - const participantEnter = useParticipantEnter(); + const participantEnter = useEnterParticipant(); const { isVisible, show } = useToast(); // 이름/비번 유효성 검사 (입력값이 있을 때만 버튼 활성화) diff --git a/hooks/api/useParticipant.ts b/hooks/api/mutation/useEnterParticipant.ts similarity index 66% rename from hooks/api/useParticipant.ts rename to hooks/api/mutation/useEnterParticipant.ts index b0de128..1f7a422 100644 --- a/hooks/api/useParticipant.ts +++ b/hooks/api/mutation/useEnterParticipant.ts @@ -2,8 +2,12 @@ import { useMutation } from '@tanstack/react-query'; import { apiPost } from '@/lib/api'; import type { ParticipantEnterRequest, ParticipantEnterResponse } from '@/types/api'; -export function useParticipantEnter() { - return useMutation({ +export function useEnterParticipant() { + return useMutation< + ParticipantEnterResponse, + Error, + { meetingId: string; data: ParticipantEnterRequest } + >({ mutationFn: async ({ meetingId, data }) => { return apiPost(`/api/participant/${meetingId}/enter`, data); }, From 46c9fe9c956479b9527146c1659207c630f4a0d1 Mon Sep 17 00:00:00 2001 From: Kangdy Date: Tue, 3 Feb 2026 00:29:29 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EB=B0=8F=20meeting=20api=20=EC=97=B0?= =?UTF-8?q?=EA=B2=B0=20=EC=8B=9C=EB=8F=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/meeting/[id]/page.tsx | 144 +++++++++++++++++++------- app/result/{ => [id]}/page.tsx | 0 app/share/[id]/page.tsx | 13 ++- components/modal/nudgeModal.tsx | 10 +- components/modal/shareModal.tsx | 10 +- hooks/api/mutation/useSetDeparture.ts | 17 +++ hooks/api/query/useCheckMeeting.ts | 14 +++ hooks/api/query/useShareMeeting.ts | 3 +- lib/api.ts | 1 + public/images/Rendering2.jpg | Bin 354431 -> 355539 bytes public/images/create_meeting.jpg | Bin 0 -> 289190 bytes public/images/nudge_meeting.png | Bin 0 -> 8780 bytes public/images/nudge_modal.jpg | Bin 0 -> 208820 bytes public/images/waiting_modal.jpg | Bin 0 -> 208820 bytes types/api.ts | 32 ++++++ 15 files changed, 201 insertions(+), 43 deletions(-) rename app/result/{ => [id]}/page.tsx (100%) create mode 100644 hooks/api/mutation/useSetDeparture.ts create mode 100644 hooks/api/query/useCheckMeeting.ts create mode 100644 public/images/create_meeting.jpg create mode 100644 public/images/nudge_meeting.png create mode 100644 public/images/nudge_modal.jpg create mode 100644 public/images/waiting_modal.jpg diff --git a/app/meeting/[id]/page.tsx b/app/meeting/[id]/page.tsx index feb698d..c0d4f0c 100644 --- a/app/meeting/[id]/page.tsx +++ b/app/meeting/[id]/page.tsx @@ -1,19 +1,31 @@ 'use client'; -import { useState } from 'react'; +import { useState, useMemo, useEffect } from 'react'; import Image from 'next/image'; import KakaoMap from '@/components/map/kakaoMap'; import StationSearch from '@/components/meeting/stationSearch'; import { useOpenModal } from '@/hooks/useOpenModal'; -import { MOCK_PARTICIPANTS } from '@/mock/mockData'; -import StationData from '@/database/stations_info.json'; import { useParams, useRouter } from 'next/navigation'; +import { useSetDeparture } from '@/hooks/api/mutation/useSetDeparture'; +import { useCheckMeeting } from '@/hooks/api/query/useCheckMeeting'; // [추가] 조회 훅 +import StationDataRaw from '@/database/stations_info.json'; +import { getRandomHexColor } from '@/lib/color'; + +// 로컬 데이터 타입 정의 +interface StationInfo { + line: string; + name: string; + latitude: number; + longitude: number; +} -const STATION_DATA = StationData; +const STATION_DATA = StationDataRaw as StationInfo[]; export default function Page() { // 선택된 역 이름 상태 관리 const [selectedStation, setSelectedStation] = useState(null); + // 내 이름 관리 (로컬 스토리지에서 가져옴) + const [myName, setMyName] = useState(''); const params = useParams(); const id = params?.id as string; @@ -21,35 +33,86 @@ export default function Page() { const openModal = useOpenModal(); const router = useRouter(); - // 1. [좌표 찾기] 선택된 역 이름으로 MOCK 데이터에서 정보 찾기 - const selectedStationInfo = STATION_DATA.find((station) => station.name === selectedStation); - - // 2. [내 참가자 객체 생성] 선택된 역이 있을 때만 생성 - const myParticipant = selectedStation - ? { - id: 'me', // 고유 ID (문자열) - name: '나', - station: selectedStation, - line: '2호선', - // 정보가 없으면 서울시청 좌표를 기본값으로 사용 (예외 처리) - latitude: selectedStationInfo?.latitude || 37.5665, - longitude: selectedStationInfo?.longitude || 126.978, + // [API Hook] 모임 정보 조회 & 출발지 등록 + const { data: meetingData } = useCheckMeeting(id); + const { mutate: setDeparture } = useSetDeparture(id); + + // 1. 컴포넌트 마운트 시 내 이름 가져오기 + useEffect(() => { + // 로그인/입장 페이지에서 저장했던 키값 사용 (userId 혹은 name) + const storedName = localStorage.getItem('userId') || sessionStorage.getItem('userId'); + if (storedName) setMyName(storedName); + }, []); + + // 2. 역 선택 시 실행될 로직 (상태 변경 + API 전송) + const handleSelectStation = (stationName: string | null) => { + // 화면 UI 즉시 업데이트 + setSelectedStation(stationName); + + if (!stationName) return; + + const stationInfo = STATION_DATA.find((s) => s.name === stationName); + + if (stationInfo) { + // API 전송 (백엔드 스펙: { departure: "역이름" }) + setDeparture({ + departure: stationInfo.name, + }); + } else { + console.error('역 정보를 찾을 수 없습니다.'); + } + }; + + // 3. '나' 객체 생성 (로컬 데이터 기반 즉시 반영) + const myParticipant = useMemo(() => { + if (!selectedStation) return null; + + // 로컬 JSON에서 좌표 즉시 조회 (API 응답 대기 X -> 속도 UP) + const info = STATION_DATA.find((s) => s.name === selectedStation); + if (!info) return null; + + return { + id: 'me', + name: myName || '나', // 로컬 스토리지 이름 사용 + station: info.name, + line: info.line, + latitude: info.latitude, + longitude: info.longitude, + status: 'done', + hexColor: '#000000', // 나는 검정색 고정 + }; + }, [selectedStation, myName]); + + // 4. [최종 리스트 병합] 서버 데이터(남) + 로컬 데이터(나) + const allParticipants = useMemo(() => { + // 서버에서 받은 '이미 등록한 참가자들' + const serverParticipants = meetingData?.data.participants || []; + + // 지도 표시용 포맷으로 변환 + const others = serverParticipants + .filter((p) => p.userName !== myName) // 혹시 모를 중복 방지 (내 이름 제외) + .map((p, index) => ({ + id: `other-${index}`, + name: p.userName, + station: p.stationName, + line: '2호선', // 서버에서 호선 정보가 없다면 임의값 혹은 추가 로직 필요 + latitude: p.latitude, + longitude: p.longitude, status: 'done', - hexColor: '#000000', - } - : null; + hexColor: getRandomHexColor(id), // 다른 사람은 파란색 (혹은 랜덤) + })); - // 3. [최종 리스트 병합] 내가 있으면 맨 앞에 추가, 없으면 기존 리스트만 사용 - const allParticipants = myParticipant ? [myParticipant, ...MOCK_PARTICIPANTS] : MOCK_PARTICIPANTS; + // 내가 선택했으면 [나, ...다른사람들], 아니면 [...다른사람들] + return myParticipant ? [myParticipant, ...others] : others; + }, [meetingData, myParticipant, myName]); const handleSubmit = () => { if (!selectedStation) { - alert('출발지를 선택해주세요!'); + alert('출발지를 먼저 선택해주세요!'); return; } - - console.log('결과 요청:', allParticipants); - router.push('/result'); + // 결과 페이지로 이동 + router.push(`/result/${id}`); }; return ( @@ -64,10 +127,12 @@ export default function Page() {

투표 마감 시간
+ {/* 마감 시간은 API 데이터가 있으면 그것을 활용하거나 기존대로 유지 */} 03: 45 남았습니다

- 아직 입력 안 한 모임원 2명 + {/* API 데이터로 '안 한 사람' 수 계산 */} + 아직 입력 안 한 모임원 {meetingData?.data.pendingParticipantCount ?? 0}명

- {/* 모바일 전용 지도 영역: allParticipants 전달 */} + {/* 모바일 전용 지도 영역 */}
@@ -102,8 +167,11 @@ export default function Page() {

참여현황

- {/* 전체 인원 수 동적 반영 */} - {allParticipants.length}명이 참여 중 + {/* 전체 인원 수 동적 반영 (API) */} + + {meetingData?.data.totalParticipantCount ?? 0}명 + + 이 참여 중
@@ -120,7 +188,12 @@ export default function Page() { 재촉하기
-
+ 아직 입력하지 않은 친구 재촉하기 {/* 출발지 컴포넌트: 리스트 렌더링 */} @@ -130,7 +203,7 @@ export default function Page() {
@@ -142,12 +215,13 @@ export default function Page() { > {user.name}
- 안가연 + {user.name}
))}
+