Scrum4Me/app/(auth)/login/page.tsx
Madhura68 3a90fa9d13 feat(ST-1007): add QR login button on /login with SSE listener
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>
2026-04-27 23:21:10 +02:00

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>
)
}