storefront-layouts 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/auth/LoginForm.js +2 -2
- package/dist/components/auth/LoginSheet.js +2 -2
- package/dist/components/auth/login-form-core.js +3 -3
- package/dist/components/auth/login-sheet-header.js +3 -3
- package/dist/components/auth/login-sheet-registry.js +2 -2
- package/dist/components/auth/login-sheet-themes/booking-agenda.theme.js +1 -1
- package/dist/components/auth/login-sheet-themes/booking.theme.js +1 -1
- package/dist/components/auth/login-sheet-themes/clothing-minimal.theme.js +1 -1
- package/dist/components/auth/login-sheet-themes/clothing.theme.js +1 -1
- package/dist/components/auth/login-sheet-themes/electronics-grid.theme.js +1 -1
- package/dist/components/auth/login-sheet-themes/electronics.theme.js +1 -1
- package/dist/components/auth/login-sheet-themes/food-modern.theme.js +1 -1
- package/dist/components/auth/login-sheet-themes/food.theme.js +1 -1
- package/dist/components/auth/login-sheet-themes/index.js +9 -9
- package/dist/components/auth/login-sheet-themes/motivational-speaker.theme.js +1 -1
- package/dist/components/auth/login-sheet-themes/theme-input-baselines.d.ts +2 -2
- package/dist/components/auth/login-sheet-themes/theme-input-baselines.d.ts.map +1 -1
- package/dist/components/auth/login-sheet-themes/theme-input-baselines.js +2 -2
- package/dist/components/auth/login-sheets/StoreLoginSheetShell.d.ts.map +1 -1
- package/dist/components/auth/login-sheets/StoreLoginSheetShell.js +6 -6
- package/dist/components/auth/use-login-submit.js +3 -3
- package/dist/components/cart/InventoryHoldCountdown.js +2 -2
- package/dist/components/categories/category-masonry-grid.js +3 -3
- package/dist/components/checkout/CheckoutFeeLines.js +1 -1
- package/dist/components/checkout/ShippingOptionsPanel.js +3 -3
- package/dist/components/learning/CertificateTemplates.js +2 -2
- package/dist/components/learning/CertificateViewer.js +3 -3
- package/dist/components/learning/MentorshipProgress.d.ts.map +1 -1
- package/dist/components/learning/MentorshipProgress.js +4 -4
- package/dist/components/product/ProductVariationSelectors.js +1 -1
- package/dist/components/rich-text/CategoryDescription.js +2 -2
- package/dist/components/rich-text/ProductDescription.js +2 -2
- package/dist/components/rich-text/RichTextExcerpt.js +1 -1
- package/dist/components/rich-text/SafeRichHtml.d.ts +8 -11
- package/dist/components/rich-text/SafeRichHtml.d.ts.map +1 -1
- package/dist/components/rich-text/SafeRichHtml.js +3 -5
- package/dist/components/shipping/ProductDeliveryInfoSection.d.ts.map +1 -1
- package/dist/components/shipping/ProductDeliveryInfoSection.js +7 -4
- package/dist/components/shipping/ShippingQuoteAddressForm.js +3 -3
- package/dist/components/ui/alert-modal.js +2 -2
- package/dist/components/ui/avatar-image.js +1 -1
- package/dist/components/ui/badge.js +1 -1
- package/dist/components/ui/breadcrumbs.js +1 -1
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/button.js +1 -1
- package/dist/components/ui/card.js +1 -1
- package/dist/components/ui/category-tree.js +2 -2
- package/dist/components/ui/checkout-button.d.ts.map +1 -1
- package/dist/components/ui/checkout-button.js +11 -283
- package/dist/components/ui/guest-checkout-modal.js +3 -3
- package/dist/components/ui/image-skeleton.js +2 -2
- package/dist/components/ui/image-with-fallback.d.ts +3 -1
- package/dist/components/ui/image-with-fallback.d.ts.map +1 -1
- package/dist/components/ui/image-with-fallback.js +55 -139
- package/dist/components/ui/input.js +1 -1
- package/dist/components/ui/limited-time-offer-badge.js +2 -2
- package/dist/components/ui/loading-skeleton-wrapper.js +1 -1
- package/dist/components/ui/modal.js +2 -2
- package/dist/components/ui/otp-input.js +1 -1
- package/dist/components/ui/out-of-stock-overlay.js +1 -1
- package/dist/components/ui/page-animate-wrapper.js +2 -2
- package/dist/components/ui/page-content-loader.js +2 -2
- package/dist/components/ui/page-loader.js +1 -1
- package/dist/components/ui/product-card.d.ts.map +1 -1
- package/dist/components/ui/product-card.js +17 -17
- package/dist/components/ui/product-rating.js +1 -1
- package/dist/components/ui/product-stock-badge.js +2 -2
- package/dist/components/ui/review-form.js +2 -2
- package/dist/components/ui/route-skeleton-loader.js +6 -6
- package/dist/components/ui/sheet.d.ts +2 -1
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +4 -4
- package/dist/components/ui/skeleton.js +1 -1
- package/dist/components/ui/skeletons/index.js +5 -5
- package/dist/components/ui/skeletons/product-grid-skeleton.js +1 -1
- package/dist/components/ui/store-logos.d.ts.map +1 -1
- package/dist/components/ui/store-logos.js +4 -4
- package/dist/components/ui/toast.js +1 -1
- package/dist/components/ui/video-player.js +2 -2
- package/dist/editor/alignment-schemas/booking.js +1 -1
- package/dist/editor/alignment-schemas/clothing-minimal.js +1 -1
- package/dist/editor/alignment-schemas/index.js +20 -20
- package/dist/editor/alignment-schemas/lazy-loader.js +1 -1
- package/dist/editor/alignment-schemas/metadata.js +1 -1
- package/dist/editor/alignment-schemas/resolve-canvas-inspector-leaf.js +3 -3
- package/dist/editor/blocks/index.js +3 -3
- package/dist/editor/blocks/migrate-clothing-minimal.js +3 -3
- package/dist/editor/blocks/nav-outline-utils.js +2 -2
- package/dist/editor/blocks/registry.js +1 -1
- package/dist/editor/document/adapter.js +4 -4
- package/dist/editor/document/block-tree.js +1 -1
- package/dist/editor/document/index.js +6 -6
- package/dist/editor/document/validate.js +1 -1
- package/dist/editor/index.js +12 -12
- package/dist/editor/layout-content-loader.js +3 -3
- package/dist/editor/payaza-form/ArrayItemManager.js +2 -2
- package/dist/editor/payaza-form/EditorCanvasPreview.js +5 -5
- package/dist/editor/payaza-form/FieldRenderer.js +17 -17
- package/dist/editor/payaza-form/IframePreviewMessageBridge.js +3 -3
- package/dist/editor/payaza-form/PayazaFormEditor.js +31 -31
- package/dist/editor/payaza-form/PayazaFormEditorFullPage.js +2 -2
- package/dist/editor/payaza-form/SliderArrayEditor.js +3 -3
- package/dist/editor/payaza-form/components/ArrayField.js +1 -1
- package/dist/editor/payaza-form/components/BlockStylesInspector.js +1 -1
- package/dist/editor/payaza-form/components/BlocksField.js +9 -9
- package/dist/editor/payaza-form/components/ButtonField.js +3 -3
- package/dist/editor/payaza-form/components/CanvasGranularInspector.js +11 -11
- package/dist/editor/payaza-form/components/IconField.js +2 -2
- package/dist/editor/payaza-form/components/ImageField.js +1 -1
- package/dist/editor/payaza-form/components/NumberField.js +1 -1
- package/dist/editor/payaza-form/components/RichTextField.js +2 -2
- package/dist/editor/payaza-form/components/SectionField.js +1 -1
- package/dist/editor/payaza-form/components/SectionSchemaInspector.js +3 -3
- package/dist/editor/payaza-form/components/StarRatingField.js +2 -2
- package/dist/editor/payaza-form/components/TextField.js +1 -1
- package/dist/editor/payaza-form/data-merger.js +6 -6
- package/dist/editor/payaza-form/data-transformer.js +3 -3
- package/dist/editor/payaza-form/engine.js +1 -1
- package/dist/editor/payaza-form/examples/custom-widgets.example.js +1 -1
- package/dist/editor/payaza-form/index.js +8 -8
- package/dist/editor/payaza-form/layout-loader.js +2 -2
- package/dist/editor/payaza-form/preview-message-handler.js +3 -3
- package/dist/editor/payaza-form/schema-bridge.js +3 -3
- package/dist/editor/payaza-form/widget-registry.js +1 -1
- package/dist/editor/shared/BlockOutlineTree.js +2 -2
- package/dist/editor/shared/EditorHeader.js +2 -2
- package/dist/editor/shared/EditorNavigator.js +2 -2
- package/dist/editor/shared/IframePreview.js +6 -6
- package/dist/editor/shared/InlineCanvasPreview.js +8 -8
- package/dist/editor/shared/LayoutRenderer.js +1 -1
- package/dist/editor/shared/ResizableSidebar.js +1 -1
- package/dist/editor/shared/index.js +7 -7
- package/dist/hooks/use-analytics.js +3 -3
- package/dist/hooks/use-api.js +1 -1
- package/dist/hooks/use-cart-api.d.ts.map +1 -1
- package/dist/hooks/use-cart-api.js +8 -3
- package/dist/hooks/use-catalog-loader.js +3 -3
- package/dist/hooks/use-catalog-price-range.d.ts +2 -5
- package/dist/hooks/use-catalog-price-range.d.ts.map +1 -1
- package/dist/hooks/use-catalog-price-range.js +20 -5
- package/dist/hooks/use-category-products.d.ts +8 -0
- package/dist/hooks/use-category-products.d.ts.map +1 -0
- package/dist/hooks/use-category-products.js +43 -0
- package/dist/hooks/use-checkout-fees.d.ts +3 -4
- package/dist/hooks/use-checkout-fees.d.ts.map +1 -1
- package/dist/hooks/use-checkout-fees.js +1 -1
- package/dist/hooks/use-content-ready.js +1 -1
- package/dist/hooks/use-featured-products.d.ts +3 -0
- package/dist/hooks/use-featured-products.d.ts.map +1 -0
- package/dist/hooks/use-featured-products.js +25 -0
- package/dist/hooks/use-multi-store-checkout-fees.d.ts +3 -4
- package/dist/hooks/use-multi-store-checkout-fees.d.ts.map +1 -1
- package/dist/hooks/use-multi-store-checkout-fees.js +1 -1
- package/dist/hooks/use-navigation-loading.js +1 -1
- package/dist/hooks/use-payaza-checkout.d.ts +1 -1
- package/dist/hooks/use-payaza-checkout.d.ts.map +1 -1
- package/dist/hooks/use-payaza-checkout.js +9 -3
- package/dist/hooks/use-product-brands.d.ts +5 -0
- package/dist/hooks/use-product-brands.d.ts.map +1 -0
- package/dist/hooks/use-product-brands.js +25 -0
- package/dist/hooks/use-product-by-slug.d.ts +7 -0
- package/dist/hooks/use-product-by-slug.d.ts.map +1 -0
- package/dist/hooks/use-product-by-slug.js +38 -0
- package/dist/hooks/use-product-reviews.d.ts +15 -0
- package/dist/hooks/use-product-reviews.d.ts.map +1 -0
- package/dist/hooks/use-product-reviews.js +75 -0
- package/dist/hooks/use-product-variation-selection.d.ts +2 -8
- package/dist/hooks/use-product-variation-selection.d.ts.map +1 -1
- package/dist/hooks/use-product-variation-selection.js +1 -1
- package/dist/hooks/use-products-api.js +3 -3
- package/dist/hooks/use-shipping-quote.js +2 -2
- package/dist/hooks/use-store-api.js +4 -4
- package/dist/hooks/usePreviewSlideSync.js +1 -1
- package/dist/index.d.ts +62 -74
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +141 -153
- package/dist/layouts/booking/components/BookingHomePage.d.ts.map +1 -1
- package/dist/layouts/booking/components/BookingHomePage.js +68 -74
- package/dist/layouts/booking/components/BookingMultilineHeading.js +1 -1
- package/dist/layouts/booking/components/BookingTestimonialCard.js +2 -2
- package/dist/layouts/booking/components/BookingTestimonialsEmptyState.js +1 -1
- package/dist/layouts/booking/components/booking-footer-defaults.js +2 -2
- package/dist/layouts/booking/index.js +8 -8
- package/dist/layouts/booking/pages/AboutPage.d.ts +1 -1
- package/dist/layouts/booking/pages/AboutPage.d.ts.map +1 -1
- package/dist/layouts/booking/pages/AboutPage.js +39 -60
- package/dist/layouts/booking/pages/BookPage.d.ts.map +1 -1
- package/dist/layouts/booking/pages/BookPage.js +16 -22
- package/dist/layouts/booking/pages/CategoriesPage.js +6 -6
- package/dist/layouts/booking/pages/CategoryPage.d.ts +1 -1
- package/dist/layouts/booking/pages/CategoryPage.d.ts.map +1 -1
- package/dist/layouts/booking/pages/CategoryPage.js +17 -26
- package/dist/layouts/booking/pages/ContactPage.d.ts +1 -1
- package/dist/layouts/booking/pages/ContactPage.d.ts.map +1 -1
- package/dist/layouts/booking/pages/ContactPage.js +5 -12
- package/dist/layouts/booking/pages/ProductDetailPage.d.ts +1 -1
- package/dist/layouts/booking/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/booking/pages/ProductDetailPage.js +203 -150
- package/dist/layouts/booking/pages/ServicesPage.d.ts +1 -1
- package/dist/layouts/booking/pages/ServicesPage.d.ts.map +1 -1
- package/dist/layouts/booking/pages/ServicesPage.js +11 -31
- package/dist/layouts/booking-agenda/components/BookingHomePageAgenda.d.ts.map +1 -1
- package/dist/layouts/booking-agenda/components/BookingHomePageAgenda.js +21 -30
- package/dist/layouts/booking-agenda/index.js +8 -8
- package/dist/layouts/booking-agenda/pages/AboutPage.d.ts +1 -1
- package/dist/layouts/booking-agenda/pages/AboutPage.d.ts.map +1 -1
- package/dist/layouts/booking-agenda/pages/AboutPage.js +11 -14
- package/dist/layouts/booking-agenda/pages/BookPage.d.ts.map +1 -1
- package/dist/layouts/booking-agenda/pages/BookPage.js +15 -21
- package/dist/layouts/booking-agenda/pages/CategoriesPage.js +5 -5
- package/dist/layouts/booking-agenda/pages/CategoryPage.d.ts +1 -1
- package/dist/layouts/booking-agenda/pages/CategoryPage.d.ts.map +1 -1
- package/dist/layouts/booking-agenda/pages/CategoryPage.js +12 -18
- package/dist/layouts/booking-agenda/pages/ContactPage.d.ts +1 -1
- package/dist/layouts/booking-agenda/pages/ContactPage.d.ts.map +1 -1
- package/dist/layouts/booking-agenda/pages/ContactPage.js +8 -13
- package/dist/layouts/booking-agenda/pages/ProductDetailPage.d.ts +1 -1
- package/dist/layouts/booking-agenda/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/booking-agenda/pages/ProductDetailPage.js +202 -149
- package/dist/layouts/booking-agenda/pages/ServicesPage.d.ts +1 -1
- package/dist/layouts/booking-agenda/pages/ServicesPage.d.ts.map +1 -1
- package/dist/layouts/booking-agenda/pages/ServicesPage.js +8 -23
- package/dist/layouts/clothing/components/ClothingHomePage.d.ts.map +1 -1
- package/dist/layouts/clothing/components/ClothingHomePage.js +199 -136
- package/dist/layouts/clothing/index.js +7 -7
- package/dist/layouts/clothing/pages/AboutPage.d.ts +1 -1
- package/dist/layouts/clothing/pages/AboutPage.d.ts.map +1 -1
- package/dist/layouts/clothing/pages/AboutPage.js +19 -33
- package/dist/layouts/clothing/pages/CategoriesPage.js +8 -8
- package/dist/layouts/clothing/pages/CategoryPage.d.ts +1 -1
- package/dist/layouts/clothing/pages/CategoryPage.d.ts.map +1 -1
- package/dist/layouts/clothing/pages/CategoryPage.js +15 -41
- package/dist/layouts/clothing/pages/ContactPage.d.ts +1 -1
- package/dist/layouts/clothing/pages/ContactPage.d.ts.map +1 -1
- package/dist/layouts/clothing/pages/ContactPage.js +5 -8
- package/dist/layouts/clothing/pages/ProductDetailPage.d.ts +1 -1
- package/dist/layouts/clothing/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/clothing/pages/ProductDetailPage.js +256 -167
- package/dist/layouts/clothing/pages/ProductsPage.d.ts +1 -1
- package/dist/layouts/clothing/pages/ProductsPage.d.ts.map +1 -1
- package/dist/layouts/clothing/pages/ProductsPage.js +125 -59
- package/dist/layouts/clothing/payaza-bindings.js +2 -2
- package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.d.ts.map +1 -1
- package/dist/layouts/clothing-minimal/components/ClothingHomePageMinimal.js +32 -131
- package/dist/layouts/clothing-minimal/components/clothing-minimal-block-render.js +8 -8
- package/dist/layouts/clothing-minimal/index.js +7 -7
- package/dist/layouts/clothing-minimal/pages/AboutPage.d.ts +1 -1
- package/dist/layouts/clothing-minimal/pages/AboutPage.d.ts.map +1 -1
- package/dist/layouts/clothing-minimal/pages/AboutPage.js +37 -61
- package/dist/layouts/clothing-minimal/pages/CategoriesPage.js +7 -7
- package/dist/layouts/clothing-minimal/pages/CategoryPage.d.ts +1 -1
- package/dist/layouts/clothing-minimal/pages/CategoryPage.d.ts.map +1 -1
- package/dist/layouts/clothing-minimal/pages/CategoryPage.js +13 -18
- package/dist/layouts/clothing-minimal/pages/ContactPage.d.ts +1 -1
- package/dist/layouts/clothing-minimal/pages/ContactPage.d.ts.map +1 -1
- package/dist/layouts/clothing-minimal/pages/ContactPage.js +5 -8
- package/dist/layouts/clothing-minimal/pages/ProductDetailPage.d.ts +1 -1
- package/dist/layouts/clothing-minimal/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/clothing-minimal/pages/ProductDetailPage.js +202 -143
- package/dist/layouts/clothing-minimal/pages/ProductsPage.d.ts +1 -1
- package/dist/layouts/clothing-minimal/pages/ProductsPage.d.ts.map +1 -1
- package/dist/layouts/clothing-minimal/pages/ProductsPage.js +126 -57
- package/dist/layouts/electronics/components/ElectronicsCategoriesPage.js +7 -7
- package/dist/layouts/electronics/components/ElectronicsGridProductCard.js +10 -10
- package/dist/layouts/electronics/components/ElectronicsHomePage.d.ts.map +1 -1
- package/dist/layouts/electronics/components/ElectronicsHomePage.js +29 -31
- package/dist/layouts/electronics/components/ElectronicsPageWrapper.js +4 -4
- package/dist/layouts/electronics/components/ElectronicsProductsPage.d.ts.map +1 -1
- package/dist/layouts/electronics/components/ElectronicsProductsPage.js +81 -23
- package/dist/layouts/electronics/components/ElectronicsStoreFooter.js +2 -2
- package/dist/layouts/electronics/components/ElectronicsStoreHeader.js +6 -6
- package/dist/layouts/electronics/index.js +18 -18
- package/dist/layouts/electronics/pages/AboutPage.d.ts +1 -1
- package/dist/layouts/electronics/pages/AboutPage.d.ts.map +1 -1
- package/dist/layouts/electronics/pages/AboutPage.js +11 -14
- package/dist/layouts/electronics/pages/CategoriesPage.js +1 -1
- package/dist/layouts/electronics/pages/CategoryPage.d.ts +1 -1
- package/dist/layouts/electronics/pages/CategoryPage.d.ts.map +1 -1
- package/dist/layouts/electronics/pages/CategoryPage.js +14 -20
- package/dist/layouts/electronics/pages/ContactPage.d.ts +1 -1
- package/dist/layouts/electronics/pages/ContactPage.d.ts.map +1 -1
- package/dist/layouts/electronics/pages/ContactPage.js +8 -13
- package/dist/layouts/electronics/pages/HelpCenterPage.js +1 -1
- package/dist/layouts/electronics/pages/PrivacyPolicyPage.js +1 -1
- package/dist/layouts/electronics/pages/ProductDetailPage.d.ts +1 -1
- package/dist/layouts/electronics/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/electronics/pages/ProductDetailPage.js +201 -142
- package/dist/layouts/electronics/pages/ProductsPage.d.ts +1 -1
- package/dist/layouts/electronics/pages/ProductsPage.d.ts.map +1 -1
- package/dist/layouts/electronics/pages/ProductsPage.js +119 -49
- package/dist/layouts/electronics/pages/TermsPage.js +1 -1
- package/dist/layouts/electronics/pages/TrackOrderPage.js +4 -4
- package/dist/layouts/electronics-grid/components/ElectronicsGridProductCard.js +10 -10
- package/dist/layouts/electronics-grid/components/ElectronicsHomePageGrid.d.ts.map +1 -1
- package/dist/layouts/electronics-grid/components/ElectronicsHomePageGrid.js +75 -33
- package/dist/layouts/electronics-grid/index.js +14 -14
- package/dist/layouts/electronics-grid/pages/AboutPage.d.ts +1 -1
- package/dist/layouts/electronics-grid/pages/AboutPage.d.ts.map +1 -1
- package/dist/layouts/electronics-grid/pages/AboutPage.js +11 -14
- package/dist/layouts/electronics-grid/pages/CategoriesPage.js +1 -1
- package/dist/layouts/electronics-grid/pages/CategoryPage.d.ts +1 -1
- package/dist/layouts/electronics-grid/pages/CategoryPage.d.ts.map +1 -1
- package/dist/layouts/electronics-grid/pages/CategoryPage.js +14 -20
- package/dist/layouts/electronics-grid/pages/ContactPage.d.ts +1 -1
- package/dist/layouts/electronics-grid/pages/ContactPage.d.ts.map +1 -1
- package/dist/layouts/electronics-grid/pages/ContactPage.js +8 -13
- package/dist/layouts/electronics-grid/pages/ElectronicsGridCategoriesPage.js +6 -6
- package/dist/layouts/electronics-grid/pages/HelpCenterPage.js +1 -1
- package/dist/layouts/electronics-grid/pages/PrivacyPolicyPage.js +1 -1
- package/dist/layouts/electronics-grid/pages/ProductDetailPage.d.ts +1 -1
- package/dist/layouts/electronics-grid/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/electronics-grid/pages/ProductDetailPage.js +201 -142
- package/dist/layouts/electronics-grid/pages/ProductsPage.d.ts +1 -1
- package/dist/layouts/electronics-grid/pages/ProductsPage.d.ts.map +1 -1
- package/dist/layouts/electronics-grid/pages/ProductsPage.js +121 -52
- package/dist/layouts/electronics-grid/pages/TermsPage.js +1 -1
- package/dist/layouts/electronics-grid/pages/TrackOrderPage.js +4 -4
- package/dist/layouts/food/components/FoodHomePage.d.ts.map +1 -1
- package/dist/layouts/food/components/FoodHomePage.js +42 -41
- package/dist/layouts/food/index.js +8 -8
- package/dist/layouts/food/pages/AboutPage.d.ts +1 -1
- package/dist/layouts/food/pages/AboutPage.d.ts.map +1 -1
- package/dist/layouts/food/pages/AboutPage.js +44 -45
- package/dist/layouts/food/pages/CategoriesPage.js +6 -6
- package/dist/layouts/food/pages/CategoryPage.d.ts +1 -1
- package/dist/layouts/food/pages/CategoryPage.d.ts.map +1 -1
- package/dist/layouts/food/pages/CategoryPage.js +23 -21
- package/dist/layouts/food/pages/ContactPage.d.ts +1 -1
- package/dist/layouts/food/pages/ContactPage.d.ts.map +1 -1
- package/dist/layouts/food/pages/ContactPage.js +5 -7
- package/dist/layouts/food/pages/MenuPage.d.ts +1 -1
- package/dist/layouts/food/pages/MenuPage.d.ts.map +1 -1
- package/dist/layouts/food/pages/MenuPage.js +81 -42
- package/dist/layouts/food/pages/ProductDetailPage.d.ts +1 -1
- package/dist/layouts/food/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/food/pages/ProductDetailPage.js +209 -171
- package/dist/layouts/food/pages/ProductsPage.d.ts +1 -1
- package/dist/layouts/food/pages/ProductsPage.d.ts.map +1 -1
- package/dist/layouts/food/pages/ProductsPage.js +130 -45
- package/dist/layouts/food-modern/components/FoodHomePageModern.js +10 -10
- package/dist/layouts/food-modern/index.js +8 -8
- package/dist/layouts/food-modern/pages/AboutPage.d.ts +1 -1
- package/dist/layouts/food-modern/pages/AboutPage.d.ts.map +1 -1
- package/dist/layouts/food-modern/pages/AboutPage.js +11 -14
- package/dist/layouts/food-modern/pages/CategoriesPage.js +5 -5
- package/dist/layouts/food-modern/pages/CategoryPage.d.ts +1 -1
- package/dist/layouts/food-modern/pages/CategoryPage.d.ts.map +1 -1
- package/dist/layouts/food-modern/pages/CategoryPage.js +23 -20
- package/dist/layouts/food-modern/pages/ContactPage.d.ts +1 -1
- package/dist/layouts/food-modern/pages/ContactPage.d.ts.map +1 -1
- package/dist/layouts/food-modern/pages/ContactPage.js +5 -7
- package/dist/layouts/food-modern/pages/MenuPage.d.ts +1 -1
- package/dist/layouts/food-modern/pages/MenuPage.d.ts.map +1 -1
- package/dist/layouts/food-modern/pages/MenuPage.js +81 -46
- package/dist/layouts/food-modern/pages/ProductDetailPage.d.ts +1 -1
- package/dist/layouts/food-modern/pages/ProductDetailPage.d.ts.map +1 -1
- package/dist/layouts/food-modern/pages/ProductDetailPage.js +202 -143
- package/dist/layouts/food-modern/pages/ProductsPage.d.ts +1 -1
- package/dist/layouts/food-modern/pages/ProductsPage.d.ts.map +1 -1
- package/dist/layouts/food-modern/pages/ProductsPage.js +117 -33
- package/dist/layouts/motivational-speaker/components/MotivationalHomePage.d.ts.map +1 -1
- package/dist/layouts/motivational-speaker/components/MotivationalHomePage.js +32 -23
- package/dist/layouts/motivational-speaker/components/MotivationalSpeakerPageWrapper.js +1 -1
- package/dist/layouts/motivational-speaker/index.js +9 -9
- package/dist/layouts/motivational-speaker/pages/AboutPage.d.ts +1 -1
- package/dist/layouts/motivational-speaker/pages/AboutPage.d.ts.map +1 -1
- package/dist/layouts/motivational-speaker/pages/AboutPage.js +6 -9
- package/dist/layouts/motivational-speaker/pages/CategoryPage.d.ts +1 -1
- package/dist/layouts/motivational-speaker/pages/CategoryPage.d.ts.map +1 -1
- package/dist/layouts/motivational-speaker/pages/CategoryPage.js +18 -22
- package/dist/layouts/motivational-speaker/pages/ContactPage.d.ts +1 -1
- package/dist/layouts/motivational-speaker/pages/ContactPage.d.ts.map +1 -1
- package/dist/layouts/motivational-speaker/pages/ContactPage.js +9 -13
- package/dist/layouts/motivational-speaker/pages/ProductsPage.d.ts +1 -1
- package/dist/layouts/motivational-speaker/pages/ProductsPage.d.ts.map +1 -1
- package/dist/layouts/motivational-speaker/pages/ProductsPage.js +121 -52
- package/dist/layouts/motivational-speaker/pages/ServiceDetailPage.d.ts +1 -1
- package/dist/layouts/motivational-speaker/pages/ServiceDetailPage.d.ts.map +1 -1
- package/dist/layouts/motivational-speaker/pages/ServiceDetailPage.js +15 -24
- package/dist/layouts/motivational-speaker/pages/ServicesPage.d.ts +1 -1
- package/dist/layouts/motivational-speaker/pages/ServicesPage.d.ts.map +1 -1
- package/dist/layouts/motivational-speaker/pages/ServicesPage.js +8 -23
- package/dist/layouts/motivational-speaker/pages/SubscriptionPage.js +7 -7
- package/dist/layouts/shared/components/BaseStoreLayout.js +9 -9
- package/dist/layouts/shared/components/CartLineQuantityControl.js +1 -1
- package/dist/layouts/shared/components/GenericPageWrapper.js +3 -3
- package/dist/layouts/shared/components/ProductPriceFilterSection.js +4 -4
- package/dist/layouts/shared/components/PromoBanner.d.ts +1 -4
- package/dist/layouts/shared/components/PromoBanner.d.ts.map +1 -1
- package/dist/layouts/shared/components/PromoBanner.js +12 -8
- package/dist/layouts/shared/components/StoreFooter.js +10 -10
- package/dist/layouts/shared/components/StoreHeader.js +7 -7
- package/dist/layouts/shared/components/TestimonialCard.d.ts.map +1 -1
- package/dist/layouts/shared/components/TestimonialCard.js +2 -2
- package/dist/layouts/shared/components/headers/BookingAgendaSidebar.js +2 -2
- package/dist/layouts/shared/components/headers/ClothingMinimalHeader.js +11 -11
- package/dist/layouts/shared/components/headers/FoodModernHeader.js +7 -7
- package/dist/layouts/shared/index.js +22 -22
- package/dist/layouts/shared/pages/AccountPage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/AccountPage.js +101 -27
- package/dist/layouts/shared/pages/CartPage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/CartPage.js +39 -90
- package/dist/layouts/shared/pages/CheckoutPage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/CheckoutPage.js +349 -868
- package/dist/layouts/shared/pages/CookiePolicyPage.js +1 -1
- package/dist/layouts/shared/pages/FeatureDisabledPage.js +1 -1
- package/dist/layouts/shared/pages/HelpCenterPage.js +1 -1
- package/dist/layouts/shared/pages/MaintenancePage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/MaintenancePage.js +2 -3
- package/dist/layouts/shared/pages/PortfolioPage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/PortfolioPage.js +2 -2
- package/dist/layouts/shared/pages/PrivacyPolicyPage.js +1 -1
- package/dist/layouts/shared/pages/SizeGuidePage.js +1 -1
- package/dist/layouts/shared/pages/StyleGuidePage.js +3 -3
- package/dist/layouts/shared/pages/TeamPage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/TeamPage.js +1 -1
- package/dist/layouts/shared/pages/TermsPage.js +1 -1
- package/dist/layouts/shared/pages/TrackOrderPage.js +4 -4
- package/dist/layouts/shared/pages/WishlistPage.d.ts.map +1 -1
- package/dist/layouts/shared/pages/WishlistPage.js +44 -29
- package/dist/layouts/shared/wrappers/BookingAgendaLayout.d.ts.map +1 -1
- package/dist/layouts/shared/wrappers/BookingAgendaLayout.js +80 -35
- package/dist/layouts/shared/wrappers/ClothingLayout.js +8 -8
- package/dist/layouts/shared/wrappers/ClothingMinimalLayout.js +7 -7
- package/dist/layouts/shared/wrappers/ElectronicsLayout.js +5 -5
- package/dist/layouts/shared/wrappers/FoodModernLayout.js +6 -6
- package/dist/layouts/shared/wrappers/MotivationalLayout.js +9 -9
- package/dist/layouts/shared/wrappers/StandardLayout.js +8 -8
- package/dist/lib/api.d.ts +3 -4
- package/dist/lib/api.d.ts.map +1 -1
- package/dist/lib/api.js +69 -109
- package/dist/lib/auth-context.js +2 -2
- package/dist/lib/content-selectors.d.ts +1 -1
- package/dist/lib/content-selectors.d.ts.map +1 -1
- package/dist/lib/content-selectors.js +8 -0
- package/dist/lib/layout-config-hydration.js +2 -2
- package/dist/lib/loading-context.d.ts.map +1 -1
- package/dist/lib/loading-context.js +11 -21
- package/dist/lib/payaza-checkout.d.ts.map +1 -1
- package/dist/lib/payaza-checkout.js +2 -4
- package/dist/lib/payaza-public-key.d.ts +6 -0
- package/dist/lib/payaza-public-key.d.ts.map +1 -0
- package/dist/lib/payaza-public-key.js +10 -0
- package/dist/lib/rich-html.d.ts +31 -26
- package/dist/lib/rich-html.d.ts.map +1 -1
- package/dist/lib/rich-html.js +1 -0
- package/dist/lib/services/analytics.service.js +1 -1
- package/dist/lib/services/auth.service.js +1 -1
- package/dist/lib/services/booking.service.js +1 -1
- package/dist/lib/services/category.service.js +1 -1
- package/dist/lib/services/checkout.service.d.ts +3 -0
- package/dist/lib/services/checkout.service.d.ts.map +1 -1
- package/dist/lib/services/checkout.service.js +1 -1
- package/dist/lib/services/contact.service.js +1 -1
- package/dist/lib/services/customer.service.js +1 -1
- package/dist/lib/services/fees.service.d.ts +17 -63
- package/dist/lib/services/fees.service.d.ts.map +1 -1
- package/dist/lib/services/fees.service.js +4 -138
- package/dist/lib/services/index.js +13 -13
- package/dist/lib/services/location.service.js +1 -1
- package/dist/lib/services/order.service.js +1 -1
- package/dist/lib/services/payment.service.js +1 -1
- package/dist/lib/services/product.service.d.ts +7 -2
- package/dist/lib/services/product.service.d.ts.map +1 -1
- package/dist/lib/services/product.service.js +13 -1
- package/dist/lib/services/promo.service.js +1 -1
- package/dist/lib/services/review.service.js +1 -1
- package/dist/lib/services/service.service.js +1 -1
- package/dist/lib/services/shipping.service.js +1 -1
- package/dist/lib/services/store.service.js +1 -1
- package/dist/lib/store-config-utils.d.ts +1 -10
- package/dist/lib/store-config-utils.d.ts.map +1 -1
- package/dist/lib/store-config-utils.js +73 -261
- package/dist/lib/store-config.js +1 -1
- package/dist/lib/store-context.d.ts +1 -23
- package/dist/lib/store-context.d.ts.map +1 -1
- package/dist/lib/store-context.js +89 -111
- package/dist/lib/store-types.d.ts +9 -91
- package/dist/lib/store-types.d.ts.map +1 -1
- package/dist/lib/utils/asset-helpers.js +1 -1
- package/dist/lib/utils/catalog-price-bounds.d.ts +16 -6
- package/dist/lib/utils/catalog-price-bounds.d.ts.map +1 -1
- package/dist/lib/utils/catalog-price-bounds.js +58 -14
- package/dist/lib/utils/category-tree.d.ts +9 -0
- package/dist/lib/utils/category-tree.d.ts.map +1 -1
- package/dist/lib/utils/category-tree.js +26 -0
- package/dist/lib/utils/editor-preview.d.ts +5 -0
- package/dist/lib/utils/editor-preview.d.ts.map +1 -1
- package/dist/lib/utils/editor-preview.js +17 -3
- package/dist/lib/utils/fee-calculations.d.ts +5 -1
- package/dist/lib/utils/fee-calculations.d.ts.map +1 -1
- package/dist/lib/utils/fee-calculations.js +28 -31
- package/dist/lib/utils/index.d.ts +1 -1
- package/dist/lib/utils/index.d.ts.map +1 -1
- package/dist/lib/utils/index.js +4 -4
- package/dist/lib/utils/product-images.js +1 -1
- package/dist/lib/utils/product-share.js +1 -1
- package/dist/lib/utils/url-helpers.d.ts +6 -0
- package/dist/lib/utils/url-helpers.d.ts.map +1 -1
- package/dist/lib/utils/url-helpers.js +20 -4
- package/dist/preview/LayoutPreview.js +6 -6
- package/dist/preview/PreviewRouter.js +4 -4
- package/dist/preview/canvas/PreviewCanvasEnhancer.js +8 -8
- package/dist/preview/canvas/preview-flush-await.js +1 -1
- package/dist/preview/canvas/preview-isolation.js +1 -1
- package/dist/preview/canvas/preview-route.js +2 -2
- package/dist/preview/index.js +5 -5
- package/dist/styles/index.css +190 -149
- package/package.json +31 -43
|
@@ -1,99 +1,36 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { Button } from '../../../components/ui/button
|
|
4
|
-
import { ArrowLeft, ShieldCheck, CheckCircle, MapPin, Lock, ChevronRight, Tag, Truck, ChevronDown
|
|
3
|
+
import { Button } from '../../../components/ui/button';
|
|
4
|
+
import { ArrowLeft, ShieldCheck, CheckCircle, MapPin, Lock, ChevronRight, Tag, Truck, ChevronDown } from 'lucide-react';
|
|
5
5
|
import Link from 'next/link';
|
|
6
6
|
import Image from 'next/image';
|
|
7
|
-
import { useState, useEffect, useRef
|
|
8
|
-
import { cn } from '../../../lib/utils
|
|
9
|
-
import { usePayazaCheckout } from '../../../hooks/use-payaza-checkout
|
|
10
|
-
import { generateCheckoutConfig } from '../../../lib/payaza-checkout
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
7
|
+
import { useState, useEffect, useRef } from 'react';
|
|
8
|
+
import { cn } from '../../../lib/utils';
|
|
9
|
+
import { usePayazaCheckout } from '../../../hooks/use-payaza-checkout';
|
|
10
|
+
import { generateCheckoutConfig } from '../../../lib/payaza-checkout';
|
|
11
|
+
import { requireCheckoutPayazaPublicKey } from '../../../lib/payaza-public-key';
|
|
12
|
+
import { useToast } from '../../../components/ui/toast';
|
|
13
|
+
import { useStore } from '../../../lib/store-context';
|
|
13
14
|
import { useRouter } from 'next/navigation';
|
|
14
|
-
import { normalizePrice, formatCurrency
|
|
15
|
-
import { checkoutService } from '../../../lib/services/checkout.service
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { shippingService } from '../../../lib/services/shipping.service.js';
|
|
27
|
-
import { CHECKOUT_UNAVAILABLE_MESSAGE } from '../../../lib/checkout-availability.js';
|
|
28
|
-
import { getCartLineImage } from '../../../lib/product-variation-utils.js';
|
|
29
|
-
import { enrichShippingQuoteAddress, buildShippingCalculatePayload, canRequestShippingQuote, isNigeria, deriveNigerianStateFromCity, NIGERIAN_STATES } from '../../../lib/shipping-address.util.js';
|
|
30
|
-
import { ShippingOptionsPanel } from '../../../components/checkout/ShippingOptionsPanel.js';
|
|
31
|
-
import { CheckoutSkeleton } from '../../../components/ui/skeletons/checkout-skeleton.js';
|
|
32
|
-
const CHECKOUT_ITEM_UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
33
|
-
function normalizeCheckoutPayloadItem(item) {
|
|
34
|
-
const product = item.product;
|
|
35
|
-
return {
|
|
36
|
-
productId: item.productId,
|
|
37
|
-
storeId: product?.storeId,
|
|
38
|
-
quantity: Number(item.quantity) || 1,
|
|
39
|
-
price: normalizePrice(item.price),
|
|
40
|
-
variationId: item.variantId && CHECKOUT_ITEM_UUID_REGEX.test(item.variantId)
|
|
41
|
-
? item.variantId
|
|
42
|
-
: undefined,
|
|
43
|
-
variantId: item.variantId ?? undefined,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
import { useAnalytics } from '../../../hooks/use-analytics.js';
|
|
47
|
-
import { getOrCreateIdempotencyKey, resetIdempotencyKey } from '../../../lib/utils/idempotency-key.js';
|
|
48
|
-
import { formatPhoneInput, validatePhone, getPhonePlaceholder, normalizePhoneForPayaza } from '../../../lib/utils/phone-format.js';
|
|
49
|
-
import { saveGuestUserInfo } from '../../../lib/guest-user.js';
|
|
50
|
-
import { InventoryHoldCountdown } from '../../../components/cart/InventoryHoldCountdown.js';
|
|
51
|
-
import { formatCheckoutStockError, getCartLineStockInfo, isInventoryHoldExpired } from '../../../lib/inventory-utils.js';
|
|
52
|
-
import { MerchantHandledDeliveryNotice } from '../../../components/shipping/MerchantHandledDeliveryNotice.js';
|
|
53
|
-
import { isAutomatedShippingEnabled } from '../../../lib/shipping-settings.util.js';
|
|
54
|
-
import { setOrderSuccessJustCompleted, isOrderSuccessPending } from '../../../lib/order-success-cart.js';
|
|
55
|
-
import { trackStorefrontEvent } from '../../../lib/mixpanel.js';
|
|
56
|
-
const PROMO_CODE_ENABLED = false;
|
|
57
|
-
function isPaymentInventoryError(error) {
|
|
58
|
-
if (!error || typeof error !== 'object')
|
|
59
|
-
return false;
|
|
60
|
-
const candidate = error;
|
|
61
|
-
if (candidate.status === 409)
|
|
62
|
-
return true;
|
|
63
|
-
const message = (candidate.message || '').toLowerCase();
|
|
64
|
-
return (message.includes('insufficient stock') ||
|
|
65
|
-
message.includes('insufficient inventory') ||
|
|
66
|
-
message.includes('out of stock'));
|
|
67
|
-
}
|
|
68
|
-
function buildOrderSuccessUrl(storeSlug, params) {
|
|
69
|
-
const search = new URLSearchParams({
|
|
70
|
-
orderId: params.orderId,
|
|
71
|
-
ref: params.ref,
|
|
72
|
-
});
|
|
73
|
-
if (params.orderNumber)
|
|
74
|
-
search.set('orderNumber', params.orderNumber);
|
|
75
|
-
if (params.email)
|
|
76
|
-
search.set('email', params.email);
|
|
77
|
-
if (params.fulfillmentPending)
|
|
78
|
-
search.set('fulfillmentPending', 'true');
|
|
79
|
-
return `/${storeSlug}/order/success?${search.toString()}`;
|
|
80
|
-
}
|
|
81
|
-
function RequiredLabel({ children }) {
|
|
82
|
-
return (_jsxs("label", { className: "text-sm font-bold text-gray-700 ml-1", children: [children, " ", _jsx("span", { className: "text-red-500", children: "*" })] }));
|
|
83
|
-
}
|
|
15
|
+
import { normalizePrice, formatCurrency } from '../../../lib/utils';
|
|
16
|
+
import { checkoutService } from '../../../lib/services/checkout.service';
|
|
17
|
+
import { paymentService } from '../../../lib/services/payment.service';
|
|
18
|
+
import { useAuth } from '../../../lib/auth-context';
|
|
19
|
+
import { customerService } from '../../../lib/services/customer.service';
|
|
20
|
+
import { AddressSelector } from '../../../components/ui/address-selector';
|
|
21
|
+
import { countries, getCountryByName, getCitiesByCountry } from '../../../lib/countries';
|
|
22
|
+
import { calculateVAT, getVATLabel } from '../../../lib/utils/fee-calculations';
|
|
23
|
+
import { shippingService } from '../../../lib/services/shipping.service';
|
|
24
|
+
import { feesService } from '../../../lib/services/fees.service';
|
|
25
|
+
import { CheckoutSkeleton } from '../../../components/ui/skeletons/checkout-skeleton';
|
|
26
|
+
import { useAnalytics } from '../../../hooks/use-analytics';
|
|
84
27
|
export function CheckoutPage({ storeConfig }) {
|
|
85
28
|
const primaryColor = storeConfig.branding.primaryColor;
|
|
86
|
-
const
|
|
87
|
-
const { cart, cartTotal, buyNowItem, clearBuyNowItem, setBuyNowItem, inventoryHoldUntil, setInventoryHoldUntil, handleInventoryHoldExpired, isCartRefreshing, refreshCart, finalizeCartAfterOrder, removeOrderItemsFromCart, isCartHydrated } = useStore();
|
|
29
|
+
const { cart, cartTotal, buyNowItem, clearBuyNowItem, setBuyNowItem } = useStore();
|
|
88
30
|
const { addToast } = useToast();
|
|
89
31
|
const router = useRouter();
|
|
90
32
|
const { user, isAuthenticated } = useAuth();
|
|
91
33
|
const { trackEvent } = useAnalytics();
|
|
92
|
-
const [isHoldExpired, setIsHoldExpired] = useState(false);
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
setIsHoldExpired(isInventoryHoldExpired(inventoryHoldUntil));
|
|
95
|
-
}, [inventoryHoldUntil]);
|
|
96
|
-
const isHoldBlocking = !!inventoryHoldUntil && (isHoldExpired || isInventoryHoldExpired(inventoryHoldUntil));
|
|
97
34
|
// Read from sessionStorage synchronously on mount if buyNowItem is not in context
|
|
98
35
|
const [sessionBuyNowItem] = useState(() => {
|
|
99
36
|
if (typeof window === 'undefined')
|
|
@@ -109,215 +46,47 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
109
46
|
}
|
|
110
47
|
return null;
|
|
111
48
|
});
|
|
112
|
-
// State to track
|
|
49
|
+
// State to track initialization
|
|
50
|
+
const [isInitializing, setIsInitializing] = useState(true);
|
|
113
51
|
const hasRestoredRef = useRef(false);
|
|
114
52
|
// Restore buyNowItem from sessionStorage to context if needed (only once on mount)
|
|
115
53
|
useEffect(() => {
|
|
54
|
+
// Only run restoration once
|
|
116
55
|
if (hasRestoredRef.current) {
|
|
117
56
|
return;
|
|
118
57
|
}
|
|
119
58
|
hasRestoredRef.current = true;
|
|
120
|
-
|
|
59
|
+
// If item is already in context, we're done
|
|
60
|
+
if (buyNowItem || sessionBuyNowItem) {
|
|
61
|
+
setIsInitializing(false);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
// If we have sessionBuyNowItem but not in context, restore it
|
|
65
|
+
if (sessionBuyNowItem) {
|
|
121
66
|
setBuyNowItem(sessionBuyNowItem);
|
|
122
67
|
}
|
|
68
|
+
// Add a small delay to ensure state propagation
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
setIsInitializing(false);
|
|
71
|
+
}, 150);
|
|
72
|
+
return () => clearTimeout(timer);
|
|
123
73
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
124
|
-
}, []);
|
|
125
|
-
//
|
|
74
|
+
}, []); // Only run once on mount
|
|
75
|
+
// Stop initializing if buyNowItem becomes available after mount
|
|
126
76
|
useEffect(() => {
|
|
127
|
-
if (
|
|
128
|
-
|
|
77
|
+
if (buyNowItem && isInitializing) {
|
|
78
|
+
setIsInitializing(false);
|
|
129
79
|
}
|
|
130
|
-
}, [
|
|
80
|
+
}, [buyNowItem, isInitializing]);
|
|
131
81
|
// Use buyNowItem from context or sessionStorage, otherwise use cart
|
|
132
82
|
const effectiveBuyNowItem = buyNowItem || sessionBuyNowItem;
|
|
133
83
|
const checkoutItems = effectiveBuyNowItem ? [effectiveBuyNowItem] : cart;
|
|
134
|
-
const isMarketplaceCheckout = storeConfig.layout === 'marketplace' || storeConfig.slug === 'marketplace';
|
|
135
|
-
// Group checkout items by their real storeId regardless of context (marketplace or not) — a
|
|
136
|
-
// cart can end up spanning more than one store, and fee calculation needs to be done per store
|
|
137
|
-
// group either way. For marketplace checkouts specifically, the backend also creates one Order
|
|
138
|
-
// (and settles one Payment) per store even though the customer pays once — group the summary
|
|
139
|
-
// the same way so the customer sees this before submitting, mirroring Amazon's "split into N
|
|
140
|
-
// shipments" pattern.
|
|
141
|
-
const storeBreakdown = useMemo(() => {
|
|
142
|
-
const groups = new Map();
|
|
143
|
-
for (const item of checkoutItems) {
|
|
144
|
-
const product = item.product;
|
|
145
|
-
const storeId = product?.storeId || storeConfig.id;
|
|
146
|
-
const storeName = product?.storeName || `Store ${storeId.slice(0, 8)}`;
|
|
147
|
-
const existing = groups.get(storeId) || { storeId, storeName, subtotal: 0, itemCount: 0 };
|
|
148
|
-
existing.subtotal += normalizePrice(item.price) * item.quantity;
|
|
149
|
-
existing.itemCount += item.quantity;
|
|
150
|
-
groups.set(storeId, existing);
|
|
151
|
-
}
|
|
152
|
-
return Array.from(groups.values());
|
|
153
|
-
}, [checkoutItems, storeConfig.id]);
|
|
154
|
-
// Fees must be split per store whenever the cart spans more than one — this isn't limited to
|
|
155
|
-
// the "marketplace" virtual storefront, it's whatever the item data says.
|
|
156
|
-
const isMultiStoreCheckout = storeBreakdown.length > 1;
|
|
157
|
-
const checkoutStockIssues = useMemo(() => {
|
|
158
|
-
return checkoutItems
|
|
159
|
-
.map((item) => {
|
|
160
|
-
const stockInfo = getCartLineStockInfo(item.product, item.quantity);
|
|
161
|
-
const message = stockInfo.stockError || formatCheckoutStockError(item.product, item.quantity);
|
|
162
|
-
if (!message)
|
|
163
|
-
return null;
|
|
164
|
-
return { id: item.id, name: item.product.name, message };
|
|
165
|
-
})
|
|
166
|
-
.filter((issue) => issue != null);
|
|
167
|
-
}, [checkoutItems]);
|
|
168
|
-
const hasCheckoutStockIssues = checkoutStockIssues.length > 0;
|
|
169
84
|
const checkoutTotal = effectiveBuyNowItem
|
|
170
85
|
? effectiveBuyNowItem.price * effectiveBuyNowItem.quantity
|
|
171
86
|
: cartTotal;
|
|
172
|
-
const checkoutEntireCart = !effectiveBuyNowItem;
|
|
173
|
-
const completingOrderRef = useRef(false);
|
|
174
|
-
const [isCompletingOrder, setIsCompletingOrder] = useState(false);
|
|
175
|
-
const isPostPaymentNavigationPending = () => completingOrderRef.current || isCompletingOrder || isOrderSuccessPending();
|
|
176
|
-
const beginPostPaymentNavigation = (orderId) => {
|
|
177
|
-
completingOrderRef.current = true;
|
|
178
|
-
setIsCompletingOrder(true);
|
|
179
|
-
setOrderSuccessJustCompleted(orderId);
|
|
180
|
-
};
|
|
181
|
-
const redirectToPaymentCallback = (transactionRef) => {
|
|
182
|
-
beginPostPaymentNavigation(transactionRef);
|
|
183
|
-
router.replace(buildPaymentCallbackUrl(transactionRef));
|
|
184
|
-
};
|
|
185
|
-
const redirectToPaymentError = (reason, transactionRef, detailMessage) => {
|
|
186
|
-
const params = new URLSearchParams({ reason });
|
|
187
|
-
if (transactionRef)
|
|
188
|
-
params.set('ref', transactionRef);
|
|
189
|
-
if (detailMessage)
|
|
190
|
-
params.set('message', detailMessage);
|
|
191
|
-
setLoading(false);
|
|
192
|
-
router.replace(`/${storeConfig.slug}/payment/error?${params.toString()}`);
|
|
193
|
-
};
|
|
194
|
-
const completeOrderAndRedirect = async (orderId, transactionRef, orderNumber, customerEmail, fulfillmentPending) => {
|
|
195
|
-
beginPostPaymentNavigation(orderId);
|
|
196
|
-
if (fulfillmentPending) {
|
|
197
|
-
addToast('Payment received. Your order is under review — we will contact you if any items are unavailable.', 'info');
|
|
198
|
-
}
|
|
199
|
-
router.replace(buildOrderSuccessUrl(storeConfig.slug, {
|
|
200
|
-
orderId,
|
|
201
|
-
ref: transactionRef,
|
|
202
|
-
orderNumber: orderNumber || undefined,
|
|
203
|
-
email: customerEmail || undefined,
|
|
204
|
-
fulfillmentPending,
|
|
205
|
-
}));
|
|
206
|
-
const purchasedItems = checkoutItems.map((item) => ({
|
|
207
|
-
productId: item.productId,
|
|
208
|
-
variantId: item.variantId,
|
|
209
|
-
quantity: item.quantity,
|
|
210
|
-
}));
|
|
211
|
-
try {
|
|
212
|
-
if (finalizeCartAfterOrder) {
|
|
213
|
-
await finalizeCartAfterOrder(purchasedItems, { checkoutEntireCart });
|
|
214
|
-
}
|
|
215
|
-
else if (removeOrderItemsFromCart) {
|
|
216
|
-
await removeOrderItemsFromCart(purchasedItems);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
console.error('[Checkout] Failed to clear cart after order:', error);
|
|
221
|
-
}
|
|
222
|
-
if (effectiveBuyNowItem) {
|
|
223
|
-
clearBuyNowItem();
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
87
|
const [step, setStep] = useState('details');
|
|
227
88
|
const [loading, setLoading] = useState(false);
|
|
228
|
-
const
|
|
229
|
-
const lastTransactionReferenceRef = useRef(null);
|
|
230
|
-
const lastCheckoutOrderIdRef = useRef(null);
|
|
231
|
-
const checkoutIdempotencyKeyRef = useRef(null);
|
|
232
|
-
const checkoutCartFingerprintRef = useRef('');
|
|
233
|
-
const checkoutItemsFingerprint = useMemo(() => {
|
|
234
|
-
return JSON.stringify(checkoutItems.map((item) => ({
|
|
235
|
-
productId: item.productId,
|
|
236
|
-
variantId: item.variantId,
|
|
237
|
-
quantity: item.quantity,
|
|
238
|
-
price: item.price,
|
|
239
|
-
})));
|
|
240
|
-
}, [checkoutItems]);
|
|
241
|
-
useEffect(() => {
|
|
242
|
-
if (checkoutCartFingerprintRef.current && checkoutCartFingerprintRef.current !== checkoutItemsFingerprint) {
|
|
243
|
-
resetIdempotencyKey(checkoutIdempotencyKeyRef);
|
|
244
|
-
}
|
|
245
|
-
checkoutCartFingerprintRef.current = checkoutItemsFingerprint;
|
|
246
|
-
}, [checkoutItemsFingerprint]);
|
|
247
|
-
const handlePaymentCancelCleanup = useCallback(async () => {
|
|
248
|
-
const orderId = lastCheckoutOrderIdRef.current;
|
|
249
|
-
if (orderId) {
|
|
250
|
-
try {
|
|
251
|
-
await orderService.abandonPaymentAttempt(orderId);
|
|
252
|
-
}
|
|
253
|
-
catch (error) {
|
|
254
|
-
console.error('[Checkout] Failed to abandon payment attempt:', error);
|
|
255
|
-
}
|
|
256
|
-
lastCheckoutOrderIdRef.current = null;
|
|
257
|
-
}
|
|
258
|
-
resetIdempotencyKey(checkoutIdempotencyKeyRef);
|
|
259
|
-
lastMerchantReferenceRef.current = null;
|
|
260
|
-
lastTransactionReferenceRef.current = null;
|
|
261
|
-
setInventoryHoldUntil?.(null);
|
|
262
|
-
try {
|
|
263
|
-
await refreshCart?.();
|
|
264
|
-
}
|
|
265
|
-
catch (error) {
|
|
266
|
-
console.error('[Checkout] Failed to refresh cart after payment cancel:', error);
|
|
267
|
-
}
|
|
268
|
-
}, [refreshCart, setInventoryHoldUntil]);
|
|
269
|
-
const buildPaymentCallbackUrl = (transactionRef) => {
|
|
270
|
-
const params = new URLSearchParams({
|
|
271
|
-
reference: transactionRef,
|
|
272
|
-
storeId: storeConfig.id,
|
|
273
|
-
});
|
|
274
|
-
const merchantRef = lastMerchantReferenceRef.current;
|
|
275
|
-
if (merchantRef) {
|
|
276
|
-
params.set('merchantReference', merchantRef);
|
|
277
|
-
params.set('orderNumber', merchantRef);
|
|
278
|
-
}
|
|
279
|
-
return `/${storeConfig.slug}/payment/callback?${params.toString()}`;
|
|
280
|
-
};
|
|
281
|
-
const enrichPayazaCallbackData = (callbackData) => {
|
|
282
|
-
const merchantRef = lastMerchantReferenceRef.current || undefined;
|
|
283
|
-
const internalTrx = lastTransactionReferenceRef.current || undefined;
|
|
284
|
-
return {
|
|
285
|
-
...callbackData,
|
|
286
|
-
type: 'success',
|
|
287
|
-
status: callbackData.status || 201,
|
|
288
|
-
data: {
|
|
289
|
-
...callbackData.data,
|
|
290
|
-
merchant_reference: callbackData.data?.merchant_reference || merchantRef,
|
|
291
|
-
additional_details: {
|
|
292
|
-
...(callbackData.data?.additional_details || {}),
|
|
293
|
-
storeId: storeConfig.id,
|
|
294
|
-
merchantReference: merchantRef,
|
|
295
|
-
orderNumber: merchantRef,
|
|
296
|
-
internalTransactionReference: internalTrx,
|
|
297
|
-
},
|
|
298
|
-
},
|
|
299
|
-
};
|
|
300
|
-
};
|
|
301
|
-
const resolvePostPaymentRefs = (response) => {
|
|
302
|
-
const merchantRef = lastMerchantReferenceRef.current || undefined;
|
|
303
|
-
const internalTrx = lastTransactionReferenceRef.current || undefined;
|
|
304
|
-
const payazaRef = response.callbackData?.data?.payaza_reference ||
|
|
305
|
-
(response.callbackData?.data?.transaction_reference?.startsWith('P-C-')
|
|
306
|
-
? response.callbackData.data.transaction_reference
|
|
307
|
-
: undefined);
|
|
308
|
-
const callbackTrx = response.callbackData?.data?.transaction_reference;
|
|
309
|
-
const transactionRef = (callbackTrx?.startsWith('TRX-') ? callbackTrx : undefined) ||
|
|
310
|
-
(response.transactionRef?.startsWith('TRX-') ? response.transactionRef : undefined) ||
|
|
311
|
-
internalTrx ||
|
|
312
|
-
response.transactionRef ||
|
|
313
|
-
callbackTrx ||
|
|
314
|
-
payazaRef ||
|
|
315
|
-
'';
|
|
316
|
-
return { transactionRef, payazaRef, merchantRef };
|
|
317
|
-
};
|
|
318
|
-
const tryConfirmFromReference = async (transactionRef, merchantRef) => {
|
|
319
|
-
return paymentService.confirmPaymentFromReference(transactionRef, storeConfig.id, merchantRef);
|
|
320
|
-
};
|
|
89
|
+
const [isLoaded, setIsLoaded] = useState(() => typeof window !== 'undefined');
|
|
321
90
|
// Address management state
|
|
322
91
|
const [savedAddresses, setSavedAddresses] = useState([]);
|
|
323
92
|
const [selectedAddressId, setSelectedAddressId] = useState(null);
|
|
@@ -331,148 +100,121 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
331
100
|
const [shippingError, setShippingError] = useState(null);
|
|
332
101
|
const [freeShippingEligible, setFreeShippingEligible] = useState(false);
|
|
333
102
|
const [freeShippingThreshold, setFreeShippingThreshold] = useState(undefined);
|
|
334
|
-
const [
|
|
335
|
-
const [
|
|
336
|
-
const [selectedCourier, setSelectedCourier] = useState(null);
|
|
337
|
-
// Payaza Configuration
|
|
338
|
-
// Fallback key only — the authoritative key is resolved per-currency by the
|
|
339
|
-
// backend from the database-managed registry and returned by /checkout/process
|
|
340
|
-
// as `payazaPublicKey`. Priority: backend-resolved key → storeConfig → env.
|
|
341
|
-
const payazaPublicKey = storeConfig.payment?.payazaPublicKey || process.env.NEXT_PUBLIC_PAYAZA_PUBLIC_KEY || '';
|
|
103
|
+
const [feePreview, setFeePreview] = useState(null);
|
|
104
|
+
const [isCalculatingFees, setIsCalculatingFees] = useState(false);
|
|
342
105
|
const { checkout: initiateCheckout, isLoading: isPayazaLoading } = usePayazaCheckout({
|
|
343
|
-
publicKey: payazaPublicKey,
|
|
344
106
|
onSuccess: async (response) => {
|
|
345
107
|
try {
|
|
346
108
|
setLoading(true);
|
|
347
|
-
const
|
|
109
|
+
const transactionRef = response.transactionRef || response.callbackData?.data?.transaction_reference;
|
|
348
110
|
if (!transactionRef) {
|
|
349
111
|
addToast('Payment successful but no transaction reference. Please contact support.', 'info');
|
|
350
112
|
setLoading(false);
|
|
351
113
|
return;
|
|
352
114
|
}
|
|
353
|
-
console.log('[Checkout] SDK callback received, verifying payment...'
|
|
354
|
-
transactionRef,
|
|
355
|
-
payazaRef,
|
|
356
|
-
merchantRef,
|
|
357
|
-
});
|
|
115
|
+
console.log('[Checkout] SDK callback received, verifying payment...');
|
|
358
116
|
addToast('Verifying payment...', 'info');
|
|
359
|
-
// Step 1: Immediate check if
|
|
117
|
+
// Step 1: Immediate check if webhook already processed payment (no delay)
|
|
360
118
|
let immediateVerification;
|
|
361
119
|
try {
|
|
362
|
-
immediateVerification = await paymentService.verifyPayment(transactionRef, storeConfig.id
|
|
363
|
-
if (immediateVerification.verified &&
|
|
364
|
-
console.log('[Checkout] Payment already completed, proceeding to success page');
|
|
365
|
-
|
|
120
|
+
immediateVerification = await paymentService.verifyPayment(transactionRef, storeConfig.id);
|
|
121
|
+
if (immediateVerification.verified && immediateVerification.paymentStatus === 'completed') {
|
|
122
|
+
console.log('[Checkout] Payment already completed by webhook, proceeding to success page');
|
|
123
|
+
const orderId = immediateVerification.orderId || transactionRef;
|
|
124
|
+
const orderNumber = immediateVerification.orderNumber || '';
|
|
125
|
+
router.push(`/${storeConfig.slug}/order/success?orderId=${orderId}&ref=${transactionRef}${orderNumber ? `&orderNumber=${orderNumber}` : ''}`);
|
|
366
126
|
return;
|
|
367
127
|
}
|
|
368
128
|
}
|
|
369
129
|
catch (error) {
|
|
370
130
|
console.log('[Checkout] Immediate verification failed, proceeding with confirmation');
|
|
371
131
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
};
|
|
375
|
-
let inventoryConfirmationError = null;
|
|
376
|
-
const recordConfirmationError = (error) => {
|
|
377
|
-
if (isPaymentInventoryError(error)) {
|
|
378
|
-
inventoryConfirmationError = error;
|
|
379
|
-
}
|
|
380
|
-
};
|
|
381
|
-
// Step 2: Confirm from enriched Payaza callback payload
|
|
132
|
+
// Step 2: If webhook hasn't processed yet, immediately confirm from callback data
|
|
133
|
+
// We have the callback data from Payaza, so we can confirm immediately instead of waiting
|
|
382
134
|
if (response.callbackData) {
|
|
383
135
|
console.log('[Checkout] Processing Payaza callback confirmation immediately');
|
|
384
|
-
|
|
136
|
+
// Retry logic for payment confirmation
|
|
385
137
|
const MAX_RETRIES = 3;
|
|
386
|
-
const RETRY_DELAY = 1000;
|
|
138
|
+
const RETRY_DELAY = 1000; // 1 second
|
|
387
139
|
let confirmation;
|
|
140
|
+
let lastError;
|
|
388
141
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
389
142
|
try {
|
|
390
|
-
|
|
143
|
+
// Confirm payment with backend and trigger order processing
|
|
144
|
+
confirmation = await paymentService.confirmPaymentFromCallback(response.callbackData);
|
|
145
|
+
if (confirmation.alreadyProcessed) {
|
|
146
|
+
console.log('[Checkout] Payment already processed, redirecting to success page');
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.log('[Checkout] Payment confirmed, order processing initiated');
|
|
150
|
+
}
|
|
151
|
+
// Success - break out of retry loop
|
|
391
152
|
break;
|
|
392
153
|
}
|
|
393
154
|
catch (error) {
|
|
155
|
+
lastError = error;
|
|
394
156
|
console.error(`[Checkout] Payment confirmation attempt ${attempt} failed:`, error);
|
|
395
|
-
|
|
396
|
-
if (inventoryConfirmationError)
|
|
397
|
-
break;
|
|
157
|
+
// If this is not the last attempt, wait before retrying
|
|
398
158
|
if (attempt < MAX_RETRIES) {
|
|
399
|
-
|
|
159
|
+
console.log(`[Checkout] Retrying payment confirmation in ${RETRY_DELAY}ms...`);
|
|
160
|
+
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * attempt)); // Exponential backoff
|
|
400
161
|
}
|
|
401
162
|
}
|
|
402
163
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
await confirmAndRedirect(confirmation);
|
|
422
|
-
return;
|
|
164
|
+
// If all retries failed, do a short poll to catch webhook processing (max 3 seconds)
|
|
165
|
+
if (!confirmation) {
|
|
166
|
+
console.log('[Checkout] Confirmation failed, doing short poll for webhook processing...');
|
|
167
|
+
try {
|
|
168
|
+
const polledVerification = await paymentService.pollPaymentStatus(transactionRef, storeConfig.id, {
|
|
169
|
+
maxAttempts: 3, // Poll for max 3 seconds (3 * 1s)
|
|
170
|
+
intervalMs: 1000, // Poll every 1 second
|
|
171
|
+
onProgress: (attempt, maxAttempts) => {
|
|
172
|
+
console.log(`[Checkout] Polling for webhook (${attempt}/${maxAttempts})...`);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
if (polledVerification && polledVerification.verified && polledVerification.paymentStatus === 'completed') {
|
|
176
|
+
console.log('[Checkout] Payment completed by webhook, proceeding to success page');
|
|
177
|
+
const orderId = polledVerification.orderId || transactionRef;
|
|
178
|
+
const orderNumber = polledVerification.orderNumber || '';
|
|
179
|
+
router.push(`/${storeConfig.slug}/order/success?orderId=${orderId}&ref=${transactionRef}${orderNumber ? `&orderNumber=${orderNumber}` : ''}`);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
423
182
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
console.error(`[Checkout] confirm-from-reference failed for ${ref}:`, error);
|
|
427
|
-
recordConfirmationError(error);
|
|
428
|
-
if (inventoryConfirmationError) {
|
|
429
|
-
redirectToPaymentError('insufficient_stock', transactionRef, inventoryConfirmationError instanceof ApiError
|
|
430
|
-
? inventoryConfirmationError.message
|
|
431
|
-
: undefined);
|
|
432
|
-
return;
|
|
183
|
+
catch (pollError) {
|
|
184
|
+
console.error('[Checkout] Polling also failed:', pollError);
|
|
433
185
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
try {
|
|
439
|
-
const polledVerification = await paymentService.pollPaymentStatus(transactionRef, storeConfig.id, {
|
|
440
|
-
maxAttempts: 10,
|
|
441
|
-
intervalMs: 1500,
|
|
442
|
-
merchantReference: merchantRef,
|
|
443
|
-
onProgress: (attempt, maxAttempts) => {
|
|
444
|
-
console.log(`[Checkout] Polling payment status (${attempt}/${maxAttempts})...`);
|
|
445
|
-
},
|
|
446
|
-
});
|
|
447
|
-
if (polledVerification?.verified && isPaymentConfirmed(polledVerification.paymentStatus)) {
|
|
448
|
-
await completeOrderAndRedirect(polledVerification.orderId || transactionRef, transactionRef, polledVerification.orderNumber || undefined, formData.email || undefined);
|
|
186
|
+
// Final fallback: Redirect to callback page for manual verification
|
|
187
|
+
console.log('[Checkout] Redirecting to callback page for verification');
|
|
188
|
+
addToast('Payment successful. Verifying transaction...', 'info');
|
|
189
|
+
router.push(`/${storeConfig.slug}/payment/callback?reference=${transactionRef}&storeId=${storeConfig.id}`);
|
|
449
190
|
return;
|
|
450
191
|
}
|
|
192
|
+
// Success - redirect to success page
|
|
193
|
+
const orderId = confirmation.orderId;
|
|
194
|
+
const orderNumber = confirmation.orderNumber || '';
|
|
195
|
+
// Redirect to success page
|
|
196
|
+
router.push(`/${storeConfig.slug}/order/success?orderId=${orderId}&ref=${transactionRef}${orderNumber ? `&orderNumber=${orderNumber}` : ''}`);
|
|
451
197
|
}
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
198
|
+
else {
|
|
199
|
+
// Fallback: Use transaction reference to verify payment
|
|
200
|
+
console.log('[Checkout] No callback data, verifying payment with transaction reference:', response.transactionRef);
|
|
201
|
+
if (response.transactionRef) {
|
|
202
|
+
// Redirect to callback page for verification and confirmation
|
|
203
|
+
router.push(`/${storeConfig.slug}/payment/callback?reference=${response.transactionRef}&storeId=${storeConfig.id}`);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
addToast('Payment successful but unable to process. Please contact support.', 'info');
|
|
207
|
+
setLoading(false);
|
|
208
|
+
}
|
|
461
209
|
}
|
|
462
|
-
console.log('[Checkout] Redirecting to callback page for verification');
|
|
463
|
-
addToast('Payment successful. Verifying transaction...', 'info');
|
|
464
|
-
redirectToPaymentCallback(transactionRef);
|
|
465
210
|
}
|
|
466
211
|
catch (error) {
|
|
467
212
|
console.error('[Checkout] Error processing payment callback:', error);
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
redirectToPaymentError('insufficient_stock', transactionRef, error instanceof ApiError ? error.message : undefined);
|
|
471
|
-
return;
|
|
472
|
-
}
|
|
213
|
+
// Fallback: Try to redirect to callback page for verification
|
|
214
|
+
const transactionRef = response.transactionRef || response.callbackData?.data?.transaction_reference;
|
|
473
215
|
if (transactionRef) {
|
|
474
216
|
addToast('Payment successful. Verifying transaction...', 'info');
|
|
475
|
-
|
|
217
|
+
router.push(`/${storeConfig.slug}/payment/callback?reference=${transactionRef}&storeId=${storeConfig.id}`);
|
|
476
218
|
}
|
|
477
219
|
else {
|
|
478
220
|
addToast('Payment successful but verification failed. Please contact support with your transaction details.', 'error');
|
|
@@ -480,92 +222,78 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
480
222
|
}
|
|
481
223
|
}
|
|
482
224
|
},
|
|
483
|
-
onError:
|
|
484
|
-
const message = String(error || '');
|
|
485
|
-
const shouldResetCheckoutState = /reference already/i.test(message) ||
|
|
486
|
-
/reservation expired/i.test(message) ||
|
|
487
|
-
/already being processed/i.test(message);
|
|
488
|
-
if (shouldResetCheckoutState) {
|
|
489
|
-
await handlePaymentCancelCleanup();
|
|
490
|
-
}
|
|
225
|
+
onError: (error) => {
|
|
491
226
|
addToast(`Checkout error: ${error}`, 'error');
|
|
492
227
|
setLoading(false);
|
|
493
228
|
},
|
|
494
|
-
onClose:
|
|
495
|
-
|
|
229
|
+
onClose: () => {
|
|
230
|
+
// onClose is only called when payment was not successful (user canceled)
|
|
231
|
+
// This is now handled correctly by payaza-checkout.ts tracking payment success state
|
|
496
232
|
addToast('Payment canceled', 'info');
|
|
497
233
|
setLoading(false);
|
|
498
234
|
},
|
|
499
235
|
});
|
|
236
|
+
useEffect(() => {
|
|
237
|
+
setIsLoaded(true);
|
|
238
|
+
}, []);
|
|
500
239
|
// Track checkout_start event when checkout page loads with items
|
|
501
240
|
useEffect(() => {
|
|
502
|
-
if (!
|
|
503
|
-
|
|
241
|
+
if (isLoaded && !isInitializing && storeConfig?.id && (checkoutItems.length > 0 || effectiveBuyNowItem)) {
|
|
242
|
+
const totalValue = checkoutTotal;
|
|
243
|
+
const itemCount = checkoutItems.length;
|
|
244
|
+
trackEvent({
|
|
245
|
+
eventType: 'checkout_start',
|
|
246
|
+
metadata: {
|
|
247
|
+
totalValue,
|
|
248
|
+
itemCount,
|
|
249
|
+
items: checkoutItems.map(item => ({
|
|
250
|
+
productId: item.productId,
|
|
251
|
+
quantity: item.quantity,
|
|
252
|
+
price: item.price,
|
|
253
|
+
})),
|
|
254
|
+
},
|
|
255
|
+
}).catch(err => console.warn('Failed to track checkout_start:', err));
|
|
504
256
|
}
|
|
505
|
-
|
|
506
|
-
const itemCount = checkoutItems.length;
|
|
507
|
-
trackEvent({
|
|
508
|
-
eventType: 'checkout_start',
|
|
509
|
-
metadata: {
|
|
510
|
-
totalValue,
|
|
511
|
-
itemCount,
|
|
512
|
-
items: checkoutItems.map(item => ({
|
|
513
|
-
productId: item.productId,
|
|
514
|
-
quantity: item.quantity,
|
|
515
|
-
price: item.price,
|
|
516
|
-
})),
|
|
517
|
-
},
|
|
518
|
-
}).catch(err => console.warn('Failed to track checkout_start:', err));
|
|
519
|
-
trackStorefrontEvent('Checkout_started', {
|
|
520
|
-
storeId: storeConfig.id,
|
|
521
|
-
storeSlug: storeConfig.slug,
|
|
522
|
-
totalValue,
|
|
523
|
-
itemCount,
|
|
524
|
-
items: checkoutItems.map(item => ({
|
|
525
|
-
productId: item.productId,
|
|
526
|
-
quantity: item.quantity,
|
|
527
|
-
price: item.price,
|
|
528
|
-
})),
|
|
529
|
-
});
|
|
530
|
-
}, [isCartHydrated, storeConfig?.id, checkoutItems, effectiveBuyNowItem, checkoutTotal, trackEvent]);
|
|
257
|
+
}, [isLoaded, isInitializing, storeConfig?.id, checkoutItems, effectiveBuyNowItem, checkoutTotal, trackEvent]);
|
|
531
258
|
// Redirect to home if checkout items are empty (with delay to allow state propagation)
|
|
532
259
|
useEffect(() => {
|
|
533
|
-
if (!
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
260
|
+
if (!isInitializing && isLoaded) {
|
|
261
|
+
// Add a delay to ensure buyNowItem state has propagated
|
|
262
|
+
const redirectTimer = setTimeout(() => {
|
|
263
|
+
// Check again if we have buyNowItem in sessionStorage (even if not in context yet)
|
|
264
|
+
const hasBuyNowInStorage = typeof window !== 'undefined' && sessionStorage.getItem('buyNowItem');
|
|
265
|
+
// Don't redirect if we have buyNowItem in sessionStorage or if checkoutItems exist
|
|
266
|
+
if (hasBuyNowInStorage) {
|
|
267
|
+
// If we have it in storage but not in state, try to restore it one more time
|
|
268
|
+
if (!effectiveBuyNowItem) {
|
|
269
|
+
try {
|
|
270
|
+
const stored = sessionStorage.getItem('buyNowItem');
|
|
271
|
+
if (stored) {
|
|
272
|
+
const parsedItem = JSON.parse(stored);
|
|
273
|
+
setBuyNowItem(parsedItem);
|
|
274
|
+
return; // Don't redirect, we're restoring the item
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch (error) {
|
|
278
|
+
console.error('Failed to restore buyNowItem:', error);
|
|
549
279
|
}
|
|
550
280
|
}
|
|
551
|
-
|
|
552
|
-
|
|
281
|
+
else {
|
|
282
|
+
return; // We have the item, don't redirect
|
|
553
283
|
}
|
|
554
284
|
}
|
|
555
|
-
|
|
556
|
-
|
|
285
|
+
// Only redirect if we truly have no items and no buyNowItem in storage
|
|
286
|
+
if (!checkoutItems || checkoutItems.length === 0) {
|
|
287
|
+
router.push(`/${storeConfig.slug}`);
|
|
557
288
|
}
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
return () => clearTimeout(redirectTimer);
|
|
564
|
-
}, [checkoutItems, isCartHydrated, router, storeConfig.slug, effectiveBuyNowItem, setBuyNowItem]);
|
|
565
|
-
// Clear buyNowItem when component unmounts or user navigates away (not during order completion)
|
|
289
|
+
}, 400);
|
|
290
|
+
return () => clearTimeout(redirectTimer);
|
|
291
|
+
}
|
|
292
|
+
}, [checkoutItems, isLoaded, isInitializing, router, storeConfig.slug, effectiveBuyNowItem, setBuyNowItem]);
|
|
293
|
+
// Clear buyNowItem when component unmounts or user navigates away
|
|
566
294
|
useEffect(() => {
|
|
567
295
|
return () => {
|
|
568
|
-
if (effectiveBuyNowItem
|
|
296
|
+
if (effectiveBuyNowItem) {
|
|
569
297
|
clearBuyNowItem();
|
|
570
298
|
}
|
|
571
299
|
};
|
|
@@ -593,7 +321,6 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
593
321
|
phone: user.phone || prev.phone,
|
|
594
322
|
address: user.address || prev.address,
|
|
595
323
|
city: user.city || prev.city,
|
|
596
|
-
state: deriveNigerianStateFromCity(user.city || prev.city) || prev.state || '',
|
|
597
324
|
country: user.country || prev.country || 'Nigeria',
|
|
598
325
|
postalCode: user.zipCode || prev.postalCode,
|
|
599
326
|
}));
|
|
@@ -612,7 +339,6 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
612
339
|
phone: user.phone || prev.phone,
|
|
613
340
|
address: user.address || prev.address,
|
|
614
341
|
city: user.city || prev.city,
|
|
615
|
-
state: deriveNigerianStateFromCity(user.city || prev.city) || prev.state || '',
|
|
616
342
|
country: user.country || prev.country || 'Nigeria',
|
|
617
343
|
postalCode: user.zipCode || prev.postalCode,
|
|
618
344
|
}));
|
|
@@ -637,26 +363,15 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
637
363
|
phone: user.phone || prev.phone,
|
|
638
364
|
address: user.address || prev.address,
|
|
639
365
|
city: user.city || prev.city,
|
|
640
|
-
state: deriveNigerianStateFromCity(user.city || prev.city) || prev.state || '',
|
|
641
366
|
country: user.country || prev.country || 'Nigeria',
|
|
642
367
|
postalCode: user.zipCode || prev.postalCode,
|
|
643
368
|
}));
|
|
644
369
|
}
|
|
645
370
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
646
371
|
}, [isAuthenticated, user, selectedAddressId, savedAddresses.length, loadingAddresses]);
|
|
647
|
-
// Calculate
|
|
372
|
+
// Calculate shipping when address is filled
|
|
648
373
|
const calculateShipping = async () => {
|
|
649
|
-
|
|
650
|
-
street: formData.address,
|
|
651
|
-
address1: formData.address,
|
|
652
|
-
city: formData.city,
|
|
653
|
-
state: formData.state || formData.city,
|
|
654
|
-
zipCode: formData.postalCode,
|
|
655
|
-
country: formData.country,
|
|
656
|
-
phone: formData.phone,
|
|
657
|
-
email: formData.email,
|
|
658
|
-
});
|
|
659
|
-
if (!canRequestShippingQuote(quoteAddress) || checkoutItems.length === 0) {
|
|
374
|
+
if (!formData.city || !formData.country || checkoutItems.length === 0) {
|
|
660
375
|
setShippingMethods([]);
|
|
661
376
|
setSelectedShippingMethod(null);
|
|
662
377
|
return;
|
|
@@ -664,22 +379,20 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
664
379
|
setIsCalculatingShipping(true);
|
|
665
380
|
setShippingError(null);
|
|
666
381
|
try {
|
|
667
|
-
const result = await shippingService.calculateShipping(
|
|
382
|
+
const result = await shippingService.calculateShipping({
|
|
668
383
|
storeId: storeConfig.id,
|
|
669
|
-
address:
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
phone: formData.phone,
|
|
674
|
-
email: formData.email,
|
|
384
|
+
address: {
|
|
385
|
+
country: formData.country,
|
|
386
|
+
city: formData.city,
|
|
387
|
+
zipCode: formData.postalCode,
|
|
675
388
|
},
|
|
676
|
-
items: checkoutItems.map(
|
|
389
|
+
items: checkoutItems.map(item => ({
|
|
677
390
|
productId: item.productId,
|
|
678
391
|
quantity: item.quantity,
|
|
679
392
|
})),
|
|
680
|
-
subtotal,
|
|
681
|
-
currency,
|
|
682
|
-
})
|
|
393
|
+
subtotal: subtotal,
|
|
394
|
+
currency: currency,
|
|
395
|
+
});
|
|
683
396
|
// Filter out any methods with invalid costs before storing
|
|
684
397
|
const validMethods = (result.methods || []).filter(method => Number.isFinite(method.cost) && method.cost >= 0);
|
|
685
398
|
setShippingMethods(validMethods);
|
|
@@ -731,36 +444,61 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
731
444
|
selectedShippingMethod.cost >= 0)
|
|
732
445
|
? selectedShippingMethod.cost
|
|
733
446
|
: 0;
|
|
734
|
-
//
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
//
|
|
738
|
-
// backend computes fees per-store during process-marketplace).
|
|
739
|
-
const multiStoreSubtotals = useMemo(() => storeBreakdown.map((s) => ({ storeId: s.storeId, subtotal: s.subtotal })), [storeBreakdown]);
|
|
740
|
-
const singleStoreFees = useCheckoutFees({
|
|
741
|
-
storeId: isMultiStoreCheckout ? undefined : storeConfig.id,
|
|
742
|
-
subtotal,
|
|
743
|
-
currency,
|
|
744
|
-
shippingCost: shipping,
|
|
745
|
-
onError: (message) => addToast(message, 'error'),
|
|
746
|
-
});
|
|
747
|
-
const multiStoreFees = useMultiStoreCheckoutFees({
|
|
748
|
-
stores: isMultiStoreCheckout ? multiStoreSubtotals : [],
|
|
749
|
-
currency,
|
|
750
|
-
onError: (message) => addToast(message, 'error'),
|
|
751
|
-
});
|
|
752
|
-
const { feeBreakdown, loading: feesLoading, totalPayable, displayLines } = isMultiStoreCheckout
|
|
753
|
-
? multiStoreFees
|
|
754
|
-
: singleStoreFees;
|
|
447
|
+
// Local VAT is only a temporary fallback while the server-authoritative preview loads.
|
|
448
|
+
const vatAmount = calculateVAT(subtotal, storeConfig.settings?.vat, storeConfig.settings?.taxRate);
|
|
449
|
+
const vatLabel = getVATLabel(storeConfig.settings?.vat, storeConfig.settings?.taxRate);
|
|
450
|
+
// Fallback total while the server-authoritative preview loads.
|
|
755
451
|
const safeSubtotal = Number.isFinite(subtotal) && subtotal >= 0 ? subtotal : 0;
|
|
756
|
-
const
|
|
452
|
+
const safeShipping = Number.isFinite(shipping) && shipping >= 0 ? shipping : 0;
|
|
453
|
+
const safeVatAmount = Number.isFinite(vatAmount) && vatAmount >= 0 ? vatAmount : 0;
|
|
454
|
+
const isFreeOrder = safeSubtotal <= 0 && checkoutItems.length > 0;
|
|
455
|
+
const calculatedTotal = safeSubtotal + safeShipping + safeVatAmount;
|
|
456
|
+
const fallbackTotal = Math.max(safeSubtotal, calculatedTotal);
|
|
457
|
+
const total = feePreview?.totalPayable ?? fallbackTotal;
|
|
458
|
+
const customerFeeLines = (feePreview?.customerLines ?? []).filter((line) => line.key !== 'shipping');
|
|
459
|
+
useEffect(() => {
|
|
460
|
+
if (!storeConfig.id || safeSubtotal <= 0 || !currency) {
|
|
461
|
+
setFeePreview(null);
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
let cancelled = false;
|
|
465
|
+
const timer = window.setTimeout(async () => {
|
|
466
|
+
setIsCalculatingFees(true);
|
|
467
|
+
try {
|
|
468
|
+
const response = await feesService.calculateFees({
|
|
469
|
+
storeId: storeConfig.id,
|
|
470
|
+
subtotal: safeSubtotal,
|
|
471
|
+
shippingCost: safeShipping,
|
|
472
|
+
currency,
|
|
473
|
+
orderType: 'product',
|
|
474
|
+
});
|
|
475
|
+
if (!cancelled)
|
|
476
|
+
setFeePreview(response.feeBreakdown);
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
// Checkout creation recalculates fees authoritatively; preserve a usable fallback preview.
|
|
480
|
+
console.warn('[Checkout] Fee preview unavailable:', error);
|
|
481
|
+
if (!cancelled)
|
|
482
|
+
setFeePreview(null);
|
|
483
|
+
}
|
|
484
|
+
finally {
|
|
485
|
+
if (!cancelled)
|
|
486
|
+
setIsCalculatingFees(false);
|
|
487
|
+
}
|
|
488
|
+
}, 250);
|
|
489
|
+
return () => {
|
|
490
|
+
cancelled = true;
|
|
491
|
+
window.clearTimeout(timer);
|
|
492
|
+
};
|
|
493
|
+
}, [currency, safeShipping, safeSubtotal, storeConfig.id]);
|
|
757
494
|
// Debug logging in development
|
|
758
495
|
if (process.env.NODE_ENV === 'development' && safeSubtotal > 0 && total === 0) {
|
|
759
496
|
console.warn('[CheckoutPage] Total calculation issue detected:', {
|
|
760
497
|
subtotal: safeSubtotal,
|
|
761
|
-
shipping,
|
|
498
|
+
shipping: safeShipping,
|
|
499
|
+
vatAmount: safeVatAmount,
|
|
500
|
+
calculatedTotal,
|
|
762
501
|
total,
|
|
763
|
-
feeBreakdown,
|
|
764
502
|
checkoutTotal,
|
|
765
503
|
checkoutItemsCount: checkoutItems.length,
|
|
766
504
|
effectiveBuyNowItem: !!effectiveBuyNowItem,
|
|
@@ -773,7 +511,6 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
773
511
|
lastName: '',
|
|
774
512
|
address: '',
|
|
775
513
|
city: '',
|
|
776
|
-
state: '',
|
|
777
514
|
country: 'Nigeria',
|
|
778
515
|
postalCode: '',
|
|
779
516
|
phone: ''
|
|
@@ -781,81 +518,17 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
781
518
|
const [errors, setErrors] = useState({});
|
|
782
519
|
const [isCountryDropdownOpen, setIsCountryDropdownOpen] = useState(false);
|
|
783
520
|
const [isCityDropdownOpen, setIsCityDropdownOpen] = useState(false);
|
|
784
|
-
|
|
785
|
-
const [countrySearch, setCountrySearch] = useState('');
|
|
786
|
-
const [citySearch, setCitySearch] = useState('');
|
|
787
|
-
const deliveryEnabled = storeConfig.features?.delivery !== false;
|
|
788
|
-
const automatedShippingEnabled = isAutomatedShippingEnabled(storeConfig);
|
|
789
|
-
const buildQuoteAddress = () => enrichShippingQuoteAddress({
|
|
790
|
-
street: formData.address,
|
|
791
|
-
address1: formData.address,
|
|
792
|
-
city: formData.city,
|
|
793
|
-
state: formData.state || formData.city,
|
|
794
|
-
zipCode: formData.postalCode,
|
|
795
|
-
country: formData.country,
|
|
796
|
-
phone: formData.phone,
|
|
797
|
-
email: formData.email,
|
|
798
|
-
});
|
|
799
|
-
const handleCourierSelect = useCallback((selection) => {
|
|
800
|
-
setSelectedCourier(selection);
|
|
801
|
-
if (!selection) {
|
|
802
|
-
setSelectedShippingMethod(null);
|
|
803
|
-
return;
|
|
804
|
-
}
|
|
805
|
-
setSelectedShippingMethod({
|
|
806
|
-
code: selection.serviceCode,
|
|
807
|
-
name: selection.courierName,
|
|
808
|
-
cost: selection.price,
|
|
809
|
-
estimatedDays: 0,
|
|
810
|
-
providerKey: selection.providerKey,
|
|
811
|
-
deliveryMethodId: selection.serviceCode,
|
|
812
|
-
deliveryQuoteId: selection.quoteId,
|
|
813
|
-
});
|
|
814
|
-
}, []);
|
|
815
|
-
// Hybrid shipping: courier quotes when providers enabled, else legacy calculate
|
|
521
|
+
// Debounced shipping calculation when address changes
|
|
816
522
|
useEffect(() => {
|
|
817
|
-
if (!
|
|
818
|
-
setIsCourierMode(false);
|
|
819
|
-
setShippingMethods([]);
|
|
820
|
-
setSelectedShippingMethod(null);
|
|
821
|
-
setSelectedCourier(null);
|
|
523
|
+
if (!formData.city || !formData.country || checkoutItems.length === 0) {
|
|
822
524
|
return;
|
|
823
525
|
}
|
|
824
|
-
const
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
setShippingMethods([]);
|
|
828
|
-
setSelectedShippingMethod(null);
|
|
829
|
-
setSelectedCourier(null);
|
|
830
|
-
return;
|
|
831
|
-
}
|
|
832
|
-
const timer = setTimeout(async () => {
|
|
833
|
-
setCheckingDeliveryStatus(true);
|
|
834
|
-
setShippingError(null);
|
|
835
|
-
try {
|
|
836
|
-
const status = await shippingService.getDeliveryStatus(storeConfig.id);
|
|
837
|
-
const anyProvider = status.enabled && Object.values(status.providers || {}).some(Boolean);
|
|
838
|
-
setIsCourierMode(anyProvider);
|
|
839
|
-
if (anyProvider) {
|
|
840
|
-
setShippingMethods([]);
|
|
841
|
-
setSelectedShippingMethod(null);
|
|
842
|
-
setSelectedCourier(null);
|
|
843
|
-
}
|
|
844
|
-
else {
|
|
845
|
-
await calculateShipping();
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
catch {
|
|
849
|
-
setIsCourierMode(false);
|
|
850
|
-
await calculateShipping();
|
|
851
|
-
}
|
|
852
|
-
finally {
|
|
853
|
-
setCheckingDeliveryStatus(false);
|
|
854
|
-
}
|
|
855
|
-
}, 500);
|
|
526
|
+
const timer = setTimeout(() => {
|
|
527
|
+
calculateShipping();
|
|
528
|
+
}, 500); // Debounce 500ms
|
|
856
529
|
return () => clearTimeout(timer);
|
|
857
530
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
858
|
-
}, [
|
|
531
|
+
}, [formData.city, formData.country, formData.postalCode, checkoutItems.length, subtotal, currency]);
|
|
859
532
|
const validateForm = () => {
|
|
860
533
|
const newErrors = {};
|
|
861
534
|
if (!formData.email)
|
|
@@ -868,18 +541,10 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
868
541
|
newErrors.lastName = 'Last name is required';
|
|
869
542
|
if (!formData.address)
|
|
870
543
|
newErrors.address = 'Address is required';
|
|
871
|
-
if (!formData.country)
|
|
872
|
-
newErrors.country = 'Country is required';
|
|
873
544
|
if (!formData.city)
|
|
874
545
|
newErrors.city = 'City is required';
|
|
875
|
-
if (isNigeria(formData.country) && !formData.state) {
|
|
876
|
-
newErrors.state = 'State is required for Nigeria';
|
|
877
|
-
}
|
|
878
546
|
if (!formData.phone)
|
|
879
547
|
newErrors.phone = 'Phone is required';
|
|
880
|
-
else if (!validatePhone(formData.phone)) {
|
|
881
|
-
newErrors.phone = 'Please enter a valid phone number';
|
|
882
|
-
}
|
|
883
548
|
setErrors(newErrors);
|
|
884
549
|
return Object.keys(newErrors).length === 0;
|
|
885
550
|
};
|
|
@@ -891,44 +556,19 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
891
556
|
setIsPromoApplied(true);
|
|
892
557
|
addToast('Promo code applied successfully!', 'success');
|
|
893
558
|
};
|
|
894
|
-
const phonePlaceholder = getPhonePlaceholder();
|
|
895
|
-
const handlePhoneChange = (value) => {
|
|
896
|
-
setFormData({ ...formData, phone: formatPhoneInput(value) });
|
|
897
|
-
};
|
|
898
559
|
// Handle address selection
|
|
899
560
|
const handleAddressSelect = (addressId) => {
|
|
900
561
|
if (addressId === null) {
|
|
901
|
-
const profileAddress = user?.address && typeof user.address === 'object'
|
|
902
|
-
? user.address.address1 ||
|
|
903
|
-
user.address.street ||
|
|
904
|
-
''
|
|
905
|
-
: typeof user?.address === 'string'
|
|
906
|
-
? user.address
|
|
907
|
-
: '';
|
|
908
562
|
// "Add new address" selected - pre-fill with profile data if available, otherwise clear
|
|
909
563
|
setFormData(prev => ({
|
|
910
564
|
email: user?.email || prev.email,
|
|
911
565
|
firstName: user?.firstName || prev.firstName,
|
|
912
566
|
lastName: user?.lastName || prev.lastName,
|
|
913
567
|
phone: user?.phone || prev.phone,
|
|
914
|
-
address:
|
|
915
|
-
city:
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
user?.city ||
|
|
919
|
-
'',
|
|
920
|
-
state: '',
|
|
921
|
-
country: (user?.address && typeof user.address === 'object'
|
|
922
|
-
? user.address.country
|
|
923
|
-
: undefined) ||
|
|
924
|
-
user?.country ||
|
|
925
|
-
prev.country ||
|
|
926
|
-
'Nigeria',
|
|
927
|
-
postalCode: (user?.address && typeof user.address === 'object'
|
|
928
|
-
? user.address.zipCode
|
|
929
|
-
: undefined) ||
|
|
930
|
-
user?.zipCode ||
|
|
931
|
-
'',
|
|
568
|
+
address: user?.address || '',
|
|
569
|
+
city: user?.city || '',
|
|
570
|
+
country: user?.country || prev.country || 'Nigeria',
|
|
571
|
+
postalCode: user?.zipCode || '',
|
|
932
572
|
}));
|
|
933
573
|
setSelectedAddressId(null);
|
|
934
574
|
setSaveNewAddress(false);
|
|
@@ -943,7 +583,6 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
943
583
|
lastName: address.lastName,
|
|
944
584
|
address: address.address1,
|
|
945
585
|
city: address.city,
|
|
946
|
-
state: address.state || '',
|
|
947
586
|
country: address.country,
|
|
948
587
|
postalCode: address.zipCode,
|
|
949
588
|
phone: address.phone || prev.phone,
|
|
@@ -953,44 +592,11 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
953
592
|
setAddressLabel('');
|
|
954
593
|
}
|
|
955
594
|
};
|
|
956
|
-
const saveCheckoutAddressIfNeeded = async () => {
|
|
957
|
-
if (!isAuthenticated || !saveNewAddress || selectedAddressId) {
|
|
958
|
-
return true;
|
|
959
|
-
}
|
|
960
|
-
try {
|
|
961
|
-
const newAddress = await customerService.addAddress({
|
|
962
|
-
firstName: formData.firstName.trim(),
|
|
963
|
-
lastName: formData.lastName.trim(),
|
|
964
|
-
address1: formData.address.trim(),
|
|
965
|
-
address2: '',
|
|
966
|
-
city: formData.city.trim(),
|
|
967
|
-
state: formData.state?.trim() || undefined,
|
|
968
|
-
zipCode: formData.postalCode?.trim() || undefined,
|
|
969
|
-
country: formData.country.trim(),
|
|
970
|
-
phone: formData.phone?.trim() || undefined,
|
|
971
|
-
isDefault: savedAddresses.length === 0,
|
|
972
|
-
label: addressLabel.trim() || undefined,
|
|
973
|
-
}, { suppressToast: true });
|
|
974
|
-
const addresses = await customerService.getAddresses();
|
|
975
|
-
setSavedAddresses(addresses);
|
|
976
|
-
handleAddressSelect(newAddress.id);
|
|
977
|
-
addToast('Address saved successfully!', 'success');
|
|
978
|
-
return true;
|
|
979
|
-
}
|
|
980
|
-
catch (addressError) {
|
|
981
|
-
console.error('[Checkout] Failed to save address:', addressError);
|
|
982
|
-
addToast(addressError?.message || 'Unable to save address right now. You can save it later from your account.', 'error');
|
|
983
|
-
return false;
|
|
984
|
-
}
|
|
985
|
-
};
|
|
986
595
|
const validateCheckoutPreFlight = () => {
|
|
987
596
|
// Validate checkout items
|
|
988
597
|
if (checkoutItems.length === 0) {
|
|
989
598
|
return { isValid: false, error: 'No items to checkout' };
|
|
990
599
|
}
|
|
991
|
-
// Note: no local Payaza key check here — the backend resolves the key for
|
|
992
|
-
// the order currency from the database registry and returns it from
|
|
993
|
-
// /checkout/process. Missing keys are reported after order creation.
|
|
994
600
|
// Validate form data
|
|
995
601
|
if (!formData.email) {
|
|
996
602
|
return { isValid: false, error: 'Email is required' };
|
|
@@ -1013,58 +619,21 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1013
619
|
if (!formData.phone) {
|
|
1014
620
|
return { isValid: false, error: 'Phone is required' };
|
|
1015
621
|
}
|
|
1016
|
-
if (!validatePhone(formData.phone)) {
|
|
1017
|
-
return { isValid: false, error: 'Please enter a valid phone number' };
|
|
1018
|
-
}
|
|
1019
|
-
if (isNigeria(formData.country) && !formData.state) {
|
|
1020
|
-
return { isValid: false, error: 'State is required for Nigeria' };
|
|
1021
|
-
}
|
|
1022
622
|
// Validate currency availability
|
|
1023
623
|
const hasCurrency = storeConfig.settings?.currency || checkoutItems.some(item => item.product.currency);
|
|
1024
624
|
if (!hasCurrency) {
|
|
1025
625
|
console.warn('[Checkout] No currency found, will use USD fallback');
|
|
1026
626
|
}
|
|
1027
|
-
for (const item of checkoutItems) {
|
|
1028
|
-
const stockError = formatCheckoutStockError(item.product, item.quantity);
|
|
1029
|
-
if (stockError) {
|
|
1030
|
-
return {
|
|
1031
|
-
isValid: false,
|
|
1032
|
-
error: stockError,
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
627
|
return { isValid: true };
|
|
1037
628
|
};
|
|
1038
629
|
const handlePayazaCheckout = async () => {
|
|
1039
|
-
if (checkoutDisabled) {
|
|
1040
|
-
addToast(CHECKOUT_UNAVAILABLE_MESSAGE, 'error');
|
|
1041
|
-
return;
|
|
1042
|
-
}
|
|
1043
630
|
// Pre-flight validation
|
|
1044
631
|
const validation = validateCheckoutPreFlight();
|
|
1045
632
|
if (!validation.isValid) {
|
|
1046
633
|
addToast(validation.error || 'Please complete all required fields', 'error');
|
|
1047
634
|
return;
|
|
1048
635
|
}
|
|
1049
|
-
if (hasCheckoutStockIssues) {
|
|
1050
|
-
addToast(checkoutStockIssues[0]?.message || 'One or more items are no longer available.', 'error');
|
|
1051
|
-
refreshCart?.();
|
|
1052
|
-
return;
|
|
1053
|
-
}
|
|
1054
636
|
setLoading(true);
|
|
1055
|
-
try {
|
|
1056
|
-
await refreshCart?.();
|
|
1057
|
-
}
|
|
1058
|
-
catch (refreshError) {
|
|
1059
|
-
console.warn('[Checkout] Cart refresh before payment failed:', refreshError);
|
|
1060
|
-
}
|
|
1061
|
-
trackStorefrontEvent('Payment_method_selected', {
|
|
1062
|
-
storeId: storeConfig.id,
|
|
1063
|
-
storeSlug: storeConfig.slug,
|
|
1064
|
-
paymentMethod: 'payaza',
|
|
1065
|
-
totalValue: checkoutTotal,
|
|
1066
|
-
itemCount: checkoutItems.length,
|
|
1067
|
-
});
|
|
1068
637
|
// DEMO MODE BYPASS
|
|
1069
638
|
if (storeConfig.slug.startsWith('demo/')) {
|
|
1070
639
|
setTimeout(() => {
|
|
@@ -1072,7 +641,6 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1072
641
|
}, 1500);
|
|
1073
642
|
return;
|
|
1074
643
|
}
|
|
1075
|
-
await saveCheckoutAddressIfNeeded();
|
|
1076
644
|
let checkoutResponse;
|
|
1077
645
|
let checkoutConfig;
|
|
1078
646
|
try {
|
|
@@ -1082,101 +650,49 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1082
650
|
itemCount: checkoutItems.length,
|
|
1083
651
|
customerEmail: formData.email,
|
|
1084
652
|
});
|
|
1085
|
-
if (!isAuthenticated && formData.email && formData.firstName && formData.lastName && formData.phone) {
|
|
1086
|
-
saveGuestUserInfo({
|
|
1087
|
-
email: formData.email,
|
|
1088
|
-
firstName: formData.firstName,
|
|
1089
|
-
lastName: formData.lastName,
|
|
1090
|
-
phone: formData.phone,
|
|
1091
|
-
});
|
|
1092
|
-
}
|
|
1093
653
|
try {
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
address1: formData.address,
|
|
1124
|
-
city: formData.city,
|
|
1125
|
-
state: formData.state || formData.city || formData.country,
|
|
1126
|
-
zipCode: formData.postalCode,
|
|
1127
|
-
country: formData.country,
|
|
1128
|
-
phone: formData.phone,
|
|
1129
|
-
},
|
|
1130
|
-
paymentMethod: 'payaza',
|
|
1131
|
-
customerId: isAuthenticated && user ? user.id : undefined,
|
|
1132
|
-
}, {
|
|
1133
|
-
idempotencyKey: getOrCreateIdempotencyKey(checkoutIdempotencyKeyRef),
|
|
1134
|
-
});
|
|
1135
|
-
}
|
|
1136
|
-
else {
|
|
1137
|
-
checkoutResponse = await checkoutService.processCheckout({
|
|
1138
|
-
storeId: storeConfig.id,
|
|
1139
|
-
sessionId: typeof window !== 'undefined' ? sessionStorage.getItem('sessionId') || undefined : undefined,
|
|
1140
|
-
items: checkoutItems.map((item) => normalizeCheckoutPayloadItem(item)),
|
|
1141
|
-
customerInfo: {
|
|
1142
|
-
email: formData.email,
|
|
1143
|
-
firstName: formData.firstName,
|
|
1144
|
-
lastName: formData.lastName,
|
|
1145
|
-
phone: formData.phone,
|
|
1146
|
-
},
|
|
1147
|
-
shippingAddress: {
|
|
1148
|
-
firstName: formData.firstName,
|
|
1149
|
-
lastName: formData.lastName,
|
|
1150
|
-
address1: formData.address,
|
|
1151
|
-
city: formData.city,
|
|
1152
|
-
state: formData.state || formData.city || formData.country,
|
|
1153
|
-
zipCode: formData.postalCode,
|
|
1154
|
-
country: formData.country,
|
|
1155
|
-
phone: formData.phone,
|
|
1156
|
-
},
|
|
1157
|
-
shippingMethod: deliveryEnabled ? selectedShippingMethod?.code : undefined,
|
|
1158
|
-
shippingCost: deliveryEnabled ? shipping : 0,
|
|
1159
|
-
paymentMethod: 'payaza',
|
|
1160
|
-
deliveryProviderKey: selectedShippingMethod?.providerKey,
|
|
1161
|
-
deliveryMethodId: selectedShippingMethod?.deliveryMethodId,
|
|
1162
|
-
deliveryQuoteId: selectedShippingMethod?.deliveryQuoteId,
|
|
1163
|
-
customerId: isAuthenticated && user ? user.id : undefined, // Pass customerId for authenticated users
|
|
1164
|
-
}, {
|
|
1165
|
-
idempotencyKey: getOrCreateIdempotencyKey(checkoutIdempotencyKeyRef),
|
|
1166
|
-
});
|
|
1167
|
-
}
|
|
654
|
+
checkoutResponse = await checkoutService.processCheckout({
|
|
655
|
+
storeId: storeConfig.id,
|
|
656
|
+
items: checkoutItems.map(item => ({
|
|
657
|
+
productId: item.productId,
|
|
658
|
+
quantity: item.quantity,
|
|
659
|
+
price: item.price,
|
|
660
|
+
variantId: item.variantId,
|
|
661
|
+
})),
|
|
662
|
+
customerInfo: {
|
|
663
|
+
email: formData.email,
|
|
664
|
+
firstName: formData.firstName,
|
|
665
|
+
lastName: formData.lastName,
|
|
666
|
+
phone: formData.phone,
|
|
667
|
+
},
|
|
668
|
+
shippingAddress: {
|
|
669
|
+
firstName: formData.firstName,
|
|
670
|
+
lastName: formData.lastName,
|
|
671
|
+
address1: formData.address,
|
|
672
|
+
city: formData.city,
|
|
673
|
+
state: formData.city || formData.country, // Use city as state fallback (form doesn't collect state field)
|
|
674
|
+
zipCode: formData.postalCode,
|
|
675
|
+
country: formData.country,
|
|
676
|
+
phone: formData.phone,
|
|
677
|
+
},
|
|
678
|
+
shippingMethod: selectedShippingMethod?.code,
|
|
679
|
+
shippingCost: shipping, // Use validated shipping value
|
|
680
|
+
paymentMethod: 'payaza',
|
|
681
|
+
customerId: isAuthenticated && user ? user.id : undefined, // Pass customerId for authenticated users
|
|
682
|
+
});
|
|
1168
683
|
console.log('[Checkout] Step 1: Order created successfully', {
|
|
1169
684
|
orderId: checkoutResponse.orderId,
|
|
1170
685
|
orderNumber: checkoutResponse.orderNumber,
|
|
1171
686
|
currency: checkoutResponse.currency,
|
|
1172
687
|
transactionReference: checkoutResponse.transactionReference,
|
|
1173
688
|
});
|
|
1174
|
-
|
|
1175
|
-
checkoutResponse.
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
689
|
+
const isFreeOrderResponse = checkoutResponse.paymentNotRequired === true ||
|
|
690
|
+
(checkoutResponse.totalAmount != null && Number(checkoutResponse.totalAmount) <= 0);
|
|
691
|
+
if (isFreeOrderResponse) {
|
|
692
|
+
addToast('Your free order is complete!', 'success');
|
|
693
|
+
router.push(`/${storeConfig.slug}/order/success?orderId=${checkoutResponse.orderId}&ref=${encodeURIComponent(checkoutResponse.transactionReference || 'FREE')}${checkoutResponse.orderNumber ? `&orderNumber=${encodeURIComponent(checkoutResponse.orderNumber)}` : ''}`);
|
|
694
|
+
setLoading(false);
|
|
695
|
+
return;
|
|
1180
696
|
}
|
|
1181
697
|
}
|
|
1182
698
|
catch (apiError) {
|
|
@@ -1190,43 +706,28 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1190
706
|
itemCount: checkoutItems.length,
|
|
1191
707
|
},
|
|
1192
708
|
});
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
apiError?.code === 'ECONNABORTED' ||
|
|
1196
|
-
apiError?.message === 'Network Error' ||
|
|
1197
|
-
apiError?.message?.includes('timeout');
|
|
1198
|
-
if (isNetworkError) {
|
|
1199
|
-
addToast(getUserFriendlyErrorMessage(apiError, 'Unable to reach the server. Please check your connection and try again.'), 'error');
|
|
709
|
+
if (apiError?.status === 0 || apiError?.code === 'ECONNABORTED' || apiError?.message?.includes('timeout')) {
|
|
710
|
+
addToast('Unable to connect to server. Please check your internet connection.', 'error');
|
|
1200
711
|
}
|
|
1201
712
|
else if (apiError?.status === 500) {
|
|
1202
|
-
addToast(
|
|
1203
|
-
}
|
|
1204
|
-
else if (apiError?.status === 400 || apiError?.status === 422) {
|
|
1205
|
-
addToast(getUserFriendlyErrorMessage(apiError, 'Invalid checkout data. Please review your shipping information.'), 'error');
|
|
713
|
+
addToast('Server error occurred. Please try again later.', 'error');
|
|
1206
714
|
}
|
|
1207
|
-
else if (apiError?.status ===
|
|
1208
|
-
addToast(
|
|
1209
|
-
refreshCart?.();
|
|
715
|
+
else if (apiError?.status === 400) {
|
|
716
|
+
addToast(apiError?.message || 'Invalid checkout data. Please review your information.', 'error');
|
|
1210
717
|
}
|
|
1211
718
|
else {
|
|
1212
|
-
addToast(
|
|
719
|
+
addToast(apiError?.message || 'Failed to create order. Please try again.', 'error');
|
|
1213
720
|
}
|
|
1214
721
|
setLoading(false);
|
|
1215
722
|
return;
|
|
1216
723
|
}
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
console.error('[Checkout] No Payaza public key available', {
|
|
1225
|
-
fromBackend: !!checkoutResponse.payazaPublicKey,
|
|
1226
|
-
fromStoreConfig: !!storeConfig.payment?.payazaPublicKey,
|
|
1227
|
-
fromEnv: !!process.env.NEXT_PUBLIC_PAYAZA_PUBLIC_KEY,
|
|
1228
|
-
});
|
|
1229
|
-
addToast('Payment gateway not configured. Please contact support.', 'error');
|
|
724
|
+
let effectivePayazaPublicKey;
|
|
725
|
+
try {
|
|
726
|
+
effectivePayazaPublicKey = requireCheckoutPayazaPublicKey(checkoutResponse);
|
|
727
|
+
}
|
|
728
|
+
catch (error) {
|
|
729
|
+
console.error('[Checkout] No Payaza public key from backend', error);
|
|
730
|
+
addToast(error instanceof Error ? error.message : 'Payment gateway not configured. Please contact support.', 'error');
|
|
1230
731
|
setLoading(false);
|
|
1231
732
|
return;
|
|
1232
733
|
}
|
|
@@ -1249,28 +750,24 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1249
750
|
orderId: checkoutResponse.orderId,
|
|
1250
751
|
orderNumber: checkoutResponse.orderNumber,
|
|
1251
752
|
});
|
|
1252
|
-
const orderMerchantRef = checkoutResponse.merchantReference || checkoutResponse.orderNumber || '';
|
|
1253
753
|
checkoutConfig = generateCheckoutConfig(payazaItems, formData.email, effectivePayazaPublicKey, {
|
|
1254
754
|
firstName: formData.firstName,
|
|
1255
755
|
lastName: formData.lastName,
|
|
1256
|
-
phone:
|
|
1257
|
-
callbackUrl: `${typeof window !== 'undefined' ? window.location.origin : ''}/${storeConfig.slug}/payment/callback?storeId=${storeConfig.id}
|
|
756
|
+
phone: formData.phone,
|
|
757
|
+
callbackUrl: `${typeof window !== 'undefined' ? window.location.origin : ''}/${storeConfig.slug}/payment/callback?storeId=${storeConfig.id}`,
|
|
1258
758
|
currency: checkoutResponse.currency || storeConfig.settings?.currency,
|
|
759
|
+
totalAmount: checkoutResponse.totalAmount,
|
|
760
|
+
merchantReference: checkoutResponse.merchantReference,
|
|
1259
761
|
storeId: storeConfig.id,
|
|
1260
762
|
orderId: checkoutResponse.orderId,
|
|
1261
763
|
orderNumber: checkoutResponse.orderNumber,
|
|
1262
|
-
merchantReference: checkoutResponse.merchantReference || checkoutResponse.orderNumber,
|
|
1263
|
-
totalAmount: checkoutResponse.totalAmount ??
|
|
1264
|
-
checkoutResponse.feeBreakdown?.totalPayable ??
|
|
1265
|
-
checkoutResponse.feeBreakdown?.total ??
|
|
1266
|
-
total,
|
|
1267
764
|
connectionMode: checkoutResponse.payazaConnectionMode,
|
|
1268
765
|
shippingAddress: {
|
|
1269
766
|
firstName: formData.firstName,
|
|
1270
767
|
lastName: formData.lastName,
|
|
1271
768
|
address: formData.address,
|
|
1272
769
|
city: formData.city,
|
|
1273
|
-
state: formData.
|
|
770
|
+
state: formData.city || formData.country, // Use city as state fallback (form doesn't collect state field)
|
|
1274
771
|
country: formData.country,
|
|
1275
772
|
zipCode: formData.postalCode,
|
|
1276
773
|
},
|
|
@@ -1296,13 +793,45 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1296
793
|
console.log('[Checkout] Step 4: Transaction reference set', {
|
|
1297
794
|
reference: checkoutConfig.reference,
|
|
1298
795
|
});
|
|
1299
|
-
// Step 5:
|
|
796
|
+
// Step 5: Save address if requested (for authenticated users)
|
|
797
|
+
if (isAuthenticated && saveNewAddress && !selectedAddressId) {
|
|
798
|
+
try {
|
|
799
|
+
// Ensure required fields are never empty (backend validation requires non-empty values)
|
|
800
|
+
const zipCode = formData.postalCode || formData.city || '00000';
|
|
801
|
+
const state = formData.city || formData.country || 'N/A';
|
|
802
|
+
const newAddress = await customerService.addAddress({
|
|
803
|
+
firstName: formData.firstName,
|
|
804
|
+
lastName: formData.lastName,
|
|
805
|
+
address1: formData.address,
|
|
806
|
+
address2: '',
|
|
807
|
+
city: formData.city,
|
|
808
|
+
state: state,
|
|
809
|
+
zipCode: zipCode,
|
|
810
|
+
country: formData.country,
|
|
811
|
+
phone: formData.phone,
|
|
812
|
+
isDefault: savedAddresses.length === 0, // Set as default if it's the first address
|
|
813
|
+
label: addressLabel || undefined,
|
|
814
|
+
});
|
|
815
|
+
console.log('[Checkout] Address saved successfully');
|
|
816
|
+
// Refresh addresses list
|
|
817
|
+
const addresses = await customerService.getAddresses();
|
|
818
|
+
setSavedAddresses(addresses);
|
|
819
|
+
// Select the newly saved address (this will also sync the form with the saved address)
|
|
820
|
+
handleAddressSelect(newAddress.id);
|
|
821
|
+
addToast('Address saved successfully!', 'success');
|
|
822
|
+
}
|
|
823
|
+
catch (addressError) {
|
|
824
|
+
console.error('[Checkout] Failed to save address:', addressError);
|
|
825
|
+
addToast('Failed to save address. You can save it later from your account.', 'error');
|
|
826
|
+
// Don't block checkout if address save fails - just log the error
|
|
827
|
+
// User can save address later from their account
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
// Step 6: Initiate Payaza checkout
|
|
1300
831
|
console.log('[Checkout] Step 6: Initiating Payaza checkout');
|
|
1301
832
|
try {
|
|
1302
833
|
await initiateCheckout(checkoutConfig);
|
|
1303
834
|
console.log('[Checkout] Step 6: Payaza checkout initiated successfully');
|
|
1304
|
-
// Order is created; release page-level loading — Payaza SDK overlay handles payment UI.
|
|
1305
|
-
setLoading(false);
|
|
1306
835
|
// Clear buyNowItem after successful checkout initiation
|
|
1307
836
|
// The item has been processed and order created
|
|
1308
837
|
if (effectiveBuyNowItem) {
|
|
@@ -1339,11 +868,7 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1339
868
|
stack: error?.stack,
|
|
1340
869
|
name: error?.name,
|
|
1341
870
|
});
|
|
1342
|
-
|
|
1343
|
-
error?.response?.status === 403 ||
|
|
1344
|
-
error?.code === 'COLLECTIONS_DISABLED' ||
|
|
1345
|
-
String(error?.message || '').toLowerCase().includes('not accepting payments');
|
|
1346
|
-
addToast(isCollectionsDisabled ? CHECKOUT_UNAVAILABLE_MESSAGE : 'Failed to initiate checkout. Please try again.', 'error');
|
|
871
|
+
addToast('Failed to initiate checkout. Please try again.', 'error');
|
|
1347
872
|
setLoading(false);
|
|
1348
873
|
}
|
|
1349
874
|
};
|
|
@@ -1365,74 +890,34 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1365
890
|
}
|
|
1366
891
|
}
|
|
1367
892
|
};
|
|
1368
|
-
// Show skeleton while
|
|
1369
|
-
if (
|
|
893
|
+
// Show skeleton while initializing or if not loaded
|
|
894
|
+
if (isInitializing || !isLoaded) {
|
|
1370
895
|
return _jsx(CheckoutSkeleton, {});
|
|
1371
896
|
}
|
|
1372
|
-
if (isPostPaymentNavigationPending()) {
|
|
1373
|
-
return (_jsxs("div", { className: "min-h-screen bg-gray-50/50 flex flex-col items-center justify-center p-4 font-sans", children: [_jsx(Loader2, { className: "mb-4 h-10 w-10 animate-spin text-gray-400" }), _jsx("p", { className: "font-medium text-gray-600", children: "Completing your order..." })] }));
|
|
1374
|
-
}
|
|
1375
897
|
// Show empty cart message if checkout items are empty (redirect will happen after delay)
|
|
1376
898
|
if (!checkoutItems || checkoutItems.length === 0) {
|
|
1377
|
-
return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-12 px-4 sm:px-6 lg:px-8 font-sans", children: _jsx("div", { className: "container mx-auto max-w-6xl", children: _jsx("div", { className: "bg-white rounded-3xl shadow-sm border border-gray-100 p-12 text-center", children: _jsxs("div", { className: "max-w-md mx-auto space-y-6", children: [_jsx("div", { className: "w-20 h-20 mx-auto rounded-full bg-gray-100 flex items-center justify-center", children: _jsx("svg", { className: "w-10 h-10 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" }) }) }), _jsx("h2", { className: "text-2xl font-bold text-gray-900", children: "Your cart is empty" }), _jsx("p", { className: "text-gray-600", children: "Add some items to your cart before proceeding to checkout." }), _jsx("div", { className: "pt-4", children: _jsx(Link, { href: `/${storeConfig.slug}`, children: _jsxs(Button, { size: "lg", className: "bg-black hover:bg-gray-800 text-white rounded-full px-8 py-6 h-auto font-semibold shadow-lg shadow-black/10 hover:shadow-xl transition-all hover:scale-105 flex items-center gap-2 mx-auto", children: [_jsx(ArrowLeft, { className: "w-5 h-5" }), "Continue Shopping"] }) }) })] }) }) }) }));
|
|
899
|
+
return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-12 px-4 sm:px-6 lg:px-8 font-sans", "data-checkout-ready": true, children: _jsx("div", { className: "container mx-auto max-w-6xl", children: _jsx("div", { className: "bg-white rounded-3xl shadow-sm border border-gray-100 p-12 text-center", children: _jsxs("div", { className: "max-w-md mx-auto space-y-6", children: [_jsx("div", { className: "w-20 h-20 mx-auto rounded-full bg-gray-100 flex items-center justify-center", children: _jsx("svg", { className: "w-10 h-10 text-gray-400", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: _jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M16 11V7a4 4 0 00-8 0v4M5 9h14l1 12H4L5 9z" }) }) }), _jsx("h2", { className: "text-2xl font-bold text-gray-900", children: "Your cart is empty" }), _jsx("p", { className: "text-gray-600", children: "Add some items to your cart before proceeding to checkout." }), _jsx("div", { className: "pt-4", children: _jsx(Link, { href: `/${storeConfig.slug}`, children: _jsxs(Button, { size: "lg", className: "bg-black hover:bg-gray-800 text-white rounded-full px-8 py-6 h-auto font-semibold shadow-lg shadow-black/10 hover:shadow-xl transition-all hover:scale-105 flex items-center gap-2 mx-auto", children: [_jsx(ArrowLeft, { className: "w-5 h-5" }), "Continue Shopping"] }) }) })] }) }) }) }));
|
|
1378
900
|
}
|
|
1379
|
-
return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-12 px-4 sm:px-6 lg:px-8 font-sans", children: _jsxs("div", { className: "container mx-auto max-w-6xl", children: [_jsxs("div", { className: "mb-10 flex items-center justify-between", children: [_jsxs(Link, { href: `/${storeConfig.slug}/cart`, className: "group flex items-center gap-2 text-gray-500 hover:text-black transition-colors font-medium", children: [_jsx("div", { className: "p-2 rounded-full bg-white border border-gray-200 group-hover:border-black transition-colors", children: _jsx(ArrowLeft, { className: "w-4 h-4" }) }), "Back to Cart"] }),
|
|
1380
|
-
? _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.address })
|
|
1381
|
-
: _jsx("p", { className: "text-[11px] text-gray-500 mt-1 ml-1", children: "Enter your house/flat number and street name for accurate delivery rates." })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Country" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsCountryDropdownOpen(!isCountryDropdownOpen), className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.country ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), children: [_jsx("span", { className: "flex items-center gap-2", children: (() => {
|
|
901
|
+
return (_jsx("div", { className: "min-h-screen bg-gray-50/50 py-12 px-4 sm:px-6 lg:px-8 font-sans", "data-checkout-ready": true, children: _jsxs("div", { className: "container mx-auto max-w-6xl", children: [_jsxs("div", { className: "mb-10 flex items-center justify-between", children: [_jsxs(Link, { href: `/${storeConfig.slug}/cart`, className: "group flex items-center gap-2 text-gray-500 hover:text-black transition-colors font-medium", children: [_jsx("div", { className: "p-2 rounded-full bg-white border border-gray-200 group-hover:border-black transition-colors", children: _jsx(ArrowLeft, { className: "w-4 h-4" }) }), "Back to Cart"] }), _jsxs("div", { className: "flex items-center gap-2 text-sm font-bold", children: [_jsx("span", { className: cn("px-3 py-1.5 rounded-full transition-colors", step === 'details' ? "bg-black text-white" : "bg-green-100 text-green-700"), children: step === 'details' ? '1' : _jsx(CheckCircle, { className: "w-4 h-4" }) }), _jsx("span", { className: cn(step === 'details' ? "text-black" : "text-gray-500"), children: "Shipping" }), _jsx("div", { className: "w-8 h-0.5 bg-gray-200" }), _jsx("span", { className: cn("px-3 py-1.5 rounded-full transition-colors", step === 'payment' ? "bg-black text-white" : "bg-gray-200 text-gray-500"), children: "2" }), _jsx("span", { className: cn(step === 'payment' ? "text-black" : "text-gray-400"), children: "Payment" })] })] }), _jsxs("div", { className: "flex flex-col lg:flex-row gap-10", children: [_jsx("div", { className: "flex-1", children: step === 'details' ? (_jsxs("form", { onSubmit: handleDetailsSubmit, className: "space-y-8 animate-fade-in", children: [_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-blue-50 text-blue-600 rounded-xl", children: _jsx(ShieldCheck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Contact Information" })] }), _jsx("div", { className: "space-y-4", children: isAuthenticated && user ? (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "p-4 bg-gray-50 rounded-xl border border-gray-200", children: [_jsx("p", { className: "text-xs font-bold text-gray-500 uppercase tracking-wider mb-2", children: "Logged in as" }), _jsxs("p", { className: "font-bold text-gray-900", children: [user.firstName, " ", user.lastName] }), _jsx("p", { className: "text-sm text-gray-600", children: user.email })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Email Address" }), _jsx("input", { type: "email", className: cn("w-full px-5 py-3.5 bg-white border-2 rounded-xl focus:outline-none focus:ring-0 transition-all font-medium", errors.email ? "border-red-500 focus:border-red-500" : "border-gray-100 focus:border-black"), placeholder: "john@example.com", value: formData.email, onChange: e => setFormData({ ...formData, email: e.target.value }) }), _jsx("p", { className: "text-xs text-gray-500 ml-1", children: "You can use a different email for this order" }), errors.email && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.email })] })] })) : (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Email Address" }), _jsx("input", { type: "email", className: cn("w-full px-5 py-3.5 bg-white border-2 rounded-xl focus:outline-none focus:ring-0 transition-all font-medium", errors.email ? "border-red-500 focus:border-red-500" : "border-gray-100 focus:border-black"), placeholder: "john@example.com", value: formData.email, onChange: e => setFormData({ ...formData, email: e.target.value }) }), errors.email && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.email })] })) })] }), _jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-orange-50 text-orange-600 rounded-xl", children: _jsx(MapPin, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: storeConfig.settings?.shippingEnabled === false ? 'Billing Address' : 'Shipping Address' })] }), isAuthenticated && (_jsx(AddressSelector, { addresses: savedAddresses, selectedAddressId: selectedAddressId, onSelect: handleAddressSelect, isLoading: loadingAddresses, isAuthenticated: isAuthenticated })), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-5", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "First Name" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.firstName ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), value: formData.firstName, onChange: e => setFormData({ ...formData, firstName: e.target.value }) }), errors.firstName && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.firstName })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Last Name" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.lastName ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), value: formData.lastName, onChange: e => setFormData({ ...formData, lastName: e.target.value }) }), errors.lastName && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.lastName })] }), _jsxs("div", { className: "space-y-1.5 md:col-span-2", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.address ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "123 Main St", value: formData.address, onChange: e => setFormData({ ...formData, address: e.target.value }) }), errors.address && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.address })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Country" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsCountryDropdownOpen(!isCountryDropdownOpen), className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.country ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), children: [_jsx("span", { className: "flex items-center gap-2", children: (() => {
|
|
1382
902
|
const selectedCountry = getCountryByName(formData.country);
|
|
1383
903
|
return selectedCountry ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-lg", children: selectedCountry.flag }), _jsx("span", { children: selectedCountry.name })] })) : (_jsx("span", { className: "text-gray-400", children: "Select country" }));
|
|
1384
|
-
})() }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isCountryDropdownOpen && "transform rotate-180") })] }), isCountryDropdownOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () =>
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
setIsCityDropdownOpen(false);
|
|
1394
|
-
setIsStateDropdownOpen(false);
|
|
1395
|
-
}, className: cn("w-full px-5 py-3 text-left flex items-center gap-3 hover:bg-gray-50 transition-colors", formData.country === country.name && "bg-gray-50 font-medium"), children: [_jsx("span", { className: "text-lg", children: country.flag }), _jsx("span", { children: country.name })] }, country.code))), countries.filter(c => c.name.toLowerCase().includes(countrySearch.toLowerCase())).length === 0 && (_jsx("div", { className: "px-5 py-3 text-gray-400 text-sm", children: "No countries found" }))] })] })] }))] }), errors.country && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.country })] }), isNigeria(formData.country) && (_jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "State" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => setIsStateDropdownOpen(!isStateDropdownOpen), className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.state ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), children: [_jsx("span", { className: formData.state ? "text-gray-900" : "text-gray-400", children: formData.state || 'Select state' }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isStateDropdownOpen && "transform rotate-180") })] }), isStateDropdownOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsStateDropdownOpen(false) }), _jsx("div", { className: "absolute z-30 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-xl max-h-52 overflow-y-auto", children: NIGERIAN_STATES.map((state) => (_jsx("button", { type: "button", onClick: () => {
|
|
1396
|
-
// Clear city when state changes so stale city isn't submitted
|
|
1397
|
-
setFormData({ ...formData, state, city: '' });
|
|
1398
|
-
setIsStateDropdownOpen(false);
|
|
1399
|
-
}, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.state === state && "bg-gray-50 font-medium"), children: state }, state))) })] }))] }), errors.state && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.state })] })), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "City" }), _jsxs("div", { className: "relative", children: [_jsx("input", { type: "text", placeholder: isNigeria(formData.country) && !formData.state ? 'Select a state first' : 'e.g. Lagos, Lekki, Ikeja', disabled: isNigeria(formData.country) && !formData.state, value: formData.city, onChange: (e) => {
|
|
1400
|
-
setFormData({ ...formData, city: e.target.value });
|
|
1401
|
-
setCitySearch(e.target.value);
|
|
1402
|
-
setIsCityDropdownOpen(true);
|
|
1403
|
-
}, onFocus: () => {
|
|
1404
|
-
if (!formData.country) {
|
|
904
|
+
})() }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isCountryDropdownOpen && "transform rotate-180") })] }), isCountryDropdownOpen && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsCountryDropdownOpen(false) }), _jsx("div", { className: "absolute z-20 w-full mt-1 bg-white border-2 border-gray-100 rounded-xl shadow-xl max-h-64 overflow-y-auto", children: countries.map((country) => (_jsxs("button", { type: "button", onClick: () => {
|
|
905
|
+
setFormData({ ...formData, country: country.name, city: '' }); // Clear city when country changes
|
|
906
|
+
setIsCountryDropdownOpen(false);
|
|
907
|
+
setIsCityDropdownOpen(false);
|
|
908
|
+
}, className: cn("w-full px-5 py-3 text-left flex items-center gap-3 hover:bg-gray-50 transition-colors", formData.country === country.name && "bg-gray-50 font-medium"), children: [_jsx("span", { className: "text-lg", children: country.flag }), _jsx("span", { children: country.name })] }, country.code))) })] }))] }), errors.country && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.country })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "City" }), _jsxs("div", { className: "relative", children: [_jsxs("button", { type: "button", onClick: () => {
|
|
909
|
+
if (formData.country) {
|
|
910
|
+
setIsCityDropdownOpen(!isCityDropdownOpen);
|
|
911
|
+
}
|
|
912
|
+
else {
|
|
1405
913
|
addToast('Please select a country first', 'error');
|
|
1406
|
-
return;
|
|
1407
914
|
}
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
? getNigerianCitiesByState(nigeriaState).filter(c => c.toLowerCase().includes((formData.city || '').toLowerCase()))
|
|
1413
|
-
: getCitiesByCountry(formData.country).filter(c => c.toLowerCase().includes((formData.city || '').toLowerCase()));
|
|
1414
|
-
if (suggestions.length === 0)
|
|
1415
|
-
return null;
|
|
1416
|
-
return (_jsx("div", { className: "absolute z-30 w-full mt-1 bg-white border border-gray-200 rounded-lg shadow-xl max-h-52 overflow-y-auto", children: suggestions.map((city) => (_jsx("button", { type: "button", onMouseDown: (e) => e.preventDefault(), onClick: () => {
|
|
1417
|
-
setFormData({ ...formData, city });
|
|
1418
|
-
setIsCityDropdownOpen(false);
|
|
1419
|
-
setCitySearch('');
|
|
1420
|
-
}, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors text-base", formData.city === city && "bg-gray-50 font-medium"), children: city }, city))) }));
|
|
1421
|
-
})()] }))] }), errors.city
|
|
1422
|
-
? _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.city })
|
|
1423
|
-
: _jsx("p", { className: "text-[11px] text-gray-500 mt-1 ml-1", children: "Type your city or town name \u2014 you can enter any location not shown in suggestions." })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Postal/Zip Code" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.postalCode ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "Optional", value: formData.postalCode, onChange: e => setFormData({ ...formData, postalCode: e.target.value }) }), errors.postalCode && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.postalCode })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx(RequiredLabel, { children: "Phone" }), _jsx("input", { type: "tel", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.phone ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: phonePlaceholder, value: formData.phone, onChange: e => handlePhoneChange(e.target.value) }), errors.phone && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.phone })] })] }), isAuthenticated && !selectedAddressId && (_jsxs("div", { className: "mt-6 space-y-3", children: [_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group", children: [_jsx("input", { type: "checkbox", checked: saveNewAddress, onChange: (e) => setSaveNewAddress(e.target.checked), className: "w-5 h-5 rounded border-2 border-gray-300 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsx("span", { className: "text-sm font-medium text-gray-700 group-hover:text-black transition-colors", children: "Save this address for future use" })] }), saveNewAddress && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address Label (optional)" }), _jsx("input", { type: "text", value: addressLabel, onChange: (e) => setAddressLabel(e.target.value), placeholder: "e.g., Home, Work, Office", className: "w-full px-5 py-3.5 bg-white border-2 border-gray-100 rounded-xl focus:outline-none focus:ring-0 focus:border-black transition-all font-medium" })] }))] }))] }), deliveryEnabled && !automatedShippingEnabled && (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-4", children: [_jsx("div", { className: "p-2 bg-amber-50 text-amber-700 rounded-xl", children: _jsx(Truck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Delivery" })] }), _jsx(MerchantHandledDeliveryNotice, { storeName: storeConfig.name })] })), deliveryEnabled && automatedShippingEnabled && canRequestShippingQuote(buildQuoteAddress()) && (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-purple-50 text-purple-600 rounded-xl", children: _jsx(Truck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Shipping Method" })] }), !isCourierMode && freeShippingThreshold !== undefined && !freeShippingEligible && (_jsxs("div", { className: "mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg", children: [_jsxs("p", { className: "text-xs font-medium text-blue-900 mb-1", children: ["Spend ", formatCurrency(freeShippingThreshold - subtotal, currency), " more for free shipping!"] }), _jsx("div", { className: "w-full bg-blue-200 rounded-full h-2", children: _jsx("div", { className: "bg-blue-600 h-2 rounded-full transition-all", style: {
|
|
915
|
+
}, disabled: !formData.country, className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-left flex items-center justify-between text-base", errors.city ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black", !formData.country && "bg-gray-50 text-gray-400 cursor-not-allowed"), children: [_jsx("span", { className: formData.city ? "text-gray-900" : "text-gray-400", children: formData.city || 'Select city' }), _jsx(ChevronDown, { className: cn("w-5 h-5 text-gray-400 transition-transform", isCityDropdownOpen && "transform rotate-180") })] }), isCityDropdownOpen && formData.country && (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 z-10", onClick: () => setIsCityDropdownOpen(false) }), _jsx("div", { className: "absolute z-20 w-full mt-1 bg-white border-2 border-gray-100 rounded-xl shadow-xl max-h-64 overflow-y-auto", children: getCitiesByCountry(formData.country).length > 0 ? (getCitiesByCountry(formData.country).map((city) => (_jsx("button", { type: "button", onClick: () => {
|
|
916
|
+
setFormData({ ...formData, city });
|
|
917
|
+
setIsCityDropdownOpen(false);
|
|
918
|
+
}, className: cn("w-full px-5 py-3 text-left hover:bg-gray-50 transition-colors", formData.city === city && "bg-gray-50 font-medium"), children: city }, city)))) : (_jsx("div", { className: "px-5 py-3 text-gray-500 text-sm", children: "No cities available for this country" })) })] }))] }), errors.city && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.city })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Postal/Zip Code" }), _jsx("input", { type: "text", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.postalCode ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), placeholder: "Optional", value: formData.postalCode, onChange: e => setFormData({ ...formData, postalCode: e.target.value }) }), errors.postalCode && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.postalCode })] }), _jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Phone" }), _jsx("input", { type: "tel", className: cn("w-full px-4 py-3 rounded-lg border focus:ring-2 focus:ring-black focus:outline-none transition-all text-base", errors.phone ? "border-red-300 focus:border-red-500 bg-red-50" : "border-gray-200 focus:border-black"), value: formData.phone, onChange: e => setFormData({ ...formData, phone: e.target.value }) }), errors.phone && _jsx("p", { className: "text-red-500 text-xs ml-1", children: errors.phone })] })] }), isAuthenticated && !selectedAddressId && (_jsxs("div", { className: "mt-6 space-y-3", children: [_jsxs("label", { className: "flex items-center gap-3 cursor-pointer group", children: [_jsx("input", { type: "checkbox", checked: saveNewAddress, onChange: (e) => setSaveNewAddress(e.target.checked), className: "w-5 h-5 rounded border-2 border-gray-300 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsx("span", { className: "text-sm font-medium text-gray-700 group-hover:text-black transition-colors", children: "Save this address for future use" })] }), saveNewAddress && (_jsxs("div", { className: "space-y-1.5", children: [_jsx("label", { className: "text-sm font-bold text-gray-700 ml-1", children: "Address Label (optional)" }), _jsx("input", { type: "text", value: addressLabel, onChange: (e) => setAddressLabel(e.target.value), placeholder: "e.g., Home, Work, Office", className: "w-full px-5 py-3.5 bg-white border-2 border-gray-100 rounded-xl focus:outline-none focus:ring-0 focus:border-black transition-all font-medium" })] }))] }))] }), storeConfig.settings?.shippingEnabled !== false && (formData.city && formData.country) && (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-purple-50 text-purple-600 rounded-xl", children: _jsx(Truck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Shipping Method" })] }), freeShippingThreshold !== undefined && !freeShippingEligible && (_jsxs("div", { className: "mb-4 p-3 bg-blue-50 border border-blue-200 rounded-lg", children: [_jsxs("p", { className: "text-xs font-medium text-blue-900 mb-1", children: ["Spend ", formatCurrency(freeShippingThreshold - subtotal, currency), " more for free shipping!"] }), _jsx("div", { className: "w-full bg-blue-200 rounded-full h-2", children: _jsx("div", { className: "bg-blue-600 h-2 rounded-full transition-all", style: {
|
|
1424
919
|
width: `${Math.min(100, (subtotal / freeShippingThreshold) * 100)}%`,
|
|
1425
|
-
} }) })] })),
|
|
1426
|
-
weight: item.product?.weight || 0.5,
|
|
1427
|
-
value: item.price,
|
|
1428
|
-
name: item.product?.name,
|
|
1429
|
-
quantity: item.quantity,
|
|
1430
|
-
})), customerInfo: {
|
|
1431
|
-
firstName: formData.firstName,
|
|
1432
|
-
lastName: formData.lastName,
|
|
1433
|
-
phone: formData.phone,
|
|
1434
|
-
email: formData.email,
|
|
1435
|
-
}, onSelect: handleCourierSelect, selected: selectedCourier })) : shippingError ? (_jsx("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", children: _jsx("p", { className: "text-sm text-red-600", children: shippingError }) })) : shippingMethods.length > 0 ? (_jsx("div", { className: "space-y-3", children: shippingMethods.map((method) => (_jsxs("label", { className: cn("flex items-center justify-between p-4 border-2 rounded-xl cursor-pointer transition-all", selectedShippingMethod?.code === method.code
|
|
920
|
+
} }) })] })), isCalculatingShipping ? (_jsx("div", { className: "flex items-center justify-center p-8", children: _jsxs("div", { className: "flex items-center gap-2 text-gray-600", children: [_jsx("div", { className: "animate-spin rounded-full h-5 w-5 border-b-2 border-gray-900" }), _jsx("span", { className: "text-sm font-medium", children: "Calculating shipping options..." })] }) })) : shippingError ? (_jsx("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", children: _jsx("p", { className: "text-sm text-red-600", children: shippingError }) })) : shippingMethods.length > 0 ? (_jsx("div", { className: "space-y-3", children: shippingMethods.map((method) => (_jsxs("label", { className: cn("flex items-center justify-between p-4 border-2 rounded-xl cursor-pointer transition-all", selectedShippingMethod?.code === method.code
|
|
1436
921
|
? "border-black bg-gray-50"
|
|
1437
922
|
: "border-gray-200 hover:border-gray-300 bg-white"), children: [_jsxs("div", { className: "flex items-center gap-4 flex-1", children: [_jsx("input", { type: "radio", name: "shippingMethod", value: method.code, checked: selectedShippingMethod?.code === method.code, onChange: () => setSelectedShippingMethod(method), className: "w-5 h-5 text-black focus:ring-2 focus:ring-black cursor-pointer" }), _jsxs("div", { className: "flex-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("p", { className: "font-bold text-gray-900", children: method.name }), freeShippingEligible && method.cost === 0 && (_jsx("span", { className: "text-xs font-normal text-green-700 bg-green-100 px-2 py-0.5 rounded-full", children: "FREE" }))] }), method.description && (_jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: method.description })), method.estimatedDays && (_jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: method.estimatedDaysMin && method.estimatedDaysMax
|
|
1438
923
|
? `Estimated delivery: ${method.estimatedDaysMin}-${method.estimatedDaysMax} business days`
|
|
@@ -1440,11 +925,7 @@ export function CheckoutPage({ storeConfig }) {
|
|
|
1440
925
|
? 'FREE'
|
|
1441
926
|
: Number.isFinite(method.cost) && method.cost >= 0
|
|
1442
927
|
? formatCurrency(method.cost, currency)
|
|
1443
|
-
: 'Not available' }) })] }, method.code))) })) : (_jsx("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-lg text-center", children: _jsx("p", { className: "text-sm text-gray-600", children: "No shipping methods available. Please check your address." }) }))] })), _jsxs(Button, { type: "submit", size: "lg", className: "w-full h-14 text-lg font-bold rounded-2xl shadow-xl hover:shadow-2xl hover:-translate-y-0.5 transition-all
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
} }) }), _jsx("div", { className: "space-y-6 mb-8 max-h-[400px] overflow-y-auto scrollbar-hide", children: checkoutItems.map((item) => {
|
|
1447
|
-
const stockInfo = getCartLineStockInfo(item.product, item.quantity);
|
|
1448
|
-
return (_jsxs("div", { className: "flex gap-4 group", children: [_jsxs("div", { className: "w-20 h-20 bg-gray-100 rounded-xl overflow-hidden flex-shrink-0 relative border border-gray-200", children: [_jsx(Image, { src: getCartLineImage(item) || item.product.images[0], alt: item.product.name, fill: true, className: "object-cover group-hover:scale-105 transition-transform duration-500", unoptimized: true }), _jsxs("span", { className: "absolute top-0 right-0 bg-black text-white text-[10px] font-bold px-2 py-1 rounded-bl-lg z-10", children: ["x", item.quantity] })] }), _jsxs("div", { className: "flex-1 py-1", children: [_jsx("p", { className: "font-bold text-gray-900 text-sm line-clamp-2 leading-relaxed", children: item.product.name }), item.variantName && _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: item.variantName }), stockInfo.displayLabel && (_jsx("p", { className: cn('text-xs font-semibold mt-1', stockInfo.stockError ? 'text-red-600' : 'text-amber-700'), children: stockInfo.displayLabel })), _jsx("p", { className: "text-gray-500 text-sm font-medium mt-1", children: formatCurrency(normalizePrice(item.price), item.product?.currency || currency) })] })] }, item.id));
|
|
1449
|
-
}) }), isMarketplaceCheckout && storeBreakdown.length > 1 && (_jsxs("div", { className: "mb-6 rounded-2xl bg-gray-50 border border-gray-100 p-4", children: [_jsxs("p", { className: "text-xs font-bold text-gray-700 mb-3", children: ["Your order will be split into ", storeBreakdown.length, " separate orders \u2014 one per store"] }), _jsx("div", { className: "space-y-2", children: storeBreakdown.map((store) => (_jsxs("div", { className: "flex justify-between text-xs text-gray-600", children: [_jsxs("span", { className: "font-medium truncate pr-2", children: [store.storeName, " (", store.itemCount, " item", store.itemCount === 1 ? '' : 's', ")"] }), _jsx("span", { className: "font-bold text-gray-900 flex-shrink-0", children: formatCurrency(store.subtotal, currency) })] }, store.storeId))) })] })), _jsxs("div", { className: "space-y-4 pt-6 border-t-2 border-gray-50", children: [_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: "Subtotal" }), _jsx("span", { className: "text-gray-900 font-bold", children: formatCurrency(subtotal, currency) })] }), deliveryEnabled && (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsxs("span", { children: ["Shipping", selectedShippingMethod ? ` (${selectedShippingMethod.name})` : ''] }), _jsx("span", { className: "text-gray-900 font-bold", children: !automatedShippingEnabled ? (_jsx("span", { className: "text-amber-700", children: "Handled by merchant" })) : isCalculatingShipping ? (_jsx("span", { className: "text-gray-400", children: "Calculating..." })) : !selectedShippingMethod ? (_jsx("span", { className: "text-gray-400", children: "Not available" })) : shipping === 0 && freeShippingEligible ? (_jsx("span", { className: "text-green-600", children: "Free" })) : shipping === 0 ? ('Free') : Number.isFinite(shipping) && shipping > 0 ? (formatCurrency(shipping, currency)) : (_jsx("span", { className: "text-gray-400", children: "Not available" })) })] })), feesLoading && (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: "Fees" }), _jsx("span", { className: "text-gray-900 font-bold", children: "Calculating..." })] })), !feesLoading && (_jsx(CheckoutFeeLines, { lines: displayLines, currency: currency, excludeKeys: ['shipping'], className: "flex justify-between text-gray-500 font-medium text-sm", labelClassName: "", amountClassName: "text-gray-900 font-bold" })), _jsxs("div", { className: "flex justify-between items-end pt-4 mt-2 border-t border-gray-100", children: [_jsx("span", { className: "text-base font-bold text-gray-900", children: "Total to pay" }), _jsx("span", { className: "text-3xl font-black text-gray-900 tracking-tight", children: formatCurrency(total, currency) })] })] }), _jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100 grid grid-cols-3 gap-2 opacity-50 grayscale", children: [_jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" }), _jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" }), _jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" })] })] }), PROMO_CODE_ENABLED && (_jsxs("div", { className: "bg-white p-6 rounded-3xl shadow-lg border border-gray-100 mt-6 sticky top-[500px]", children: [_jsxs("h3", { className: "font-bold text-gray-900 mb-4 flex items-center gap-2", children: [_jsx(Tag, { className: "w-4 h-4" }), " Have a promo code?"] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { type: "text", value: promoCode, onChange: (e) => setPromoCode(e.target.value), placeholder: "Enter code", className: "flex-1 px-4 py-2 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:border-black transition-all uppercase placeholder:normal-case font-medium" }), _jsx(Button, { onClick: handleApplyPromo, disabled: !promoCode || isPromoApplied, className: "bg-black text-white rounded-xl", style: isPromoApplied ? { backgroundColor: '#22c55e' } : {}, children: isPromoApplied ? 'Applied' : 'Apply' })] }), isPromoApplied && (_jsxs("p", { className: "text-green-600 text-xs font-bold mt-2 flex items-center gap-1", children: [_jsx(CheckCircle, { className: "w-3 h-3" }), " Discount applied to total"] }))] }))] })] })] }) }));
|
|
928
|
+
: 'Not available' }) })] }, method.code))) })) : (_jsx("div", { className: "p-4 bg-gray-50 border border-gray-200 rounded-lg text-center", children: _jsx("p", { className: "text-sm text-gray-600", children: "No shipping methods available. Please check your address." }) }))] })), _jsxs(Button, { type: "submit", size: "lg", className: "w-full h-14 text-lg font-bold rounded-2xl shadow-xl hover:shadow-2xl hover:-translate-y-0.5 transition-all", style: { backgroundColor: primaryColor }, children: ["Continue to Payment ", _jsx(ChevronRight, { className: "ml-2 w-5 h-5" })] })] })) : (_jsxs("div", { className: "space-y-8 animate-fade-in", children: [_jsxs("section", { className: "bg-white p-6 rounded-3xl shadow-sm border border-gray-100 flex justify-between items-center group hover:border-gray-300 transition-colors cursor-pointer", onClick: () => setStep('details'), children: [_jsxs("div", { className: "flex gap-4 items-center", children: [_jsx("div", { className: "w-10 h-10 bg-green-50 rounded-full flex items-center justify-center border border-green-100", children: _jsx(CheckCircle, { className: "w-5 h-5 text-green-600" }) }), _jsxs("div", { children: [_jsx("p", { className: "text-xs text-gray-500 font-bold uppercase tracking-wider mb-1", children: "Shipping To" }), _jsxs("p", { className: "font-bold text-gray-900", children: [formData.firstName, " ", formData.lastName] }), _jsxs("p", { className: "text-sm text-gray-500", children: [formData.address, ", ", formData.city] })] })] }), _jsx("span", { className: "text-sm font-bold text-blue-600 px-4 py-2 bg-blue-50 rounded-lg group-hover:bg-blue-100 transition-colors", children: "Edit" })] }), isFreeOrder ? (_jsxs("section", { className: "bg-green-50 p-8 rounded-3xl shadow-sm border border-green-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-4", children: [_jsx("div", { className: "p-2 bg-white text-green-600 rounded-xl", children: _jsx(CheckCircle, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Free Order" })] }), _jsx("p", { className: "text-sm text-green-800 leading-relaxed", children: "No payment is required. Complete your details and confirm to get access to your digital product." })] })) : (_jsxs("section", { className: "bg-white p-8 rounded-3xl shadow-sm border border-gray-100", children: [_jsxs("div", { className: "flex items-center gap-3 mb-6", children: [_jsx("div", { className: "p-2 bg-blue-50 text-blue-600 rounded-xl", children: _jsx(ShieldCheck, { className: "w-5 h-5" }) }), _jsx("h2", { className: "text-xl font-bold text-gray-900", children: "Payment Security" })] }), _jsx("div", { className: "bg-blue-50/50 p-6 rounded-2xl border border-blue-100 mb-6", children: _jsxs("div", { className: "flex items-start gap-4", children: [_jsx(Lock, { className: "w-5 h-5 text-blue-600 mt-1" }), _jsxs("div", { children: [_jsx("h4", { className: "font-bold text-blue-900 mb-1", children: "Encrypted Transaction" }), _jsx("p", { className: "text-sm text-blue-700/80 leading-relaxed", children: "Your payment is processed securely by Payaza. We do not store your card details." })] })] }) }), _jsxs("div", { className: "flex items-center justify-center gap-2 text-xs font-bold text-gray-400 uppercase tracking-widest", children: [_jsx(Lock, { className: "w-3 h-3" }), "256-bit SSL Secure Payment"] })] })), _jsx(Button, { onClick: handlePayazaCheckout, size: "lg", className: "w-full h-16 text-lg font-bold rounded-2xl shadow-xl hover:shadow-2xl hover:-translate-y-0.5 transition-all", style: { backgroundColor: isFreeOrder ? '#059669' : primaryColor }, disabled: loading || isPayazaLoading, children: loading || isPayazaLoading ? (_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "w-5 h-5 border-2 border-white/30 border-t-white rounded-full animate-spin" }), _jsx("span", { children: isFreeOrder ? 'Completing order...' : 'Processing Securely...' })] })) : (_jsxs("div", { className: "flex items-center gap-2", children: [isFreeOrder ? _jsx(CheckCircle, { className: "w-5 h-5" }) : _jsx(Lock, { className: "w-5 h-5" }), _jsx("span", { children: isFreeOrder ? 'Complete Free Order' : 'Pay Now' })] })) })] })) }), _jsxs("div", { className: "w-full lg:w-[400px] flex-shrink-0", children: [_jsxs("div", { className: "bg-white p-8 rounded-3xl shadow-lg border border-gray-100 sticky top-8", children: [_jsx("h2", { className: "text-xl font-black text-gray-900 mb-6", children: "Order Summary" }), _jsx("div", { className: "space-y-6 mb-8 max-h-[400px] overflow-y-auto scrollbar-hide", children: checkoutItems.map((item) => (_jsxs("div", { className: "flex gap-4 group", children: [_jsxs("div", { className: "w-20 h-20 bg-gray-100 rounded-xl overflow-hidden flex-shrink-0 relative border border-gray-200", children: [_jsx(Image, { src: item.product.images[0], alt: item.product.name, fill: true, className: "object-cover group-hover:scale-105 transition-transform duration-500", unoptimized: true }), _jsxs("span", { className: "absolute top-0 right-0 bg-black text-white text-[10px] font-bold px-2 py-1 rounded-bl-lg z-10", children: ["x", item.quantity] })] }), _jsxs("div", { className: "flex-1 py-1", children: [_jsx("p", { className: "font-bold text-gray-900 text-sm line-clamp-2 leading-relaxed", children: item.product.name }), item.variantName && _jsx("p", { className: "text-xs text-gray-500 mt-0.5", children: item.variantName }), _jsx("p", { className: "text-gray-500 text-sm font-medium mt-1", children: normalizePrice(item.price) <= 0
|
|
929
|
+
? 'Free'
|
|
930
|
+
: formatCurrency(normalizePrice(item.price), item.product?.currency || currency) })] })] }, item.id))) }), _jsxs("div", { className: "space-y-4 pt-6 border-t-2 border-gray-50", children: [_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: "Subtotal" }), _jsx("span", { className: "text-gray-900 font-bold", children: isFreeOrder ? 'Free' : formatCurrency(subtotal, currency) })] }), _jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsxs("span", { children: ["Shipping", selectedShippingMethod ? ` (${selectedShippingMethod.name})` : ''] }), _jsx("span", { className: "text-gray-900 font-bold", children: isCalculatingShipping ? (_jsx("span", { className: "text-gray-400", children: "Calculating..." })) : !selectedShippingMethod ? (_jsx("span", { className: "text-gray-400", children: "Not available" })) : shipping === 0 && freeShippingEligible ? (_jsx("span", { className: "text-green-600", children: "Free" })) : shipping === 0 ? ('Free') : Number.isFinite(shipping) && shipping > 0 ? (formatCurrency(shipping, currency)) : (_jsx("span", { className: "text-gray-400", children: "Not available" })) })] }), customerFeeLines.map((line) => (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: line.label }), _jsx("span", { className: "text-gray-900 font-bold", children: formatCurrency(line.amount, currency) })] }, line.key))), !feePreview && vatAmount > 0 && (_jsxs("div", { className: "flex justify-between text-gray-500 font-medium text-sm", children: [_jsx("span", { children: vatLabel }), _jsx("span", { className: "text-gray-900 font-bold", children: formatCurrency(vatAmount, currency) })] })), _jsxs("div", { className: "flex justify-between items-end pt-4 mt-2 border-t border-gray-100", children: [_jsx("span", { className: "text-base font-bold text-gray-900", children: "Total to pay" }), _jsx("span", { className: "text-3xl font-black text-gray-900 tracking-tight", children: isCalculatingFees ? 'Calculating…' : isFreeOrder ? 'Free' : formatCurrency(total, currency) })] })] }), _jsxs("div", { className: "mt-8 pt-6 border-t border-gray-100 grid grid-cols-3 gap-2 opacity-50 grayscale", children: [_jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" }), _jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" }), _jsx("div", { className: "h-8 bg-gray-100 rounded border border-gray-200" })] })] }), _jsxs("div", { className: "bg-white p-6 rounded-3xl shadow-lg border border-gray-100 mt-6 sticky top-[500px]", children: [_jsxs("h3", { className: "font-bold text-gray-900 mb-4 flex items-center gap-2", children: [_jsx(Tag, { className: "w-4 h-4" }), " Have a promo code?"] }), _jsxs("div", { className: "flex gap-2", children: [_jsx("input", { type: "text", value: promoCode, onChange: (e) => setPromoCode(e.target.value), placeholder: "Enter code", className: "flex-1 px-4 py-2 bg-gray-50 border border-gray-200 rounded-xl focus:outline-none focus:border-black transition-all uppercase placeholder:normal-case font-medium" }), _jsx(Button, { onClick: handleApplyPromo, disabled: !promoCode || isPromoApplied, className: "bg-black text-white rounded-xl", style: isPromoApplied ? { backgroundColor: '#22c55e' } : {}, children: isPromoApplied ? 'Applied' : 'Apply' })] }), isPromoApplied && (_jsxs("p", { className: "text-green-600 text-xs font-bold mt-2 flex items-center gap-1", children: [_jsx(CheckCircle, { className: "w-3 h-3" }), " Discount applied to total"] }))] })] })] })] }) }));
|
|
1450
931
|
}
|