80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useRouter, useSearchParams } from 'next/navigation';
|
|
import Link from 'next/link';
|
|
import { Field } from '@/components/ui';
|
|
import { t } from '@/lib/i18n';
|
|
|
|
export function AuthForm({ mode }: { mode: 'login' | 'register' }) {
|
|
const router = useRouter();
|
|
const params = useSearchParams();
|
|
const next = params.get('next') || '/dashboard';
|
|
const [busy, setBusy] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
async function onSubmit(e: React.FormEvent<HTMLFormElement>) {
|
|
e.preventDefault();
|
|
setBusy(true);
|
|
setError(null);
|
|
const form = new FormData(e.currentTarget);
|
|
const body = Object.fromEntries(form.entries());
|
|
try {
|
|
const res = await fetch(`/api/auth/${mode}`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body),
|
|
});
|
|
if (!res.ok) {
|
|
const data = (await res.json().catch(() => ({}))) as { error?: string };
|
|
throw new Error(data.error || `Request failed (${res.status})`);
|
|
}
|
|
router.push(next);
|
|
router.refresh();
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : 'Request failed');
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
const isRegister = mode === 'register';
|
|
|
|
return (
|
|
<div className="card">
|
|
<h1 className="text-xl font-semibold">{isRegister ? t('auth.registerTitle') : t('auth.loginTitle')}</h1>
|
|
<form className="mt-6 space-y-4" onSubmit={onSubmit} noValidate>
|
|
{isRegister && (
|
|
<Field label={t('auth.name')} htmlFor="name">
|
|
<input id="name" name="name" className="input" autoComplete="name" />
|
|
</Field>
|
|
)}
|
|
<Field label={t('auth.email')} htmlFor="email">
|
|
<input id="email" name="email" type="email" required className="input" autoComplete="email" />
|
|
</Field>
|
|
<Field label={t('auth.password')} htmlFor="password">
|
|
<input
|
|
id="password"
|
|
name="password"
|
|
type="password"
|
|
required
|
|
minLength={8}
|
|
className="input"
|
|
autoComplete={isRegister ? 'new-password' : 'current-password'}
|
|
/>
|
|
</Field>
|
|
{error && <p className="text-sm text-red-600">{error}</p>}
|
|
<button type="submit" className="btn-primary w-full" disabled={busy}>
|
|
{busy ? '…' : isRegister ? t('auth.submitRegister') : t('auth.submitLogin')}
|
|
</button>
|
|
</form>
|
|
<p className="muted mt-5 text-center text-sm">
|
|
{isRegister ? t('auth.hasAccount') : t('auth.noAccount')}{' '}
|
|
<Link href={isRegister ? '/login' : '/register'} className="font-medium text-brand-600 hover:underline">
|
|
{isRegister ? t('auth.submitLogin') : t('auth.submitRegister')}
|
|
</Link>
|
|
</p>
|
|
{!isRegister && <p className="muted mt-3 text-center text-xs">{t('auth.demoHint')}</p>}
|
|
</div>
|
|
);
|
|
}
|