@fayz-ai/storefront 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/app/createStorefrontApp.d.ts +65 -0
- package/dist/app/createStorefrontApp.d.ts.map +1 -0
- package/dist/{define.d.ts → app/define.d.ts} +3 -3
- package/dist/app/define.d.ts.map +1 -0
- package/dist/app/hydration.d.ts +37 -0
- package/dist/app/hydration.d.ts.map +1 -0
- package/dist/app/prerender.d.ts +57 -0
- package/dist/app/prerender.d.ts.map +1 -0
- package/dist/{router.d.ts → app/router.d.ts} +140 -113
- package/dist/app/router.d.ts.map +1 -0
- package/dist/{scaffold.d.ts → app/scaffold.d.ts} +2 -2
- package/dist/app/scaffold.d.ts.map +1 -0
- package/dist/app/serving.d.ts +70 -0
- package/dist/app/serving.d.ts.map +1 -0
- package/dist/app/static-render.d.ts +46 -0
- package/dist/app/static-render.d.ts.map +1 -0
- package/dist/blocks/block-at-path.d.ts +59 -0
- package/dist/blocks/block-at-path.d.ts.map +1 -0
- package/dist/blocks/page-blocks.d.ts +163 -0
- package/dist/blocks/page-blocks.d.ts.map +1 -0
- package/dist/blocks/page-data.d.ts +105 -0
- package/dist/blocks/page-data.d.ts.map +1 -0
- package/dist/{blocks.d.ts → blocks/registry.d.ts} +1 -1
- package/dist/blocks/registry.d.ts.map +1 -0
- package/dist/{sections.d.ts → blocks/sections.d.ts} +40 -2
- package/dist/blocks/sections.d.ts.map +1 -0
- package/dist/chunk-6IXTENLB.js +162 -0
- package/dist/chunk-6IXTENLB.js.map +1 -0
- package/dist/chunk-SX3J3QIT.js +11046 -0
- package/dist/chunk-SX3J3QIT.js.map +1 -0
- package/dist/components/PaymentTerms.d.ts +34 -0
- package/dist/components/PaymentTerms.d.ts.map +1 -0
- package/dist/components/Skeleton.d.ts +17 -0
- package/dist/components/Skeleton.d.ts.map +1 -0
- package/dist/components/SmoothImage.d.ts +1 -1
- package/dist/components/SmoothImage.d.ts.map +1 -1
- package/dist/components/account/CustomerAccountShell.d.ts +17 -0
- package/dist/components/account/CustomerAccountShell.d.ts.map +1 -0
- package/dist/components/account/SignInModal.d.ts.map +1 -0
- package/dist/components/account/section.d.ts +15 -0
- package/dist/components/account/section.d.ts.map +1 -0
- package/dist/components/cart/CartDrawer.d.ts.map +1 -0
- package/dist/components/cart/QuantityInput.d.ts.map +1 -0
- package/dist/components/checkout/CheckoutPayment.d.ts +17 -0
- package/dist/components/checkout/CheckoutPayment.d.ts.map +1 -0
- package/dist/components/checkout/CheckoutSteps.d.ts +9 -0
- package/dist/components/checkout/CheckoutSteps.d.ts.map +1 -0
- package/dist/components/checkout/DeliveryEstimator.d.ts.map +1 -0
- package/dist/components/collection/CategoryShowcase.d.ts +44 -0
- package/dist/components/collection/CategoryShowcase.d.ts.map +1 -0
- package/dist/components/collection/FiltersPanel.d.ts +19 -0
- package/dist/components/collection/FiltersPanel.d.ts.map +1 -0
- package/dist/components/collection/ProductGrid.d.ts.map +1 -0
- package/dist/components/layout/PageBodyBlocks.d.ts +40 -0
- package/dist/components/layout/PageBodyBlocks.d.ts.map +1 -0
- package/dist/components/layout/SearchOverlay.d.ts +13 -0
- package/dist/components/layout/SearchOverlay.d.ts.map +1 -0
- package/dist/components/layout/StorefrontErrorBoundary.d.ts.map +1 -0
- package/dist/components/layout/StorefrontFooter.d.ts.map +1 -0
- package/dist/components/layout/StorefrontHeader.d.ts.map +1 -0
- package/dist/components/layout/Toaster.d.ts.map +1 -0
- package/dist/components/marketing/CollectionSections.d.ts +463 -0
- package/dist/components/marketing/CollectionSections.d.ts.map +1 -0
- package/dist/components/marketing/ConversionSections.d.ts +221 -0
- package/dist/components/marketing/ConversionSections.d.ts.map +1 -0
- package/dist/components/marketing/EditorialSections.d.ts +248 -0
- package/dist/components/marketing/EditorialSections.d.ts.map +1 -0
- package/dist/components/marketing/EntityList.d.ts +97 -0
- package/dist/components/marketing/EntityList.d.ts.map +1 -0
- package/dist/components/marketing/FormBlock.d.ts +167 -0
- package/dist/components/marketing/FormBlock.d.ts.map +1 -0
- package/dist/components/marketing/HeroSection.d.ts +125 -0
- package/dist/components/marketing/HeroSection.d.ts.map +1 -0
- package/dist/components/marketing/MediaCarousel.d.ts +135 -0
- package/dist/components/marketing/MediaCarousel.d.ts.map +1 -0
- package/dist/components/marketing/MiscSections.d.ts +252 -0
- package/dist/components/marketing/MiscSections.d.ts.map +1 -0
- package/dist/components/order/OrderTrackingTimeline.d.ts.map +1 -0
- package/dist/components/order/SuccessMark.d.ts +10 -0
- package/dist/components/order/SuccessMark.d.ts.map +1 -0
- package/dist/components/product/ProductCard.d.ts +14 -0
- package/dist/components/product/ProductCard.d.ts.map +1 -0
- package/dist/components/product/ProductEnquiryForm.d.ts.map +1 -0
- package/dist/components/product/ProductGallery.d.ts +36 -0
- package/dist/components/product/ProductGallery.d.ts.map +1 -0
- package/dist/components/{ProductOptionSelector.d.ts → product/ProductOptionSelector.d.ts} +1 -1
- package/dist/components/product/ProductOptionSelector.d.ts.map +1 -0
- package/dist/components/product/ProductRail.d.ts +93 -0
- package/dist/components/product/ProductRail.d.ts.map +1 -0
- package/dist/components/product/ProductReviews.d.ts +39 -0
- package/dist/components/product/ProductReviews.d.ts.map +1 -0
- package/dist/components/product/ProductSlider.d.ts +169 -0
- package/dist/components/product/ProductSlider.d.ts.map +1 -0
- package/dist/components/product/ProductSpecs.d.ts.map +1 -0
- package/dist/components/product/RelatedProducts.d.ts.map +1 -0
- package/dist/{auth.d.ts → data/auth.d.ts} +9 -0
- package/dist/data/auth.d.ts.map +1 -0
- package/dist/data/facets.d.ts +39 -0
- package/dist/data/facets.d.ts.map +1 -0
- package/dist/data/loaders.d.ts +52 -0
- package/dist/data/loaders.d.ts.map +1 -0
- package/dist/data/local-orders.d.ts +5 -0
- package/dist/data/local-orders.d.ts.map +1 -0
- package/dist/data/order-status.d.ts.map +1 -0
- package/dist/data/product-options.d.ts +36 -0
- package/dist/data/product-options.d.ts.map +1 -0
- package/dist/data/shipping-items.d.ts +20 -0
- package/dist/data/shipping-items.d.ts.map +1 -0
- package/dist/diagnostics/seo-audit.d.ts +54 -0
- package/dist/diagnostics/seo-audit.d.ts.map +1 -0
- package/dist/diagnostics/seo-audit.js +3 -0
- package/dist/diagnostics/seo-audit.js.map +1 -0
- package/dist/document/change.d.ts +100 -0
- package/dist/document/change.d.ts.map +1 -0
- package/dist/document/config.d.ts +311 -0
- package/dist/document/config.d.ts.map +1 -0
- package/dist/document/describe.d.ts +135 -0
- package/dist/document/describe.d.ts.map +1 -0
- package/dist/document/overlay.d.ts +58 -0
- package/dist/document/overlay.d.ts.map +1 -0
- package/dist/{presets.d.ts → document/presets.d.ts} +2 -2
- package/dist/document/presets.d.ts.map +1 -0
- package/dist/document/render.d.ts +171 -0
- package/dist/document/render.d.ts.map +1 -0
- package/dist/document/round-trip.d.ts +46 -0
- package/dist/document/round-trip.d.ts.map +1 -0
- package/dist/document/sections.d.ts +194 -0
- package/dist/document/sections.d.ts.map +1 -0
- package/dist/document/store-policy.d.ts +29 -0
- package/dist/document/store-policy.d.ts.map +1 -0
- package/dist/document/upgrade.d.ts +112 -0
- package/dist/document/upgrade.d.ts.map +1 -0
- package/dist/document/validate.d.ts +24 -0
- package/dist/document/validate.d.ts.map +1 -0
- package/dist/{component-contracts.d.ts → extend/component-contracts.d.ts} +74 -22
- package/dist/extend/component-contracts.d.ts.map +1 -0
- package/dist/extend/component-override.d.ts +38 -0
- package/dist/extend/component-override.d.ts.map +1 -0
- package/dist/extend/component-selectors.d.ts.map +1 -0
- package/dist/extend/editor-bridge.d.ts +59 -0
- package/dist/extend/editor-bridge.d.ts.map +1 -0
- package/dist/extend/extension-points.d.ts +103 -0
- package/dist/extend/extension-points.d.ts.map +1 -0
- package/dist/extend/slots.d.ts +216 -0
- package/dist/extend/slots.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +25 -6
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/use-address.d.ts +26 -0
- package/dist/hooks/use-address.d.ts.map +1 -0
- package/dist/hooks/use-cart.d.ts +3 -0
- package/dist/hooks/use-cart.d.ts.map +1 -0
- package/dist/hooks/use-catalog.d.ts +24 -0
- package/dist/hooks/use-catalog.d.ts.map +1 -0
- package/dist/hooks/use-categories.d.ts +19 -0
- package/dist/hooks/use-categories.d.ts.map +1 -0
- package/dist/hooks/use-checkout.d.ts +41 -0
- package/dist/hooks/use-checkout.d.ts.map +1 -0
- package/dist/hooks/use-discount-validator.d.ts +20 -0
- package/dist/hooks/use-discount-validator.d.ts.map +1 -0
- package/dist/hooks/use-enquiry.d.ts +7 -0
- package/dist/hooks/use-enquiry.d.ts.map +1 -0
- package/dist/hooks/{useMyOrders.d.ts → use-my-orders.d.ts} +1 -1
- package/dist/hooks/use-my-orders.d.ts.map +1 -0
- package/dist/hooks/use-order.d.ts +17 -0
- package/dist/hooks/use-order.d.ts.map +1 -0
- package/dist/hooks/use-payment-methods.d.ts +19 -0
- package/dist/hooks/use-payment-methods.d.ts.map +1 -0
- package/dist/hooks/use-policies.d.ts +25 -0
- package/dist/hooks/use-policies.d.ts.map +1 -0
- package/dist/hooks/use-product.d.ts +15 -0
- package/dist/hooks/use-product.d.ts.map +1 -0
- package/dist/hooks/{useProducts.d.ts → use-products.d.ts} +1 -1
- package/dist/hooks/use-products.d.ts.map +1 -0
- package/dist/hooks/use-region.d.ts +15 -0
- package/dist/hooks/use-region.d.ts.map +1 -0
- package/dist/hooks/use-shipping.d.ts +32 -0
- package/dist/hooks/use-shipping.d.ts.map +1 -0
- package/dist/hooks/use-storefront-actions.d.ts +12 -0
- package/dist/hooks/use-storefront-actions.d.ts.map +1 -0
- package/dist/hooks/use-storefront-head.d.ts +19 -0
- package/dist/hooks/use-storefront-head.d.ts.map +1 -0
- package/dist/hooks/use-storefront.d.ts +3 -0
- package/dist/hooks/use-storefront.d.ts.map +1 -0
- package/dist/index.d.ts +124 -54
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1310 -5010
- package/dist/index.js.map +1 -1
- package/dist/lib/errors.d.ts +9 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/format.d.ts.map +1 -0
- package/dist/lib/motion.d.ts.map +1 -0
- package/dist/lib/seo.d.ts +26 -0
- package/dist/lib/seo.d.ts.map +1 -0
- package/dist/{testids.d.ts → lib/testids.d.ts} +33 -0
- package/dist/lib/testids.d.ts.map +1 -0
- package/dist/{theme.d.ts → lib/theme.d.ts} +41 -3
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/render.d.ts +5 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +359 -0
- package/dist/render.js.map +1 -0
- package/dist/stores/cart.store.d.ts +34 -2
- package/dist/stores/cart.store.d.ts.map +1 -1
- package/dist/stores/catalog.store.d.ts +11 -0
- package/dist/stores/catalog.store.d.ts.map +1 -1
- package/dist/stores/delivery.store.d.ts +9 -0
- package/dist/stores/delivery.store.d.ts.map +1 -1
- package/dist/views/CatalogPage.d.ts.map +1 -0
- package/dist/views/CheckoutPage.d.ts.map +1 -0
- package/dist/views/ContentPage.d.ts +11 -0
- package/dist/views/ContentPage.d.ts.map +1 -0
- package/dist/views/HomePage.d.ts +9 -0
- package/dist/views/HomePage.d.ts.map +1 -0
- package/dist/views/MyPurchasesPage.d.ts.map +1 -0
- package/dist/views/NotFoundPage.d.ts.map +1 -0
- package/dist/views/OrderConfirmationPage.d.ts.map +1 -0
- package/dist/{pages → views}/PolicyPage.d.ts.map +1 -1
- package/dist/views/ProductDetailPage.d.ts.map +1 -0
- package/dist/views/StorefrontPage.d.ts +35 -0
- package/dist/views/StorefrontPage.d.ts.map +1 -0
- package/dist/workflows/checkout.d.ts +1 -1
- package/dist/workflows/checkout.d.ts.map +1 -1
- package/package.json +21 -14
- package/dist/auth.d.ts.map +0 -1
- package/dist/blocks.d.ts.map +0 -1
- package/dist/component-contracts.d.ts.map +0 -1
- package/dist/component-selectors.d.ts.map +0 -1
- package/dist/components/CartDrawer.d.ts.map +0 -1
- package/dist/components/CustomerAccountShell.d.ts +0 -13
- package/dist/components/CustomerAccountShell.d.ts.map +0 -1
- package/dist/components/DeliveryEstimator.d.ts.map +0 -1
- package/dist/components/FiltersPanel.d.ts +0 -3
- package/dist/components/FiltersPanel.d.ts.map +0 -1
- package/dist/components/OrderTrackingTimeline.d.ts.map +0 -1
- package/dist/components/ProductCard.d.ts +0 -4
- package/dist/components/ProductCard.d.ts.map +0 -1
- package/dist/components/ProductEnquiryForm.d.ts.map +0 -1
- package/dist/components/ProductGallery.d.ts +0 -22
- package/dist/components/ProductGallery.d.ts.map +0 -1
- package/dist/components/ProductGrid.d.ts.map +0 -1
- package/dist/components/ProductOptionSelector.d.ts.map +0 -1
- package/dist/components/ProductSpecs.d.ts.map +0 -1
- package/dist/components/QuantityInput.d.ts.map +0 -1
- package/dist/components/RelatedProducts.d.ts.map +0 -1
- package/dist/components/SignInModal.d.ts.map +0 -1
- package/dist/components/StorefrontErrorBoundary.d.ts.map +0 -1
- package/dist/components/StorefrontFooter.d.ts.map +0 -1
- package/dist/components/StorefrontHeader.d.ts.map +0 -1
- package/dist/components/Toaster.d.ts.map +0 -1
- package/dist/components/sections/CategoryShowcase.d.ts +0 -6
- package/dist/components/sections/CategoryShowcase.d.ts.map +0 -1
- package/dist/components/sections/HeroSection.d.ts +0 -9
- package/dist/components/sections/HeroSection.d.ts.map +0 -1
- package/dist/components/sections/MediaCarousel.d.ts +0 -11
- package/dist/components/sections/MediaCarousel.d.ts.map +0 -1
- package/dist/components/sections/MiscSections.d.ts +0 -33
- package/dist/components/sections/MiscSections.d.ts.map +0 -1
- package/dist/components/sections/ProductRail.d.ts +0 -9
- package/dist/components/sections/ProductRail.d.ts.map +0 -1
- package/dist/components/sections/ProductSlider.d.ts +0 -15
- package/dist/components/sections/ProductSlider.d.ts.map +0 -1
- package/dist/config.d.ts +0 -174
- package/dist/config.d.ts.map +0 -1
- package/dist/createStorefrontApp.d.ts +0 -28
- package/dist/createStorefrontApp.d.ts.map +0 -1
- package/dist/define.d.ts.map +0 -1
- package/dist/format.d.ts.map +0 -1
- package/dist/hooks/useCategories.d.ts +0 -6
- package/dist/hooks/useCategories.d.ts.map +0 -1
- package/dist/hooks/useDiscountValidator.d.ts +0 -14
- package/dist/hooks/useDiscountValidator.d.ts.map +0 -1
- package/dist/hooks/useMyOrders.d.ts.map +0 -1
- package/dist/hooks/useProduct.d.ts +0 -7
- package/dist/hooks/useProduct.d.ts.map +0 -1
- package/dist/hooks/useProducts.d.ts.map +0 -1
- package/dist/hooks/useStorefront.d.ts +0 -35
- package/dist/hooks/useStorefront.d.ts.map +0 -1
- package/dist/hooks/useStorefrontHead.d.ts +0 -11
- package/dist/hooks/useStorefrontHead.d.ts.map +0 -1
- package/dist/motion.d.ts.map +0 -1
- package/dist/order-status.d.ts.map +0 -1
- package/dist/pages/CatalogPage.d.ts.map +0 -1
- package/dist/pages/CheckoutPage.d.ts.map +0 -1
- package/dist/pages/HomePage.d.ts +0 -9
- package/dist/pages/HomePage.d.ts.map +0 -1
- package/dist/pages/MyPurchasesPage.d.ts.map +0 -1
- package/dist/pages/NotFoundPage.d.ts.map +0 -1
- package/dist/pages/OrderConfirmationPage.d.ts.map +0 -1
- package/dist/pages/ProductDetailPage.d.ts.map +0 -1
- package/dist/presets.d.ts.map +0 -1
- package/dist/product-options.d.ts +0 -12
- package/dist/product-options.d.ts.map +0 -1
- package/dist/router.d.ts.map +0 -1
- package/dist/scaffold.d.ts.map +0 -1
- package/dist/sections.d.ts.map +0 -1
- package/dist/testids.d.ts.map +0 -1
- package/dist/theme.d.ts.map +0 -1
- package/src/auth.ts +0 -227
- package/src/blocks.tsx +0 -51
- package/src/catalog.ts +0 -3
- package/src/component-contracts.ts +0 -154
- package/src/component-selectors.ts +0 -31
- package/src/components/CartDrawer.tsx +0 -279
- package/src/components/CustomerAccountShell.tsx +0 -102
- package/src/components/DeliveryEstimator.tsx +0 -157
- package/src/components/FiltersPanel.tsx +0 -109
- package/src/components/OrderTrackingTimeline.tsx +0 -135
- package/src/components/Price.tsx +0 -32
- package/src/components/ProductCard.tsx +0 -120
- package/src/components/ProductEnquiryForm.tsx +0 -175
- package/src/components/ProductGallery.tsx +0 -174
- package/src/components/ProductGrid.tsx +0 -43
- package/src/components/ProductOptionSelector.tsx +0 -50
- package/src/components/ProductSpecs.tsx +0 -75
- package/src/components/QuantityInput.tsx +0 -52
- package/src/components/RelatedProducts.tsx +0 -40
- package/src/components/SignInModal.tsx +0 -120
- package/src/components/SmoothImage.tsx +0 -59
- package/src/components/StorefrontErrorBoundary.tsx +0 -51
- package/src/components/StorefrontFooter.tsx +0 -110
- package/src/components/StorefrontHeader.tsx +0 -477
- package/src/components/Toaster.tsx +0 -60
- package/src/components/sections/CategoryShowcase.tsx +0 -80
- package/src/components/sections/HeroSection.tsx +0 -193
- package/src/components/sections/MediaCarousel.tsx +0 -141
- package/src/components/sections/MiscSections.tsx +0 -158
- package/src/components/sections/ProductRail.tsx +0 -81
- package/src/components/sections/ProductSlider.tsx +0 -119
- package/src/config.ts +0 -229
- package/src/createStorefrontApp.tsx +0 -247
- package/src/define.ts +0 -34
- package/src/format.ts +0 -7
- package/src/hooks/index.ts +0 -6
- package/src/hooks/useCategories.ts +0 -26
- package/src/hooks/useDiscountValidator.ts +0 -43
- package/src/hooks/useMyOrders.ts +0 -41
- package/src/hooks/useProduct.ts +0 -36
- package/src/hooks/useProducts.ts +0 -39
- package/src/hooks/useStorefront.ts +0 -58
- package/src/hooks/useStorefrontHead.ts +0 -25
- package/src/index.ts +0 -202
- package/src/motion.tsx +0 -94
- package/src/order-status.ts +0 -33
- package/src/pages/CatalogPage.tsx +0 -118
- package/src/pages/CheckoutPage.tsx +0 -689
- package/src/pages/HomePage.tsx +0 -16
- package/src/pages/MyPurchasesPage.tsx +0 -311
- package/src/pages/NotFoundPage.tsx +0 -25
- package/src/pages/OrderConfirmationPage.tsx +0 -190
- package/src/pages/PolicyPage.tsx +0 -47
- package/src/pages/ProductDetailPage.tsx +0 -264
- package/src/presets.ts +0 -324
- package/src/product-options.ts +0 -63
- package/src/router.ts +0 -49
- package/src/scaffold.tsx +0 -115
- package/src/sections.ts +0 -116
- package/src/shop.ts +0 -4
- package/src/stores/cart.store.ts +0 -185
- package/src/stores/catalog.store.ts +0 -39
- package/src/stores/delivery.store.ts +0 -152
- package/src/stores/session.store.ts +0 -28
- package/src/stores/toast.store.ts +0 -43
- package/src/testids.ts +0 -136
- package/src/theme.ts +0 -194
- package/src/ui.ts +0 -5
- package/src/workflows/checkout.ts +0 -119
- /package/dist/components/{SignInModal.d.ts → account/SignInModal.d.ts} +0 -0
- /package/dist/components/{CartDrawer.d.ts → cart/CartDrawer.d.ts} +0 -0
- /package/dist/components/{QuantityInput.d.ts → cart/QuantityInput.d.ts} +0 -0
- /package/dist/components/{DeliveryEstimator.d.ts → checkout/DeliveryEstimator.d.ts} +0 -0
- /package/dist/components/{ProductGrid.d.ts → collection/ProductGrid.d.ts} +0 -0
- /package/dist/components/{StorefrontErrorBoundary.d.ts → layout/StorefrontErrorBoundary.d.ts} +0 -0
- /package/dist/components/{StorefrontFooter.d.ts → layout/StorefrontFooter.d.ts} +0 -0
- /package/dist/components/{StorefrontHeader.d.ts → layout/StorefrontHeader.d.ts} +0 -0
- /package/dist/components/{Toaster.d.ts → layout/Toaster.d.ts} +0 -0
- /package/dist/components/{OrderTrackingTimeline.d.ts → order/OrderTrackingTimeline.d.ts} +0 -0
- /package/dist/components/{ProductEnquiryForm.d.ts → product/ProductEnquiryForm.d.ts} +0 -0
- /package/dist/components/{ProductSpecs.d.ts → product/ProductSpecs.d.ts} +0 -0
- /package/dist/components/{RelatedProducts.d.ts → product/RelatedProducts.d.ts} +0 -0
- /package/dist/{order-status.d.ts → data/order-status.d.ts} +0 -0
- /package/dist/{component-selectors.d.ts → extend/component-selectors.d.ts} +0 -0
- /package/dist/{format.d.ts → lib/format.d.ts} +0 -0
- /package/dist/{motion.d.ts → lib/motion.d.ts} +0 -0
- /package/dist/{pages → views}/CatalogPage.d.ts +0 -0
- /package/dist/{pages → views}/CheckoutPage.d.ts +0 -0
- /package/dist/{pages → views}/MyPurchasesPage.d.ts +0 -0
- /package/dist/{pages → views}/NotFoundPage.d.ts +0 -0
- /package/dist/{pages → views}/OrderConfirmationPage.d.ts +0 -0
- /package/dist/{pages → views}/PolicyPage.d.ts +0 -0
- /package/dist/{pages → views}/ProductDetailPage.d.ts +0 -0
|
@@ -1,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
|
-
}
|
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import React, { useState } from 'react'
|
|
2
|
-
import { Mail, MessageCircle, Send } from 'lucide-react'
|
|
3
|
-
import { getShopProvider } from '@fayz-ai/shop/runtime'
|
|
4
|
-
import type { Product } from '@fayz-ai/shop/types'
|
|
5
|
-
import { useStorefrontConfig } from '../config'
|
|
6
|
-
import { storefrontComponentContracts } from '../component-selectors'
|
|
7
|
-
import { TID } from '../testids'
|
|
8
|
-
|
|
9
|
-
export interface ProductEnquiryFormProps {
|
|
10
|
-
product: Product
|
|
11
|
-
onSuccess?: () => void
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function defaultSubject(prefix: string, product: Product): string {
|
|
15
|
-
return `${prefix}: ${product.name}`
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function buildWhatsAppHref(url: string, product: Product): string {
|
|
19
|
-
const text = encodeURIComponent(`Hello, I would like more information about ${product.name}.`)
|
|
20
|
-
const sep = url.includes('?') ? '&' : '?'
|
|
21
|
-
return `${url}${sep}text=${text}`
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function ProductEnquiryForm({ product, onSuccess }: ProductEnquiryFormProps) {
|
|
25
|
-
const config = useStorefrontConfig()
|
|
26
|
-
const [customerName, setCustomerName] = useState('')
|
|
27
|
-
const [customerEmail, setCustomerEmail] = useState('')
|
|
28
|
-
const [customerPhone, setCustomerPhone] = useState('')
|
|
29
|
-
const [subject, setSubject] = useState(defaultSubject(config.enquiry.subjectPrefix, product))
|
|
30
|
-
const [message, setMessage] = useState(`I would like more information about ${product.name}.`)
|
|
31
|
-
const [status, setStatus] = useState<'idle' | 'submitting' | 'success' | 'error'>('idle')
|
|
32
|
-
const [error, setError] = useState<string | null>(null)
|
|
33
|
-
|
|
34
|
-
const submit = async (event: React.FormEvent) => {
|
|
35
|
-
event.preventDefault()
|
|
36
|
-
setStatus('submitting')
|
|
37
|
-
setError(null)
|
|
38
|
-
try {
|
|
39
|
-
const provider = getShopProvider()
|
|
40
|
-
if (provider.createProductEnquiry) {
|
|
41
|
-
await provider.createProductEnquiry({
|
|
42
|
-
productId: product.id,
|
|
43
|
-
productName: product.name,
|
|
44
|
-
productSlug: product.slug,
|
|
45
|
-
customerName,
|
|
46
|
-
customerEmail,
|
|
47
|
-
customerPhone: customerPhone || undefined,
|
|
48
|
-
subject,
|
|
49
|
-
message,
|
|
50
|
-
sourceUrl: typeof window === 'undefined' ? undefined : window.location.href,
|
|
51
|
-
metadata: { sku: product.sku, categoryName: product.categoryName },
|
|
52
|
-
})
|
|
53
|
-
} else if (config.enquiry.email) {
|
|
54
|
-
window.location.href = `mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`
|
|
55
|
-
}
|
|
56
|
-
setStatus('success')
|
|
57
|
-
onSuccess?.()
|
|
58
|
-
} catch (e) {
|
|
59
|
-
setStatus('error')
|
|
60
|
-
setError(e instanceof Error ? e.message : String(e))
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return (
|
|
65
|
-
<form
|
|
66
|
-
{...storefrontComponentContracts.productDetail.enquiryForm}
|
|
67
|
-
onSubmit={submit}
|
|
68
|
-
className="space-y-4"
|
|
69
|
-
>
|
|
70
|
-
<div className="grid gap-4 sm:grid-cols-2">
|
|
71
|
-
<label className="space-y-1.5 text-sm font-medium">
|
|
72
|
-
<span>Your name</span>
|
|
73
|
-
<input
|
|
74
|
-
data-testid={TID.enquiryName}
|
|
75
|
-
required
|
|
76
|
-
value={customerName}
|
|
77
|
-
onChange={(e) => setCustomerName(e.target.value)}
|
|
78
|
-
className="w-full border bg-background px-3 py-2 outline-none focus:border-primary"
|
|
79
|
-
style={{ borderRadius: 'var(--sf-radius-input)' }}
|
|
80
|
-
/>
|
|
81
|
-
</label>
|
|
82
|
-
<label className="space-y-1.5 text-sm font-medium">
|
|
83
|
-
<span>Your email</span>
|
|
84
|
-
<input
|
|
85
|
-
data-testid={TID.enquiryEmail}
|
|
86
|
-
required
|
|
87
|
-
type="email"
|
|
88
|
-
value={customerEmail}
|
|
89
|
-
onChange={(e) => setCustomerEmail(e.target.value)}
|
|
90
|
-
className="w-full border bg-background px-3 py-2 outline-none focus:border-primary"
|
|
91
|
-
style={{ borderRadius: 'var(--sf-radius-input)' }}
|
|
92
|
-
/>
|
|
93
|
-
</label>
|
|
94
|
-
</div>
|
|
95
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
96
|
-
<span>Phone{config.enquiry.requirePhone ? '' : ' (optional)'}</span>
|
|
97
|
-
<input
|
|
98
|
-
data-testid={TID.enquiryPhone}
|
|
99
|
-
required={config.enquiry.requirePhone}
|
|
100
|
-
value={customerPhone}
|
|
101
|
-
onChange={(e) => setCustomerPhone(e.target.value)}
|
|
102
|
-
className="w-full border bg-background px-3 py-2 outline-none focus:border-primary"
|
|
103
|
-
style={{ borderRadius: 'var(--sf-radius-input)' }}
|
|
104
|
-
/>
|
|
105
|
-
</label>
|
|
106
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
107
|
-
<span>Subject</span>
|
|
108
|
-
<input
|
|
109
|
-
data-testid={TID.enquirySubject}
|
|
110
|
-
required
|
|
111
|
-
value={subject}
|
|
112
|
-
onChange={(e) => setSubject(e.target.value)}
|
|
113
|
-
className="w-full border bg-background px-3 py-2 outline-none focus:border-primary"
|
|
114
|
-
style={{ borderRadius: 'var(--sf-radius-input)' }}
|
|
115
|
-
/>
|
|
116
|
-
</label>
|
|
117
|
-
<label className="block space-y-1.5 text-sm font-medium">
|
|
118
|
-
<span>Message</span>
|
|
119
|
-
<textarea
|
|
120
|
-
data-testid={TID.enquiryMessage}
|
|
121
|
-
required
|
|
122
|
-
rows={5}
|
|
123
|
-
value={message}
|
|
124
|
-
onChange={(e) => setMessage(e.target.value)}
|
|
125
|
-
className="w-full resize-none border bg-background px-3 py-2 outline-none focus:border-primary"
|
|
126
|
-
style={{ borderRadius: 'var(--sf-radius-input)' }}
|
|
127
|
-
/>
|
|
128
|
-
</label>
|
|
129
|
-
<div className="flex flex-wrap items-center gap-3">
|
|
130
|
-
<button
|
|
131
|
-
data-testid={TID.enquirySubmit}
|
|
132
|
-
type="submit"
|
|
133
|
-
disabled={status === 'submitting'}
|
|
134
|
-
className="sf-cta inline-flex items-center gap-2 bg-primary px-5 py-3 font-semibold text-primary-foreground disabled:cursor-wait disabled:opacity-60"
|
|
135
|
-
style={{ borderRadius: 'var(--sf-radius-button)' }}
|
|
136
|
-
>
|
|
137
|
-
<Send className="h-4 w-4" />
|
|
138
|
-
{status === 'submitting' ? 'Sending...' : 'Send enquiry'}
|
|
139
|
-
</button>
|
|
140
|
-
{config.enquiry.whatsappUrl && (
|
|
141
|
-
<a
|
|
142
|
-
href={buildWhatsAppHref(config.enquiry.whatsappUrl, product)}
|
|
143
|
-
target="_blank"
|
|
144
|
-
rel="noreferrer"
|
|
145
|
-
className="inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted"
|
|
146
|
-
style={{ borderRadius: 'var(--sf-radius-button)' }}
|
|
147
|
-
>
|
|
148
|
-
<MessageCircle className="h-4 w-4" />
|
|
149
|
-
WhatsApp
|
|
150
|
-
</a>
|
|
151
|
-
)}
|
|
152
|
-
{config.enquiry.email && (
|
|
153
|
-
<a
|
|
154
|
-
href={`mailto:${config.enquiry.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(message)}`}
|
|
155
|
-
className="inline-flex items-center gap-2 border px-5 py-3 text-sm font-semibold transition-colors hover:bg-muted"
|
|
156
|
-
style={{ borderRadius: 'var(--sf-radius-button)' }}
|
|
157
|
-
>
|
|
158
|
-
<Mail className="h-4 w-4" />
|
|
159
|
-
Email
|
|
160
|
-
</a>
|
|
161
|
-
)}
|
|
162
|
-
</div>
|
|
163
|
-
{status === 'success' && (
|
|
164
|
-
<p data-testid={TID.enquirySuccess} className="text-sm font-medium text-emerald-600">
|
|
165
|
-
{config.enquiry.successMessage}
|
|
166
|
-
</p>
|
|
167
|
-
)}
|
|
168
|
-
{error && (
|
|
169
|
-
<p data-testid={TID.enquiryError} className="text-sm font-medium text-red-600">
|
|
170
|
-
{error}
|
|
171
|
-
</p>
|
|
172
|
-
)}
|
|
173
|
-
</form>
|
|
174
|
-
)
|
|
175
|
-
}
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useRef, useState } from 'react'
|
|
2
|
-
import type { ProductImage } from '@fayz-ai/shop/types'
|
|
3
|
-
import type { ProductGalleryProps } from '../component-contracts'
|
|
4
|
-
import { SmoothImage } from './SmoothImage'
|
|
5
|
-
import { TID } from '../testids'
|
|
6
|
-
import { storefrontComponentContracts } from '../component-selectors'
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Product gallery: one framed image, hover-to-zoom, and a thumbnail strip when
|
|
10
|
-
* the product has more than one photo.
|
|
11
|
-
*
|
|
12
|
-
* It replaces a bare <img> that rendered only the primary image. Three things
|
|
13
|
-
* were wrong with that:
|
|
14
|
-
*
|
|
15
|
-
* · the frame was a grid child with the default `stretch`, so it grew to the
|
|
16
|
-
* height of the (much taller) details column and left a slab of empty dark
|
|
17
|
-
* box under a square photo;
|
|
18
|
-
* · `plg_shop_product_images` has always been a collection with sort_order and
|
|
19
|
-
* is_primary, and every extra photo a merchant uploaded was invisible;
|
|
20
|
-
* · zooming is table stakes for anything sold by its looks — food especially.
|
|
21
|
-
*
|
|
22
|
-
* Zoom is pointer-driven: the transform origin follows the cursor, so the part
|
|
23
|
-
* under the pointer is the part magnified. Disabled on coarse pointers, where
|
|
24
|
-
* there is no hover and a stuck 2× would just be a broken image.
|
|
25
|
-
*/
|
|
26
|
-
export function ProductGallery({ product, images, primaryImage }: ProductGalleryProps) {
|
|
27
|
-
const ordered = orderImages(images, primaryImage)
|
|
28
|
-
const [index, setIndex] = useState(0)
|
|
29
|
-
const [zoom, setZoom] = useState<{ x: number; y: number } | null>(null)
|
|
30
|
-
const frameRef = useRef<HTMLDivElement>(null)
|
|
31
|
-
const canHover = useCanHover()
|
|
32
|
-
|
|
33
|
-
// A different product (client-side navigation) must not keep the previous
|
|
34
|
-
// product's selected slide.
|
|
35
|
-
useEffect(() => { setIndex(0); setZoom(null) }, [product.id])
|
|
36
|
-
|
|
37
|
-
const current = ordered[Math.min(index, ordered.length - 1)]
|
|
38
|
-
|
|
39
|
-
function trackPointer(event: React.MouseEvent<HTMLDivElement>) {
|
|
40
|
-
if (!canHover) return
|
|
41
|
-
const frame = frameRef.current
|
|
42
|
-
if (!frame) return
|
|
43
|
-
const rect = frame.getBoundingClientRect()
|
|
44
|
-
setZoom({
|
|
45
|
-
x: ((event.clientX - rect.left) / rect.width) * 100,
|
|
46
|
-
y: ((event.clientY - rect.top) / rect.height) * 100,
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function step(delta: number) {
|
|
51
|
-
if (ordered.length < 2) return
|
|
52
|
-
setIndex((current) => (current + delta + ordered.length) % ordered.length)
|
|
53
|
-
setZoom(null)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<div className="flex flex-col gap-3 self-start">
|
|
58
|
-
<div
|
|
59
|
-
{...storefrontComponentContracts.productDetail.gallery}
|
|
60
|
-
ref={frameRef}
|
|
61
|
-
// `self-start` above and a fixed aspect here: the frame is sized by the
|
|
62
|
-
// image, never by whatever the column beside it happens to contain.
|
|
63
|
-
className="group relative aspect-square w-full overflow-hidden border bg-muted"
|
|
64
|
-
style={{ borderRadius: 'var(--sf-radius-card)' }}
|
|
65
|
-
onMouseMove={trackPointer}
|
|
66
|
-
onMouseLeave={() => setZoom(null)}
|
|
67
|
-
onKeyDown={(event) => {
|
|
68
|
-
if (event.key === 'ArrowRight') step(1)
|
|
69
|
-
if (event.key === 'ArrowLeft') step(-1)
|
|
70
|
-
}}
|
|
71
|
-
tabIndex={ordered.length > 1 ? 0 : -1}
|
|
72
|
-
role={ordered.length > 1 ? 'group' : undefined}
|
|
73
|
-
aria-label={ordered.length > 1 ? `Imagens de ${product.name}` : undefined}
|
|
74
|
-
>
|
|
75
|
-
{/*
|
|
76
|
-
The transform lives on a wrapper, not on the image: SmoothImage writes
|
|
77
|
-
its own inline `transition` for the load fade (SmoothImage.tsx:52,
|
|
78
|
-
spread AFTER any style passed in), which would have stretched the zoom
|
|
79
|
-
to 420ms and left it visibly lagging the cursor.
|
|
80
|
-
*/}
|
|
81
|
-
{current && (
|
|
82
|
-
<div
|
|
83
|
-
className="h-full w-full transition-transform duration-150 ease-out"
|
|
84
|
-
style={zoom
|
|
85
|
-
? { transform: 'scale(2)', transformOrigin: `${zoom.x}% ${zoom.y}%` }
|
|
86
|
-
: { transform: 'scale(1)' }}
|
|
87
|
-
>
|
|
88
|
-
<SmoothImage
|
|
89
|
-
key={current.id}
|
|
90
|
-
src={current.url}
|
|
91
|
-
alt={current.altText ?? product.name}
|
|
92
|
-
data-testid={TID.pdpGalleryImage}
|
|
93
|
-
className="h-full w-full object-cover"
|
|
94
|
-
/>
|
|
95
|
-
</div>
|
|
96
|
-
)}
|
|
97
|
-
|
|
98
|
-
{ordered.length > 1 && (
|
|
99
|
-
<>
|
|
100
|
-
<GalleryArrow side="left" onClick={() => step(-1)} />
|
|
101
|
-
<GalleryArrow side="right" onClick={() => step(1)} />
|
|
102
|
-
<span className="pointer-events-none absolute bottom-3 right-3 rounded-full bg-black/60 px-2 py-0.5 text-[11px] font-medium text-white">
|
|
103
|
-
{index + 1}/{ordered.length}
|
|
104
|
-
</span>
|
|
105
|
-
</>
|
|
106
|
-
)}
|
|
107
|
-
</div>
|
|
108
|
-
|
|
109
|
-
{ordered.length > 1 && (
|
|
110
|
-
<ul data-testid={TID.pdpGalleryThumbs} className="flex gap-2 overflow-x-auto pb-1">
|
|
111
|
-
{ordered.map((image, position) => (
|
|
112
|
-
<li key={image.id}>
|
|
113
|
-
<button
|
|
114
|
-
type="button"
|
|
115
|
-
data-testid={TID.pdpGalleryThumb}
|
|
116
|
-
data-index={position}
|
|
117
|
-
aria-label={`Imagem ${position + 1} de ${ordered.length}`}
|
|
118
|
-
aria-current={position === index}
|
|
119
|
-
onClick={() => { setIndex(position); setZoom(null) }}
|
|
120
|
-
className={`h-16 w-16 shrink-0 overflow-hidden rounded-lg border transition ${
|
|
121
|
-
position === index ? 'border-primary ring-2 ring-primary/20' : 'border-border opacity-70 hover:opacity-100'
|
|
122
|
-
}`}
|
|
123
|
-
>
|
|
124
|
-
<img src={image.url} alt="" className="h-full w-full object-cover" loading="lazy" />
|
|
125
|
-
</button>
|
|
126
|
-
</li>
|
|
127
|
-
))}
|
|
128
|
-
</ul>
|
|
129
|
-
)}
|
|
130
|
-
</div>
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Primary first, then sort_order — the order the merchant set in the admin. */
|
|
135
|
-
function orderImages(images: ProductImage[], primary?: ProductImage): ProductImage[] {
|
|
136
|
-
const all = images.length > 0 ? images : primary ? [primary] : []
|
|
137
|
-
return [...all].sort((a, b) => {
|
|
138
|
-
if (a.isPrimary !== b.isPrimary) return a.isPrimary ? -1 : 1
|
|
139
|
-
return (a.sortOrder ?? 0) - (b.sortOrder ?? 0)
|
|
140
|
-
})
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Whether the device actually has a hover-capable pointer. On a phone every
|
|
145
|
-
* element is permanently "hovered" by some emulations, which would leave the
|
|
146
|
-
* photo stuck at 2× with no way back.
|
|
147
|
-
*/
|
|
148
|
-
function useCanHover(): boolean {
|
|
149
|
-
const [canHover, setCanHover] = useState(false)
|
|
150
|
-
useEffect(() => {
|
|
151
|
-
const query = window.matchMedia?.('(hover: hover) and (pointer: fine)')
|
|
152
|
-
if (!query) return
|
|
153
|
-
setCanHover(query.matches)
|
|
154
|
-
const onChange = (event: MediaQueryListEvent) => setCanHover(event.matches)
|
|
155
|
-
query.addEventListener?.('change', onChange)
|
|
156
|
-
return () => query.removeEventListener?.('change', onChange)
|
|
157
|
-
}, [])
|
|
158
|
-
return canHover
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function GalleryArrow({ side, onClick }: { side: 'left' | 'right'; onClick: () => void }) {
|
|
162
|
-
return (
|
|
163
|
-
<button
|
|
164
|
-
type="button"
|
|
165
|
-
onClick={onClick}
|
|
166
|
-
aria-label={side === 'left' ? 'Imagem anterior' : 'Próxima imagem'}
|
|
167
|
-
className={`absolute top-1/2 z-10 flex h-9 w-9 -translate-y-1/2 items-center justify-center rounded-full bg-background/80 opacity-0 shadow-md backdrop-blur transition hover:bg-background focus:opacity-100 group-hover:opacity-100 ${
|
|
168
|
-
side === 'left' ? 'left-3' : 'right-3'
|
|
169
|
-
}`}
|
|
170
|
-
>
|
|
171
|
-
<span aria-hidden className="text-lg leading-none">{side === 'left' ? '‹' : '›'}</span>
|
|
172
|
-
</button>
|
|
173
|
-
)
|
|
174
|
-
}
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import React from 'react'
|
|
2
|
-
import type { Product } from '@fayz-ai/shop/types'
|
|
3
|
-
import { ProductCard } from './ProductCard'
|
|
4
|
-
import { getStorefrontComponents, useStorefrontConfig } from '../config'
|
|
5
|
-
import { useStorefrontActions } from '../hooks/useStorefront'
|
|
6
|
-
import { TID } from '../testids'
|
|
7
|
-
|
|
8
|
-
export function ProductGrid({ products, loading }: { products: Product[]; loading: boolean }) {
|
|
9
|
-
const config = useStorefrontConfig()
|
|
10
|
-
const actions = useStorefrontActions()
|
|
11
|
-
const components = getStorefrontComponents(config)
|
|
12
|
-
const ProductCardComponent = components.ProductCard ?? ProductCard
|
|
13
|
-
|
|
14
|
-
if (loading) {
|
|
15
|
-
return (
|
|
16
|
-
<div data-testid={TID.productGrid} className="grid grid-cols-2 gap-5 md:grid-cols-3 xl:grid-cols-4">
|
|
17
|
-
{Array.from({ length: 8 }, (_, i) => (
|
|
18
|
-
<div key={i} className="aspect-[3/4] animate-pulse rounded-xl bg-muted" />
|
|
19
|
-
))}
|
|
20
|
-
</div>
|
|
21
|
-
)
|
|
22
|
-
}
|
|
23
|
-
if (products.length === 0) {
|
|
24
|
-
return (
|
|
25
|
-
<div data-testid={TID.productGrid} className="py-20 text-center text-muted-foreground">
|
|
26
|
-
Nenhum produto encontrado.
|
|
27
|
-
</div>
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
return (
|
|
31
|
-
<div data-testid={TID.productGrid} className="grid grid-cols-2 gap-5 md:grid-cols-3 xl:grid-cols-4">
|
|
32
|
-
{products.map((p) => (
|
|
33
|
-
<ProductCardComponent
|
|
34
|
-
key={p.id}
|
|
35
|
-
product={p}
|
|
36
|
-
config={config}
|
|
37
|
-
commerceMode={config.commerceMode}
|
|
38
|
-
actions={actions}
|
|
39
|
-
/>
|
|
40
|
-
))}
|
|
41
|
-
</div>
|
|
42
|
-
)
|
|
43
|
-
}
|