Comments, appointments adjustments, fixed some issues

This commit is contained in:
augmentedpotato
2026-04-20 19:19:30 -06:00
parent d3b9c51952
commit 2cb0a94bbb
34 changed files with 402 additions and 104 deletions

View File

@@ -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) {

View File

@@ -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");

View File

@@ -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");