46 lines
2.4 KiB
TypeScript
46 lines
2.4 KiB
TypeScript
|
|
'use client';
|
||
|
|
import { useEffect, useState, type FormEvent } from 'react';
|
||
|
|
import { useRouter } from 'next/navigation';
|
||
|
|
import Link from 'next/link';
|
||
|
|
import { api, errMsg, setCsrf } from '@/lib/api';
|
||
|
|
import { useSession } from '@/lib/session';
|
||
|
|
import { Alert, Field } from '@/components/ui';
|
||
|
|
|
||
|
|
export default function Login() {
|
||
|
|
const r = useRouter(); const { me, reload } = useSession();
|
||
|
|
const [step, setStep] = useState<'pw' | 'mfa'>('pw');
|
||
|
|
const [err, setErr] = useState(''); const [busy, setBusy] = useState(false);
|
||
|
|
useEffect(() => { if (me?.pendingMfa) setStep('mfa'); else if (me) r.replace('/dashboard'); }, [me, r]);
|
||
|
|
|
||
|
|
async function submit(e: FormEvent<HTMLFormElement>) {
|
||
|
|
e.preventDefault(); setErr(''); setBusy(true);
|
||
|
|
const f = new FormData(e.currentTarget);
|
||
|
|
try {
|
||
|
|
if (step === 'pw') {
|
||
|
|
const res = await api<{ status: string; csrf: string }>('POST', '/auth/login', { email: f.get('email'), password: f.get('password') });
|
||
|
|
setCsrf(res.csrf);
|
||
|
|
if (res.status === 'mfa_required') { await reload(); setStep('mfa'); } else { await reload(); r.replace('/dashboard'); }
|
||
|
|
} else {
|
||
|
|
await api('POST', '/auth/mfa/verify', { code: f.get('code') });
|
||
|
|
await reload(); r.replace('/dashboard');
|
||
|
|
}
|
||
|
|
} catch (x) { setErr(errMsg(x)); } finally { setBusy(false); }
|
||
|
|
}
|
||
|
|
return (
|
||
|
|
<main id="main" className="center"><div className="auth card">
|
||
|
|
<h1>{step === 'pw' ? 'Anmelden' : 'Zweiter Faktor'}</h1>
|
||
|
|
{err && <Alert kind="err">{err}</Alert>}
|
||
|
|
<form onSubmit={submit}>
|
||
|
|
{step === 'pw' ? (<>
|
||
|
|
<Field id="email" label="E-Mail"><input id="email" name="email" type="email" autoComplete="username" required autoFocus /></Field>
|
||
|
|
<Field id="password" label="Passwort"><input id="password" name="password" type="password" autoComplete="current-password" required /></Field>
|
||
|
|
</>) : (
|
||
|
|
<Field id="code" label="Code aus der Authenticator-App" hint="Alternativ ein Wiederherstellungscode."><input id="code" name="code" inputMode="text" autoComplete="one-time-code" required autoFocus /></Field>
|
||
|
|
)}
|
||
|
|
<button className="btn primary" disabled={busy} type="submit">{busy ? 'Bitte warten …' : step === 'pw' ? 'Anmelden' : 'Bestätigen'}</button>
|
||
|
|
</form>
|
||
|
|
{step === 'pw' && <p className="small" style={{ marginTop: 16 }}><Link href="/passwort-vergessen">Passwort vergessen?</Link></p>}
|
||
|
|
</div></main>
|
||
|
|
);
|
||
|
|
}
|