Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 50 additions & 16 deletions app/join/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -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 (
Expand Down Expand Up @@ -101,17 +132,20 @@ export default function Page() {
</div>
</div>
{/* 하단 버튼 */}
<button
type="submit"
disabled={!isFormValid}
className={`text-gray-2 mt-6 h-12 w-full rounded-sm py-4 pt-3 pb-2.5 text-lg font-semibold transition-colors md:max-w-sm ${
isFormValid
? 'hover:bg-blue-8 bg-blue-5' // 활성화 상태
: 'bg-gray-4 cursor-not-allowed' // 비활성화 상태
}`}
>
모임 참여하기
</button>
<div className="relative w-full md:max-w-sm">
<button
type="submit"
disabled={!isFormValid || participantEnter.isPending}
className={`text-gray-2 mt-6 h-12 w-full rounded-sm py-4 pt-3 pb-2.5 text-lg font-semibold transition-colors ${
isFormValid && !participantEnter.isPending
? 'hover:bg-blue-8 bg-blue-5' // 활성화 상태
: 'bg-gray-4 cursor-not-allowed' // 비활성화 상태
}`}
>
모임 참여하기
</button>
<Toast message={errorMessage} isVisible={isVisible} />
</div>
</form>
</div>
);
Expand Down
2 changes: 1 addition & 1 deletion app/share/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
try {
await navigator.clipboard.writeText(shareUrl);
show();
} catch (error) {

Check warning on line 45 in app/share/[id]/page.tsx

View workflow job for this annotation

GitHub Actions / build

'error' is defined but never used

Check warning on line 45 in app/share/[id]/page.tsx

View workflow job for this annotation

GitHub Actions / build

'error' is defined but never used
alert('복사 실패');
}
};
Expand Down Expand Up @@ -83,7 +83,7 @@
</div>

<Link
href={`/meeting/${id}`}
href={`/join/${id}`}
className="bg-blue-5 hover:bg-blue-8 h-12 w-full rounded-sm py-2.5 pt-3 text-center text-lg font-normal text-white transition-colors md:w-90"
Comment on lines 85 to 87
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

공유 링크 경로를 CTA와 일치시키세요.
버튼은 /join/{id}로 이동하지만, 복사되는 링크는 /meeting/{id}라서 공유 경로가 분리됩니다. join 흐름을 기준으로 복사 링크도 동일하게 맞춰주세요.

🔧 수정 제안
-        setShareUrl(`${window.location.origin}/meeting/${id}`);
+        setShareUrl(`${window.location.origin}/join/${id}`);
🤖 Prompt for AI Agents
In `@app/share/`[id]/page.tsx around lines 85 - 87, The share button’s visual Link
uses `/join/${id}` but the copied URL is built with `/meeting/${id}`, causing
inconsistent share paths; update the code that generates the copied share URL
(the function or variable that composes the share link—e.g., any
`copyMeetingLink`, `generateShareUrl`, `shareUrl` or copy handler) to use
`/join/${id}` so it matches the Link href and the join flow, and ensure the `id`
interpolation and origin are reused rather than a different path.

>
내 출발지 등록하기
Expand Down
11 changes: 11 additions & 0 deletions hooks/api/useParticipant.ts
Original file line number Diff line number Diff line change
@@ -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<ParticipantEnterResponse, Error, { meetingId: string; data: ParticipantEnterRequest }>({
mutationFn: async ({ meetingId, data }) => {
return apiPost<ParticipantEnterResponse>(`/api/participant/${meetingId}/enter`, data);
},
});
}
12 changes: 12 additions & 0 deletions types/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,15 @@ export interface MeetingCreateData {

// 모임 생성 API 조립 (ApiResponse에 MeetingCreateData를 담기)
export type MeetingCreateResponse = ApiResponse<MeetingCreateData>;

// 참여자 입장 API 요청 타입
export interface ParticipantEnterRequest {
userId: string;
password: string;
}

// 참여자 입장 API 응답 데이터 타입 (데이터 없음, success만 확인)
export type ParticipantEnterData = Record<string, never>;

// 참여자 입장 API 조립
export type ParticipantEnterResponse = ApiResponse<ParticipantEnterData>;
Loading