"use client" import type React from "react" import { useEffect, useState } from "react" import { Calendar, Users, ChevronDown, CheckCircle2, Clock3, Hourglass, Lock, ShieldAlert, } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Wordmark } from "@/components/wordmark" import { cn } from "@/lib/utils" // --- Types ----------------------------------------------------------------- // Interne visningstyper -- matcher ikke API-et 1:1 (se ApiTournamentInfo/ // ApiSession under og mappingen i PublicTournament), men er det // presentasjonskomponentene under (uendret fra V0) faktisk konsumerer. type Session = { id: string name: string format: string time?: string // ISO-streng, optional } type Sponsor = { id: string name: string url: string } type TournamentInfo = { name: string club: string intro: string startDate?: Date endDate?: Date registered: number capacity?: number // undefined = no cap sessions: Session[] sponsors: Sponsor[] } // "waitlisted" fra API-et vises som "waitlist" her -- se mapOutcome(). type Outcome = "confirmed" | "waitlist" | "pending" // --- API-typer (matcher app/routers/registration.py) ----------------------- type ApiSponsor = { id: string; name: string; url: string | null } type ApiTournamentInfo = { id: string name: string organization_name: string status: string visibility: string description: string | null start_date: string | null end_date: string | null registration_open: boolean registration_deadline: string | null registration_capacity: number | null confirmed_count: number sponsors: ApiSponsor[] } type ApiSession = { id: string name: string format: string scheduled_at: string | null } function mapOutcome(status: string): Outcome { return status === "waitlisted" ? "waitlist" : (status as Outcome) } // --- Formatting helpers ---------------------------------------------------- const DATE_FMT = new Intl.DateTimeFormat("no-NO", { day: "numeric", month: "long", year: "numeric", }) const TIME_FMT = new Intl.DateTimeFormat("no-NO", { hour: "2-digit", minute: "2-digit", }) function formatDateRange(start?: Date, end?: Date) { if (!start) return "Ingen datoer satt" if (!end || start.toDateString() === end.toDateString()) { return DATE_FMT.format(start) } return `${DATE_FMT.format(start)} – ${DATE_FMT.format(end)}` } // --- Root ------------------------------------------------------------------ export function PublicTournament({ tournamentId }: { tournamentId: string }) { const [info, setInfo] = useState(null) const [sessions, setSessions] = useState([]) const [loading, setLoading] = useState(true) const [accessError, setAccessError] = useState(null) useEffect(() => { let cancelled = false async function load() { try { const res = await fetch(`/public/tournaments/${tournamentId}`, { credentials: "include" }) if (res.status === 403 || res.status === 404) { const body = await res.json().catch(() => null) if (!cancelled) { setAccessError( body?.detail?.message ?? (res.status === 404 ? "Turneringen finnes ikke." : "Du har ikke tilgang til denne turneringen."), ) } return } if (!res.ok) throw new Error(`tournament: ${res.status}`) const data: ApiTournamentInfo = await res.json() if (cancelled) return setInfo(data) const sessionsRes = await fetch(`/public/tournaments/${tournamentId}/sessions`, { credentials: "include", }) if (sessionsRes.ok) { const sessionData: ApiSession[] = await sessionsRes.json() if (!cancelled) { setSessions( sessionData.map((s) => ({ id: s.id, name: s.name, format: s.format, time: s.scheduled_at ?? undefined, })), ) } } } catch { if (!cancelled) setAccessError("Klarte ikke å laste turneringen. Prøv å laste siden på nytt.") } finally { if (!cancelled) setLoading(false) } } void load() return () => { cancelled = true } }, [tournamentId]) if (loading) { return (
) } if (accessError || !info) { return (

{accessError ?? "Turneringen finnes ikke."}

) } const t: TournamentInfo = { name: info.name, club: info.organization_name, intro: info.description ?? "", startDate: info.start_date ? new Date(info.start_date) : undefined, endDate: info.end_date ? new Date(info.end_date) : undefined, registered: info.confirmed_count, capacity: info.registration_capacity ?? undefined, sessions, sponsors: info.sponsors.map((s) => ({ id: s.id, name: s.name, url: s.url ?? "#" })), } return (
{t.intro && (

{t.intro}

)} {info.registration_open ? ( ) : ( )}
) } // --- Banner ---------------------------------------------------------------- function Banner({ name, club }: { name: string; club: string }) { return (
{/* Subtle decorative rings — intentional, part of the brand banner look. */}