@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
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { MapPin, Truck } from 'lucide-react'
|
|
3
|
-
import { formatPostalCode } from '@fayz-ai/core'
|
|
4
|
-
import { useDeliveryStore, resolveOptionRate } from '../stores/delivery.store'
|
|
5
|
-
import { useCartStore, selectSubtotal } from '../stores/cart.store'
|
|
6
|
-
import { useStorefrontConfig } from '../config'
|
|
7
|
-
import { formatMoney } from '../format'
|
|
8
|
-
import { TID } from '../testids'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* "Do you deliver to me, for how much, and when" — asked on the product page,
|
|
12
|
-
* before the shopper has invested anything in a cart.
|
|
13
|
-
*
|
|
14
|
-
* The CEP is kept in the delivery store, so the answer follows them to the cart
|
|
15
|
-
* and lands at checkout with street/district/city/UF already filled. That is
|
|
16
|
-
* the actual payoff: the buyer types a house number instead of an address.
|
|
17
|
-
*
|
|
18
|
-
* `subtotal` is the PRE-discount cart subtotal, the same number shop_place_order
|
|
19
|
-
* uses to resolve free-above thresholds. Quoting against anything else is how
|
|
20
|
-
* the cart and the order end up disagreeing (see migration 0017).
|
|
21
|
-
*/
|
|
22
|
-
export function DeliveryEstimator({ compact = false }: { compact?: boolean }) {
|
|
23
|
-
const config = useStorefrontConfig()
|
|
24
|
-
const cart = useCartStore()
|
|
25
|
-
const delivery = useDeliveryStore()
|
|
26
|
-
const [draft, setDraft] = useState(() => formatPostalCode(delivery.postalCode))
|
|
27
|
-
|
|
28
|
-
const subtotal = selectSubtotal(cart)
|
|
29
|
-
const money = (value: number) => formatMoney(value, config.currency, config.locale)
|
|
30
|
-
const loading = delivery.status === 'loading'
|
|
31
|
-
|
|
32
|
-
async function submit(event: React.FormEvent) {
|
|
33
|
-
event.preventDefault()
|
|
34
|
-
await delivery.resolve(draft, subtotal)
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function eta(option: { etaMinDays?: number; etaMaxDays?: number }): string | null {
|
|
38
|
-
const { etaMinDays: min, etaMaxDays: max } = option
|
|
39
|
-
if (min == null && max == null) return null
|
|
40
|
-
if (min === 0 && (max === 0 || max === 1)) return 'hoje ou amanhã'
|
|
41
|
-
if (min != null && max != null && min !== max) return `${min} a ${max} dias úteis`
|
|
42
|
-
return `${max ?? min} dia(s) útil(eis)`
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return (
|
|
46
|
-
<section
|
|
47
|
-
data-testid={TID.deliveryEstimator}
|
|
48
|
-
className={compact ? 'rounded-lg border p-3' : 'mt-6 rounded-lg border p-4'}
|
|
49
|
-
>
|
|
50
|
-
<div className="flex items-center gap-2 text-sm font-semibold">
|
|
51
|
-
<Truck className="h-4 w-4 text-primary" />
|
|
52
|
-
Calcular entrega
|
|
53
|
-
</div>
|
|
54
|
-
|
|
55
|
-
<form onSubmit={submit} className="mt-3 flex gap-2">
|
|
56
|
-
<input
|
|
57
|
-
data-testid={TID.deliveryCepInput}
|
|
58
|
-
aria-label="CEP"
|
|
59
|
-
inputMode="numeric"
|
|
60
|
-
autoComplete="postal-code"
|
|
61
|
-
placeholder="00000-000"
|
|
62
|
-
value={draft}
|
|
63
|
-
onChange={(event) => setDraft(formatPostalCode(event.target.value))}
|
|
64
|
-
className="min-w-0 flex-1 rounded-lg border bg-background px-3 py-2 text-sm outline-none transition focus:border-primary focus:ring-2 focus:ring-primary/10"
|
|
65
|
-
/>
|
|
66
|
-
<button
|
|
67
|
-
type="submit"
|
|
68
|
-
data-testid={TID.deliveryCepSubmit}
|
|
69
|
-
disabled={loading}
|
|
70
|
-
className="shrink-0 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition hover:opacity-90 disabled:opacity-50"
|
|
71
|
-
>
|
|
72
|
-
{loading ? '...' : 'Calcular'}
|
|
73
|
-
</button>
|
|
74
|
-
</form>
|
|
75
|
-
|
|
76
|
-
{/* Shown whenever there is one, including alongside a price: the range
|
|
77
|
-
may be covered even though the CEP itself was mistyped. */}
|
|
78
|
-
{delivery.error && (
|
|
79
|
-
<p data-testid={TID.deliveryError} className="mt-3 text-sm text-destructive">
|
|
80
|
-
{delivery.error}
|
|
81
|
-
</p>
|
|
82
|
-
)}
|
|
83
|
-
|
|
84
|
-
{delivery.address && (
|
|
85
|
-
<p data-testid={TID.deliveryAddress} className="mt-3 flex items-start gap-1.5 text-xs text-muted-foreground">
|
|
86
|
-
<MapPin className="mt-0.5 h-3.5 w-3.5 shrink-0" />
|
|
87
|
-
<span>
|
|
88
|
-
{[delivery.address.street, delivery.address.district].filter(Boolean).join(', ')}
|
|
89
|
-
{delivery.address.street || delivery.address.district ? ' — ' : ''}
|
|
90
|
-
{delivery.address.city}/{delivery.address.state}
|
|
91
|
-
</span>
|
|
92
|
-
</p>
|
|
93
|
-
)}
|
|
94
|
-
|
|
95
|
-
{delivery.status === 'served' && (
|
|
96
|
-
<ul className="mt-3 space-y-2">
|
|
97
|
-
{delivery.options.map((option) => {
|
|
98
|
-
const selected = delivery.selectedZoneId === option.zoneId
|
|
99
|
-
const label = eta(option)
|
|
100
|
-
// Resolved for the cart as it stands right now, not for whatever it
|
|
101
|
-
// held when the CEP was typed — the same rule the server applies.
|
|
102
|
-
const rate = resolveOptionRate(option, subtotal)
|
|
103
|
-
return (
|
|
104
|
-
<li key={option.zoneId}>
|
|
105
|
-
<button
|
|
106
|
-
type="button"
|
|
107
|
-
data-testid={TID.deliveryOption}
|
|
108
|
-
data-zone={option.zoneId}
|
|
109
|
-
data-rate={rate.toFixed(2)}
|
|
110
|
-
aria-pressed={selected}
|
|
111
|
-
onClick={() => delivery.selectZone(option.zoneId)}
|
|
112
|
-
className={`flex w-full items-center justify-between gap-3 rounded-lg border px-3 py-2 text-left text-sm transition ${
|
|
113
|
-
selected ? 'border-primary bg-primary/5' : 'border-border hover:bg-muted/40'
|
|
114
|
-
}`}
|
|
115
|
-
>
|
|
116
|
-
<span className="min-w-0">
|
|
117
|
-
<span className="block truncate font-medium">
|
|
118
|
-
{option.carrier ? `${option.carrier} · ${option.name}` : option.name}
|
|
119
|
-
</span>
|
|
120
|
-
{label && <span className="block text-xs text-muted-foreground">{label}</span>}
|
|
121
|
-
</span>
|
|
122
|
-
<span className="shrink-0 font-semibold">
|
|
123
|
-
{rate === 0 ? 'Grátis' : money(rate)}
|
|
124
|
-
</span>
|
|
125
|
-
</button>
|
|
126
|
-
</li>
|
|
127
|
-
)
|
|
128
|
-
})}
|
|
129
|
-
</ul>
|
|
130
|
-
)}
|
|
131
|
-
|
|
132
|
-
{/*
|
|
133
|
-
No options for a valid address. For a store that configured zones this
|
|
134
|
-
means "outside our delivery area" and checkout will refuse the order, so
|
|
135
|
-
it is said plainly here rather than at the end of the funnel. A store
|
|
136
|
-
with no zones at all never reaches this state — it quotes nothing and
|
|
137
|
-
keeps its flat rate.
|
|
138
|
-
*/}
|
|
139
|
-
{delivery.status === 'unserved' && (
|
|
140
|
-
<p data-testid={TID.deliveryUnserved} className="mt-3 text-sm text-destructive">
|
|
141
|
-
Ainda não entregamos nesse CEP.
|
|
142
|
-
</p>
|
|
143
|
-
)}
|
|
144
|
-
|
|
145
|
-
{delivery.postalCode && (
|
|
146
|
-
<button
|
|
147
|
-
type="button"
|
|
148
|
-
data-testid={TID.deliveryClear}
|
|
149
|
-
onClick={() => { delivery.clear(); setDraft('') }}
|
|
150
|
-
className="mt-3 text-xs text-muted-foreground underline-offset-2 hover:underline"
|
|
151
|
-
>
|
|
152
|
-
Trocar CEP
|
|
153
|
-
</button>
|
|
154
|
-
)}
|
|
155
|
-
</section>
|
|
156
|
-
)
|
|
157
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { useCatalogStore } from '../stores/catalog.store'
|
|
3
|
-
import type { CatalogSort } from '../stores/catalog.store'
|
|
4
|
-
import { useCategories } from '../hooks/useCategories'
|
|
5
|
-
import { TID } from '../testids'
|
|
6
|
-
|
|
7
|
-
export function FiltersPanel() {
|
|
8
|
-
const { categories } = useCategories()
|
|
9
|
-
const catalog = useCatalogStore()
|
|
10
|
-
|
|
11
|
-
return (
|
|
12
|
-
<aside className="w-full shrink-0 space-y-6 lg:w-56">
|
|
13
|
-
{/* Sort */}
|
|
14
|
-
<div className="space-y-2">
|
|
15
|
-
<h3 className="text-sm font-semibold">Ordenar por</h3>
|
|
16
|
-
<select
|
|
17
|
-
data-testid={TID.sortSelect}
|
|
18
|
-
value={catalog.sort}
|
|
19
|
-
onChange={(e) => catalog.setSort(e.target.value as CatalogSort)}
|
|
20
|
-
className="w-full rounded-lg border bg-background px-3 py-2 text-sm"
|
|
21
|
-
>
|
|
22
|
-
<option value="newest">Mais recentes</option>
|
|
23
|
-
<option value="price-asc">Menor preço</option>
|
|
24
|
-
<option value="price-desc">Maior preço</option>
|
|
25
|
-
<option value="name">Nome (A–Z)</option>
|
|
26
|
-
</select>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
{/* Categories */}
|
|
30
|
-
<div className="space-y-2">
|
|
31
|
-
<h3 className="text-sm font-semibold">Categorias</h3>
|
|
32
|
-
<ul className="space-y-1">
|
|
33
|
-
<li>
|
|
34
|
-
<button
|
|
35
|
-
type="button"
|
|
36
|
-
onClick={() => catalog.setCategoryId(null)}
|
|
37
|
-
className={`w-full rounded-md px-2 py-1.5 text-left text-sm transition-colors ${
|
|
38
|
-
catalog.categoryId === null ? 'bg-primary/10 font-medium text-primary' : 'hover:bg-muted'
|
|
39
|
-
}`}
|
|
40
|
-
>
|
|
41
|
-
Todas
|
|
42
|
-
</button>
|
|
43
|
-
</li>
|
|
44
|
-
{categories.map((c) => (
|
|
45
|
-
<li key={c.id}>
|
|
46
|
-
<button
|
|
47
|
-
type="button"
|
|
48
|
-
data-testid={TID.filterCategory(c.slug)}
|
|
49
|
-
onClick={() => catalog.setCategoryId(catalog.categoryId === c.id ? null : c.id)}
|
|
50
|
-
className={`w-full rounded-md px-2 py-1.5 text-left text-sm transition-colors ${
|
|
51
|
-
catalog.categoryId === c.id ? 'bg-primary/10 font-medium text-primary' : 'hover:bg-muted'
|
|
52
|
-
}`}
|
|
53
|
-
>
|
|
54
|
-
{c.name}
|
|
55
|
-
</button>
|
|
56
|
-
</li>
|
|
57
|
-
))}
|
|
58
|
-
</ul>
|
|
59
|
-
</div>
|
|
60
|
-
|
|
61
|
-
{/* Price range */}
|
|
62
|
-
<div className="space-y-2">
|
|
63
|
-
<h3 className="text-sm font-semibold">Preço</h3>
|
|
64
|
-
<div className="flex items-center gap-2">
|
|
65
|
-
<input
|
|
66
|
-
data-testid={TID.filterPriceMin}
|
|
67
|
-
type="number"
|
|
68
|
-
placeholder="Mín"
|
|
69
|
-
min={0}
|
|
70
|
-
value={catalog.priceMin ?? ''}
|
|
71
|
-
onChange={(e) => catalog.setPriceMin(e.target.value === '' ? null : Number(e.target.value))}
|
|
72
|
-
className="w-full rounded-lg border bg-background px-2.5 py-1.5 text-sm"
|
|
73
|
-
/>
|
|
74
|
-
<span className="text-muted-foreground">–</span>
|
|
75
|
-
<input
|
|
76
|
-
data-testid={TID.filterPriceMax}
|
|
77
|
-
type="number"
|
|
78
|
-
placeholder="Máx"
|
|
79
|
-
min={0}
|
|
80
|
-
value={catalog.priceMax ?? ''}
|
|
81
|
-
onChange={(e) => catalog.setPriceMax(e.target.value === '' ? null : Number(e.target.value))}
|
|
82
|
-
className="w-full rounded-lg border bg-background px-2.5 py-1.5 text-sm"
|
|
83
|
-
/>
|
|
84
|
-
</div>
|
|
85
|
-
</div>
|
|
86
|
-
|
|
87
|
-
{/* Availability */}
|
|
88
|
-
<label className="flex cursor-pointer items-center gap-2 text-sm">
|
|
89
|
-
<input
|
|
90
|
-
data-testid={TID.filterInstock}
|
|
91
|
-
type="checkbox"
|
|
92
|
-
checked={catalog.inStockOnly}
|
|
93
|
-
onChange={(e) => catalog.setInStockOnly(e.target.checked)}
|
|
94
|
-
className="h-4 w-4 rounded border"
|
|
95
|
-
/>
|
|
96
|
-
Somente em estoque
|
|
97
|
-
</label>
|
|
98
|
-
|
|
99
|
-
<button
|
|
100
|
-
type="button"
|
|
101
|
-
data-testid={TID.filtersClear}
|
|
102
|
-
onClick={() => catalog.reset()}
|
|
103
|
-
className="text-sm text-muted-foreground underline-offset-2 hover:text-foreground hover:underline"
|
|
104
|
-
>
|
|
105
|
-
Limpar filtros
|
|
106
|
-
</button>
|
|
107
|
-
</aside>
|
|
108
|
-
)
|
|
109
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { Check, Clock, PackageCheck, RotateCcw, Truck, XCircle } from 'lucide-react'
|
|
3
|
-
import type { Order } from '@fayz-ai/shop/types'
|
|
4
|
-
import { isPaid, isRefunded } from '../order-status'
|
|
5
|
-
import { TID } from '../testids'
|
|
6
|
-
|
|
7
|
-
type TrackingState = 'complete' | 'current' | 'pending' | 'cancelled'
|
|
8
|
-
|
|
9
|
-
interface TrackingStep {
|
|
10
|
-
id: string
|
|
11
|
-
label: string
|
|
12
|
-
description: string
|
|
13
|
-
state: TrackingState
|
|
14
|
-
icon: React.ComponentType<{ className?: string }>
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
function orderTrackingSteps(order: Order): TrackingStep[] {
|
|
18
|
-
const received: TrackingStep = {
|
|
19
|
-
id: 'received',
|
|
20
|
-
label: 'Pedido recebido',
|
|
21
|
-
description: `#${order.orderNumber}`,
|
|
22
|
-
state: 'complete',
|
|
23
|
-
icon: Check,
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// Terminal states short-circuit the happy path.
|
|
27
|
-
if (order.status === 'cancelled' || order.financialStatus === 'voided') {
|
|
28
|
-
return [
|
|
29
|
-
received,
|
|
30
|
-
{ id: 'payment', label: 'Pedido cancelado', description: 'Este pedido foi cancelado', state: 'cancelled', icon: XCircle },
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
if (isRefunded(order.financialStatus)) {
|
|
34
|
-
return [
|
|
35
|
-
received,
|
|
36
|
-
{ id: 'payment', label: 'Pagamento aprovado', description: 'Compra confirmada', state: 'complete', icon: Check },
|
|
37
|
-
{
|
|
38
|
-
id: 'refund',
|
|
39
|
-
label: order.financialStatus === 'refunded' ? 'Reembolsado' : 'Reembolso parcial',
|
|
40
|
-
description: 'Valor devolvido ao cliente',
|
|
41
|
-
state: 'cancelled',
|
|
42
|
-
icon: RotateCcw,
|
|
43
|
-
},
|
|
44
|
-
]
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const paid = isPaid(order.financialStatus)
|
|
48
|
-
const partiallyFulfilled = order.fulfillmentStatus === 'partially_fulfilled'
|
|
49
|
-
const fulfilled = order.fulfillmentStatus === 'fulfilled'
|
|
50
|
-
|
|
51
|
-
return [
|
|
52
|
-
received,
|
|
53
|
-
{
|
|
54
|
-
id: 'payment',
|
|
55
|
-
label: paid ? 'Pagamento aprovado' : 'Aguardando pagamento',
|
|
56
|
-
description: paid ? 'Compra confirmada' : 'Processando',
|
|
57
|
-
state: paid ? 'complete' : 'current',
|
|
58
|
-
icon: Check,
|
|
59
|
-
},
|
|
60
|
-
{
|
|
61
|
-
id: 'preparing',
|
|
62
|
-
label: 'Preparando envio',
|
|
63
|
-
description: paid ? 'Separando produtos' : 'Começa após o pagamento',
|
|
64
|
-
state: fulfilled || partiallyFulfilled ? 'complete' : paid ? 'current' : 'pending',
|
|
65
|
-
icon: PackageCheck,
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
id: 'delivery',
|
|
69
|
-
label: fulfilled ? 'Pedido enviado' : partiallyFulfilled ? 'Envio parcial' : 'Entrega',
|
|
70
|
-
description: fulfilled ? 'A caminho do cliente' : partiallyFulfilled ? 'Parte do pedido saiu' : 'Próxima etapa',
|
|
71
|
-
state: fulfilled ? 'complete' : partiallyFulfilled ? 'current' : 'pending',
|
|
72
|
-
icon: fulfilled || partiallyFulfilled ? Truck : Clock,
|
|
73
|
-
},
|
|
74
|
-
]
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const GRID_COLS: Record<number, string> = {
|
|
78
|
-
2: 'sm:grid-cols-2',
|
|
79
|
-
3: 'sm:grid-cols-3',
|
|
80
|
-
4: 'sm:grid-cols-4',
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export function OrderTrackingTimeline({ order, compact = false }: { order: Order; compact?: boolean }) {
|
|
84
|
-
const steps = orderTrackingSteps(order)
|
|
85
|
-
|
|
86
|
-
return (
|
|
87
|
-
<ol
|
|
88
|
-
data-testid={TID.orderTracking}
|
|
89
|
-
className={`grid ${compact ? 'gap-2' : 'gap-3'} ${GRID_COLS[steps.length] ?? 'sm:grid-cols-4'}`}
|
|
90
|
-
aria-label="Acompanhamento do pedido"
|
|
91
|
-
>
|
|
92
|
-
{steps.map((step) => {
|
|
93
|
-
const Icon = step.icon
|
|
94
|
-
const cancelled = step.state === 'cancelled'
|
|
95
|
-
const active = step.state !== 'pending'
|
|
96
|
-
return (
|
|
97
|
-
<li
|
|
98
|
-
key={step.id}
|
|
99
|
-
data-testid={TID.orderTrackingStep(step.id)}
|
|
100
|
-
data-state={step.state}
|
|
101
|
-
className={[
|
|
102
|
-
'relative min-w-0 rounded-xl border p-3',
|
|
103
|
-
cancelled
|
|
104
|
-
? 'border-rose-200 bg-rose-50 text-rose-700'
|
|
105
|
-
: active
|
|
106
|
-
? 'border-primary/35 bg-primary/5'
|
|
107
|
-
: 'bg-muted/20 text-muted-foreground',
|
|
108
|
-
compact ? 'text-xs' : 'text-sm',
|
|
109
|
-
].join(' ')}
|
|
110
|
-
>
|
|
111
|
-
<div className="flex items-start gap-2">
|
|
112
|
-
<span
|
|
113
|
-
className={[
|
|
114
|
-
'grid h-7 w-7 shrink-0 place-items-center rounded-full border',
|
|
115
|
-
cancelled
|
|
116
|
-
? 'border-rose-300 bg-rose-500 text-white'
|
|
117
|
-
: active
|
|
118
|
-
? 'border-primary bg-primary text-primary-foreground'
|
|
119
|
-
: 'border-muted-foreground/30 bg-background',
|
|
120
|
-
].join(' ')}
|
|
121
|
-
aria-hidden
|
|
122
|
-
>
|
|
123
|
-
<Icon className="h-3.5 w-3.5" />
|
|
124
|
-
</span>
|
|
125
|
-
<span className="min-w-0">
|
|
126
|
-
<span className="block truncate font-semibold text-foreground">{step.label}</span>
|
|
127
|
-
<span className="mt-0.5 block truncate text-muted-foreground">{step.description}</span>
|
|
128
|
-
</span>
|
|
129
|
-
</div>
|
|
130
|
-
</li>
|
|
131
|
-
)
|
|
132
|
-
})}
|
|
133
|
-
</ol>
|
|
134
|
-
)
|
|
135
|
-
}
|
package/src/components/Price.tsx
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { formatMoney } from '../format'
|
|
3
|
-
import { useStorefrontConfig } from '../config'
|
|
4
|
-
|
|
5
|
-
export interface PriceProps {
|
|
6
|
-
value: number
|
|
7
|
-
compareAt?: number | null
|
|
8
|
-
testId?: string
|
|
9
|
-
compareTestId?: string
|
|
10
|
-
className?: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Formatted money. Always carries data-price so tests assert raw numbers, never locale strings. */
|
|
14
|
-
export function Price({ value, compareAt, testId, compareTestId, className }: PriceProps) {
|
|
15
|
-
const { currency, locale } = useStorefrontConfig()
|
|
16
|
-
return (
|
|
17
|
-
<span className={`inline-flex items-baseline gap-2 ${className ?? ''}`}>
|
|
18
|
-
<span data-testid={testId} data-price={value.toFixed(2)} className="font-semibold">
|
|
19
|
-
{formatMoney(value, currency, locale)}
|
|
20
|
-
</span>
|
|
21
|
-
{compareAt != null && compareAt > value && (
|
|
22
|
-
<span
|
|
23
|
-
data-testid={compareTestId}
|
|
24
|
-
data-price={compareAt.toFixed(2)}
|
|
25
|
-
className="text-sm text-muted-foreground line-through"
|
|
26
|
-
>
|
|
27
|
-
{formatMoney(compareAt, currency, locale)}
|
|
28
|
-
</span>
|
|
29
|
-
)}
|
|
30
|
-
</span>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import { ShoppingBag } from 'lucide-react'
|
|
3
|
-
import { useStorefrontConfig } from '../config'
|
|
4
|
-
import { Link, navigateTo } from '../router'
|
|
5
|
-
import { getProductOptionGroups } from '../product-options'
|
|
6
|
-
import { Price } from './Price'
|
|
7
|
-
import { TID } from '../testids'
|
|
8
|
-
import { productCardComponentContract } from '../component-selectors'
|
|
9
|
-
import { useStorefrontActions } from '../hooks/useStorefront'
|
|
10
|
-
import type { ProductCardProps } from '../component-contracts'
|
|
11
|
-
import { SmoothImage } from './SmoothImage'
|
|
12
|
-
|
|
13
|
-
export function ProductCard({ product, config: providedConfig, actions: providedActions }: ProductCardProps) {
|
|
14
|
-
const runtimeConfig = useStorefrontConfig()
|
|
15
|
-
const runtimeActions = useStorefrontActions()
|
|
16
|
-
const config = providedConfig ?? runtimeConfig
|
|
17
|
-
const actions = providedActions ?? runtimeActions
|
|
18
|
-
const hasOptions = getProductOptionGroups(product).length > 0
|
|
19
|
-
const soldOut = product.inventoryCount <= 0
|
|
20
|
-
const onSale = product.compareAtPrice != null && product.compareAtPrice > product.price
|
|
21
|
-
const image = product.images.find((i) => i.isPrimary) ?? product.images[0]
|
|
22
|
-
const cardStyle = config.theme?.productCard?.style ?? 'card'
|
|
23
|
-
const aspect = config.theme?.productCard?.imageAspect === 'portrait' ? 'aspect-[3/4]' : 'aspect-square'
|
|
24
|
-
const purchaseMode = config.commerceMode === 'checkout'
|
|
25
|
-
|
|
26
|
-
return (
|
|
27
|
-
<div
|
|
28
|
-
{...productCardComponentContract.root}
|
|
29
|
-
data-slug={product.slug}
|
|
30
|
-
className={`group flex flex-col overflow-hidden transition-all duration-300 ${
|
|
31
|
-
cardStyle === 'editorial'
|
|
32
|
-
? 'hover:-translate-y-1'
|
|
33
|
-
: 'border bg-card hover:-translate-y-1 hover:shadow-lg'
|
|
34
|
-
}`}
|
|
35
|
-
style={{ borderRadius: 'var(--sf-radius-card)' }}
|
|
36
|
-
>
|
|
37
|
-
<Link to={`/product/${product.slug}`} className={`relative block ${aspect} overflow-hidden bg-muted`} style={{ borderRadius: cardStyle === 'editorial' ? 'var(--sf-radius-card)' : undefined }}>
|
|
38
|
-
{image && (
|
|
39
|
-
<SmoothImage
|
|
40
|
-
src={image.url}
|
|
41
|
-
alt={image.altText ?? product.name}
|
|
42
|
-
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
|
|
43
|
-
loading="lazy"
|
|
44
|
-
/>
|
|
45
|
-
)}
|
|
46
|
-
<div className="absolute left-3 top-3 flex gap-2">
|
|
47
|
-
{onSale && !soldOut && (
|
|
48
|
-
<span
|
|
49
|
-
data-testid={TID.badgeSale}
|
|
50
|
-
className="rounded-full bg-red-600 px-2.5 py-0.5 text-xs font-semibold text-white"
|
|
51
|
-
>
|
|
52
|
-
Oferta
|
|
53
|
-
</span>
|
|
54
|
-
)}
|
|
55
|
-
{soldOut && (
|
|
56
|
-
<span
|
|
57
|
-
data-testid={TID.badgeSoldout}
|
|
58
|
-
className="rounded-full bg-gray-900/80 px-2.5 py-0.5 text-xs font-semibold text-white"
|
|
59
|
-
>
|
|
60
|
-
Esgotado
|
|
61
|
-
</span>
|
|
62
|
-
)}
|
|
63
|
-
</div>
|
|
64
|
-
</Link>
|
|
65
|
-
|
|
66
|
-
<div className={`flex flex-1 flex-col gap-1 ${cardStyle === 'editorial' ? 'px-1 py-3' : 'p-4'}`}>
|
|
67
|
-
<span className="text-xs text-muted-foreground">{product.categoryName}</span>
|
|
68
|
-
<Link
|
|
69
|
-
to={`/product/${product.slug}`}
|
|
70
|
-
{...productCardComponentContract.name}
|
|
71
|
-
className="font-medium leading-snug hover:underline"
|
|
72
|
-
>
|
|
73
|
-
{product.name}
|
|
74
|
-
</Link>
|
|
75
|
-
<div className="mt-auto flex items-center justify-between gap-3 pt-2">
|
|
76
|
-
{purchaseMode ? (
|
|
77
|
-
<Price
|
|
78
|
-
value={product.price}
|
|
79
|
-
compareAt={product.compareAtPrice}
|
|
80
|
-
testId={productCardComponentContract.priceTestId}
|
|
81
|
-
/>
|
|
82
|
-
) : (
|
|
83
|
-
<span data-testid={productCardComponentContract.priceTestId} className="text-sm text-muted-foreground">
|
|
84
|
-
{config.commerceMode === 'enquiry' ? config.enquiry.label : 'Ver detalhes'}
|
|
85
|
-
</span>
|
|
86
|
-
)}
|
|
87
|
-
<button
|
|
88
|
-
type="button"
|
|
89
|
-
{...productCardComponentContract.addButton}
|
|
90
|
-
disabled={purchaseMode && soldOut}
|
|
91
|
-
aria-label={
|
|
92
|
-
purchaseMode
|
|
93
|
-
? hasOptions
|
|
94
|
-
? `Escolher opções de ${product.name}`
|
|
95
|
-
: `Adicionar ${product.name} ao carrinho`
|
|
96
|
-
: `${config.enquiry.label} ${product.name}`
|
|
97
|
-
}
|
|
98
|
-
onClick={() => {
|
|
99
|
-
if (!purchaseMode) {
|
|
100
|
-
navigateTo(`/product/${product.slug}`)
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
// Products with variants (size/color) must pick an option on the PDP
|
|
104
|
-
// — quick-adding would create an option-less line. Others add inline
|
|
105
|
-
// (addItem auto-opens the cart drawer).
|
|
106
|
-
if (hasOptions) {
|
|
107
|
-
navigateTo(`/product/${product.slug}`)
|
|
108
|
-
return
|
|
109
|
-
}
|
|
110
|
-
actions.addToCart(product)
|
|
111
|
-
}}
|
|
112
|
-
className="rounded-full border bg-background/90 p-2.5 text-foreground shadow-sm transition-all duration-200 hover:scale-110 hover:bg-primary hover:text-primary-foreground hover:shadow disabled:cursor-not-allowed disabled:opacity-40 lg:translate-y-1 lg:opacity-0 lg:group-hover:translate-y-0 lg:group-hover:opacity-100 lg:disabled:opacity-0 lg:group-hover:disabled:opacity-40"
|
|
113
|
-
>
|
|
114
|
-
<ShoppingBag className="h-4 w-4" />
|
|
115
|
-
</button>
|
|
116
|
-
</div>
|
|
117
|
-
</div>
|
|
118
|
-
</div>
|
|
119
|
-
)
|
|
120
|
-
}
|