Files

221 lines
5.3 KiB
JavaScript

// Author: Shiv
// Date: April 2026
"use client";
import { createContext, useContext, useState, useEffect, useCallback } from "react";
import { useAuth } from "@/context/AuthContext";
import {
fetchCart,
apiAddToCart,
apiUpdateCartItem,
apiRemoveCartItem,
apiClearCart,
apiApplyCoupon,
apiRemoveCoupon,
apiApplyPoints,
apiCheckout,
apiCancelCheckout,
} from "@/lib/cartApi";
//Cart context
//Holds the user's cart and all cart actions
const CartContext = createContext(null);
//Key used to save the selected store in localStorage
const STORE_KEY = "selected_store_id";
//Provides cart state to all child components
export function CartProvider({ children }) {
const { user, token } = useAuth();
const [cart, setCart] = useState(null);
const [selectedStoreId, setSelectedStoreIdState] = useState(null);
const [cartLoading, setCartLoading] = useState(false);
const [cartError, setCartError] = useState(null);
//Saves the selected store in state and localStorage
const setStoreId = useCallback((id) => {
const parsed = id ? Number(id) : null;
setSelectedStoreIdState(parsed);
if (parsed) {
localStorage.setItem(STORE_KEY, String(parsed));
}
else {
localStorage.removeItem(STORE_KEY);
}
}, []);
useEffect(() => {
const stored = localStorage.getItem(STORE_KEY);
if (stored) {
setSelectedStoreIdState(Number(stored));
}
}, []);
useEffect(() => {
if (user?.storeId && !localStorage.getItem(STORE_KEY)) {
setStoreId(user.storeId);
}
}, [user, setStoreId]);
//Fetches the latest cart from the backend
const refreshCart = useCallback(async () => {
if (!token || !selectedStoreId) {
setCart(null);
return;
}
setCartLoading(true);
setCartError(null);
try {
const data = await fetchCart(token, selectedStoreId);
setCart(data);
}
catch (err) {
setCartError(err.message);
}
finally {
setCartLoading(false);
}
}, [token, selectedStoreId]);
useEffect(() => {
if (user && selectedStoreId) {
refreshCart();
}
else {
setCart(null);
}
}, [user, selectedStoreId, refreshCart]);
const addItem = useCallback(
async (prodId, quantity = 1) => {
if (!token || !selectedStoreId) throw new Error("Select a store first");
const updated = await apiAddToCart(token, { prodId, storeId: selectedStoreId, quantity });
setCart(updated);
return updated;
},
[token, selectedStoreId]
);
const updateItem = useCallback(
async (cartItemId, quantity) => {
if (!token) return;
const updated = await apiUpdateCartItem(token, { cartItemId, quantity });
setCart(updated);
return updated;
},
[token]
);
const removeItem = useCallback(
async (cartItemId) => {
if (!token) return;
const updated = await apiRemoveCartItem(token, cartItemId);
setCart(updated);
return updated;
},
[token]
);
const clearCart = useCallback(async () => {
if (!token || !selectedStoreId) return;
await apiClearCart(token, selectedStoreId);
setCart(null);
}, [token, selectedStoreId]);
const applyCoupon = useCallback(
async (couponCode) => {
if (!token || !selectedStoreId) throw new Error("Select a store first");
const updated = await apiApplyCoupon(token, selectedStoreId, couponCode);
setCart(updated);
return updated;
},
[token, selectedStoreId]
);
const applyPoints = useCallback(
async (useLoyaltyPoints) => {
if (!token || !selectedStoreId) throw new Error("Select a store first");
const updated = await apiApplyPoints(token, selectedStoreId, useLoyaltyPoints);
setCart(updated);
return updated;
},
[token, selectedStoreId]
);
const removeCoupon = useCallback(
async () => {
if (!token || !selectedStoreId) throw new Error("Select a store first");
const updated = await apiRemoveCoupon(token, selectedStoreId);
setCart(updated);
return updated;
},
[token, selectedStoreId]
);
const checkout = useCallback(
async () => {
if (!token || !selectedStoreId) throw new Error("Select a store first");
const result = await apiCheckout(token, { storeId: selectedStoreId });
return result;
},
[token, selectedStoreId]
);
const cancelCheckout = useCallback(
async () => {
if (!token || !selectedStoreId) return;
await apiCancelCheckout(token, selectedStoreId);
await refreshCart();
},
[token, selectedStoreId, refreshCart]
);
//Total number of items across all cart rows, used for the cart badge in the nav
const itemCount = cart?.items?.reduce((sum, i) => sum + i.quantity, 0) ?? 0;
return (
<CartContext.Provider
value={{
cart,
cartLoading,
cartError,
itemCount,
selectedStoreId,
setStoreId,
addItem,
updateItem,
removeItem,
clearCart,
applyCoupon,
applyPoints,
removeCoupon,
checkout,
cancelCheckout,
refreshCart,
}}
>
{children}
</CartContext.Provider>
);
}
//Hook to access cart state
//Must be used inside a CartProvider
export function useCart() {
const ctx = useContext(CartContext);
if (!ctx) throw new Error("useCart must be used within a CartProvider");
return ctx;
}