"use client" import type React from "react" import { useEffect, useState } from "react" import { useRouter } from "next/navigation" import Link from "next/link" import { Building2, Calendar, ChevronRight, ChevronsUpDown, LogOut, Plus, Trophy, Users, UserCircle, X, Check } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Wordmark } from "@/components/wordmark" import { TournamentCard, type Tournament } from "@/components/tournament-card" import { TournamentStatusBadge, type TournamentStatus } from "@/components/tournament-status-badge" import { cn } from "@/lib/utils" type MyOrg = { organization_id: string; name: string; role: string } // ADR-031: "Mine runder" -- turneringer brukeren er SPILLER i (rostret på et // lag), på tvers av organisasjoner, uavhengig av organisasjonsmedlemskap. type MyTournament = { organization_id: string organization_name: string tournament_id: string tournament_name: string status: TournamentStatus team_id: string team_name: string team_color: string | null next_session_at: string | null } type Me = { id: string email: string display_name: string preferred_locale: string organizations: MyOrg[] my_tournaments: MyTournament[] } type ApiTournament = { id: string name: string status: TournamentStatus start_date: string | null end_date: string | null } function toTournament(t: ApiTournament): Tournament { return { id: t.id, name: t.name, status: t.status, startDate: t.start_date ?? undefined, endDate: t.end_date ?? undefined, } } // --- Component ------------------------------------------------------------- export function Dashboard() { const router = useRouter() const [me, setMe] = useState(null) const [loadingMe, setLoadingMe] = useState(true) const [activeOrgId, setActiveOrgId] = useState(null) const [tournaments, setTournaments] = useState([]) const [loadingTournaments, setLoadingTournaments] = useState(false) const [error, setError] = useState(null) // Hent innlogget bruker + organisasjonsmedlemskap. Ingen gyldig sesjon -> // tilbake til innloggingssiden (denne siden krever auth). useEffect(() => { let cancelled = false async function loadMe() { try { const res = await fetch("/auth/me", { credentials: "include" }) if (!res.ok) { router.replace("/") return } const data: Me = await res.json() if (cancelled) return setMe(data) setActiveOrgId(data.organizations[0]?.organization_id ?? null) } catch { if (!cancelled) router.replace("/") } finally { if (!cancelled) setLoadingMe(false) } } void loadMe() return () => { cancelled = true } }, [router]) // Turneringer hentes per valgt organisasjon, ikke i /auth/me-kallet. useEffect(() => { if (!activeOrgId) { setTournaments([]) return } let cancelled = false async function loadTournaments() { setLoadingTournaments(true) setError(null) try { const res = await fetch(`/orgs/${activeOrgId}/tournaments`, { credentials: "include" }) if (!res.ok) throw new Error(`tournaments: ${res.status}`) const data: ApiTournament[] = await res.json() if (!cancelled) setTournaments(data.map(toTournament)) } catch { if (!cancelled) setError("Klarte ikke å hente turneringer. Prøv igjen om litt.") } finally { if (!cancelled) setLoadingTournaments(false) } } void loadTournaments() return () => { cancelled = true } }, [activeOrgId]) async function handleCreateOrg(name: string) { setError(null) try { const res = await fetch("/orgs", { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ name }), }) if (!res.ok) throw new Error(`orgs: ${res.status}`) const org: { id: string; name: string; role: string } = await res.json() setMe((prev) => prev ? { ...prev, organizations: [ ...prev.organizations, { organization_id: org.id, name: org.name, role: org.role }, ], } : prev, ) setActiveOrgId(org.id) } catch { setError("Klarte ikke å opprette organisasjonen. Prøv igjen.") } } async function handleCreateTournament(name: string) { if (!activeOrgId) return setError(null) try { const res = await fetch(`/orgs/${activeOrgId}/tournaments`, { method: "POST", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ name }), }) if (!res.ok) throw new Error(`create tournament: ${res.status}`) const created: ApiTournament = await res.json() setTournaments((prev) => [toTournament(created), ...prev]) } catch { setError("Klarte ikke å opprette turneringen. Prøv igjen.") } } async function handleLogout() { try { await fetch("/auth/logout", { method: "POST", credentials: "include" }) } finally { router.replace("/") } } if (loadingMe) { return (
) } if (!me) return null // router.replace("/") er allerede utløst const activeOrg = me.organizations.find((o) => o.organization_id === activeOrgId) ?? null const hasOrg = me.organizations.length > 0 && activeOrg !== null const hasMyTournaments = me.my_tournaments.length > 0 return (
{error && (

{error}

)}
{hasMyTournaments && } {hasOrg && activeOrg ? ( ) : ( )}
) } // --- "Mine runder" (ADR-031) ----------------------------------------------- // Turneringer brukeren er SPILLER i, uavhengig av organisasjonsmedlemskap. // Lenker til den offentlige turnering-siden (fungerer for enhver deltaker, // se ADR-031-utvidelsen av check_visibility) -- IKKE til lag-chat/ // scorekort ennå, som fortsatt krever organisasjonsmedlemskap (kjent, // notert begrensning, se FEATURE_BACKLOG.md). function MyToursSection({ tournaments }: { tournaments: MyTournament[] }) { return (

Mine runder

{tournaments.map((t) => (
) } function formatNextSession(iso: string) { const date = new Date(iso) if (Number.isNaN(date.getTime())) return iso return new Intl.DateTimeFormat("no-NO", { weekday: "short", day: "numeric", month: "short", hour: "2-digit", minute: "2-digit", }).format(date) } // --- State A: no organization --------------------------------------------- function CreateOrganizationState({ onCreate, compact = false, }: { onCreate: (name: string) => void compact?: boolean }) { const [name, setName] = useState("") const valid = name.trim().length >= 2 function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!valid) return onCreate(name.trim()) setName("") } return (
{!compact && (
)}

{compact ? "Opprett din egen organisasjon" : "Du har ingen organisasjon ennå"}

{compact ? "Skal du selv arrangere turneringer? En organisasjon er golfklubben eller bedriften som står bak." : "En organisasjon er golfklubben eller bedriften som arrangerer turneringen. Opprett en for å komme i gang."}

setName(e.target.value)} className="h-14 rounded-2xl text-base" />
) } // --- State B: has organization(s) ----------------------------------------- function OrganizationView({ org, orgs, tournaments, loadingTournaments, onSelectOrg, onCreateTournament, }: { org: MyOrg orgs: MyOrg[] tournaments: Tournament[] loadingTournaments: boolean onSelectOrg: (id: string) => void onCreateTournament: (name: string) => void }) { return (
{orgs.length > 1 ? ( ) : (
Organisasjon {org.name}
)}
{loadingTournaments ? (
) : tournaments.length > 0 ? ( tournaments.map((t) => ( )) ) : ( )}
) } function OrganizationSwitcher({ org, orgs, onSelectOrg, }: { org: MyOrg orgs: MyOrg[] onSelectOrg: (id: string) => void }) { return (
Organisasjon {org.name}
{orgs.map((o) => ( onSelectOrg(o.organization_id)} className="flex cursor-pointer items-center justify-between gap-2 rounded-xl px-3 py-2.5 text-sm font-semibold" > {o.name} {o.organization_id === org.organization_id && ( ))}
) } function NewTournamentControl({ onCreate }: { onCreate: (name: string) => void }) { const [open, setOpen] = useState(false) const [name, setName] = useState("") const valid = name.trim().length >= 2 function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!valid) return onCreate(name.trim()) setName("") setOpen(false) } if (!open) { return ( ) } return (
setName(e.target.value)} onKeyDown={(e) => { if (e.key === "Escape") setOpen(false) }} className="h-10 min-w-0 flex-1 rounded-xl text-base sm:w-56" />
) } function EmptyTournamentState({ orgName }: { orgName: string }) { return (

Ingen turneringer ennå i {orgName}

Trykk på «Ny turnering» for å opprette den første.

) }