Files

244 lines
6.3 KiB
TypeScript

"use client";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import {
addCartItem,
getCart,
removeCartItem,
updateCartItem,
} from "@/lib/meshkee/api";
import {
cartFromGuest,
emptyCart,
normalizeCartResponse,
} from "@/lib/meshkee/cart";
import {
clearGuestCart,
readGuestCart,
writeGuestCart,
} from "@/lib/meshkee/guest-cart";
import type { CartState, GuestCartLine } from "@/lib/meshkee/types";
import { useAuth } from "@/app/providers/auth-provider";
import { useToast } from "@/app/providers/toast-provider";
type CartContextValue = {
cart: CartState;
itemCount: number;
ready: boolean;
loading: boolean;
open: boolean;
setOpen: (open: boolean) => void;
refreshCart: () => Promise<void>;
addItem: (item: {
storeItemVariantId: string;
name: string;
price: number;
quantity?: number;
image?: string | null;
slug?: string;
}) => Promise<void>;
updateQuantity: (itemId: string, quantity: number) => Promise<void>;
removeItem: (itemId: string) => Promise<void>;
guestItems: GuestCartLine[];
};
const CartContext = createContext<CartContextValue | null>(null);
export function CartProvider({ children }: { children: React.ReactNode }) {
const { ready: authReady, isAuthenticated, businessId, getAccessToken } =
useAuth();
const { showToast } = useToast();
const [cart, setCart] = useState<CartState>(emptyCart());
const [ready, setReady] = useState(false);
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const [guestItems, setGuestItems] = useState<GuestCartLine[]>([]);
const refreshCart = useCallback(async () => {
if (!authReady) return;
const token = getAccessToken();
if (isAuthenticated && token && businessId) {
setLoading(true);
try {
const localGuest = readGuestCart();
if (localGuest.length > 0) {
for (const line of localGuest) {
await addCartItem(businessId, token, {
storeItemVariantId: line.id,
quantity: line.quantity,
});
}
clearGuestCart();
setGuestItems([]);
}
const payload = await getCart(businessId, token);
setCart(normalizeCartResponse(payload));
} catch {
setCart(emptyCart());
} finally {
setLoading(false);
setReady(true);
}
return;
}
const guest = readGuestCart();
setGuestItems(guest);
setCart(cartFromGuest(guest));
setReady(true);
}, [authReady, businessId, getAccessToken, isAuthenticated]);
useEffect(() => {
void refreshCart().catch(() => {
setCart(emptyCart());
setReady(true);
});
}, [refreshCart]);
const addItem = useCallback(
async (item: {
storeItemVariantId: string;
name: string;
price: number;
quantity?: number;
image?: string | null;
slug?: string;
}) => {
const quantity = Math.max(1, Math.floor(item.quantity ?? 1));
const token = getAccessToken();
if (isAuthenticated && token && businessId) {
setLoading(true);
try {
const payload = await addCartItem(businessId, token, {
storeItemVariantId: item.storeItemVariantId,
quantity,
});
setCart(normalizeCartResponse(payload));
showToast("به سبد خرید اضافه شد");
setOpen(true);
} catch (error) {
showToast(
error instanceof Error ? error.message : "افزودن به سبد خرید ناموفق بود",
);
} finally {
setLoading(false);
}
return;
}
const current = readGuestCart();
const existing = current.find((line) => line.id === item.storeItemVariantId);
const next = existing
? current.map((line) =>
line.id === item.storeItemVariantId
? { ...line, quantity: line.quantity + quantity }
: line,
)
: [
...current,
{
id: item.storeItemVariantId,
name: item.name,
price: item.price,
image: item.image ?? null,
slug: item.slug,
quantity,
},
];
writeGuestCart(next);
setGuestItems(next);
setCart(cartFromGuest(next));
showToast("به سبد خرید اضافه شد");
setOpen(true);
},
[businessId, getAccessToken, isAuthenticated, showToast],
);
const updateQuantity = useCallback(
async (itemId: string, quantity: number) => {
const token = getAccessToken();
if (isAuthenticated && token && businessId) {
setLoading(true);
try {
const payload =
quantity < 1
? await removeCartItem(businessId, token, itemId)
: await updateCartItem(businessId, token, itemId, quantity);
setCart(normalizeCartResponse(payload));
} finally {
setLoading(false);
}
return;
}
const current = readGuestCart();
const next =
quantity < 1
? current.filter((line) => line.id !== itemId)
: current.map((line) =>
line.id === itemId ? { ...line, quantity } : line,
);
writeGuestCart(next);
setGuestItems(next);
setCart(cartFromGuest(next));
},
[businessId, getAccessToken, isAuthenticated],
);
const removeItem = useCallback(
async (itemId: string) => {
await updateQuantity(itemId, 0);
},
[updateQuantity],
);
const itemCount = useMemo(
() => cart.items.reduce((sum, line) => sum + line.quantity, 0),
[cart.items],
);
const value = useMemo<CartContextValue>(
() => ({
cart,
itemCount,
ready,
loading,
open,
setOpen,
refreshCart,
addItem,
updateQuantity,
removeItem,
guestItems,
}),
[
cart,
itemCount,
ready,
loading,
open,
refreshCart,
addItem,
updateQuantity,
removeItem,
guestItems,
],
);
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}
export function useCart() {
const ctx = useContext(CartContext);
if (!ctx) throw new Error("useCart must be used within CartProvider");
return ctx;
}