@wix/agent-skills 1.19.2 → 1.20.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/references/SERVICE_PLUGIN.md +14 -1
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +7 -6
- package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
- package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
- package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
- package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
- package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
- package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
- package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
- package/skills/wix-headless/references/CAPABILITIES.md +2 -2
- package/skills/wix-headless/references/inline-recipes/experience-store.md +4 -3
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +98 -18
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
- package/skills/wix-headless-fast/SKILL.md +200 -34
- package/skills/wix-headless-fast/entry/skill.md +10 -2
- package/skills/wix-headless-fast/install/attach.mjs +253 -0
- package/skills/wix-headless-fast/install/deploy.mjs +109 -9
- package/skills/wix-headless-fast/install/fast-path.mjs +28 -31
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +224 -60
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
- package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
- package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
- package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +179 -33
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
- package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
- package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +236 -62
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
- package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
- package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +239 -83
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
- package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
- package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
- package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +214 -26
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
- package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
- package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
- package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +314 -63
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
- package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
- package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +238 -59
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
- package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +212 -51
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
- package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
- package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +240 -47
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
- package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
- package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
- package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +55 -1
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
- package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
- package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
- package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +371 -81
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +136 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -130
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -182
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +53 -136
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +309 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +73 -230
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
- package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
- package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +25 -5
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +24 -0
- package/skills/wix-manage/SKILL.md +4 -1
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
- package/skills/wix-manage/references/events/create-wix-event.md +46 -0
- package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
- package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
- package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
- package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44.md +19 -4
- package/skills/wix-vibe-headless/platforms/generic.md +6 -0
- package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
- package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
- package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
- package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +42 -41
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +7 -1
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
- package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
|
@@ -1,227 +1,80 @@
|
|
|
1
|
-
// Catalog reads (Wix Stores Catalog V3) — the only file that touches raw catalog
|
|
2
|
-
// Everything it returns is a plain DTO from ./types
|
|
3
|
-
//
|
|
1
|
+
// Catalog reads (Wix Stores Catalog V3) over the SDK — the only file that touches raw catalog
|
|
2
|
+
// entities on this transport. Everything it returns is a plain DTO from ./types. The rules and
|
|
3
|
+
// mappers live in ./catalog-core (shared with the REST twin in references/storefront/rest/); this
|
|
4
|
+
// file is the transport only. Copy as-is; extend by adding functions, not by editing these.
|
|
4
5
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
|
|
5
6
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/get-product-by-slug.md
|
|
6
7
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/query-categories.md
|
|
7
8
|
import { productsV3 } from "@wix/stores";
|
|
8
9
|
import { categories as categoriesModule } from "@wix/categories";
|
|
9
10
|
import { wixModule } from "../sdk";
|
|
10
|
-
import { imgSrc } from "../media";
|
|
11
|
-
import
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
import { imgSrc, mediaKey } from "../media";
|
|
12
|
+
import {
|
|
13
|
+
CATALOG_SORTS,
|
|
14
|
+
DETAIL_FIELDS,
|
|
15
|
+
LIST_FIELDS,
|
|
16
|
+
catalogFilter,
|
|
17
|
+
resolveVariant,
|
|
18
|
+
searchQuery,
|
|
19
|
+
sellingPrice,
|
|
20
|
+
toCategory,
|
|
21
|
+
toDetail,
|
|
22
|
+
toFacetData,
|
|
23
|
+
toSummary,
|
|
24
|
+
type CatalogSearchOptions,
|
|
25
|
+
type CatalogSort,
|
|
26
|
+
type Raw,
|
|
27
|
+
} from "./catalog-core";
|
|
28
|
+
import type { Category, Facet, FacetData, ProductDetail, ProductSummary } from "./types";
|
|
29
|
+
|
|
30
|
+
export { CATALOG_SORTS, resolveVariant, sellingPrice, type CatalogSearchOptions, type CatalogSort };
|
|
20
31
|
|
|
21
32
|
const products = wixModule(productsV3);
|
|
22
33
|
const categories = wixModule(categoriesModule);
|
|
23
34
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function toSummary(raw: RawProduct): ProductSummary {
|
|
40
|
-
const options: RawProduct[] = raw.options ?? [];
|
|
41
|
-
const galleryItems: RawProduct[] = raw.media?.itemsInfo?.items ?? [];
|
|
42
|
-
const mainUrl = imgSrc(raw.media?.main, 800, 800);
|
|
43
|
-
const hover = galleryItems
|
|
44
|
-
.map((m) => imgSrc(m.image ?? m, 800, 800))
|
|
45
|
-
.filter((u) => u && u !== mainUrl);
|
|
46
|
-
const availability = toAvailability(raw);
|
|
47
|
-
const preorder = raw.inventory?.preorderStatus === "ENABLED" && availability === "OUT_OF_STOCK";
|
|
48
|
-
const optionsSummary = options
|
|
49
|
-
.map((o) => {
|
|
50
|
-
const visible = (o.choicesSettings?.choices ?? []).filter((c: RawProduct) => c.visible !== false);
|
|
51
|
-
return `${visible.length} ${String(o.name ?? "").toLowerCase()}${visible.length === 1 ? "" : "s"}`;
|
|
52
|
-
})
|
|
53
|
-
.join(" · ");
|
|
54
|
-
return {
|
|
55
|
-
id: raw._id ?? "",
|
|
56
|
-
slug: raw.slug ?? "",
|
|
57
|
-
name: raw.name ?? "",
|
|
58
|
-
price: raw.actualPriceRange?.minValue?.formattedAmount ?? "",
|
|
59
|
-
maxPrice: raw.actualPriceRange?.maxValue?.formattedAmount ?? "",
|
|
60
|
-
compareAtPrice: raw.compareAtPriceRange?.minValue?.formattedAmount ?? null,
|
|
61
|
-
ribbon: raw.ribbon?.name ?? null,
|
|
62
|
-
availability,
|
|
63
|
-
preorder,
|
|
64
|
-
imageUrl: mainUrl,
|
|
65
|
-
hoverImageUrl: hover[0] ?? "",
|
|
66
|
-
optionsSummary,
|
|
67
|
-
quickAddable: options.length === 0 && availability === "IN_STOCK",
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function toOptions(raw: RawProduct): ProductOption[] {
|
|
72
|
-
return (raw.options ?? []).map((o: RawProduct) => {
|
|
73
|
-
const isColor = o.optionRenderType === "SWATCH_CHOICES" || o.optionRenderType === "COLOR_CHOICES";
|
|
74
|
-
return {
|
|
75
|
-
id: o._id ?? o.id ?? o.name ?? "",
|
|
76
|
-
name: o.name ?? "",
|
|
77
|
-
isColor,
|
|
78
|
-
choices: (o.choicesSettings?.choices ?? [])
|
|
79
|
-
.filter((c: RawProduct) => c.visible !== false)
|
|
80
|
-
.map((c: RawProduct) => ({
|
|
81
|
-
choiceId: c.choiceId ?? "",
|
|
82
|
-
name: c.name ?? "",
|
|
83
|
-
colorCode: c.colorCode ?? null,
|
|
84
|
-
inStock: c.inStock !== false,
|
|
85
|
-
})),
|
|
86
|
-
};
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function toModifiers(raw: RawProduct): ProductModifier[] {
|
|
91
|
-
return (raw.modifiers ?? []).map((m: RawProduct) => ({
|
|
92
|
-
key: m.key ?? m.freeTextSettings?.key ?? m.name ?? "",
|
|
93
|
-
name: m.name ?? "",
|
|
94
|
-
mandatory: m.mandatory === true,
|
|
95
|
-
type: m.modifierRenderType === "FREE_TEXT" ? ("text" as const) : ("choices" as const),
|
|
96
|
-
choices: (m.choicesSettings?.choices ?? []).map((c: RawProduct) => ({
|
|
97
|
-
key: c.key ?? c.name ?? "",
|
|
98
|
-
name: c.name ?? "",
|
|
99
|
-
})),
|
|
100
|
-
}));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function toVariants(raw: RawProduct): ProductVariant[] {
|
|
104
|
-
return (raw.variantsInfo?.variants ?? []).map((v: RawProduct) => {
|
|
105
|
-
const choices: Record<string, string> = {};
|
|
106
|
-
for (const c of v.choices ?? []) {
|
|
107
|
-
const names = c.optionChoiceNames;
|
|
108
|
-
if (names?.optionName) choices[names.optionName] = names.choiceName ?? "";
|
|
35
|
+
/**
|
|
36
|
+
* Search visible catalog products — sorted, filtered, faceted, and searched by Wix across the
|
|
37
|
+
* whole catalog, then cursor-paged. `nextCursor` continues the same query; start over (no cursor)
|
|
38
|
+
* whenever any selection changes. `total` (the "N products" line) is issued with the first page.
|
|
39
|
+
*/
|
|
40
|
+
export async function searchCatalog(o: CatalogSearchOptions = {}): Promise<{ products: ProductSummary[]; nextCursor: string | null; total: number | null }> {
|
|
41
|
+
const query = searchQuery(o);
|
|
42
|
+
const res: Raw = await products.searchProducts(query, { fields: LIST_FIELDS as any });
|
|
43
|
+
let total: number | null = null;
|
|
44
|
+
if (!o.cursor && query.filter) {
|
|
45
|
+
try {
|
|
46
|
+
const count: Raw = await products.countProducts({ filter: query.filter });
|
|
47
|
+
total = typeof count?.count === "number" ? count.count : null;
|
|
48
|
+
} catch {
|
|
49
|
+
/* the count is a nicety — the page is still valid */
|
|
109
50
|
}
|
|
110
|
-
|
|
111
|
-
variantId: v._id ?? v.variantId ?? "",
|
|
112
|
-
choices,
|
|
113
|
-
price: v.price?.actualPrice?.formattedAmount ?? "",
|
|
114
|
-
compareAtPrice: v.price?.compareAtPrice?.formattedAmount ?? null,
|
|
115
|
-
inStock: v.inventoryStatus?.inStock !== false,
|
|
116
|
-
};
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function toDetail(raw: RawProduct): ProductDetail {
|
|
121
|
-
const summary = toSummary(raw);
|
|
122
|
-
const gallery = [
|
|
123
|
-
summary.imageUrl,
|
|
124
|
-
...(raw.media?.itemsInfo?.items ?? []).map((m: RawProduct) => imgSrc(m.image ?? m, 1200, 1200)),
|
|
125
|
-
].filter((u, i, arr) => u && arr.indexOf(u) === i);
|
|
51
|
+
}
|
|
126
52
|
return {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
options: toOptions(raw),
|
|
131
|
-
modifiers: toModifiers(raw),
|
|
132
|
-
variants: toVariants(raw),
|
|
53
|
+
products: ((res.products ?? []) as Raw[]).map((p) => toSummary(p, imgSrc, mediaKey)),
|
|
54
|
+
nextCursor: res.pagingMetadata?.cursors?.next ?? null,
|
|
55
|
+
total,
|
|
133
56
|
};
|
|
134
57
|
}
|
|
135
58
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
|
|
140
|
-
export const CATALOG_SORTS = {
|
|
141
|
-
featured: { label: "Default order" }, // the catalog's own order; no merchant-curated promise
|
|
142
|
-
priceAsc: { label: "Price: low to high" },
|
|
143
|
-
priceHigh: { label: "Price: high to low" },
|
|
144
|
-
name: { label: "Name: A\u2013Z" },
|
|
145
|
-
newest: { label: "Newest" },
|
|
146
|
-
} as const;
|
|
147
|
-
export type CatalogSort = keyof typeof CATALOG_SORTS;
|
|
148
|
-
|
|
149
|
-
const SORT_FIELDS: Partial<Record<CatalogSort, { fieldName: string; order: "ASC" | "DESC" }[]>> = {
|
|
150
|
-
priceAsc: [{ fieldName: "actualPriceRange.minValue.amount", order: "ASC" }, { fieldName: "name", order: "ASC" }],
|
|
151
|
-
priceHigh: [{ fieldName: "actualPriceRange.minValue.amount", order: "DESC" }, { fieldName: "name", order: "ASC" }],
|
|
152
|
-
name: [{ fieldName: "name", order: "ASC" }],
|
|
153
|
-
newest: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "name", order: "ASC" }],
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
function priceBound(value: number | string | undefined, name: string): number | undefined {
|
|
157
|
-
if (value == null || value === "") return undefined;
|
|
158
|
-
const parsed = Number(value);
|
|
159
|
-
if (!String(value).trim() || !Number.isFinite(parsed) || parsed < 0) {
|
|
160
|
-
throw new Error(`${name} must be a non-negative number.`);
|
|
161
|
-
}
|
|
162
|
-
return parsed;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export interface CatalogSearchOptions {
|
|
166
|
-
limit?: number;
|
|
167
|
-
/** Continues the ORIGINAL query — pass alone; new sort/filter values start without one. */
|
|
168
|
-
cursor?: string;
|
|
169
|
-
categoryId?: string | null;
|
|
170
|
-
sort?: CatalogSort;
|
|
171
|
-
/** Bounds compare the product's minimum actual variant price, in site currency. */
|
|
172
|
-
minPrice?: number | string;
|
|
173
|
-
maxPrice?: number | string;
|
|
174
|
-
inStockOnly?: boolean;
|
|
175
|
-
/** Name search, max 100 chars. */
|
|
176
|
-
search?: string;
|
|
59
|
+
/** The filterable options of the catalog (or one category). Non-fatal: [] when the read fails. */
|
|
60
|
+
export async function fetchFacets(scope: { categoryId?: string | null } = {}): Promise<Facet[]> {
|
|
61
|
+
return (await fetchFacetData(scope)).facets;
|
|
177
62
|
}
|
|
178
63
|
|
|
179
64
|
/**
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
* whenever any selection changes.
|
|
65
|
+
* Facets plus the scope's price bounds (lowest minimum, highest maximum across its products) — the
|
|
66
|
+
* filter panel's slider needs both, from one 100-product read. Non-fatal: empty on failure.
|
|
183
67
|
*/
|
|
184
|
-
export async function
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
}: CatalogSearchOptions = {}): Promise<{ products: ProductSummary[]; nextCursor: string | null }> {
|
|
194
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
195
|
-
throw new Error("limit must be between 1 and 100.");
|
|
196
|
-
let query: RawProduct = { cursorPaging: { limit, ...(cursor ? { cursor } : {}) } };
|
|
197
|
-
if (!cursor) {
|
|
198
|
-
if (!(sort in CATALOG_SORTS)) throw new Error("Unsupported catalog sort.");
|
|
199
|
-
const min = priceBound(minPrice, "minPrice");
|
|
200
|
-
const max = priceBound(maxPrice, "maxPrice");
|
|
201
|
-
if (min !== undefined && max !== undefined && min > max)
|
|
202
|
-
throw new Error("minPrice must not exceed maxPrice.");
|
|
203
|
-
if (search.trim().length > 100) throw new Error("Search must be at most 100 characters.");
|
|
204
|
-
const conditions: RawProduct[] = [{ visible: true }];
|
|
205
|
-
if (categoryId)
|
|
206
|
-
conditions.push({ "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] } });
|
|
207
|
-
// Search rejects two operators in one field object — separate bounds, joined by $and.
|
|
208
|
-
if (min !== undefined)
|
|
209
|
-
conditions.push({ "actualPriceRange.minValue.amount": { $gte: String(min) } });
|
|
210
|
-
if (max !== undefined)
|
|
211
|
-
conditions.push({ "actualPriceRange.minValue.amount": { $lte: String(max) } });
|
|
212
|
-
if (inStockOnly) conditions.push({ "inventory.availabilityStatus": { $eq: "IN_STOCK" } });
|
|
213
|
-
query = {
|
|
214
|
-
...query,
|
|
215
|
-
filter: { $and: conditions },
|
|
216
|
-
...(SORT_FIELDS[sort] ? { sort: SORT_FIELDS[sort] } : {}),
|
|
217
|
-
...(search.trim() ? { search: { expression: search.trim(), fields: ["name"] } } : {}),
|
|
218
|
-
};
|
|
68
|
+
export async function fetchFacetData({ categoryId }: { categoryId?: string | null } = {}): Promise<FacetData> {
|
|
69
|
+
try {
|
|
70
|
+
const res: Raw = await products.searchProducts(
|
|
71
|
+
{ filter: catalogFilter({ categoryId }), cursorPaging: { limit: 100 } },
|
|
72
|
+
{ fields: ["CURRENCY"] as any },
|
|
73
|
+
);
|
|
74
|
+
return toFacetData((res.products ?? []) as Raw[]);
|
|
75
|
+
} catch {
|
|
76
|
+
return { facets: [], priceRange: null };
|
|
219
77
|
}
|
|
220
|
-
const res: RawProduct = await products.searchProducts(query, { fields: LIST_FIELDS as any });
|
|
221
|
-
return {
|
|
222
|
-
products: (res.products ?? []).map((p: RawProduct) => toSummary(p)),
|
|
223
|
-
nextCursor: res.pagingMetadata?.cursors?.next ?? null,
|
|
224
|
-
};
|
|
225
78
|
}
|
|
226
79
|
|
|
227
80
|
/** First page of visible products in the default order — a thin wrap of searchCatalog. */
|
|
@@ -230,10 +83,7 @@ export async function fetchProducts({ limit = 24 } = {}): Promise<ProductSummary
|
|
|
230
83
|
}
|
|
231
84
|
|
|
232
85
|
/** First page of a category — same wrap; category filtering only works through search. */
|
|
233
|
-
export async function fetchProductsByCategory(
|
|
234
|
-
categoryId: string,
|
|
235
|
-
{ limit = 24 } = {},
|
|
236
|
-
): Promise<ProductSummary[]> {
|
|
86
|
+
export async function fetchProductsByCategory(categoryId: string, { limit = 24 } = {}): Promise<ProductSummary[]> {
|
|
237
87
|
return (await searchCatalog({ limit, categoryId })).products;
|
|
238
88
|
}
|
|
239
89
|
|
|
@@ -241,7 +91,7 @@ export async function fetchProductsByCategory(
|
|
|
241
91
|
export async function fetchProductBySlug(slug: string): Promise<ProductDetail | null> {
|
|
242
92
|
try {
|
|
243
93
|
const res = await products.getProductBySlug(slug, { fields: DETAIL_FIELDS as any });
|
|
244
|
-
return res.product ? toDetail(res.product as
|
|
94
|
+
return res.product ? toDetail(res.product as Raw, imgSrc, mediaKey) : null;
|
|
245
95
|
} catch {
|
|
246
96
|
return null;
|
|
247
97
|
}
|
|
@@ -259,32 +109,25 @@ export async function fetchCategories(): Promise<Category[]> {
|
|
|
259
109
|
.queryCategories({ treeReference: { appNamespace: "@wix/stores" } })
|
|
260
110
|
.exists("name", true)
|
|
261
111
|
.find();
|
|
262
|
-
return (res.items ?? [])
|
|
263
|
-
.map((c) => ({
|
|
264
|
-
id: (c as RawProduct)._id ?? "",
|
|
265
|
-
slug: (c as RawProduct).slug ?? "",
|
|
266
|
-
name: (c as RawProduct).name ?? "",
|
|
267
|
-
}))
|
|
268
|
-
.filter((c) => c.slug !== "all-products");
|
|
112
|
+
return (res.items ?? []).map((c) => toCategory(c as Raw)).filter((c) => c.slug !== "all-products");
|
|
269
113
|
} catch {
|
|
270
114
|
return [];
|
|
271
115
|
}
|
|
272
116
|
}
|
|
273
117
|
|
|
274
118
|
/**
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
-
*
|
|
119
|
+
* One category by its URL slug — the data a /category/[slug] page needs (its id feeds
|
|
120
|
+
* searchCatalog / useShop, its name and description head the page). The response is WRAPPED:
|
|
121
|
+
* { category }. Null when not found or hidden, which the page turns into a real 404 — never a
|
|
122
|
+
* fallback to all products.
|
|
278
123
|
*/
|
|
279
|
-
export function
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
) ?? null
|
|
289
|
-
);
|
|
124
|
+
export async function fetchCategoryBySlug(slug: string): Promise<Category | null> {
|
|
125
|
+
try {
|
|
126
|
+
const res: Raw = await categories.getCategoryBySlug(slug, { appNamespace: "@wix/stores" } as any);
|
|
127
|
+
const raw: Raw | undefined = res?.category;
|
|
128
|
+
if (!raw || raw.visible === false) return null;
|
|
129
|
+
return toCategory(raw);
|
|
130
|
+
} catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
290
133
|
}
|
package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Product detail as a framework-free store — the logic behind useProductDetail, usable from React
|
|
2
|
+
// (useProductDetail wraps it), from a static page's PDP and quick-add picker, from Vue/Svelte, or as
|
|
3
|
+
// the specification for a port. Option selection → variant resolution → add-to-cart, plus modifier
|
|
4
|
+
// inputs. This is where the "adds variants[0] regardless of the buyer's choice" class of bugs comes
|
|
5
|
+
// from — always drive a purchase surface through this store, never resolve variants by hand.
|
|
6
|
+
//
|
|
7
|
+
// Selections start EMPTY (a buyer who never noticed a pre-picked default buys the wrong colour).
|
|
8
|
+
// Pass the server-fetched ProductDetail as `initial` (SSR) or a `slug` (the store fetches on start).
|
|
9
|
+
// One store per product surface: createProductDetailStore(), not a singleton.
|
|
10
|
+
import { fetchProductBySlug, resolveVariant } from "./catalog";
|
|
11
|
+
import { addLine } from "./cart-store";
|
|
12
|
+
import type { ProductDetail, ProductOption, ProductVariant } from "./types";
|
|
13
|
+
|
|
14
|
+
export interface ProductDetailStoreOptions {
|
|
15
|
+
initial?: ProductDetail | null;
|
|
16
|
+
slug?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OptionGroupView extends ProductOption {
|
|
20
|
+
choices: (ProductOption["choices"][number] & { selected: boolean })[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ProductDetailState {
|
|
24
|
+
/** null while loading (or when the slug doesn't resolve — check notFound). */
|
|
25
|
+
product: ProductDetail | null;
|
|
26
|
+
notFound: boolean;
|
|
27
|
+
/** Option groups with per-choice `selected`, ready to render as pills/swatches. */
|
|
28
|
+
optionGroups: OptionGroupView[];
|
|
29
|
+
/** Modifier inputs keyed by modifier key. */
|
|
30
|
+
modifierValues: Record<string, string>;
|
|
31
|
+
/** The resolved variant; null while the selection is incomplete. */
|
|
32
|
+
variant: ProductVariant | null;
|
|
33
|
+
/** The RANGE ("€24.99 – €34.99", one value when equal) until every option is picked, then the variant's price. */
|
|
34
|
+
price: string;
|
|
35
|
+
/** Struck "was" price — only once a variant is resolved (or for a single-price product), never beside a range. */
|
|
36
|
+
compareAtPrice: string | null;
|
|
37
|
+
/** The resolved variant is out of stock but pre-orderable — label the action "Pre-order". */
|
|
38
|
+
isPreorder: boolean;
|
|
39
|
+
/** False until every option is selected and the resolved variant is in stock or pre-orderable. */
|
|
40
|
+
canAdd: boolean;
|
|
41
|
+
/** Why the action is disabled — neutral guidance ("Choose Size"), not an error; null when addable. */
|
|
42
|
+
blockedReason: string | null;
|
|
43
|
+
quantity: number;
|
|
44
|
+
adding: boolean;
|
|
45
|
+
error: string | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface ProductDetailStore {
|
|
49
|
+
getState(): ProductDetailState;
|
|
50
|
+
subscribe(listener: () => void): () => void;
|
|
51
|
+
/** Fetch by slug when no `initial` was given. Call once when mounted. */
|
|
52
|
+
start(): void;
|
|
53
|
+
stop(): void;
|
|
54
|
+
selectOption(optionName: string, choiceName: string): void;
|
|
55
|
+
setModifier(key: string, value: string): void;
|
|
56
|
+
setQuantity(n: number): void;
|
|
57
|
+
/** Adds the resolved variant to the cart (the cart store opens the drawer). Throws on refusal. */
|
|
58
|
+
add(): Promise<void>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function createProductDetailStore({ initial, slug }: ProductDetailStoreOptions): ProductDetailStore {
|
|
62
|
+
let product: ProductDetail | null = initial ?? null;
|
|
63
|
+
let notFound = false;
|
|
64
|
+
let selections: Record<string, string> = {};
|
|
65
|
+
let modifierValues: Record<string, string> = {};
|
|
66
|
+
let quantity = 1;
|
|
67
|
+
let adding = false;
|
|
68
|
+
let error: string | null = null;
|
|
69
|
+
let started = false;
|
|
70
|
+
const listeners = new Set<() => void>();
|
|
71
|
+
let snapshot: ProductDetailState | null = null;
|
|
72
|
+
const emit = () => { snapshot = null; for (const fn of listeners) fn(); };
|
|
73
|
+
|
|
74
|
+
function getState(): ProductDetailState {
|
|
75
|
+
if (snapshot) return snapshot;
|
|
76
|
+
const variant = product ? resolveVariant(product, selections) : null;
|
|
77
|
+
const optionGroups: OptionGroupView[] = (product?.options ?? []).map((o) => ({
|
|
78
|
+
...o,
|
|
79
|
+
choices: o.choices.map((c) => ({ ...c, selected: selections[o.name] === c.name })),
|
|
80
|
+
}));
|
|
81
|
+
const missingModifier = (product?.modifiers ?? []).find((m) => m.mandatory && (modifierValues[m.key] ?? "").length === 0);
|
|
82
|
+
const missingOptions = (product?.options ?? []).filter((o) => !selections[o.name]).map((o) => o.name);
|
|
83
|
+
const selectionComplete = missingOptions.length === 0;
|
|
84
|
+
// In stock OR pre-orderable counts as buyable; a preorder add carries preOrderRequested.
|
|
85
|
+
const isPreorder = !!variant && !variant.inStock && variant.preorderEnabled;
|
|
86
|
+
const available = !!variant && (variant.inStock || variant.preorderEnabled);
|
|
87
|
+
const canAdd = !!product && available && !missingModifier;
|
|
88
|
+
const blockedReason: string | null = !product
|
|
89
|
+
? null
|
|
90
|
+
: !selectionComplete ? `Choose ${missingOptions.join(" and ")}`
|
|
91
|
+
: !variant ? "This combination isn't available"
|
|
92
|
+
: !available ? "Out of stock"
|
|
93
|
+
: missingModifier ? `Add ${missingModifier.name}`
|
|
94
|
+
: null;
|
|
95
|
+
// Before every option is picked, the range — never an empty price, never a lone struck minimum.
|
|
96
|
+
const isRange = !!product && product.price !== product.maxPrice && !!product.maxPrice;
|
|
97
|
+
const rangeDisplay = product ? (isRange ? `${product.price} – ${product.maxPrice}` : product.price) : "";
|
|
98
|
+
snapshot = {
|
|
99
|
+
product, notFound, optionGroups, modifierValues, variant,
|
|
100
|
+
price: variant?.price || rangeDisplay,
|
|
101
|
+
compareAtPrice: variant ? variant.compareAtPrice : isRange ? null : (product?.compareAtPrice ?? null),
|
|
102
|
+
isPreorder, canAdd, blockedReason, quantity, adding, error,
|
|
103
|
+
};
|
|
104
|
+
return snapshot;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
getState,
|
|
109
|
+
subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); },
|
|
110
|
+
start() {
|
|
111
|
+
if (started) return;
|
|
112
|
+
started = true;
|
|
113
|
+
if (initial || !slug) return;
|
|
114
|
+
fetchProductBySlug(slug)
|
|
115
|
+
.then((p) => { if (!started) return; product = p; notFound = p === null; emit(); })
|
|
116
|
+
.catch(() => { if (started) { notFound = true; emit(); } });
|
|
117
|
+
},
|
|
118
|
+
stop() { started = false; },
|
|
119
|
+
selectOption(optionName, choiceName) {
|
|
120
|
+
selections = { ...selections, [optionName]: choiceName };
|
|
121
|
+
quantity = 1; // the ceiling belongs to the newly resolved variant
|
|
122
|
+
emit();
|
|
123
|
+
},
|
|
124
|
+
setModifier(key, value) { modifierValues = { ...modifierValues, [key]: value }; emit(); },
|
|
125
|
+
setQuantity(n) { quantity = n; emit(); },
|
|
126
|
+
async add() {
|
|
127
|
+
const s = getState();
|
|
128
|
+
if (!product || !s.variant) return;
|
|
129
|
+
adding = true; error = null; emit();
|
|
130
|
+
try {
|
|
131
|
+
const choiceModifiers: Record<string, string> = {};
|
|
132
|
+
const textModifiers: Record<string, string> = {};
|
|
133
|
+
for (const m of product.modifiers) {
|
|
134
|
+
const value = modifierValues[m.key];
|
|
135
|
+
if (!value) continue;
|
|
136
|
+
if (m.type === "text") textModifiers[m.key] = value; else choiceModifiers[m.key] = value;
|
|
137
|
+
}
|
|
138
|
+
await addLine(product.id, s.variant.variantId, quantity, { modifierChoices: choiceModifiers, customTextFields: textModifiers, preorder: s.isPreorder });
|
|
139
|
+
} catch (e) {
|
|
140
|
+
error = e instanceof Error ? e.message : String(e);
|
|
141
|
+
throw e;
|
|
142
|
+
} finally {
|
|
143
|
+
adding = false; emit();
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|