@wix/agent-skills 1.20.1 → 1.22.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 +71 -113
- package/skills/wix-app/references/APP_TOOLS.md +2 -1
- package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
- package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
- package/skills/wix-app/references/DASHBOARD_PAGE.md +11 -25
- package/skills/wix-app/references/DATA_COLLECTION.md +16 -219
- package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
- package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
- package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
- package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +182 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +97 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_COLLECTION.md +184 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +203 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_SETTINGS.md +44 -0
- package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
- package/skills/wix-app/references/dashboard-page/QUERY_AND_PAGING.md +182 -0
- package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +133 -0
- package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +10 -5
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
- package/skills/wix-app/references/data-collection/LIFECYCLE.md +68 -0
- package/skills/wix-app/references/data-collection/PERMISSIONS.md +47 -0
- package/skills/wix-app/references/data-collection/SCHEMA.md +181 -0
- package/skills/wix-app/references/data-collection/WIX_DATA.md +38 -66
- package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +144 -221
- package/skills/wix-headless-fast/cold-start/bootstrap.mjs +156 -0
- package/skills/wix-headless-fast/{entry/skill.md → cold-start/cold-start.md} +15 -15
- package/skills/wix-headless-fast/guides/existing-site.md +76 -0
- package/skills/wix-headless-fast/guides/reference-mode.md +97 -0
- package/skills/wix-headless-fast/install/AGENTS.md +35 -0
- package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
- package/skills/wix-headless-fast/install/attach.mjs +161 -86
- package/skills/wix-headless-fast/install/deploy.mjs +21 -60
- package/skills/wix-headless-fast/install/lock.mjs +55 -0
- package/skills/wix-headless-fast/install/setup.mjs +259 -0
- package/skills/wix-headless-fast/install/templates.mjs +168 -0
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
- package/skills/wix-manage/references/events/create-wix-event.md +5 -1
- package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +27 -0
- package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +83 -37
- package/skills/wix-headless-fast/install/fast-path.mjs +0 -181
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +0 -301
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +0 -136
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +0 -112
- package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +0 -42
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +0 -31
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +0 -22
- package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +0 -137
- package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +0 -78
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +0 -117
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +0 -52
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +0 -47
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +0 -58
- package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +0 -62
- package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +0 -97
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +0 -24
- package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +0 -16156
- package/skills/wix-headless-fast/references/blog/rest/posts.ts +0 -42
- package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +0 -62
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +0 -81
- package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +0 -287
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +0 -275
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +0 -144
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +0 -119
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +0 -32
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +0 -23
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +0 -218
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +0 -169
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +0 -121
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +0 -92
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +0 -90
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +0 -57
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +0 -72
- package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +0 -76
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +0 -22
- package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +0 -11962
- package/skills/wix-headless-fast/references/bookings/rest/booking.ts +0 -113
- package/skills/wix-headless-fast/references/bookings/rest/services.ts +0 -55
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +0 -71
- package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +0 -302
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +0 -353
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +0 -50
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +0 -37
- package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +0 -158
- package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +0 -93
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +0 -177
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +0 -123
- package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +0 -75
- package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +0 -41
- package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +0 -11262
- package/skills/wix-headless-fast/references/cms/rest/items.ts +0 -125
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +0 -112
- package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +0 -324
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +0 -305
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +0 -66
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +0 -178
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +0 -118
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +0 -35
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +0 -24
- package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +0 -130
- package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +0 -97
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +0 -55
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +0 -113
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +0 -156
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +0 -97
- package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +0 -61
- package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +0 -12
- package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +0 -80
- package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +0 -21
- package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +0 -11884
- package/skills/wix-headless-fast/references/events/rest/events.ts +0 -57
- package/skills/wix-headless-fast/references/events/rest/registration.ts +0 -83
- package/skills/wix-headless-fast/references/events/seed/SEED.md +0 -95
- package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +0 -276
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +0 -338
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +0 -85
- package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +0 -332
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +0 -238
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +0 -54
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +0 -216
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +0 -59
- package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +0 -135
- package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +0 -10593
- package/skills/wix-headless-fast/references/forms/rest/forms.ts +0 -34
- package/skills/wix-headless-fast/references/forms/rest/submissions.ts +0 -56
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +0 -116
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +0 -327
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +0 -314
- package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +0 -60
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +0 -155
- package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +0 -78
- package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +0 -47
- package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +0 -66
- package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +0 -105
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +0 -59
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +0 -53
- package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +0 -122
- package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +0 -43
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +0 -29
- package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +0 -21
- package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +0 -42
- package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +0 -10
- package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +0 -11
- package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +0 -10329
- package/skills/wix-headless-fast/references/members/rest/auth.ts +0 -215
- package/skills/wix-headless-fast/references/members/rest/members.ts +0 -26
- package/skills/wix-headless-fast/references/members/seed/SEED.md +0 -39
- package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +0 -94
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +0 -313
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +0 -110
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +0 -90
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +0 -35
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +0 -85
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +0 -32
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +0 -21
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +0 -31
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +0 -82
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +0 -58
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +0 -177
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +0 -78
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +0 -81
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +0 -55
- package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +0 -30
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +0 -21
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +0 -29
- package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +0 -10197
- package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +0 -75
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +0 -86
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +0 -258
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +0 -294
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +0 -47
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +0 -101
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +0 -41
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +0 -16
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +0 -22
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +0 -54
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +0 -106
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +0 -64
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +0 -27
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +0 -43
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +0 -22
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +0 -30
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +0 -41
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +0 -40
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +0 -21
- package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +0 -11226
- package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +0 -31
- package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +0 -20
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +0 -79
- package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +0 -279
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +0 -358
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +0 -142
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +0 -26
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +0 -121
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +0 -155
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +0 -34
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +0 -43
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +0 -34
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +0 -155
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +0 -89
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +0 -71
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +0 -103
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +0 -124
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +0 -185
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +0 -193
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +0 -94
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +0 -74
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +0 -168
- package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +0 -47
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +0 -22
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +0 -12
- package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +0 -12560
- package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +0 -60
- package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +0 -169
- package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +0 -68
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +0 -89
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +0 -479
- package/skills/wix-headless-fast/references/shared/CONTENT.md +0 -22
- package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +0 -73
- package/skills/wix-headless-fast/references/shared/DESIGN.md +0 -26
- package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +0 -23
- package/skills/wix-headless-fast/references/shared/app/styles/global.css +0 -52
- package/skills/wix-headless-fast/references/shared/app/wix/config.ts +0 -15
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +0 -81
- package/skills/wix-headless-fast/references/shared/app/wix/money.ts +0 -24
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +0 -89
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +0 -46
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +0 -46
- package/skills/wix-headless-fast/references/shared/rest/client.ts +0 -155
- package/skills/wix-headless-fast/references/shared/rest/config.ts +0 -3
- package/skills/wix-headless-fast/references/shared/rest/media.ts +0 -65
- package/skills/wix-headless-fast/references/shared/seed/images.mjs +0 -145
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +0 -608
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +0 -26
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +0 -155
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +0 -266
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +0 -95
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +0 -136
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +0 -48
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +0 -39
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +0 -34
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +0 -121
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +0 -107
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +0 -138
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +0 -309
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +0 -133
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +0 -147
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +0 -289
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +0 -167
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +0 -65
- package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +0 -12247
- package/skills/wix-headless-fast/references/storefront/rest/cart.ts +0 -132
- package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +0 -123
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +0 -124
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +0 -525
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
// Catalog rules and DTO mapping — transport-agnostic, imported by BOTH transports: ./catalog.ts
|
|
2
|
-
// (the SDK, managed Astro and React) and the REST twin in references/storefront/rest/catalog.ts
|
|
3
|
-
// (fetch, a static site or a port to another language). Every rule about prices, ribbons,
|
|
4
|
-
// ranges, media, variants lives HERE, once. A raw entity may come from the SDK (`_id`) or from
|
|
5
|
-
// REST (`id`); the mappers accept both. Imports are type-only so a strip to JS emits no imports.
|
|
6
|
-
import type {
|
|
7
|
-
Availability,
|
|
8
|
-
Category,
|
|
9
|
-
Facet,
|
|
10
|
-
FacetData,
|
|
11
|
-
ProductDetail,
|
|
12
|
-
ProductModifier,
|
|
13
|
-
ProductOption,
|
|
14
|
-
ProductSummary,
|
|
15
|
-
ProductVariant,
|
|
16
|
-
} from "./types";
|
|
17
|
-
|
|
18
|
-
/** A raw Catalog V3 entity as either transport returns it. */
|
|
19
|
-
export type Raw = Record<string, any>;
|
|
20
|
-
/** Media value + size → https URL. Injected: the SDK transport scales through @wix/sdk, REST through the URL form. */
|
|
21
|
-
export type ImgSrc = (value: any, width: number, height: number) => string;
|
|
22
|
-
/** Media identity before scaling — de-duplicate on this, never on a resolved URL. */
|
|
23
|
-
export type MediaKey = (value: any) => string;
|
|
24
|
-
|
|
25
|
-
const id = (raw: Raw | undefined): string => raw?._id ?? raw?.id ?? "";
|
|
26
|
-
|
|
27
|
-
// Requested on every product read. CURRENCY → formattedAmount (without it prices render as bare
|
|
28
|
-
// numbers); MEDIA_ITEMS_INFO → the gallery; MIN_PRICE_VARIANT + DISCOUNT_INFO → the cheapest variant
|
|
29
|
-
// with its discounted price: a card's real price and the direct-add variant id, no per-product fetch.
|
|
30
|
-
export const LIST_FIELDS = ["CURRENCY", "MEDIA_ITEMS_INFO", "MIN_PRICE_VARIANT", "DISCOUNT_INFO"] as const;
|
|
31
|
-
// The detail read adds the HTML description, the variants (VARIANT_OPTION_CHOICE_NAMES — without it
|
|
32
|
-
// variantsInfo is null), and the info sections with HTML bodies.
|
|
33
|
-
export const DETAIL_FIELDS = [
|
|
34
|
-
...LIST_FIELDS,
|
|
35
|
-
"PLAIN_DESCRIPTION",
|
|
36
|
-
"VARIANT_OPTION_CHOICE_NAMES",
|
|
37
|
-
"INFO_SECTION",
|
|
38
|
-
"INFO_SECTION_PLAIN_DESCRIPTION",
|
|
39
|
-
] as const;
|
|
40
|
-
|
|
41
|
-
// ---- sort + filter ---------------------------------------------------------------------------------
|
|
42
|
-
|
|
43
|
-
export const CATALOG_SORTS = {
|
|
44
|
-
featured: { label: "Default order" }, // the catalog's own order — no sort sent, and NOT a sales ranking
|
|
45
|
-
priceAsc: { label: "Price: low to high" },
|
|
46
|
-
priceHigh: { label: "Price: high to low" },
|
|
47
|
-
name: { label: "Name: A–Z" },
|
|
48
|
-
newest: { label: "Newest" },
|
|
49
|
-
} as const;
|
|
50
|
-
export type CatalogSort = keyof typeof CATALOG_SORTS;
|
|
51
|
-
|
|
52
|
-
export const SORT_FIELDS: Partial<Record<CatalogSort, { fieldName: string; order: "ASC" | "DESC" }[]>> = {
|
|
53
|
-
priceAsc: [{ fieldName: "actualPriceRange.minValue.amount", order: "ASC" }, { fieldName: "name", order: "ASC" }],
|
|
54
|
-
priceHigh: [{ fieldName: "actualPriceRange.minValue.amount", order: "DESC" }, { fieldName: "name", order: "ASC" }],
|
|
55
|
-
name: [{ fieldName: "name", order: "ASC" }],
|
|
56
|
-
newest: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "name", order: "ASC" }],
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export interface CatalogSearchOptions {
|
|
60
|
-
/** 1–100, default 24. */
|
|
61
|
-
limit?: number;
|
|
62
|
-
/** Continues the ORIGINAL query — send it alone; any change of selection starts over without one. */
|
|
63
|
-
cursor?: string | null;
|
|
64
|
-
categoryId?: string | null;
|
|
65
|
-
sort?: CatalogSort;
|
|
66
|
-
/** Inclusive bounds on the product's minimum actual variant price, site currency. */
|
|
67
|
-
minPrice?: number | string;
|
|
68
|
-
maxPrice?: number | string;
|
|
69
|
-
/** IN_STOCK only — excludes partially stocked and preorder-only products. */
|
|
70
|
-
inStockOnly?: boolean;
|
|
71
|
-
/** Name search, max 100 chars. */
|
|
72
|
-
search?: string;
|
|
73
|
-
/** Option-choice facets: products carrying ANY of these choice ids (see fetchFacetData). */
|
|
74
|
-
choiceIds?: string[];
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function priceBound(value: number | string | undefined, name: string): number | undefined {
|
|
78
|
-
if (value == null || value === "") return undefined;
|
|
79
|
-
const n = Number(value);
|
|
80
|
-
if (!Number.isFinite(n) || n < 0) throw new Error(`${name} must be a non-negative number.`);
|
|
81
|
-
return n;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* The filter every catalog read shares (the page, its count, the facets) — one place, so they
|
|
86
|
-
* can't drift. Search rejects two operators in ONE field object: price bounds are separate
|
|
87
|
-
* conditions joined by $and. Category: `allCategoriesInfo.categories` with `$matchItems` on the
|
|
88
|
-
* id (includes parent categories); never `$hasSome`, never V1 `collectionIds`. Facets discover
|
|
89
|
-
* PRODUCTS carrying a choice; the picker / PDP still resolves the variant.
|
|
90
|
-
*/
|
|
91
|
-
export function catalogFilter({ categoryId, minPrice, maxPrice, inStockOnly = false, choiceIds = [] }: CatalogSearchOptions = {}): Raw {
|
|
92
|
-
const min = priceBound(minPrice, "minPrice"), max = priceBound(maxPrice, "maxPrice");
|
|
93
|
-
if (min !== undefined && max !== undefined && min > max) throw new Error("minPrice must not exceed maxPrice.");
|
|
94
|
-
const and: Raw[] = [{ visible: true }];
|
|
95
|
-
if (categoryId) and.push({ "allCategoriesInfo.categories": { $matchItems: [{ id: categoryId }] } });
|
|
96
|
-
if (min !== undefined) and.push({ "actualPriceRange.minValue.amount": { $gte: String(min) } });
|
|
97
|
-
if (max !== undefined) and.push({ "actualPriceRange.minValue.amount": { $lte: String(max) } });
|
|
98
|
-
if (inStockOnly) and.push({ "inventory.availabilityStatus": { $eq: "IN_STOCK" } });
|
|
99
|
-
if (choiceIds.length) and.push({ "options.choicesSettings.choices.choiceId": { $hasSome: choiceIds } });
|
|
100
|
-
return { $and: and };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/** The full Search Products body (minus fields) for a page; a cursor request carries ONLY cursorPaging. */
|
|
104
|
-
export function searchQuery(o: CatalogSearchOptions): Raw {
|
|
105
|
-
const { limit = 24, cursor, sort = "featured", search = "" } = o;
|
|
106
|
-
if (!Number.isInteger(limit) || limit < 1 || limit > 100) throw new Error("limit must be between 1 and 100.");
|
|
107
|
-
const paging = { cursorPaging: { limit, ...(cursor ? { cursor } : {}) } };
|
|
108
|
-
if (cursor) return paging;
|
|
109
|
-
if (!(sort in CATALOG_SORTS)) throw new Error("Unsupported catalog sort.");
|
|
110
|
-
if (search.trim().length > 100) throw new Error("Search must be at most 100 characters.");
|
|
111
|
-
return {
|
|
112
|
-
...paging,
|
|
113
|
-
filter: catalogFilter(o),
|
|
114
|
-
...(SORT_FIELDS[sort] ? { sort: SORT_FIELDS[sort] } : {}),
|
|
115
|
-
...(search.trim() ? { search: { expression: search.trim(), fields: ["name"] } } : {}),
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// ---- price rules ----------------------------------------------------------------------------------
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* The price the buyer pays and the price to strike — exact precedence, shared by cards and
|
|
123
|
-
* variants. An automatic discount (priceAfterDiscount) wins and strikes the regular actualPrice;
|
|
124
|
-
* else actualPrice with the merchant's compareAtPrice as the "was". `!== undefined` on purpose:
|
|
125
|
-
* a discounted price of 0 is a real price.
|
|
126
|
-
*/
|
|
127
|
-
export function sellingPrice(price: Raw | undefined): { current?: Raw; original?: Raw } {
|
|
128
|
-
if (price?.priceAfterDiscount !== undefined) return { current: price.priceAfterDiscount, original: price.actualPrice };
|
|
129
|
-
return { current: price?.actualPrice, original: price?.compareAtPrice };
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/** The struck price only when it is real and higher than what the buyer pays. */
|
|
133
|
-
export function strike(original: Raw | undefined, current: Raw | undefined): string | null {
|
|
134
|
-
const o = Number(original?.amount), c = Number(current?.amount);
|
|
135
|
-
return original?.formattedAmount && Number.isFinite(o) && Number.isFinite(c) && o > c ? original.formattedAmount : null;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/** Every merchant ribbon, primary first, de-duplicated. A ribbon is a label, never proof of a price. */
|
|
139
|
-
export function ribbonsOf(raw: Raw): string[] {
|
|
140
|
-
return [raw.ribbon?.name, ...((raw.additionalRibbons ?? []) as Raw[]).map((r) => r?.name)]
|
|
141
|
-
.filter((n, i, all): n is string => typeof n === "string" && n.length > 0 && all.indexOf(n) === i);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
export function toAvailability(raw: Raw): Availability {
|
|
145
|
-
const s = raw.inventory?.availabilityStatus;
|
|
146
|
-
return s === "OUT_OF_STOCK" || s === "PARTIALLY_OUT_OF_STOCK" ? s : "IN_STOCK";
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Every distinct media entry, main first, keyed on identity — two scaled URLs of one photo are
|
|
151
|
-
* ONE entry (the "two thumbnails for the same image" bug). Shared by the tile and the PDP.
|
|
152
|
-
*/
|
|
153
|
-
export function mediaEntries(raw: Raw, mediaKey: MediaKey): unknown[] {
|
|
154
|
-
const out: unknown[] = [];
|
|
155
|
-
const seen = new Set<string>();
|
|
156
|
-
for (const m of [raw.media?.main, ...((raw.media?.itemsInfo?.items ?? []) as Raw[])]) {
|
|
157
|
-
const v = m?.image ?? m;
|
|
158
|
-
const k = mediaKey(v);
|
|
159
|
-
if (!k || seen.has(k)) continue;
|
|
160
|
-
seen.add(k);
|
|
161
|
-
out.push(v);
|
|
162
|
-
}
|
|
163
|
-
return out;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// ---- DTO mappers -----------------------------------------------------------------------------------
|
|
167
|
-
|
|
168
|
-
export function toSummary(raw: Raw, imgSrc: ImgSrc, mediaKey: MediaKey): ProductSummary {
|
|
169
|
-
const options: Raw[] = raw.options ?? [];
|
|
170
|
-
const media = mediaEntries(raw, mediaKey);
|
|
171
|
-
const mainUrl = imgSrc(media[0], 800, 800);
|
|
172
|
-
const hover = media.slice(1).map((m) => imgSrc(m, 800, 800)).filter(Boolean);
|
|
173
|
-
const availability = toAvailability(raw);
|
|
174
|
-
// Price: the cheapest variant's selling price (discount applied); the product RANGE when variants
|
|
175
|
-
// are priced differently — and NO struck price beside a range (a lone struck minimum claims a
|
|
176
|
-
// saving that may not apply to the variant picked).
|
|
177
|
-
const min = raw.actualPriceRange?.minValue, max = raw.actualPriceRange?.maxValue;
|
|
178
|
-
const isRange = !!(min?.amount && max?.amount && min.amount !== max.amount);
|
|
179
|
-
const minVariant: Raw | undefined = raw.variantSummary?.minPriceVariant;
|
|
180
|
-
const { current, original } = sellingPrice(minVariant?.price);
|
|
181
|
-
const ribbons = ribbonsOf(raw);
|
|
182
|
-
return {
|
|
183
|
-
id: id(raw),
|
|
184
|
-
slug: raw.slug ?? "",
|
|
185
|
-
name: raw.name ?? "",
|
|
186
|
-
price: current?.formattedAmount ?? min?.formattedAmount ?? "",
|
|
187
|
-
maxPrice: max?.formattedAmount ?? "",
|
|
188
|
-
compareAtPrice: isRange ? null : strike(original ?? raw.compareAtPriceRange?.minValue, current ?? min),
|
|
189
|
-
ribbon: ribbons[0] ?? null,
|
|
190
|
-
ribbons,
|
|
191
|
-
minPriceVariantId: minVariant ? id(minVariant) || minVariant.variantId || null : null,
|
|
192
|
-
availability,
|
|
193
|
-
// Out of stock but pre-orderable — label "Pre-order", not "Sold out".
|
|
194
|
-
preorder: raw.inventory?.preorderStatus === "ENABLED" && availability === "OUT_OF_STOCK",
|
|
195
|
-
imageUrl: mainUrl,
|
|
196
|
-
hoverImageUrl: hover[0] ?? "",
|
|
197
|
-
optionsSummary: options
|
|
198
|
-
.map((o) => {
|
|
199
|
-
const n = ((o.choicesSettings?.choices ?? []) as Raw[]).filter((c) => c.visible !== false).length;
|
|
200
|
-
return `${n} ${String(o.name ?? "").toLowerCase()}${n === 1 ? "" : "s"}`;
|
|
201
|
-
})
|
|
202
|
-
.join(" · "),
|
|
203
|
-
swatches: options
|
|
204
|
-
.filter((o) => o.optionRenderType === "SWATCH_CHOICES" || o.optionRenderType === "COLOR_CHOICES")
|
|
205
|
-
.flatMap((o) => (o.choicesSettings?.choices ?? []) as Raw[])
|
|
206
|
-
.filter((c) => c.visible !== false && c.colorCode)
|
|
207
|
-
.map((c) => String(c.colorCode)),
|
|
208
|
-
// Addable in one click: no options and plainly in stock.
|
|
209
|
-
quickAddable: options.length === 0 && availability === "IN_STOCK",
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export function toOptions(raw: Raw): ProductOption[] {
|
|
214
|
-
return ((raw.options ?? []) as Raw[]).map((o) => ({
|
|
215
|
-
id: id(o) || o.name || "",
|
|
216
|
-
name: o.name ?? "",
|
|
217
|
-
isColor: o.optionRenderType === "SWATCH_CHOICES" || o.optionRenderType === "COLOR_CHOICES",
|
|
218
|
-
choices: ((o.choicesSettings?.choices ?? []) as Raw[])
|
|
219
|
-
.filter((c) => c.visible !== false) // a retired choice the merchant no longer sells
|
|
220
|
-
.map((c) => ({ choiceId: c.choiceId ?? "", name: c.name ?? "", colorCode: c.colorCode ?? null, inStock: c.inStock !== false })),
|
|
221
|
-
}));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
export function toModifiers(raw: Raw): ProductModifier[] {
|
|
225
|
-
return ((raw.modifiers ?? []) as Raw[]).map((m) => ({
|
|
226
|
-
key: m.key ?? m.freeTextSettings?.key ?? m.name ?? "",
|
|
227
|
-
name: m.name ?? "",
|
|
228
|
-
mandatory: m.mandatory === true,
|
|
229
|
-
type: m.modifierRenderType === "FREE_TEXT" ? ("text" as const) : ("choices" as const),
|
|
230
|
-
choices: ((m.choicesSettings?.choices ?? []) as Raw[]).map((c) => ({ key: c.key ?? c.name ?? "", name: c.name ?? "" })),
|
|
231
|
-
}));
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** Variants keyed by optionName → choiceName. Buyable when in stock OR pre-orderable. */
|
|
235
|
-
export function toVariants(raw: Raw): ProductVariant[] {
|
|
236
|
-
return ((raw.variantsInfo?.variants ?? []) as Raw[])
|
|
237
|
-
.filter((v) => v.visible !== false)
|
|
238
|
-
.map((v) => {
|
|
239
|
-
const choices: Record<string, string> = {};
|
|
240
|
-
for (const c of (v.choices ?? []) as Raw[]) {
|
|
241
|
-
const on = c.optionChoiceNames?.optionName, cn = c.optionChoiceNames?.choiceName;
|
|
242
|
-
if (on && cn) choices[on] = cn;
|
|
243
|
-
}
|
|
244
|
-
const { current, original } = sellingPrice(v.price);
|
|
245
|
-
return {
|
|
246
|
-
variantId: id(v),
|
|
247
|
-
choices,
|
|
248
|
-
price: current?.formattedAmount ?? "",
|
|
249
|
-
compareAtPrice: strike(original, current),
|
|
250
|
-
inStock: v.inventoryStatus?.inStock !== false,
|
|
251
|
-
preorderEnabled: v.inventoryStatus?.preorderEnabled === true,
|
|
252
|
-
};
|
|
253
|
-
});
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function toDetail(raw: Raw, imgSrc: ImgSrc, mediaKey: MediaKey): ProductDetail {
|
|
257
|
-
return {
|
|
258
|
-
...toSummary(raw, imgSrc, mediaKey),
|
|
259
|
-
descriptionHtml: raw.plainDescription ?? "", // an HTML string despite the name — render as HTML
|
|
260
|
-
infoSections: ((raw.infoSections ?? []) as Raw[])
|
|
261
|
-
.map((s) => ({ title: s.title ?? "", html: s.plainDescription ?? "" }))
|
|
262
|
-
.filter((s) => s.title || s.html),
|
|
263
|
-
// De-duplicated on media identity, then resolved once at one size.
|
|
264
|
-
gallery: mediaEntries(raw, mediaKey).map((m) => imgSrc(m, 1200, 1200)).filter(Boolean),
|
|
265
|
-
options: toOptions(raw),
|
|
266
|
-
modifiers: toModifiers(raw),
|
|
267
|
-
variants: toVariants(raw),
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
export function toCategory(raw: Raw): Category {
|
|
272
|
-
return { id: id(raw), slug: raw.slug ?? "", name: raw.name ?? "", description: raw.description ?? "" };
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
/** Facets + price bounds from a scope's products (the 100-product read), so the panel only offers filters that exist. */
|
|
276
|
-
export function toFacetData(products: Raw[]): FacetData {
|
|
277
|
-
const byName = new Map<string, Facet>();
|
|
278
|
-
let lo = Infinity, hi = -Infinity, currency = "";
|
|
279
|
-
for (const p of products) {
|
|
280
|
-
const min = Number(p.actualPriceRange?.minValue?.amount), max = Number(p.actualPriceRange?.maxValue?.amount);
|
|
281
|
-
if (Number.isFinite(min)) lo = Math.min(lo, min);
|
|
282
|
-
if (Number.isFinite(max)) hi = Math.max(hi, max);
|
|
283
|
-
currency = currency || p.currency || "";
|
|
284
|
-
for (const o of (p.options ?? []) as Raw[]) {
|
|
285
|
-
if (!o.name) continue;
|
|
286
|
-
const facet: Facet = byName.get(o.name) ?? { name: o.name, isColor: o.optionRenderType === "SWATCH_CHOICES" || o.optionRenderType === "COLOR_CHOICES", choices: [] };
|
|
287
|
-
for (const c of (o.choicesSettings?.choices ?? []) as Raw[]) {
|
|
288
|
-
if (c.visible === false || !c.choiceId) continue;
|
|
289
|
-
if (!facet.choices.some((x) => x.id === c.choiceId)) facet.choices.push({ id: c.choiceId, name: c.name ?? "", colorCode: c.colorCode ?? null });
|
|
290
|
-
}
|
|
291
|
-
byName.set(o.name, facet);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return {
|
|
295
|
-
facets: [...byName.values()].filter((f) => f.choices.length > 1),
|
|
296
|
-
priceRange: Number.isFinite(lo) && Number.isFinite(hi) && hi > lo ? { min: Math.floor(lo), max: Math.ceil(hi), currency } : null,
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/**
|
|
301
|
-
* Resolve the variant for the buyer's selections (optionName → choiceName). A product with no
|
|
302
|
-
* options resolves to its single variant; with options every option must be selected and match.
|
|
303
|
-
* null while incomplete. Selections start EMPTY — never pre-pick a first choice.
|
|
304
|
-
*/
|
|
305
|
-
export function resolveVariant(detail: ProductDetail, selections: Record<string, string>): ProductVariant | null {
|
|
306
|
-
if (detail.options.length === 0) return detail.variants[0] ?? null;
|
|
307
|
-
if (!detail.options.every((o) => selections[o.name])) return null;
|
|
308
|
-
return detail.variants.find((v) => detail.options.every((o) => v.choices[o.name] === selections[o.name])) ?? null;
|
|
309
|
-
}
|
|
@@ -1,133 +0,0 @@
|
|
|
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.
|
|
5
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/search-products.md
|
|
6
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/products-v3/get-product-by-slug.md
|
|
7
|
-
// docs: https://dev.wix.com/docs/api-reference/business-solutions/stores/catalog-v3/categories/query-categories.md
|
|
8
|
-
import { productsV3 } from "@wix/stores";
|
|
9
|
-
import { categories as categoriesModule } from "@wix/categories";
|
|
10
|
-
import { wixModule } from "../sdk";
|
|
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 };
|
|
31
|
-
|
|
32
|
-
const products = wixModule(productsV3);
|
|
33
|
-
const categories = wixModule(categoriesModule);
|
|
34
|
-
|
|
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 */
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
products: ((res.products ?? []) as Raw[]).map((p) => toSummary(p, imgSrc, mediaKey)),
|
|
54
|
-
nextCursor: res.pagingMetadata?.cursors?.next ?? null,
|
|
55
|
-
total,
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
|
|
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;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
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.
|
|
67
|
-
*/
|
|
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 };
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** First page of visible products in the default order — a thin wrap of searchCatalog. */
|
|
81
|
-
export async function fetchProducts({ limit = 24 } = {}): Promise<ProductSummary[]> {
|
|
82
|
-
return (await searchCatalog({ limit })).products;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** First page of a category — same wrap; category filtering only works through search. */
|
|
86
|
-
export async function fetchProductsByCategory(categoryId: string, { limit = 24 } = {}): Promise<ProductSummary[]> {
|
|
87
|
-
return (await searchCatalog({ limit, categoryId })).products;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/** Fetch one product by its URL slug, with options/modifiers/variants. Null when not found. */
|
|
91
|
-
export async function fetchProductBySlug(slug: string): Promise<ProductDetail | null> {
|
|
92
|
-
try {
|
|
93
|
-
const res = await products.getProductBySlug(slug, { fields: DETAIL_FIELDS as any });
|
|
94
|
-
return res.product ? toDetail(res.product as Raw, imgSrc, mediaKey) : null;
|
|
95
|
-
} catch {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* List the store's categories for nav/filter UI, minus Wix's auto-created "all-products"
|
|
102
|
-
* system category. The query must carry a filter condition (`.exists("name", true)` — a
|
|
103
|
-
* tautology): a bare .find() serializes an empty filter that the API rejects on the
|
|
104
|
-
* visitor-client path. Treat a failure as "no category nav", not a fatal error.
|
|
105
|
-
*/
|
|
106
|
-
export async function fetchCategories(): Promise<Category[]> {
|
|
107
|
-
try {
|
|
108
|
-
const res = await categories
|
|
109
|
-
.queryCategories({ treeReference: { appNamespace: "@wix/stores" } })
|
|
110
|
-
.exists("name", true)
|
|
111
|
-
.find();
|
|
112
|
-
return (res.items ?? []).map((c) => toCategory(c as Raw)).filter((c) => c.slug !== "all-products");
|
|
113
|
-
} catch {
|
|
114
|
-
return [];
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
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.
|
|
123
|
-
*/
|
|
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
|
-
}
|
|
133
|
-
}
|
package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
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
|
-
}
|