@wix/agent-skills 1.17.0 → 1.18.1
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 +74 -83
- 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 +47 -14
- 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-forms.md +57 -10
- 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 +113 -89
- package/skills/wix-manage/references/dashboard-navigation/routes.json +12 -52
- package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-api.md +0 -2
- package/skills/wix-manage/references/forms/create-form.md +272 -184
- package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +13 -10
- package/skills/wix-manage/references/forms/update-form.md +122 -0
- 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/media/generate-image-with-ai.md +86 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +37 -7
- 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 +27 -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 +486 -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 +1836 -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/hooks/storefront/useProductDetail.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Product detail state: option selection → variant resolution → add-to-cart, plus modifier
|
|
2
|
+
// inputs. This is the logic the turtle-run class of bugs comes from (adding variants[0]
|
|
3
|
+
// regardless of the buyer's choice) — always drive a PDP through this hook.
|
|
4
|
+
//
|
|
5
|
+
// SSR-friendly: pass the server-fetched ProductDetail as `initial` (Astro/Next); in a SPA
|
|
6
|
+
// pass the slug and it fetches on mount.
|
|
7
|
+
import { useEffect, useMemo, useState } from "react";
|
|
8
|
+
import { fetchProductBySlug, resolveVariant } from "../../wix/storefront/catalog";
|
|
9
|
+
import type { ProductDetail, ProductOption, ProductVariant } from "../../wix/storefront/types";
|
|
10
|
+
import { useCart } from "./useCart";
|
|
11
|
+
|
|
12
|
+
export interface UseProductDetailOptions {
|
|
13
|
+
initial?: ProductDetail | null;
|
|
14
|
+
slug?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface OptionGroupView extends ProductOption {
|
|
18
|
+
choices: (ProductOption["choices"][number] & { selected: boolean })[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UseProductDetail {
|
|
22
|
+
/** null while loading (or when the slug doesn't resolve — check notFound). */
|
|
23
|
+
product: ProductDetail | null;
|
|
24
|
+
notFound: boolean;
|
|
25
|
+
/** Option groups with per-choice `selected`, ready to render as pills/swatches. */
|
|
26
|
+
optionGroups: OptionGroupView[];
|
|
27
|
+
selectOption: (optionName: string, choiceName: string) => void;
|
|
28
|
+
/** Modifier inputs keyed by modifier key. */
|
|
29
|
+
modifierValues: Record<string, string>;
|
|
30
|
+
setModifier: (key: string, value: string) => void;
|
|
31
|
+
/** The resolved variant; null while the selection is incomplete. */
|
|
32
|
+
variant: ProductVariant | null;
|
|
33
|
+
/** Price to display right now (variant price once resolved, else the product range). */
|
|
34
|
+
price: string;
|
|
35
|
+
compareAtPrice: string | null;
|
|
36
|
+
/** False until every option is selected (and the resolved variant is in stock). */
|
|
37
|
+
canAdd: boolean;
|
|
38
|
+
quantity: number;
|
|
39
|
+
setQuantity: (n: number) => void;
|
|
40
|
+
/** Adds the resolved variant to the cart (opens the drawer). Throws on refusal. */
|
|
41
|
+
add: () => Promise<void>;
|
|
42
|
+
adding: boolean;
|
|
43
|
+
error: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useProductDetail({ initial, slug }: UseProductDetailOptions): UseProductDetail {
|
|
47
|
+
const [product, setProduct] = useState<ProductDetail | null>(initial ?? null);
|
|
48
|
+
const [notFound, setNotFound] = useState(false);
|
|
49
|
+
const [selections, setSelections] = useState<Record<string, string>>({});
|
|
50
|
+
const [modifierValues, setModifierValues] = useState<Record<string, string>>({});
|
|
51
|
+
const [quantity, setQuantity] = useState(1);
|
|
52
|
+
const [adding, setAdding] = useState(false);
|
|
53
|
+
const [error, setError] = useState<string | null>(null);
|
|
54
|
+
const { addToCart } = useCart();
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
if (initial || !slug) return;
|
|
58
|
+
let alive = true;
|
|
59
|
+
fetchProductBySlug(slug)
|
|
60
|
+
.then((p) => {
|
|
61
|
+
if (!alive) return;
|
|
62
|
+
setProduct(p);
|
|
63
|
+
setNotFound(p === null);
|
|
64
|
+
})
|
|
65
|
+
.catch(() => alive && setNotFound(true));
|
|
66
|
+
return () => {
|
|
67
|
+
alive = false;
|
|
68
|
+
};
|
|
69
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
|
+
}, [slug]);
|
|
71
|
+
|
|
72
|
+
const variant = useMemo(
|
|
73
|
+
() => (product ? resolveVariant(product, selections) : null),
|
|
74
|
+
[product, selections],
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const optionGroups: OptionGroupView[] = useMemo(
|
|
78
|
+
() =>
|
|
79
|
+
(product?.options ?? []).map((o) => ({
|
|
80
|
+
...o,
|
|
81
|
+
choices: o.choices.map((c) => ({ ...c, selected: selections[o.name] === c.name })),
|
|
82
|
+
})),
|
|
83
|
+
[product, selections],
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
const mandatoryModifiersFilled = (product?.modifiers ?? [])
|
|
87
|
+
.filter((m) => m.mandatory)
|
|
88
|
+
.every((m) => (modifierValues[m.key] ?? "").length > 0);
|
|
89
|
+
|
|
90
|
+
const canAdd =
|
|
91
|
+
!!product &&
|
|
92
|
+
product.availability !== "OUT_OF_STOCK" &&
|
|
93
|
+
variant !== null &&
|
|
94
|
+
variant.inStock &&
|
|
95
|
+
mandatoryModifiersFilled;
|
|
96
|
+
|
|
97
|
+
async function add(): Promise<void> {
|
|
98
|
+
if (!product || !variant) return;
|
|
99
|
+
setAdding(true);
|
|
100
|
+
setError(null);
|
|
101
|
+
try {
|
|
102
|
+
const choiceModifiers: Record<string, string> = {};
|
|
103
|
+
const textModifiers: Record<string, string> = {};
|
|
104
|
+
for (const m of product.modifiers) {
|
|
105
|
+
const value = modifierValues[m.key];
|
|
106
|
+
if (!value) continue;
|
|
107
|
+
if (m.type === "text") textModifiers[m.key] = value;
|
|
108
|
+
else choiceModifiers[m.key] = value;
|
|
109
|
+
}
|
|
110
|
+
await addToCart(product.id, variant.variantId, quantity, {
|
|
111
|
+
modifierChoices: choiceModifiers,
|
|
112
|
+
customTextFields: textModifiers,
|
|
113
|
+
});
|
|
114
|
+
} catch (e) {
|
|
115
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
116
|
+
throw e;
|
|
117
|
+
} finally {
|
|
118
|
+
setAdding(false);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
product,
|
|
124
|
+
notFound,
|
|
125
|
+
optionGroups,
|
|
126
|
+
selectOption: (optionName, choiceName) =>
|
|
127
|
+
setSelections((s) => ({ ...s, [optionName]: choiceName })),
|
|
128
|
+
modifierValues,
|
|
129
|
+
setModifier: (key, value) => setModifierValues((v) => ({ ...v, [key]: value })),
|
|
130
|
+
variant,
|
|
131
|
+
price: variant?.price || product?.price || "",
|
|
132
|
+
compareAtPrice: variant ? variant.compareAtPrice : (product?.compareAtPrice ?? null),
|
|
133
|
+
canAdd,
|
|
134
|
+
quantity,
|
|
135
|
+
setQuantity,
|
|
136
|
+
add,
|
|
137
|
+
adding,
|
|
138
|
+
error,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Catalog listing + category filter for a shop/listing surface.
|
|
2
|
+
//
|
|
3
|
+
// SSR-friendly: when the host fetched the data already (Astro frontmatter, a server
|
|
4
|
+
// component), pass it as `initial` and no client fetch happens for it. In a pure SPA,
|
|
5
|
+
// pass nothing and the hook fetches on mount. Category switches always fetch live
|
|
6
|
+
// (server-side-filtered by the category id).
|
|
7
|
+
import { useEffect, useState } from "react";
|
|
8
|
+
import {
|
|
9
|
+
fetchCategories,
|
|
10
|
+
fetchProducts,
|
|
11
|
+
fetchProductsByCategory,
|
|
12
|
+
} from "../../wix/storefront/catalog";
|
|
13
|
+
import type { Category, ProductSummary } from "../../wix/storefront/types";
|
|
14
|
+
|
|
15
|
+
export interface UseShopOptions {
|
|
16
|
+
initialProducts?: ProductSummary[];
|
|
17
|
+
initialCategories?: Category[];
|
|
18
|
+
limit?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UseShop {
|
|
22
|
+
/** null while the first load is in flight — render skeletons, not an empty state. */
|
|
23
|
+
products: ProductSummary[] | null;
|
|
24
|
+
categories: Category[];
|
|
25
|
+
/** null = "all products". */
|
|
26
|
+
activeCategoryId: string | null;
|
|
27
|
+
setActiveCategoryId: (id: string | null) => void;
|
|
28
|
+
loading: boolean;
|
|
29
|
+
error: string | null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useShop({ initialProducts, initialCategories, limit = 100 }: UseShopOptions = {}): UseShop {
|
|
33
|
+
const [products, setProducts] = useState<ProductSummary[] | null>(initialProducts ?? null);
|
|
34
|
+
const [categories, setCategories] = useState<Category[]>(initialCategories ?? []);
|
|
35
|
+
const [activeCategoryId, setActiveCategoryId] = useState<string | null>(null);
|
|
36
|
+
const [loading, setLoading] = useState(false);
|
|
37
|
+
const [error, setError] = useState<string | null>(null);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
if (initialCategories) return;
|
|
41
|
+
let alive = true;
|
|
42
|
+
fetchCategories().then((c) => alive && setCategories(c));
|
|
43
|
+
return () => {
|
|
44
|
+
alive = false;
|
|
45
|
+
};
|
|
46
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
47
|
+
}, []);
|
|
48
|
+
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
// The initial all-products data may have come from the server; only fetch when it
|
|
51
|
+
// didn't, or when the buyer picked a category.
|
|
52
|
+
if (activeCategoryId === null && initialProducts) {
|
|
53
|
+
setProducts(initialProducts);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
let alive = true;
|
|
57
|
+
setLoading(true);
|
|
58
|
+
setError(null);
|
|
59
|
+
const load = activeCategoryId
|
|
60
|
+
? fetchProductsByCategory(activeCategoryId, { limit })
|
|
61
|
+
: fetchProducts({ limit });
|
|
62
|
+
load
|
|
63
|
+
.then((items) => {
|
|
64
|
+
if (!alive) return;
|
|
65
|
+
setProducts(items);
|
|
66
|
+
})
|
|
67
|
+
.catch((e) => {
|
|
68
|
+
if (!alive) return;
|
|
69
|
+
setProducts([]);
|
|
70
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
71
|
+
})
|
|
72
|
+
.finally(() => alive && setLoading(false));
|
|
73
|
+
return () => {
|
|
74
|
+
alive = false;
|
|
75
|
+
};
|
|
76
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
77
|
+
}, [activeCategoryId, limit]);
|
|
78
|
+
|
|
79
|
+
return { products, categories, activeCategoryId, setActiveCategoryId, loading, error };
|
|
80
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Client-side cart state — a module-scope store, deliberately NOT a React context.
|
|
2
|
+
// A context can't span Astro islands (each island is its own React root); a module
|
|
3
|
+
// singleton is shared by every island in the page bundle, and works identically in a
|
|
4
|
+
// single-root SPA. Consume it through useCart() (hooks/), or subscribe directly.
|
|
5
|
+
import type { Cart } from "./types";
|
|
6
|
+
import {
|
|
7
|
+
addToCart as apiAdd,
|
|
8
|
+
fetchCart,
|
|
9
|
+
removeLine as apiRemove,
|
|
10
|
+
updateQuantity as apiUpdate,
|
|
11
|
+
checkoutUrl,
|
|
12
|
+
type AddToCartExtras,
|
|
13
|
+
} from "./cart";
|
|
14
|
+
|
|
15
|
+
export interface CartState {
|
|
16
|
+
cart: Cart | null;
|
|
17
|
+
/** True while any cart operation is in flight. */
|
|
18
|
+
busy: boolean;
|
|
19
|
+
/** Last failed operation's message — render it; a new operation clears it. */
|
|
20
|
+
error: string | null;
|
|
21
|
+
/** Cart drawer visibility (CartButton opens it, CartDrawer renders by it). */
|
|
22
|
+
open: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const EMPTY: CartState = { cart: null, busy: false, error: null, open: false };
|
|
26
|
+
|
|
27
|
+
let state: CartState = EMPTY;
|
|
28
|
+
const listeners = new Set<() => void>();
|
|
29
|
+
let loaded = false;
|
|
30
|
+
|
|
31
|
+
function setState(patch: Partial<CartState>): void {
|
|
32
|
+
state = { ...state, ...patch };
|
|
33
|
+
for (const l of listeners) l();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getCartState(): CartState {
|
|
37
|
+
return state;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function subscribeCart(listener: () => void): () => void {
|
|
41
|
+
listeners.add(listener);
|
|
42
|
+
// First subscriber triggers the initial load (browser only — SSR renders the empty state).
|
|
43
|
+
if (!loaded && typeof window !== "undefined") {
|
|
44
|
+
loaded = true;
|
|
45
|
+
void refreshCart();
|
|
46
|
+
}
|
|
47
|
+
return () => listeners.delete(listener);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function run(op: () => Promise<Cart>): Promise<void> {
|
|
51
|
+
setState({ busy: true, error: null });
|
|
52
|
+
try {
|
|
53
|
+
setState({ cart: await op(), busy: false });
|
|
54
|
+
} catch (e) {
|
|
55
|
+
setState({ busy: false, error: e instanceof Error ? e.message : String(e) });
|
|
56
|
+
throw e;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function refreshCart(): Promise<void> {
|
|
61
|
+
try {
|
|
62
|
+
setState({ cart: await fetchCart() });
|
|
63
|
+
} catch {
|
|
64
|
+
/* no cart yet — leave the empty state */
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Add to cart and open the drawer. Throws (and sets .error) on refusal. */
|
|
69
|
+
export async function addLine(
|
|
70
|
+
productId: string,
|
|
71
|
+
variantId?: string | null,
|
|
72
|
+
quantity = 1,
|
|
73
|
+
extras?: AddToCartExtras,
|
|
74
|
+
): Promise<void> {
|
|
75
|
+
await run(() => apiAdd(productId, variantId, quantity, extras));
|
|
76
|
+
setState({ open: true });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function updateLineQuantity(lineItemId: string, quantity: number): Promise<void> {
|
|
80
|
+
await run(() => apiUpdate(lineItemId, quantity));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function removeCartLine(lineItemId: string): Promise<void> {
|
|
84
|
+
await run(() => apiRemove(lineItemId));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Navigate to the Wix-hosted checkout. Never hand-build a checkout URL. */
|
|
88
|
+
export async function goToCheckout(): Promise<void> {
|
|
89
|
+
setState({ busy: true, error: null });
|
|
90
|
+
try {
|
|
91
|
+
const url = await checkoutUrl();
|
|
92
|
+
window.location.href = url;
|
|
93
|
+
} catch (e) {
|
|
94
|
+
setState({ busy: false, error: e instanceof Error ? e.message : String(e) });
|
|
95
|
+
throw e;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function setCartOpen(open: boolean): void {
|
|
100
|
+
setState({ open });
|
|
101
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
// Cart + checkout (Wix eCom Cart V2) — the only file that touches raw cart entities.
|
|
2
|
+
// Copy as-is; extend by calling these exports, never by editing them. The request shapes are
|
|
3
|
+
// exact (catalogItems wrapper, options.variantId, the redirect-session body) and rewriting
|
|
4
|
+
// them is how carts break.
|
|
5
|
+
//
|
|
6
|
+
// Failures are loud: these throw on out-of-stock lines, an empty cart at checkout, and a
|
|
7
|
+
// missing required selection — surface the message to the buyer, don't swallow it.
|
|
8
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/get-current-cart.md
|
|
9
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/add-line-items-to-current-cart.md
|
|
10
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/update-line-items.md
|
|
11
|
+
// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
|
|
12
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/query-products.md
|
|
13
|
+
import { currentCartV2 } from "@wix/ecom";
|
|
14
|
+
import { redirects as redirectsModule } from "@wix/redirects";
|
|
15
|
+
import { productsV3 } from "@wix/stores";
|
|
16
|
+
import { wixModule } from "../sdk";
|
|
17
|
+
import { imgSrc } from "../media";
|
|
18
|
+
import { formatMoney } from "../money";
|
|
19
|
+
import type { Cart, CartLine } from "./types";
|
|
20
|
+
|
|
21
|
+
const cartApi = wixModule(currentCartV2);
|
|
22
|
+
const redirects = wixModule(redirectsModule);
|
|
23
|
+
const productsApi = wixModule(productsV3);
|
|
24
|
+
|
|
25
|
+
// The V2 cart does NOT return line-item images (attributes.image is typed but comes back
|
|
26
|
+
// absent — verified against a live cart), so images are joined from the catalog by the
|
|
27
|
+
// line's catalogItemId and cached for the session.
|
|
28
|
+
const lineImageCache = new Map<string, string>();
|
|
29
|
+
|
|
30
|
+
async function fillLineImages(lines: CartLine[], raws: RawCart[]): Promise<void> {
|
|
31
|
+
const wanted = new Map<string, number[]>(); // productId -> line indexes
|
|
32
|
+
raws.forEach((raw, i) => {
|
|
33
|
+
if (lines[i].imageUrl) return;
|
|
34
|
+
const pid = raw.source?.catalogReference?.catalogItemId;
|
|
35
|
+
if (!pid) return;
|
|
36
|
+
const cached = lineImageCache.get(pid);
|
|
37
|
+
if (cached !== undefined) {
|
|
38
|
+
lines[i].imageUrl = cached;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
wanted.set(pid, [...(wanted.get(pid) ?? []), i]);
|
|
42
|
+
});
|
|
43
|
+
if (!wanted.size) return;
|
|
44
|
+
try {
|
|
45
|
+
const res = await productsApi
|
|
46
|
+
.queryProducts()
|
|
47
|
+
.in("_id", [...wanted.keys()])
|
|
48
|
+
.find();
|
|
49
|
+
for (const p of (res.items ?? []) as RawCart[]) {
|
|
50
|
+
const url = imgSrc(p.media?.main, 300, 300);
|
|
51
|
+
lineImageCache.set(p._id, url);
|
|
52
|
+
for (const i of wanted.get(p._id) ?? []) lines[i].imageUrl = url;
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
/* images are a nicety — the cart stays correct without them */
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Public app id of the Wix Stores catalog — required inside every catalogReference.
|
|
60
|
+
const WIX_STORES_APP_ID = "215238eb-22a5-4c36-9e7b-e7c08025e04e";
|
|
61
|
+
|
|
62
|
+
type RawCart = Record<string, any>;
|
|
63
|
+
|
|
64
|
+
function cartCurrency(raw: RawCart | null): string {
|
|
65
|
+
return raw?.customerInfo?.currencyCode ?? raw?.businessInfo?.currencyCode ?? "";
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function toLine(raw: RawCart, currency: string): CartLine {
|
|
69
|
+
const descriptionLines: string[] = (raw.attributes?.descriptionLines ?? [])
|
|
70
|
+
.map((d: RawCart) => {
|
|
71
|
+
const label = d.name?.original ?? "";
|
|
72
|
+
const value = d.plainText?.original ?? d.colorInfo?.original ?? "";
|
|
73
|
+
return label && value ? `${label}: ${value}` : value || label;
|
|
74
|
+
})
|
|
75
|
+
.filter(Boolean);
|
|
76
|
+
return {
|
|
77
|
+
lineItemId: raw._id ?? "",
|
|
78
|
+
productName: raw.name?.original ?? "",
|
|
79
|
+
quantity: raw.quantityInfo?.confirmedQuantity ?? 0,
|
|
80
|
+
unitPrice: formatMoney(raw.pricing?.unitPrice, currency),
|
|
81
|
+
linePrice: formatMoney(raw.pricing?.totalPrice, currency),
|
|
82
|
+
imageUrl: imgSrc(raw.attributes?.image, 300, 300),
|
|
83
|
+
descriptionLines,
|
|
84
|
+
status: raw.status ?? "IN_STOCK",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function toCart(raw: RawCart | null, subtotal: string): Cart {
|
|
89
|
+
const currency = cartCurrency(raw);
|
|
90
|
+
const lines = (raw?.lineItems ?? []).map((l: RawCart) => toLine(l, currency));
|
|
91
|
+
return {
|
|
92
|
+
lines,
|
|
93
|
+
itemCount: lines.reduce((sum: number, l: CartLine) => sum + l.quantity, 0),
|
|
94
|
+
subtotal,
|
|
95
|
+
currency,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function readCartWithSubtotal(raw: RawCart | null): Promise<Cart> {
|
|
100
|
+
if (!raw?.lineItems?.length) return toCart(raw, "");
|
|
101
|
+
// The authoritative after-discount subtotal comes from the cart estimate, never from
|
|
102
|
+
// hand-summing line items (tax and shipping resolve at checkout).
|
|
103
|
+
let subtotal = "";
|
|
104
|
+
try {
|
|
105
|
+
const estimate: RawCart = await cartApi.estimateCurrentCart();
|
|
106
|
+
subtotal = formatMoney(estimate?.summary?.priceSummary?.subtotal, cartCurrency(raw));
|
|
107
|
+
} catch {
|
|
108
|
+
/* estimate is a display nicety — the cart itself is still valid */
|
|
109
|
+
}
|
|
110
|
+
const cart = toCart(raw, subtotal);
|
|
111
|
+
await fillLineImages(cart.lines, raw.lineItems as RawCart[]);
|
|
112
|
+
return cart;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Read the visitor's current cart. An empty Cart (not an error) when none exists yet. */
|
|
116
|
+
export async function fetchCart(): Promise<Cart> {
|
|
117
|
+
try {
|
|
118
|
+
const { cart } = await cartApi.getCurrentCart();
|
|
119
|
+
return readCartWithSubtotal(cart as RawCart);
|
|
120
|
+
} catch {
|
|
121
|
+
return toCart(null, "");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface AddToCartExtras {
|
|
126
|
+
/** TEXT_CHOICES modifier selections: modifier key -> choice key. */
|
|
127
|
+
modifierChoices?: Record<string, string>;
|
|
128
|
+
/** FREE_TEXT modifier inputs: freeTextSettings key -> the buyer's text. */
|
|
129
|
+
customTextFields?: Record<string, string>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Add a product to the current cart.
|
|
134
|
+
* For a product WITH options, `variantId` is mandatory — resolve it with
|
|
135
|
+
* `resolveVariant()` from ./catalog first. Mandatory modifiers must be included.
|
|
136
|
+
*/
|
|
137
|
+
export async function addToCart(
|
|
138
|
+
productId: string,
|
|
139
|
+
variantId?: string | null,
|
|
140
|
+
quantity = 1,
|
|
141
|
+
{ modifierChoices, customTextFields }: AddToCartExtras = {},
|
|
142
|
+
): Promise<Cart> {
|
|
143
|
+
const options: Record<string, unknown> = {};
|
|
144
|
+
if (variantId) options.variantId = variantId;
|
|
145
|
+
if (modifierChoices && Object.keys(modifierChoices).length) options.options = modifierChoices;
|
|
146
|
+
if (customTextFields && Object.keys(customTextFields).length) options.customTextFields = customTextFields;
|
|
147
|
+
|
|
148
|
+
const { cart } = await cartApi.addLineItemsToCurrentCart({
|
|
149
|
+
catalogItems: [
|
|
150
|
+
{
|
|
151
|
+
quantity,
|
|
152
|
+
catalogReference: {
|
|
153
|
+
catalogItemId: productId,
|
|
154
|
+
appId: WIX_STORES_APP_ID,
|
|
155
|
+
...(Object.keys(options).length ? { options } : {}),
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const line = ((cart as RawCart)?.lineItems ?? []).find(
|
|
162
|
+
(l: RawCart) =>
|
|
163
|
+
l.source?.catalogReference?.catalogItemId === productId &&
|
|
164
|
+
(!variantId || l.source?.catalogReference?.options?.variantId === variantId),
|
|
165
|
+
);
|
|
166
|
+
if (line?.status && line.status !== "IN_STOCK") {
|
|
167
|
+
throw new Error(`This item isn't available right now (${line.status.toLowerCase().replace(/_/g, " ")}).`);
|
|
168
|
+
}
|
|
169
|
+
if (!line || line.quantityInfo?.confirmedQuantity === 0) {
|
|
170
|
+
throw new Error(
|
|
171
|
+
"The item couldn't be added. Make sure every required selection was made " +
|
|
172
|
+
"(options for a product with variants, and all mandatory customizations).",
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return readCartWithSubtotal(cart as RawCart);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Change a line's quantity. `lineItemId` is CartLine.lineItemId, never the product id. */
|
|
179
|
+
export async function updateQuantity(lineItemId: string, quantity: number): Promise<Cart> {
|
|
180
|
+
const { cart } = await cartApi.updateLineItemsInCurrentCart({
|
|
181
|
+
lineItems: [{ lineItemId, quantity: { newQuantity: quantity } }],
|
|
182
|
+
});
|
|
183
|
+
return readCartWithSubtotal(cart as RawCart);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Remove a line from the cart by CartLine.lineItemId. */
|
|
187
|
+
export async function removeLine(lineItemId: string): Promise<Cart> {
|
|
188
|
+
const { cart } = await cartApi.removeLineItemsFromCurrentCart([lineItemId]);
|
|
189
|
+
return readCartWithSubtotal(cart as RawCart);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Start the Wix-hosted checkout for the current cart and return the URL to navigate to.
|
|
194
|
+
* The cart's id IS the checkout id — no separate checkout-creation call.
|
|
195
|
+
* Call from the browser: the return origin must be the site's real https origin
|
|
196
|
+
* (window.location.origin), never a server-derived request origin.
|
|
197
|
+
*/
|
|
198
|
+
export async function checkoutUrl(): Promise<string> {
|
|
199
|
+
const { cart } = await cartApi.getCurrentCart();
|
|
200
|
+
const raw = cart as RawCart;
|
|
201
|
+
const lines: RawCart[] = raw?.lineItems ?? [];
|
|
202
|
+
if (!lines.length) throw new Error("Your cart is empty.");
|
|
203
|
+
const unavailable = lines.filter((l) => l.status && l.status !== "IN_STOCK");
|
|
204
|
+
if (unavailable.length) {
|
|
205
|
+
const names = unavailable.map((l) => l.name?.original).filter(Boolean).join(", ");
|
|
206
|
+
throw new Error(`Some items are no longer available: ${names}.`);
|
|
207
|
+
}
|
|
208
|
+
if (!raw?._id) throw new Error("Checkout couldn't start: the cart has no id.");
|
|
209
|
+
|
|
210
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
211
|
+
const session = await redirects.createRedirectSession({
|
|
212
|
+
ecomCheckout: { checkoutId: raw._id },
|
|
213
|
+
callbacks: {
|
|
214
|
+
postFlowUrl: origin ? `${origin}/` : undefined,
|
|
215
|
+
thankYouPageUrl: origin ? `${origin}/` : undefined,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
const url = session?.redirectSession?.fullUrl;
|
|
219
|
+
if (!url) throw new Error("Checkout couldn't start: no redirect URL returned.");
|
|
220
|
+
return url;
|
|
221
|
+
}
|