Alt rent, ingen byggeartefakter liggende igjen, dokumentasjonen oppdatert. Oppsummering:
Live og verifisert ende-til-ende:
/verify-siden bygget og live, e-postmalen sender nå en ekte klikkbar lenke (app/email.py/app/config.py)
Frontend containerisert (frontend/Dockerfile, standalone Next.js) og rullet ut som egen teecup_frontend-tjeneste
Caddy peker nå teecup.teeoff.no på frontend-en, som selv proxyer API-kall server-side — same-origin, ingen CORS, cookie uendret (dokumentert som ny ADR-016)
Fant og fikset en reell fallgruve: Next.js sin rewrites() bakes inn ved build-tid for standalone-output, ikke lest ved kjøretid — løst med en Docker build-time ARG
Ekte e-post sendt, ekte lenke klikket, sesjon opprettet — bekreftet av deg
Én ting du må huske: Caddy-endringen ligger i det separate /opt/teeoff-repoet (deploy/Caddyfile), ikke i teecup-repoet — den er ikke committet ennå. Lett å glemme siden resten av denne økten kun har jobbet i /opt/teecup. Vil du at jeg minner deg, eller committer du den nå selv i teeoff-repoet?
CLAUDE.md, FEATURE_BACKLOG.md og ARCHITECTURE_DECISIONS.md (ny ADR-016) er oppdatert. Klar for commit i teecup-repoet når du vil.
2026-07-17 22:28:43 +02:00
|
|
|
"use client"
|
|
|
|
|
|
|
|
|
|
import type React from "react"
|
|
|
|
|
import { useEffect, useState } from "react"
|
2026-07-18 06:46:09 +02:00
|
|
|
import { useRouter, useSearchParams } from "next/navigation"
|
Alt rent, ingen byggeartefakter liggende igjen, dokumentasjonen oppdatert. Oppsummering:
Live og verifisert ende-til-ende:
/verify-siden bygget og live, e-postmalen sender nå en ekte klikkbar lenke (app/email.py/app/config.py)
Frontend containerisert (frontend/Dockerfile, standalone Next.js) og rullet ut som egen teecup_frontend-tjeneste
Caddy peker nå teecup.teeoff.no på frontend-en, som selv proxyer API-kall server-side — same-origin, ingen CORS, cookie uendret (dokumentert som ny ADR-016)
Fant og fikset en reell fallgruve: Next.js sin rewrites() bakes inn ved build-tid for standalone-output, ikke lest ved kjøretid — løst med en Docker build-time ARG
Ekte e-post sendt, ekte lenke klikket, sesjon opprettet — bekreftet av deg
Én ting du må huske: Caddy-endringen ligger i det separate /opt/teeoff-repoet (deploy/Caddyfile), ikke i teecup-repoet — den er ikke committet ennå. Lett å glemme siden resten av denne økten kun har jobbet i /opt/teecup. Vil du at jeg minner deg, eller committer du den nå selv i teeoff-repoet?
CLAUDE.md, FEATURE_BACKLOG.md og ARCHITECTURE_DECISIONS.md (ny ADR-016) er oppdatert. Klar for commit i teecup-repoet når du vil.
2026-07-17 22:28:43 +02:00
|
|
|
import { CheckCircle2, KeyRound } from "lucide-react"
|
|
|
|
|
import { Button } from "@/components/ui/button"
|
|
|
|
|
import { Input } from "@/components/ui/input"
|
|
|
|
|
import { Label } from "@/components/ui/label"
|
|
|
|
|
|
|
|
|
|
type Status = "checking" | "verifying" | "manual" | "success" | "error"
|
|
|
|
|
|
|
|
|
|
export function VerifyForm() {
|
2026-07-18 06:46:09 +02:00
|
|
|
const router = useRouter()
|
Alt rent, ingen byggeartefakter liggende igjen, dokumentasjonen oppdatert. Oppsummering:
Live og verifisert ende-til-ende:
/verify-siden bygget og live, e-postmalen sender nå en ekte klikkbar lenke (app/email.py/app/config.py)
Frontend containerisert (frontend/Dockerfile, standalone Next.js) og rullet ut som egen teecup_frontend-tjeneste
Caddy peker nå teecup.teeoff.no på frontend-en, som selv proxyer API-kall server-side — same-origin, ingen CORS, cookie uendret (dokumentert som ny ADR-016)
Fant og fikset en reell fallgruve: Next.js sin rewrites() bakes inn ved build-tid for standalone-output, ikke lest ved kjøretid — løst med en Docker build-time ARG
Ekte e-post sendt, ekte lenke klikket, sesjon opprettet — bekreftet av deg
Én ting du må huske: Caddy-endringen ligger i det separate /opt/teeoff-repoet (deploy/Caddyfile), ikke i teecup-repoet — den er ikke committet ennå. Lett å glemme siden resten av denne økten kun har jobbet i /opt/teecup. Vil du at jeg minner deg, eller committer du den nå selv i teeoff-repoet?
CLAUDE.md, FEATURE_BACKLOG.md og ARCHITECTURE_DECISIONS.md (ny ADR-016) er oppdatert. Klar for commit i teecup-repoet når du vil.
2026-07-17 22:28:43 +02:00
|
|
|
const searchParams = useSearchParams()
|
|
|
|
|
const urlToken = searchParams.get("token")
|
|
|
|
|
|
|
|
|
|
const [status, setStatus] = useState<Status>(urlToken ? "checking" : "manual")
|
|
|
|
|
const [manualToken, setManualToken] = useState("")
|
|
|
|
|
const [email, setEmail] = useState<string | null>(null)
|
|
|
|
|
const [error, setError] = useState<string | null>(null)
|
|
|
|
|
|
|
|
|
|
async function verify(token: string) {
|
|
|
|
|
setStatus("verifying")
|
|
|
|
|
setError(null)
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch("/auth/verify-link", {
|
|
|
|
|
method: "POST",
|
|
|
|
|
headers: { "Content-Type": "application/json" },
|
|
|
|
|
credentials: "include",
|
|
|
|
|
body: JSON.stringify({ token }),
|
|
|
|
|
})
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
const body = await res.json().catch(() => null)
|
|
|
|
|
throw new Error(body?.detail?.message ?? "Lenken er ugyldig eller utløpt.")
|
|
|
|
|
}
|
|
|
|
|
const user = await res.json()
|
|
|
|
|
setEmail(user.email)
|
|
|
|
|
setStatus("success")
|
2026-07-18 06:46:09 +02:00
|
|
|
// Kort pause så brukeren rekker å se bekreftelsen før vi går videre.
|
|
|
|
|
setTimeout(() => router.replace("/dashboard"), 900)
|
Alt rent, ingen byggeartefakter liggende igjen, dokumentasjonen oppdatert. Oppsummering:
Live og verifisert ende-til-ende:
/verify-siden bygget og live, e-postmalen sender nå en ekte klikkbar lenke (app/email.py/app/config.py)
Frontend containerisert (frontend/Dockerfile, standalone Next.js) og rullet ut som egen teecup_frontend-tjeneste
Caddy peker nå teecup.teeoff.no på frontend-en, som selv proxyer API-kall server-side — same-origin, ingen CORS, cookie uendret (dokumentert som ny ADR-016)
Fant og fikset en reell fallgruve: Next.js sin rewrites() bakes inn ved build-tid for standalone-output, ikke lest ved kjøretid — løst med en Docker build-time ARG
Ekte e-post sendt, ekte lenke klikket, sesjon opprettet — bekreftet av deg
Én ting du må huske: Caddy-endringen ligger i det separate /opt/teeoff-repoet (deploy/Caddyfile), ikke i teecup-repoet — den er ikke committet ennå. Lett å glemme siden resten av denne økten kun har jobbet i /opt/teecup. Vil du at jeg minner deg, eller committer du den nå selv i teeoff-repoet?
CLAUDE.md, FEATURE_BACKLOG.md og ARCHITECTURE_DECISIONS.md (ny ADR-016) er oppdatert. Klar for commit i teecup-repoet når du vil.
2026-07-17 22:28:43 +02:00
|
|
|
} catch (err) {
|
|
|
|
|
setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.")
|
|
|
|
|
setStatus("error")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (urlToken) void verify(urlToken)
|
|
|
|
|
// Kjør kun ved mount / når URL-tokenet faktisk endres.
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
}, [urlToken])
|
|
|
|
|
|
|
|
|
|
function handleManualSubmit(e: React.FormEvent) {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
if (!manualToken.trim()) return
|
|
|
|
|
void verify(manualToken.trim())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status === "checking" || status === "verifying") {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex w-full flex-col items-center gap-4 rounded-3xl border border-border bg-card p-8 text-center shadow-lg shadow-black/5">
|
|
|
|
|
<div
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
className="size-10 animate-spin rounded-full border-4 border-primary/20 border-t-primary"
|
|
|
|
|
/>
|
|
|
|
|
<p className="text-sm text-muted-foreground" role="status">
|
|
|
|
|
Logger deg inn …
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (status === "success") {
|
|
|
|
|
return (
|
|
|
|
|
<div className="flex w-full flex-col items-center gap-5 rounded-3xl border border-border bg-card p-6 text-center shadow-lg shadow-black/5 sm:p-8">
|
|
|
|
|
<div className="flex size-16 items-center justify-center rounded-2xl bg-primary/15">
|
|
|
|
|
<CheckCircle2 aria-hidden="true" className="size-8 text-primary" />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<h2 className="text-2xl font-extrabold tracking-tight text-balance">Du er innlogget</h2>
|
|
|
|
|
{email && (
|
|
|
|
|
<p className="text-sm leading-relaxed text-muted-foreground text-pretty">
|
|
|
|
|
Logget inn som <span className="font-semibold text-foreground">{email}</span>.
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// "manual" eller "error" -- samme skjema, ev. med en feilmelding over knappen.
|
|
|
|
|
return (
|
|
|
|
|
<div className="w-full rounded-3xl border border-border bg-card p-6 shadow-lg shadow-black/5 sm:p-8">
|
|
|
|
|
<form onSubmit={handleManualSubmit} className="flex flex-col gap-6" noValidate>
|
|
|
|
|
<div className="flex flex-col gap-2">
|
|
|
|
|
<Label htmlFor="token" className="text-sm font-semibold">
|
|
|
|
|
Kode fra e-posten
|
|
|
|
|
</Label>
|
|
|
|
|
<div className="relative">
|
|
|
|
|
<KeyRound
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
className="pointer-events-none absolute left-3.5 top-1/2 size-5 -translate-y-1/2 text-muted-foreground"
|
|
|
|
|
/>
|
|
|
|
|
<Input
|
|
|
|
|
id="token"
|
|
|
|
|
type="text"
|
|
|
|
|
autoComplete="one-time-code"
|
|
|
|
|
autoFocus
|
|
|
|
|
placeholder="Lim inn koden"
|
|
|
|
|
value={manualToken}
|
|
|
|
|
onChange={(e) => setManualToken(e.target.value)}
|
|
|
|
|
className="h-14 rounded-2xl pl-11 text-base"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{error && (
|
|
|
|
|
<p role="alert" className="text-center text-sm font-medium text-destructive">
|
|
|
|
|
{error}
|
|
|
|
|
</p>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
type="submit"
|
|
|
|
|
disabled={!manualToken.trim()}
|
|
|
|
|
className="h-14 rounded-2xl text-base font-bold shadow-sm"
|
|
|
|
|
>
|
|
|
|
|
Logg inn
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
|
|
<a
|
|
|
|
|
href="/"
|
|
|
|
|
className="text-center text-sm font-semibold text-muted-foreground transition-colors hover:text-foreground"
|
|
|
|
|
>
|
|
|
|
|
Be om en ny lenke
|
|
|
|
|
</a>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|