"use client"; import { create } from "zustand"; import { persist } from "zustand/middleware"; import { saleorClient } from "@/lib/saleor/client"; import { CHECKOUT_CREATE, CHECKOUT_LINES_ADD, CHECKOUT_LINES_UPDATE, CHECKOUT_LINES_DELETE, CHECKOUT_EMAIL_UPDATE, } from "@/lib/saleor/mutations/Checkout"; import { GET_CHECKOUT } from "@/lib/saleor/queries/Checkout"; import type { Checkout, CheckoutLine } from "@/types/saleor"; const CHANNEL = process.env.NEXT_PUBLIC_SALEOR_CHANNEL || "default-channel"; // GraphQL Response Types interface CheckoutCreateResponse { checkoutCreate?: { checkout?: Checkout; errors?: Array<{ message: string }>; }; } interface CheckoutLinesAddResponse { checkoutLinesAdd?: { checkout?: Checkout; errors?: Array<{ message: string }>; }; } interface CheckoutLinesUpdateResponse { checkoutLinesUpdate?: { checkout?: Checkout; errors?: Array<{ message: string }>; }; } interface CheckoutLinesDeleteResponse { checkoutLinesDelete?: { checkout?: Checkout; errors?: Array<{ message: string }>; }; } interface CheckoutEmailUpdateResponse { checkoutEmailUpdate?: { checkout?: Checkout; errors?: Array<{ message: string }>; }; } interface GetCheckoutResponse { checkout?: Checkout; } interface SaleorCheckoutStore { checkout: Checkout | null; checkoutToken: string | null; isOpen: boolean; isLoading: boolean; error: string | null; // Actions initCheckout: () => Promise; addLine: (variantId: string, quantity: number) => Promise; updateLine: (lineId: string, quantity: number) => Promise; removeLine: (lineId: string) => Promise; setEmail: (email: string) => Promise; refreshCheckout: () => Promise; toggleCart: () => void; openCart: () => void; closeCart: () => void; clearError: () => void; // Getters getLineCount: () => number; getTotal: () => number; getLines: () => CheckoutLine[]; } export const useSaleorCheckoutStore = create()( persist( (set, get) => ({ checkout: null, checkoutToken: null, isOpen: false, isLoading: false, error: null, initCheckout: async () => { const { checkoutToken } = get(); if (checkoutToken) { // Try to fetch existing checkout try { const { data } = await saleorClient.query({ query: GET_CHECKOUT, variables: { token: checkoutToken }, }); if (data?.checkout) { set({ checkout: data.checkout }); return; } } catch (e) { // Checkout not found or expired, create new one } } // Create new checkout try { const { data } = await saleorClient.mutate({ mutation: CHECKOUT_CREATE, variables: { input: { channel: CHANNEL, lines: [], }, }, }); if (data?.checkoutCreate?.checkout) { set({ checkout: data.checkoutCreate.checkout, checkoutToken: data.checkoutCreate.checkout.token, }); } } catch (e: any) { set({ error: e.message }); } }, addLine: async (variantId: string, quantity: number) => { set({ isLoading: true, error: null }); try { let { checkout, checkoutToken } = get(); // Initialize checkout if needed if (!checkout) { await get().initCheckout(); checkout = get().checkout; checkoutToken = get().checkoutToken; } if (!checkout) { throw new Error("Failed to initialize checkout"); } const { data } = await saleorClient.mutate({ mutation: CHECKOUT_LINES_ADD, variables: { checkoutId: checkout.id, lines: [{ variantId, quantity }], }, }); if (data?.checkoutLinesAdd?.checkout) { set({ checkout: data.checkoutLinesAdd.checkout, isOpen: true, isLoading: false, }); } else if (data?.checkoutLinesAdd?.errors && data.checkoutLinesAdd.errors.length > 0) { throw new Error(data.checkoutLinesAdd.errors[0].message); } } catch (e: any) { set({ error: e.message, isLoading: false }); } }, updateLine: async (lineId: string, quantity: number) => { set({ isLoading: true, error: null }); try { const { checkout } = get(); if (!checkout) { throw new Error("No active checkout"); } if (quantity <= 0) { // Remove line if quantity is 0 or less await get().removeLine(lineId); return; } const { data } = await saleorClient.mutate({ mutation: CHECKOUT_LINES_UPDATE, variables: { checkoutId: checkout.id, lines: [{ lineId, quantity }], }, }); if (data?.checkoutLinesUpdate?.checkout) { set({ checkout: data.checkoutLinesUpdate.checkout, isLoading: false, }); } else if (data?.checkoutLinesUpdate?.errors && data.checkoutLinesUpdate.errors.length > 0) { throw new Error(data.checkoutLinesUpdate.errors[0].message); } } catch (e: any) { set({ error: e.message, isLoading: false }); } }, removeLine: async (lineId: string) => { set({ isLoading: true, error: null }); try { const { checkout } = get(); if (!checkout) { throw new Error("No active checkout"); } const { data } = await saleorClient.mutate({ mutation: CHECKOUT_LINES_DELETE, variables: { id: checkout.id, linesIds: [lineId], }, }); if (data?.checkoutLinesDelete?.checkout) { set({ checkout: data.checkoutLinesDelete.checkout, isLoading: false, }); } else if (data?.checkoutLinesDelete?.errors && data.checkoutLinesDelete.errors.length > 0) { throw new Error(data.checkoutLinesDelete.errors[0].message); } } catch (e: any) { set({ error: e.message, isLoading: false }); } }, setEmail: async (email: string) => { set({ isLoading: true, error: null }); try { const { checkout } = get(); if (!checkout) { throw new Error("No active checkout"); } const { data } = await saleorClient.mutate({ mutation: CHECKOUT_EMAIL_UPDATE, variables: { checkoutId: checkout.id, email, }, }); if (data?.checkoutEmailUpdate?.checkout) { set({ checkout: data.checkoutEmailUpdate.checkout, isLoading: false, }); } else if (data?.checkoutEmailUpdate?.errors && data.checkoutEmailUpdate.errors.length > 0) { throw new Error(data.checkoutEmailUpdate.errors[0].message); } } catch (e: any) { set({ error: e.message, isLoading: false }); } }, refreshCheckout: async () => { const { checkoutToken } = get(); if (!checkoutToken) return; try { const { data } = await saleorClient.query({ query: GET_CHECKOUT, variables: { token: checkoutToken }, }); if (data?.checkout) { set({ checkout: data.checkout }); } } catch (e) { // Checkout might be expired set({ checkout: null, checkoutToken: null }); } }, toggleCart: () => set((state) => ({ isOpen: !state.isOpen })), openCart: () => set({ isOpen: true }), closeCart: () => set({ isOpen: false }), clearError: () => set({ error: null }), getLineCount: () => { const { checkout } = get(); if (!checkout?.lines) return 0; return checkout.lines.reduce((count, line) => count + line.quantity, 0); }, getTotal: () => { const { checkout } = get(); return checkout?.totalPrice?.gross?.amount || 0; }, getLines: () => { const { checkout } = get(); return checkout?.lines || []; }, }), { name: "manoonoils-saleor-checkout", partialize: (state) => ({ checkoutToken: state.checkoutToken, }), } ) );