e1d466a638
Updated the `ForgotPasswordDialog` component to use the provided API paths for requesting and confirming password resets.
259 lines
8.3 KiB
TypeScript
259 lines
8.3 KiB
TypeScript
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>
|
|
);
|
|
} |