'use client'; import { useCallback, useEffect, useState } from 'react'; import { loadStripe } from '@stripe/stripe-js'; import { Elements } from '@stripe/react-stripe-js'; import { useOrderStore, orderStore, type CheckoutStep } from '@/store/useOrderStore'; import { ShippingStep } from './checkout/ShippingStep'; import { PaymentStep } from './checkout/PaymentStep'; import { ReviewStep } from './checkout/ReviewStep'; import { ConfirmationStep } from './checkout/ConfirmationStep'; // Without a publishable key (local builds), loadStripe throws on every page that // imports this module. Elements accepts null and the payment step stays empty. const publishableKey = process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY; const stripePromise = publishableKey ? loadStripe(publishableKey) : null; const STEPS: { id: CheckoutStep; label: string }[] = [ { id: 'shipping', label: 'Shipping' }, { id: 'payment', label: 'Payment' }, { id: 'review', label: 'Review' }, ]; function getStepIndex(step: CheckoutStep): number { return STEPS.findIndex((s) => s.id === step); } export function CheckoutOverlay() { const step = useOrderStore((s) => s.step); const clientSecret = useOrderStore((s) => s.payment.clientSecret); const paymentStatus = useOrderStore((s) => s.payment.status); const paymentError = useOrderStore((s) => s.payment.errorMessage); const [isCreatingIntent, setIsCreatingIntent] = useState(false); const handleClose = useCallback(() => { orderStore.getState().resetOrder(); }, []); const handleBack = useCallback(() => { const currentIndex = getStepIndex(step); if (currentIndex > 0) { orderStore.getState().setStep(STEPS[currentIndex - 1].id); } else { orderStore.getState().setStep('config'); } }, [step]); // Create payment intent when entering the payment step (only once) useEffect(() => { if (step === 'payment' && !clientSecret && !isCreatingIntent && paymentStatus !== 'failed') { setIsCreatingIntent(true); orderStore.getState().createPaymentIntent().finally(() => { setIsCreatingIntent(false); }); } }, [step, clientSecret, isCreatingIntent, paymentStatus]); // Close on Escape key useEffect(() => { if (step === 'config') return; const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { handleClose(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [step, handleClose]); if (step === 'config') return null; return (
{/* Header with progress */} {step !== 'confirmed' && (

Checkout

{/* Progress bar */}
{STEPS.map((s, i) => { const currentIdx = getStepIndex(step); const isActive = i === currentIdx; const isComplete = i < currentIdx; return (
{s.label}
); })}
)} {/* Step Content */}
{step === 'shipping' && } {(step === 'payment' || step === 'review') && clientSecret ? ( {/* Keep PaymentElement mounted (hidden) during review so confirmPayment works */}
{step === 'review' && }
) : (step === 'payment' || step === 'review') && !clientSecret ? (
{paymentStatus === 'failed' ? (
{paymentError || 'Failed to initialize payment'}
) : ( Initializing secure payment... )}
) : null} {step === 'confirmed' && }
); }