"use client" import { useEffect, useRef, useState } from "react" import { Building2, Check, Globe, Loader2, TriangleAlert, Users, } from "lucide-react" import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Switch } from "@/components/ui/switch" export type TournamentSettingsValues = { name: string startDate: string | null // "YYYY-MM-DD", or null if not set endDate: string | null // "YYYY-MM-DD", or null if not set visibility: "public" | "org" | "participants" description: string // "" if empty, never null registrationDeadline: string | null // ISO datetime ("YYYY-MM-DDTHH:mm"), or null registrationCapacity: number | null // null = uncapped registrationOverflowPolicy: "waitlist" | "closed" // only meaningful once a capacity is set registrationRequiresApproval: boolean } const VISIBILITY_OPTIONS: { value: TournamentSettingsValues["visibility"] label: string description: string icon: typeof Globe }[] = [ { value: "public", label: "Offentlig", description: "Hvem som helst kan finne turneringen.", icon: Globe, }, { value: "org", label: "Organisasjonen", description: "Bare medlemmer av organisasjonen din.", icon: Building2, }, { value: "participants", label: "Kun deltakere", description: "Bare de som allerede er påmeldt.", icon: Users, }, ] const inputBase = "h-11 w-full rounded-xl border border-border bg-background px-3 text-base text-foreground shadow-sm transition-colors placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50" export function TournamentSettingsCard({ values, onSave, }: { values: TournamentSettingsValues onSave: (next: TournamentSettingsValues) => Promise }) { const [draft, setDraft] = useState(values) const [status, setStatus] = useState<"idle" | "saving" | "saved" | "error">("idle") const [errorMessage, setErrorMessage] = useState(null) // Only re-sync the draft from props when the SAVED values genuinely change // (e.g. after a real save round-trip), never on an incidental parent // re-render that hands us a new object with identical contents. const lastSyncedRef = useRef(JSON.stringify(values)) useEffect(() => { const serialized = JSON.stringify(values) if (serialized !== lastSyncedRef.current) { lastSyncedRef.current = serialized setDraft(values) } }, [values]) const savedFadeRef = useRef | null>(null) useEffect(() => { return () => { if (savedFadeRef.current) clearTimeout(savedFadeRef.current) } }, []) function patch( key: K, value: TournamentSettingsValues[K], ) { setDraft((prev) => ({ ...prev, [key]: value })) // Any edit clears a lingering "saved"/"error" indicator. setStatus((s) => (s === "saving" ? s : "idle")) setErrorMessage(null) } const nameEmpty = draft.name.trim() === "" const dirty = JSON.stringify(draft) !== JSON.stringify(values) const capacitySet = draft.registrationCapacity != null const saving = status === "saving" const canSave = dirty && !nameEmpty && !saving async function handleSave() { if (!canSave) return setStatus("saving") setErrorMessage(null) try { // Normalize: trim the name; overflow policy is irrelevant without a cap. const next: TournamentSettingsValues = { ...draft, name: draft.name.trim(), } await onSave(next) lastSyncedRef.current = JSON.stringify(next) setDraft(next) setStatus("saved") if (savedFadeRef.current) clearTimeout(savedFadeRef.current) savedFadeRef.current = setTimeout(() => setStatus("idle"), 2600) } catch (err) { setStatus("error") setErrorMessage( err instanceof Error ? err.message : "Kunne ikke lagre. Prøv igjen.", ) } } return (

Grunnleggende

Navn, datoer, synlighet og påmelding for turneringen.

{/* Navn — spans full width */}
patch("name", e.target.value)} aria-required="true" aria-invalid={nameEmpty} aria-describedby={nameEmpty ? "tsc-name-error" : undefined} className="h-11 rounded-xl text-base" placeholder="F.eks. Klubbmesterskapet 2026" /> {nameEmpty && (

Navn er påkrevd.

)}
{/* Datoer — one connected range control */}
Datoer
patch("startDate", e.target.value || null)} className={inputBase} />
patch("endDate", e.target.value || null)} className={inputBase} />
{/* Synlighet — real radio group, spans full width */}
Synlighet
{VISIBILITY_OPTIONS.map((o) => { const selected = draft.visibility === o.value const Icon = o.icon return ( ) })}
{/* Beskrivelse — spans full width */}