diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 27919a5..e5d2ab8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -155,7 +155,13 @@ "Bash(python3)", "Bash(docker --version)", "Bash(docker run --rm -v /opt/teecup/frontend:/app -w /app node:22-slim bash -c ' *)", - "Bash(git -C /opt/teecup status --short frontend/)" + "Bash(git -C /opt/teecup status --short frontend/)", + "Bash(grep -n \"CORS\\\\|cors\\\\|allow_origin\" /opt/teecup/app/main.py *)", + "Bash(git -C /opt/teecup ls-files frontend/.gitignore)", + "Bash(git -C /opt/teecup rev-parse --show-toplevel)", + "Bash(git -C /opt/teecup config --get core.excludesfile)", + "Bash(git -C /opt/teecup ls-files .claude/)", + "Bash(git -C /opt/teecup status --porcelain)" ], "additionalDirectories": [ "/opt/teeoff/deploy", diff --git a/frontend/.gitignore b/frontend/.gitignore index 97eb8c0..5c17774 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -12,4 +12,7 @@ __v0_jsx-dev-runtime.ts # Common ignores node_modules .next/ +.pnpm-store/ +next-env.d.ts +pnpm-workspace.yaml .DS_Store \ No newline at end of file diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index c98533a..140bca5 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,4 +1,3 @@ -import { Analytics } from '@vercel/analytics/next' import type { Metadata, Viewport } from 'next' import { Nunito } from 'next/font/google' import './globals.css' @@ -47,10 +46,7 @@ export default function RootLayout({ }>) { return ( - - {children} - {process.env.NODE_ENV === 'production' && } - + {children} ) } diff --git a/frontend/components/login-form.tsx b/frontend/components/login-form.tsx index a2a4787..955cff5 100644 --- a/frontend/components/login-form.tsx +++ b/frontend/components/login-form.tsx @@ -19,6 +19,7 @@ export function LoginForm() { const [sent, setSent] = useState(false) const [sending, setSending] = useState(false) const [cooldown, setCooldown] = useState(0) + const [error, setError] = useState(null) const emailValid = isValidEmail(email) const showError = touched && email.length > 0 && !emailValid @@ -33,12 +34,25 @@ export function LoginForm() { }, [cooldown]) async function sendLink() { - // Mock only — pretend to dispatch a magic link. setSending(true) - await new Promise((r) => setTimeout(r, 700)) - setSending(false) - setSent(true) - setCooldown(RESEND_COOLDOWN) + setError(null) + try { + // Alltid samme suksess-respons uansett om e-posten finnes (anti- + // enumerering, se ADR-009) -- kun nettverks-/serverfeil havner i catch. + const res = await fetch("/auth/request-link", { + method: "POST", + headers: { "Content-Type": "application/json" }, + credentials: "include", + body: JSON.stringify({ email: email.trim(), locale: "nb" }), + }) + if (!res.ok) throw new Error(`request-link: ${res.status}`) + setSent(true) + setCooldown(RESEND_COOLDOWN) + } catch { + setError("Klarte ikke å sende lenken. Sjekk tilkoblingen og prøv igjen.") + } finally { + setSending(false) + } } function handleSubmit(e: React.FormEvent) { @@ -65,6 +79,7 @@ export function LoginForm() { email={email} cooldown={cooldown} sending={sending} + error={error} onResend={handleResend} onReset={handleReset} /> @@ -101,6 +116,12 @@ export function LoginForm() { )} + {error && ( +

+ {error} +

+ )} +