Authentication reaches zero -- sign-in, bootstrap, forgot and reset password. These are the first words a new account reads, which is why the brief ranked them above the larger screens. DavAppPasswords had the third toLocaleDateString(undefined, ...) in the shell; it now uses useFormat().date like the other two. DebugPanel's three labels are keyed too. Worth knowing rather than acting on: that panel is rendered behind showDebug, which Background.tsx hardcodes to false, so it never appears. Keyed rather than deleted because removing it is not my call.
123 lines
4.4 KiB
TypeScript
123 lines
4.4 KiB
TypeScript
import { useState } from 'react';
|
|
import { toast } from 'sonner';
|
|
import { useNavigate } from 'react-router';
|
|
import { useClient } from 'hooks/useClient';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Card } from '@/components/Card';
|
|
import { useForm } from 'hooks/useForm';
|
|
import { useTranslation } from '@/lib/i18n';
|
|
|
|
export function Bootstrap() {
|
|
const { t } = useTranslation();
|
|
const authClient = useClient('/api/auth');
|
|
const navigate = useNavigate();
|
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
const { formRef, state, isValid } = useForm(
|
|
{},
|
|
(s) => !!s.email && !!s.name && !!s.username && !!s.password && s.password === s.confirmPassword,
|
|
);
|
|
|
|
const handleBootstrap = async (ev: React.FormEvent) => {
|
|
ev.preventDefault();
|
|
if (!isValid || isSubmitting) return;
|
|
|
|
setIsSubmitting(true);
|
|
try {
|
|
await authClient.post('/bootstrap', {
|
|
email: state.email.trim(),
|
|
name: state.name.trim(),
|
|
username: state.username.trim(),
|
|
password: state.password,
|
|
confirmPassword: state.confirmPassword,
|
|
});
|
|
toast.success(t('auth.bootstrapOk'));
|
|
navigate('/');
|
|
} catch (ex) {
|
|
const error = ex as { message?: string };
|
|
toast.error(error.message || 'Bootstrap failed. Please try again.');
|
|
} finally {
|
|
setIsSubmitting(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Card className="md:min-w-[480px] flex flex-col gap-6">
|
|
<div className="text-center">
|
|
<div className="text-duck-dark text-2xl font-bold">{t('auth.bootstrapTitle')}</div>
|
|
<div className="text-duck-dark/60">{t('auth.bootstrapSubtitle')}</div>
|
|
</div>
|
|
|
|
<form ref={formRef} onSubmit={handleBootstrap} className="grid gap-4">
|
|
<Label className="grid gap-2">
|
|
<span className="text-duck-dark/70">{t('auth.name')}</span>
|
|
<Input
|
|
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark placeholder:text-duck-dark/40"
|
|
type="text"
|
|
name="name"
|
|
placeholder={t('auth.namePlaceholder')}
|
|
autoComplete="name"
|
|
/>
|
|
</Label>
|
|
|
|
<Label className="grid gap-2">
|
|
<span className="text-duck-dark/70">{t('auth.email')}</span>
|
|
<Input
|
|
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark placeholder:text-duck-dark/40"
|
|
type="email"
|
|
name="email"
|
|
placeholder={t('auth.adminEmailPlaceholder')}
|
|
autoComplete="email"
|
|
/>
|
|
</Label>
|
|
|
|
<Label className="grid gap-2">
|
|
<span className="text-duck-dark/70">{t('auth.username')}</span>
|
|
<Input
|
|
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark placeholder:text-duck-dark/40"
|
|
type="text"
|
|
name="username"
|
|
placeholder="your-username"
|
|
autoComplete="username"
|
|
/>
|
|
</Label>
|
|
|
|
<div className="grid md:flex gap-4">
|
|
<Label className="grid gap-2 flex-1">
|
|
<span className="text-duck-dark/70">{t('auth.password')}</span>
|
|
<Input
|
|
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark placeholder:text-duck-dark/40"
|
|
type="password"
|
|
name="password"
|
|
placeholder={t('auth.minChars')}
|
|
autoComplete="new-password"
|
|
/>
|
|
</Label>
|
|
|
|
<Label className="grid gap-2 flex-1">
|
|
<span className="text-duck-dark/70">{t('auth.confirmPassword')}</span>
|
|
<Input
|
|
className="h-11 bg-background/60 border-duck-dark/20 text-duck-dark placeholder:text-duck-dark/40"
|
|
type="password"
|
|
name="confirmPassword"
|
|
placeholder={t('auth.confirmPlaceholder')}
|
|
autoComplete="new-password"
|
|
/>
|
|
</Label>
|
|
</div>
|
|
|
|
<div className="pt-2">
|
|
<Button
|
|
type="submit"
|
|
disabled={!isValid || isSubmitting}
|
|
className="w-full h-11 bg-duck-yellow hover:bg-duck-yellow/90 text-duck-teal font-bold text-lg transition-all duration-200 hover:scale-105 cursor-pointer disabled:opacity-50 disabled:hover:scale-100"
|
|
>
|
|
{isSubmitting ? 'Creating account...' : 'Create Account'}
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Card>
|
|
);
|
|
}
|