diff --git a/src/components/CartDrawer.tsx b/src/components/CartDrawer.tsx deleted file mode 100644 index e4150be..0000000 --- a/src/components/CartDrawer.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { useState, useEffect } from "react"; -import { ShoppingCart, Trash2, ExternalLink, Loader2 } from "lucide-react"; -import { Button } from "@/components/ui/button"; -import { Badge } from "@/components/ui/badge"; -import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, - SheetTrigger, -} from "@/components/ui/sheet"; -import { useCartStore } from "@/stores/cartStore"; - -export const CartDrawer = () => { - const [isOpen, setIsOpen] = useState(false); - const { items, isLoading, isSyncing, removeItem, clearCart, getCheckoutUrl, syncCart } = useCartStore(); - - const totalItems = items.reduce((sum, item) => sum + item.quantity, 0); - const totalPrice = items.reduce((sum, item) => sum + (parseFloat(item.price.amount) * item.quantity), 0); - - // Sync cart with Shopify when drawer opens - useEffect(() => { - if (isOpen) syncCart(); - }, [isOpen, syncCart]); - - const handleCheckout = () => { - const checkoutUrl = getCheckoutUrl(); - if (checkoutUrl) { - window.open(checkoutUrl, '_blank'); - setIsOpen(false); - } - }; - - const handleRemoveItem = async (variantId: string) => { - await removeItem(variantId); - }; - - if (totalItems === 0) { - return null; - } - - return ( - - - - - - - Varukorg - - {totalItems === 0 ? "Din varukorg är tom" : `${totalItems} produkt${totalItems !== 1 ? 'er' : ''} i varukorgen`} - - - -
- {items.length === 0 ? ( -
-
- -

Din varukorg är tom

-
-
- ) : ( - <> -
-
- {items.map((item) => ( -
-
-

- {item.product.node.title} -

-

- Antal: {item.quantity} -

-

- {parseFloat(item.price.amount).toLocaleString('sv-SE')} {item.price.currencyCode} -

-
-
- -
-
- ))} -
-
- -
-
- Totalt - - {totalPrice.toLocaleString('sv-SE')} {items[0]?.price.currencyCode || 'SEK'} - -
- - - - -
- - )} -
-
-
- ); -}; - -export default CartDrawer; diff --git a/src/hooks/useCartSync.ts b/src/hooks/useCartSync.ts deleted file mode 100644 index e48c440..0000000 --- a/src/hooks/useCartSync.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useEffect } from 'react'; -import { useCartStore } from '@/stores/cartStore'; - -export function useCartSync() { - const syncCart = useCartStore(state => state.syncCart); - - useEffect(() => { - syncCart(); - const handleVisibilityChange = () => { - if (document.visibilityState === 'visible') syncCart(); - }; - document.addEventListener('visibilitychange', handleVisibilityChange); - return () => document.removeEventListener('visibilitychange', handleVisibilityChange); - }, [syncCart]); -} diff --git a/src/stores/cartStore.ts b/src/stores/cartStore.ts deleted file mode 100644 index 1491575..0000000 --- a/src/stores/cartStore.ts +++ /dev/null @@ -1,276 +0,0 @@ -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 }), - } - ) -);