Diagnostisere de to feilmeldingene fra skjermbildene
Flytte frontend-sidene fra /rounds/* til /my-rounds/* (kollisjon med API-proxyen)
Oppdatere alle interne lenker/router-kall til nye stier
Oppdatere next.config.mjs-kommentaren med denne lærdommen
Typesjekket produksjonsbuild
Rulle ut live etter bekreftelse
Oppdatere statusdokumentasjon
Fant og fikset rot-årsaken til begge feilmeldingene — en reell, alvorlig bug, ikke bare kosmetikk:
/rounds var samtidig navnet på både frontend-listesiden og backend-APIets ressursprefiks. Next.js sin rewrite-presedens ("afterFiles") gjorde at:
GET/POST /rounds fra klienten aldri nådde backend — den statiske siden vant, så fetch() fikk Next sin egen HTML tilbake i stedet for JSON. Det ga akkurat de to feilmeldingene i skjermbildene.
/rounds/[id] (rundedetaljsiden) hadde det motsatte problemet — der vant rewriten, så selve siden var fullstendig uoppnåelig (ville vist rå backend-JSON i stedet for UI-et om du hadde kommet så langt).
Dette er nøyaktig samme klasse feil som medlemsside-hendelsen under ADR-016, bare i begge retninger samtidig siden det denne gangen var en eksakt navnekollisjon, ikke bare nøsting.
Fikset: flyttet alle tre frontend-sidene til /my-rounds/* (list, new, [id]) — API-et beholder /rounds uendret. Oppdatert alle interne lenker (round-card.tsx, own-rounds.tsx, new-round.tsx, round-detail.tsx, dashboard.tsx), lagt inn en tydelig advarsel i next.config.mjs for å hindre at dette gjentar seg. Bekreftet: «55/50/44/32» i det andre skjermbildet er ikke en bug — det er Tjøme Golfklubb sine faktiske utslagsnavn i teeoff (lengde i hundremeter).
Typesjekket produksjonsbuild kompilerer rent med de nye rutene.
100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
import Link from "next/link"
|
|
import { CalendarDays, ChevronRight, Flag, MapPin, Users } from "lucide-react"
|
|
import { cn } from "@/lib/utils"
|
|
|
|
export type RoundStatus = "active" | "completed"
|
|
|
|
export type Round = {
|
|
id: string
|
|
courseName: string
|
|
status: RoundStatus
|
|
teeName: string
|
|
holes: 9 | 18
|
|
date: string
|
|
playerCount: number
|
|
}
|
|
|
|
const STATUS_CONFIG: Record<RoundStatus, { label: string; dot: string; badge: string }> = {
|
|
active: {
|
|
label: "Pågår",
|
|
dot: "bg-primary",
|
|
badge: "bg-primary/15 text-foreground",
|
|
},
|
|
completed: {
|
|
label: "Fullført",
|
|
dot: "bg-brand-orange",
|
|
badge: "bg-brand-orange/12 text-foreground",
|
|
},
|
|
}
|
|
|
|
const dateFormatter = new Intl.DateTimeFormat("no-NO", {
|
|
day: "numeric",
|
|
month: "short",
|
|
year: "numeric",
|
|
})
|
|
|
|
function formatDate(value: string) {
|
|
const parsed = new Date(value)
|
|
if (Number.isNaN(parsed.getTime())) return value
|
|
return dateFormatter.format(parsed)
|
|
}
|
|
|
|
function RoundStatusBadge({ status }: { status: RoundStatus }) {
|
|
const config = STATUS_CONFIG[status]
|
|
return (
|
|
<span
|
|
className={cn(
|
|
"inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-sm font-semibold",
|
|
config.badge,
|
|
)}
|
|
>
|
|
<span className={cn("size-2 rounded-full", config.dot)} aria-hidden="true" />
|
|
{config.label}
|
|
</span>
|
|
)
|
|
}
|
|
|
|
export function RoundCard({ round }: { round: Round }) {
|
|
const playerLabel = round.playerCount === 1 ? "spiller" : "spillere"
|
|
|
|
return (
|
|
<Link
|
|
href={`/my-rounds/${round.id}`}
|
|
className="group flex min-h-[88px] w-full items-center gap-4 rounded-2xl border border-border bg-card p-5 text-left shadow-sm shadow-black/5 transition-colors hover:border-primary/60 hover:bg-accent/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
|
|
>
|
|
<div className="flex min-w-0 flex-1 flex-col gap-3">
|
|
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
|
|
<h3 className="flex min-w-0 items-center gap-2 text-lg font-bold text-foreground">
|
|
<MapPin aria-hidden="true" className="size-5 shrink-0 text-primary" />
|
|
<span className="truncate">{round.courseName}</span>
|
|
</h3>
|
|
<RoundStatusBadge status={round.status} />
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-x-5 gap-y-1.5 text-base text-muted-foreground">
|
|
<span className="flex items-center gap-1.5">
|
|
<Flag aria-hidden="true" className="size-4 shrink-0" />
|
|
<span>
|
|
{round.teeName} · {round.holes} hull
|
|
</span>
|
|
</span>
|
|
<span className="flex items-center gap-1.5">
|
|
<CalendarDays aria-hidden="true" className="size-4 shrink-0" />
|
|
<span className="tabular-nums">{formatDate(round.date)}</span>
|
|
</span>
|
|
<span className="flex items-center gap-1.5">
|
|
<Users aria-hidden="true" className="size-4 shrink-0" />
|
|
<span>
|
|
{round.playerCount} {playerLabel}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<ChevronRight
|
|
aria-hidden="true"
|
|
className="size-6 shrink-0 text-muted-foreground transition-transform group-hover:translate-x-0.5 group-hover:text-foreground"
|
|
/>
|
|
</Link>
|
|
)
|
|
}
|