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..6a5584b 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, useSyncExternalStore } from "react"; import { onAuthStateChanged, signInWithEmailAndPassword, @@ -17,88 +17,98 @@ 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 fetchAccount = useCallback(async () => { + 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" || + setState({ + userTier: data.subscription_tier || "free", + tierLimits: data.tier_limits || DEFAULT_TIER_LIMITS, + isSubscribed: + data.subscription_status === "active" || data.subscription_status === "trialing", - ); + }); } catch { - setUserTier("free"); - setTierLimits(DEFAULT_TIER_LIMITS); - setIsSubscribed(false); + setState({ isSubscribed: false }); } - }, []); + }; - useEffect(() => { - const unsubscribe = onAuthStateChanged(auth, async (user) => { - setCurrentUser(user); - if (user) { - await fetchAccount(); - } else { - setUserTier("free"); - setTierLimits(DEFAULT_TIER_LIMITS); - setIsSubscribed(false); - } - setLoading(false); - }); - return unsubscribe; - }, [fetchAccount]); + // Set up Firebase listener once, outside of 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 }); + }); - async function signup(email, password) { - const cred = await createUserWithEmailAndPassword(auth, email, password); - return cred.user; - } + return { + subscribe: (listener) => { + listeners.add(listener); + return () => listeners.delete(listener); + }, + getSnapshot: () => state, + refreshAccount: fetchAccount, + }; +}; - async function login(email, password) { - const cred = await signInWithEmailAndPassword(auth, email, password); - return cred.user; - } +const authStore = createAuthStore(); - async function logout() { - await signOut(auth); - } +export function AuthProvider({ children }) { + const state = useSyncExternalStore(authStore.subscribe, authStore.getSnapshot); - async function sendEmailVerification() { + const signup = (email, password) => createUserWithEmailAndPassword(auth, email, password); + const login = (email, password) => signInWithEmailAndPassword(auth, email, password); + const logout = () => signOut(auth); + + 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, + ...state, signup, login, logout, sendEmailVerification, reloadUser, - refreshAccount: fetchAccount, + refreshAccount: authStore.refreshAccount, }; - return {children}; + return ( + + {!state.loading && children} + + ); } export function useAuth() {