diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 52afc9c..d2c25e7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,6 +1,5 @@ import { Routes, Route, Navigate } from "react-router-dom"; import { useAuth } from "./contexts/AuthContext"; -import { useContext } from 'react' import Navbar from "./components/Navbar"; import Login from "./pages/login/Login"; import Signup from "./pages/Signup"; @@ -9,11 +8,11 @@ import Addresses from "./pages/addresses/Addresses"; import Alerts from "./pages/alerts/Alerts"; import AlertHistory from "./pages/alertHistory/AlertHistory"; import Account from "./pages/user_account/Account"; -import { AuthProvider } from "./ShopContext"; -import useAuth from "./ShopContext"; export default function App() { - const { context } = useContext(ShopContext) + const { currentUser, isSubscribed, loading } = useAuth(); + + if (loading) return null; if (!currentUser) { return ( diff --git a/frontend/src/contexts/AuthContext.jsx b/frontend/src/contexts/AuthContext.jsx index 47cc435..cb375e6 100644 --- a/frontend/src/contexts/AuthContext.jsx +++ b/frontend/src/contexts/AuthContext.jsx @@ -1,54 +1,94 @@ -import { createContext, useReducer, useContext } from "react"; -import shopReducer, { initialState } from "./shopReducer"; +import { createContext, useContext, useState, useEffect, useCallback } from "react"; +import { + onAuthStateChanged, + signInWithEmailAndPassword, + createUserWithEmailAndPassword, + signOut, +} from "firebase/auth"; +import { auth } from "../firebase/config"; +import { getAccount } from "../api/account"; -const AuthContext = createContext(initialState); +const AuthContext = createContext(undefined); -export const AuthProvider = ({ children }) => { - const [state, dispatch] = useReducer(shopReducer, initialState); - - const addAuthUser = (args) => { - const updatedUser = state.user.concat(product); - updatePrice(updatedCart); - dispatch({ - type: "ADD_AUTH_USER", - payload: { - args: newUser - } - }); - }; - - const logoutAuthUser = (args) => { - const updatedUser = state.user.filter( - (currentUser) => currentUser.name !== args.name - ); - updateUser(updatedCart); - - dispatch({ - type: "REMOVE_AUTH_USER", - payload: { - args: updatedCart - } - }); - }; - - - const value = { - authUser: state.name, - authUserTier: state.tier, - addAuthUser, - logoutAuthUser - }; - return {children}; +const DEFAULT_TIER_LIMITS = { + max_addresses: 1, + max_alert_types: 1, + allowed_channels: ["email"], }; -const useAuth = () => { - const context = useContext(ShopContext); +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); - if (context === undefined) { - throw new Error("authUser must be used within AuthContext"); + const fetchAccount = useCallback(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", + ); + } catch { + setUserTier("free"); + setTierLimits(DEFAULT_TIER_LIMITS); + setIsSubscribed(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]); + + async function signup(email, password) { + const cred = await createUserWithEmailAndPassword(auth, email, password); + return cred.user; } + async function login(email, password) { + const cred = await signInWithEmailAndPassword(auth, email, password); + return cred.user; + } + + async function logout() { + await signOut(auth); + } + + const value = { + currentUser, + userTier, + tierLimits, + isSubscribed, + loading, + signup, + login, + logout, + refreshAccount: fetchAccount, + }; + + return {children}; +} + +export function useAuth() { + const context = useContext(AuthContext); + if (context === undefined) { + throw new Error("useAuth must be used within an AuthProvider"); + } return context; -}; +} export default useAuth; diff --git a/frontend/src/contexts/UserPropertiesContext.jsx b/frontend/src/contexts/UserPropertiesContext.jsx new file mode 100644 index 0000000..0bb91a4 --- /dev/null +++ b/frontend/src/contexts/UserPropertiesContext.jsx @@ -0,0 +1,44 @@ +import { createContext, useReducer, useContext } from "react"; + +const initialState = { + email: "", + password: "", +}; + +const ACTION_TYPES = { + SET_USER_PROPERTIES: "SET_USER_PROPERTIES", + CLEAR_USER_PROPERTIES: "CLEAR_USER_PROPERTIES", +}; + +function userPropertiesReducer(state, action) { + switch (action.type) { + case ACTION_TYPES.SET_USER_PROPERTIES: + return { ...state, ...action.payload }; + case ACTION_TYPES.CLEAR_USER_PROPERTIES: + return { ...initialState }; + default: + return state; + } +} + +const UserPropertiesContext = createContext(undefined); + +export function UserPropertiesProvider({ children }) { + const [state, dispatch] = useReducer(userPropertiesReducer, initialState); + + return ( + + {children} + + ); +} + +export function useUserProperties() { + const context = useContext(UserPropertiesContext); + if (context === undefined) { + throw new Error( + "useUserProperties must be used within a UserPropertiesProvider", + ); + } + return context; +} diff --git a/frontend/src/pages/subscribe/Subscribe.jsx b/frontend/src/pages/subscribe/Subscribe.jsx index 98e44e7..7a9560a 100644 --- a/frontend/src/pages/subscribe/Subscribe.jsx +++ b/frontend/src/pages/subscribe/Subscribe.jsx @@ -141,6 +141,8 @@ export default function Subscribe() { return; } + sessionStorage.setItem("kp_onboard_email", data.email); + sessionStorage.setItem("kp_onboard_pw", data.password); dispatch({ type: ACTION_TYPES.SET_USER_PROPERTIES, payload: { email: data.email, password: data.password }, diff --git a/frontend/src/reducers/authReducer.jsx b/frontend/src/reducers/authReducer.jsx deleted file mode 100644 index dd1c505..0000000 --- a/frontend/src/reducers/authReducer.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useReducer } from "react" - -const UserAuthContext = createContext(); - -export const initialState = { - authUser: null, - AuthTier: null -}; - -const authReducer = (state, action) => { - const { type, payload = "FREE_TIER" } = action - - switch (type) { - case "ADD_AUTH_USER": - console.log("ADD_AUTH_USER", payload); - return { - ...state, - auth: payload - }; - default: - throw new Error(`No case for type ${type} found.`); - } - switch (type) { - case "DELETE_AUTH_USER": - return { - ...state, - users: state.users.filter(user => user.id !== action.userIdToDelete) - }; - } -} - -export default shopReducer \ No newline at end of file