/logg-inn (clubhouse-palett, TeeCupAuth) var en foreldreløs V0-
utforskning -- ingenting i appen lenket eller redirectet dit, og
TeeCupAuth var 100% mock (sleep()-baserte stubber, hardkodet
demo-passord, falske invitasjonskoder, en falsk "2FA-kode sendes"-tekst
som aldri sendte noe). Flagget eksplisitt til bruker før arbeid startet,
siden dette er sikkerhetskritisk kode og omfanget var langt større enn
en ren redirect-ombytting.
Full port: ekte POST /auth/request-link, POST /auth/login-password
(identisk LoginResult-statushåndtering som den gamle LoginForm), GET
/public/tournaments/by-code/{code}. TwoFactorVerifyForm/
TwoFactorSetupForm gjenbrukt UENDRET (lavest mulig risiko for
sikkerhetskritisk 2FA-kode). app/page.tsx redusert til en tynn
videresending (autentisert -> /dashboard//account, uautentisert ->
/logg-inn), beholdt for gamle bokmerker. Gammel LoginForm slettet.
Alle ~11 uautentisert-redirect-steder (committet i forrige commit)
pekte allerede til /logg-inn.
Én reell bug funnet og rettet under scratch-verifisering:
app/logg-inn/page.tsx kalte redirect() INNI en try/catch som fanget
Next.js sin egen NEXT_REDIRECT-kontrollflyt-exception stille -- en
allerede innlogget bruker fikk se innloggingsskjemaet på nytt i stedet
for å bli sendt videre. Rettet ved å flytte redirect()-kallet utenfor
try/catch, samme mønster som den opprinnelige /-siden alltid brukte.
Se CHANGELOG.md punkt 42 for full verifiseringsdetalj.
84 lines
3.8 KiB
TypeScript
84 lines
3.8 KiB
TypeScript
import type { Metadata } from "next"
|
||
import { cookies } from "next/headers"
|
||
import { redirect } from "next/navigation"
|
||
import { TeeCupWordmark } from "@/components/teecup/wordmark"
|
||
import { TeeCupAuth } from "@/components/teecup/teecup-auth"
|
||
|
||
export const metadata: Metadata = {
|
||
title: "Logg inn – TeeCup",
|
||
description: "Logg inn på TeeCup for live-scoring og golfturneringer med vennegjengen, klubben eller bedriften.",
|
||
}
|
||
|
||
// Server-side, IKKE nettleser-fetch -- går derfor IKKE gjennom
|
||
// next.config.mjs sin rewrites() (samme mønster som generateMetadata i
|
||
// app/t/[id]/page.tsx og den forrige app/page.tsx). Peker direkte på API-et.
|
||
const API_ORIGIN = process.env.TEECUP_API_ORIGIN || "http://localhost:8000"
|
||
// Må matche SESSION_COOKIE_NAME i app/auth.py -- ingen delt konstant på
|
||
// tvers av Python/TypeScript, samme mønster som andre API-kontrakt-felt
|
||
// som dupliseres bevisst i frontend-koden.
|
||
const SESSION_COOKIE_NAME = "teecup_session"
|
||
|
||
export default async function LoggInnPage() {
|
||
// Samme allerede-innlogget-sjekk som /-siden hadde (2026-08-08: /logg-inn
|
||
// overtok rollen som ekte innloggingsside, se CHANGELOG.md) -- en bruker
|
||
// med gyldig sesjonscookie skal aldri se innloggingsskjemaet på nytt.
|
||
const cookieStore = await cookies()
|
||
const session = cookieStore.get(SESSION_COOKIE_NAME)
|
||
let authenticated = false
|
||
let profileComplete = true
|
||
if (session) {
|
||
try {
|
||
const res = await fetch(`${API_ORIGIN}/auth/me`, {
|
||
headers: { Cookie: `${SESSION_COOKIE_NAME}=${session.value}` },
|
||
cache: "no-store",
|
||
})
|
||
authenticated = res.ok
|
||
if (res.ok) {
|
||
const data = await res.json()
|
||
profileComplete = Boolean(data.profile_complete)
|
||
}
|
||
} catch {
|
||
// API utilgjengelig -- vis innloggingsskjemaet i stedet for å henge.
|
||
authenticated = false
|
||
}
|
||
}
|
||
// redirect() kaster en egen NEXT_REDIRECT-kontrollflyt-exception som MÅ
|
||
// boble videre til Next.js -- må derfor stå UTENFOR try/catch-en over
|
||
// (en catch der ville slukt den stille og latt innloggingsskjemaet vises
|
||
// i stedet for å omdirigere, reell bug funnet under scratch-verifisering
|
||
// 2026-08-08, se CHANGELOG.md).
|
||
if (authenticated) {
|
||
redirect(profileComplete ? "/dashboard" : "/account")
|
||
}
|
||
|
||
return (
|
||
<main className="relative flex min-h-dvh flex-col items-center justify-center overflow-hidden bg-clubhouse-bg px-4 py-8 text-clubhouse-ink">
|
||
{/* Quiet turf-horizon backdrop — the one bit of atmosphere, kept subtle. */}
|
||
<div aria-hidden="true" className="pointer-events-none absolute inset-x-0 bottom-0 h-1/3 bg-gradient-to-t from-tee/15 to-transparent" />
|
||
|
||
<div className="relative z-10 w-full max-w-md">
|
||
{/* Header */}
|
||
<header className="mb-6 flex flex-col items-center text-center">
|
||
<TeeCupWordmark className="h-11" />
|
||
<p className="mt-3 text-pretty text-[0.9375rem] font-medium text-clubhouse-muted">
|
||
Live-scoring og turneringer — fra første tee til pokalen.
|
||
</p>
|
||
</header>
|
||
|
||
{/* Card — the scorecard. Continuation screens (2FA, link confirm) can
|
||
reuse this exact container. */}
|
||
<section className="overflow-hidden rounded-3xl border border-clubhouse-border bg-clubhouse-card shadow-[0_1px_0_rgba(23,32,15,0.04),0_18px_40px_-24px_rgba(23,32,15,0.35)]">
|
||
{/* Signature element: a fairway-green scorecard header stripe. */}
|
||
<div className="h-2 w-full bg-gradient-to-r from-tee-strong via-tee to-cup" />
|
||
<div className="p-6 sm:p-8">
|
||
<TeeCupAuth />
|
||
</div>
|
||
</section>
|
||
|
||
<p className="mt-6 text-center text-[0.875rem] leading-relaxed text-clubhouse-muted">
|
||
Ved å logge inn godtar du vilkårene våre. Trenger du hjelp? Ta kontakt med klubben din.
|
||
</p>
|
||
</div>
|
||
</main>
|
||
)
|
||
}
|