dev1/src/components/ResetPassword.js

116 lines
3.8 KiB
JavaScript

import React, { useState } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { Button } from './ui/button.js';
import { validatePassword, passwordHelp } from '../utils/passwordRules.ts';
export default function ResetPassword() {
const { token } = useParams();
const navigate = useNavigate();
const [pw, setPw] = useState('');
const [pw2, setPw2] = useState('');
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState('');
const [ok, setOk] = useState(false);
async function onSubmit(e) {
e.preventDefault();
setError('');
const pwErr = validatePassword(pw);
if (pwErr) { setError(pwErr); return; }
if (pw !== pw2) {
setError('Passwords do not match.');
return;
}
setSubmitting(true);
try {
const r = await fetch('/api/auth/password-reset/confirm', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, password: pw })
});
if (!r.ok) {
if (r.status === 429) throw new Error('Too many attempts. Please wait ~30 seconds and try again.');
const j = await r.json().catch(() => ({}));
throw new Error(j.error || 'Reset failed');
}
setOk(true);
} catch (err) {
setError(err.message || 'Reset failed');
} finally {
setSubmitting(false);
}
}
if (ok) {
return (
<div className="max-w-md mx-auto bg-white p-6 rounded shadow">
<h2 className="text-xl font-semibold mb-2">Password updated</h2>
<p className="text-sm text-gray-700">You can now sign in with your new password.</p>
<div className="mt-4">
<Button
onClick={() => {
try {
localStorage.removeItem('token');
localStorage.removeItem('id');
} catch {}
window.location.replace('/signin');
}}
>
Go to Sign In
</Button>
</div>
</div>
);
}
if (!token) {
return (
<div className="max-w-md mx-auto bg-white p-6 rounded shadow">
<h2 className="text-xl font-semibold mb-2">Invalid link</h2>
<p className="text-sm text-gray-700">This reset link is missing or malformed.</p>
<Link className="text-blue-600 underline text-sm" to="/forgot-password">Request a new link</Link>
</div>
);
}
return (
<div className="max-w-md mx-auto bg-white p-6 rounded shadow">
<h2 className="text-xl font-semibold mb-3">Set a new password</h2>
<form onSubmit={onSubmit} className="space-y-3">
<div>
<label className="block text-sm mb-1">New password</label>
<input
type="password"
className="w-full border rounded px-3 py-2"
value={pw}
onChange={(e) => setPw(e.target.value)}
autoComplete="new-password"
required
minLength={8}
/>
<p className="mt-1 text-xs text-gray-500">{passwordHelp}</p>
</div>
<div>
<label className="block text-sm mb-1">Confirm password</label>
<input
type="password"
className="w-full border rounded px-3 py-2"
value={pw2}
onChange={(e) => setPw2(e.target.value)}
autoComplete="new-password"
required
minLength={8}
/>
</div>
{error && <p className="text-red-600 text-xs">{error}</p>}
<Button type="submit" disabled={submitting}>
{submitting ? 'Saving…' : 'Update password'}
</Button>
</form>
<div className="mt-3">
<Link className="text-blue-600 underline text-sm" to="/forgot-password">Need a new link?</Link>
</div>
</div>
);
}