teecup/frontend/components/teecup/velkommen.tsx
Erol Haagenrud 2562e20ec3 13-årsgrense — håndhevet server-side (eneste vei inn i appen, ADR-051) og med umiddelbar rød feilmelding klientside i begge profil-skjemaer. Testet på nøyaktig dagsgrensen.
PWA-installasjonstekst — skiller nå Safari/Chrome på iOS, ikke lenger feilaktig Safari-only.
Beskrivelsestekst — "golfklubber" fjernet, nytt punkt om at turneringsadmin/-presentasjon fungerer godt på PC.
Dette krever både teecup_api og teecup_frontend (ingen migrasjon).
2026-08-10 07:00:11 +02:00

170 lines
8.4 KiB
TypeScript

"use client"
// Ny landingsside for innlogget-men-ikke-fullført-profil (ADR-048-tillegg
// 2026-08-09 -- se ARCHITECTURE_DECISIONS.md). Erstatter det tidligere
// direkte hoppet til /account: brukeren møtte skjemaet FØR de fikk noen
// kontekst om hva TeeCup er, uten mulighet til å orientere seg -- brukeren
// rapporterte eksplisitt at dette var feil. Denne siden viser i stedet en
// vanlig PWA-installasjonsoppfordring, LÅSTE (men synlige, forklarte)
// hurtighandlinger, og selve TeeCup-presentasjonen (innhold hentet fra
// teecup-beskrivelse.md, skrevet om til kort UI-tekst) -- før brukeren
// dyttes inn i skjemaet.
import { useState } from "react"
import Link from "next/link"
import { useRouter } from "next/navigation"
import { ArrowRight, Calculator, Flag, KeyRound, Lock, Monitor, Trophy, Users } from "lucide-react"
import { InstallPrompt } from "@/components/install-prompt"
import { TeeCupWordmark } from "@/components/teecup/wordmark"
import { BottomNav } from "@/components/teecup/bottom-nav"
// Alle fanene unntatt "Profil" vises låst (brukerønske 2026-08-09: "Alle
// knappene bør egentlig være der, deaktivert, bortsett fra til profilen")
// -- selve navigasjonens FORM skal se ut som den vanlige appen, ikke en
// avkortet versjon, men kun profilen skal faktisk være nåbar herfra.
const LOCKED_NAV_HREFS = ["/dashboard", "/my-rounds", "/dashboard#kommende-turneringer", "/more"]
export function Velkommen({ firstName, displayName }: { firstName: string | null; displayName: string }) {
const router = useRouter()
const [showLockedHint, setShowLockedHint] = useState(false)
async function handleLogout() {
try {
await fetch("/auth/logout", { method: "POST", credentials: "include" })
} finally {
router.replace("/logg-inn")
}
}
return (
<div className="flex min-h-dvh flex-col bg-clubhouse-bg text-clubhouse-ink">
<header className="sticky top-0 z-10 border-b border-clubhouse-border bg-clubhouse-card/95 backdrop-blur pt-[env(safe-area-inset-top)] supports-[backdrop-filter]:bg-clubhouse-card/80">
<div className="mx-auto flex w-full max-w-2xl items-center justify-between gap-4 px-5 py-4">
<TeeCupWordmark className="h-7" />
<button
type="button"
onClick={handleLogout}
className="inline-flex min-h-[44px] items-center rounded-lg px-3 text-sm font-semibold text-clubhouse-muted transition-colors hover:bg-clubhouse-field hover:text-clubhouse-ink focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tee-strong"
>
Logg ut
</button>
</div>
</header>
<main className="mx-auto flex w-full max-w-2xl flex-1 flex-col gap-6 px-5 pb-24 pt-6">
<div>
<h1 className="text-2xl font-extrabold text-balance">{`Velkommen, ${firstName ?? displayName}!`}</h1>
<p className="mt-1 text-[0.9375rem] text-clubhouse-muted text-pretty">
Du er logget inn. Ett steg gjenstår før du kan bli med i runder og turneringer.
</p>
</div>
<InstallPrompt />
{/* Fullfør profilen -- primær CTA */}
<div className="flex flex-col gap-3 rounded-2xl border border-tee-strong/30 bg-tee/10 p-5">
<div>
<h2 className="text-base font-bold">Fullfør profilen din</h2>
<p className="mt-1 text-sm text-clubhouse-muted text-pretty">
Navn, fødselsdato, kjønn og HCP trengs for at runder og handicap skal beregnes riktig.
</p>
</div>
<Link
href="/account"
className="inline-flex min-h-[48px] w-fit items-center gap-2 rounded-xl bg-tee-strong px-5 text-base font-bold text-white transition-opacity hover:opacity-90 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tee-strong"
>
Fyll ut
<ArrowRight aria-hidden="true" className="size-4" />
</Link>
</div>
{/* Låste hurtighandlinger -- synlige, forklarte, ikke skjulte. Trykk
viser en delt påminnelse (fast plassert over bunnnavigasjonen,
se lenger ned -- fungerer uansett om det er en hurtighandling
eller en bunnfane som trykkes) i stedet for å navigere. */}
<section aria-label="Hurtighandlinger (låst til profilen er fullført)" className="flex flex-col gap-3">
<div className="grid grid-cols-3 gap-2 sm:gap-3">
<LockedAction icon={Flag} label="Ny runde" onClick={() => setShowLockedHint(true)} />
<LockedAction icon={Trophy} label="Ny turnering" onClick={() => setShowLockedHint(true)} />
<LockedAction icon={KeyRound} label="Bli med med kode" onClick={() => setShowLockedHint(true)} />
</div>
</section>
{/* Om TeeCup -- innhold fra teecup-beskrivelse.md, skrevet om til kort
UI-tekst (ikke limt inn rått). */}
<section className="flex flex-col gap-4 rounded-2xl border border-clubhouse-border bg-clubhouse-card p-5 sm:p-6">
<h2 className="text-lg font-extrabold">Hva er TeeCup?</h2>
<p className="text-[0.9375rem] leading-relaxed text-clubhouse-muted text-pretty">
En mobil-først app for golfturneringer og live-scoring bygget for bedrifter og
vennegjenger som spiller sammen. Klubbhus-stemning, ikke countryklubb-snobberi.
</p>
<ul className="flex flex-col gap-2.5 text-[0.9375rem] text-clubhouse-ink">
<li className="flex items-start gap-2.5">
<Trophy aria-hidden="true" className="mt-0.5 size-4 shrink-0 text-cup-strong" />
Ni turneringsformater fra vanlig slagspill til Scramble, Chapman og Money Ball.
</li>
<li className="flex items-start gap-2.5">
<Flag aria-hidden="true" className="mt-0.5 size-4 shrink-0 text-tee-strong" />
Ekte, live scoreføring ute banen leaderboardet oppdaterer seg i sanntid.
</li>
<li className="flex items-start gap-2.5">
<Calculator aria-hidden="true" className="mt-0.5 size-4 shrink-0 text-tee-strong" />
Fullt WHS-handicapsystem, ikke en forenklet tilnærming.
</li>
<li className="flex items-start gap-2.5">
<Users aria-hidden="true" className="mt-0.5 size-4 shrink-0 text-cup-strong" />
Venner, delte runder og følg-live for tilskuere.
</li>
<li className="flex items-start gap-2.5">
<Monitor aria-hidden="true" className="mt-0.5 size-4 shrink-0 text-tee-strong" />
Turneringsadministrasjon og -presentasjon fungerer minst like godt PC som telefon.
</li>
</ul>
</section>
</main>
{showLockedHint ? (
<div
role="alert"
className="fixed inset-x-0 z-40 mx-auto max-w-2xl px-4"
style={{ bottom: "calc(56px + env(safe-area-inset-bottom) + 12px)" }}
>
<div className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-clubhouse-border bg-clubhouse-card p-3 text-sm shadow-lg shadow-black/10">
<span className="text-clubhouse-ink">Du fylle ut profilen din før du kan bruke dette.</span>
<Link
href="/account"
onClick={() => setShowLockedHint(false)}
className="shrink-0 font-semibold text-tee-strong underline-offset-2 hover:underline"
>
til skjemaet
</Link>
</div>
</div>
) : null}
<BottomNav disabledHrefs={LOCKED_NAV_HREFS} onDisabledClick={() => setShowLockedHint(true)} />
</div>
)
}
function LockedAction({
icon: Icon,
label,
onClick,
}: {
icon: typeof Flag
label: string
onClick: () => void
}) {
return (
<button
type="button"
onClick={onClick}
aria-disabled="true"
className="relative flex min-h-[84px] flex-1 flex-col items-center justify-center gap-1.5 rounded-2xl border border-clubhouse-border bg-clubhouse-field px-2 py-3 text-center font-semibold text-clubhouse-muted transition-colors hover:bg-clubhouse-field/70 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-tee-strong"
>
<Lock aria-hidden="true" className="absolute right-2 top-2 size-3.5 text-clubhouse-muted" />
<Icon aria-hidden="true" className="size-5 sm:size-6" />
<span className="text-xs font-bold leading-tight text-balance sm:text-sm">{label}</span>
</button>
)
}