@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,6 +1,8 @@
|
|
|
1
|
-
// Availability, the booking form, and the createBooking → Cart V2 → checkout-or-place
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// Availability, the booking form, and the createBooking → Cart V2 → checkout-or-place sequence
|
|
2
|
+
// over the SDK. The payload shapes are exact and easy to get subtly wrong — they are built in
|
|
3
|
+
// ./booking-core (shared with the REST twin in references/bookings/rest/); this file is the
|
|
4
|
+
// transport only. Copy as-is; extend by calling these exports, never by editing them. Failures
|
|
5
|
+
// are loud.
|
|
4
6
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/bookings/time-slots/time-slots-v2/list-availability-time-slots.md
|
|
5
7
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/bookings/time-slots/time-slots-v2/list-event-time-slots.md
|
|
6
8
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/bookings/bookings/bookings-writer-v2/create-booking.md
|
|
@@ -9,106 +11,51 @@
|
|
|
9
11
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/place-order.md
|
|
10
12
|
// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
|
|
11
13
|
// docs: https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/get-form-summary.md
|
|
12
|
-
import {
|
|
13
|
-
availabilityTimeSlots,
|
|
14
|
-
eventTimeSlots,
|
|
15
|
-
bookings as bookingsModule,
|
|
16
|
-
} from "@wix/bookings";
|
|
14
|
+
import { availabilityTimeSlots, eventTimeSlots, bookings as bookingsModule } from "@wix/bookings";
|
|
17
15
|
import { createCart, calculateCart, placeOrder } from "@wix/auto_sdk_ecom_cart-v-2";
|
|
18
16
|
import { redirects as redirectsModule } from "@wix/redirects";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
|
|
17
|
+
import { wixFetch, wixModule } from "../sdk";
|
|
18
|
+
import {
|
|
19
|
+
FALLBACK_FIELDS,
|
|
20
|
+
appointmentSlotsRequest,
|
|
21
|
+
bookingCartRequest,
|
|
22
|
+
bookingIdOf,
|
|
23
|
+
bookingRequest,
|
|
24
|
+
cartIdOf,
|
|
25
|
+
cartTotal,
|
|
26
|
+
checkoutRedirectRequest,
|
|
27
|
+
checkoutRequired,
|
|
28
|
+
classSlotsRequest,
|
|
29
|
+
confirmedResult,
|
|
30
|
+
defaultTimeZone,
|
|
31
|
+
toFormFields,
|
|
32
|
+
toLocalDateString,
|
|
33
|
+
toSlot,
|
|
34
|
+
type Raw,
|
|
35
|
+
type SlotsWindow,
|
|
36
|
+
} from "./booking-core";
|
|
22
37
|
import type { BookingFormField, BookingResult, ServiceDetail, Slot } from "./types";
|
|
23
38
|
|
|
39
|
+
export { toLocalDateString };
|
|
40
|
+
|
|
24
41
|
const apptSlots = wixModule(availabilityTimeSlots);
|
|
25
42
|
const classSlots = wixModule(eventTimeSlots);
|
|
26
43
|
const bookings = wixModule(bookingsModule);
|
|
27
44
|
const cart = wixModule({ createCart, calculateCart, placeOrder });
|
|
28
45
|
const redirects = wixModule(redirectsModule);
|
|
29
|
-
const forms = wixModule(formsModule);
|
|
30
|
-
|
|
31
|
-
type Raw = Record<string, any>;
|
|
32
|
-
|
|
33
|
-
const pad = (n: number) => String(n).padStart(2, "0");
|
|
34
|
-
/** Local wall-clock "YYYY-MM-DDThh:mm:ss" (NO Z) — the format the availability APIs require. */
|
|
35
|
-
export function toLocalDateString(d: Date): string {
|
|
36
|
-
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function toSlot(raw: Raw): Slot {
|
|
40
|
-
const start: string = raw.localStartDate ?? "";
|
|
41
|
-
const staff = (raw.availableResources ?? [])
|
|
42
|
-
.flatMap((ar: Raw) => ar.resources ?? [])
|
|
43
|
-
.map((r: Raw) => ({ id: r._id ?? "", name: r.name ?? "" }))
|
|
44
|
-
.filter((r: { id: string }) => r.id);
|
|
45
|
-
let label = start.slice(11, 16);
|
|
46
|
-
try {
|
|
47
|
-
label = new Date(start).toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
|
|
48
|
-
} catch {
|
|
49
|
-
/* keep the hh:mm fallback */
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
startLocal: start,
|
|
53
|
-
endLocal: raw.localEndDate ?? "",
|
|
54
|
-
dayKey: start.slice(0, 10),
|
|
55
|
-
label,
|
|
56
|
-
scheduleId: raw.scheduleId ?? null,
|
|
57
|
-
eventId: raw.eventInfo?.eventId ?? null,
|
|
58
|
-
staff,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
46
|
|
|
62
47
|
/**
|
|
63
|
-
* Bookable slots for a service in [from,
|
|
48
|
+
* Bookable slots for a service in [from, from + days) — APPOINTMENT and CLASS use different APIs;
|
|
64
49
|
* this branches for you. Dates are local wall-clock; timeZone defaults to the visitor's.
|
|
65
50
|
*/
|
|
66
|
-
export async function fetchSlots(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
staffId,
|
|
73
|
-
}: { from?: Date; days?: number; timeZone?: string; staffId?: string } = {},
|
|
74
|
-
): Promise<Slot[]> {
|
|
75
|
-
const to = new Date(from.getTime() + days * 24 * 3600 * 1000);
|
|
76
|
-
const fromLocalDate = toLocalDateString(from);
|
|
77
|
-
const toLocalDate = toLocalDateString(to);
|
|
78
|
-
|
|
79
|
-
if (service.type === "CLASS") {
|
|
80
|
-
const res: Raw = await classSlots.listEventTimeSlots({
|
|
81
|
-
serviceIds: [service.id],
|
|
82
|
-
fromLocalDate,
|
|
83
|
-
toLocalDate,
|
|
84
|
-
timeZone,
|
|
85
|
-
includeNonBookable: false,
|
|
86
|
-
...(staffId ? { eventFilter: { "resources.id": { $hasSome: [staffId] } } } : {}),
|
|
87
|
-
} as any);
|
|
88
|
-
return (res.timeSlots ?? []).map((t: Raw) => toSlot(t));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const res: Raw = await apptSlots.listAvailabilityTimeSlots({
|
|
92
|
-
serviceId: service.id,
|
|
93
|
-
fromLocalDate,
|
|
94
|
-
toLocalDate,
|
|
95
|
-
timeZone,
|
|
96
|
-
bookable: true,
|
|
97
|
-
cursorPaging: { limit: 100 },
|
|
98
|
-
includeResourceTypeIds: [STAFF_RESOURCE_TYPE_ID],
|
|
99
|
-
...(staffId
|
|
100
|
-
? { resourceTypes: [{ resourceTypeId: STAFF_RESOURCE_TYPE_ID, resourceIds: [staffId] }] }
|
|
101
|
-
: {}),
|
|
102
|
-
} as any);
|
|
103
|
-
return (res.timeSlots ?? []).map((t: Raw) => toSlot(t));
|
|
51
|
+
export async function fetchSlots(service: Pick<ServiceDetail, "id" | "type">, window: SlotsWindow = {}): Promise<Slot[]> {
|
|
52
|
+
const res: Raw =
|
|
53
|
+
service.type === "CLASS"
|
|
54
|
+
? await classSlots.listEventTimeSlots(classSlotsRequest(service.id, window) as any)
|
|
55
|
+
: await apptSlots.listAvailabilityTimeSlots(appointmentSlotsRequest(service.id, window) as any);
|
|
56
|
+
return ((res.timeSlots ?? []) as Raw[]).map(toSlot);
|
|
104
57
|
}
|
|
105
58
|
|
|
106
|
-
const FALLBACK_FIELDS: BookingFormField[] = [
|
|
107
|
-
{ target: "first_name", label: "First Name", type: "STRING" },
|
|
108
|
-
{ target: "last_name", label: "Last Name", type: "STRING" },
|
|
109
|
-
{ target: "email", label: "Email", type: "EMAIL" },
|
|
110
|
-
];
|
|
111
|
-
|
|
112
59
|
/**
|
|
113
60
|
* The service's booking-form fields, flat and render-ready (values are keyed by `target`).
|
|
114
61
|
* ALWAYS returns a non-empty list — contact basics when the schema is missing/unusable —
|
|
@@ -117,18 +64,20 @@ const FALLBACK_FIELDS: BookingFormField[] = [
|
|
|
117
64
|
export async function fetchBookingForm(formId: string | null): Promise<BookingFormField[]> {
|
|
118
65
|
if (!formId) return FALLBACK_FIELDS;
|
|
119
66
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
})
|
|
130
|
-
.
|
|
131
|
-
|
|
67
|
+
// Over wixFetch, not the @wix/forms `forms` module: that generated module is 15 MB and would
|
|
68
|
+
// ride into the booking island's client chunk (11 MB per visitor). The summary has labels and
|
|
69
|
+
// types; only the full schema says which fields are required.
|
|
70
|
+
const id = encodeURIComponent(formId);
|
|
71
|
+
const json = async (path: string): Promise<Raw | null> => {
|
|
72
|
+
const r = await wixFetch(path);
|
|
73
|
+
return r.ok ? ((await r.json()) as Raw) : null;
|
|
74
|
+
};
|
|
75
|
+
const [res, form] = await Promise.all([
|
|
76
|
+
json(`/form-schema-service/v4/forms/${id}/summary`),
|
|
77
|
+
json(`/form-schema-service/v4/forms/${id}`).catch(() => null),
|
|
78
|
+
]);
|
|
79
|
+
if (!res) return FALLBACK_FIELDS;
|
|
80
|
+
return toFormFields(res.formSummary, form?.form);
|
|
132
81
|
} catch {
|
|
133
82
|
return FALLBACK_FIELDS;
|
|
134
83
|
}
|
|
@@ -145,62 +94,28 @@ export async function bookService(
|
|
|
145
94
|
service: ServiceDetail,
|
|
146
95
|
slot: Slot,
|
|
147
96
|
formValues: Record<string, unknown>,
|
|
148
|
-
{ staffId, timeZone =
|
|
97
|
+
{ staffId, timeZone = defaultTimeZone() }: { staffId?: string; timeZone?: string } = {},
|
|
149
98
|
): Promise<BookingResult> {
|
|
150
|
-
const staff = staffId ? slot.staff.find((s) => s.id === staffId) : undefined;
|
|
151
99
|
const created: Raw = await bookings.createBooking(
|
|
152
|
-
{
|
|
153
|
-
selectedPaymentOption: service.paymentOption,
|
|
154
|
-
totalParticipants: 1,
|
|
155
|
-
bookedEntity: {
|
|
156
|
-
slot: {
|
|
157
|
-
serviceId: service.id,
|
|
158
|
-
scheduleId: slot.scheduleId ?? undefined, // APPOINTMENT: the SLOT's scheduleId (never service.schedule.id)
|
|
159
|
-
eventId: slot.eventId ?? undefined, // CLASS
|
|
160
|
-
startDate: slot.startLocal,
|
|
161
|
-
endDate: slot.endLocal,
|
|
162
|
-
timezone: timeZone,
|
|
163
|
-
...(staff
|
|
164
|
-
? { resource: { _id: staff.id, name: staff.name } }
|
|
165
|
-
: {
|
|
166
|
-
resourceSelections: [
|
|
167
|
-
{ resourceTypeId: STAFF_RESOURCE_TYPE_ID, selectionMethod: "ANY_RESOURCE" },
|
|
168
|
-
],
|
|
169
|
-
}),
|
|
170
|
-
location: { locationType: "OWNER_BUSINESS" },
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
} as any,
|
|
100
|
+
bookingRequest(service, slot, { staffId, timeZone, idKey: "_id" }) as any,
|
|
174
101
|
{ formSubmission: formValues } as any,
|
|
175
102
|
);
|
|
176
|
-
const bookingId
|
|
103
|
+
const bookingId = bookingIdOf(created);
|
|
177
104
|
if (!bookingId) throw new Error("The booking couldn't be created — the slot may have just been taken.");
|
|
178
105
|
|
|
179
|
-
const newCart: Raw = await cart.createCart(
|
|
180
|
-
|
|
181
|
-
{ quantity: 1, catalogReference: { catalogItemId: bookingId, appId: BOOKINGS_APP_ID } },
|
|
182
|
-
],
|
|
183
|
-
cart: { source: { channelType: "WEB" } },
|
|
184
|
-
} as any);
|
|
185
|
-
const cartId: string | undefined = newCart._id ?? newCart.cart?._id;
|
|
106
|
+
const newCart: Raw = await cart.createCart(bookingCartRequest(bookingId) as any);
|
|
107
|
+
const cartId = cartIdOf(newCart);
|
|
186
108
|
if (!cartId) throw new Error("The booking couldn't be reserved — please try again.");
|
|
187
109
|
|
|
188
110
|
const calc: Raw = await cart.calculateCart(cartId);
|
|
189
|
-
|
|
190
|
-
const checkoutRequired =
|
|
191
|
-
service.cancellationFeeEnabled || (total > 0 && service.paymentOption !== "OFFLINE");
|
|
192
|
-
|
|
193
|
-
if (checkoutRequired) {
|
|
111
|
+
if (checkoutRequired(service, cartTotal(calc))) {
|
|
194
112
|
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
195
|
-
const session: Raw = await redirects.createRedirectSession(
|
|
196
|
-
ecomCheckout: { checkoutId: cartId },
|
|
197
|
-
callbacks: { postFlowUrl: origin ? `${origin}/` : undefined },
|
|
198
|
-
});
|
|
113
|
+
const session: Raw = await redirects.createRedirectSession(checkoutRedirectRequest(cartId, origin));
|
|
199
114
|
const url = session?.redirectSession?.fullUrl;
|
|
200
115
|
if (!url) throw new Error("Checkout couldn't start — please try again.");
|
|
201
116
|
return { kind: "redirect", url };
|
|
202
117
|
}
|
|
203
118
|
|
|
204
119
|
const order: Raw = await cart.placeOrder(cartId);
|
|
205
|
-
return
|
|
120
|
+
return confirmedResult(bookingId, order);
|
|
206
121
|
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Service rules and DTO mapping — transport-agnostic, imported by BOTH transports: ./services.ts
|
|
2
|
+
// (the SDK, managed Astro and React) and the REST twin in references/bookings/rest/services.ts
|
|
3
|
+
// (fetch, a static site or a port to another language). Every rule about price, "Free", duration,
|
|
4
|
+
// media, staff, and the payment option lives HERE, once. A raw entity may come from the SDK (`_id`,
|
|
5
|
+
// media as a `wix:image://` string) or from REST (`id`, media as an { id, url, width, height }
|
|
6
|
+
// object); the mappers accept both. Imports are type-only so a strip to JS emits no imports.
|
|
7
|
+
import type { BookingCategory, ServiceDetail, ServiceSummary, ServiceType } from "./types";
|
|
8
|
+
|
|
9
|
+
/** A raw Services V2 entity as either transport returns it. */
|
|
10
|
+
export type Raw = Record<string, any>;
|
|
11
|
+
/** Media value + size → https URL. Injected: the SDK transport scales through @wix/sdk, REST through the URL form. */
|
|
12
|
+
export type ImgSrc = (value: any, width: number, height: number) => string;
|
|
13
|
+
|
|
14
|
+
/** The Wix Bookings app id — the cart's catalogReference.appId and the services filter. */
|
|
15
|
+
export const BOOKINGS_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
16
|
+
/** Staff-member resource type id (ANY_RESOURCE fallback + staff filtering). */
|
|
17
|
+
export const STAFF_RESOURCE_TYPE_ID = "1cd44cf8-756f-41c3-bd90-3e2ffcaf1155";
|
|
18
|
+
/** Requested on every service read — without it `staffMemberDetails` is absent and the DTO's staff is []. */
|
|
19
|
+
export const SERVICES_CONDITIONAL_FIELDS = ["STAFF_MEMBER_DETAILS"] as const;
|
|
20
|
+
|
|
21
|
+
export const rawId = (raw: Raw | undefined | null): string => raw?._id ?? raw?.id ?? "";
|
|
22
|
+
|
|
23
|
+
/** The filter every services read shares: this app's services, optionally one slug (`mainSlug.name`). */
|
|
24
|
+
export function servicesFilter(slug?: string): Raw {
|
|
25
|
+
return { appId: BOOKINGS_APP_ID, ...(slug ? { "mainSlug.name": slug } : {}) };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Hidden services never reach a tile — the same rule on both transports. */
|
|
29
|
+
export const isVisible = (raw: Raw): boolean => !raw.hidden;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* A media value the image helpers can resolve. The SDK hands over a `wix:image://v1/<id>/<name>#…`
|
|
33
|
+
* string (or an https URL); REST hands over the image OBJECT with a bare file id — rebuilt into the
|
|
34
|
+
* same `wix:image://` form the SDK produces, so imgSrc scales both identically.
|
|
35
|
+
*/
|
|
36
|
+
export function mediaValue(image: unknown): unknown {
|
|
37
|
+
if (!image || typeof image === "string") return image;
|
|
38
|
+
const o = image as Raw;
|
|
39
|
+
const url: string = o.url ?? "";
|
|
40
|
+
if (/^(https?:|wix:image:)/.test(url)) return url;
|
|
41
|
+
if (o.id) return `wix:image://v1/${o.id}/${encodeURIComponent(o.filename ?? "")}#originWidth=${o.width ?? 0}&originHeight=${o.height ?? 0}`;
|
|
42
|
+
return url;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function formatPrice(value: string | undefined, currency: string | undefined): string {
|
|
46
|
+
if (value == null || value === "" || Number(value) === 0) return "Free";
|
|
47
|
+
try {
|
|
48
|
+
return new Intl.NumberFormat(undefined, { style: "currency", currency: currency || "USD" }).format(Number(value));
|
|
49
|
+
} catch {
|
|
50
|
+
return `${value} ${currency ?? ""}`.trim();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function toSummary(raw: Raw, imgSrc: ImgSrc): ServiceSummary {
|
|
55
|
+
const price = raw.payment?.fixed?.price;
|
|
56
|
+
const free = raw.payment?.rateType === "NO_FEE" || !price?.value || Number(price.value) === 0;
|
|
57
|
+
return {
|
|
58
|
+
id: rawId(raw),
|
|
59
|
+
slug: raw.mainSlug?.name ?? raw.supportedSlugs?.[0]?.name ?? "",
|
|
60
|
+
name: raw.name ?? "",
|
|
61
|
+
tagLine: raw.tagLine ?? "",
|
|
62
|
+
type: (raw.type ?? "APPOINTMENT") as ServiceType,
|
|
63
|
+
price: free ? "Free" : formatPrice(price?.value, price?.currency),
|
|
64
|
+
free,
|
|
65
|
+
durationMinutes: raw.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? null,
|
|
66
|
+
imageUrl: imgSrc(mediaValue(raw.media?.mainMedia?.image), 800, 800),
|
|
67
|
+
categoryId: rawId(raw.category) || null,
|
|
68
|
+
staff: ((raw.staffMemberDetails?.staffMembers ?? []) as Raw[])
|
|
69
|
+
.map((m) => ({ id: m.staffMemberId ?? "", name: m.name ?? "" }))
|
|
70
|
+
.filter((m) => m.id),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function toDetail(raw: Raw, imgSrc: ImgSrc): ServiceDetail {
|
|
75
|
+
const summary = toSummary(raw, imgSrc);
|
|
76
|
+
const online = raw.payment?.options?.online === true;
|
|
77
|
+
const inPerson = raw.payment?.options?.inPerson === true;
|
|
78
|
+
// Derive — never hardcode "ONLINE": a free/pay-in-person service booked ONLINE gets
|
|
79
|
+
// rejected by the cart with INSUFFICIENT_INVENTORY.
|
|
80
|
+
const paymentOption: "ONLINE" | "OFFLINE" = !online && inPerson ? "OFFLINE" : "ONLINE";
|
|
81
|
+
return {
|
|
82
|
+
...summary,
|
|
83
|
+
description: raw.description ?? "",
|
|
84
|
+
formId: rawId(raw.form) || null,
|
|
85
|
+
paymentOption,
|
|
86
|
+
cancellationFeeEnabled: raw.bookingPolicy?.cancellationFeePolicy?.enabled === true,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function toCategory(raw: Raw): BookingCategory {
|
|
91
|
+
return { id: rawId(raw), name: raw.name ?? "" };
|
|
92
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// The services listing as a framework-free store — the logic behind useServices, usable from React
|
|
2
|
+
// (useServices wraps it with useSyncExternalStore), from a static page or Vue/Svelte (subscribe and
|
|
3
|
+
// render), or as the specification for a port. State, actions, subscribe/getState, emit after every
|
|
4
|
+
// change. SSR-friendly: seed with `initialServices`/`initialCategories` and nothing is fetched;
|
|
5
|
+
// without them `start()` loads both. Category filtering is client-side — bookings catalogs are
|
|
6
|
+
// small and fully fetched. One store per mounted listing: createServicesStore(), not a singleton.
|
|
7
|
+
import { fetchBookingCategories, fetchServices } from "./services";
|
|
8
|
+
import type { BookingCategory, ServiceSummary } from "./types";
|
|
9
|
+
|
|
10
|
+
export interface ServicesStoreOptions {
|
|
11
|
+
initialServices?: ServiceSummary[];
|
|
12
|
+
initialCategories?: BookingCategory[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Everything a listing surface renders from. Read it with getState() or through a subscription. */
|
|
16
|
+
export interface ServicesState {
|
|
17
|
+
/** null while the first load is in flight — render skeletons, not an empty state. */
|
|
18
|
+
services: ServiceSummary[] | null;
|
|
19
|
+
categories: BookingCategory[];
|
|
20
|
+
activeCategoryId: string | null;
|
|
21
|
+
error: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ServicesStore {
|
|
25
|
+
getState(): ServicesState;
|
|
26
|
+
subscribe(listener: () => void): () => void;
|
|
27
|
+
/** Fetch whatever wasn't seeded. Call once when mounted (a browser). */
|
|
28
|
+
start(): void;
|
|
29
|
+
/** Stop reacting; drop late responses. */
|
|
30
|
+
stop(): void;
|
|
31
|
+
setActiveCategoryId(id: string | null): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createServicesStore({ initialServices, initialCategories }: ServicesStoreOptions = {}): ServicesStore {
|
|
35
|
+
let all: ServiceSummary[] | null = initialServices ?? null;
|
|
36
|
+
let categories: BookingCategory[] = initialCategories ?? [];
|
|
37
|
+
let activeCategoryId: string | null = null;
|
|
38
|
+
let error: string | null = null;
|
|
39
|
+
let started = false;
|
|
40
|
+
const listeners = new Set<() => void>();
|
|
41
|
+
let snapshot: ServicesState | null = null;
|
|
42
|
+
const emit = () => {
|
|
43
|
+
snapshot = null;
|
|
44
|
+
for (const fn of listeners) fn();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function getState(): ServicesState {
|
|
48
|
+
if (snapshot) return snapshot;
|
|
49
|
+
snapshot = {
|
|
50
|
+
services: all === null ? null : activeCategoryId ? all.filter((s) => s.categoryId === activeCategoryId) : all,
|
|
51
|
+
categories,
|
|
52
|
+
activeCategoryId,
|
|
53
|
+
error,
|
|
54
|
+
};
|
|
55
|
+
return snapshot;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
getState,
|
|
60
|
+
subscribe(fn) {
|
|
61
|
+
listeners.add(fn);
|
|
62
|
+
return () => listeners.delete(fn);
|
|
63
|
+
},
|
|
64
|
+
start() {
|
|
65
|
+
if (started) return;
|
|
66
|
+
started = true;
|
|
67
|
+
if (!initialServices) {
|
|
68
|
+
fetchServices()
|
|
69
|
+
.then((s) => { if (started) { all = s; emit(); } })
|
|
70
|
+
.catch((e) => {
|
|
71
|
+
if (!started) return;
|
|
72
|
+
all = [];
|
|
73
|
+
error = e instanceof Error ? e.message : String(e);
|
|
74
|
+
emit();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (!initialCategories) {
|
|
78
|
+
fetchBookingCategories().then((c) => { if (started) { categories = c; emit(); } });
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
stop() {
|
|
82
|
+
started = false;
|
|
83
|
+
},
|
|
84
|
+
setActiveCategoryId(id) {
|
|
85
|
+
if (id === activeCategoryId) return;
|
|
86
|
+
activeCategoryId = id;
|
|
87
|
+
emit();
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -1,95 +1,56 @@
|
|
|
1
|
-
// Service reads (Wix Bookings Services V2) — the only file that touches raw service
|
|
2
|
-
// entities. Everything it returns is a plain DTO from ./types.
|
|
3
|
-
//
|
|
1
|
+
// Service reads (Wix Bookings Services V2) over the SDK — the only file that touches raw service
|
|
2
|
+
// entities on this transport. Everything it returns is a plain DTO from ./types. The rules and
|
|
3
|
+
// mappers live in ./services-core (shared with the REST twin in references/bookings/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/bookings/services/services-v2/query-services.md
|
|
5
6
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/bookings/services/categories-v2/query-categories.md
|
|
6
7
|
import { services as servicesModule, categoriesV2 } from "@wix/bookings";
|
|
7
8
|
import { wixModule } from "../sdk";
|
|
8
9
|
import { imgSrc } from "../media";
|
|
9
|
-
import
|
|
10
|
+
import {
|
|
11
|
+
BOOKINGS_APP_ID,
|
|
12
|
+
SERVICES_CONDITIONAL_FIELDS,
|
|
13
|
+
STAFF_RESOURCE_TYPE_ID,
|
|
14
|
+
isVisible,
|
|
15
|
+
toCategory,
|
|
16
|
+
toDetail,
|
|
17
|
+
toSummary,
|
|
18
|
+
type Raw,
|
|
19
|
+
} from "./services-core";
|
|
20
|
+
import type { BookingCategory, ServiceDetail, ServiceSummary } from "./types";
|
|
21
|
+
|
|
22
|
+
export { BOOKINGS_APP_ID, STAFF_RESOURCE_TYPE_ID };
|
|
10
23
|
|
|
11
24
|
const services = wixModule(servicesModule);
|
|
12
25
|
const categories = wixModule(categoriesV2);
|
|
13
26
|
|
|
14
|
-
/** The Wix Bookings app id — the cart's catalogReference.appId and the services filter. */
|
|
15
|
-
export const BOOKINGS_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
16
|
-
/** Staff-member resource type id (ANY_RESOURCE fallback + staff filtering). */
|
|
17
|
-
export const STAFF_RESOURCE_TYPE_ID = "1cd44cf8-756f-41c3-bd90-3e2ffcaf1155";
|
|
18
|
-
|
|
19
|
-
type Raw = Record<string, any>;
|
|
20
|
-
|
|
21
|
-
function formatPrice(value: string | undefined, currency: string | undefined): string {
|
|
22
|
-
if (value == null || value === "" || Number(value) === 0) return "Free";
|
|
23
|
-
try {
|
|
24
|
-
return new Intl.NumberFormat(undefined, { style: "currency", currency: currency || "USD" }).format(Number(value));
|
|
25
|
-
} catch {
|
|
26
|
-
return `${value} ${currency ?? ""}`.trim();
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function toSummary(raw: Raw): ServiceSummary {
|
|
31
|
-
const price = raw.payment?.fixed?.price;
|
|
32
|
-
const free = raw.payment?.rateType === "NO_FEE" || !price?.value || Number(price.value) === 0;
|
|
33
|
-
return {
|
|
34
|
-
id: raw._id ?? "",
|
|
35
|
-
slug: raw.mainSlug?.name ?? raw.supportedSlugs?.[0]?.name ?? "",
|
|
36
|
-
name: raw.name ?? "",
|
|
37
|
-
tagLine: raw.tagLine ?? "",
|
|
38
|
-
type: (raw.type ?? "APPOINTMENT") as ServiceType,
|
|
39
|
-
price: free ? "Free" : formatPrice(price?.value, price?.currency),
|
|
40
|
-
free,
|
|
41
|
-
durationMinutes: raw.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? null,
|
|
42
|
-
imageUrl: imgSrc(raw.media?.mainMedia?.image, 800, 800),
|
|
43
|
-
categoryId: raw.category?._id ?? null,
|
|
44
|
-
staff: (raw.staffMemberDetails?.staffMembers ?? [])
|
|
45
|
-
.map((m: Raw) => ({ id: m.staffMemberId ?? "", name: m.name ?? "" }))
|
|
46
|
-
.filter((m: { id: string }) => m.id),
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function toDetail(raw: Raw): ServiceDetail {
|
|
51
|
-
const summary = toSummary(raw);
|
|
52
|
-
const online = raw.payment?.options?.online === true;
|
|
53
|
-
const inPerson = raw.payment?.options?.inPerson === true;
|
|
54
|
-
// Derive — never hardcode "ONLINE": a free/pay-in-person service booked ONLINE gets
|
|
55
|
-
// rejected by the cart with INSUFFICIENT_INVENTORY.
|
|
56
|
-
const paymentOption: "ONLINE" | "OFFLINE" = !online && inPerson ? "OFFLINE" : "ONLINE";
|
|
57
|
-
return {
|
|
58
|
-
...summary,
|
|
59
|
-
description: raw.description ?? "",
|
|
60
|
-
formId: raw.form?._id ?? null,
|
|
61
|
-
paymentOption,
|
|
62
|
-
cancellationFeeEnabled: raw.bookingPolicy?.cancellationFeePolicy?.enabled === true,
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
27
|
/** List bookable services (hidden ones filtered), mapped to grid-ready DTOs. */
|
|
67
28
|
export async function fetchServices({ limit = 100 } = {}): Promise<ServiceSummary[]> {
|
|
68
29
|
const res = await services
|
|
69
|
-
.queryServices({ conditionalFields:
|
|
30
|
+
.queryServices({ conditionalFields: SERVICES_CONDITIONAL_FIELDS as any })
|
|
70
31
|
.eq("appId", BOOKINGS_APP_ID)
|
|
71
32
|
.limit(limit)
|
|
72
33
|
.find();
|
|
73
|
-
return (res.items ?? [])
|
|
34
|
+
return ((res.items ?? []) as Raw[]).filter(isVisible).map((s) => toSummary(s, imgSrc));
|
|
74
35
|
}
|
|
75
36
|
|
|
76
37
|
/** Fetch one service by its URL slug (mainSlug.name). Null when not found. */
|
|
77
38
|
export async function fetchServiceBySlug(slug: string): Promise<ServiceDetail | null> {
|
|
78
39
|
const res = await services
|
|
79
|
-
.queryServices({ conditionalFields:
|
|
40
|
+
.queryServices({ conditionalFields: SERVICES_CONDITIONAL_FIELDS as any })
|
|
80
41
|
.eq("mainSlug.name", slug)
|
|
81
42
|
.eq("appId", BOOKINGS_APP_ID)
|
|
82
43
|
.limit(1)
|
|
83
44
|
.find();
|
|
84
45
|
const raw = res.items?.[0];
|
|
85
|
-
return raw ? toDetail(raw as Raw) : null;
|
|
46
|
+
return raw ? toDetail(raw as Raw, imgSrc) : null;
|
|
86
47
|
}
|
|
87
48
|
|
|
88
49
|
/** Service categories for a filter bar — non-fatal (empty array on failure). */
|
|
89
50
|
export async function fetchBookingCategories(): Promise<BookingCategory[]> {
|
|
90
51
|
try {
|
|
91
52
|
const res = await categories.queryCategories().find();
|
|
92
|
-
return (res.items ?? [])
|
|
53
|
+
return ((res.items ?? []) as Raw[]).map(toCategory).filter((c) => c.id);
|
|
93
54
|
} catch {
|
|
94
55
|
return [];
|
|
95
56
|
}
|
|
@@ -56,12 +56,14 @@ export interface Slot {
|
|
|
56
56
|
staff: { id: string; name: string }[];
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
/** A booking-form field (flat, from
|
|
59
|
+
/** A booking-form field (flat, from the form summary + schema; values are keyed by `target`). */
|
|
60
60
|
export interface BookingFormField {
|
|
61
61
|
target: string;
|
|
62
62
|
label: string;
|
|
63
63
|
type: "STRING" | "EMAIL" | "PHONE" | "NUMBER" | "URL";
|
|
64
64
|
options?: string[];
|
|
65
|
+
/** The form marks it required — the only fields that gate `canBook`; the rest may stay empty. */
|
|
66
|
+
required: boolean;
|
|
65
67
|
}
|
|
66
68
|
|
|
67
69
|
/** The outcome of book(): either the browser is being redirected, or it's done. */
|