Frontend for individuelle turneringer er ferdig håndkodet og grundig browserverifisert — inkludert ekte innlogging, håndregnet HCP-kryssjekk (stemte eksakt), databasebekreftet slagregistrering, og full opprett-fra-bunnen-flyt. Underveis fant og fikset jeg tre reelle feil: Et backend-hull som ville rammet alle brukere: list_tournaments (brukt av dashbordet) manglet de to nye kolonnene i sin egen SELECT-spørring — ga en rå 500 på hele turneringslisten, ikke bare individuelle turneringer. Et layout-problem der turneringsnavnet ble avkuttet på smal mobilskjerm. Manglende form-språk i scorekortet (kun farge, ikke sirkel/firkant som resten av appen bruker). Alle tre er rettet og re-verifisert. Utrullingsplan (denne gangen berører det både backend og frontend): Redeploy teecup_api — for list_tournaments-fiksen (ingen migrasjon, ren kodefiks). Redeploy teecup_frontend — den nye individuell-turnering-skjermen + dashbordets format-valg. Verifiser: begge containere booter rent, /health//dashboard fortsatt 200, teeoff.no upåvirket.
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
"use client"
|
||
|
||
// Velger riktig turnering-skjerm basert på tournament.format_type
|
||
// (ADR-011 lagformat vs. ADR-037 individuell) -- egen, autoritativ henting
|
||
// FØR noe rendres, i stedet for å stole på et query-param-hint (ville brutt
|
||
// for enhver inngang som ikke går via dashbordets akkurat-nå-opprettet-flyt,
|
||
// f.eks. en bokmerket lenke). Samme "hent org-ens turneringsliste og finn
|
||
// egen rad"-mønster som tournament-detail.tsx allerede bruker for join_code.
|
||
|
||
import { useEffect, useState } from "react"
|
||
import { TournamentDetail } from "@/components/tournament-detail"
|
||
import { IndividualTournamentDetail } from "@/components/individual-tournament-detail"
|
||
|
||
export function TournamentRouter({
|
||
organizationId,
|
||
tournamentId,
|
||
tournamentName,
|
||
}: {
|
||
organizationId: string
|
||
tournamentId: string
|
||
tournamentName: string
|
||
}) {
|
||
const [formatType, setFormatType] = useState<"team" | "individual" | null>(null)
|
||
const [error, setError] = useState(false)
|
||
|
||
useEffect(() => {
|
||
let cancelled = false
|
||
fetch(`/orgs/${organizationId}/tournaments`, { credentials: "include" })
|
||
.then((res) => (res.ok ? res.json() : Promise.reject()))
|
||
.then((list: { id: string; format_type: string }[]) => {
|
||
if (cancelled) return
|
||
const mine = list.find((t) => t.id === tournamentId)
|
||
setFormatType(mine?.format_type === "individual" ? "individual" : "team")
|
||
})
|
||
.catch(() => {
|
||
if (!cancelled) setError(true)
|
||
})
|
||
return () => {
|
||
cancelled = true
|
||
}
|
||
}, [organizationId, tournamentId])
|
||
|
||
if (error) {
|
||
// Samme trygge fallback som resten av appen: en ukjent/utilgjengelig
|
||
// turnering skal ikke krasje -- TournamentDetail sin egen feilhåndtering
|
||
// (join_code/teams/players) tar over og viser en tydelig feilmelding.
|
||
return (
|
||
<TournamentDetail organizationId={organizationId} tournamentId={tournamentId} tournamentName={tournamentName} />
|
||
)
|
||
}
|
||
|
||
if (formatType === null) {
|
||
return (
|
||
<div className="flex min-h-[100dvh] flex-col items-center justify-center gap-4 bg-background">
|
||
<div
|
||
aria-hidden="true"
|
||
className="size-10 animate-spin rounded-full border-4 border-primary/20 border-t-primary"
|
||
/>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (formatType === "individual") {
|
||
return (
|
||
<IndividualTournamentDetail
|
||
organizationId={organizationId}
|
||
tournamentId={tournamentId}
|
||
tournamentName={tournamentName}
|
||
/>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<TournamentDetail organizationId={organizationId} tournamentId={tournamentId} tournamentName={tournamentName} />
|
||
)
|
||
}
|