Skip to content

Commit 6e3c489

Browse files
jun-0411young-52
andauthored
✨ Add a event home for guests (#28)
### πŸ“ μž‘μ—… λ‚΄μš© - μ°Έμ—¬μžμ˜ 일정 상세 νŽ˜μ΄μ§€λ₯Ό λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€. - 일정 상세 내역에 λŒ€ν•œ μ€‘λ³΅λœ 뢀뢄을 μ»΄ν¬λ„ŒνŠΈλ‘œ λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€. ### πŸ“Έ μŠ€ν¬λ¦°μƒ· (선택) <img width="1886" height="1520" alt="image" src="https://github.com/user-attachments/assets/f71530ab-608c-4251-902d-9b01e8d0d078" /> ### πŸš€ 리뷰 μš”κ΅¬μ‚¬ν•­ (선택) --------- Co-authored-by: PARK Junyoung <bloomwayz@snu.ac.kr>
1 parent 46e21b1 commit 6e3c489

File tree

7 files changed

+257
-107
lines changed

7 files changed

+257
-107
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { ScrollArea } from '@/components/ui/scroll-area';
2+
import { useNavigate } from 'react-router';
3+
import type { Events } from '../types/schema';
4+
import { formatEventDate } from '../utils/date';
5+
6+
interface Props {
7+
schedule: Events;
8+
}
9+
10+
// μ•„μ΄μ½˜
11+
const IconChevronLeft = () => (
12+
<svg
13+
width="24"
14+
height="24"
15+
viewBox="0 0 24 24"
16+
fill="none"
17+
stroke="currentColor"
18+
strokeWidth="2"
19+
strokeLinecap="round"
20+
strokeLinejoin="round"
21+
>
22+
<path d="m15 18-6-6 6-6" />
23+
</svg>
24+
);
25+
26+
export default function EventDetailContent({ schedule }: Props) {
27+
const navigate = useNavigate();
28+
29+
return (
30+
<div className="w-full flex flex-col items-start gap-10">
31+
{/* 1. μΌμ‹œ 및 μž₯μ†Œ */}
32+
<div className="text-left space-y-3 w-full">
33+
<p className="text-lg sm:text-xl font-bold text-black">
34+
μΌμ‹œ {formatEventDate(schedule.start_at)}
35+
</p>
36+
<p className="text-lg sm:text-xl font-bold text-black">
37+
μž₯μ†Œ {schedule.location || 'λ―Έμ •'}
38+
</p>
39+
</div>
40+
41+
{/* 2. μ‹ μ²­ ν˜„ν™© λ²„νŠΌ */}
42+
<button
43+
onClick={() => navigate('guests')}
44+
className="flex items-center text-lg font-bold group hover:opacity-70 transition-opacity"
45+
>
46+
{schedule.capacity}λͺ… 쀑{' '}
47+
<span className="text-black ml-2 font-extrabold">
48+
{/* μ‹ μ²­ 인원 ν•„λ“œ ν•„μš” */}8λͺ… μ‹ μ²­
49+
</span>
50+
<div className="rotate-180 ml-2 group-hover:translate-x-1 transition-transform text-black">
51+
<IconChevronLeft />
52+
</div>
53+
</button>
54+
55+
{/* 3. 상세 μ„€λͺ… */}
56+
<ScrollArea className="h-40 w-full rounded-md border-none">
57+
<p className="text-base text-gray-500 leading-relaxed whitespace-pre-wrap pr-4">
58+
{schedule.description}
59+
</p>
60+
</ScrollArea>
61+
62+
{/* 4. 마감 정보 (λ²„νŠΌ 상단 문ꡬ) */}
63+
<p className="text-lg font-bold text-black">
64+
{schedule.registration_deadline
65+
? `${formatEventDate(schedule.registration_deadline)} λͺ¨μ§‘ 마감`
66+
: 'μƒμ‹œ λͺ¨μ§‘'}
67+
</p>
68+
</div>
69+
);
70+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
2+
import { Button } from '@/components/ui/button';
3+
import { useNavigate } from 'react-router';
4+
5+
interface Guest {
6+
name: string;
7+
img?: string;
8+
}
9+
10+
interface Props {
11+
guests: Guest[];
12+
totalCount: number;
13+
eventId: number | null;
14+
}
15+
16+
export default function GuestSummaryList({
17+
guests,
18+
totalCount,
19+
eventId,
20+
}: Props) {
21+
const navigate = useNavigate();
22+
23+
return (
24+
<div className="w-full">
25+
{/* μ°Έμ—¬μž λͺ…단 헀더 */}
26+
<div className="flex justify-between items-center mb-8 px-2">
27+
<h2 className="font-bold text-2xl text-black">
28+
μ°Έμ—¬μž λͺ…단({totalCount})
29+
</h2>
30+
<Button
31+
variant="link"
32+
onClick={() => navigate(`/event/${eventId}/guests`)}
33+
className="text-base font-bold text-black p-0 h-auto"
34+
>
35+
더보기
36+
</Button>
37+
</div>
38+
39+
{/* λͺ…단 λ°•μŠ€ */}
40+
<div className="border-2 border-black p-10 bg-white">
41+
<div className="grid grid-cols-4 gap-8">
42+
{guests.map((p, idx) => (
43+
<div key={idx} className="flex flex-col items-center gap-4">
44+
<Avatar className="w-16 h-16 border-none">
45+
<AvatarImage src={p.img} />
46+
<AvatarFallback className="bg-black text-white text-xs">
47+
{p.name.slice(0, 2)}
48+
</AvatarFallback>
49+
</Avatar>
50+
<span className="text-sm font-bold text-gray-700 truncate w-full text-center">
51+
{p.name}
52+
</span>
53+
</div>
54+
))}
55+
</div>
56+
</div>
57+
</div>
58+
);
59+
}

β€Žsrc/routes.tsβ€Ž

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import EventRegister from './routes/EventRegister';
55
import EventRegisterSuccess from './routes/EventRegisterSuccess';
66
import Guests from './routes/Guests';
77
import Home from './routes/Home';
8+
import JoinEvent from './routes/JoinEvent';
89
import Login from './routes/Login';
910
import RegisterChoice from './routes/RegisterChoice';
1011
import RegisterForm from './routes/RegisterForm';
@@ -34,7 +35,7 @@ export const router = createBrowserRouter([
3435
path: '/join/:id',
3536
Component: RootLayout,
3637
children: [
37-
{ index: true, Component: Event },
38+
{ index: true, Component: JoinEvent },
3839
{ path: 'register', Component: EventRegister },
3940
{ path: 'success', Component: EventRegisterSuccess },
4041
],

β€Žsrc/routes/Event.tsxβ€Ž

Lines changed: 9 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useEffect, useState } from 'react';
22
import { useNavigate, useParams } from 'react-router';
33
import { toast } from 'sonner';
4+
import EventDetailContent from '../components/EventDetailContent';
5+
import GuestSummaryList from '../components/GuestSummaryList';
46
import type { Events } from '../types/schema';
5-
import { formatEventDate } from '../utils/date';
67
// import useAuth from '../hooks/useAuth';
78

89
// shadcn UI μ»΄ν¬λ„ŒνŠΈ
@@ -17,15 +18,13 @@ import {
1718
AlertDialogTitle,
1819
AlertDialogTrigger,
1920
} from '@/components/ui/alert-dialog';
20-
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
2121
import { Button } from '@/components/ui/button';
2222
import {
2323
DropdownMenu,
2424
DropdownMenuContent,
2525
DropdownMenuItem,
2626
DropdownMenuTrigger,
2727
} from '@/components/ui/dropdown-menu';
28-
import { ScrollArea } from '@/components/ui/scroll-area';
2928

3029
// SVG μ•„μ΄μ½˜ μ»΄ν¬λ„ŒνŠΈ
3130
const IconChevronLeft = () => (
@@ -204,45 +203,11 @@ export default function Event() {
204203

205204
{/* 2. 메인 μ½˜ν…μΈ */}
206205
<div className="max-w-2xl min-w-[320px] mx-auto w-[90%] px-6 flex flex-col items-start gap-10">
207-
{/* 일정 정보 (μ™Όμͺ½ μ •λ ¬) */}
208-
<div className="text-left space-y-3 w-full">
209-
<p className="text-lg sm:text-xl font-bold text-black">
210-
μΌμ‹œ {formatEventDate(schedule.start_at)}
211-
</p>
212-
<p className="text-lg sm:text-xl font-bold text-black">
213-
μž₯μ†Œ {schedule.location || 'λ―Έμ •'}
214-
</p>
215-
</div>
216-
217-
{/* μ‹ μ²­ ν˜„ν™© λ²„νŠΌ */}
218-
<button
219-
onClick={() => navigate('guests')}
220-
className="flex items-center text-lg font-bold group hover:opacity-70 transition-opacity"
221-
>
222-
{schedule.capacity}λͺ… 쀑{' '}
223-
<span className="text-black ml-2 font-extrabold">
224-
{/* μ‹ μ²­ 인원 ν•„λ“œ ν•„μš” */} 8λͺ… μ‹ μ²­
225-
</span>
226-
<div className="rotate-180 ml-2 group-hover:translate-x-1 transition-transform text-black">
227-
<IconChevronLeft />
228-
</div>
229-
</button>
230-
231-
{/* 상세 μ„€λͺ… */}
232-
<ScrollArea className="h-40 w-full rounded-md border-none">
233-
<p className="text-base text-gray-500 leading-relaxed whitespace-pre-wrap pr-4">
234-
{schedule.description}
235-
</p>
236-
</ScrollArea>
206+
{/* 일정 정보 */}
207+
<EventDetailContent schedule={schedule} />
237208

238209
{/* λͺ¨μ§‘ 마감 및 링크 블둝 */}
239210
<div className="w-full flex flex-col items-start">
240-
<p className="text-lg font-bold mb-6 text-black">
241-
{schedule.registration_deadline
242-
? `${formatEventDate(schedule.registration_deadline)} λͺ¨μ§‘ 마감`
243-
: 'μƒμ‹œ λͺ¨μ§‘'}
244-
</p>
245-
246211
<div className="w-full bg-[#F8F9FA] rounded-3xl p-10 flex flex-col items-center gap-6 border border-gray-100">
247212
<div className="flex flex-col items-center gap-4 text-center">
248213
<div className="text-gray-400 scale-125">
@@ -263,40 +228,11 @@ export default function Event() {
263228
</div>
264229

265230
{/* μ°Έμ—¬μž λͺ…단 μ„Ήμ…˜ */}
266-
<div className="w-full">
267-
<div className="flex justify-between items-center mb-8 px-2">
268-
<h2 className="font-bold text-2xl text-black">
269-
{/* μ‹ μ²­ 인원 ν•„λ“œ ν•„μš” */}
270-
μ°Έμ—¬μž λͺ…단(8)
271-
</h2>
272-
<Button
273-
variant="link"
274-
onClick={() => navigate('guests')}
275-
className="text-base font-bold text-black p-0 h-auto"
276-
>
277-
더보기
278-
</Button>
279-
</div>
280-
281-
{/* 링크 볡사 블둝과 같은 λ„ˆλΉ„μ˜ λͺ…단 λ°•μŠ€ */}
282-
<div className="border-2 border-black p-10 bg-white">
283-
<div className="grid grid-cols-4 gap-8">
284-
{displayGuests.map((p, idx) => (
285-
<div key={idx} className="flex flex-col items-center gap-4">
286-
<Avatar className="w-16 h-16 border-none">
287-
<AvatarImage src={p.img} />
288-
<AvatarFallback className="bg-black text-white text-xs">
289-
{p.name}
290-
</AvatarFallback>
291-
</Avatar>
292-
<span className="text-sm font-bold text-gray-700">
293-
{p.name}
294-
</span>
295-
</div>
296-
))}
297-
</div>
298-
</div>
299-
</div>
231+
<GuestSummaryList
232+
guests={displayGuests}
233+
totalCount={8}
234+
eventId={schedule.id}
235+
/>
300236
</div>
301237
</div>
302238
);

β€Žsrc/routes/EventRegisterSuccess.tsxβ€Ž

Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Button } from '@/components/ui/button';
2-
import { ScrollArea } from '@/components/ui/scroll-area';
32
import { useEffect, useState } from 'react';
43
import { useNavigate, useParams } from 'react-router';
4+
import EventDetailContent from '../components/EventDetailContent';
55
import type { Events } from '../types/schema';
6-
import { formatEventDate } from '../utils/date';
76

87
// shadcn UI μ»΄ν¬λ„ŒνŠΈ
98
import {
@@ -45,7 +44,7 @@ export default function EventRegisterSuccess() {
4544
id: Number(id) || 1,
4645
title: '제2회 기획 μ„Έλ―Έλ‚˜',
4746
description:
48-
'일정 μ„€λͺ… 일정섀λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…일정 μ„€λͺ… 일정 μ„€λͺ… 일정섀λͺ…',
47+
'일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… 일정섀λͺ… ...',
4948
location: 'μ„œμšΈλŒ€',
5049
start_at: '2026-02-02T18:00:00Z',
5150
end_at: '2026-02-02T20:00:00Z',
@@ -87,34 +86,10 @@ export default function EventRegisterSuccess() {
8786
<h1 className="text-2xl sm:text-3xl font-bold flex-1 truncate text-black">
8887
{schedule.title}
8988
</h1>
90-
<p className="text-lg sm:text-xl font-bold text-black">
91-
μΌμ‹œ {formatEventDate(schedule.start_at)}
92-
</p>
93-
<p className="text-lg sm:text-xl font-bold text-black">
94-
μž₯μ†Œ {schedule.location || 'λ―Έμ •'}
95-
</p>
9689
</div>
9790

98-
{/* μ‹ μ²­ ν˜„ν™© λ²„νŠΌ */}
99-
<button
100-
onClick={() => navigate('guests')}
101-
className="flex items-center text-lg font-bold group hover:opacity-70 transition-opacity"
102-
>
103-
{schedule.capacity}λͺ… 쀑{' '}
104-
<span className="text-black ml-2 font-extrabold">
105-
{/* μ‹ μ²­ 인원 ν•„λ“œ ν•„μš” */} 8λͺ… μ‹ μ²­
106-
</span>
107-
<div className="rotate-180 ml-2 group-hover:translate-x-1 transition-transform text-black">
108-
<IconChevronLeft />
109-
</div>
110-
</button>
111-
112-
{/* 상세 μ„€λͺ… */}
113-
<ScrollArea className="h-40 w-full rounded-md border-none">
114-
<p className="text-base text-gray-500 leading-relaxed whitespace-pre-wrap pr-4">
115-
{schedule.description}
116-
</p>
117-
</ScrollArea>
91+
{/* 일정 정보 */}
92+
<EventDetailContent schedule={schedule} />
11893

11994
{/* μ·¨μ†Œ λ²„νŠΌ */}
12095
<AlertDialog>
@@ -123,7 +98,7 @@ export default function EventRegisterSuccess() {
12398
variant="secondary"
12499
className="w-full h-16 rounded-2xl bg-[#333333] hover:bg-black text-xl font-bold text-white transition-all shadow-lg active:scale-[0.98]"
125100
>
126-
κ°•μ œμ·¨μ†Œ
101+
μ·¨μ†Œν•˜κΈ°
127102
</Button>
128103
</AlertDialogTrigger>
129104
<AlertDialogContent>

β€Žsrc/routes/Guests.tsxβ€Ž

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ const IconChevronLeft = () => (
3636
interface GuestResponse {
3737
registration_id: number;
3838
name: string;
39-
email: string;
39+
email: string | null;
4040
profile_image: string | null;
4141
}
4242

@@ -77,7 +77,7 @@ export default function Guests() {
7777
{
7878
registration_id: 5,
7979
name: '이름5',
80-
email: '이메일@example.com',
80+
email: null,
8181
profile_image: null,
8282
},
8383
{
@@ -146,7 +146,9 @@ export default function Guests() {
146146
<span className="text-xl font-bold text-black">
147147
{guest.name}
148148
</span>
149-
<span className="text-gray-400 text-lg">{guest.email}</span>
149+
{guest.email ? (
150+
<span className="text-gray-400 text-lg">{guest.email}</span>
151+
) : null}
150152
</div>
151153
</div>
152154

0 commit comments

Comments
Β (0)