Skip to content

Commit 676011d

Browse files
jun-0411young-52
andauthored
๐Ÿ‘ฝ๏ธ Use the API for event-related pages (#30)
### ๐Ÿ“ ์ž‘์—… ๋‚ด์šฉ - ์ผ์ • ์ƒ์„ธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ, ์ฐธ์—ฌ์ž ๋ช…๋‹จ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ, ์ผ์ • ์‹ ์ฒญํ•˜๊ธฐ api๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค. - ๊ฐ ํŽ˜์ด์ง€์— ํ•˜๋“œ์ฝ”๋”ฉ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์ง€์šฐ๊ณ , api๋ฅผ ์—ฐ๊ฒฐํ–ˆ์Šต๋‹ˆ๋‹ค. ### ๐Ÿ“ธ ์Šคํฌ๋ฆฐ์ƒท (์„ ํƒ) ### ๐Ÿš€ ๋ฆฌ๋ทฐ ์š”๊ตฌ์‚ฌํ•ญ (์„ ํƒ) - api์—์„œ ์ถ”๊ฐ€๋กœ ํ•„์š”ํ•œ ํ•„๋“œ๊ฐ€ ๋ช‡๊ฐ€์ง€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ถ”ํ›„ ๋ฐฑ์—”๋“œ์™€ ํ˜‘์˜ํ•ด์„œ api๋ฅผ ์ˆ˜์ •ํ•ด์•ผ ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค. - ์ฐธ์—ฌ์ž ๋ช…๋‹จ์—์„œ ํ”„๋กœํ•„ ์ด๋ฏธ์ง€์™€, ๋กœ๊ทธ์ธ ๋œ ์‚ฌ๋žŒ์ธ ๊ฒฝ์šฐ ์ด๋ฆ„์ด ์—†์Šต๋‹ˆ๋‹ค. - ๋Œ€๊ธฐ ๋ฒˆํ˜ธ ์ˆœ๋ฒˆ ํ•„๋“œ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. - ๋ช‡ ๋ช…์ด ์ฐธ์—ฌํ–ˆ๋Š”์ง€ ํ•„๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.(์ด ๋ถ€๋ถ„์€ ํ”„๋ก ํŠธ์—์„œ ๊ตฌํ˜„์ด ์‰ฌ์›Œ ๋งŒ๋“ค์–ด ๋†“์•˜์Šต๋‹ˆ๋‹ค.) - ์ƒํƒœ ์ฝ”๋“œ ์„ค์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. - ํ•˜๋“œ ์ฝ”๋”ฉ ๋ถ€๋ถ„์„ ๋‹ค ์ง€์› ์Šต๋‹ˆ๋‹ค(Event.tsx์™€ Guests.tsx์—๋Š” ์ฃผ์„์ฒ˜๋ฆฌ, ๋‚˜๋จธ์ง€ ํŽ˜์ด์ง€์—์„œ๋Š” ๋ชจ๋‘ ์‚ญ์ œ) ์ด์— ๋”ฐ๋ผ ํŽ˜์ด์ง€ ์‹œํ˜„์ด ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ค‘๊ฐ„ ํ‰๊ฐ€๋•Œ๋Š” ์ผ์ • ์ƒ์„ธ ๋ฐ ์‹ ์ฒญ ํŽ˜์ด์ง€๋Š” ํ•˜๋“œ์ฝ”๋”ฉํ•œ ๋ฐ์ดํ„ฐ๋กœ ํ‰๊ฐ€ ๋ฐ›๊ณ , ์ดํ›„ ์ด PR์„ ๋จธ์ง€ํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.(์˜ค๋Š˜ 3์‹œ ์ „์— ์—ฐ๊ฒฐ์ด ์ž˜ ๋ ์ง€ ๋ชจ๋ฅด๊ฒ ๋„ค์š”...) --------- Co-authored-by: PARK Junyoung <[email protected]>
1 parent b659ec9 commit 676011d

File tree

12 files changed

+519
-248
lines changed

12 files changed

+519
-248
lines changed

โ€Žsrc/api/apiClient.tsโ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ import useAuthStore from '../hooks/useAuthStore';
33

44
// ๊ณตํ†ต ์„ค์ •์„ ๊ฐ€์ง„ axios ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
55
const apiClient = axios.create({
6-
baseURL: '/api', // ๋ชจ๋“  ์š”์ฒญ์˜ ๊ธฐ๋ณธ ๊ฒฝ๋กœ
6+
baseURL: 'http://3.35.89.220/api/v1', // ๋ชจ๋“  ์š”์ฒญ์˜ ๊ธฐ๋ณธ ๊ฒฝ๋กœ
77
timeout: 5000,
8+
headers: { 'Content-Type': 'application/json' },
89
});
910

1011
// ์š”์ฒญ ์ธํ„ฐ์…‰ํ„ฐ: ๋ชจ๋“  ์š”์ฒญ ์ง์ „์— ์‹คํ–‰๋จ

โ€Žsrc/api/event.tsโ€Ž

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type {
2+
EventDetailResponse,
3+
JoinEventRequest,
4+
JoinEventResponse,
5+
RegistrationListResponse,
6+
} from '../types/event';
7+
import apiClient from './apiClient';
8+
9+
// ์ผ์ • ์ƒ์„ธ ์‘๋‹ต (GET /api/events/:id)
10+
export const getEventDetail = async (id: string) => {
11+
const response = await apiClient.get<EventDetailResponse>(`/events/${id}`);
12+
return {
13+
data: response.data,
14+
status: response.status,
15+
};
16+
};
17+
18+
// ์ฐธ์—ฌ์ž ๋ช…๋‹จ (GET /api/events/:id/registrations)
19+
export const getRegistrations = async (id: string) => {
20+
const response = await apiClient.get<RegistrationListResponse>(
21+
`/events/${id}/registrations`
22+
);
23+
return response.data;
24+
};
25+
26+
// ์ฐธ์—ฌ ์‹ ์ฒญ (POST /api/events/:id/registrations)
27+
export const registerEvent = async (id: string, data: JoinEventRequest) => {
28+
const response = await apiClient.post<JoinEventResponse>(
29+
`/events/${id}/registrations`,
30+
data
31+
);
32+
return {
33+
data: response.data,
34+
status: response.status,
35+
};
36+
};
37+
38+
// ์ด๋ฒคํŠธ ์‹ ์ฒญ ์ทจ์†Œ(POST /api/v1/events/{eventId}/registrations/{registrationId}/cancel)
39+
// export const cancelRegistration = async (
40+
// eventId: string,
41+
// registrationId: number,
42+
// token: string
43+
// ) => {
44+
// const response = await apiClient.post(
45+
// `/events/${eventId}/registrations/${registrationId}/cancel`,
46+
// null, // body๋Š” ๋น„์šฐ๊ณ  query parameter๋กœ ์ „๋‹ฌ
47+
// { params: { token } }
48+
// );
49+
// return response.data;
50+
// };

โ€Žsrc/components/EventDetailContent.tsxโ€Ž

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { ScrollArea } from '@/components/ui/scroll-area';
22
import { useNavigate } from 'react-router';
3-
import type { Events } from '../types/schema';
3+
import type { Event } from '../types/event';
44
import { formatEventDate } from '../utils/date';
55

6-
interface Props {
7-
schedule: Events;
6+
interface EventDetailContentProps {
7+
schedule: Event;
8+
currentParticipants: number;
89
}
910

1011
// ์•„์ด์ฝ˜
@@ -23,15 +24,18 @@ const IconChevronLeft = () => (
2324
</svg>
2425
);
2526

26-
export default function EventDetailContent({ schedule }: Props) {
27+
export default function EventDetailContent({
28+
schedule,
29+
currentParticipants,
30+
}: EventDetailContentProps) {
2731
const navigate = useNavigate();
2832

2933
return (
3034
<div className="w-full flex flex-col items-start gap-10">
3135
{/* 1. ์ผ์‹œ ๋ฐ ์žฅ์†Œ */}
3236
<div className="text-left space-y-3 w-full">
3337
<p className="text-lg sm:text-xl font-bold text-black">
34-
์ผ์‹œ {formatEventDate(schedule.start_at)}
38+
์ผ์‹œ {formatEventDate(schedule.startAt)}
3539
</p>
3640
<p className="text-lg sm:text-xl font-bold text-black">
3741
์žฅ์†Œ {schedule.location || '๋ฏธ์ •'}
@@ -40,12 +44,12 @@ export default function EventDetailContent({ schedule }: Props) {
4044

4145
{/* 2. ์‹ ์ฒญ ํ˜„ํ™ฉ ๋ฒ„ํŠผ */}
4246
<button
43-
onClick={() => navigate('guests')}
47+
onClick={() => navigate(`/event/${schedule.id}/guests`)}
4448
className="flex items-center text-lg font-bold group hover:opacity-70 transition-opacity"
4549
>
4650
{schedule.capacity}๋ช… ์ค‘{' '}
4751
<span className="text-black ml-2 font-extrabold">
48-
{/* ์‹ ์ฒญ ์ธ์› ํ•„๋“œ ํ•„์š” */}8๋ช… ์‹ ์ฒญ
52+
{currentParticipants}๋ช… ์‹ ์ฒญ
4953
</span>
5054
<div className="rotate-180 ml-2 group-hover:translate-x-1 transition-transform text-black">
5155
<IconChevronLeft />
@@ -61,8 +65,8 @@ export default function EventDetailContent({ schedule }: Props) {
6165

6266
{/* 4. ๋งˆ๊ฐ ์ •๋ณด (๋ฒ„ํŠผ ์ƒ๋‹จ ๋ฌธ๊ตฌ) */}
6367
<p className="text-lg font-bold text-black">
64-
{schedule.registration_deadline
65-
? `${formatEventDate(schedule.registration_deadline)} ๋ชจ์ง‘ ๋งˆ๊ฐ`
68+
{schedule.registrationDeadline
69+
? `${formatEventDate(schedule.registrationDeadline)} ๋ชจ์ง‘ ๋งˆ๊ฐ`
6670
: '์ƒ์‹œ ๋ชจ์ง‘'}
6771
</p>
6872
</div>

โ€Žsrc/components/GuestSummaryList.tsxโ€Ž

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
22
import { Button } from '@/components/ui/button';
3+
import type { Guest } from '@/types/event';
34
import { useNavigate } from 'react-router';
45

5-
interface Guest {
6-
name: string;
7-
img?: string;
8-
}
9-
106
interface Props {
117
guests: Guest[];
128
totalCount: number;
@@ -42,13 +38,13 @@ export default function GuestSummaryList({
4238
{guests.map((p, idx) => (
4339
<div key={idx} className="flex flex-col items-center gap-4">
4440
<Avatar className="w-16 h-16 border-none">
45-
<AvatarImage src={p.img} />
41+
<AvatarImage src={p.guestEmail ?? undefined} />
4642
<AvatarFallback className="bg-black text-white text-xs">
47-
{p.name.slice(0, 2)}
43+
{p.guestName?.slice(0, 2)}
4844
</AvatarFallback>
4945
</Avatar>
5046
<span className="text-sm font-bold text-gray-700 truncate w-full text-center">
51-
{p.name}
47+
{p.guestName}
5248
</span>
5349
</div>
5450
))}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { isAxiosError } from 'axios';
2+
import { useCallback, useState } from 'react';
3+
import { toast } from 'sonner';
4+
import { getEventDetail, getRegistrations, registerEvent } from '../api/event';
5+
import type { Event, Guest, JoinEventRequest } from '../types/event';
6+
7+
type EventFetchStatus =
8+
| 'GUEST'
9+
| 'ADMIN'
10+
| 'REGISTERED'
11+
| 'NOT_FOUND'
12+
| 'ERROR';
13+
14+
export default function useEventDetail() {
15+
const [loading, setLoading] = useState<boolean>(false);
16+
const [event, setEvent] = useState<Event | null>(null);
17+
const [registrations, setRegistrations] = useState<Guest[]>([]);
18+
19+
// 1. ์ผ์ • ์ƒ์„ธ ์ •๋ณด ๋กœ๋“œ ํ•ธ๋“ค๋Ÿฌ
20+
const handleFetchDetail = useCallback(
21+
async (id: string): Promise<EventFetchStatus> => {
22+
setLoading(true);
23+
try {
24+
const [eventRes, guestsRes] = await Promise.all([
25+
getEventDetail(id),
26+
getRegistrations(id),
27+
]);
28+
setEvent(eventRes.data);
29+
setRegistrations(guestsRes);
30+
31+
if (eventRes.status === 202) return 'ADMIN';
32+
if (eventRes.status === 204) return 'REGISTERED';
33+
return 'GUEST';
34+
} catch (error: unknown) {
35+
if (isAxiosError(error)) {
36+
if (error.response?.status === 404) return 'NOT_FOUND';
37+
}
38+
console.error('Fetch event detail failed:', error);
39+
toast.error('์ผ์ • ์ •๋ณด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.');
40+
return 'ERROR';
41+
} finally {
42+
setLoading(false);
43+
}
44+
},
45+
[]
46+
);
47+
48+
// 2. ์ฐธ์—ฌ์ž ์ „์ฒด ๋ช…๋‹จ ๋กœ๋“œ ํ•ธ๋“ค๋Ÿฌ
49+
const handleFetchRegistrations = useCallback(async (id: string) => {
50+
setLoading(true);
51+
try {
52+
const data = await getRegistrations(id);
53+
setRegistrations(data);
54+
} catch (error: unknown) {
55+
console.error('Fetch registrations error:', error);
56+
toast.error('์ฐธ์—ฌ์ž ๋ช…๋‹จ์„ ๋ถˆ๋Ÿฌ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค.');
57+
} finally {
58+
setLoading(false);
59+
}
60+
}, []);
61+
62+
// 3. ์ฐธ์—ฌ ์‹ ์ฒญ ๋กœ์ง ํ•ธ๋“ค๋Ÿฌ
63+
const handleJoinEvent = async (
64+
id: string,
65+
data: JoinEventRequest
66+
): Promise<boolean> => {
67+
setLoading(true);
68+
try {
69+
const { status } = await registerEvent(id, data);
70+
return status === 200 || status === 201;
71+
} catch (error: unknown) {
72+
if (isAxiosError(error) && error.response?.status === 409) {
73+
toast.error('์ด๋ฏธ ์‹ ์ฒญ์ด ์™„๋ฃŒ๋œ ๋ชจ์ž„์ž…๋‹ˆ๋‹ค.');
74+
} else {
75+
toast.error('์‹ ์ฒญ ์ฒ˜๋ฆฌ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.');
76+
}
77+
console.error('Join event error:', error);
78+
return false;
79+
} finally {
80+
setLoading(false);
81+
}
82+
};
83+
84+
const confirmedCount = registrations.filter(
85+
(r) => r.status === 'CONFIRMED'
86+
).length;
87+
88+
return {
89+
loading,
90+
event,
91+
registrations,
92+
confirmedCount,
93+
handleFetchDetail,
94+
handleFetchRegistrations,
95+
handleJoinEvent,
96+
};
97+
}

0 commit comments

Comments
ย (0)