From 77a52892e300517f7a1ecf1a87b34da8ebbd05a4 Mon Sep 17 00:00:00 2001 From: kimtaewoo <70637743+kim3360@users.noreply.github.com> Date: Mon, 2 Feb 2026 20:15:14 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20=EB=AA=A8=EC=9E=84=20=EC=9E=85?= =?UTF-8?q?=EC=9E=A5=ED=95=98=EA=B8=B0=20api=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/join/[id]/page.tsx | 66 ++++++++++++++++++++++++++++--------- app/share/[id]/page.tsx | 2 +- hooks/api/useParticipant.ts | 11 +++++++ types/api.ts | 12 +++++++ 4 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 hooks/api/useParticipant.ts diff --git a/app/join/[id]/page.tsx b/app/join/[id]/page.tsx index 5567e63..41dbf3a 100644 --- a/app/join/[id]/page.tsx +++ b/app/join/[id]/page.tsx @@ -1,23 +1,54 @@ 'use client'; -import { useRouter } from 'next/navigation'; +import { useRouter, useParams } from 'next/navigation'; import { useState } from 'react'; +import { useParticipantEnter } from '@/hooks/api/useParticipant'; +import { useToast } from '@/hooks/useToast'; +import Toast from '@/components/ui/toast'; export default function Page() { + const params = useParams(); + const meetingId = params?.id as string; const [name, setName] = useState(''); const [password, setPassword] = useState(''); const [isRemembered, setIsRemembered] = useState(true); + const [errorMessage, setErrorMessage] = useState(''); const router = useRouter(); + const participantEnter = useParticipantEnter(); + const { isVisible, show } = useToast(); // 이름/비번 유효성 검사 (입력값이 있을 때만 버튼 활성화) const isFormValid = name.length > 0 && password.length === 4; - const handleSubmit = (e: React.FormEvent) => { + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); - if (!isFormValid) return; + if (!isFormValid || !meetingId) return; - console.log('참여 요청:', { name, password, isRemembered }); - router.push('/meeting'); + try { + const result = await participantEnter.mutateAsync({ + meetingId, + data: { + userId: name, + password, + }, + }); + + if (result.success) { + if (isRemembered) { + localStorage.setItem('userId', name); + } else { + sessionStorage.setItem('userId', name); + } + + router.push(`/meeting/${meetingId}`); + } else { + setErrorMessage('모임 참여에 실패했습니다. 다시 시도해주세요.'); + show(); + } + } catch { + setErrorMessage('모임 참여에 실패했습니다. 이름과 비밀번호를 확인해주세요.'); + show(); + } }; return ( @@ -101,17 +132,20 @@ export default function Page() { {/* 하단 버튼 */} - +
+ + +
); diff --git a/app/share/[id]/page.tsx b/app/share/[id]/page.tsx index aac001a..31849cc 100644 --- a/app/share/[id]/page.tsx +++ b/app/share/[id]/page.tsx @@ -83,7 +83,7 @@ export default function SharePage() { 내 출발지 등록하기 diff --git a/hooks/api/useParticipant.ts b/hooks/api/useParticipant.ts new file mode 100644 index 0000000..b0de128 --- /dev/null +++ b/hooks/api/useParticipant.ts @@ -0,0 +1,11 @@ +import { useMutation } from '@tanstack/react-query'; +import { apiPost } from '@/lib/api'; +import type { ParticipantEnterRequest, ParticipantEnterResponse } from '@/types/api'; + +export function useParticipantEnter() { + return useMutation({ + mutationFn: async ({ meetingId, data }) => { + return apiPost(`/api/participant/${meetingId}/enter`, data); + }, + }); +} diff --git a/types/api.ts b/types/api.ts index 0e88810..4ce7828 100644 --- a/types/api.ts +++ b/types/api.ts @@ -21,3 +21,15 @@ export interface MeetingCreateData { // 모임 생성 API 조립 (ApiResponse에 MeetingCreateData를 담기) export type MeetingCreateResponse = ApiResponse; + +// 참여자 입장 API 요청 타입 +export interface ParticipantEnterRequest { + userId: string; + password: string; +} + +// 참여자 입장 API 응답 데이터 타입 (데이터 없음, success만 확인) +export type ParticipantEnterData = Record; + +// 참여자 입장 API 조립 +export type ParticipantEnterResponse = ApiResponse;