@fayz-ai/storefront 0.9.0 → 0.11.0
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/README.md +3 -3
- package/dist/app/createStorefrontApp.d.ts +65 -0
- package/dist/app/createStorefrontApp.d.ts.map +1 -0
- package/dist/{define.d.ts → app/define.d.ts} +3 -3
- package/dist/app/define.d.ts.map +1 -0
- package/dist/app/hydration.d.ts +37 -0
- package/dist/app/hydration.d.ts.map +1 -0
- package/dist/app/prerender.d.ts +57 -0
- package/dist/app/prerender.d.ts.map +1 -0
- package/dist/{router.d.ts → app/router.d.ts} +140 -113
- package/dist/app/router.d.ts.map +1 -0
- package/dist/{scaffold.d.ts → app/scaffold.d.ts} +2 -2
- package/dist/app/scaffold.d.ts.map +1 -0
- package/dist/app/serving.d.ts +70 -0
- package/dist/app/serving.d.ts.map +1 -0
- package/dist/app/static-render.d.ts +46 -0
- package/dist/app/static-render.d.ts.map +1 -0
- package/dist/blocks/block-at-path.d.ts +59 -0
- package/dist/blocks/block-at-path.d.ts.map +1 -0
- package/dist/blocks/page-blocks.d.ts +163 -0
- package/dist/blocks/page-blocks.d.ts.map +1 -0
- package/dist/blocks/page-data.d.ts +105 -0
- package/dist/blocks/page-data.d.ts.map +1 -0
- package/dist/{blocks.d.ts → blocks/registry.d.ts} +1 -1
- package/dist/blocks/registry.d.ts.map +1 -0
- package/dist/{sections.d.ts → blocks/sections.d.ts} +40 -2
- package/dist/blocks/sections.d.ts.map +1 -0
- package/dist/chunk-6IXTENLB.js +162 -0
- package/dist/chunk-6IXTENLB.js.map +1 -0
- package/dist/chunk-SX3J3QIT.js +11046 -0
- package/dist/chunk-SX3J3QIT.js.map +1 -0
- package/dist/components/PaymentTerms.d.ts +34 -0
- package/dist/components/PaymentTerms.d.ts.map +1 -0
- package/dist/components/Skeleton.d.ts +17 -0
- package/dist/components/Skeleton.d.ts.map +1 -0
- package/dist/components/SmoothImage.d.ts +1 -1
- package/dist/components/SmoothImage.d.ts.map +1 -1
- package/dist/components/account/CustomerAccountShell.d.ts +17 -0
- package/dist/components/account/CustomerAccountShell.d.ts.map +1 -0
- package/dist/components/account/SignInModal.d.ts.map +1 -0
- package/dist/components/account/section.d.ts +15 -0
- package/dist/components/account/section.d.ts.map +1 -0
- package/dist/components/cart/CartDrawer.d.ts.map +1 -0
- package/dist/components/cart/QuantityInput.d.ts.map +1 -0
- package/dist/components/checkout/CheckoutPayment.d.ts +17 -0
- package/dist/components/checkout/CheckoutPayment.d.ts.map +1 -0
- package/dist/components/checkout/CheckoutSteps.d.ts +9 -0
- package/dist/components/checkout/CheckoutSteps.d.ts.map +1 -0
- package/dist/components/checkout/DeliveryEstimator.d.ts.map +1 -0
- package/dist/components/collection/CategoryShowcase.d.ts +44 -0
- package/dist/components/collection/CategoryShowcase.d.ts.map +1 -0
- package/dist/components/collection/FiltersPanel.d.ts +19 -0
- package/dist/components/collection/FiltersPanel.d.ts.map +1 -0
- package/dist/components/collection/ProductGrid.d.ts.map +1 -0
- package/dist/components/layout/PageBodyBlocks.d.ts +40 -0
- package/dist/components/layout/PageBodyBlocks.d.ts.map +1 -0
- package/dist/components/layout/SearchOverlay.d.ts +13 -0
- package/dist/components/layout/SearchOverlay.d.ts.map +1 -0
- package/dist/components/layout/StorefrontErrorBoundary.d.ts.map +1 -0
- package/dist/components/layout/StorefrontFooter.d.ts.map +1 -0
- package/dist/components/layout/StorefrontHeader.d.ts.map +1 -0
- package/dist/components/layout/Toaster.d.ts.map +1 -0
- package/dist/components/marketing/CollectionSections.d.ts +463 -0
- package/dist/components/marketing/CollectionSections.d.ts.map +1 -0
- package/dist/components/marketing/ConversionSections.d.ts +221 -0
- package/dist/components/marketing/ConversionSections.d.ts.map +1 -0
- package/dist/components/marketing/EditorialSections.d.ts +248 -0
- package/dist/components/marketing/EditorialSections.d.ts.map +1 -0
- package/dist/components/marketing/EntityList.d.ts +97 -0
- package/dist/components/marketing/EntityList.d.ts.map +1 -0
- package/dist/components/marketing/FormBlock.d.ts +167 -0
- package/dist/components/marketing/FormBlock.d.ts.map +1 -0
- package/dist/components/marketing/HeroSection.d.ts +125 -0
- package/dist/components/marketing/HeroSection.d.ts.map +1 -0
- package/dist/components/marketing/MediaCarousel.d.ts +135 -0
- package/dist/components/marketing/MediaCarousel.d.ts.map +1 -0
- package/dist/components/marketing/MiscSections.d.ts +252 -0
- package/dist/components/marketing/MiscSections.d.ts.map +1 -0
- package/dist/components/order/OrderTrackingTimeline.d.ts.map +1 -0
- package/dist/components/order/SuccessMark.d.ts +10 -0
- package/dist/components/order/SuccessMark.d.ts.map +1 -0
- package/dist/components/product/ProductCard.d.ts +14 -0
- package/dist/components/product/ProductCard.d.ts.map +1 -0
- package/dist/components/product/ProductEnquiryForm.d.ts.map +1 -0
- package/dist/components/product/ProductGallery.d.ts +36 -0
- package/dist/components/product/ProductGallery.d.ts.map +1 -0
- package/dist/components/{ProductOptionSelector.d.ts → product/ProductOptionSelector.d.ts} +1 -1
- package/dist/components/product/ProductOptionSelector.d.ts.map +1 -0
- package/dist/components/product/ProductRail.d.ts +93 -0
- package/dist/components/product/ProductRail.d.ts.map +1 -0
- package/dist/components/product/ProductReviews.d.ts +39 -0
- package/dist/components/product/ProductReviews.d.ts.map +1 -0
- package/dist/components/product/ProductSlider.d.ts +169 -0
- package/dist/components/product/ProductSlider.d.ts.map +1 -0
- package/dist/components/product/ProductSpecs.d.ts.map +1 -0
- package/dist/components/product/RelatedProducts.d.ts.map +1 -0
- package/dist/{auth.d.ts → data/auth.d.ts} +9 -0
- package/dist/data/auth.d.ts.map +1 -0
- package/dist/data/facets.d.ts +39 -0
- package/dist/data/facets.d.ts.map +1 -0
- package/dist/data/loaders.d.ts +52 -0
- package/dist/data/loaders.d.ts.map +1 -0
- package/dist/data/local-orders.d.ts +5 -0
- package/dist/data/local-orders.d.ts.map +1 -0
- package/dist/data/order-status.d.ts.map +1 -0
- package/dist/data/product-options.d.ts +36 -0
- package/dist/data/product-options.d.ts.map +1 -0
- package/dist/data/shipping-items.d.ts +20 -0
- package/dist/data/shipping-items.d.ts.map +1 -0
- package/dist/diagnostics/seo-audit.d.ts +54 -0
- package/dist/diagnostics/seo-audit.d.ts.map +1 -0
- package/dist/diagnostics/seo-audit.js +3 -0
- package/dist/diagnostics/seo-audit.js.map +1 -0
- package/dist/document/change.d.ts +100 -0
- package/dist/document/change.d.ts.map +1 -0
- package/dist/document/config.d.ts +311 -0
- package/dist/document/config.d.ts.map +1 -0
- package/dist/document/describe.d.ts +135 -0
- package/dist/document/describe.d.ts.map +1 -0
- package/dist/document/overlay.d.ts +58 -0
- package/dist/document/overlay.d.ts.map +1 -0
- package/dist/{presets.d.ts → document/presets.d.ts} +2 -2
- package/dist/document/presets.d.ts.map +1 -0
- package/dist/document/render.d.ts +171 -0
- package/dist/document/render.d.ts.map +1 -0
- package/dist/document/round-trip.d.ts +46 -0
- package/dist/document/round-trip.d.ts.map +1 -0
- package/dist/document/sections.d.ts +194 -0
- package/dist/document/sections.d.ts.map +1 -0
- package/dist/document/store-policy.d.ts +29 -0
- package/dist/document/store-policy.d.ts.map +1 -0
- package/dist/document/upgrade.d.ts +112 -0
- package/dist/document/upgrade.d.ts.map +1 -0
- package/dist/document/validate.d.ts +24 -0
- package/dist/document/validate.d.ts.map +1 -0
- package/dist/{component-contracts.d.ts → extend/component-contracts.d.ts} +74 -22
- package/dist/extend/component-contracts.d.ts.map +1 -0
- package/dist/extend/component-override.d.ts +38 -0
- package/dist/extend/component-override.d.ts.map +1 -0
- package/dist/extend/component-selectors.d.ts.map +1 -0
- package/dist/extend/editor-bridge.d.ts +59 -0
- package/dist/extend/editor-bridge.d.ts.map +1 -0
- package/dist/extend/extension-points.d.ts +103 -0
- package/dist/extend/extension-points.d.ts.map +1 -0
- package/dist/extend/slots.d.ts +216 -0
- package/dist/extend/slots.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +25 -6
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/use-address.d.ts +26 -0
- package/dist/hooks/use-address.d.ts.map +1 -0
- package/dist/hooks/use-cart.d.ts +3 -0
- package/dist/hooks/use-cart.d.ts.map +1 -0
- package/dist/hooks/use-catalog.d.ts +24 -0
- package/dist/hooks/use-catalog.d.ts.map +1 -0
- package/dist/hooks/use-categories.d.ts +19 -0
- package/dist/hooks/use-categories.d.ts.map +1 -0
- package/dist/hooks/use-checkout.d.ts +41 -0
- package/dist/hooks/use-checkout.d.ts.map +1 -0
- package/dist/hooks/use-discount-validator.d.ts +20 -0
- package/dist/hooks/use-discount-validator.d.ts.map +1 -0
- package/dist/hooks/use-enquiry.d.ts +7 -0
- package/dist/hooks/use-enquiry.d.ts.map +1 -0
- package/dist/hooks/{useMyOrders.d.ts → use-my-orders.d.ts} +1 -1
- package/dist/hooks/use-my-orders.d.ts.map +1 -0
- package/dist/hooks/use-order.d.ts +17 -0
- package/dist/hooks/use-order.d.ts.map +1 -0
- package/dist/hooks/use-payment-methods.d.ts +19 -0
- package/dist/hooks/use-payment-methods.d.ts.map +1 -0
- package/dist/hooks/use-policies.d.ts +25 -0
- package/dist/hooks/use-policies.d.ts.map +1 -0
- package/dist/hooks/use-product.d.ts +15 -0
- package/dist/hooks/use-product.d.ts.map +1 -0
- package/dist/hooks/{useProducts.d.ts → use-products.d.ts} +1 -1
- package/dist/hooks/use-products.d.ts.map +1 -0
- package/dist/hooks/use-region.d.ts +15 -0
- package/dist/hooks/use-region.d.ts.map +1 -0
- package/dist/hooks/use-shipping.d.ts +32 -0
- package/dist/hooks/use-shipping.d.ts.map +1 -0
- package/dist/hooks/use-storefront-actions.d.ts +12 -0
- package/dist/hooks/use-storefront-actions.d.ts.map +1 -0
- package/dist/hooks/use-storefront-head.d.ts +19 -0
- package/dist/hooks/use-storefront-head.d.ts.map +1 -0
- package/dist/hooks/use-storefront.d.ts +3 -0
- package/dist/hooks/use-storefront.d.ts.map +1 -0
- package/dist/index.d.ts +124 -54
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1310 -5010
- package/dist/index.js.map +1 -1
- package/dist/lib/errors.d.ts +9 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/motion.d.ts.map +1 -0
- package/dist/lib/seo.d.ts +26 -0
- package/dist/lib/seo.d.ts.map +1 -0
- package/dist/{testids.d.ts → lib/testids.d.ts} +33 -0
- package/dist/lib/testids.d.ts.map +1 -0
- package/dist/{theme.d.ts → lib/theme.d.ts} +41 -3
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/render.d.ts +5 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +359 -0
- package/dist/render.js.map +1 -0
- package/dist/stores/cart.store.d.ts +34 -2
- package/dist/stores/cart.store.d.ts.map +1 -1
- package/dist/stores/catalog.store.d.ts +11 -0
- package/dist/stores/catalog.store.d.ts.map +1 -1
- package/dist/stores/delivery.store.d.ts +9 -0
- package/dist/stores/delivery.store.d.ts.map +1 -1
- package/dist/views/CatalogPage.d.ts.map +1 -0
- package/dist/views/CheckoutPage.d.ts.map +1 -0
- package/dist/views/ContentPage.d.ts +11 -0
- package/dist/views/ContentPage.d.ts.map +1 -0
- package/dist/views/HomePage.d.ts +9 -0
- package/dist/views/HomePage.d.ts.map +1 -0
- package/dist/views/MyPurchasesPage.d.ts.map +1 -0
- package/dist/views/NotFoundPage.d.ts.map +1 -0
- package/dist/views/OrderConfirmationPage.d.ts.map +1 -0
- package/dist/{pages → views}/PolicyPage.d.ts.map +1 -1
- package/dist/views/ProductDetailPage.d.ts.map +1 -0
- package/dist/views/StorefrontPage.d.ts +35 -0
- package/dist/views/StorefrontPage.d.ts.map +1 -0
- package/dist/workflows/checkout.d.ts +1 -1
- package/dist/workflows/checkout.d.ts.map +1 -1
- package/package.json +21 -14
- package/dist/auth.d.ts.map +0 -1
- package/dist/blocks.d.ts.map +0 -1
- package/dist/component-contracts.d.ts.map +0 -1
- package/dist/component-selectors.d.ts.map +0 -1
- package/dist/components/CartDrawer.d.ts.map +0 -1
- package/dist/components/CustomerAccountShell.d.ts +0 -13
- package/dist/components/CustomerAccountShell.d.ts.map +0 -1
- package/dist/components/DeliveryEstimator.d.ts.map +0 -1
- package/dist/components/FiltersPanel.d.ts +0 -3
- package/dist/components/FiltersPanel.d.ts.map +0 -1
- package/dist/components/OrderTrackingTimeline.d.ts.map +0 -1
- package/dist/components/ProductCard.d.ts +0 -4
- package/dist/components/ProductCard.d.ts.map +0 -1
- package/dist/components/ProductEnquiryForm.d.ts.map +0 -1
- package/dist/components/ProductGallery.d.ts +0 -22
- package/dist/components/ProductGallery.d.ts.map +0 -1
- package/dist/components/ProductGrid.d.ts.map +0 -1
- package/dist/components/ProductOptionSelector.d.ts.map +0 -1
- package/dist/components/ProductSpecs.d.ts.map +0 -1
- package/dist/components/QuantityInput.d.ts.map +0 -1
- package/dist/components/RelatedProducts.d.ts.map +0 -1
- package/dist/components/SignInModal.d.ts.map +0 -1
- package/dist/components/StorefrontErrorBoundary.d.ts.map +0 -1
- package/dist/components/StorefrontFooter.d.ts.map +0 -1
- package/dist/components/StorefrontHeader.d.ts.map +0 -1
- package/dist/components/Toaster.d.ts.map +0 -1
- package/dist/components/sections/CategoryShowcase.d.ts +0 -6
- package/dist/components/sections/CategoryShowcase.d.ts.map +0 -1
- package/dist/components/sections/HeroSection.d.ts +0 -9
- package/dist/components/sections/HeroSection.d.ts.map +0 -1
- package/dist/components/sections/MediaCarousel.d.ts +0 -11
- package/dist/components/sections/MediaCarousel.d.ts.map +0 -1
- package/dist/components/sections/MiscSections.d.ts +0 -33
- package/dist/components/sections/MiscSections.d.ts.map +0 -1
- package/dist/components/sections/ProductRail.d.ts +0 -9
- package/dist/components/sections/ProductRail.d.ts.map +0 -1
- package/dist/components/sections/ProductSlider.d.ts +0 -15
- package/dist/components/sections/ProductSlider.d.ts.map +0 -1
- package/dist/config.d.ts +0 -174
- package/dist/config.d.ts.map +0 -1
- package/dist/createStorefrontApp.d.ts +0 -28
- package/dist/createStorefrontApp.d.ts.map +0 -1
- package/dist/define.d.ts.map +0 -1
- package/dist/format.d.ts.map +0 -1
- package/dist/hooks/useCategories.d.ts +0 -6
- package/dist/hooks/useCategories.d.ts.map +0 -1
- package/dist/hooks/useDiscountValidator.d.ts +0 -14
- package/dist/hooks/useDiscountValidator.d.ts.map +0 -1
- package/dist/hooks/useMyOrders.d.ts.map +0 -1
- package/dist/hooks/useProduct.d.ts +0 -7
- package/dist/hooks/useProduct.d.ts.map +0 -1
- package/dist/hooks/useProducts.d.ts.map +0 -1
- package/dist/hooks/useStorefront.d.ts +0 -35
- package/dist/hooks/useStorefront.d.ts.map +0 -1
- package/dist/hooks/useStorefrontHead.d.ts +0 -11
- package/dist/hooks/useStorefrontHead.d.ts.map +0 -1
- package/dist/motion.d.ts.map +0 -1
- package/dist/order-status.d.ts.map +0 -1
- package/dist/pages/CatalogPage.d.ts.map +0 -1
- package/dist/pages/CheckoutPage.d.ts.map +0 -1
- package/dist/pages/HomePage.d.ts +0 -9
- package/dist/pages/HomePage.d.ts.map +0 -1
- package/dist/pages/MyPurchasesPage.d.ts.map +0 -1
- package/dist/pages/NotFoundPage.d.ts.map +0 -1
- package/dist/pages/OrderConfirmationPage.d.ts.map +0 -1
- package/dist/pages/ProductDetailPage.d.ts.map +0 -1
- package/dist/presets.d.ts.map +0 -1
- package/dist/product-options.d.ts +0 -12
- package/dist/product-options.d.ts.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/scaffold.d.ts.map +0 -1
- package/dist/sections.d.ts.map +0 -1
- package/dist/testids.d.ts.map +0 -1
- package/dist/theme.d.ts.map +0 -1
- package/src/auth.ts +0 -227
- package/src/blocks.tsx +0 -51
- package/src/catalog.ts +0 -3
- package/src/component-contracts.ts +0 -154
- package/src/component-selectors.ts +0 -31
- package/src/components/CartDrawer.tsx +0 -279
- package/src/components/CustomerAccountShell.tsx +0 -102
- package/src/components/DeliveryEstimator.tsx +0 -157
- package/src/components/FiltersPanel.tsx +0 -109
- package/src/components/OrderTrackingTimeline.tsx +0 -135
- package/src/components/Price.tsx +0 -32
- package/src/components/ProductCard.tsx +0 -120
- package/src/components/ProductEnquiryForm.tsx +0 -175
- package/src/components/ProductGallery.tsx +0 -174
- package/src/components/ProductGrid.tsx +0 -43
- package/src/components/ProductOptionSelector.tsx +0 -50
- package/src/components/ProductSpecs.tsx +0 -75
- package/src/components/QuantityInput.tsx +0 -52
- package/src/components/RelatedProducts.tsx +0 -40
- package/src/components/SignInModal.tsx +0 -120
- package/src/components/SmoothImage.tsx +0 -59
- package/src/components/StorefrontErrorBoundary.tsx +0 -51
- package/src/components/StorefrontFooter.tsx +0 -110
- package/src/components/StorefrontHeader.tsx +0 -477
- package/src/components/Toaster.tsx +0 -60
- package/src/components/sections/CategoryShowcase.tsx +0 -80
- package/src/components/sections/HeroSection.tsx +0 -193
- package/src/components/sections/MediaCarousel.tsx +0 -141
- package/src/components/sections/MiscSections.tsx +0 -158
- package/src/components/sections/ProductRail.tsx +0 -81
- package/src/components/sections/ProductSlider.tsx +0 -119
- package/src/config.ts +0 -229
- package/src/createStorefrontApp.tsx +0 -247
- package/src/define.ts +0 -34
- package/src/format.ts +0 -7
- package/src/hooks/index.ts +0 -6
- package/src/hooks/useCategories.ts +0 -26
- package/src/hooks/useDiscountValidator.ts +0 -43
- package/src/hooks/useMyOrders.ts +0 -41
- package/src/hooks/useProduct.ts +0 -36
- package/src/hooks/useProducts.ts +0 -39
- package/src/hooks/useStorefront.ts +0 -58
- package/src/hooks/useStorefrontHead.ts +0 -25
- package/src/index.ts +0 -202
- package/src/motion.tsx +0 -94
- package/src/order-status.ts +0 -33
- package/src/pages/CatalogPage.tsx +0 -118
- package/src/pages/CheckoutPage.tsx +0 -689
- package/src/pages/HomePage.tsx +0 -16
- package/src/pages/MyPurchasesPage.tsx +0 -311
- package/src/pages/NotFoundPage.tsx +0 -25
- package/src/pages/OrderConfirmationPage.tsx +0 -190
- package/src/pages/PolicyPage.tsx +0 -47
- package/src/pages/ProductDetailPage.tsx +0 -264
- package/src/presets.ts +0 -324
- package/src/product-options.ts +0 -63
- package/src/router.ts +0 -49
- package/src/scaffold.tsx +0 -115
- package/src/sections.ts +0 -116
- package/src/shop.ts +0 -4
- package/src/stores/cart.store.ts +0 -185
- package/src/stores/catalog.store.ts +0 -39
- package/src/stores/delivery.store.ts +0 -152
- package/src/stores/session.store.ts +0 -28
- package/src/stores/toast.store.ts +0 -43
- package/src/testids.ts +0 -136
- package/src/theme.ts +0 -194
- package/src/ui.ts +0 -5
- package/src/workflows/checkout.ts +0 -119
- /package/dist/components/{SignInModal.d.ts → account/SignInModal.d.ts} +0 -0
- /package/dist/components/{CartDrawer.d.ts → cart/CartDrawer.d.ts} +0 -0
- /package/dist/components/{QuantityInput.d.ts → cart/QuantityInput.d.ts} +0 -0
- /package/dist/components/{DeliveryEstimator.d.ts → checkout/DeliveryEstimator.d.ts} +0 -0
- /package/dist/components/{ProductGrid.d.ts → collection/ProductGrid.d.ts} +0 -0
- /package/dist/components/{StorefrontErrorBoundary.d.ts → layout/StorefrontErrorBoundary.d.ts} +0 -0
- /package/dist/components/{StorefrontFooter.d.ts → layout/StorefrontFooter.d.ts} +0 -0
- /package/dist/components/{StorefrontHeader.d.ts → layout/StorefrontHeader.d.ts} +0 -0
- /package/dist/components/{Toaster.d.ts → layout/Toaster.d.ts} +0 -0
- /package/dist/components/{OrderTrackingTimeline.d.ts → order/OrderTrackingTimeline.d.ts} +0 -0
- /package/dist/components/{ProductEnquiryForm.d.ts → product/ProductEnquiryForm.d.ts} +0 -0
- /package/dist/components/{ProductSpecs.d.ts → product/ProductSpecs.d.ts} +0 -0
- /package/dist/components/{RelatedProducts.d.ts → product/RelatedProducts.d.ts} +0 -0
- /package/dist/{order-status.d.ts → data/order-status.d.ts} +0 -0
- /package/dist/{component-selectors.d.ts → extend/component-selectors.d.ts} +0 -0
- /package/dist/{format.d.ts → lib/format.d.ts} +0 -0
- /package/dist/{motion.d.ts → lib/motion.d.ts} +0 -0
- /package/dist/{pages → views}/CatalogPage.d.ts +0 -0
- /package/dist/{pages → views}/CheckoutPage.d.ts +0 -0
- /package/dist/{pages → views}/MyPurchasesPage.d.ts +0 -0
- /package/dist/{pages → views}/NotFoundPage.d.ts +0 -0
- /package/dist/{pages → views}/OrderConfirmationPage.d.ts +0 -0
- /package/dist/{pages → views}/PolicyPage.d.ts +0 -0
- /package/dist/{pages → views}/ProductDetailPage.d.ts +0 -0
|
@@ -1,689 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react'
|
|
2
|
-
import { CreditCard, Lock, PackageCheck, ShieldCheck } from 'lucide-react'
|
|
3
|
-
import { getShopProvider } from '@fayz-ai/shop/runtime'
|
|
4
|
-
import { formatPostalCode, normalizePostalCode, lookupPostalCode } from '@fayz-ai/core'
|
|
5
|
-
import type { CustomerAddress, PaymentMethodKind } from '@fayz-ai/shop/types'
|
|
6
|
-
import { prefersReducedMotion } from '../motion'
|
|
7
|
-
import {
|
|
8
|
-
useCartStore,
|
|
9
|
-
selectSubtotal,
|
|
10
|
-
selectDiscountTotal,
|
|
11
|
-
selectShipping,
|
|
12
|
-
selectTotal,
|
|
13
|
-
} from '../stores/cart.store'
|
|
14
|
-
import { useSessionStore } from '../stores/session.store'
|
|
15
|
-
import { useDeliveryStore } from '../stores/delivery.store'
|
|
16
|
-
import { useStorefrontConfig } from '../config'
|
|
17
|
-
import { Link, navigateTo } from '../router'
|
|
18
|
-
import { formatMoney } from '../format'
|
|
19
|
-
import { TID } from '../testids'
|
|
20
|
-
import { placeStorefrontOrder } from '../workflows/checkout'
|
|
21
|
-
import { useDiscountValidator } from '../hooks/useDiscountValidator'
|
|
22
|
-
import { toast } from '../stores/toast.store'
|
|
23
|
-
import { useStorefrontHead } from '../hooks/useStorefrontHead'
|
|
24
|
-
import { SignInModal } from '../components/SignInModal'
|
|
25
|
-
import { SmoothImage } from '../components/SmoothImage'
|
|
26
|
-
|
|
27
|
-
interface CheckoutForm {
|
|
28
|
-
email: string
|
|
29
|
-
name: string
|
|
30
|
-
street: string
|
|
31
|
-
city: string
|
|
32
|
-
zip: string
|
|
33
|
-
number: string
|
|
34
|
-
complement: string
|
|
35
|
-
district: string
|
|
36
|
-
state: string
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type AddressMode = 'saved' | 'new'
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* There is no payment service provider connected, so the checkout does not ask
|
|
43
|
-
* for a card number. It used to, prefilled with 4242 4242 4242 4242, and the
|
|
44
|
-
* digits were validated and then thrown away — a PAN typed into React state
|
|
45
|
-
* that reaches no processor is a liability with no upside. The buyer states how
|
|
46
|
-
* they intend to pay; the order stays `pending` until the merchant confirms the
|
|
47
|
-
* money arrived (shop_confirm_payment, which since 0019 only they can call).
|
|
48
|
-
*/
|
|
49
|
-
const PAYMENT_LABELS: Record<PaymentMethodKind, { label: string; hint: string }> = {
|
|
50
|
-
pix: { label: 'Pix', hint: 'Você recebe a chave para pagar após confirmar o pedido' },
|
|
51
|
-
credit_card: { label: 'Cartão de crédito', hint: 'Maquininha na entrega' },
|
|
52
|
-
debit_card: { label: 'Cartão de débito', hint: 'Maquininha na entrega' },
|
|
53
|
-
boleto: { label: 'Boleto', hint: 'Enviado por e-mail após a confirmação' },
|
|
54
|
-
cash: { label: 'Dinheiro', hint: 'Pagamento na entrega' },
|
|
55
|
-
other: { label: 'Combinar com a loja', hint: 'A loja entra em contato para acertar o pagamento' },
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const PROCESSING_STEPS = [
|
|
59
|
-
{ icon: Lock, label: 'Validando seus dados...' },
|
|
60
|
-
{ icon: PackageCheck, label: 'Registrando seu pedido...' },
|
|
61
|
-
]
|
|
62
|
-
|
|
63
|
-
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))
|
|
64
|
-
|
|
65
|
-
function ProcessingOverlay({ step }: { step: number }) {
|
|
66
|
-
const current = PROCESSING_STEPS[Math.min(step, PROCESSING_STEPS.length - 1)]!
|
|
67
|
-
const Icon = current.icon
|
|
68
|
-
return (
|
|
69
|
-
<div
|
|
70
|
-
data-testid={TID.checkoutProcessing}
|
|
71
|
-
className="fixed inset-0 z-50 flex animate-fade-in flex-col items-center justify-center bg-background/95 backdrop-blur-sm"
|
|
72
|
-
role="status"
|
|
73
|
-
aria-live="polite"
|
|
74
|
-
>
|
|
75
|
-
<div className="relative flex h-24 w-24 items-center justify-center">
|
|
76
|
-
<span className="absolute inset-0 animate-spin rounded-full border-[3px] border-muted border-t-primary" style={{ animationDuration: '1.1s' }} />
|
|
77
|
-
<Icon key={step} className="h-9 w-9 animate-pop-in text-primary" />
|
|
78
|
-
</div>
|
|
79
|
-
<p key={`label-${step}`} className="mt-6 animate-fade-up font-medium">{current.label}</p>
|
|
80
|
-
<p className="mt-8 text-xs text-muted-foreground">Pagamento seguro - não feche esta janela</p>
|
|
81
|
-
</div>
|
|
82
|
-
)
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function field(
|
|
86
|
-
label: string,
|
|
87
|
-
testId: string,
|
|
88
|
-
value: string,
|
|
89
|
-
onChange: (value: string) => void,
|
|
90
|
-
props?: React.InputHTMLAttributes<HTMLInputElement>,
|
|
91
|
-
) {
|
|
92
|
-
return (
|
|
93
|
-
<label className="block">
|
|
94
|
-
<span className="sr-only">{label}</span>
|
|
95
|
-
<input
|
|
96
|
-
data-testid={testId}
|
|
97
|
-
aria-label={label}
|
|
98
|
-
value={value}
|
|
99
|
-
onChange={(event) => onChange(event.target.value)}
|
|
100
|
-
className="w-full rounded-lg border bg-background px-3.5 py-3 text-sm outline-none transition focus:border-primary focus:ring-2 focus:ring-primary/10"
|
|
101
|
-
{...props}
|
|
102
|
-
/>
|
|
103
|
-
</label>
|
|
104
|
-
)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export function CheckoutPage() {
|
|
108
|
-
const config = useStorefrontConfig()
|
|
109
|
-
const cart = useCartStore()
|
|
110
|
-
const session = useSessionStore()
|
|
111
|
-
const delivery = useDeliveryStore()
|
|
112
|
-
const validateDiscount = useDiscountValidator()
|
|
113
|
-
useStorefrontHead({ title: `Checkout — ${config.name}` })
|
|
114
|
-
|
|
115
|
-
const paymentMethods = config.payments.methods
|
|
116
|
-
const [selectedAddressId, setSelectedAddressId] = useState('new')
|
|
117
|
-
const [savedAddresses, setSavedAddresses] = useState<CustomerAddress[]>([])
|
|
118
|
-
// 'new' until the address book actually loads: an empty book must show an
|
|
119
|
-
// empty form, never a placeholder address belonging to nobody.
|
|
120
|
-
const [addressMode, setAddressMode] = useState<AddressMode>('new')
|
|
121
|
-
const [paymentMethod, setPaymentMethod] = useState<PaymentMethodKind>(paymentMethods[0] ?? 'pix')
|
|
122
|
-
const [form, setForm] = useState<CheckoutForm>({
|
|
123
|
-
email: session.email ?? '',
|
|
124
|
-
name: session.name ?? '',
|
|
125
|
-
street: '',
|
|
126
|
-
city: '',
|
|
127
|
-
zip: '',
|
|
128
|
-
number: '',
|
|
129
|
-
complement: '',
|
|
130
|
-
district: '',
|
|
131
|
-
state: '',
|
|
132
|
-
})
|
|
133
|
-
const [error, setError] = useState<string | null>(null)
|
|
134
|
-
const [discountCode, setDiscountCode] = useState('')
|
|
135
|
-
const [discountError, setDiscountError] = useState<string | null>(null)
|
|
136
|
-
const [discountSuccess, setDiscountSuccess] = useState<string | null>(null)
|
|
137
|
-
const [applyingDiscount, setApplyingDiscount] = useState(false)
|
|
138
|
-
const [placing, setPlacing] = useState(false)
|
|
139
|
-
const [processingStep, setProcessingStep] = useState(0)
|
|
140
|
-
const [showSignin, setShowSignin] = useState(false)
|
|
141
|
-
const [zipLookup, setZipLookup] = useState<'idle' | 'loading' | 'found' | 'not-found'>('idle')
|
|
142
|
-
|
|
143
|
-
const subtotal = selectSubtotal(cart)
|
|
144
|
-
const discountTotal = selectDiscountTotal(cart)
|
|
145
|
-
const shipping = selectShipping(cart, config)
|
|
146
|
-
const total = selectTotal(cart, config)
|
|
147
|
-
const money = (value: number) => formatMoney(value, config.currency, config.locale)
|
|
148
|
-
|
|
149
|
-
useEffect(() => {
|
|
150
|
-
if (cart.lines.length === 0 && !placing) navigateTo(config.catalogPath)
|
|
151
|
-
}, [cart.lines.length, config.catalogPath, placing])
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const set = (key: keyof CheckoutForm) => (value: string) => setForm((current) => ({ ...current, [key]: value }))
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Editing the CEP re-fetches the address and refills what the postal service
|
|
158
|
-
* knows, leaving number and complement alone — those are the buyer's and must
|
|
159
|
-
* survive a correction to the postcode.
|
|
160
|
-
*
|
|
161
|
-
* Only what the lookup can supply is overwritten. Silently keeping a stale
|
|
162
|
-
* street from a previous CEP is worse than an empty one: the parcel goes to
|
|
163
|
-
* the wrong place and everything on screen looks filled in.
|
|
164
|
-
*/
|
|
165
|
-
async function setZip(value: string) {
|
|
166
|
-
const masked = formatPostalCode(value)
|
|
167
|
-
setForm((current) => ({ ...current, zip: masked }))
|
|
168
|
-
if (normalizePostalCode(masked).length !== 8) {
|
|
169
|
-
setZipLookup('idle')
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
setZipLookup('loading')
|
|
173
|
-
try {
|
|
174
|
-
const found = await lookupPostalCode(masked)
|
|
175
|
-
if (!found) {
|
|
176
|
-
setZipLookup('not-found')
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
setZipLookup('found')
|
|
180
|
-
setForm((current) => ({
|
|
181
|
-
...current,
|
|
182
|
-
street: found.street || current.street,
|
|
183
|
-
district: found.district || current.district,
|
|
184
|
-
city: found.city,
|
|
185
|
-
state: found.state,
|
|
186
|
-
}))
|
|
187
|
-
} catch {
|
|
188
|
-
// Offline or the provider is down — the buyer can still type it out.
|
|
189
|
-
setZipLookup('idle')
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
async function applyDiscountCode() {
|
|
194
|
-
setApplyingDiscount(true)
|
|
195
|
-
setDiscountError(null)
|
|
196
|
-
setDiscountSuccess(null)
|
|
197
|
-
try {
|
|
198
|
-
const appliedCode = discountCode.trim().toUpperCase()
|
|
199
|
-
const result = await validateDiscount(discountCode)
|
|
200
|
-
if (result.valid) {
|
|
201
|
-
cart.applyDiscount(appliedCode, result.percent)
|
|
202
|
-
setDiscountCode(appliedCode)
|
|
203
|
-
setDiscountSuccess(`${appliedCode} aplicado: ${result.percent}% de desconto.`)
|
|
204
|
-
toast.success('Cupom aplicado!', `${appliedCode} • ${result.percent}% de desconto`)
|
|
205
|
-
} else {
|
|
206
|
-
setDiscountError(result.message ?? 'Cupom inválido.')
|
|
207
|
-
toast.error('Cupom inválido', result.message ?? 'Verifique o código e tente novamente.')
|
|
208
|
-
}
|
|
209
|
-
} finally {
|
|
210
|
-
setApplyingDiscount(false)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function clearDiscountCode() {
|
|
215
|
-
cart.clearDiscount()
|
|
216
|
-
setDiscountCode('')
|
|
217
|
-
setDiscountSuccess(null)
|
|
218
|
-
setDiscountError(null)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// The signed-in shopper's real address book. RLS scopes it to them, so a
|
|
222
|
-
// guest (or a customer whose account was never linked to an auth identity)
|
|
223
|
-
// simply gets nothing back and types their address.
|
|
224
|
-
useEffect(() => {
|
|
225
|
-
const customerId = session.customerId
|
|
226
|
-
if (!customerId) {
|
|
227
|
-
setSavedAddresses([])
|
|
228
|
-
setAddressMode('new')
|
|
229
|
-
return
|
|
230
|
-
}
|
|
231
|
-
let cancelled = false
|
|
232
|
-
const provider = getShopProvider()
|
|
233
|
-
void provider
|
|
234
|
-
.listCustomerAddresses?.(customerId)
|
|
235
|
-
.then((addresses) => {
|
|
236
|
-
if (cancelled || addresses.length === 0) return
|
|
237
|
-
setSavedAddresses(addresses)
|
|
238
|
-
applySavedAddress(addresses[0]!)
|
|
239
|
-
})
|
|
240
|
-
.catch(() => {})
|
|
241
|
-
return () => {
|
|
242
|
-
cancelled = true
|
|
243
|
-
}
|
|
244
|
-
}, [session.customerId])
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* The CEP given back on the product page lands here.
|
|
248
|
-
*
|
|
249
|
-
* This is the payoff of the whole delivery flow: street, district, city and
|
|
250
|
-
* UF arrive filled and the buyer types a house number. It only fires when no
|
|
251
|
-
* saved address was selected — a signed-in shopper's own address book still
|
|
252
|
-
* wins over a postcode lookup, because it also knows their number and
|
|
253
|
-
* complement, which no postal service can supply.
|
|
254
|
-
*/
|
|
255
|
-
useEffect(() => {
|
|
256
|
-
if (addressMode !== 'new') return
|
|
257
|
-
const looked = delivery.address
|
|
258
|
-
if (!looked) return
|
|
259
|
-
setForm((current) => {
|
|
260
|
-
if (current.zip.trim() || current.street.trim()) return current
|
|
261
|
-
return {
|
|
262
|
-
...current,
|
|
263
|
-
zip: formatPostalCode(looked.postalCode),
|
|
264
|
-
street: looked.street,
|
|
265
|
-
district: looked.district,
|
|
266
|
-
city: looked.city,
|
|
267
|
-
state: looked.state,
|
|
268
|
-
}
|
|
269
|
-
})
|
|
270
|
-
}, [delivery.address, addressMode])
|
|
271
|
-
|
|
272
|
-
function applySavedAddress(address: CustomerAddress) {
|
|
273
|
-
setAddressMode('saved')
|
|
274
|
-
setSelectedAddressId(address.id)
|
|
275
|
-
setForm((current) => ({
|
|
276
|
-
...current,
|
|
277
|
-
street: address.street,
|
|
278
|
-
city: address.city,
|
|
279
|
-
zip: address.postalCode,
|
|
280
|
-
number: address.number ?? '',
|
|
281
|
-
complement: address.complement ?? '',
|
|
282
|
-
district: address.district ?? '',
|
|
283
|
-
state: address.state,
|
|
284
|
-
}))
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function selectAddress(addressId: string) {
|
|
288
|
-
const address = savedAddresses.find((item) => item.id === addressId)
|
|
289
|
-
if (address) applySavedAddress(address)
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function addNewAddress() {
|
|
293
|
-
setAddressMode('new')
|
|
294
|
-
setSelectedAddressId('new')
|
|
295
|
-
setForm((current) => ({ ...current, street: '', city: '', zip: '', number: '', complement: '', district: '', state: '' }))
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
function validate(): boolean {
|
|
299
|
-
setError(null)
|
|
300
|
-
if (!form.email.trim() || !/.+@.+\..+/.test(form.email)) {
|
|
301
|
-
setError('Informe um e-mail válido.')
|
|
302
|
-
return false
|
|
303
|
-
}
|
|
304
|
-
if (!form.name.trim()) {
|
|
305
|
-
setError('Informe seu nome completo.')
|
|
306
|
-
return false
|
|
307
|
-
}
|
|
308
|
-
// Number and district are required because a courier cannot deliver without
|
|
309
|
-
// them; the old form collected only street/city/CEP and logistics had to guess.
|
|
310
|
-
if (!form.street.trim() || !form.city.trim() || !form.zip.trim() || !form.district.trim()) {
|
|
311
|
-
setError('Preencha ou selecione o endereço de entrega.')
|
|
312
|
-
return false
|
|
313
|
-
}
|
|
314
|
-
// Called out on its own: with the CEP filling everything else, the number is
|
|
315
|
-
// usually the only thing missing, and "preencha o endereço" sent people
|
|
316
|
-
// hunting through fields that were already correct.
|
|
317
|
-
if (!form.number.trim()) {
|
|
318
|
-
setError('Falta o número do endereço.')
|
|
319
|
-
return false
|
|
320
|
-
}
|
|
321
|
-
// UF is required now: 117 of the 266 addresses already in the pool have no
|
|
322
|
-
// state, and a carrier cannot quote or ship without one.
|
|
323
|
-
if (form.state.trim().length !== 2) {
|
|
324
|
-
setError('Informe a UF com duas letras (ex.: RJ).')
|
|
325
|
-
return false
|
|
326
|
-
}
|
|
327
|
-
// Coverage. shop_place_order refuses an unserved postal code anyway (0021),
|
|
328
|
-
// so this only turns a raw SQL error into a sentence the buyer can act on —
|
|
329
|
-
// the rule itself lives on the server, not here.
|
|
330
|
-
if (delivery.status === 'unserved' && delivery.postalCode === normalizePostalCode(form.zip)) {
|
|
331
|
-
setError('Ainda não entregamos nesse CEP. Tente outro endereço.')
|
|
332
|
-
return false
|
|
333
|
-
}
|
|
334
|
-
return true
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
async function placeOrder() {
|
|
338
|
-
if (!validate()) return
|
|
339
|
-
|
|
340
|
-
setPlacing(true)
|
|
341
|
-
setProcessingStep(0)
|
|
342
|
-
const stepMs = prefersReducedMotion() ? 120 : 600
|
|
343
|
-
const stepTimers = [
|
|
344
|
-
setTimeout(() => setProcessingStep(1), stepMs),
|
|
345
|
-
setTimeout(() => setProcessingStep(2), stepMs * 2),
|
|
346
|
-
]
|
|
347
|
-
const minDuration = sleep(stepMs * 2)
|
|
348
|
-
try {
|
|
349
|
-
const { order, customerId } = await placeStorefrontOrder({
|
|
350
|
-
config,
|
|
351
|
-
cart,
|
|
352
|
-
session,
|
|
353
|
-
customer: { email: form.email, name: form.name },
|
|
354
|
-
address: { street: form.street, city: form.city, zip: form.zip },
|
|
355
|
-
shippingAddress: {
|
|
356
|
-
postalCode: form.zip.trim(),
|
|
357
|
-
street: form.street.trim(),
|
|
358
|
-
number: form.number.trim() || undefined,
|
|
359
|
-
complement: form.complement.trim() || undefined,
|
|
360
|
-
district: form.district.trim() || undefined,
|
|
361
|
-
city: form.city.trim(),
|
|
362
|
-
state: form.state.trim().toUpperCase(),
|
|
363
|
-
},
|
|
364
|
-
// The method the buyer actually chose, which opens the ledger row in
|
|
365
|
-
// public.transactions with the right kind.
|
|
366
|
-
paymentMethod,
|
|
367
|
-
// Never marked paid from the browser. It used to be, through an RPC that
|
|
368
|
-
// was granted to anon — so any buyer holding their own order id could
|
|
369
|
-
// declare it settled. The order is now born `pending` and only the
|
|
370
|
-
// merchant (or a PSP webhook) can confirm the money arrived.
|
|
371
|
-
})
|
|
372
|
-
// placeStorefrontOrder resolves/creates the customer record but doesn't persist
|
|
373
|
-
// it — without this, "My purchases" right after checkout queries by email, which
|
|
374
|
-
// anon can't read under RLS, and silently comes back empty.
|
|
375
|
-
if (customerId) session.setSession({ customerId, email: form.email, name: form.name })
|
|
376
|
-
|
|
377
|
-
await minDuration
|
|
378
|
-
cart.clear()
|
|
379
|
-
navigateTo(`/order/${order.id}`)
|
|
380
|
-
} catch (error) {
|
|
381
|
-
setError(error instanceof Error ? error.message : 'Não foi possível concluir o pedido.')
|
|
382
|
-
setPlacing(false)
|
|
383
|
-
} finally {
|
|
384
|
-
stepTimers.forEach(clearTimeout)
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
const primaryCta = placing ? 'Processando...' : `Comprar agora - ${money(total)}`
|
|
389
|
-
|
|
390
|
-
return (
|
|
391
|
-
<main className="min-h-screen bg-background">
|
|
392
|
-
{placing && <ProcessingOverlay step={processingStep} />}
|
|
393
|
-
{showSignin && !session.email && (
|
|
394
|
-
<SignInModal
|
|
395
|
-
defaultEmail={form.email}
|
|
396
|
-
onClose={() => setShowSignin(false)}
|
|
397
|
-
onSignedIn={(signed) =>
|
|
398
|
-
setForm((current) => ({ ...current, email: signed.email ?? current.email, name: signed.name || current.name }))
|
|
399
|
-
}
|
|
400
|
-
/>
|
|
401
|
-
)}
|
|
402
|
-
<header className="border-b bg-background">
|
|
403
|
-
<div className="mx-auto flex max-w-6xl items-center justify-between gap-4 px-4 py-6 sm:px-6">
|
|
404
|
-
<Link to={config.catalogPath} className="text-sm text-muted-foreground hover:text-foreground">
|
|
405
|
-
Voltar à loja
|
|
406
|
-
</Link>
|
|
407
|
-
<div className="text-center">
|
|
408
|
-
<div className="sf-heading text-2xl font-bold tracking-tight">{config.logo ?? config.name}</div>
|
|
409
|
-
<div className="mt-2 flex items-center justify-center gap-2 text-xs text-muted-foreground">
|
|
410
|
-
<Lock className="h-3.5 w-3.5" />
|
|
411
|
-
Checkout seguro
|
|
412
|
-
</div>
|
|
413
|
-
</div>
|
|
414
|
-
<a href="mailto:suporte@example.com" className="text-sm text-muted-foreground hover:text-foreground">
|
|
415
|
-
Ajuda
|
|
416
|
-
</a>
|
|
417
|
-
</div>
|
|
418
|
-
</header>
|
|
419
|
-
|
|
420
|
-
<div className="mx-auto grid max-w-6xl grid-cols-1 gap-8 px-4 py-8 sm:px-6 lg:grid-cols-5 lg:px-6">
|
|
421
|
-
<section className="lg:col-span-3 lg:pr-10">
|
|
422
|
-
<nav className="mb-8 flex flex-wrap items-center gap-2 text-sm text-muted-foreground" aria-label="Checkout steps">
|
|
423
|
-
<span className="font-medium text-foreground">Informações</span>
|
|
424
|
-
<span>/</span>
|
|
425
|
-
<span>Entrega</span>
|
|
426
|
-
<span>/</span>
|
|
427
|
-
<span>Pagamento</span>
|
|
428
|
-
</nav>
|
|
429
|
-
|
|
430
|
-
<div className="space-y-8">
|
|
431
|
-
<section>
|
|
432
|
-
<div className="mb-3 flex items-center justify-between gap-4">
|
|
433
|
-
<h1 className="text-xl font-semibold tracking-tight">Informações de contato</h1>
|
|
434
|
-
{session.email ? (
|
|
435
|
-
<span className="text-sm text-muted-foreground">Conectado como {session.email}</span>
|
|
436
|
-
) : (
|
|
437
|
-
<button
|
|
438
|
-
type="button"
|
|
439
|
-
onClick={() => setShowSignin((value) => !value)}
|
|
440
|
-
className="text-sm font-medium text-primary hover:underline"
|
|
441
|
-
>
|
|
442
|
-
Já tem conta? Entrar
|
|
443
|
-
</button>
|
|
444
|
-
)}
|
|
445
|
-
</div>
|
|
446
|
-
<div className="grid gap-3 sm:grid-cols-2">
|
|
447
|
-
{field('E-mail', TID.checkoutEmail, form.email, set('email'), {
|
|
448
|
-
type: 'email',
|
|
449
|
-
placeholder: 'E-mail ou telefone',
|
|
450
|
-
})}
|
|
451
|
-
{field('Nome completo', TID.checkoutName, form.name, set('name'), {
|
|
452
|
-
placeholder: 'Nome completo',
|
|
453
|
-
})}
|
|
454
|
-
</div>
|
|
455
|
-
</section>
|
|
456
|
-
|
|
457
|
-
<section>
|
|
458
|
-
<h2 className="mb-3 text-xl font-semibold tracking-tight">Endereço de entrega</h2>
|
|
459
|
-
<div className="mb-3 grid gap-3">
|
|
460
|
-
{savedAddresses.map((address) => (
|
|
461
|
-
<div
|
|
462
|
-
key={address.id}
|
|
463
|
-
className={`group relative rounded-lg border p-3 pr-11 text-left text-sm transition hover:bg-muted/40 ${
|
|
464
|
-
selectedAddressId === address.id ? 'border-primary bg-primary/5' : 'border-border bg-background'
|
|
465
|
-
}`}
|
|
466
|
-
>
|
|
467
|
-
<button type="button" onClick={() => selectAddress(address.id)} className="block w-full text-left">
|
|
468
|
-
<span className="flex items-center justify-between gap-3 font-semibold">
|
|
469
|
-
{address.label}
|
|
470
|
-
{addressMode === 'saved' && selectedAddressId === address.id && (
|
|
471
|
-
<span className="rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary">Selecionado</span>
|
|
472
|
-
)}
|
|
473
|
-
</span>
|
|
474
|
-
<span className="mt-1 block text-muted-foreground">
|
|
475
|
-
{[address.street, address.number].filter(Boolean).join(', ')}
|
|
476
|
-
{address.complement ? ` — ${address.complement}` : ''}
|
|
477
|
-
</span>
|
|
478
|
-
<span className="block text-muted-foreground">
|
|
479
|
-
{[address.district, address.city, address.state].filter(Boolean).join(' · ')} — {address.postalCode}
|
|
480
|
-
</span>
|
|
481
|
-
</button>
|
|
482
|
-
</div>
|
|
483
|
-
))}
|
|
484
|
-
<button
|
|
485
|
-
type="button"
|
|
486
|
-
onClick={addNewAddress}
|
|
487
|
-
className={`rounded-lg border border-dashed p-3 text-left text-sm transition hover:bg-muted/40 ${
|
|
488
|
-
addressMode === 'new' ? 'border-primary bg-primary/5' : 'border-border bg-muted/20'
|
|
489
|
-
}`}
|
|
490
|
-
>
|
|
491
|
-
<span className="block font-semibold text-primary">
|
|
492
|
-
{savedAddresses.length > 0 ? '+ Adicionar novo endereço' : 'Informe o endereço de entrega'}
|
|
493
|
-
</span>
|
|
494
|
-
<span className="mt-1 block text-muted-foreground">Preencher outro endereço para esta compra</span>
|
|
495
|
-
</button>
|
|
496
|
-
</div>
|
|
497
|
-
{addressMode === 'new' && (
|
|
498
|
-
<div className="grid gap-3">
|
|
499
|
-
{/* CEP first, because it FILLS the fields below it. With it at
|
|
500
|
-
the bottom the buyer typed street, district, city and UF by
|
|
501
|
-
hand and only then reached the one field that would have
|
|
502
|
-
supplied all four — and editing it later has to refill
|
|
503
|
-
them, which reads as the form rewriting itself. */}
|
|
504
|
-
<div className="grid gap-1 sm:grid-cols-[200px_1fr] sm:items-center">
|
|
505
|
-
{field('CEP', TID.checkoutZip, form.zip, setZip, {
|
|
506
|
-
placeholder: 'CEP',
|
|
507
|
-
inputMode: 'numeric',
|
|
508
|
-
autoComplete: 'postal-code',
|
|
509
|
-
})}
|
|
510
|
-
<p className="px-1 text-xs text-muted-foreground">
|
|
511
|
-
{zipLookup === 'loading' ? 'Buscando endereço…'
|
|
512
|
-
: zipLookup === 'not-found' ? 'CEP não encontrado — preencha o endereço à mão.'
|
|
513
|
-
: 'Preenchemos o endereço para você.'}
|
|
514
|
-
</p>
|
|
515
|
-
</div>
|
|
516
|
-
{field('Endereço', TID.checkoutStreet, form.street, set('street'), { placeholder: 'Rua, avenida…' })}
|
|
517
|
-
<div className="grid grid-cols-2 gap-3">
|
|
518
|
-
{field('Número', 'checkout-number', form.number, set('number'), { placeholder: 'Número' })}
|
|
519
|
-
{field('Complemento', 'checkout-complement', form.complement, set('complement'), { placeholder: 'Apto, bloco (opcional)' })}
|
|
520
|
-
</div>
|
|
521
|
-
<div className="grid grid-cols-2 gap-3">
|
|
522
|
-
{field('Bairro', 'checkout-district', form.district, set('district'), { placeholder: 'Bairro' })}
|
|
523
|
-
{field('UF', 'checkout-state', form.state, set('state'), { placeholder: 'UF' })}
|
|
524
|
-
</div>
|
|
525
|
-
{field('Cidade', TID.checkoutCity, form.city, set('city'), { placeholder: 'Cidade' })}
|
|
526
|
-
</div>
|
|
527
|
-
)}
|
|
528
|
-
</section>
|
|
529
|
-
|
|
530
|
-
<section>
|
|
531
|
-
<h2 className="mb-3 text-xl font-semibold tracking-tight">Pagamento</h2>
|
|
532
|
-
<div className="mb-3 grid gap-3" role="radiogroup" aria-label="Forma de pagamento">
|
|
533
|
-
{paymentMethods.map((method) => {
|
|
534
|
-
const copy = PAYMENT_LABELS[method]
|
|
535
|
-
const selected = paymentMethod === method
|
|
536
|
-
return (
|
|
537
|
-
<button
|
|
538
|
-
key={method}
|
|
539
|
-
type="button"
|
|
540
|
-
role="radio"
|
|
541
|
-
aria-checked={selected}
|
|
542
|
-
data-testid={`checkout-payment-${method}`}
|
|
543
|
-
onClick={() => setPaymentMethod(method)}
|
|
544
|
-
className={`rounded-lg border p-3 text-left text-sm transition hover:bg-muted/40 ${
|
|
545
|
-
selected ? 'border-primary bg-primary/5' : 'border-border bg-background'
|
|
546
|
-
}`}
|
|
547
|
-
>
|
|
548
|
-
<span className="flex items-center justify-between gap-3 font-semibold">
|
|
549
|
-
<span className="flex items-center gap-2"><CreditCard className="h-4 w-4" />{copy.label}</span>
|
|
550
|
-
{selected && (
|
|
551
|
-
<span className="rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-bold text-primary">Selecionado</span>
|
|
552
|
-
)}
|
|
553
|
-
</span>
|
|
554
|
-
<span className="mt-1 block text-muted-foreground">{copy.hint}</span>
|
|
555
|
-
</button>
|
|
556
|
-
)
|
|
557
|
-
})}
|
|
558
|
-
</div>
|
|
559
|
-
<p className="mt-2 text-xs text-muted-foreground">
|
|
560
|
-
Nenhuma cobrança é feita agora. Seu pedido é registrado e a loja confirma o pagamento.
|
|
561
|
-
</p>
|
|
562
|
-
</section>
|
|
563
|
-
|
|
564
|
-
{error && (
|
|
565
|
-
<p data-testid={TID.checkoutError} className="rounded-lg border border-destructive/30 bg-destructive/5 px-3 py-2 text-sm text-destructive">
|
|
566
|
-
{error}
|
|
567
|
-
</p>
|
|
568
|
-
)}
|
|
569
|
-
|
|
570
|
-
</div>
|
|
571
|
-
|
|
572
|
-
<footer className="mt-10 flex flex-wrap gap-4 border-t pt-5 text-xs text-muted-foreground">
|
|
573
|
-
<a href="#/privacy" className="hover:text-foreground">Privacidade</a>
|
|
574
|
-
<a href="#/terms" className="hover:text-foreground">Termos</a>
|
|
575
|
-
<a href="#/refunds" className="hover:text-foreground">Trocas e devoluções</a>
|
|
576
|
-
</footer>
|
|
577
|
-
</section>
|
|
578
|
-
|
|
579
|
-
<aside className="rounded-xl border bg-muted/20 p-5 lg:sticky lg:top-6 lg:col-span-2 lg:self-start">
|
|
580
|
-
<h2 className="sr-only">Resumo do pedido</h2>
|
|
581
|
-
<ul className="space-y-4">
|
|
582
|
-
{cart.lines.map((line) => (
|
|
583
|
-
<li key={line.lineId ?? line.productId} className="flex gap-3 text-sm">
|
|
584
|
-
<div className="relative h-16 w-16 flex-none overflow-hidden rounded-lg border bg-background">
|
|
585
|
-
{line.imageUrl && <SmoothImage src={line.imageUrl} alt={line.name} className="h-full w-full object-cover" />}
|
|
586
|
-
<span className="absolute -right-1 -top-1 flex h-5 min-w-5 items-center justify-center rounded-full bg-muted-foreground px-1.5 text-[10px] font-bold text-background">
|
|
587
|
-
{line.quantity}
|
|
588
|
-
</span>
|
|
589
|
-
</div>
|
|
590
|
-
<div className="min-w-0 flex-1">
|
|
591
|
-
<p className="truncate font-medium">{line.name}</p>
|
|
592
|
-
{line.sku && <p className="text-xs text-muted-foreground">{line.sku}</p>}
|
|
593
|
-
{line.optionsLabel && <p className="text-xs text-muted-foreground">{line.optionsLabel}</p>}
|
|
594
|
-
</div>
|
|
595
|
-
<span data-price={(line.unitPrice * line.quantity).toFixed(2)}>
|
|
596
|
-
{money(line.unitPrice * line.quantity)}
|
|
597
|
-
</span>
|
|
598
|
-
</li>
|
|
599
|
-
))}
|
|
600
|
-
</ul>
|
|
601
|
-
{config.features.discounts && (
|
|
602
|
-
<div className="mt-5 space-y-2 border-t pt-5">
|
|
603
|
-
<div className="flex gap-2">
|
|
604
|
-
<input
|
|
605
|
-
data-testid={TID.discountInput}
|
|
606
|
-
type="text"
|
|
607
|
-
placeholder="Cupom de desconto"
|
|
608
|
-
value={discountCode}
|
|
609
|
-
onChange={(event) => {
|
|
610
|
-
setDiscountCode(event.target.value)
|
|
611
|
-
setDiscountSuccess(null)
|
|
612
|
-
setDiscountError(null)
|
|
613
|
-
}}
|
|
614
|
-
className="min-w-0 flex-1 rounded-lg border bg-background px-3 py-2 text-sm uppercase"
|
|
615
|
-
/>
|
|
616
|
-
<button
|
|
617
|
-
type="button"
|
|
618
|
-
data-testid={TID.discountApply}
|
|
619
|
-
onClick={applyDiscountCode}
|
|
620
|
-
disabled={applyingDiscount || !discountCode.trim()}
|
|
621
|
-
className="rounded-lg border px-3 py-2 text-sm font-semibold hover:bg-muted disabled:opacity-50"
|
|
622
|
-
>
|
|
623
|
-
Aplicar
|
|
624
|
-
</button>
|
|
625
|
-
</div>
|
|
626
|
-
{discountError && (
|
|
627
|
-
<p data-testid={TID.discountError} className="text-xs text-destructive">
|
|
628
|
-
{discountError}
|
|
629
|
-
</p>
|
|
630
|
-
)}
|
|
631
|
-
{discountSuccess && (
|
|
632
|
-
<p className="text-xs font-semibold text-emerald-700">
|
|
633
|
-
{discountSuccess}
|
|
634
|
-
</p>
|
|
635
|
-
)}
|
|
636
|
-
</div>
|
|
637
|
-
)}
|
|
638
|
-
<dl className="mt-8 space-y-3 border-t pt-6 text-sm">
|
|
639
|
-
<div className="flex justify-between">
|
|
640
|
-
<dt className="text-muted-foreground">Subtotal</dt>
|
|
641
|
-
<dd data-price={subtotal.toFixed(2)}>{money(subtotal)}</dd>
|
|
642
|
-
</div>
|
|
643
|
-
{discountTotal > 0 && (
|
|
644
|
-
<div className="flex justify-between text-emerald-700" data-testid={TID.discountRow}>
|
|
645
|
-
<dt>
|
|
646
|
-
Desconto{' '}
|
|
647
|
-
<button
|
|
648
|
-
type="button"
|
|
649
|
-
onClick={clearDiscountCode}
|
|
650
|
-
className="text-xs text-muted-foreground underline"
|
|
651
|
-
>
|
|
652
|
-
({cart.discountCode} ×)
|
|
653
|
-
</button>
|
|
654
|
-
</dt>
|
|
655
|
-
<dd data-price={discountTotal.toFixed(2)}>-{money(discountTotal)}</dd>
|
|
656
|
-
</div>
|
|
657
|
-
)}
|
|
658
|
-
<div className="flex justify-between">
|
|
659
|
-
<dt className="text-muted-foreground">Entrega</dt>
|
|
660
|
-
<dd data-price={shipping.toFixed(2)}>{shipping === 0 ? 'Grátis' : money(shipping)}</dd>
|
|
661
|
-
</div>
|
|
662
|
-
<div className="flex justify-between border-t pt-5 text-lg font-bold">
|
|
663
|
-
<dt>Total</dt>
|
|
664
|
-
<dd data-price={total.toFixed(2)}>{money(total)}</dd>
|
|
665
|
-
</div>
|
|
666
|
-
</dl>
|
|
667
|
-
<div className="mt-6 flex items-center gap-2 text-xs text-muted-foreground">
|
|
668
|
-
<ShieldCheck className="h-4 w-4 text-primary" />
|
|
669
|
-
Compra protegida e dados criptografados.
|
|
670
|
-
</div>
|
|
671
|
-
<div className="mt-6 grid gap-3 border-t pt-5">
|
|
672
|
-
<button
|
|
673
|
-
type="button"
|
|
674
|
-
data-testid={TID.placeOrder}
|
|
675
|
-
onClick={placeOrder}
|
|
676
|
-
disabled={placing}
|
|
677
|
-
className="rounded-lg bg-primary px-6 py-4 text-sm font-bold text-primary-foreground transition hover:opacity-90 disabled:opacity-60"
|
|
678
|
-
>
|
|
679
|
-
{primaryCta}
|
|
680
|
-
</button>
|
|
681
|
-
<Link to={config.catalogPath} className="text-center text-sm text-muted-foreground hover:text-foreground">
|
|
682
|
-
Voltar ao carrinho
|
|
683
|
-
</Link>
|
|
684
|
-
</div>
|
|
685
|
-
</aside>
|
|
686
|
-
</div>
|
|
687
|
-
</main>
|
|
688
|
-
)
|
|
689
|
-
}
|