106 lines
3.4 KiB
TypeScript
106 lines
3.4 KiB
TypeScript
import { createSignal, Show } from 'solid-js';
|
|
import { useNavigate, useSearchParams } from '@solidjs/router';
|
|
|
|
const API = import.meta.env.VITE_API_URL ?? 'http://localhost:8000';
|
|
|
|
export default function ResetPassword() {
|
|
const [params] = useSearchParams();
|
|
const navigate = useNavigate();
|
|
const [password, setPassword] = createSignal('');
|
|
const [confirm, setConfirm] = createSignal('');
|
|
const [error, setError] = createSignal('');
|
|
const [loading, setLoading] = createSignal(false);
|
|
const [success, setSuccess] = createSignal(false);
|
|
|
|
async function handleReset(e: Event) {
|
|
e.preventDefault();
|
|
if (password() !== confirm()) {
|
|
setError('Passwords do not match');
|
|
return;
|
|
}
|
|
|
|
setLoading(true);
|
|
setError('');
|
|
|
|
try {
|
|
const res = await fetch(`${API}/api/auth/reset-password`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
token: params.token,
|
|
new_password: password()
|
|
}),
|
|
});
|
|
|
|
if (res.ok) {
|
|
setSuccess(true);
|
|
setTimeout(() => navigate('/login'), 2000);
|
|
} else {
|
|
const data = await res.json();
|
|
setError(data.error ?? 'Invalid or expired reset link');
|
|
}
|
|
} catch (e) {
|
|
setError('Network error. Please try again.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div style={{
|
|
height: '100vh', display: 'flex', 'align-items': 'center', 'justify-content': 'center',
|
|
background: 'linear-gradient(135deg, #f0fdf4 0%, #fff 100%)'
|
|
}}>
|
|
<div class="form-card" style={{ 'max-width': '400px', width: '100%', padding: '40px' }}>
|
|
<div style={{ 'text-align': 'center', 'margin-bottom': '24px' }}>
|
|
<h1 style={{ 'font-size': '24px', 'font-weight': '800', margin: 0 }}>Reset Password</h1>
|
|
<p style={{ color: '#64748b', 'font-size': '14px', 'margin-top': '8px' }}>
|
|
Choose a new secure password.
|
|
</p>
|
|
</div>
|
|
|
|
<Show when={success()}>
|
|
<div class="status-banner status-banner--success" style={{ 'margin-bottom': '20px' }}>
|
|
✓ Password reset! Redirecting to login...
|
|
</div>
|
|
</Show>
|
|
|
|
<Show when={error()}>
|
|
<div class="error-banner" style={{ 'margin-bottom': '20px' }}>{error()}</div>
|
|
</Show>
|
|
|
|
<form onSubmit={handleReset} style={{ display: 'flex', 'flex-direction': 'column', gap: '20px' }}>
|
|
<div class="field">
|
|
<label class="label">New Password</label>
|
|
<input
|
|
class="input"
|
|
type="password"
|
|
placeholder="••••••••"
|
|
minLength={8}
|
|
value={password()}
|
|
onInput={(e) => setPassword(e.currentTarget.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label class="label">Confirm New Password</label>
|
|
<input
|
|
class="input"
|
|
type="password"
|
|
placeholder="••••••••"
|
|
minLength={8}
|
|
value={confirm()}
|
|
onInput={(e) => setConfirm(e.currentTarget.value)}
|
|
required
|
|
/>
|
|
</div>
|
|
|
|
<button class="btn btn-primary" type="submit" disabled={loading() || success()} style={{ width: '100%' }}>
|
|
{loading() ? 'Updating...' : 'Reset Password'}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|