Files
vyndr/web/src/app/forgot-password/page.tsx
T

107 lines
4.0 KiB
TypeScript

'use client';
import { useState } from 'react';
import { getBrowserSupabase } from '@/lib/supabase';
import Wordmark from '@/components/Wordmark';
export default function ForgotPasswordPage() {
const [email, setEmail] = useState('');
const [busy, setBusy] = useState(false);
const [sent, setSent] = useState(false);
const [error, setError] = useState('');
const request = async (e?: React.FormEvent) => {
e?.preventDefault();
setError('');
if (!email) return setError('Enter your email.');
setBusy(true);
const supabase = getBrowserSupabase();
if (!supabase) {
setBusy(false);
return setError('Auth is not configured.');
}
const { error: err } = await supabase.auth.resetPasswordForEmail(email, {
redirectTo: `${window.location.origin}/auth/callback?next=/profile`,
});
setBusy(false);
if (err) return setError(err.message);
setSent(true);
};
return (
<section style={{ minHeight: '80vh', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '32px 16px' }}>
<div className="surface diagonal-cut animate-fade-up" style={{ width: '100%', maxWidth: 420, padding: 32 }}>
<a
href="/"
style={{ display: 'flex', justifyContent: 'center', color: 'var(--text-0)', textDecoration: 'none', marginBottom: 24 }}
aria-label="VYNDR — home"
>
<Wordmark size={26} />
</a>
{!sent ? (
<>
<h1 style={{ fontSize: 22, fontWeight: 700, textAlign: 'center', marginBottom: 6 }}>Reset your password</h1>
<p style={{ textAlign: 'center', color: 'var(--text-1)', fontSize: 13, marginBottom: 24 }}>
Enter your email. We&apos;ll send you a reset link.
</p>
<form onSubmit={request} style={{ display: 'grid', gap: 12 }}>
<div>
<label className="lbl" style={{ display: 'block', marginBottom: 6 }}>Email</label>
<input
type="email"
required
autoComplete="email"
className="input-field"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</div>
{error && <p style={{ color: 'var(--grade-d)', fontSize: 13, margin: 0 }}>{error}</p>}
<button type="submit" disabled={busy} className="btn-primary" style={{ padding: 14, marginTop: 4 }}>
{busy ? 'Sending…' : 'Send Reset Link'}
</button>
</form>
</>
) : (
<ConfirmationBlock email={email} onResend={request} busy={busy} />
)}
<p style={{ textAlign: 'center', fontSize: 13, color: 'var(--text-1)', marginTop: 20 }}>
<a href="/login" style={{ color: 'var(--text-1)' }}> Back to sign in</a>
</p>
</div>
</section>
);
}
function ConfirmationBlock({ email, onResend, busy }: { email: string; onResend: () => void; busy: boolean }) {
return (
<div style={{ textAlign: 'center', display: 'grid', gap: 8, justifyItems: 'center' }}>
<EnvelopeIcon />
<h2 style={{ fontSize: 20, fontWeight: 700 }}>Check your inbox</h2>
<p style={{ color: 'var(--text-1)', fontSize: 14, maxWidth: 320 }}>
We sent a reset link to <span className="mono" style={{ color: 'var(--text-0)' }}>{email}</span>. It expires in 1 hour.
</p>
<button
type="button"
onClick={onResend}
disabled={busy}
className="btn-ghost"
style={{ marginTop: 8, fontSize: 13 }}
>
{busy ? 'Resending…' : "Didn't get it? Resend"}
</button>
</div>
);
}
function EnvelopeIcon() {
return (
<svg width="56" height="56" viewBox="0 0 24 24" fill="none" aria-hidden>
<rect x="3" y="5" width="18" height="14" rx="2" stroke="var(--grade-a)" strokeWidth="1.5" />
<path d="M3 7l9 6 9-6" stroke="var(--grade-a)" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
}