From 8b2db08db1f6739e032c75e7e637217af645d43f Mon Sep 17 00:00:00 2001 From: KS Jannette Date: Tue, 12 May 2026 04:53:53 -0400 Subject: [PATCH 1/3] simplified auth context provider --- frontend/src/App.jsx | 4 +- frontend/src/contexts/AuthContext.jsx | 55 ++++++++++++--------------- 2 files changed, 26 insertions(+), 33 deletions(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a99c4ac..014f142 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,9 +11,7 @@ import AlertHistory from "./pages/alertHistory/AlertHistory"; import Account from "./pages/user_account/Account"; export default function App() { - const { currentUser, isSubscribed, loading } = useAuth(); - - if (loading) return null; + const { currentUser, isSubscribed } = useAuth(); if (!currentUser) { return ( diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx index 3505bc6..43701c6 100644 --- a/frontend/src/contexts/AuthContext.jsx +++ b/frontend/src/contexts/AuthContext.jsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useState, useEffect, useCallback } from "react"; +import { createContext, useContext, useState, useEffect } from "react"; import { onAuthStateChanged, signInWithEmailAndPassword, @@ -24,21 +24,23 @@ export function AuthProvider({ children }) { const [isSubscribed, setIsSubscribed] = useState(false); const [loading, setLoading] = useState(true); - const fetchAccount = useCallback(async () => { + const resetAccountState = () => { + setIsSubscribed(false); + }; + + const fetchAccount = async () => { try { const data = await getAccount(); setUserTier(data.subscription_tier || "free"); setTierLimits(data.tier_limits || DEFAULT_TIER_LIMITS); setIsSubscribed( data.subscription_status === "active" || - data.subscription_status === "trialing", + data.subscription_status === "trialing", ); } catch { - setUserTier("free"); - setTierLimits(DEFAULT_TIER_LIMITS); - setIsSubscribed(false); + resetAccountState(); } - }, []); + }; useEffect(() => { const unsubscribe = onAuthStateChanged(auth, async (user) => { @@ -46,50 +48,39 @@ export function AuthProvider({ children }) { if (user) { await fetchAccount(); } else { - setUserTier("free"); - setTierLimits(DEFAULT_TIER_LIMITS); - setIsSubscribed(false); + resetAccountState(); } setLoading(false); }); - return unsubscribe; - }, [fetchAccount]); + return () => unsubscribe(); + }, []); - async function signup(email, password) { - const cred = await createUserWithEmailAndPassword(auth, email, password); - return cred.user; - } + const signup = (email, password) => createUserWithEmailAndPassword(auth, email, password); - async function login(email, password) { - const cred = await signInWithEmailAndPassword(auth, email, password); - return cred.user; - } + const login = (email, password) => signInWithEmailAndPassword(auth, email, password); - async function logout() { - await signOut(auth); - } + const logout = () => signOut(auth); - async function sendEmailVerification() { + const sendEmailVerification = () => { if (auth.currentUser) { - await firebaseSendEmailVerification(auth.currentUser); + return firebaseSendEmailVerification(auth.currentUser); } - } + }; - async function reloadUser() { + const reloadUser = async () => { if (auth.currentUser) { await auth.currentUser.reload(); setCurrentUser({ ...auth.currentUser }); return auth.currentUser; } return null; - } + }; const value = { currentUser, userTier, tierLimits, isSubscribed, - loading, signup, login, logout, @@ -98,7 +89,11 @@ export function AuthProvider({ children }) { refreshAccount: fetchAccount, }; - return {children}; + return ( + + {!loading && children} + + ); } export function useAuth() { From c4371f7886dfd0df82feba053ef35b6ddf63a2f2 Mon Sep 17 00:00:00 2001 From: KS Jannette Date: Tue, 12 May 2026 05:14:12 -0400 Subject: [PATCH 2/3] fixed login race condition --- frontend/src/contexts/AuthContext.jsx | 89 ++++++++++++++++----------- 1 file changed, 52 insertions(+), 37 deletions(-) diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx index 43701c6..317a570 100644 --- a/frontend/src/contexts/AuthContext.jsx +++ b/frontend/src/contexts/AuthContext.jsx @@ -1,4 +1,4 @@ -import { createContext, useContext, useState, useEffect } from "react"; +import { createContext, useContext, useSyncExternalStore } from "react"; import { onAuthStateChanged, signInWithEmailAndPassword, @@ -17,48 +17,67 @@ const DEFAULT_TIER_LIMITS = { allowed_channels: ["email"], }; -export function AuthProvider({ children }) { - const [currentUser, setCurrentUser] = useState(null); - const [userTier, setUserTier] = useState("free"); - const [tierLimits, setTierLimits] = useState(DEFAULT_TIER_LIMITS); - const [isSubscribed, setIsSubscribed] = useState(false); - const [loading, setLoading] = useState(true); +// External auth store - lives outside React +const createAuthStore = () => { + let state = { + currentUser: null, + userTier: "free", + tierLimits: DEFAULT_TIER_LIMITS, + isSubscribed: false, + loading: true, + }; + const listeners = new Set(); - const resetAccountState = () => { - setIsSubscribed(false); + const notify = () => listeners.forEach((fn) => fn()); + + const setState = (partial) => { + state = { ...state, ...partial }; + notify(); }; const fetchAccount = async () => { try { const data = await getAccount(); - setUserTier(data.subscription_tier || "free"); - setTierLimits(data.tier_limits || DEFAULT_TIER_LIMITS); - setIsSubscribed( - data.subscription_status === "active" || - data.subscription_status === "trialing", - ); + setState({ + userTier: data.subscription_tier || "free", + tierLimits: data.tier_limits || DEFAULT_TIER_LIMITS, + isSubscribed: + data.subscription_status === "active" || + data.subscription_status === "trialing", + }); } catch { - resetAccountState(); + setState({ isSubscribed: false }); } }; - useEffect(() => { - const unsubscribe = onAuthStateChanged(auth, async (user) => { - setCurrentUser(user); - if (user) { - await fetchAccount(); - } else { - resetAccountState(); - } - setLoading(false); - }); - return () => unsubscribe(); - }, []); + // Set up Firebase listener once, outside React + onAuthStateChanged(auth, async (user) => { + setState({ loading: true, currentUser: user }); + if (user) { + await fetchAccount(); + } else { + setState({ isSubscribed: false, userTier: "free", tierLimits: DEFAULT_TIER_LIMITS }); + } + setState({ loading: false }); + }); + + return { + subscribe: (listener) => { + listeners.add(listener); + return () => listeners.delete(listener); + }, + getSnapshot: () => state, + refreshAccount: fetchAccount, + }; +}; + +const authStore = createAuthStore(); + +export function AuthProvider({ children }) { + const state = useSyncExternalStore(authStore.subscribe, authStore.getSnapshot); const signup = (email, password) => createUserWithEmailAndPassword(auth, email, password); - const login = (email, password) => signInWithEmailAndPassword(auth, email, password); - const logout = () => signOut(auth); const sendEmailVerification = () => { @@ -70,28 +89,24 @@ export function AuthProvider({ children }) { const reloadUser = async () => { if (auth.currentUser) { await auth.currentUser.reload(); - setCurrentUser({ ...auth.currentUser }); return auth.currentUser; } return null; }; const value = { - currentUser, - userTier, - tierLimits, - isSubscribed, + ...state, signup, login, logout, sendEmailVerification, reloadUser, - refreshAccount: fetchAccount, + refreshAccount: authStore.refreshAccount, }; return ( - {!loading && children} + {!state.loading && children} ); } From cea031334e9ec2f03413c3eb009238208ae017ea Mon Sep 17 00:00:00 2001 From: KS Jannette Date: Tue, 12 May 2026 05:18:33 -0400 Subject: [PATCH 3/3] add comment --- frontend/src/contexts/AuthContext.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx index 317a570..6a5584b 100644 --- a/frontend/src/contexts/AuthContext.jsx +++ b/frontend/src/contexts/AuthContext.jsx @@ -50,7 +50,7 @@ const createAuthStore = () => { } }; - // Set up Firebase listener once, outside React + // Set up Firebase listener once, outside of React onAuthStateChanged(auth, async (user) => { setState({ loading: true, currentUser: user }); if (user) {