From 6b07d51cb1bfbaffd6408adedf6c980ca88b9fa3 Mon Sep 17 00:00:00 2001 From: KS Jannette Date: Mon, 11 May 2026 22:13:53 -0400 Subject: [PATCH] add email verification step to new user onboarding flow --- frontend/src/contexts/AuthContext.jsx | 16 ++++ frontend/src/pages/subscribe/Subscribe.css | 17 ++++ frontend/src/pages/subscribe/Subscribe.jsx | 95 +++++++++++++++++++--- 3 files changed, 116 insertions(+), 12 deletions(-) diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx index cb375e6..269bd94 100644 --- a/frontend/src/contexts/AuthContext.jsx +++ b/frontend/src/contexts/AuthContext.jsx @@ -4,6 +4,7 @@ import { signInWithEmailAndPassword, createUserWithEmailAndPassword, signOut, + sendEmailVerification as firebaseSendEmailVerification, } from "firebase/auth"; import { auth } from "../firebase/config"; import { getAccount } from "../api/account"; @@ -68,6 +69,19 @@ export function AuthProvider({ children }) { await signOut(auth); } + async function sendEmailVerification() { + if (auth.currentUser) { + await firebaseSendEmailVerification(auth.currentUser); + } + } + + async function reloadUser() { + if (auth.currentUser) { + await auth.currentUser.reload(); + setCurrentUser({ ...auth.currentUser }); + } + } + const value = { currentUser, userTier, @@ -77,6 +91,8 @@ export function AuthProvider({ children }) { signup, login, logout, + sendEmailVerification, + reloadUser, refreshAccount: fetchAccount, }; diff --git a/frontend/src/pages/subscribe/Subscribe.css b/frontend/src/pages/subscribe/Subscribe.css index b2b48f7..531f7d6 100644 --- a/frontend/src/pages/subscribe/Subscribe.css +++ b/frontend/src/pages/subscribe/Subscribe.css @@ -128,6 +128,23 @@ font-size: 0.9rem; } +.subscribe__subtitle strong { + color: var(--color-primary); +} + +/* Verify email actions */ +.subscribe__verify-actions { + text-align: center; + margin-top: 1rem; +} + +.subscribe__verify-sent { + color: var(--color-success, #22c55e); + text-align: center; + font-size: 0.9rem; + margin-bottom: 0.5rem; +} + /* Subscribe card (Step 2 tier cards) */ .subscribe-card { background-color: var(--color-bg-card, #1a1a2e); diff --git a/frontend/src/pages/subscribe/Subscribe.jsx b/frontend/src/pages/subscribe/Subscribe.jsx index ebbf99f..10c151d 100644 --- a/frontend/src/pages/subscribe/Subscribe.jsx +++ b/frontend/src/pages/subscribe/Subscribe.jsx @@ -10,12 +10,12 @@ import Button from "../../components/Button"; import TierPicker from "../../components/TierPicker"; import "./Subscribe.css"; -const STEPS = ["Create Account", "Choose Plan"]; +const STEPS = ["Create Account", "Verify Email", "Choose Plan"]; export default function Subscribe() { const navigate = useNavigate(); const [searchParams] = useSearchParams(); - const { currentUser, signup, refreshAccount } = useAuth(); + const { currentUser, signup, sendEmailVerification, reloadUser, refreshAccount } = useAuth(); const queryParameters = new URLSearchParams(window.location.search) const success = queryParameters?.get("payment") @@ -30,7 +30,10 @@ export default function Subscribe() { setTimeout(forward, 2000, success, session_id); - const [step, setStep] = useState(currentUser ? 2 : 1); + const [step, setStep] = useState( + currentUser ? (currentUser.emailVerified ? 3 : 2) : 1 + ); + const [verificationSent, setVerificationSent] = useState(false); const [data, setData] = useState({ email: currentUser?.email || "", password: "", @@ -65,6 +68,8 @@ export default function Subscribe() { setLoading(true); try { await signup(data.email, data.password); + await sendEmailVerification(); + setVerificationSent(true); setStep(2); } catch (err) { setError(err.message || "Failed to create account"); @@ -74,6 +79,44 @@ export default function Subscribe() { } async function handleStep2() { + setError(""); + setLoading(true); + try { + await reloadUser(); + const { currentUser: user } = await import("../../firebase/config").then( + (m) => ({ currentUser: m.auth.currentUser }) + ); + if (user?.emailVerified) { + setStep(3); + } else { + setError("Email not yet verified. Please check your inbox and click the verification link."); + } + } catch (err) { + setError(err.message || "Failed to check verification status"); + } finally { + setLoading(false); + } + } + + async function handleResendVerification() { + setError(""); + setLoading(true); + try { + await sendEmailVerification(); + setVerificationSent(true); + setError(""); + } catch (err) { + if (err.code === "auth/too-many-requests") { + setError("Too many requests. Please wait a moment before trying again."); + } else { + setError(err.message || "Failed to resend verification email"); + } + } finally { + setLoading(false); + } + } + + async function handleStep3() { setError(""); if (!data.selectedTier) { setError("Please select a plan"); @@ -173,6 +216,30 @@ export default function Subscribe() { ); } + function StepVerifyEmail() { + return ( + <> +

Verify your email

+

+ We've sent a verification link to {currentUser?.email}. + Please check your inbox and click the link to verify your email address. +

+ {verificationSent && !error && ( +

Verification email sent!

+ )} +
+ +
+ + ); + } + function StepChoosePlan() { return ( <> @@ -194,6 +261,7 @@ export default function Subscribe() { async function handleNext() { if (step === 1) handleStep1(); else if (step === 2) await handleStep2(); + else if (step === 3) await handleStep3(); } function handleBack() { @@ -204,16 +272,18 @@ export default function Subscribe() { const nextLabel = step === 1 ? "Create Account" - : !data.selectedTier - ? "Continue" - : data.selectedTier === "free" - ? "Start Free Trial" - : "Subscribe & Continue"; + : step === 2 + ? "I've verified my email" + : !data.selectedTier + ? "Continue" + : data.selectedTier === "free" + ? "Start Free Trial" + : "Subscribe & Continue"; return (
- {step === 2 && ( + {step > 1 && ( @@ -222,7 +292,7 @@ export default function Subscribe() {