@wix/agent-skills 1.17.0 → 1.18.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/package.json +1 -1
- package/skills/wix-app/SKILL.md +115 -12
- package/skills/wix-app/references/APP_MARKET_REVIEW.md +1 -1
- package/skills/wix-app/references/BACKEND_API.md +18 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +6 -0
- package/skills/wix-app/references/DASHBOARD_MODAL.md +10 -2
- package/skills/wix-app/references/DASHBOARD_PAGE.md +32 -70
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +14 -3
- package/skills/wix-app/references/SITE_PLUGIN.md +12 -0
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +74 -68
- package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +79 -0
- package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +73 -0
- package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +52 -0
- package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +184 -0
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +12 -11
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +1 -0
- package/skills/wix-app/references/editor-react-component/BROWSER-SUPPORT.md +56 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +16 -8
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +19 -1
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +3 -0
- package/skills/wix-base44-connector/SKILL.md +133 -44
- package/skills/wix-base44-connector/references/CAPABILITY_MAP.md +78 -0
- package/skills/wix-base44-connector/scripts/utils.js +216 -30
- package/skills/wix-headless/entry/skill.md +1 -1
- package/skills/wix-headless/references/CAPABILITIES.md +9 -1
- package/skills/wix-headless/references/DESIGN.md +1 -0
- package/skills/wix-headless/references/DISCOVERY.md +2 -1
- package/skills/wix-headless/references/IMAGE_GENERATION.md +1 -0
- package/skills/wix-headless/references/SDK_HANDOFF.md +4 -1
- package/skills/wix-headless/references/SEED.md +2 -1
- package/skills/wix-headless/references/SETUP.md +1 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +2 -2
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +26 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +26 -10
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +8 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-rentals.md +254 -0
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +13 -0
- package/skills/wix-headless/references/inline-recipes/setup-rentals.md +254 -0
- package/skills/wix-headless/references/non-astro.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +166 -0
- package/skills/wix-headless-fast/entry/skill.md +99 -0
- package/skills/wix-headless-fast/install/deploy.mjs +433 -0
- package/skills/wix-headless-fast/install/fast-path.mjs +141 -0
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +137 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +137 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +113 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +42 -0
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +123 -0
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +70 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +99 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +76 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +62 -0
- package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +99 -0
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +24 -0
- package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +16156 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +72 -0
- package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +287 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +129 -0
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +143 -0
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +119 -0
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +138 -0
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +54 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +206 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +96 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +70 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +76 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +22 -0
- package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +11962 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +61 -0
- package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +302 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +179 -0
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +83 -0
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +57 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +151 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +75 -0
- package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +41 -0
- package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +11262 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +103 -0
- package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +324 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +149 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +66 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +176 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +118 -0
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +117 -0
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +59 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +136 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +128 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +61 -0
- package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +12 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +80 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +21 -0
- package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +11884 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +86 -0
- package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +276 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +150 -0
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +317 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +269 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +231 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +135 -0
- package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +10593 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +108 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +320 -0
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +63 -0
- package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +60 -0
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +155 -0
- package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +78 -0
- package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +47 -0
- package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +66 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +88 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +53 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +122 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +50 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +21 -0
- package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +42 -0
- package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +10 -0
- package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +11 -0
- package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +10329 -0
- package/skills/wix-headless-fast/references/members/seed/SEED.md +39 -0
- package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +94 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +134 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +110 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +90 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +115 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +59 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +39 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +165 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +55 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +30 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +21 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +29 -0
- package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +10197 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +78 -0
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +258 -0
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +133 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +58 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +126 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +37 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +39 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +75 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +45 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +30 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +40 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +21 -0
- package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +11226 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +71 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +279 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +165 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +142 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +26 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +121 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +55 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +43 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +177 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +165 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +103 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +254 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +146 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +168 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +47 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +22 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +12 -0
- package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +12560 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +81 -0
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +479 -0
- package/skills/wix-headless-fast/references/shared/CONTENT.md +22 -0
- package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +73 -0
- package/skills/wix-headless-fast/references/shared/DESIGN.md +26 -0
- package/skills/wix-headless-fast/references/shared/app/styles/global.css +52 -0
- package/skills/wix-headless-fast/references/shared/app/wix/config.ts +15 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +27 -0
- package/skills/wix-headless-fast/references/shared/app/wix/money.ts +24 -0
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +76 -0
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +46 -0
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +46 -0
- package/skills/wix-headless-fast/references/shared/seed/images.mjs +145 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +149 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +26 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +121 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx +100 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductDetailView.tsx +130 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx +48 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ShopView.tsx +66 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx +103 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +48 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +140 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +80 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +101 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +221 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +215 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +109 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +46 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/products/[slug].astro +56 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/shop.astro +23 -0
- package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +12247 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +85 -0
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +415 -0
- package/skills/wix-manage/SKILL.md +18 -2
- package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +141 -0
- package/skills/wix-manage/references/google-business-profile/connect-google-business-profile.md +134 -0
- package/skills/wix-manage/references/google-business-profile/manage-google-business-profile-locations.md +182 -0
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +3 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +21 -6
- package/skills/wix-manage/references/stores/update-product-with-options.md +83 -0
- package/skills/wix-vibe-headless/SKILL.md +18 -4
- package/skills/wix-vibe-headless/install/deploy.cjs +12 -4
- package/skills/wix-vibe-headless/install/pin-agents-md.cjs +2 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +3 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +3 -1
- package/skills/wix-vibe-headless/platforms/base44.md +23 -61
- package/skills/wix-vibe-headless/platforms/generic.md +6 -3
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/RentalDurationPicker.jsx +67 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useRentalDuration.js +128 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js +5 -3
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-rentals.js +250 -0
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-services.js +12 -5
- package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +41 -0
- package/skills/wix-vibe-headless/references/bookings/seed/seed-rentals.js +267 -0
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +6 -3
- package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +470 -0
- package/skills/wix-vibe-headless/references/forms/app/hooks/useWixForm.js +425 -0
- package/skills/wix-vibe-headless/references/forms/app/lib/wix-form-schema-utils.js +187 -0
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms-submissions.js +238 -0
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms.js +108 -0
- package/skills/wix-vibe-headless/references/forms/seed/SEED.md +1762 -0
- package/skills/wix-vibe-headless/references/rentals/INSTRUCTIONS.md +13 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +137 -84
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +15 -8
- package/skills/wix-vibe-headless/references/storefront/app/lib/storeImage.js +37 -0
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +18 -6
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +30 -7
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.js +46 -10
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductCard.jsx +0 -110
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGallery.jsx +0 -51
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGrid.jsx +0 -49
- package/skills/wix-vibe-headless/references/storefront/app/components/VariantPicker.jsx +0 -92
- package/skills/wix-vibe-headless/references/storefront/app/pages/ProductDetail.jsx +0 -139
package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// Slide-over cart with quantity stepper, remove, live subtotal, and Wix-hosted checkout.
|
|
2
|
+
// Mount ONCE per page, as-is; opens via useCart().openCart() / CartButton. Styled from the
|
|
3
|
+
// @theme tokens.
|
|
4
|
+
import { useCart } from "../../hooks/storefront/useCart";
|
|
5
|
+
|
|
6
|
+
export default function CartDrawer() {
|
|
7
|
+
const { cart, open, closeCart, busy, error, updateQuantity, removeLine, checkout } = useCart();
|
|
8
|
+
if (!open) return null;
|
|
9
|
+
const lines = cart?.lines ?? [];
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div
|
|
13
|
+
className="fixed inset-0 z-50 flex justify-end bg-foreground/40 backdrop-blur-[2px]"
|
|
14
|
+
onClick={closeCart}
|
|
15
|
+
>
|
|
16
|
+
<aside
|
|
17
|
+
role="dialog"
|
|
18
|
+
aria-label="Cart"
|
|
19
|
+
onClick={(e) => e.stopPropagation()}
|
|
20
|
+
className="flex h-full w-full max-w-md flex-col bg-background text-foreground shadow-2xl"
|
|
21
|
+
>
|
|
22
|
+
<div className="flex items-center justify-between border-b border-border px-6 py-5">
|
|
23
|
+
<span className="text-base font-semibold">
|
|
24
|
+
Cart{cart && cart.itemCount > 0 ? ` (${cart.itemCount})` : ""}
|
|
25
|
+
</span>
|
|
26
|
+
<button
|
|
27
|
+
type="button"
|
|
28
|
+
aria-label="Close cart"
|
|
29
|
+
onClick={closeCart}
|
|
30
|
+
className="text-xl leading-none text-muted-foreground transition-colors hover:text-foreground"
|
|
31
|
+
>
|
|
32
|
+
×
|
|
33
|
+
</button>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div className="flex-1 overflow-y-auto px-6 py-5">
|
|
37
|
+
{lines.length === 0 && (
|
|
38
|
+
<p className="py-16 text-center text-sm text-muted-foreground">Your cart is empty.</p>
|
|
39
|
+
)}
|
|
40
|
+
{lines.map((line) => (
|
|
41
|
+
<div className="mb-6 flex gap-4" key={line.lineItemId}>
|
|
42
|
+
{line.imageUrl ? (
|
|
43
|
+
<img
|
|
44
|
+
src={line.imageUrl}
|
|
45
|
+
alt=""
|
|
46
|
+
loading="lazy"
|
|
47
|
+
className="h-16 w-16 flex-shrink-0 rounded-md bg-secondary object-cover"
|
|
48
|
+
/>
|
|
49
|
+
) : (
|
|
50
|
+
<div aria-hidden="true" className="h-16 w-16 flex-shrink-0 rounded-md bg-secondary" />
|
|
51
|
+
)}
|
|
52
|
+
<div className="min-w-0 flex-1">
|
|
53
|
+
<p className="text-sm font-medium">{line.productName}</p>
|
|
54
|
+
{line.descriptionLines.map((d) => (
|
|
55
|
+
<p className="mt-0.5 text-xs text-muted-foreground" key={d}>
|
|
56
|
+
{d}
|
|
57
|
+
</p>
|
|
58
|
+
))}
|
|
59
|
+
{line.status !== "IN_STOCK" && (
|
|
60
|
+
<p className="mt-1 text-xs text-red-600">No longer available at this quantity</p>
|
|
61
|
+
)}
|
|
62
|
+
<div className="mt-2 flex items-center">
|
|
63
|
+
<div className="inline-flex items-center gap-3 rounded-full border border-border px-2 py-0.5">
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
aria-label="Decrease quantity"
|
|
67
|
+
disabled={busy || line.quantity <= 1}
|
|
68
|
+
onClick={() => updateQuantity(line.lineItemId, line.quantity - 1).catch(() => {})}
|
|
69
|
+
className="px-1 text-base disabled:opacity-40"
|
|
70
|
+
>
|
|
71
|
+
−
|
|
72
|
+
</button>
|
|
73
|
+
<span className="text-sm tabular-nums">{line.quantity}</span>
|
|
74
|
+
<button
|
|
75
|
+
type="button"
|
|
76
|
+
aria-label="Increase quantity"
|
|
77
|
+
disabled={busy}
|
|
78
|
+
onClick={() => updateQuantity(line.lineItemId, line.quantity + 1).catch(() => {})}
|
|
79
|
+
className="px-1 text-base disabled:opacity-40"
|
|
80
|
+
>
|
|
81
|
+
+
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
disabled={busy}
|
|
87
|
+
onClick={() => removeLine(line.lineItemId).catch(() => {})}
|
|
88
|
+
className="ml-3 text-xs text-muted-foreground underline transition-colors hover:text-foreground disabled:opacity-40"
|
|
89
|
+
>
|
|
90
|
+
Remove
|
|
91
|
+
</button>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
<p className="text-sm font-medium">{line.linePrice}</p>
|
|
95
|
+
</div>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
{lines.length > 0 && (
|
|
100
|
+
<div className="border-t border-border px-6 py-5">
|
|
101
|
+
{cart?.subtotal && (
|
|
102
|
+
<div className="mb-4 flex justify-between text-sm">
|
|
103
|
+
<span className="text-muted-foreground">Subtotal</span>
|
|
104
|
+
<strong className="font-semibold">{cart.subtotal}</strong>
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
{error && <p className="mb-3 text-xs text-red-600">{error}</p>}
|
|
108
|
+
<button
|
|
109
|
+
type="button"
|
|
110
|
+
disabled={busy}
|
|
111
|
+
onClick={() => checkout().catch(() => {})}
|
|
112
|
+
className="w-full rounded-full bg-primary py-3.5 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-50"
|
|
113
|
+
>
|
|
114
|
+
{busy ? "One moment…" : "Checkout"}
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
)}
|
|
118
|
+
</aside>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Grid tile — a REFERENCE implementation over the ProductSummary DTO. Complete and correct
|
|
2
|
+
// (badges, sale price, hover image, quick-add); styled entirely from the @theme tokens via
|
|
3
|
+
// Tailwind utilities. Designing your own card on the same DTO is encouraged.
|
|
4
|
+
import type { ComponentType, ReactNode } from "react";
|
|
5
|
+
import type { ProductSummary } from "../../wix/storefront/types";
|
|
6
|
+
import { useCart } from "../../hooks/storefront/useCart";
|
|
7
|
+
|
|
8
|
+
export interface LinkLikeProps {
|
|
9
|
+
href: string;
|
|
10
|
+
className?: string;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ProductCardProps {
|
|
15
|
+
product: ProductSummary;
|
|
16
|
+
/** Route pattern for detail pages; default matches the shipped /products/[slug] page. */
|
|
17
|
+
productHref?: (slug: string) => string;
|
|
18
|
+
/** Router-specific link (react-router Link, next/link); defaults to a plain <a>. */
|
|
19
|
+
LinkComponent?: ComponentType<LinkLikeProps>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const PlainLink = ({ href, className, children }: LinkLikeProps) => (
|
|
23
|
+
<a href={href} className={className}>
|
|
24
|
+
{children}
|
|
25
|
+
</a>
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
export default function ProductCard({
|
|
29
|
+
product,
|
|
30
|
+
productHref = (slug) => `/products/${slug}`,
|
|
31
|
+
LinkComponent = PlainLink,
|
|
32
|
+
}: ProductCardProps) {
|
|
33
|
+
const { addToCart, busy } = useCart();
|
|
34
|
+
const soldOut = product.availability === "OUT_OF_STOCK" && !product.preorder;
|
|
35
|
+
const priceLabel =
|
|
36
|
+
product.maxPrice && product.maxPrice !== product.price
|
|
37
|
+
? `${product.price} – ${product.maxPrice}`
|
|
38
|
+
: product.price;
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div className="group">
|
|
42
|
+
<LinkComponent href={productHref(product.slug)} className="block no-underline">
|
|
43
|
+
<div className="relative aspect-square overflow-hidden rounded-lg bg-secondary">
|
|
44
|
+
{product.imageUrl && (
|
|
45
|
+
<img
|
|
46
|
+
src={product.imageUrl}
|
|
47
|
+
alt={product.name}
|
|
48
|
+
loading="lazy"
|
|
49
|
+
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
|
|
50
|
+
/>
|
|
51
|
+
)}
|
|
52
|
+
{product.hoverImageUrl && (
|
|
53
|
+
<img
|
|
54
|
+
src={product.hoverImageUrl}
|
|
55
|
+
alt=""
|
|
56
|
+
aria-hidden="true"
|
|
57
|
+
loading="lazy"
|
|
58
|
+
className="absolute inset-0 h-full w-full object-cover opacity-0 transition-opacity duration-300 group-hover:opacity-100"
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
{product.ribbon && (
|
|
62
|
+
<span className="absolute left-3 top-3 rounded-full bg-background/90 px-3 py-1 text-xs font-semibold text-foreground backdrop-blur">
|
|
63
|
+
{product.ribbon}
|
|
64
|
+
</span>
|
|
65
|
+
)}
|
|
66
|
+
{soldOut && (
|
|
67
|
+
<span className="absolute right-3 top-3 rounded-full bg-background/90 px-3 py-1 text-xs font-semibold text-muted-foreground backdrop-blur">
|
|
68
|
+
Sold out
|
|
69
|
+
</span>
|
|
70
|
+
)}
|
|
71
|
+
{product.preorder && (
|
|
72
|
+
<span className="absolute right-3 top-3 rounded-full bg-background/90 px-3 py-1 text-xs font-semibold text-foreground backdrop-blur">
|
|
73
|
+
Pre-order
|
|
74
|
+
</span>
|
|
75
|
+
)}
|
|
76
|
+
</div>
|
|
77
|
+
<p className="mt-3 text-sm font-medium text-foreground">{product.name}</p>
|
|
78
|
+
<p className="mt-0.5 flex items-baseline gap-2 text-sm">
|
|
79
|
+
<span className="text-foreground">{priceLabel}</span>
|
|
80
|
+
{product.compareAtPrice && (
|
|
81
|
+
<span className="text-muted-foreground line-through">{product.compareAtPrice}</span>
|
|
82
|
+
)}
|
|
83
|
+
</p>
|
|
84
|
+
{product.optionsSummary && (
|
|
85
|
+
<p className="mt-0.5 text-xs text-muted-foreground">{product.optionsSummary}</p>
|
|
86
|
+
)}
|
|
87
|
+
</LinkComponent>
|
|
88
|
+
{product.quickAddable && (
|
|
89
|
+
<button
|
|
90
|
+
type="button"
|
|
91
|
+
disabled={busy}
|
|
92
|
+
onClick={() => addToCart(product.id).catch(() => {})}
|
|
93
|
+
className="mt-2 rounded-full border border-border px-4 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary disabled:opacity-50"
|
|
94
|
+
>
|
|
95
|
+
Add to cart
|
|
96
|
+
</button>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// The full product detail surface: gallery, price, option/modifier picker, quantity,
|
|
2
|
+
// add-to-cart — styled from the @theme tokens. Mount as-is (Astro: an island with the
|
|
3
|
+
// server-fetched `initial` product; SPA: pass the `slug`). All selection→variant logic lives
|
|
4
|
+
// in useProductDetail — never bypass it to add a product with options.
|
|
5
|
+
import { useState } from "react";
|
|
6
|
+
import { useProductDetail } from "../../hooks/storefront/useProductDetail";
|
|
7
|
+
import VariantPicker from "./VariantPicker";
|
|
8
|
+
|
|
9
|
+
export interface ProductDetailViewProps {
|
|
10
|
+
/** Server-fetched product (Astro/Next SSR). */
|
|
11
|
+
initial?: import("../../wix/storefront/types").ProductDetail | null;
|
|
12
|
+
/** SPA alternative: fetch by slug on mount. */
|
|
13
|
+
slug?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default function ProductDetailView({ initial, slug }: ProductDetailViewProps) {
|
|
17
|
+
const {
|
|
18
|
+
product,
|
|
19
|
+
notFound,
|
|
20
|
+
optionGroups,
|
|
21
|
+
selectOption,
|
|
22
|
+
modifierValues,
|
|
23
|
+
setModifier,
|
|
24
|
+
price,
|
|
25
|
+
compareAtPrice,
|
|
26
|
+
canAdd,
|
|
27
|
+
quantity,
|
|
28
|
+
setQuantity,
|
|
29
|
+
add,
|
|
30
|
+
adding,
|
|
31
|
+
error,
|
|
32
|
+
} = useProductDetail({ initial, slug });
|
|
33
|
+
const [imageIndex, setImageIndex] = useState(0);
|
|
34
|
+
|
|
35
|
+
if (notFound)
|
|
36
|
+
return <p className="py-16 text-center text-muted-foreground">This product doesn't exist (anymore).</p>;
|
|
37
|
+
if (!product) return <div className="aspect-square max-w-md animate-pulse rounded-xl bg-secondary" />;
|
|
38
|
+
|
|
39
|
+
const soldOut = product.availability === "OUT_OF_STOCK" && !product.preorder;
|
|
40
|
+
const mainImage = product.gallery[imageIndex] ?? product.imageUrl;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="grid gap-10 md:grid-cols-2">
|
|
44
|
+
<div>
|
|
45
|
+
<div className="aspect-square overflow-hidden rounded-xl bg-secondary">
|
|
46
|
+
{mainImage && <img src={mainImage} alt={product.name} className="h-full w-full object-cover" />}
|
|
47
|
+
</div>
|
|
48
|
+
{product.gallery.length > 1 && (
|
|
49
|
+
<div className="mt-3 flex flex-wrap gap-2">
|
|
50
|
+
{product.gallery.map((url, i) => (
|
|
51
|
+
<button
|
|
52
|
+
key={url}
|
|
53
|
+
type="button"
|
|
54
|
+
aria-label={`Image ${i + 1}`}
|
|
55
|
+
onClick={() => setImageIndex(i)}
|
|
56
|
+
className={`h-16 w-16 overflow-hidden rounded-md border-2 transition-colors ${
|
|
57
|
+
i === imageIndex ? "border-primary" : "border-transparent hover:border-border"
|
|
58
|
+
}`}
|
|
59
|
+
>
|
|
60
|
+
<img src={url} alt="" className="h-full w-full object-cover" />
|
|
61
|
+
</button>
|
|
62
|
+
))}
|
|
63
|
+
</div>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div>
|
|
68
|
+
<h1 className="text-2xl font-semibold tracking-tight">{product.name}</h1>
|
|
69
|
+
<p className="mt-2 flex items-baseline gap-3">
|
|
70
|
+
<span className="text-xl font-medium">{price}</span>
|
|
71
|
+
{compareAtPrice && <span className="text-base text-muted-foreground line-through">{compareAtPrice}</span>}
|
|
72
|
+
</p>
|
|
73
|
+
{soldOut && <p className="mt-2 text-sm font-medium text-red-600">Out of stock</p>}
|
|
74
|
+
{product.preorder && <p className="mt-2 text-sm text-muted-foreground">Available for pre-order</p>}
|
|
75
|
+
|
|
76
|
+
{product.descriptionHtml && (
|
|
77
|
+
// plainDescription is HTML (despite the name) — render it, don't print it.
|
|
78
|
+
<div
|
|
79
|
+
className="mt-4 space-y-2 text-sm leading-relaxed text-muted-foreground [&_strong]:font-semibold [&_strong]:text-foreground"
|
|
80
|
+
dangerouslySetInnerHTML={{ __html: product.descriptionHtml }}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
|
|
84
|
+
<div className="mt-6">
|
|
85
|
+
<VariantPicker
|
|
86
|
+
optionGroups={optionGroups}
|
|
87
|
+
selectOption={selectOption}
|
|
88
|
+
modifiers={product.modifiers}
|
|
89
|
+
modifierValues={modifierValues}
|
|
90
|
+
setModifier={setModifier}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{!soldOut && (
|
|
95
|
+
<div className="mt-2 flex items-center gap-3">
|
|
96
|
+
<div className="inline-flex items-center gap-3 rounded-full border border-border px-3 py-2">
|
|
97
|
+
<button
|
|
98
|
+
type="button"
|
|
99
|
+
aria-label="Decrease quantity"
|
|
100
|
+
disabled={quantity <= 1}
|
|
101
|
+
onClick={() => setQuantity(quantity - 1)}
|
|
102
|
+
className="px-1 text-base disabled:opacity-40"
|
|
103
|
+
>
|
|
104
|
+
−
|
|
105
|
+
</button>
|
|
106
|
+
<span className="text-sm tabular-nums">{quantity}</span>
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
aria-label="Increase quantity"
|
|
110
|
+
onClick={() => setQuantity(quantity + 1)}
|
|
111
|
+
className="px-1 text-base"
|
|
112
|
+
>
|
|
113
|
+
+
|
|
114
|
+
</button>
|
|
115
|
+
</div>
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
disabled={!canAdd || adding}
|
|
119
|
+
onClick={() => add().catch(() => {})}
|
|
120
|
+
className="rounded-full bg-primary px-8 py-3 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-50"
|
|
121
|
+
>
|
|
122
|
+
{adding ? "Adding…" : canAdd ? "Add to cart" : "Select options"}
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
)}
|
|
126
|
+
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
|
|
127
|
+
</div>
|
|
128
|
+
</div>
|
|
129
|
+
);
|
|
130
|
+
}
|
package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// Listing grid with loading skeletons and an honest empty state — a REFERENCE layout on the
|
|
2
|
+
// @theme tokens. Keep the states (they're correct); choosing a grid arrangement that fits the
|
|
3
|
+
// brand is encouraged. Never mock products: empty catalog → the empty state.
|
|
4
|
+
import type { ComponentType } from "react";
|
|
5
|
+
import type { ProductSummary } from "../../wix/storefront/types";
|
|
6
|
+
import ProductCard, { type ProductCardProps } from "./ProductCard";
|
|
7
|
+
|
|
8
|
+
export interface ProductGridProps {
|
|
9
|
+
/** null → loading skeletons; [] → the empty state. */
|
|
10
|
+
products: ProductSummary[] | null;
|
|
11
|
+
emptyMessage?: string;
|
|
12
|
+
productHref?: ProductCardProps["productHref"];
|
|
13
|
+
LinkComponent?: ProductCardProps["LinkComponent"];
|
|
14
|
+
/** Swap in your own tile (built on ProductSummary) without re-doing the states. */
|
|
15
|
+
CardComponent?: ComponentType<ProductCardProps>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default function ProductGrid({
|
|
19
|
+
products,
|
|
20
|
+
emptyMessage = "No products yet — check back soon.",
|
|
21
|
+
productHref,
|
|
22
|
+
LinkComponent,
|
|
23
|
+
CardComponent = ProductCard,
|
|
24
|
+
}: ProductGridProps) {
|
|
25
|
+
if (products === null) {
|
|
26
|
+
return (
|
|
27
|
+
<div className="grid grid-cols-2 gap-x-5 gap-y-8 sm:grid-cols-3 lg:grid-cols-4" aria-busy="true">
|
|
28
|
+
{Array.from({ length: 8 }, (_, i) => (
|
|
29
|
+
<div key={i}>
|
|
30
|
+
<div className="aspect-square animate-pulse rounded-lg bg-secondary" />
|
|
31
|
+
<div className="mt-3 h-3.5 w-3/4 animate-pulse rounded bg-secondary" />
|
|
32
|
+
<div className="mt-2 h-3.5 w-1/3 animate-pulse rounded bg-secondary" />
|
|
33
|
+
</div>
|
|
34
|
+
))}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (products.length === 0) {
|
|
39
|
+
return <p className="py-16 text-center text-muted-foreground">{emptyMessage}</p>;
|
|
40
|
+
}
|
|
41
|
+
return (
|
|
42
|
+
<div className="grid grid-cols-2 gap-x-5 gap-y-8 sm:grid-cols-3 lg:grid-cols-4">
|
|
43
|
+
{products.map((p) => (
|
|
44
|
+
<CardComponent key={p.id} product={p} productHref={productHref} LinkComponent={LinkComponent} />
|
|
45
|
+
))}
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// The full shop surface: live category filter bar + product grid, styled from the @theme
|
|
2
|
+
// tokens. Mount as-is (Astro: one island with server-fetched initial data; SPA: no props).
|
|
3
|
+
import { useShop } from "../../hooks/storefront/useShop";
|
|
4
|
+
import type { Category, ProductSummary } from "../../wix/storefront/types";
|
|
5
|
+
import ProductGrid, { type ProductGridProps } from "./ProductGrid";
|
|
6
|
+
|
|
7
|
+
export interface ShopViewProps {
|
|
8
|
+
/** Server-fetched data (Astro/Next SSR) — omit in a SPA and it fetches client-side. */
|
|
9
|
+
initialProducts?: ProductSummary[];
|
|
10
|
+
initialCategories?: Category[];
|
|
11
|
+
emptyMessage?: string;
|
|
12
|
+
productHref?: ProductGridProps["productHref"];
|
|
13
|
+
LinkComponent?: ProductGridProps["LinkComponent"];
|
|
14
|
+
CardComponent?: ProductGridProps["CardComponent"];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const pill = (active: boolean) =>
|
|
18
|
+
`rounded-full border px-4 py-1.5 text-sm font-medium transition-colors ${
|
|
19
|
+
active
|
|
20
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
21
|
+
: "border-border text-foreground hover:bg-secondary"
|
|
22
|
+
}`;
|
|
23
|
+
|
|
24
|
+
export default function ShopView({
|
|
25
|
+
initialProducts,
|
|
26
|
+
initialCategories,
|
|
27
|
+
emptyMessage,
|
|
28
|
+
productHref,
|
|
29
|
+
LinkComponent,
|
|
30
|
+
CardComponent,
|
|
31
|
+
}: ShopViewProps) {
|
|
32
|
+
const { products, categories, activeCategoryId, setActiveCategoryId, loading, error } = useShop({
|
|
33
|
+
initialProducts,
|
|
34
|
+
initialCategories,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<div>
|
|
39
|
+
{categories.length > 1 && (
|
|
40
|
+
<div className="mb-8 flex flex-wrap gap-2" role="group" aria-label="Categories">
|
|
41
|
+
<button type="button" className={pill(activeCategoryId === null)} onClick={() => setActiveCategoryId(null)}>
|
|
42
|
+
All
|
|
43
|
+
</button>
|
|
44
|
+
{categories.map((c) => (
|
|
45
|
+
<button
|
|
46
|
+
key={c.id}
|
|
47
|
+
type="button"
|
|
48
|
+
className={pill(activeCategoryId === c.id)}
|
|
49
|
+
onClick={() => setActiveCategoryId(c.id)}
|
|
50
|
+
>
|
|
51
|
+
{c.name}
|
|
52
|
+
</button>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
)}
|
|
56
|
+
{error && <p className="mb-4 text-sm text-red-600">{error}</p>}
|
|
57
|
+
<ProductGrid
|
|
58
|
+
products={loading ? null : products}
|
|
59
|
+
emptyMessage={emptyMessage}
|
|
60
|
+
productHref={productHref}
|
|
61
|
+
LinkComponent={LinkComponent}
|
|
62
|
+
CardComponent={CardComponent}
|
|
63
|
+
/>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Option/modifier selector — wire as-is on the PDP (color options render as swatches, text
|
|
2
|
+
// options as pills, modifiers as pills or a text input). Styled from the @theme tokens.
|
|
3
|
+
import type { ProductModifier } from "../../wix/storefront/types";
|
|
4
|
+
import type { OptionGroupView } from "../../hooks/storefront/useProductDetail";
|
|
5
|
+
|
|
6
|
+
export interface VariantPickerProps {
|
|
7
|
+
optionGroups: OptionGroupView[];
|
|
8
|
+
selectOption: (optionName: string, choiceName: string) => void;
|
|
9
|
+
modifiers?: ProductModifier[];
|
|
10
|
+
modifierValues?: Record<string, string>;
|
|
11
|
+
setModifier?: (key: string, value: string) => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const chip = (selected: boolean) =>
|
|
15
|
+
`rounded-full border px-4 py-1.5 text-sm transition-colors disabled:cursor-not-allowed disabled:opacity-40 disabled:line-through ${
|
|
16
|
+
selected
|
|
17
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
18
|
+
: "border-border text-foreground hover:bg-secondary"
|
|
19
|
+
}`;
|
|
20
|
+
|
|
21
|
+
export default function VariantPicker({
|
|
22
|
+
optionGroups,
|
|
23
|
+
selectOption,
|
|
24
|
+
modifiers = [],
|
|
25
|
+
modifierValues = {},
|
|
26
|
+
setModifier,
|
|
27
|
+
}: VariantPickerProps) {
|
|
28
|
+
return (
|
|
29
|
+
<div>
|
|
30
|
+
{optionGroups.map((group) => (
|
|
31
|
+
<div className="mb-5" key={group.id || group.name}>
|
|
32
|
+
<p className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
33
|
+
{group.name}
|
|
34
|
+
</p>
|
|
35
|
+
<div className="flex flex-wrap items-center gap-2">
|
|
36
|
+
{group.choices.map((choice) =>
|
|
37
|
+
group.isColor && choice.colorCode ? (
|
|
38
|
+
<button
|
|
39
|
+
key={choice.choiceId || choice.name}
|
|
40
|
+
type="button"
|
|
41
|
+
style={{ background: choice.colorCode }}
|
|
42
|
+
title={choice.name}
|
|
43
|
+
aria-label={`${group.name}: ${choice.name}`}
|
|
44
|
+
aria-pressed={choice.selected}
|
|
45
|
+
disabled={!choice.inStock}
|
|
46
|
+
onClick={() => selectOption(group.name, choice.name)}
|
|
47
|
+
className={`h-8 w-8 rounded-full border transition-shadow disabled:cursor-not-allowed disabled:opacity-35 ${
|
|
48
|
+
choice.selected
|
|
49
|
+
? "border-primary ring-2 ring-primary ring-offset-2 ring-offset-background"
|
|
50
|
+
: "border-border"
|
|
51
|
+
}`}
|
|
52
|
+
/>
|
|
53
|
+
) : (
|
|
54
|
+
<button
|
|
55
|
+
key={choice.choiceId || choice.name}
|
|
56
|
+
type="button"
|
|
57
|
+
aria-pressed={choice.selected}
|
|
58
|
+
disabled={!choice.inStock}
|
|
59
|
+
onClick={() => selectOption(group.name, choice.name)}
|
|
60
|
+
className={chip(choice.selected)}
|
|
61
|
+
>
|
|
62
|
+
{choice.name}
|
|
63
|
+
</button>
|
|
64
|
+
),
|
|
65
|
+
)}
|
|
66
|
+
</div>
|
|
67
|
+
</div>
|
|
68
|
+
))}
|
|
69
|
+
|
|
70
|
+
{modifiers.map((m) => (
|
|
71
|
+
<div className="mb-5" key={m.key}>
|
|
72
|
+
<p className="mb-2 text-xs font-semibold uppercase tracking-wider text-muted-foreground">
|
|
73
|
+
{m.name}
|
|
74
|
+
{m.mandatory ? " *" : ""}
|
|
75
|
+
</p>
|
|
76
|
+
{m.type === "text" ? (
|
|
77
|
+
<input
|
|
78
|
+
type="text"
|
|
79
|
+
value={modifierValues[m.key] ?? ""}
|
|
80
|
+
onChange={(e) => setModifier?.(m.key, e.target.value)}
|
|
81
|
+
aria-label={m.name}
|
|
82
|
+
className="w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none transition-shadow focus:ring-2 focus:ring-primary"
|
|
83
|
+
/>
|
|
84
|
+
) : (
|
|
85
|
+
<div className="flex flex-wrap gap-2">
|
|
86
|
+
{m.choices.map((c) => (
|
|
87
|
+
<button
|
|
88
|
+
key={c.key}
|
|
89
|
+
type="button"
|
|
90
|
+
aria-pressed={modifierValues[m.key] === c.key}
|
|
91
|
+
onClick={() => setModifier?.(m.key, c.key)}
|
|
92
|
+
className={chip(modifierValues[m.key] === c.key)}
|
|
93
|
+
>
|
|
94
|
+
{c.name}
|
|
95
|
+
</button>
|
|
96
|
+
))}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
</div>
|
|
100
|
+
))}
|
|
101
|
+
</div>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// React binding for the cart store. Works in any React root — Astro islands (several on one
|
|
2
|
+
// page share the same store) and SPAs alike.
|
|
3
|
+
import { useSyncExternalStore } from "react";
|
|
4
|
+
import {
|
|
5
|
+
addLine,
|
|
6
|
+
getCartState,
|
|
7
|
+
goToCheckout,
|
|
8
|
+
refreshCart,
|
|
9
|
+
removeCartLine,
|
|
10
|
+
setCartOpen,
|
|
11
|
+
subscribeCart,
|
|
12
|
+
updateLineQuantity,
|
|
13
|
+
type CartState,
|
|
14
|
+
} from "../../wix/storefront/cart-store";
|
|
15
|
+
import type { AddToCartExtras } from "../../wix/storefront/cart";
|
|
16
|
+
|
|
17
|
+
const SERVER_STATE = getCartState();
|
|
18
|
+
|
|
19
|
+
export interface UseCart extends CartState {
|
|
20
|
+
itemCount: number;
|
|
21
|
+
addToCart: (
|
|
22
|
+
productId: string,
|
|
23
|
+
variantId?: string | null,
|
|
24
|
+
quantity?: number,
|
|
25
|
+
extras?: AddToCartExtras,
|
|
26
|
+
) => Promise<void>;
|
|
27
|
+
updateQuantity: (lineItemId: string, quantity: number) => Promise<void>;
|
|
28
|
+
removeLine: (lineItemId: string) => Promise<void>;
|
|
29
|
+
checkout: () => Promise<void>;
|
|
30
|
+
openCart: () => void;
|
|
31
|
+
closeCart: () => void;
|
|
32
|
+
refresh: () => Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useCart(): UseCart {
|
|
36
|
+
const state = useSyncExternalStore(subscribeCart, getCartState, () => SERVER_STATE);
|
|
37
|
+
return {
|
|
38
|
+
...state,
|
|
39
|
+
itemCount: state.cart?.itemCount ?? 0,
|
|
40
|
+
addToCart: addLine,
|
|
41
|
+
updateQuantity: updateLineQuantity,
|
|
42
|
+
removeLine: removeCartLine,
|
|
43
|
+
checkout: goToCheckout,
|
|
44
|
+
openCart: () => setCartOpen(true),
|
|
45
|
+
closeCart: () => setCartOpen(false),
|
|
46
|
+
refresh: refreshCart,
|
|
47
|
+
};
|
|
48
|
+
}
|