teecup/frontend/app/t/[id]/page.tsx
Erol Haagenrud 317e24a800 Alt er ferdig og live. Kort oppsummering av hele runden:
Backend (ADR-020):

tournament.join_code — kort, unik kode generert automatisk, overstyrer synlighet
match.leading_side — cachet ledende side, oppdateres ved hvert hull
Leaderboardet fikk projected_points — hva stillingen blir om pågående matcher holder seg
Ny migrasjon 011, kjørt mot ekte teecup_db, test_isolation.sql 12/12
Frontend:

Login-skjermen har nå et kode-felt som tar deg rett til turneringen, uten innlogging
Invitasjonskoden vises i turnering-detalj med kopier-knapp
Leaderboardet har to fargesegmenterte barer øverst — faktisk stilling og projisert stilling
Matchlisten i blind draw er fargekodet etter hvem som leder
Underveis oppsto en reell hendelse: en feilformulert kommando eksponerte teeoff_db sitt superbrukerpassord. Det ble flagget umiddelbart, du valgte å rotere det, og det ble gjort trygt uten at verdien noensinne ble vist på nytt — bekreftet med rene logger og 200 på både teeoff.no og teecup.teeoff.no etterpå.

Alt er verifisert i scratch før utrulling, typesjekket build kjørt, og .md-filene (CLAUDE.md, FEATURE_BACKLOG.md, ARCHITECTURE_DECISIONS.md) er oppdatert. Jeg kommuniserer på norsk videre i dette prosjektet.
2026-07-19 09:33:34 +02:00

55 lines
1.8 KiB
TypeScript

import type { Metadata } from "next"
import { PublicTournament } from "@/components/public-tournament"
// Server-side -- IKKE nettleser-fetch, går derfor IKKE gjennom
// next.config.mjs sin rewrites() (kun for trafikk inn til Next.js-serveren
// fra en klient). Peker direkte på API-et.
const API_ORIGIN = process.env.TEECUP_API_ORIGIN || "http://localhost:8000"
type PublicTournamentInfo = {
name: string
organization_name: string
description: string | null
hero_image_url: string | null
}
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>
}): Promise<Metadata> {
const { id } = await params
try {
const res = await fetch(`${API_ORIGIN}/public/tournaments/${id}`, { cache: "no-store" })
if (!res.ok) {
return { title: "Turnering | TeeCup" }
}
const data: PublicTournamentInfo = await res.json()
const title = `${data.name} | ${data.organization_name}`
const description =
data.description?.trim() || `${data.name} arrangeres av ${data.organization_name} på TeeCup.`
const images = data.hero_image_url ? [{ url: data.hero_image_url }] : undefined
return {
title,
description,
openGraph: { title, description, siteName: "TeeCup", type: "website", images },
twitter: { card: images ? "summary_large_image" : "summary", title, description, images },
}
} catch {
// Feil her skal ALDRI hindre selve siden i å laste -- kun forringe
// delingsforhåndsvisningen, som fortsatt får en trygg standardtittel.
return { title: "TeeCup" }
}
}
export default async function PublicTournamentPage({
params,
searchParams,
}: {
params: Promise<{ id: string }>
searchParams: Promise<{ code?: string }>
}) {
const { id } = await params
const { code } = await searchParams
return <PublicTournament tournamentId={id} code={code} />
}