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 ( + <> +
+ 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!
+ )} +