import { create } from 'zustand'; import { persist, createJSONStorage } from 'zustand/middleware'; import { storefrontApiRequest, ShopifyProduct, CART_QUERY, CART_CREATE_MUTATION, CART_LINES_ADD_MUTATION, CART_LINES_UPDATE_MUTATION, CART_LINES_REMOVE_MUTATION, formatCheckoutUrl, isCartNotFoundError } from '@/lib/shopify'; export interface CartItem { lineId: string | null; product: ShopifyProduct; variantId: string; variantTitle: string; price: { amount: string; currencyCode: string }; quantity: number; selectedOptions: Array<{ name: string; value: string }>; } interface CartStore { items: CartItem[]; cartId: string | null; checkoutUrl: string | null; isLoading: boolean; isSyncing: boolean; addItem: (item: Omit) => Promise; updateQuantity: (variantId: string, quantity: number) => Promise; removeItem: (variantId: string) => Promise; clearCart: () => void; syncCart: () => Promise; getCheckoutUrl: () => string | null; } async function createShopifyCart(item: CartItem): Promise<{ cartId: string; checkoutUrl: string; lineId: string } | null> { const data = await storefrontApiRequest(CART_CREATE_MUTATION, { input: { lines: [{ quantity: item.quantity, merchandiseId: item.variantId }] }, }); if (data?.data?.cartCreate?.userErrors?.length > 0) { console.error('Cart creation failed:', data.data.cartCreate.userErrors); return null; } const cart = data?.data?.cartCreate?.cart; if (!cart?.checkoutUrl) return null; const lineId = cart.lines.edges[0]?.node?.id; if (!lineId) return null; return { cartId: cart.id, checkoutUrl: formatCheckoutUrl(cart.checkoutUrl), lineId }; } async function addLineToShopifyCart(cartId: string, item: CartItem): Promise<{ success: boolean; lineId?: string; cartNotFound?: boolean }> { const data = await storefrontApiRequest(CART_LINES_ADD_MUTATION, { cartId, lines: [{ quantity: item.quantity, merchandiseId: item.variantId }], }); const userErrors = data?.data?.cartLinesAdd?.userErrors || []; if (isCartNotFoundError(userErrors)) return { success: false, cartNotFound: true }; if (userErrors.length > 0) { console.error('Add line failed:', userErrors); return { success: false }; } const lines = data?.data?.cartLinesAdd?.cart?.lines?.edges || []; const newLine = lines.find((l: { node: { id: string; merchandise: { id: string } } }) => l.node.merchandise.id === item.variantId); return { success: true, lineId: newLine?.node?.id }; } async function updateShopifyCartLine(cartId: string, lineId: string, quantity: number): Promise<{ success: boolean; cartNotFound?: boolean }> { const data = await storefrontApiRequest(CART_LINES_UPDATE_MUTATION, { cartId, lines: [{ id: lineId, quantity }], }); const userErrors = data?.data?.cartLinesUpdate?.userErrors || []; if (isCartNotFoundError(userErrors)) return { success: false, cartNotFound: true }; if (userErrors.length > 0) { console.error('Update line failed:', userErrors); return { success: false }; } return { success: true }; } async function removeLineFromShopifyCart(cartId: string, lineId: string): Promise<{ success: boolean; cartNotFound?: boolean }> { const data = await storefrontApiRequest(CART_LINES_REMOVE_MUTATION, { cartId, lineIds: [lineId], }); const userErrors = data?.data?.cartLinesRemove?.userErrors || []; if (isCartNotFoundError(userErrors)) return { success: false, cartNotFound: true }; if (userErrors.length > 0) { console.error('Remove line failed:', userErrors); return { success: false }; } return { success: true }; } export const useCartStore = create()( persist( (set, get) => ({ items: [], cartId: null, checkoutUrl: null, isLoading: false, isSyncing: false, addItem: async (item) => { const { items, cartId, clearCart } = get(); const existingItem = items.find(i => i.variantId === item.variantId); set({ isLoading: true }); try { if (!cartId) { const result = await createShopifyCart({ ...item, lineId: null }); if (result) { set({ cartId: result.cartId, checkoutUrl: result.checkoutUrl, items: [{ ...item, lineId: result.lineId }] }); } } else if (existingItem) { const newQuantity = existingItem.quantity + item.quantity; if (!existingItem.lineId) { console.error('Cannot update quantity for item without lineId:', existingItem); return; } const result = await updateShopifyCartLine(cartId, existingItem.lineId, newQuantity); if (result.success) { const currentItems = get().items; set({ items: currentItems.map(i => i.variantId === item.variantId ? { ...i, quantity: newQuantity } : i) }); } else if (result.cartNotFound) { clearCart(); } } else { const result = await addLineToShopifyCart(cartId, { ...item, lineId: null }); if (result.success) { const currentItems = get().items; set({ items: [...currentItems, { ...item, lineId: result.lineId ?? null }] }); } else if (result.cartNotFound) { clearCart(); } } } catch (error) { console.error('Failed to add item:', error); } finally { set({ isLoading: false }); } }, updateQuantity: async (variantId, quantity) => { if (quantity <= 0) { await get().removeItem(variantId); return; } const { items, cartId, clearCart } = get(); const item = items.find(i => i.variantId === variantId); if (!item?.lineId || !cartId) return; set({ isLoading: true }); try { const result = await updateShopifyCartLine(cartId, item.lineId, quantity); if (result.success) { const currentItems = get().items; set({ items: currentItems.map(i => i.variantId === variantId ? { ...i, quantity } : i) }); } else if (result.cartNotFound) { clearCart(); } } catch (error) { console.error('Failed to update quantity:', error); } finally { set({ isLoading: false }); } }, removeItem: async (variantId) => { const { items, cartId, clearCart } = get(); const item = items.find(i => i.variantId === variantId); if (!item?.lineId || !cartId) return; set({ isLoading: true }); try { const result = await removeLineFromShopifyCart(cartId, item.lineId); if (result.success) { const currentItems = get().items; const newItems = currentItems.filter(i => i.variantId !== variantId); newItems.length === 0 ? clearCart() : set({ items: newItems }); } else if (result.cartNotFound) { clearCart(); } } catch (error) { console.error('Failed to remove item:', error); } finally { set({ isLoading: false }); } }, clearCart: () => set({ items: [], cartId: null, checkoutUrl: null }), getCheckoutUrl: () => get().checkoutUrl, syncCart: async () => { const { cartId, isSyncing, clearCart, items } = get(); if (!cartId || isSyncing) return; set({ isSyncing: true }); try { const data = await storefrontApiRequest(CART_QUERY, { id: cartId }); if (!data) { set({ isSyncing: false }); return; } const cart = data?.data?.cart; // Cart doesn't exist or is empty - clear local state if (!cart || cart.totalQuantity === 0) { clearCart(); return; } // Sync local items with Shopify cart const shopifyLines = cart.lines?.edges || []; // Build a map of Shopify line items by variant ID const shopifyItemsMap = new Map(); for (const edge of shopifyLines) { const variantId = edge.node.merchandise.id; shopifyItemsMap.set(variantId, { lineId: edge.node.id, quantity: edge.node.quantity }); } // Update local items to match Shopify state const syncedItems = items .filter(item => shopifyItemsMap.has(item.variantId)) .map(item => { const shopifyItem = shopifyItemsMap.get(item.variantId)!; return { ...item, lineId: shopifyItem.lineId, quantity: shopifyItem.quantity }; }); // Update checkout URL if available const checkoutUrl = cart.checkoutUrl ? formatCheckoutUrl(cart.checkoutUrl) : get().checkoutUrl; if (syncedItems.length === 0) { clearCart(); } else { set({ items: syncedItems, checkoutUrl }); } } catch (error) { console.error('Failed to sync cart with Shopify:', error); } finally { set({ isSyncing: false }); } } }), { name: 'shopify-cart', storage: createJSONStorage(() => localStorage), partialize: (state) => ({ items: state.items, cartId: state.cartId, checkoutUrl: state.checkoutUrl }), } ) );