This commit is contained in:
KS Jannette
2026-03-29 01:31:20 -04:00
parent 3a52e7afb3
commit c0aaaedaf1
4 changed files with 69 additions and 200 deletions

View File

@@ -1,140 +1,52 @@
/**
* AuthContext - Firebase Authentication State Management
*
* Provides authentication state, tier info, and methods throughout the app
*/
import { createContext, useReducer, useContext } from "react";
import shopReducer, { initialState } from "./shopReducer";
import { createContext, useContext, useEffect, useState, useCallback } from "react";
import {
createUserWithEmailAndPassword,
signInWithEmailAndPassword,
signOut,
onAuthStateChanged,
} from "firebase/auth";
import { auth } from "../firebase/config";
import { getAccount } from "../api/account";
const AuthContext = createContext(initialState);
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [state, dispatch] = useReducer(shopReducer, initialState);
const DEFAULT_TIER_LIMITS = {
max_addresses: 1,
max_alert_types: 1,
allowed_channels: ["email"],
const addAuthUser = (args) => {
const updatedUser = state.user.concat(product);
updatePrice(updatedCart);
dispatch({
type: "ADD_AUTH_USER",
payload: {
args: updatedCart
}
});
};
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 = {
user: state.name,
user: state.tier
};
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
};
/**
* Hook to access auth context
* @returns {Object} Auth context value
*/
export function useAuth() {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within AuthProvider");
}
return context;
}
const useAuthContext = () => {
const context = useContext(ShopContext);
/**
* AuthProvider - Wraps app and provides auth state + tier info
*/
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
if (context === undefined) {
throw new Error("authUser must be used within AuthContext");
}
const [userTier, setUserTier] = useState("free");
const [tierLimits, setTierLimits] = useState(DEFAULT_TIER_LIMITS);
const [addressCount, setAddressCount] = useState(0);
const [subscriptionStatus, setSubscriptionStatus] = useState("none");
return context;
};
const refreshAccount = useCallback(async () => {
try {
const data = await getAccount();
setUserTier(data.subscription_tier || "free");
setTierLimits(data.tier_limits || DEFAULT_TIER_LIMITS);
setAddressCount(data.address_count || 0);
setSubscriptionStatus(data.subscription_status || "none");
} catch {
// account fetch can fail during onboarding before subscription is active
}
}, []);
async function signup(email, password) {
try {
setError(null);
const result = await createUserWithEmailAndPassword(
auth,
email,
password,
);
return result.user;
} catch (err) {
setError(err.message);
throw err;
}
}
async function login(email, password) {
try {
setError(null);
const result = await signInWithEmailAndPassword(
auth,
email,
password,
);
return result.user;
} catch (err) {
setError(err.message);
throw err;
}
}
async function logout() {
try {
setError(null);
setUserTier("free");
setTierLimits(DEFAULT_TIER_LIMITS);
setAddressCount(0);
setSubscriptionStatus("none");
await signOut(auth);
} catch (err) {
setError(err.message);
throw err;
}
}
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
setCurrentUser(user);
setLoading(false);
if (user) {
refreshAccount();
}
});
return unsubscribe;
}, [refreshAccount]);
const isSubscribed =
subscriptionStatus === "active" || subscriptionStatus === "trialing";
const value = {
currentUser,
signup,
login,
logout,
error,
loading,
userTier,
tierLimits,
addressCount,
subscriptionStatus,
isSubscribed,
refreshAccount,
};
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
}
export default useAuthContext;

View File

@@ -1,48 +0,0 @@
import { createContext, useContext, useReducer, useEffect } from "react";
import {
userPropertiesReducer,
initialState,
ACTION_TYPES,
} from "./userPropertiesReducer";
const STORAGE_KEY = "kp_onboard_user";
function hydrateState() {
try {
const raw = sessionStorage.getItem(STORAGE_KEY);
if (raw) return { ...initialState, ...JSON.parse(raw) };
} catch {
/* corrupt data — fall through */
}
return initialState;
}
const UserPropertiesContext = createContext(initialState);
export function useUserProperties() {
const ctx = useContext(UserPropertiesContext);
if (!ctx) {
throw new Error(
"useUserProperties must be used within UserPropertiesProvider",
);
}
return ctx;
}
export function UserPropertiesProvider({ children }) {
const [state, dispatch] = useReducer(userPropertiesReducer, null, hydrateState);
useEffect(() => {
if (state.email || state.password) {
sessionStorage.setItem(STORAGE_KEY, JSON.stringify(state));
} else {
sessionStorage.removeItem(STORAGE_KEY);
}
}, [state]);
return (
<UserPropertiesContext.Provider value={{ state, dispatch, ACTION_TYPES }}>
{children}
</UserPropertiesContext.Provider>
);
}

View File

@@ -1,20 +0,0 @@
export const initialState = {
email: "",
password: "",
};
export const ACTION_TYPES = {
SET_USER_PROPERTIES: "SET_USER_PROPERTIES",
CLEAR_USER_PROPERTIES: "CLEAR_USER_PROPERTIES",
};
export 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;
}
}

View File

@@ -0,0 +1,25 @@
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.`);
}
}
export default shopReducer