Sessions 5-7a: 955 tests, deployment ready
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
'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'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user