@fayz-ai/storefront 0.9.0 → 0.10.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-6B7H3IZ6.js +10232 -0
- package/dist/chunk-6B7H3IZ6.js.map +1 -0
- package/dist/chunk-6IXTENLB.js +162 -0
- package/dist/chunk-6IXTENLB.js.map +1 -0
- package/dist/components/PaymentTerms.d.ts +34 -0
- package/dist/components/PaymentTerms.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.map +1 -0
- package/dist/components/account/SignInModal.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/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/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 +56 -0
- package/dist/data/loaders.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/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/{config.d.ts → document/config.d.ts} +126 -7
- 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/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 +29 -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 +123 -54
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1311 -5012
- package/dist/index.js.map +1 -1
- 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} +21 -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 +6 -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/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/{pages → views}/OrderConfirmationPage.d.ts.map +1 -1
- 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.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.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/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/{CustomerAccountShell.d.ts → account/CustomerAccountShell.d.ts} +0 -0
- /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
package/src/router.ts
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useState } from 'react'
|
|
2
|
-
import { hashRouterAdapter } from '@fayz-ai/core'
|
|
3
|
-
|
|
4
|
-
// Minimal hash router for the storefront. Deliberately not react-router:
|
|
5
|
-
// an SDK package shipping a router dependency creates singleton hazards
|
|
6
|
-
// under source-alias dev setups, and six routes don't justify one.
|
|
7
|
-
|
|
8
|
-
const adapter = hashRouterAdapter()
|
|
9
|
-
|
|
10
|
-
export function navigateTo(to: string): void {
|
|
11
|
-
adapter.navigate(to)
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function useHashPath(): string {
|
|
15
|
-
const [path, setPath] = useState(() => adapter.getCurrentPath() || '/')
|
|
16
|
-
useEffect(() => {
|
|
17
|
-
const unsub = adapter.onPathChange((p) => {
|
|
18
|
-
setPath(p || '/')
|
|
19
|
-
window.scrollTo(0, 0)
|
|
20
|
-
})
|
|
21
|
-
return unsub
|
|
22
|
-
}, [])
|
|
23
|
-
return path
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** Match '/product/:slug' against '/product/leather-backpack' → { slug: 'leather-backpack' } */
|
|
27
|
-
export function matchPath(pattern: string, path: string): Record<string, string> | null {
|
|
28
|
-
const cleanPath = (path.split('?')[0] ?? '').replace(/\/+$/, '') || '/'
|
|
29
|
-
const patternParts = pattern.split('/').filter(Boolean)
|
|
30
|
-
const pathParts = cleanPath.split('/').filter(Boolean)
|
|
31
|
-
if (patternParts.length !== pathParts.length) return null
|
|
32
|
-
const params: Record<string, string> = {}
|
|
33
|
-
for (let i = 0; i < patternParts.length; i++) {
|
|
34
|
-
const pat = patternParts[i]!
|
|
35
|
-
const part = pathParts[i]!
|
|
36
|
-
if (pat.startsWith(':')) params[pat.slice(1)] = decodeURIComponent(part)
|
|
37
|
-
else if (pat !== part) return null
|
|
38
|
-
}
|
|
39
|
-
return params
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface LinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
|
|
43
|
-
to: string
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** Real anchor (#/path) — middle-click/cmd-click friendly, Playwright friendly. */
|
|
47
|
-
export function Link({ to, children, ...rest }: LinkProps) {
|
|
48
|
-
return React.createElement('a', { href: `#${to}`, ...rest }, children)
|
|
49
|
-
}
|
package/src/scaffold.tsx
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { registerScaffold, defineApp } from '@fayz-ai/core'
|
|
3
|
-
import type { AppManifest, BackendProvider } from '@fayz-ai/core'
|
|
4
|
-
import { StorefrontConfigProvider, resolveConfig } from './config'
|
|
5
|
-
import type { StorefrontConfig } from './config'
|
|
6
|
-
import type { StorefrontTheme } from './theme'
|
|
7
|
-
import type { HomeConfig, NavLink, FooterConfig } from './sections'
|
|
8
|
-
import { initStorefrontRuntime, StorefrontShell } from './createStorefrontApp'
|
|
9
|
-
import './blocks' // ensure section blocks are registered
|
|
10
|
-
|
|
11
|
-
type StorefrontBackendProvider = NonNullable<StorefrontConfig['backend']>['provider']
|
|
12
|
-
const runtimeConfigRegistry = new Map<string, StorefrontConfig>()
|
|
13
|
-
|
|
14
|
-
// ---------------------------------------------------------------------------
|
|
15
|
-
// Storefront scaffold — renders a store from a pure-data AppManifest. The
|
|
16
|
-
// storefront surface keeps its declarative config under surface.options, so the
|
|
17
|
-
// mapping is loss-free for the serializable fields (the code escape hatches —
|
|
18
|
-
// custom provider/adapter/logo — arrive via the registries instead).
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
|
|
21
|
-
function slug(name: string): string {
|
|
22
|
-
return name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'app'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** StorefrontConfig → AppManifest (the sugar-to-data direction). */
|
|
26
|
-
export function defineStorefront(config: StorefrontConfig): AppManifest {
|
|
27
|
-
const id = slug(config.name)
|
|
28
|
-
runtimeConfigRegistry.set(id, config)
|
|
29
|
-
const backendProvider = (config.backend?.provider ?? (config.provider ? 'custom' : 'mock')) as BackendProvider
|
|
30
|
-
|
|
31
|
-
return defineApp({
|
|
32
|
-
id,
|
|
33
|
-
name: config.name,
|
|
34
|
-
backend: {
|
|
35
|
-
provider: backendProvider,
|
|
36
|
-
url: config.backend?.url,
|
|
37
|
-
},
|
|
38
|
-
locale: {
|
|
39
|
-
default: config.locale ?? 'pt-BR',
|
|
40
|
-
supported: [config.locale ?? 'pt-BR'],
|
|
41
|
-
currency: config.currency ?? 'BRL',
|
|
42
|
-
},
|
|
43
|
-
theme: config.theme as Record<string, unknown> | undefined,
|
|
44
|
-
surfaces: {
|
|
45
|
-
storefront: {
|
|
46
|
-
scaffold: 'storefront',
|
|
47
|
-
options: {
|
|
48
|
-
announcement: config.announcement,
|
|
49
|
-
home: config.home,
|
|
50
|
-
nav: config.nav,
|
|
51
|
-
footer: config.footer,
|
|
52
|
-
shipping: config.shipping,
|
|
53
|
-
features: config.features,
|
|
54
|
-
catalog: config.catalog,
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
},
|
|
58
|
-
})
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** AppManifest → StorefrontConfig (the data-to-render direction). */
|
|
62
|
-
function manifestToStorefrontConfig(manifest: AppManifest, surfaceName: string): StorefrontConfig {
|
|
63
|
-
const runtimeConfig = runtimeConfigRegistry.get(manifest.id)
|
|
64
|
-
const surface = manifest.surfaces[surfaceName]
|
|
65
|
-
const o = (surface?.options ?? {}) as Record<string, unknown>
|
|
66
|
-
return {
|
|
67
|
-
...runtimeConfig,
|
|
68
|
-
name: manifest.name,
|
|
69
|
-
currency: (manifest.locale as { currency?: string } | undefined)?.currency,
|
|
70
|
-
locale: (manifest.locale as { default?: string } | undefined)?.default,
|
|
71
|
-
theme: manifest.theme as StorefrontTheme | undefined,
|
|
72
|
-
announcement: o.announcement as string | undefined,
|
|
73
|
-
home: o.home as HomeConfig | undefined,
|
|
74
|
-
nav: o.nav as NavLink[] | undefined,
|
|
75
|
-
footer: o.footer as FooterConfig | undefined,
|
|
76
|
-
shipping: o.shipping as StorefrontConfig['shipping'],
|
|
77
|
-
features: o.features as StorefrontConfig['features'],
|
|
78
|
-
catalog: o.catalog as StorefrontConfig['catalog'],
|
|
79
|
-
backend: {
|
|
80
|
-
provider: manifest.backend?.provider as StorefrontBackendProvider,
|
|
81
|
-
url: manifest.backend?.url,
|
|
82
|
-
},
|
|
83
|
-
components: runtimeConfig?.components,
|
|
84
|
-
routes: runtimeConfig?.routes,
|
|
85
|
-
provider: runtimeConfig?.provider,
|
|
86
|
-
auth: runtimeConfig?.auth,
|
|
87
|
-
logo: runtimeConfig?.logo,
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export function StorefrontScaffold({ manifest, surface }: { manifest: AppManifest; surface: string }) {
|
|
92
|
-
const config = React.useMemo(() => manifestToStorefrontConfig(manifest, surface), [manifest, surface])
|
|
93
|
-
const resolved = React.useMemo(() => resolveConfig(config), [config])
|
|
94
|
-
const inited = React.useRef(false)
|
|
95
|
-
if (!inited.current) {
|
|
96
|
-
initStorefrontRuntime(config)
|
|
97
|
-
inited.current = true
|
|
98
|
-
}
|
|
99
|
-
return (
|
|
100
|
-
<StorefrontConfigProvider value={resolved}>
|
|
101
|
-
<StorefrontShell />
|
|
102
|
-
</StorefrontConfigProvider>
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
StorefrontScaffold.displayName = 'StorefrontScaffold'
|
|
106
|
-
|
|
107
|
-
// Registration is lazy (called from the storefront renderApp wrapper and
|
|
108
|
-
// createStorefront*) so that importing the @fayz-ai/storefront barrel stays
|
|
109
|
-
// side-effect-free and tree-shakable.
|
|
110
|
-
let storefrontScaffoldRegistered = false
|
|
111
|
-
export function registerStorefrontScaffold(): void {
|
|
112
|
-
if (storefrontScaffoldRegistered) return
|
|
113
|
-
storefrontScaffoldRegistered = true
|
|
114
|
-
registerScaffold('storefront', StorefrontScaffold, { source: 'sdk', label: 'Storefront' })
|
|
115
|
-
}
|
package/src/sections.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import type { HeroVariant } from './theme'
|
|
2
|
-
import type React from 'react'
|
|
3
|
-
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
// Home-page section blueprint — the storefront's PageConfig equivalent.
|
|
6
|
-
// A template (or the Fayz agent) declares the home as an ordered list of
|
|
7
|
-
// typed sections; HomePage renders them. Every recurring block found in the
|
|
8
|
-
// Nuvemshop research maps to one section type.
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
|
|
11
|
-
export interface HeroSlide {
|
|
12
|
-
title: string
|
|
13
|
-
subtitle?: string
|
|
14
|
-
cta?: string
|
|
15
|
-
href?: string
|
|
16
|
-
/** Image URL; omit for a gradient placeholder derived from `hue` */
|
|
17
|
-
image?: string
|
|
18
|
-
hue?: number
|
|
19
|
-
/** Editorial boxed copy block (Uyuni) instead of free overlay */
|
|
20
|
-
boxed?: boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface MediaCarouselItem {
|
|
24
|
-
title: string
|
|
25
|
-
subtitle?: string
|
|
26
|
-
eyebrow?: string
|
|
27
|
-
cta?: string
|
|
28
|
-
href?: string
|
|
29
|
-
image: string
|
|
30
|
-
imageAlt?: string
|
|
31
|
-
align?: 'start' | 'center' | 'end'
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export type StorefrontSection =
|
|
35
|
-
| { type: 'hero'; variant: HeroVariant; slides: HeroSlide[]; height?: 'tall' | 'medium' }
|
|
36
|
-
| {
|
|
37
|
-
type: 'mediaCarousel'
|
|
38
|
-
items: MediaCarouselItem[]
|
|
39
|
-
height?: 'medium' | 'tall' | 'screen'
|
|
40
|
-
autoplayMs?: number
|
|
41
|
-
overlay?: 'dark' | 'soft' | 'none'
|
|
42
|
-
className?: string
|
|
43
|
-
}
|
|
44
|
-
| { type: 'categories'; style: 'bubbles' | 'tiles'; title?: string }
|
|
45
|
-
| {
|
|
46
|
-
type: 'products'
|
|
47
|
-
title: string
|
|
48
|
-
subtitle?: string
|
|
49
|
-
/** Small-caps label above the title (e.g. 'curadoria') */
|
|
50
|
-
eyebrow?: string
|
|
51
|
-
/** 'sale' = compareAtPrice set; 'new' = newest first; 'all' = catalog order */
|
|
52
|
-
filter?: 'sale' | 'new' | 'all'
|
|
53
|
-
limit?: number
|
|
54
|
-
}
|
|
55
|
-
| {
|
|
56
|
-
type: 'productSlider'
|
|
57
|
-
title: string
|
|
58
|
-
subtitle?: string
|
|
59
|
-
eyebrow?: string
|
|
60
|
-
filter?: 'sale' | 'new' | 'all'
|
|
61
|
-
categoryName?: string
|
|
62
|
-
collection?: string
|
|
63
|
-
limit?: number
|
|
64
|
-
cta?: string
|
|
65
|
-
href?: string
|
|
66
|
-
className?: string
|
|
67
|
-
}
|
|
68
|
-
| { type: 'benefits'; items: Array<{ icon: string; title: string; text?: string }> }
|
|
69
|
-
| { type: 'banner'; title: string; subtitle?: string; eyebrow?: string; cta?: string; href?: string; image?: string; hue?: number }
|
|
70
|
-
| { type: 'manifesto'; title?: string; text: string }
|
|
71
|
-
| { type: 'testimonials'; title?: string; items: Array<{ quote: string; author: string }> }
|
|
72
|
-
| { type: 'newsletter'; title?: string; subtitle?: string }
|
|
73
|
-
|
|
74
|
-
/** @deprecated Use StorefrontSection. Kept for existing generated stores. */
|
|
75
|
-
export type HomeSection = StorefrontSection
|
|
76
|
-
|
|
77
|
-
export interface HomeConfig {
|
|
78
|
-
sections: readonly StorefrontSection[]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface NavLink {
|
|
82
|
-
label: string
|
|
83
|
-
to: string
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
export interface FooterConfig {
|
|
87
|
-
about?: string
|
|
88
|
-
contact?: { phone?: string; email?: string; address?: string }
|
|
89
|
-
social?: Array<{ label: string; href: string }>
|
|
90
|
-
credit?: React.ReactNode
|
|
91
|
-
labels?: {
|
|
92
|
-
categories?: string
|
|
93
|
-
navigation?: string
|
|
94
|
-
contact?: string
|
|
95
|
-
viewAll?: string
|
|
96
|
-
purchases?: string
|
|
97
|
-
privacy?: string
|
|
98
|
-
terms?: string
|
|
99
|
-
returns?: string
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** Wide gradient SVG data-URI — deterministic hero/banner art, no network. */
|
|
104
|
-
export function bannerPlaceholder(label: string, hueA: number, hueB: number, w = 1600, h = 700): string {
|
|
105
|
-
const svg =
|
|
106
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}">` +
|
|
107
|
-
`<defs><linearGradient id="g" x1="0" y1="0" x2="1" y2="1">` +
|
|
108
|
-
`<stop offset="0" stop-color="hsl(${hueA},40%,78%)"/>` +
|
|
109
|
-
`<stop offset="1" stop-color="hsl(${hueB},45%,55%)"/>` +
|
|
110
|
-
`</linearGradient></defs>` +
|
|
111
|
-
`<rect width="${w}" height="${h}" fill="url(#g)"/>` +
|
|
112
|
-
`<circle cx="${w * 0.8}" cy="${h * 0.3}" r="${h * 0.45}" fill="hsla(${hueB},50%,90%,0.25)"/>` +
|
|
113
|
-
`<circle cx="${w * 0.15}" cy="${h * 0.85}" r="${h * 0.35}" fill="hsla(${hueA},45%,30%,0.12)"/>` +
|
|
114
|
-
`</svg>`
|
|
115
|
-
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`
|
|
116
|
-
}
|
package/src/shop.ts
DELETED
package/src/stores/cart.store.ts
DELETED
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
import { create } from 'zustand'
|
|
2
|
-
import { persist } from 'zustand/middleware'
|
|
3
|
-
import type { Product } from '@fayz-ai/shop/types'
|
|
4
|
-
import { roundCents } from '../format'
|
|
5
|
-
import type { ResolvedStorefrontConfig } from '../config'
|
|
6
|
-
import { useDeliveryStore, selectQuotedShipping } from './delivery.store'
|
|
7
|
-
import {
|
|
8
|
-
formatProductOptionSelection,
|
|
9
|
-
normalizeProductOptionSelection,
|
|
10
|
-
productOptionSelectionKey,
|
|
11
|
-
type ProductOptionSelection,
|
|
12
|
-
} from '../product-options'
|
|
13
|
-
|
|
14
|
-
export interface CartLine {
|
|
15
|
-
lineId: string
|
|
16
|
-
productId: string
|
|
17
|
-
slug: string
|
|
18
|
-
name: string
|
|
19
|
-
sku: string | null
|
|
20
|
-
options: ProductOptionSelection
|
|
21
|
-
optionsLabel: string
|
|
22
|
-
unitPrice: number
|
|
23
|
-
quantity: number
|
|
24
|
-
imageUrl: string | null
|
|
25
|
-
/** inventoryCount at add time — clamps quantity */
|
|
26
|
-
maxQuantity: number
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface CartState {
|
|
30
|
-
lines: CartLine[]
|
|
31
|
-
discountCode: string | null
|
|
32
|
-
discountPercent: number
|
|
33
|
-
isOpen: boolean
|
|
34
|
-
/** Line most recently added — lets the drawer briefly highlight it. */
|
|
35
|
-
justAddedLineId: string | null
|
|
36
|
-
addItem(product: Product, qty?: number, options?: ProductOptionSelection): void
|
|
37
|
-
setQuantity(lineId: string, qty: number): void
|
|
38
|
-
removeItem(lineId: string): void
|
|
39
|
-
applyDiscount(code: string, percent: number): void
|
|
40
|
-
clearDiscount(): void
|
|
41
|
-
clear(): void
|
|
42
|
-
openDrawer(): void
|
|
43
|
-
closeDrawer(): void
|
|
44
|
-
/** Drop persisted lines the active backend can no longer resolve (e.g. a cart
|
|
45
|
-
* built in an earlier mock-mode session, whose ids don't exist live). The
|
|
46
|
-
* resolver returns false only for a definitive "not found"; on error it must
|
|
47
|
-
* return true so a transient failure never discards a legit line. */
|
|
48
|
-
reconcile(resolve: (line: CartLine) => Promise<boolean>): Promise<void>
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export const useCartStore = create<CartState>()(
|
|
52
|
-
persist(
|
|
53
|
-
(set, get) => ({
|
|
54
|
-
lines: [],
|
|
55
|
-
discountCode: null,
|
|
56
|
-
discountPercent: 0,
|
|
57
|
-
isOpen: false,
|
|
58
|
-
justAddedLineId: null,
|
|
59
|
-
|
|
60
|
-
addItem: (product, qty = 1, options) =>
|
|
61
|
-
set((state) => {
|
|
62
|
-
const normalizedOptions = normalizeProductOptionSelection(options)
|
|
63
|
-
const optionKey = productOptionSelectionKey(normalizedOptions)
|
|
64
|
-
const lineId = optionKey ? `${product.id}:${optionKey}` : product.id
|
|
65
|
-
const existing = state.lines.find((l) => (l.lineId ?? l.productId) === lineId)
|
|
66
|
-
if (existing) {
|
|
67
|
-
const nextQty = Math.min(existing.quantity + qty, existing.maxQuantity || Infinity)
|
|
68
|
-
return {
|
|
69
|
-
// Auto-open the cart on add — the standard premium "slide-in cart"
|
|
70
|
-
// signal — and flag the line so the drawer can highlight it.
|
|
71
|
-
isOpen: true,
|
|
72
|
-
justAddedLineId: lineId,
|
|
73
|
-
lines: state.lines.map((l) =>
|
|
74
|
-
(l.lineId ?? l.productId) === lineId ? { ...l, lineId, quantity: nextQty } : l,
|
|
75
|
-
),
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const image = product.images.find((i) => i.isPrimary) ?? product.images[0]
|
|
79
|
-
const line: CartLine = {
|
|
80
|
-
lineId,
|
|
81
|
-
productId: product.id,
|
|
82
|
-
slug: product.slug,
|
|
83
|
-
name: product.name,
|
|
84
|
-
sku: product.sku,
|
|
85
|
-
options: normalizedOptions,
|
|
86
|
-
optionsLabel: formatProductOptionSelection(normalizedOptions),
|
|
87
|
-
unitPrice: product.price,
|
|
88
|
-
quantity: Math.min(qty, product.inventoryCount || Infinity),
|
|
89
|
-
imageUrl: image?.url ?? null,
|
|
90
|
-
maxQuantity: product.inventoryCount,
|
|
91
|
-
}
|
|
92
|
-
return { isOpen: true, justAddedLineId: lineId, lines: [...state.lines, line] }
|
|
93
|
-
}),
|
|
94
|
-
|
|
95
|
-
setQuantity: (lineId, qty) =>
|
|
96
|
-
set((state) => {
|
|
97
|
-
if (qty <= 0) return { lines: state.lines.filter((l) => (l.lineId ?? l.productId) !== lineId) }
|
|
98
|
-
return {
|
|
99
|
-
lines: state.lines.map((l) =>
|
|
100
|
-
(l.lineId ?? l.productId) === lineId
|
|
101
|
-
? { ...l, quantity: Math.min(qty, l.maxQuantity || Infinity) }
|
|
102
|
-
: l,
|
|
103
|
-
),
|
|
104
|
-
}
|
|
105
|
-
}),
|
|
106
|
-
|
|
107
|
-
removeItem: (lineId) =>
|
|
108
|
-
set((state) => ({ lines: state.lines.filter((l) => (l.lineId ?? l.productId) !== lineId) })),
|
|
109
|
-
|
|
110
|
-
applyDiscount: (code, percent) => set({ discountCode: code, discountPercent: percent }),
|
|
111
|
-
clearDiscount: () => set({ discountCode: null, discountPercent: 0 }),
|
|
112
|
-
clear: () => set({ lines: [], discountCode: null, discountPercent: 0 }),
|
|
113
|
-
openDrawer: () => set({ isOpen: true }),
|
|
114
|
-
closeDrawer: () => set({ isOpen: false, justAddedLineId: null }),
|
|
115
|
-
|
|
116
|
-
reconcile: async (resolve) => {
|
|
117
|
-
const lines = get().lines
|
|
118
|
-
if (lines.length === 0) return
|
|
119
|
-
const keep = await Promise.all(
|
|
120
|
-
lines.map((line) => resolve(line).catch(() => true)),
|
|
121
|
-
)
|
|
122
|
-
const kept = lines.filter((_, i) => keep[i])
|
|
123
|
-
if (kept.length !== lines.length) set({ lines: kept })
|
|
124
|
-
},
|
|
125
|
-
}),
|
|
126
|
-
{
|
|
127
|
-
name: 'fayz.storefront.cart.v1',
|
|
128
|
-
partialize: (state) => ({
|
|
129
|
-
lines: state.lines,
|
|
130
|
-
discountCode: state.discountCode,
|
|
131
|
-
discountPercent: state.discountPercent,
|
|
132
|
-
}),
|
|
133
|
-
},
|
|
134
|
-
),
|
|
135
|
-
)
|
|
136
|
-
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
// Money selectors — the single place cart math happens. Checkout passes these
|
|
139
|
-
// exact values to createOrder so drawer totals always match order totals.
|
|
140
|
-
// ---------------------------------------------------------------------------
|
|
141
|
-
|
|
142
|
-
export const selectCount = (s: Pick<CartState, 'lines'>): number =>
|
|
143
|
-
s.lines.reduce((n, l) => n + l.quantity, 0)
|
|
144
|
-
|
|
145
|
-
export const selectSubtotal = (s: Pick<CartState, 'lines'>): number =>
|
|
146
|
-
roundCents(s.lines.reduce((sum, l) => sum + l.unitPrice * l.quantity, 0))
|
|
147
|
-
|
|
148
|
-
export const selectDiscountTotal = (s: Pick<CartState, 'lines' | 'discountPercent'>): number =>
|
|
149
|
-
roundCents(selectSubtotal(s) * (s.discountPercent / 100))
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Freight to display.
|
|
153
|
-
*
|
|
154
|
-
* When the shopper has given a CEP and the store quoted it, that quote wins —
|
|
155
|
-
* it came from the same shipping_zones rows shop_place_order will charge from.
|
|
156
|
-
* Otherwise this falls back to the store-wide rate in config, which is what
|
|
157
|
-
* every storefront did before zones existed.
|
|
158
|
-
*
|
|
159
|
-
* The subtotal is PRE-discount on both sides of this. That is 0017's rule, and
|
|
160
|
-
* the quote is requested against the same number, so a coupon can never make
|
|
161
|
-
* the cart and the order disagree.
|
|
162
|
-
*
|
|
163
|
-
* Read through getState() rather than a hook because this is a plain selector
|
|
164
|
-
* called from several screens; components that render the value subscribe to
|
|
165
|
-
* useDeliveryStore themselves so a fresh quote re-renders them.
|
|
166
|
-
*/
|
|
167
|
-
export const selectShipping = (
|
|
168
|
-
s: Pick<CartState, 'lines'>,
|
|
169
|
-
cfg: ResolvedStorefrontConfig,
|
|
170
|
-
): number => {
|
|
171
|
-
if (s.lines.length === 0) return 0
|
|
172
|
-
const subtotal = selectSubtotal(s)
|
|
173
|
-
|
|
174
|
-
const quoted = selectQuotedShipping(useDeliveryStore.getState(), subtotal)
|
|
175
|
-
if (quoted != null) return quoted
|
|
176
|
-
|
|
177
|
-
if (cfg.shipping.freeAbove != null && subtotal >= cfg.shipping.freeAbove) return 0
|
|
178
|
-
return cfg.shipping.flatRate
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export const selectTotal = (
|
|
182
|
-
s: Pick<CartState, 'lines' | 'discountPercent'>,
|
|
183
|
-
cfg: ResolvedStorefrontConfig,
|
|
184
|
-
): number =>
|
|
185
|
-
roundCents(selectSubtotal(s) - selectDiscountTotal(s) + selectShipping(s, cfg))
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { create } from 'zustand'
|
|
2
|
-
|
|
3
|
-
export type CatalogSort = 'newest' | 'price-asc' | 'price-desc' | 'name'
|
|
4
|
-
|
|
5
|
-
export interface CatalogState {
|
|
6
|
-
search: string
|
|
7
|
-
categoryId: string | null
|
|
8
|
-
priceMin: number | null
|
|
9
|
-
priceMax: number | null
|
|
10
|
-
inStockOnly: boolean
|
|
11
|
-
sort: CatalogSort
|
|
12
|
-
setSearch(search: string): void
|
|
13
|
-
setCategoryId(categoryId: string | null): void
|
|
14
|
-
setPriceMin(priceMin: number | null): void
|
|
15
|
-
setPriceMax(priceMax: number | null): void
|
|
16
|
-
setInStockOnly(inStockOnly: boolean): void
|
|
17
|
-
setSort(sort: CatalogSort): void
|
|
18
|
-
reset(): void
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const initial = {
|
|
22
|
-
search: '',
|
|
23
|
-
categoryId: null,
|
|
24
|
-
priceMin: null,
|
|
25
|
-
priceMax: null,
|
|
26
|
-
inStockOnly: false,
|
|
27
|
-
sort: 'newest' as CatalogSort,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const useCatalogStore = create<CatalogState>()((set) => ({
|
|
31
|
-
...initial,
|
|
32
|
-
setSearch: (search) => set({ search }),
|
|
33
|
-
setCategoryId: (categoryId) => set({ categoryId }),
|
|
34
|
-
setPriceMin: (priceMin) => set({ priceMin }),
|
|
35
|
-
setPriceMax: (priceMax) => set({ priceMax }),
|
|
36
|
-
setInStockOnly: (inStockOnly) => set({ inStockOnly }),
|
|
37
|
-
setSort: (sort) => set({ sort }),
|
|
38
|
-
reset: () => set(initial),
|
|
39
|
-
}))
|
|
@@ -1,152 +0,0 @@
|
|
|
1
|
-
import { create } from 'zustand'
|
|
2
|
-
import { persist } from 'zustand/middleware'
|
|
3
|
-
import { lookupPostalCode, normalizePostalCode, type PostalAddress } from '@fayz-ai/core'
|
|
4
|
-
import { getShopProvider } from '@fayz-ai/shop/runtime'
|
|
5
|
-
import type { ShippingQuoteOption } from '@fayz-ai/shop/types'
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Where the shopper wants it delivered, decided before the cart exists.
|
|
9
|
-
*
|
|
10
|
-
* Two different things live here and they are persisted differently on purpose:
|
|
11
|
-
*
|
|
12
|
-
* the ADDRESS is durable — a CEP maps to the same street for years, and
|
|
13
|
-
* carrying it to checkout is the whole point (the buyer types the number and
|
|
14
|
-
* nothing else);
|
|
15
|
-
*
|
|
16
|
-
* the QUOTE is not persisted at all. It is a price, and a price restored from
|
|
17
|
-
* localStorage is a price the shopper can edit with devtools. It is re-asked
|
|
18
|
-
* on demand, and resolved for the current cart through resolveOptionRate —
|
|
19
|
-
* the same rule shop_shipping_for applies — so it can never go stale. The
|
|
20
|
-
* order is charged from shop_place_order, which recomputes from the same
|
|
21
|
-
* zones and ignores anything the client sends.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
export type DeliveryStatus = 'idle' | 'loading' | 'served' | 'unserved' | 'error'
|
|
25
|
-
|
|
26
|
-
export interface DeliveryState {
|
|
27
|
-
postalCode: string
|
|
28
|
-
address: PostalAddress | null
|
|
29
|
-
options: ShippingQuoteOption[]
|
|
30
|
-
selectedZoneId: string | null
|
|
31
|
-
status: DeliveryStatus
|
|
32
|
-
error: string | null
|
|
33
|
-
|
|
34
|
-
/** Look the CEP up and quote it. `subtotal` decides free-above thresholds. */
|
|
35
|
-
resolve: (postalCode: string, subtotal: number) => Promise<void>
|
|
36
|
-
selectZone: (zoneId: string) => void
|
|
37
|
-
clear: () => void
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const EMPTY = {
|
|
41
|
-
postalCode: '',
|
|
42
|
-
address: null,
|
|
43
|
-
options: [] as ShippingQuoteOption[],
|
|
44
|
-
selectedZoneId: null,
|
|
45
|
-
status: 'idle' as DeliveryStatus,
|
|
46
|
-
error: null,
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export const useDeliveryStore = create<DeliveryState>()(
|
|
50
|
-
persist(
|
|
51
|
-
(set, get) => ({
|
|
52
|
-
...EMPTY,
|
|
53
|
-
|
|
54
|
-
async resolve(postalCode: string, subtotal: number) {
|
|
55
|
-
const code = normalizePostalCode(postalCode)
|
|
56
|
-
if (code.length !== 8) {
|
|
57
|
-
set({ ...EMPTY, postalCode, status: 'error', error: 'Digite os 8 dígitos do CEP.' })
|
|
58
|
-
return
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
set({ postalCode: code, status: 'loading', error: null })
|
|
62
|
-
|
|
63
|
-
// The QUOTE comes first and is the answer that matters. Whether we
|
|
64
|
-
// deliver to a postcode, and for how much, is decided entirely by our
|
|
65
|
-
// own zones against eight digits — it does not need to know the street.
|
|
66
|
-
//
|
|
67
|
-
// Doing it the other way round made the delivery answer depend on a
|
|
68
|
-
// third party: with ViaCEP slow or rate-limited, a perfectly serviceable
|
|
69
|
-
// address reported an error instead of a price.
|
|
70
|
-
let options: ShippingQuoteOption[] = []
|
|
71
|
-
try {
|
|
72
|
-
options = (await getShopProvider().quoteShipping?.(code, subtotal)) ?? []
|
|
73
|
-
} catch {
|
|
74
|
-
set({ ...EMPTY, postalCode: code, status: 'error',
|
|
75
|
-
error: 'Não foi possível calcular o frete agora. Tente de novo.' })
|
|
76
|
-
return
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// The ADDRESS is the convenience layer: it saves the buyer typing, and
|
|
80
|
-
// losing it costs typing, not the sale. Failures here never change the
|
|
81
|
-
// delivery verdict already decided above.
|
|
82
|
-
let address: PostalAddress | null = null
|
|
83
|
-
let notFound = false
|
|
84
|
-
try {
|
|
85
|
-
address = await lookupPostalCode(code)
|
|
86
|
-
notFound = address === null
|
|
87
|
-
} catch {
|
|
88
|
-
address = null
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
set({
|
|
92
|
-
postalCode: code,
|
|
93
|
-
address,
|
|
94
|
-
options,
|
|
95
|
-
selectedZoneId: options[0]?.zoneId ?? null,
|
|
96
|
-
// A store with no zones configured quotes nothing, and that must NOT
|
|
97
|
-
// read as "we don't deliver here" — it falls back to the store-wide
|
|
98
|
-
// flat rate, which the cart already knows how to show.
|
|
99
|
-
status: options.length > 0 ? 'served' : 'unserved',
|
|
100
|
-
// Reported alongside the price, not instead of it: the CEP may be
|
|
101
|
-
// mistyped even though its range is covered.
|
|
102
|
-
error: notFound ? 'CEP não encontrado. Confira os números.' : null,
|
|
103
|
-
})
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
selectZone(zoneId: string) {
|
|
107
|
-
if (get().options.some((option) => option.zoneId === zoneId)) set({ selectedZoneId: zoneId })
|
|
108
|
-
},
|
|
109
|
-
|
|
110
|
-
clear() {
|
|
111
|
-
set({ ...EMPTY })
|
|
112
|
-
},
|
|
113
|
-
}),
|
|
114
|
-
{
|
|
115
|
-
name: 'fayz.storefront.delivery.v1',
|
|
116
|
-
// Deliberately NOT persisting `options`: a rehydrated
|
|
117
|
-
// price would be shown as current without anything having quoted it. The
|
|
118
|
-
// address survives, the money is asked for again.
|
|
119
|
-
partialize: (state) => ({ postalCode: state.postalCode, address: state.address }),
|
|
120
|
-
onRehydrateStorage: () => (state) => {
|
|
121
|
-
if (state?.address) state.status = 'idle'
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
),
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* The freight for the chosen zone at THIS subtotal, or null when there is no
|
|
129
|
-
* zone in play (no CEP yet, or a store that configured none) and the caller
|
|
130
|
-
* should use the store-wide rate. Null never means free.
|
|
131
|
-
*
|
|
132
|
-
* The free-above rule is applied here rather than trusting the `rate` the
|
|
133
|
-
* server resolved, because that one was resolved for whatever the cart held at
|
|
134
|
-
* quote time. Re-deriving from `baseRate`/`freeAbove` — the same CASE
|
|
135
|
-
* shop_shipping_for runs — means adding an item can never leave a stale price
|
|
136
|
-
* on screen. An earlier version fell back to the config flat rate when the
|
|
137
|
-
* quote went stale, which showed R$ 8 (the flat rate) for an address in an
|
|
138
|
-
* R$ 18 zone: shown one number, charged another.
|
|
139
|
-
*/
|
|
140
|
-
export function resolveOptionRate(option: ShippingQuoteOption, subtotal: number): number {
|
|
141
|
-
if (option.freeAbove != null && subtotal >= option.freeAbove) return 0
|
|
142
|
-
return option.baseRate
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export const selectQuotedShipping = (
|
|
146
|
-
state: Pick<DeliveryState, 'options' | 'selectedZoneId' | 'status'>,
|
|
147
|
-
subtotal: number,
|
|
148
|
-
): number | null => {
|
|
149
|
-
if (state.status !== 'served') return null
|
|
150
|
-
const option = state.options.find((o) => o.zoneId === state.selectedZoneId) ?? state.options[0]
|
|
151
|
-
return option ? resolveOptionRate(option, subtotal) : null
|
|
152
|
-
}
|