Files
vfxreview/components/settings/ChangePasswordForm.tsx
T
twotalesanimation 0fbe856dce Initial commit
2026-05-19 22:20:29 +02:00

167 lines
6.0 KiB
TypeScript

'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { AlertTriangle, Eye, EyeOff, KeyRound } from 'lucide-react';
import { changeOwnPassword } from '@/actions/users';
interface Props {
mustChangePassword: boolean;
}
export function ChangePasswordForm({ mustChangePassword }: Props) {
const router = useRouter();
const [currentPassword, setCurrentPassword] = useState('');
const [newPassword, setNewPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [showCurrent, setShowCurrent] = useState(false);
const [showNew, setShowNew] = useState(false);
const [showConfirm, setShowConfirm] = useState(false);
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
const [loading, setLoading] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
setError(null);
if (newPassword.length < 8) {
setError('New password must be at least 8 characters.');
return;
}
if (newPassword !== confirmPassword) {
setError('New passwords do not match.');
return;
}
setLoading(true);
try {
await changeOwnPassword({ currentPassword, newPassword });
setSuccess(true);
setCurrentPassword('');
setNewPassword('');
setConfirmPassword('');
// Reload so the session banner clears
router.refresh();
} catch (err: unknown) {
setError(err instanceof Error ? err.message : 'Failed to change password.');
} finally {
setLoading(false);
}
}
return (
<div className="space-y-4">
{mustChangePassword && (
<div className="flex items-start gap-3 rounded-lg border border-amber-500/40 bg-amber-500/10 px-4 py-3 text-sm text-amber-300">
<AlertTriangle className="h-4 w-4 mt-0.5 shrink-0 text-amber-400" />
<span>
You must set a new password before continuing. Your account was created with a
temporary password.
</span>
</div>
)}
<Card>
<CardHeader>
<CardTitle className="text-base flex items-center gap-2">
<KeyRound className="h-4 w-4" />
Change Password
</CardTitle>
</CardHeader>
<CardContent>
{success ? (
<p className="text-sm text-emerald-400">
Password updated successfully.
</p>
) : (
<form onSubmit={handleSubmit} className="space-y-4 max-w-sm">
<div className="space-y-1.5">
<Label htmlFor="currentPassword">Current password</Label>
<div className="relative">
<Input
id="currentPassword"
type={showCurrent ? 'text' : 'password'}
value={currentPassword}
onChange={(e) => setCurrentPassword(e.target.value)}
required
autoComplete="current-password"
className="pr-10"
/>
<button
type="button"
onClick={() => setShowCurrent((v) => !v)}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-white"
tabIndex={-1}
>
{showCurrent ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
</div>
<div className="space-y-1.5">
<Label htmlFor="newPassword">New password</Label>
<div className="relative">
<Input
id="newPassword"
type={showNew ? 'text' : 'password'}
value={newPassword}
onChange={(e) => setNewPassword(e.target.value)}
required
autoComplete="new-password"
className="pr-10"
/>
<button
type="button"
onClick={() => setShowNew((v) => !v)}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-white"
tabIndex={-1}
>
{showNew ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
<p className="text-xs text-zinc-500">At least 8 characters</p>
</div>
<div className="space-y-1.5">
<Label htmlFor="confirmPassword">Confirm new password</Label>
<div className="relative">
<Input
id="confirmPassword"
type={showConfirm ? 'text' : 'password'}
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
autoComplete="new-password"
className="pr-10"
/>
<button
type="button"
onClick={() => setShowConfirm((v) => !v)}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-zinc-400 hover:text-white"
tabIndex={-1}
>
{showConfirm ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
</div>
</div>
{error && (
<p className="text-sm text-red-400">{error}</p>
)}
<Button type="submit" disabled={loading}>
{loading ? 'Saving…' : 'Update password'}
</Button>
</form>
)}
</CardContent>
</Card>
</div>
);
}