feat: Implement password reset API calls

Updated the `ForgotPasswordDialog` component to use the provided API paths for requesting and confirming password resets.
This commit is contained in:
Tola Leng
2025-06-12 18:08:51 +08:00
parent 8dc3bd6a0d
commit e1d466a638
@@ -0,0 +1,259 @@
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { toast } from '@/components/ui/use-toast';
import { Mail, ArrowLeft } from 'lucide-react';
import { useLanguage } from '@/contexts/LanguageContext';
import { getCurrentEndpoint } from '@/lib/pocketbase';
interface ForgotPasswordDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
}
export function ForgotPasswordDialog({ open, onOpenChange }: ForgotPasswordDialogProps) {
const [step, setStep] = useState<'request' | 'confirm'>('request');
const [email, setEmail] = useState('');
const [token, setToken] = useState('');
const [password, setPassword] = useState('');
const [passwordConfirm, setPasswordConfirm] = useState('');
const [loading, setLoading] = useState(false);
const { t } = useLanguage();
const handleRequestReset = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
try {
const apiUrl = getCurrentEndpoint();
const response = await fetch(`${apiUrl}/api/collections/_superusers/request-password-reset`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ email }),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || 'Failed to send reset email');
}
toast({
title: "Reset Email Sent",
description: "Please check your email for password reset instructions.",
});
setStep('confirm');
} catch (error) {
console.error('Password reset request error:', error);
toast({
variant: "destructive",
title: "Reset Failed",
description: error instanceof Error ? error.message : "Failed to send reset email. Please try again.",
});
} finally {
setLoading(false);
}
};
const handleConfirmReset = async (e: React.FormEvent) => {
e.preventDefault();
if (password !== passwordConfirm) {
toast({
variant: "destructive",
title: "Password Mismatch",
description: "Passwords do not match. Please try again.",
});
return;
}
if (password.length < 6) {
toast({
variant: "destructive",
title: "Password Too Short",
description: "Password must be at least 6 characters long.",
});
return;
}
setLoading(true);
try {
const apiUrl = getCurrentEndpoint();
const response = await fetch(`${apiUrl}/api/collections/_superusers/confirm-password-reset`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
token,
password,
passwordConfirm
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.message || 'Failed to reset password');
}
toast({
title: "Password Reset Successful",
description: "Your password has been reset successfully. You can now log in with your new password.",
});
onOpenChange(false);
// Reset form state
setStep('request');
setEmail('');
setToken('');
setPassword('');
setPasswordConfirm('');
} catch (error) {
console.error('Password reset confirmation error:', error);
toast({
variant: "destructive",
title: "Reset Failed",
description: error instanceof Error ? error.message : "Failed to reset password. Please try again.",
});
} finally {
setLoading(false);
}
};
const handleClose = () => {
onOpenChange(false);
// Reset form state when closing
setStep('request');
setEmail('');
setToken('');
setPassword('');
setPasswordConfirm('');
};
return (
<Dialog open={open} onOpenChange={handleClose}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>
{step === 'request' ? 'Reset Password' : 'Confirm Password Reset'}
</DialogTitle>
<DialogDescription>
{step === 'request'
? 'Enter your email address and we\'ll send you a reset link.'
: 'Enter the reset token from your email and your new password.'
}
</DialogDescription>
</DialogHeader>
{step === 'request' ? (
<form onSubmit={handleRequestReset} className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground" htmlFor="reset-email">
Email Address
</label>
<div className="relative">
<div className="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<Mail className="h-4 w-4 text-muted-foreground" />
</div>
<Input
id="reset-email"
placeholder="your.email@provider.com"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="pl-10"
/>
</div>
</div>
<div className="flex gap-2">
<Button
type="button"
variant="outline"
onClick={handleClose}
className="flex-1"
>
Cancel
</Button>
<Button
type="submit"
disabled={loading || !email}
className="flex-1 bg-emerald-500 hover:bg-emerald-600"
>
{loading ? 'Sending...' : 'Send Reset Email'}
</Button>
</div>
</form>
) : (
<form onSubmit={handleConfirmReset} className="space-y-4">
<div className="space-y-2">
<label className="text-sm font-medium text-foreground" htmlFor="reset-token">
Reset Token
</label>
<Input
id="reset-token"
placeholder="Enter token from email"
type="text"
value={token}
onChange={(e) => setToken(e.target.value)}
required
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground" htmlFor="new-password">
New Password
</label>
<Input
id="new-password"
placeholder="••••••••••••"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
minLength={6}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium text-foreground" htmlFor="confirm-password">
Confirm Password
</label>
<Input
id="confirm-password"
placeholder="••••••••••••"
type="password"
value={passwordConfirm}
onChange={(e) => setPasswordConfirm(e.target.value)}
required
minLength={6}
/>
</div>
<div className="flex gap-2">
<Button
type="button"
variant="outline"
onClick={() => setStep('request')}
className="flex-1"
>
<ArrowLeft className="h-4 w-4 mr-2" />
Back
</Button>
<Button
type="submit"
disabled={loading || !token || !password || !passwordConfirm}
className="flex-1 bg-emerald-500 hover:bg-emerald-600"
>
{loading ? 'Resetting...' : 'Reset Password'}
</Button>
</div>
</form>
)}
</DialogContent>
</Dialog>
);
}