223 lines
6.5 KiB
TypeScript
223 lines
6.5 KiB
TypeScript
import React, {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
useEffect,
|
|
useCallback,
|
|
type ReactNode,
|
|
} from "react";
|
|
import {
|
|
getCart,
|
|
addToCart as apiAddToCart,
|
|
removeFromCart as apiRemoveFromCart,
|
|
clearCart as apiClearCart,
|
|
} from "../api/api";
|
|
import { getToken } from "../auth/tokenStorage";
|
|
import { extractUsernameFromToken } from "../auth/jwtUtils";
|
|
|
|
export interface CartItem {
|
|
id: number;
|
|
product_id: number;
|
|
name_product: string;
|
|
price: number;
|
|
quantity: number;
|
|
category: string;
|
|
image?: string;
|
|
}
|
|
|
|
interface ToastData {
|
|
message: string;
|
|
type: "success" | "error" | "warning" | "info";
|
|
}
|
|
|
|
interface CartContextType {
|
|
cartItems: CartItem[];
|
|
addToCart: (item: Omit<CartItem, "id">) => Promise<void>;
|
|
removeFromCart: (id: number) => Promise<void>;
|
|
clearCart: () => Promise<void>;
|
|
cartCount: number;
|
|
cartTotal: number;
|
|
loading: boolean;
|
|
refreshCart: () => Promise<void>;
|
|
toast: ToastData | null;
|
|
clearToast: () => void;
|
|
isAuthenticated: boolean;
|
|
}
|
|
|
|
const CartContext = createContext<CartContextType | undefined>(undefined);
|
|
|
|
export function CartProvider({ children }: { children: ReactNode }) {
|
|
const [cartItems, setCartItems] = useState<CartItem[]>([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const [toast, setToast] = useState<ToastData | null>(null);
|
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
|
|
const showToast = (
|
|
message: string,
|
|
type: ToastData["type"] = "success",
|
|
) => {
|
|
setToast({ message, type });
|
|
setTimeout(() => setToast(null), 3000);
|
|
};
|
|
|
|
const clearToast = () => setToast(null);
|
|
|
|
const getUsername = async (): Promise<string | null> => {
|
|
const token = await getToken();
|
|
if (!token) return null;
|
|
return extractUsernameFromToken(token);
|
|
};
|
|
|
|
const refreshCart = useCallback(async () => {
|
|
const username = await getUsername();
|
|
if (!username) {
|
|
setCartItems([]);
|
|
setIsAuthenticated(false);
|
|
return;
|
|
}
|
|
setIsAuthenticated(true);
|
|
setLoading(true);
|
|
try {
|
|
const response = await getCart(username);
|
|
if (response.success && response.panier) {
|
|
const items: CartItem[] = response.panier.map((item: any) => ({
|
|
id: item.id,
|
|
product_id: item.product_id,
|
|
name_product:
|
|
item.product_name || item.name_product || "Produit",
|
|
price: item.price,
|
|
quantity: item.quantity,
|
|
category: (item.category || "autre").toLowerCase().trim(),
|
|
image: item.image,
|
|
}));
|
|
setCartItems(items);
|
|
} else {
|
|
setCartItems([]);
|
|
}
|
|
} catch {
|
|
setCartItems([]);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
refreshCart();
|
|
}, [refreshCart]);
|
|
|
|
const addToCart = async (item: Omit<CartItem, "id">) => {
|
|
const username = await getUsername();
|
|
if (!username) {
|
|
showToast("Vous devez être connecté.", "warning");
|
|
return;
|
|
}
|
|
if (!item.quantity || item.quantity <= 0) {
|
|
showToast("Quantité invalide", "error");
|
|
return;
|
|
}
|
|
const cleanName = (item.name_product || "Produit")
|
|
.replace(/\s*\([^)]*\)\s*/g, "")
|
|
.trim();
|
|
setLoading(true);
|
|
try {
|
|
const response = await apiAddToCart({
|
|
username,
|
|
product_id: item.product_id,
|
|
name_product: cleanName,
|
|
category: (item.category || "autre").toLowerCase().trim(),
|
|
quantity: Number(item.quantity),
|
|
price: Number(item.price) || 0,
|
|
});
|
|
if (response.success) {
|
|
await refreshCart();
|
|
showToast(
|
|
`${cleanName} (${item.quantity}g) ajouté !`,
|
|
"success",
|
|
);
|
|
} else {
|
|
showToast(
|
|
response.message || "Erreur lors de l'ajout",
|
|
"error",
|
|
);
|
|
}
|
|
} catch {
|
|
showToast("Erreur lors de l'ajout", "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const removeFromCart = async (id: number) => {
|
|
const username = await getUsername();
|
|
if (!username) {
|
|
showToast("Vous devez être connecté.", "warning");
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
try {
|
|
const response = await apiRemoveFromCart(id, username);
|
|
if (response.success) {
|
|
await refreshCart();
|
|
showToast("Produit supprimé", "success");
|
|
} else {
|
|
showToast(response.message || "Erreur suppression", "error");
|
|
}
|
|
} catch {
|
|
showToast("Erreur suppression", "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const clearCartAction = async () => {
|
|
const username = await getUsername();
|
|
if (!username) {
|
|
showToast("Vous devez être connecté.", "warning");
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
try {
|
|
const response = await apiClearCart(username);
|
|
if (response.success) {
|
|
setCartItems([]);
|
|
showToast(response.message || "Panier vidé", "success");
|
|
} else {
|
|
showToast(response.message || "Erreur vidage", "error");
|
|
}
|
|
} catch {
|
|
showToast("Erreur vidage", "error");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const cartCount = cartItems.length;
|
|
const cartTotal = cartItems.reduce((sum, item) => sum + item.price, 0);
|
|
|
|
return (
|
|
<CartContext.Provider
|
|
value={{
|
|
cartItems,
|
|
addToCart,
|
|
removeFromCart,
|
|
clearCart: clearCartAction,
|
|
cartCount,
|
|
cartTotal,
|
|
loading,
|
|
refreshCart,
|
|
toast,
|
|
clearToast,
|
|
isAuthenticated,
|
|
}}
|
|
>
|
|
{children}
|
|
</CartContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useCart() {
|
|
const context = useContext(CartContext);
|
|
if (!context) throw new Error("useCart must be used within a CartProvider");
|
|
return context;
|
|
}
|