From 242d1e6cc5dda32a5997fcb503a596dfd42df368 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 10:40:15 +0000 Subject: [PATCH 1/2] Changes Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com> --- src/App.tsx | 3 +- src/components/Header.tsx | 4 +- src/components/PlansSection.tsx | 110 ++++++++++++++++++++++---------- 3 files changed, 79 insertions(+), 38 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index dfaaed0..8fcb5ce 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -12,12 +12,11 @@ import AccountConfirmation from "./pages/AccountConfirmation"; import Contact from "./pages/Contact"; import Admin from "./pages/Admin"; import FAQ from "./pages/FAQ"; -import { useCartSync } from "./hooks/useCartSync"; const queryClient = new QueryClient(); function AppContent() { - useCartSync(); + return ( diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 708ecbd..392c822 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,5 +1,4 @@ import AccountMenu from "./AccountMenu"; -import CartDrawer from "./CartDrawer"; const Header = () => { return ( @@ -13,7 +12,6 @@ const Header = () => { />
-
@@ -21,4 +19,4 @@ const Header = () => { ); }; -export default Header; \ No newline at end of file +export default Header; diff --git a/src/components/PlansSection.tsx b/src/components/PlansSection.tsx index e520482..e984e85 100644 --- a/src/components/PlansSection.tsx +++ b/src/components/PlansSection.tsx @@ -1,7 +1,11 @@ import { useState, useMemo } from "react"; -import { Loader2 } from "lucide-react"; +import { Loader2, CreditCard, FileText } from "lucide-react"; import { useShopifyProducts } from "@/hooks/useShopifyProducts"; -import { useCartStore } from "@/stores/cartStore"; +import { + storefrontApiRequest, + CART_CREATE_MUTATION, + formatCheckoutUrl, +} from "@/lib/shopify"; import { toast } from "sonner"; const PRICE_PER_KG = 325; @@ -19,12 +23,15 @@ const pickPlan = (kg: number) => { return match || availablePlans[availablePlans.length - 1]; }; +type PaymentMethod = "card" | "invoice"; + const PlansSection = () => { const [employees, setEmployees] = useState(10); - const [checkoutLoading, setCheckoutLoading] = useState(false); + const [loadingMethod, setLoadingMethod] = useState(null); - const { products, isLoading: productsLoading } = useShopifyProducts("product_type:Abonnemang"); - const { addItem, clearCart, isLoading: cartLoading } = useCartStore(); + const { products, isLoading: productsLoading } = useShopifyProducts( + "product_type:Abonnemang" + ); const recommendedKg = useMemo( () => Math.max(1, Math.round(employees * KG_PER_EMPLOYEE)), @@ -32,7 +39,7 @@ const PlansSection = () => { ); const weeklyPrice = recommendedKg * PRICE_PER_KG; - const handleStart = async () => { + const handleStart = async (method: PaymentMethod) => { const plan = pickPlan(recommendedKg); const shopifyProduct = products.find((p) => p.node.handle === plan.handle); @@ -46,29 +53,46 @@ const PlansSection = () => { return; } - setCheckoutLoading(true); + setLoadingMethod(method); try { - clearCart(); - await addItem({ - product: shopifyProduct, - variantId: variant.id, - variantTitle: variant.title, - price: variant.price, - quantity: 1, - selectedOptions: variant.selectedOptions || [], - }); - toast.success(`Abonnemang tillagt (${plan.name} – ${plan.kg} kg/vecka)`, { - description: "Klicka på varukorgen för att gå till betalning", + const data = await storefrontApiRequest(CART_CREATE_MUTATION, { + input: { + lines: [{ quantity: 1, merchandiseId: variant.id }], + attributes: [ + { key: "Antal anställda", value: String(employees) }, + { key: "Rekommenderad mängd", value: `${recommendedKg} kg/vecka` }, + { key: "Veckopris (exkl. moms)", value: `${weeklyPrice} kr` }, + { + key: "Önskad betalningsmetod", + value: method === "card" ? "Automatiskt kort varje månad" : "Månadsfaktura", + }, + ], + }, }); + + const errors = data?.data?.cartCreate?.userErrors || []; + if (errors.length > 0) { + console.error("Cart creation failed:", errors); + toast.error("Kunde inte skapa checkout"); + return; + } + + const checkoutUrl = data?.data?.cartCreate?.cart?.checkoutUrl; + if (!checkoutUrl) { + toast.error("Ingen checkout-länk mottagen"); + return; + } + + window.open(formatCheckoutUrl(checkoutUrl), "_blank"); } catch (e) { console.error(e); toast.error("Något gick fel"); } finally { - setCheckoutLoading(false); + setLoadingMethod(null); } }; - const isLoading = productsLoading || cartLoading || checkoutLoading; + const isBusy = productsLoading || loadingMethod !== null; return (
@@ -139,20 +163,40 @@ const PlansSection = () => {
exklusive moms
- + ) : ( + <> + + Automatiskt varje månad + + )} + + + +

+ Du slutför beställningen säkert hos Shopify. +

From db0404f93b35550cb1ba59cf0d216ecb30e70e8c Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 11 Jul 2026 10:40:22 +0000 Subject: [PATCH 2/2] Changes Co-authored-by: wolfoftyreso-debug <250630591+wolfoftyreso-debug@users.noreply.github.com> --- src/components/CartDrawer.tsx | 149 ------------------ src/hooks/useCartSync.ts | 15 -- src/stores/cartStore.ts | 276 ---------------------------------- 3 files changed, 440 deletions(-) delete mode 100644 src/components/CartDrawer.tsx delete mode 100644 src/hooks/useCartSync.ts delete mode 100644 src/stores/cartStore.ts 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 }), - } - ) -);