194 lines
7.1 KiB
TypeScript

// app/events/[id]/page.tsx
// Event detail page with registration form
import { query } from '@/lib/db';
import { Event, EventRegistration } from '@/types/racing';
import { notFound } from 'next/navigation';
import { TrophyIcon, MapPinIcon, UsersIcon } from '@/components/ui/icons';
import EventRegistrationForm from '@/components/events/EventRegistrationForm';
async function getEvent(eventId: number): Promise<Event | null> {
const sql = `
SELECT e.*, COUNT(er.registration_id) as registrations_count
FROM events e
LEFT JOIN event_registrations er ON e.event_id = er.event_id AND er.status = 'REGISTERED'
WHERE e.event_id = $1
GROUP BY e.event_id
`;
const rows = await query(sql, [eventId]);
return rows.length > 0 ? rows[0] as Event : null;
}
async function getEventRegistrations(eventId: number): Promise<EventRegistration[]> {
const sql = `
SELECT
er.*,
u.driver_name
FROM event_registrations er
JOIN users u ON er.driver_guid = u.driver_guid
WHERE er.event_id = $1 AND er.status = 'REGISTERED'
ORDER BY er.registration_date ASC
`;
const rows = await query(sql, [eventId]);
return rows as EventRegistration[];
}
function formatDate(date: Date): string {
return new Date(date).toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
export default async function EventDetailPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const eventId = parseInt(id);
const event: any = await getEvent(eventId);
if (!event) {
notFound();
}
const registrations = await getEventRegistrations(eventId);
const isOpen = event.event_status === 'OPEN';
const isFull = event.registrations_count >= event.max_participants;
const deadlinePassed = event.registration_deadline && new Date(event.registration_deadline) < new Date();
const canRegister = isOpen && !isFull && !deadlinePassed;
return (
<>
{/* Hero */}
<div className="relative border-b border-white/10 grid-overlay">
<div className="max-w-7xl mx-auto px-6 py-16">
<div className="space-y-4">
<div className="inline-flex items-center space-x-2 px-3 py-1 border border-white/20 bg-black">
<TrophyIcon className="w-4 h-4" />
<span className="text-xs font-medium tracking-wider">EVENT DETAILS</span>
</div>
<h1 className="text-5xl font-bold tracking-tight">
{event.event_name}
</h1>
<p className="text-white/70 text-lg max-w-3xl">
{event.event_description}
</p>
</div>
{/* Event Info Cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mt-12">
<InfoCard label="TRACK" value={event.event_track} icon={<MapPinIcon className="w-6 h-6" />} />
<InfoCard label="DATE" value={formatDate(event.event_date)} icon="📅" />
<InfoCard label="DURATION" value={`${event.event_duration || 'TBD'} min`} icon="⏱️" />
<InfoCard
label="PARTICIPANTS"
value={`${event.registrations_count}/${event.max_participants}`}
icon={<UsersIcon className="w-6 h-6" />}
/>
</div>
</div>
</div>
{/* Main Content */}
<div className="max-w-7xl mx-auto px-6 py-12">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Left Column - Registration */}
<div className="lg:col-span-2 space-y-6">
{/* Registration Form */}
{canRegister ? (
<div className="border border-white/10 bg-black p-6">
<h2 className="text-2xl font-bold tracking-tight mb-6">REGISTER FOR EVENT</h2>
<EventRegistrationForm eventId={event.event_id} />
</div>
) : (
<div className="border border-white/10 bg-black p-6">
<h2 className="text-2xl font-bold tracking-tight mb-4">REGISTRATION CLOSED</h2>
<p className="text-white/60">
{isFull && 'This event is full.'}
{deadlinePassed && !isFull && 'Registration deadline has passed.'}
{event.event_status === 'CLOSED' && !isFull && !deadlinePassed && 'Registration is closed for this event.'}
</p>
</div>
)}
{/* Event Rules */}
{event.event_rules && (
<div className="border border-white/10 bg-black p-6 topo-lines-dense">
<h3 className="text-xl font-bold tracking-tight mb-4">EVENT RULES</h3>
<div className="text-white/70 text-sm space-y-2 whitespace-pre-line">
{event.event_rules}
</div>
</div>
)}
</div>
{/* Right Column - Registered Participants */}
<div className="lg:col-span-1">
<div className="border border-white/10 bg-black p-6 sticky top-20">
<h3 className="text-xl font-bold tracking-tight mb-4">
REGISTERED DRIVERS ({event.registrations_count})
</h3>
<div className="space-y-3 max-h-[600px] overflow-y-auto">
{registrations.length === 0 ? (
<p className="text-white/40 text-sm">No registrations yet</p>
) : (
registrations.map((reg: any, index: number) => (
<div key={reg.registration_id} className="border border-white/10 p-3">
<div className="flex items-start justify-between mb-2">
<div className="flex items-center space-x-2">
<span className="text-xs font-bold text-white/40">
#{String(index + 1).padStart(2, '0')}
</span>
<span className="font-semibold text-sm">{reg.driver_name}</span>
</div>
</div>
<div className="space-y-1 text-xs text-white/60">
<div>Car: <span className="text-white/80 font-mono">{reg.car_model}</span></div>
{reg.car_skin && <div>Skin: <span className="text-white/80">{reg.car_skin}</span></div>}
{reg.team_name && <div>Team: <span className="text-white/80">{reg.team_name}</span></div>}
</div>
</div>
))
)}
</div>
</div>
</div>
</div>
</div>
</>
);
}
function InfoCard({
label,
value,
icon
}: {
label: string;
value: string;
icon: React.ReactNode;
}) {
return (
<div className="border border-white/10 p-4 sharp-border">
<div className="flex items-center justify-between mb-2">
<span className="text-xs font-bold tracking-wider text-white/60">{label}</span>
<div className="text-white/40 text-sm">
{icon}
</div>
</div>
<div className="text-lg font-bold tracking-tight">
{value}
</div>
</div>
);
}