import { createStore } from 'zustand/vanilla'; import { useSyncExternalStore } from 'react'; export type CheckoutStep = 'config' | 'shipping' | 'payment' | 'review' | 'confirmed'; export interface ShippingInfo { name: string; email: string; phone: string; address: string; city: string; country: string; postalCode: string; } export interface PaymentInfo { paymentIntentId: string; clientSecret: string; status: 'idle' | 'processing' | 'succeeded' | 'failed'; errorMessage: string; } export interface PriceLineItem { label: string; price: number; } export interface OrderState { step: CheckoutStep; shipping: ShippingInfo; payment: PaymentInfo; orderId: string; orderTotal: number; personaSummary: string; colorSummary: string; priceItems: PriceLineItem[]; } export interface OrderActions { setStep: (step: CheckoutStep) => void; setShipping: (shipping: ShippingInfo) => void; setPayment: (payment: Partial) => void; setOrderTotal: (total: number) => void; setConfigSummary: (persona: string, color: string, priceItems?: PriceLineItem[]) => void; createPaymentIntent: () => Promise; placeOrder: () => void; resetOrder: () => void; } export type OrderStore = OrderState & OrderActions; const emptyShipping: ShippingInfo = { name: '', email: '', phone: '', address: '', city: '', country: '', postalCode: '', }; const emptyPayment: PaymentInfo = { paymentIntentId: '', clientSecret: '', status: 'idle', errorMessage: '', }; const defaultState: OrderState = { step: 'config', shipping: emptyShipping, payment: emptyPayment, orderId: '', orderTotal: 0, personaSummary: '', colorSummary: '', priceItems: [], }; function generateOrderId(): string { const timestamp = Date.now().toString(36).toUpperCase(); const random = Math.random().toString(36).substring(2, 6).toUpperCase(); return `LR-G1-${timestamp}${random}`; } export const orderStore = createStore((set) => ({ ...defaultState, setStep: (step: CheckoutStep) => set({ step }), setShipping: (shipping: ShippingInfo) => set({ shipping }), setPayment: (payment: Partial) => set((state) => ({ payment: { ...state.payment, ...payment }, })), setOrderTotal: (total: number) => set({ orderTotal: total }), setConfigSummary: (persona: string, color: string, priceItems: PriceLineItem[] = []) => set({ personaSummary: persona, colorSummary: color, priceItems, }), createPaymentIntent: async (): Promise => { const { orderTotal, personaSummary, colorSummary, shipping, priceItems } = orderStore.getState(); try { const res: Response = await fetch('/api/create-payment-intent/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: orderTotal, currency: 'aed', receiptEmail: shipping.email || undefined, metadata: { persona: personaSummary, color: colorSummary, priceItems: JSON.stringify(priceItems), customerName: shipping.name, customerEmail: shipping.email, customerPhone: shipping.phone, customerAddress: shipping.address, customerCity: shipping.city, customerCountry: shipping.country, customerPostalCode: shipping.postalCode, }, }), }); const data: { clientSecret: string; paymentIntentId: string; error?: string } = await res.json(); if (!res.ok) throw new Error(data.error || 'Failed to create payment intent'); set({ payment: { paymentIntentId: data.paymentIntentId, clientSecret: data.clientSecret, status: 'idle', errorMessage: '', }, }); return data.clientSecret; } catch (err) { const msg = err instanceof Error ? err.message : 'Payment initialization failed'; set((s) => ({ payment: { ...s.payment, status: 'failed', errorMessage: msg } })); return null; } }, placeOrder: () => set({ orderId: generateOrderId(), step: 'confirmed', }), resetOrder: () => set({ ...defaultState }), })); export const useOrderStore = (selector: (state: OrderStore) => T): T => { return useSyncExternalStore( orderStore.subscribe, () => selector(orderStore.getState()), () => selector(orderStore.getState()) ); };