Voltooit de desktop-zijde van de QR-pairing-flow. Gebruiker klikt "Inloggen
via mobiel" naast het wachtwoord-formulier → krijgt een QR-code → telefoon
scant en bevestigt → desktop wordt automatisch ingelogd zonder dat er ooit
een wachtwoord is getypt op het publieke apparaat.
app/(auth)/login/qr-login-button.tsx (Client Component):
- Phase-state: idle | starting | showing | expired | claiming
- klik → POST /api/auth/pair/start (credentials:'same-origin' voor s4m_pair)
- QRCodeSVG met fragment-URL als value (level=M, 200px); aria-label
- EventSource('/api/auth/pair/stream/<id>', { withCredentials: true })
vereist voor cookie-auth — standaard verstuurt EventSource geen credentials
- bij data.status === 'approved': es.close → POST /pair/claim → router.push('/dashboard')
- aftellende timer (mm:ss); bij 0s → 'expired' state met Vernieuwen-knop
- cleanup bij unmount: removeEventListener + close
- A11y: <details> sectie toont fragment-URL als kopieerbare tekst voor screenreaders en gebruikers zonder camera
app/(auth)/login/page.tsx: QrLoginButton onder het bestaande wachtwoord-form
met "of"-divider, achter de bestaande surface-container-low styling.
Dependency: qrcode.react ^4.2.0 (client-side SVG; geen extra round-trip;
mobileSecret blijft op desktop in JS-geheugen).
Quality gates: lint 0 errors, tsc clean, vitest 139/139, next build slaagt
(login-route static, m/pair en pair/* dynamic).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
47 lines
1.9 KiB
TypeScript
47 lines
1.9 KiB
TypeScript
import Link from 'next/link'
|
|
import { loginAction } from '@/actions/auth'
|
|
import { AuthForm } from '@/components/auth/auth-form'
|
|
import { QrLoginButton } from './qr-login-button'
|
|
|
|
export default function LoginPage() {
|
|
return (
|
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
|
<div className="w-full max-w-sm space-y-6">
|
|
|
|
{/* Logo / titel */}
|
|
<div className="text-center space-y-1">
|
|
<h1 className="text-2xl font-medium text-foreground">Scrum4Me</h1>
|
|
<p className="text-sm text-muted-foreground">Inloggen bij je account</p>
|
|
</div>
|
|
|
|
{/* Formulier */}
|
|
<div className="bg-surface-container-low rounded-xl p-6 space-y-4 border border-border">
|
|
<AuthForm action={loginAction} submitLabel="Inloggen" />
|
|
|
|
{/* M10 — Inloggen via mobiel zonder wachtwoord */}
|
|
<div className="flex items-center gap-2 py-1">
|
|
<div className="border-border h-px flex-1 border-t" />
|
|
<span className="text-muted-foreground text-xs">of</span>
|
|
<div className="border-border h-px flex-1 border-t" />
|
|
</div>
|
|
<QrLoginButton />
|
|
|
|
<div className="text-center text-sm text-muted-foreground">
|
|
Nog geen account?{' '}
|
|
<Link href="/register" className="text-primary hover:underline font-medium">
|
|
Registreer hier
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Demo credentials */}
|
|
<div className="bg-info-container text-info-container-foreground rounded-xl p-4 text-sm space-y-1 border border-border">
|
|
<p className="font-medium">Demo-account (alleen lezen)</p>
|
|
<p>Gebruikersnaam: <span className="font-mono font-medium">demo</span></p>
|
|
<p>Wachtwoord: <span className="font-mono font-medium">demo1234</span></p>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|