"use client" // Kontoinnstillinger (ADR-021): sett/fjern passord (valgfritt tillegg til // magic-link), aktiver/deaktiver 2FA. Frivillig 2FA-oppsett herfra bruker // SAMME TwoFactorSetupForm som den tvungne flyten rett etter innlogging // (forced=false her -- kun tekst-copyen skiller de to). import type React from "react" import { useEffect, useRef, useState } from "react" import Link from "next/link" import { useRouter } from "next/navigation" import { ArrowLeft, Camera, KeyRound, Lock, LogOut, Mail, Plus, ShieldCheck, ShieldOff, Trash2, User, X, } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Wordmark } from "@/components/wordmark" import { TwoFactorSetupForm } from "@/components/two-factor-flow" // ADR-031: personlig profil, ETT sett per konto (app_user), atskilt fra de // org-scopede `player`-radene organisatorer administrerer. type Me = { id: string email: string display_name: string has_password: boolean two_factor_method: "totp" | "email" | null first_name: string | null last_name: string | null birth_date: string | null gender: "m" | "f" | "x" | null handicap_index: number | null home_club: string | null country: string | null bio: string | null mobile_country_code: string | null mobile_number: string | null avatar_url: string | null // 2026-07-22: alle obligatoriske profilfelt utfylt (bilde/beskrivelse // teller ikke). Se AccountSettings sin bruk under. profile_complete: boolean secondary_emails: { id: string; email: string }[] } export function AccountSettings() { const router = useRouter() const [me, setMe] = useState(null) const [loading, setLoading] = useState(true) const [settingUp2fa, setSettingUp2fa] = useState(false) async function loadMe() { try { const res = await fetch("/auth/me", { credentials: "include" }) if (res.ok) setMe(await res.json()) } finally { setLoading(false) } } useEffect(() => { void loadMe() }, []) async function handleDisable2fa() { if (!confirm("Er du sikker på at du vil slå av topartsautentisering?")) return const res = await fetch("/auth/2fa/disable", { method: "POST", credentials: "include" }) if (res.ok) void loadMe() } if (loading) { return (
) } if (!me) return null // Obligatorisk profil-fullføring (2026-07-22): en ny/ufullstendig konto // ser KUN dette skjemaet -- ingen tilgang til resten av kontosidene eller // dashbordet før alle obligatoriske felt er utfylt. Se page.tsx og // Dashboard for de to inngangspunktene som sender brukeren hit. if (!me.profile_complete) { return ( router.replace("/dashboard")} /> ) } return (
{settingUp2fa ? (
{ setSettingUp2fa(false) void loadMe() }} />
) : (

Topartsautentisering (2FA)

{me.two_factor_method ? ( <>

Aktivert med{" "} {me.two_factor_method === "totp" ? "autentisator-app" : "engangskode på e-post"} .

) : ( <>

Ikke aktivert. Anbefales for organisasjonseiere/administratorer -- da vil du bli bedt om å sette det opp ved neste innlogging uansett.

)}
)}
) } // --- Obligatorisk profil-fullføring (2026-07-22) ---------------------------- // Vises i STEDET for resten av appen (dashbord, øvrige kontoseksjoner) helt // til alle obligatoriske felt er utfylt -- se AccountSettings sin // profile_complete-gren. Kun bilde og beskrivelse er valgfrie her. function ProfileOnboarding({ me, onComplete }: { me: Me; onComplete: () => void }) { const router = useRouter() const [firstName, setFirstName] = useState(me.first_name ?? "") const [lastName, setLastName] = useState(me.last_name ?? "") const [birthDate, setBirthDate] = useState(me.birth_date ?? "") const [gender, setGender] = useState(me.gender ?? "") // WHS-maksimum (54) er riktig utgangspunkt for en spiller uten offisiell // HCP ennå -- forhåndsutfylt, ikke tomt, jf. brukerens eksplisitte ønske. const [hcp, setHcp] = useState(me.handicap_index === null ? "54" : String(me.handicap_index)) const [homeClub, setHomeClub] = useState(me.home_club ?? "") const [country, setCountry] = useState(me.country ?? "") const [bio, setBio] = useState(me.bio ?? "") const [saving, setSaving] = useState(false) const [error, setError] = useState(null) const valid = firstName.trim() !== "" && lastName.trim() !== "" && birthDate !== "" && gender !== "" && hcp.trim() !== "" && homeClub.trim() !== "" && country.trim() !== "" async function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!valid || saving) return setSaving(true) setError(null) try { const res = await fetch("/auth/profile", { method: "PATCH", headers: { "Content-Type": "application/json" }, credentials: "include", body: JSON.stringify({ first_name: firstName.trim(), last_name: lastName.trim(), birth_date: birthDate, gender, handicap_index: Number(hcp.replace(",", ".")), home_club: homeClub.trim(), country: country.trim(), bio: bio.trim() === "" ? null : bio.trim(), }), }) if (!res.ok) { const body = await res.json().catch(() => null) throw new Error(body?.detail?.message ?? "Klarte ikke å lagre profilen.") } const updated: Me = await res.json() if (updated.profile_complete) { onComplete() } } catch (err) { setError(err instanceof Error ? err.message : "Noe gikk galt. Prøv igjen.") } finally { setSaving(false) } } async function handleLogout() { try { await fetch("/auth/logout", { method: "POST", credentials: "include" }) } finally { router.replace("/") } } return (

Fullfør profilen din

Før du kan komme videre trenger vi noen opplysninger om deg. Alt utenom beskrivelse er obligatorisk. Du kan redigere alt dette senere under «Konto».

setFirstName(e.target.value)} className="h-11 rounded-xl" />
setLastName(e.target.value)} className="h-11 rounded-xl" />
setBirthDate(e.target.value)} className="h-11 rounded-xl" />
setHcp(e.target.value)} className="h-11 rounded-xl" />

Ferske spillere har 54 -- la stå om du ikke har en offisiell HCP ennå.

setHomeClub(e.target.value)} className="h-11 rounded-xl" />
setCountry(e.target.value)} className="h-11 rounded-xl" />