118 lines
4.1 KiB
TypeScript
118 lines
4.1 KiB
TypeScript
'use client';
|
|
|
|
import { useState } from 'react';
|
|
import { useRouter } from 'next/navigation';
|
|
import { Lock } from 'lucide-react';
|
|
import { apiFetch } from '@/lib/browser-navigation';
|
|
|
|
export default function ChangePasswordPage() {
|
|
const router = useRouter();
|
|
const [currentPassword, setCurrentPassword] = useState('');
|
|
const [newPassword, setNewPassword] = useState('');
|
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
const [error, setError] = useState('');
|
|
const [success, setSuccess] = useState(false);
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
async function handleSubmit(e: React.FormEvent) {
|
|
e.preventDefault();
|
|
setError('');
|
|
setSuccess(false);
|
|
|
|
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);
|
|
const res = await apiFetch('/api/admin/change-password', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ currentPassword, newPassword }),
|
|
});
|
|
|
|
if (res.ok) {
|
|
setSuccess(true);
|
|
setCurrentPassword('');
|
|
setNewPassword('');
|
|
setConfirmPassword('');
|
|
setTimeout(() => router.push('/admin'), 2000);
|
|
} else {
|
|
const data = await res.json().catch(() => ({}));
|
|
setError(data.error || 'Failed to change password.');
|
|
}
|
|
setLoading(false);
|
|
}
|
|
|
|
return (
|
|
<div className="max-w-md mx-auto space-y-6">
|
|
<div>
|
|
<h1 className="text-2xl font-semibold text-foreground">Change Password</h1>
|
|
<p className="text-sm text-muted-foreground mt-1">Update your admin password.</p>
|
|
</div>
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-4">
|
|
<div className="space-y-1.5">
|
|
<label className="text-sm font-medium text-foreground">Current Password</label>
|
|
<div className="relative">
|
|
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
|
<input
|
|
type="password"
|
|
value={currentPassword}
|
|
onChange={e => setCurrentPassword(e.target.value)}
|
|
required
|
|
className="w-full h-9 ps-9 pe-3 rounded-md border border-input bg-background text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
autoComplete="current-password"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-1.5">
|
|
<label className="text-sm font-medium text-foreground">New Password</label>
|
|
<input
|
|
type="password"
|
|
value={newPassword}
|
|
onChange={e => setNewPassword(e.target.value)}
|
|
required
|
|
minLength={8}
|
|
className="w-full h-9 px-3 rounded-md border border-input bg-background text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
autoComplete="new-password"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-1.5">
|
|
<label className="text-sm font-medium text-foreground">Confirm New Password</label>
|
|
<input
|
|
type="password"
|
|
value={confirmPassword}
|
|
onChange={e => setConfirmPassword(e.target.value)}
|
|
required
|
|
minLength={8}
|
|
className="w-full h-9 px-3 rounded-md border border-input bg-background text-sm text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
|
|
autoComplete="new-password"
|
|
/>
|
|
</div>
|
|
|
|
{error && (
|
|
<p className="text-sm text-red-500">{error}</p>
|
|
)}
|
|
{success && (
|
|
<p className="text-sm text-green-600">Password changed. Redirecting...</p>
|
|
)}
|
|
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
className="w-full h-9 rounded-md bg-primary text-primary-foreground text-sm font-medium hover:bg-primary/90 transition-colors disabled:opacity-50"
|
|
>
|
|
{loading ? 'Changing...' : 'Change Password'}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
);
|
|
}
|