Sessions 5-7a: 955 tests, deployment ready
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
'use client';
|
||||
|
||||
import { Suspense, useEffect, useState } from 'react';
|
||||
import { useRouter, useSearchParams } from 'next/navigation';
|
||||
import { useAuth } from '@/contexts/AuthContext';
|
||||
import { getBrowserSupabase } from '@/lib/supabase';
|
||||
import Wordmark from '@/components/Wordmark';
|
||||
|
||||
const RESEND_COOLDOWN_SECONDS = 60;
|
||||
|
||||
function VerifyInner() {
|
||||
const router = useRouter();
|
||||
const search = useSearchParams();
|
||||
const emailFromQuery = search.get('email') || '';
|
||||
const { user } = useAuth();
|
||||
const [email, setEmail] = useState(emailFromQuery || user?.email || '');
|
||||
const [cooldown, setCooldown] = useState(0);
|
||||
const [sending, setSending] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [resentAt, setResentAt] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (user?.email_confirmed_at) {
|
||||
router.replace('/welcome');
|
||||
}
|
||||
}, [user, router]);
|
||||
|
||||
useEffect(() => {
|
||||
if (cooldown <= 0) return;
|
||||
const id = setInterval(() => setCooldown((c) => Math.max(0, c - 1)), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, [cooldown]);
|
||||
|
||||
const resend = async () => {
|
||||
setError('');
|
||||
if (!email) return setError('Enter the email you signed up with.');
|
||||
if (cooldown > 0) return;
|
||||
setSending(true);
|
||||
const supabase = getBrowserSupabase();
|
||||
if (!supabase) {
|
||||
setSending(false);
|
||||
return setError('Auth is not configured.');
|
||||
}
|
||||
const { error: err } = await supabase.auth.resend({ type: 'signup', email });
|
||||
setSending(false);
|
||||
if (err) return setError(err.message);
|
||||
setResentAt(Date.now());
|
||||
setCooldown(RESEND_COOLDOWN_SECONDS);
|
||||
};
|
||||
|
||||
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>
|
||||
|
||||
<div style={{ textAlign: 'center', display: 'grid', gap: 8, justifyItems: 'center' }}>
|
||||
<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>
|
||||
<h1 style={{ fontSize: 22, fontWeight: 700 }}>Check your inbox</h1>
|
||||
<p style={{ color: 'var(--text-1)', fontSize: 14, maxWidth: 320 }}>
|
||||
We sent a verification link to{' '}
|
||||
<span className="mono" style={{ color: 'var(--text-0)' }}>{email || 'your email'}</span>.
|
||||
</p>
|
||||
|
||||
{!emailFromQuery && !user && (
|
||||
<input
|
||||
type="email"
|
||||
placeholder="you@vyndr.app"
|
||||
className="input-field"
|
||||
style={{ marginTop: 8, maxWidth: 280 }}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{error && <p style={{ color: 'var(--grade-d)', fontSize: 13 }}>{error}</p>}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={resend}
|
||||
disabled={sending || cooldown > 0}
|
||||
className="btn-primary"
|
||||
style={{ marginTop: 12 }}
|
||||
>
|
||||
{sending ? 'Sending…' : cooldown > 0 ? `Resend in ${cooldown}s` : "Didn't get it? Resend"}
|
||||
</button>
|
||||
|
||||
{resentAt ? (
|
||||
<p className="lbl" style={{ color: 'var(--grade-a)', marginTop: 4 }}>SENT</p>
|
||||
) : null}
|
||||
|
||||
<p style={{ fontSize: 13, color: 'var(--text-1)', marginTop: 16 }}>
|
||||
Wrong email?{' '}
|
||||
<a href="/signup" style={{ color: 'var(--grade-a)' }}>Sign up again</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function VerifyPage() {
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<VerifyInner />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user