Comments, appointments adjustments, fixed some issues
This commit is contained in:
@@ -2,10 +2,14 @@
|
||||
|
||||
import { createContext, useContext, useState, useEffect, useCallback } from "react";
|
||||
|
||||
//Auth context
|
||||
//Stores the logged in user, token, and auth actions
|
||||
const AuthContext = createContext(null);
|
||||
|
||||
//Key used to save the token in localStorage
|
||||
const TOKEN_KEY = "auth_token";
|
||||
|
||||
//Fetches the current user from the backend using the stored token
|
||||
async function fetchCurrentUser(token) {
|
||||
const res = await fetch("/api/v1/auth/me", {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
@@ -14,11 +18,13 @@ async function fetchCurrentUser(token) {
|
||||
return res.json();
|
||||
}
|
||||
|
||||
//Provides auth state to all child components
|
||||
export function AuthProvider({ children }) {
|
||||
const [user, setUser] = useState(null);
|
||||
const [token, setToken] = useState(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
//Re-fetches the user info using the current or a newly provided token
|
||||
const refreshUser = useCallback(async (providedToken) => {
|
||||
const activeToken = providedToken ?? token;
|
||||
if (!activeToken) {
|
||||
@@ -41,6 +47,7 @@ export function AuthProvider({ children }) {
|
||||
return userInfo;
|
||||
}, [token]);
|
||||
|
||||
//On first load, check if a token was saved and try to restore the session
|
||||
useEffect(() => {
|
||||
const stored = localStorage.getItem(TOKEN_KEY);
|
||||
if (!stored) {
|
||||
@@ -57,6 +64,7 @@ export function AuthProvider({ children }) {
|
||||
.finally(() => setLoading(false));
|
||||
}, [refreshUser]);
|
||||
|
||||
//Logs the user in and saves the token
|
||||
const login = useCallback(async (username, password) => {
|
||||
const res = await fetch("/api/v1/auth/login", {
|
||||
method: "POST",
|
||||
@@ -84,6 +92,7 @@ export function AuthProvider({ children }) {
|
||||
return userInfo;
|
||||
}, [refreshUser]);
|
||||
|
||||
//Creates a new account and logs the user in right away
|
||||
const register = useCallback(async ({ username, password, email, firstName, lastName, phone }) => {
|
||||
const res = await fetch("/api/v1/auth/register", {
|
||||
method: "POST",
|
||||
@@ -118,6 +127,7 @@ export function AuthProvider({ children }) {
|
||||
return userInfo;
|
||||
}, [refreshUser]);
|
||||
|
||||
//Clears the token and user from memory and localStorage
|
||||
const logout = useCallback(() => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
setToken(null);
|
||||
@@ -130,6 +140,7 @@ export function AuthProvider({ children }) {
|
||||
);
|
||||
}
|
||||
|
||||
//Hook to access auth state, must be used inside an AuthProvider
|
||||
export function useAuth() {
|
||||
const ctx = useContext(AuthContext);
|
||||
if (!ctx) {
|
||||
|
||||
@@ -15,10 +15,14 @@ import {
|
||||
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);
|
||||
@@ -26,6 +30,7 @@ export function CartProvider({ children }) {
|
||||
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);
|
||||
@@ -51,6 +56,7 @@ export function CartProvider({ children }) {
|
||||
}
|
||||
}, [user, setStoreId]);
|
||||
|
||||
//Fetches the latest cart from the backend
|
||||
const refreshCart = useCallback(async () => {
|
||||
if (!token || !selectedStoreId) {
|
||||
setCart(null);
|
||||
@@ -173,6 +179,7 @@ export function CartProvider({ children }) {
|
||||
[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 (
|
||||
@@ -201,6 +208,8 @@ export function CartProvider({ children }) {
|
||||
);
|
||||
}
|
||||
|
||||
//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");
|
||||
|
||||
@@ -4,15 +4,18 @@ import { createContext, useContext, useState, useRef, useCallback, useEffect } f
|
||||
import { createStompClient } from "@/lib/chatSocket";
|
||||
import { useAuth } from "@/context/AuthContext";
|
||||
|
||||
//Chat widget context
|
||||
//Manages both the AI chat and the live support chat
|
||||
const ChatWidgetContext = createContext(null);
|
||||
const API_BASE = "";
|
||||
|
||||
//Provides chat state and actions for the floating chat widget
|
||||
export function ChatWidgetProvider({ children }) {
|
||||
const { user } = useAuth();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [view, setView] = useState("ai"); // "ai" | "history" | "live"
|
||||
|
||||
// AI chat
|
||||
//AI chat messages, loaded from localStorage so they survive page refreshes
|
||||
const [aiMessages, setAiMessages] = useState(() => {
|
||||
try {
|
||||
const saved = localStorage.getItem("fc_aiMessages");
|
||||
@@ -22,15 +25,16 @@ export function ChatWidgetProvider({ children }) {
|
||||
const [aiSending, setAiSending] = useState(false);
|
||||
const [aiError, setAiError] = useState(null);
|
||||
|
||||
// Persist aiMessages to localStorage
|
||||
//Save AI messages to localStorage whenever they change
|
||||
useEffect(() => {
|
||||
localStorage.setItem("fc_aiMessages", JSON.stringify(aiMessages));
|
||||
}, [aiMessages]);
|
||||
|
||||
// Keep a ref so sendAiMessage stays stable (no stale-closure over messages)
|
||||
//Ref to the latest messages so the send function always sees current state
|
||||
const aiMessagesRef = useRef(aiMessages);
|
||||
useEffect(() => { aiMessagesRef.current = aiMessages; }, [aiMessages]);
|
||||
|
||||
//Sends a message to the AI and appends the response to the chat
|
||||
const sendAiMessage = useCallback(async (text, token) => {
|
||||
if (!text.trim() || !token) return;
|
||||
const userMsg = { role: "user", content: text, id: Date.now() };
|
||||
@@ -54,7 +58,7 @@ export function ChatWidgetProvider({ children }) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Live chat
|
||||
//Live chat state
|
||||
const [conversations, setConversations] = useState([]);
|
||||
const [convsLoading, setConvsLoading] = useState(false);
|
||||
const [activeConvId, setActiveConvId] = useState(null);
|
||||
@@ -67,6 +71,7 @@ export function ChatWidgetProvider({ children }) {
|
||||
const activeConvIdRef = useRef(null);
|
||||
const tokenRef = useRef(null);
|
||||
|
||||
//Disconnects the WebSocket if it is active
|
||||
const disconnectStomp = useCallback(() => {
|
||||
if (stompRef.current) {
|
||||
stompRef.current.deactivate();
|
||||
@@ -74,6 +79,7 @@ export function ChatWidgetProvider({ children }) {
|
||||
}
|
||||
}, []);
|
||||
|
||||
//Clears all chat state when the user logs out or switches accounts
|
||||
const prevUserIdRef = useRef(user?.id);
|
||||
useEffect(() => {
|
||||
const currentId = user?.id ?? null;
|
||||
@@ -92,6 +98,7 @@ export function ChatWidgetProvider({ children }) {
|
||||
}
|
||||
}, [user?.id, disconnectStomp]);
|
||||
|
||||
//Subscribes to incoming messages and conversation updates for a given chat
|
||||
const subscribeToConversation = useCallback((client, convId) => {
|
||||
client.subscribe(`/topic/chat/conversations/${convId}`, (frame) => {
|
||||
try {
|
||||
@@ -172,6 +179,7 @@ export function ChatWidgetProvider({ children }) {
|
||||
}
|
||||
}, [liveSending]);
|
||||
|
||||
//Creates a new live chat conversation and requests a human agent
|
||||
const startLiveChat = useCallback(async (token) => {
|
||||
if (!token || switchingToHuman) return;
|
||||
setSwitchingToHuman(true);
|
||||
@@ -228,6 +236,7 @@ export function ChatWidgetProvider({ children }) {
|
||||
);
|
||||
}
|
||||
|
||||
//Hook to access chat widget state - must be used inside a ChatWidgetProvider
|
||||
export function useChatWidget() {
|
||||
const ctx = useContext(ChatWidgetContext);
|
||||
if (!ctx) throw new Error("useChatWidget must be used within ChatWidgetProvider");
|
||||
|
||||
Reference in New Issue
Block a user