@wix/agent-skills 1.17.0 → 1.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +115 -12
- package/skills/wix-app/references/APP_MARKET_REVIEW.md +1 -1
- package/skills/wix-app/references/BACKEND_API.md +18 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +6 -0
- package/skills/wix-app/references/DASHBOARD_MODAL.md +10 -2
- package/skills/wix-app/references/DASHBOARD_PAGE.md +32 -70
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +74 -83
- package/skills/wix-app/references/SITE_PLUGIN.md +12 -0
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +74 -68
- package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +79 -0
- package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +73 -0
- package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +52 -0
- package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +184 -0
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +12 -11
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +1 -0
- package/skills/wix-app/references/editor-react-component/BROWSER-SUPPORT.md +56 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +16 -8
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +47 -14
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +3 -0
- package/skills/wix-base44-connector/SKILL.md +133 -44
- package/skills/wix-base44-connector/references/CAPABILITY_MAP.md +78 -0
- package/skills/wix-base44-connector/scripts/utils.js +216 -30
- package/skills/wix-headless/entry/skill.md +1 -1
- package/skills/wix-headless/references/CAPABILITIES.md +9 -1
- package/skills/wix-headless/references/DESIGN.md +1 -0
- package/skills/wix-headless/references/DISCOVERY.md +2 -1
- package/skills/wix-headless/references/IMAGE_GENERATION.md +1 -0
- package/skills/wix-headless/references/SDK_HANDOFF.md +4 -1
- package/skills/wix-headless/references/SEED.md +2 -1
- package/skills/wix-headless/references/SETUP.md +1 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +2 -2
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +26 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +26 -10
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +8 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-rentals.md +254 -0
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +57 -10
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +13 -0
- package/skills/wix-headless/references/inline-recipes/setup-rentals.md +254 -0
- package/skills/wix-headless/references/non-astro.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +166 -0
- package/skills/wix-headless-fast/entry/skill.md +99 -0
- package/skills/wix-headless-fast/install/deploy.mjs +433 -0
- package/skills/wix-headless-fast/install/fast-path.mjs +141 -0
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +137 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +137 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +113 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +42 -0
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +123 -0
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +70 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +99 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +76 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +62 -0
- package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +99 -0
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +24 -0
- package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +16156 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +72 -0
- package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +287 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +129 -0
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +143 -0
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +119 -0
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +138 -0
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +54 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +206 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +96 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +70 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +76 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +22 -0
- package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +11962 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +61 -0
- package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +302 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +179 -0
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +83 -0
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +57 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +151 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +75 -0
- package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +41 -0
- package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +11262 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +103 -0
- package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +324 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +149 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +66 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +176 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +118 -0
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +117 -0
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +59 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +136 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +128 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +61 -0
- package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +12 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +80 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +21 -0
- package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +11884 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +86 -0
- package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +276 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +150 -0
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +317 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +269 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +231 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +135 -0
- package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +10593 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +108 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +320 -0
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +63 -0
- package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +60 -0
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +155 -0
- package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +78 -0
- package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +47 -0
- package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +66 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +88 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +53 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +122 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +50 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +21 -0
- package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +42 -0
- package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +10 -0
- package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +11 -0
- package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +10329 -0
- package/skills/wix-headless-fast/references/members/seed/SEED.md +39 -0
- package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +94 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +134 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +110 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +90 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +115 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +59 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +39 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +165 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +55 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +30 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +21 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +29 -0
- package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +10197 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +78 -0
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +258 -0
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +133 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +58 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +126 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +37 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +39 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +75 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +45 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +30 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +40 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +21 -0
- package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +11226 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +71 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +279 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +165 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +142 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +26 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +121 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +55 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +43 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +177 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +165 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +103 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +254 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +146 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +168 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +47 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +22 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +12 -0
- package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +12560 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +81 -0
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +479 -0
- package/skills/wix-headless-fast/references/shared/CONTENT.md +22 -0
- package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +73 -0
- package/skills/wix-headless-fast/references/shared/DESIGN.md +26 -0
- package/skills/wix-headless-fast/references/shared/app/styles/global.css +52 -0
- package/skills/wix-headless-fast/references/shared/app/wix/config.ts +15 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +27 -0
- package/skills/wix-headless-fast/references/shared/app/wix/money.ts +24 -0
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +76 -0
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +46 -0
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +46 -0
- package/skills/wix-headless-fast/references/shared/seed/images.mjs +145 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +149 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +26 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +121 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx +100 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductDetailView.tsx +130 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx +48 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ShopView.tsx +66 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx +103 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +48 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +140 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +80 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +101 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +221 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +215 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +109 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +46 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/products/[slug].astro +56 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/shop.astro +23 -0
- package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +12247 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +85 -0
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +415 -0
- package/skills/wix-manage/SKILL.md +113 -89
- package/skills/wix-manage/references/dashboard-navigation/routes.json +12 -52
- package/skills/wix-manage/references/ecommerce/shipping/ecom-shipping-api.md +0 -2
- package/skills/wix-manage/references/forms/create-form.md +272 -184
- package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +13 -10
- package/skills/wix-manage/references/forms/update-form.md +122 -0
- package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +141 -0
- package/skills/wix-manage/references/google-business-profile/connect-google-business-profile.md +134 -0
- package/skills/wix-manage/references/google-business-profile/manage-google-business-profile-locations.md +182 -0
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +3 -0
- package/skills/wix-manage/references/media/generate-image-with-ai.md +86 -0
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +37 -7
- package/skills/wix-manage/references/stores/update-product-with-options.md +83 -0
- package/skills/wix-vibe-headless/SKILL.md +18 -4
- package/skills/wix-vibe-headless/install/deploy.cjs +12 -4
- package/skills/wix-vibe-headless/install/pin-agents-md.cjs +2 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +3 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +3 -1
- package/skills/wix-vibe-headless/platforms/base44.md +27 -61
- package/skills/wix-vibe-headless/platforms/generic.md +6 -3
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/RentalDurationPicker.jsx +67 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useRentalDuration.js +128 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js +5 -3
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-rentals.js +250 -0
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-services.js +12 -5
- package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +41 -0
- package/skills/wix-vibe-headless/references/bookings/seed/seed-rentals.js +267 -0
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +6 -3
- package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +486 -0
- package/skills/wix-vibe-headless/references/forms/app/hooks/useWixForm.js +425 -0
- package/skills/wix-vibe-headless/references/forms/app/lib/wix-form-schema-utils.js +187 -0
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms-submissions.js +238 -0
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms.js +108 -0
- package/skills/wix-vibe-headless/references/forms/seed/SEED.md +1836 -0
- package/skills/wix-vibe-headless/references/rentals/INSTRUCTIONS.md +13 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +137 -84
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +15 -8
- package/skills/wix-vibe-headless/references/storefront/app/lib/storeImage.js +37 -0
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +18 -6
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +30 -7
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.js +46 -10
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductCard.jsx +0 -110
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGallery.jsx +0 -51
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGrid.jsx +0 -49
- package/skills/wix-vibe-headless/references/storefront/app/components/VariantPicker.jsx +0 -92
- package/skills/wix-vibe-headless/references/storefront/app/pages/ProductDetail.jsx +0 -139
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { wixApiRequest } from "./wix-client.js";
|
|
2
|
+
import { listAvailableSlots } from "./wix-bookings-services.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Wix Rentals — a DELTA on `wix-bookings-services.js`, not a replacement.
|
|
6
|
+
*
|
|
7
|
+
* Wix Rentals has no APIs of its own. A rental is a Bookings **service** with rentals-specific
|
|
8
|
+
* field values, so the catalog read, the booking form, and the whole
|
|
9
|
+
* `createBooking → cart → checkout` chain in `wix-bookings-checkout.js` are UNCHANGED. Only
|
|
10
|
+
* three things differ, and they are all here:
|
|
11
|
+
*
|
|
12
|
+
* 1. every catalog read filters by the rentals `appId`, or rentals and appointments interleave
|
|
13
|
+
* 2. the customer picks the LENGTH, so availability is two calls: start times, then the valid
|
|
14
|
+
* ends for the chosen start
|
|
15
|
+
* 3. price is a rate per unit, so the total for a chosen length comes from the server
|
|
16
|
+
*
|
|
17
|
+
* What makes a service a rental (from the docs' own mapping — nothing is inferred):
|
|
18
|
+
* type always "APPOINTMENT"
|
|
19
|
+
* appId always RENTALS_APP_ID
|
|
20
|
+
* schedule.availabilityConstraints.durationRange replaces `sessionDurations`
|
|
21
|
+
* — MUTUALLY EXCLUSIVE, one or the other
|
|
22
|
+
* primaryResourceType a resource type id, not staff — availability comes from its resources
|
|
23
|
+
* payment.rateType always "FIXED", charged per hour or per day
|
|
24
|
+
*
|
|
25
|
+
* ⚠️ Because the two constraint fields are mutually exclusive, `isRental()` below is a reliable
|
|
26
|
+
* test on the DATA. Never decide "this is a rental" from the brief, the service name, or a
|
|
27
|
+
* category — a 60-minute yoga class and a meeting room rented for 1–8 hours are told apart by
|
|
28
|
+
* which constraint the service carries, and nothing else.
|
|
29
|
+
*
|
|
30
|
+
* docs: https://dev.wix.com/docs/api-reference/business-solutions/rentals/wix-rentals-and-the-bookings-apis.md
|
|
31
|
+
* docs: https://dev.wix.com/docs/api-reference/business-solutions/rentals/about-wix-rentals-availability.md
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/** The Wix Rentals app. Used twice: to filter the catalog, and as the cart's catalogReference.appId. */
|
|
35
|
+
export const RENTALS_APP_ID = "ff5d6eb1-65e4-4f9a-8b14-64d34c12cc2e";
|
|
36
|
+
|
|
37
|
+
/** The Wix Bookings app — the other half of a mixed site. */
|
|
38
|
+
export const BOOKINGS_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Is this service a rental? True when it carries a duration RANGE the customer picks within,
|
|
42
|
+
* rather than a fixed session length. This is the only correct test.
|
|
43
|
+
* @param {object} service
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
export function isRental(service) {
|
|
47
|
+
return Boolean(service?.schedule?.availabilityConstraints?.durationRange);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The rental's unit and bounds, or null for an ordinary service.
|
|
52
|
+
* Hourly bounds are MINUTES (30–1440), daily bounds are DAYS (1–8).
|
|
53
|
+
* @param {object} service
|
|
54
|
+
* @returns {{ unit: "HOUR"|"DAY", min: number, max: number, step: number }|null}
|
|
55
|
+
*/
|
|
56
|
+
export function rentalDuration(service) {
|
|
57
|
+
const range = service?.schedule?.availabilityConstraints?.durationRange;
|
|
58
|
+
if (!range) return null;
|
|
59
|
+
if (range.unitType === "DAY") {
|
|
60
|
+
return {
|
|
61
|
+
unit: "DAY",
|
|
62
|
+
min: range.dayOptions?.minDurationInDays ?? 1,
|
|
63
|
+
max: range.dayOptions?.maxDurationInDays ?? 1,
|
|
64
|
+
step: 1,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
unit: "HOUR",
|
|
69
|
+
min: range.hourOptions?.minDurationInMinutes ?? 30,
|
|
70
|
+
max: range.hourOptions?.maxDurationInMinutes ?? 1440,
|
|
71
|
+
step: 30,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Query rentals only. On a site that has both apps an unfiltered `queryServices` returns
|
|
77
|
+
* haircuts next to meeting rooms, so this is not optional — it is the same query with the
|
|
78
|
+
* rentals `appId` on the filter.
|
|
79
|
+
*
|
|
80
|
+
* Pass `appId: BOOKINGS_APP_ID` to get the ordinary services instead, on a mixed site.
|
|
81
|
+
* @param {{ limit?: number, offset?: number, appId?: string }} [options]
|
|
82
|
+
* @returns {Promise<{ services: object[], total: number, nextOffset: number|null }>}
|
|
83
|
+
*/
|
|
84
|
+
export async function queryRentals({ limit = 100, offset = 0, appId = RENTALS_APP_ID } = {}) {
|
|
85
|
+
const res = await wixApiRequest("/bookings/v2/services/query", {
|
|
86
|
+
method: "POST",
|
|
87
|
+
body: { query: { filter: { hidden: false, appId }, paging: { limit, offset } } },
|
|
88
|
+
});
|
|
89
|
+
const services = res?.services ?? [];
|
|
90
|
+
const total = res?.pagingMetadata?.total ?? services.length;
|
|
91
|
+
const loaded = offset + services.length;
|
|
92
|
+
return { services, total, nextOffset: loaded < total ? loaded : null };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The resource type id(s) a rental's availability comes from — read off `serviceResources`.
|
|
97
|
+
* Every rental availability call must pass these as `includeResourceTypeIds`, or it returns zero slots.
|
|
98
|
+
* @param {object} service
|
|
99
|
+
* @returns {string[]}
|
|
100
|
+
*/
|
|
101
|
+
export function rentalResourceTypeIds(service) {
|
|
102
|
+
return (service?.serviceResources ?? [])
|
|
103
|
+
.map((sr) => sr.resourceType?._id ?? sr.resourceType?.id)
|
|
104
|
+
.filter(Boolean);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Start times for a rental — step 1 of 2.
|
|
109
|
+
*
|
|
110
|
+
* A rental is an APPOINTMENT-typed service, so this is the same availability call an appointment
|
|
111
|
+
* uses; what comes back are the times a rental may START. The customer then picks how long, which is
|
|
112
|
+
* `listEndOptions` below. Pass the whole `service` so its resource types ride along — the
|
|
113
|
+
* availability engine reads them to find a resource-driven service's slots.
|
|
114
|
+
* @param {object} service A rental service from queryRentals.
|
|
115
|
+
* @param {{ fromLocalDate: string, toLocalDate: string, timeZone?: string, limit?: number, cursor?: string }} options
|
|
116
|
+
* @returns {Promise<{ slots: object[], nextCursor: string|null, timeZone: string|null }>}
|
|
117
|
+
*/
|
|
118
|
+
export function listRentalStartSlots(service, options = {}) {
|
|
119
|
+
const serviceId = service?._id || service?.id;
|
|
120
|
+
return listAvailableSlots(serviceId, { ...options, includeResourceTypeIds: rentalResourceTypeIds(service) });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The valid END times for a chosen start — step 2 of 2, HOURLY rentals only.
|
|
125
|
+
*
|
|
126
|
+
* Each entry is a TimeSlot sharing `localStartDate` with the request and varying only in
|
|
127
|
+
* `localEndDate`, sorted shortest first. The service's maximum duration always caps the list,
|
|
128
|
+
* so a `maxLocalEndDate` beyond it is ignored rather than rejected.
|
|
129
|
+
*
|
|
130
|
+
* ⚠️ Hourly only. A DAILY rental has no end options — its lengths are whole days, so walk
|
|
131
|
+
* consecutive days from the chosen start instead (`dailyEndOptions` below).
|
|
132
|
+
* https://dev.wix.com/docs/api-reference/business-solutions/bookings/time-slots/time-slots-v2/list-availability-time-slot-end-options.md
|
|
133
|
+
* Pass the chosen start slot's own `location`. It carries `locationType` (and an `id` on a
|
|
134
|
+
* multi-location site); `{ locationType: "BUSINESS" }` is enough on a single-location site.
|
|
135
|
+
* @param {string} serviceId
|
|
136
|
+
* @param {{ localStartDate: string, location: object, maxLocalEndDate?: string, timeZone?: string }} options
|
|
137
|
+
* @returns {Promise<{ endOptions: object[], timeZone: string|null }>}
|
|
138
|
+
*/
|
|
139
|
+
export async function listEndOptions(serviceId, { localStartDate, location, maxLocalEndDate, timeZone } = {}) {
|
|
140
|
+
if (!localStartDate) throw new Error("listEndOptions requires localStartDate (local 'YYYY-MM-DDThh:mm:ss').");
|
|
141
|
+
if (!location) throw new Error("listEndOptions requires the chosen slot's location.");
|
|
142
|
+
const res = await wixApiRequest("/_api/service-availability/v2/time-slots/end-options", {
|
|
143
|
+
method: "POST",
|
|
144
|
+
body: {
|
|
145
|
+
serviceId,
|
|
146
|
+
localStartDate,
|
|
147
|
+
location,
|
|
148
|
+
...(maxLocalEndDate ? { maxLocalEndDate } : {}),
|
|
149
|
+
...(timeZone ? { timeZone } : {}),
|
|
150
|
+
},
|
|
151
|
+
});
|
|
152
|
+
return { endOptions: res?.endOptions ?? [], timeZone: res?.timeZone ?? null };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* The lengths a DAILY rental can run for from a chosen start day — the daily counterpart to
|
|
157
|
+
* `listEndOptions`. A day's slot list is the availability, so this walks forward from the start
|
|
158
|
+
* and stops at the first day that has none: a 3-day rental needs all 3 days free, and a gap
|
|
159
|
+
* means the longer options are not bookable however far out the calendar goes.
|
|
160
|
+
*
|
|
161
|
+
* Returns one entry per bookable length, `{ days, localEndDate }`, shortest first.
|
|
162
|
+
* `localEndDate` is midnight of the day AFTER the last rented day — the end boundary is
|
|
163
|
+
* exclusive, which is the same convention the catalog's availability window uses.
|
|
164
|
+
* @param {object} service A rental service (daily).
|
|
165
|
+
* @param {{ localStartDate: string, timeZone?: string }} options
|
|
166
|
+
* @returns {Promise<{ days: number, localEndDate: string }[]>}
|
|
167
|
+
*/
|
|
168
|
+
export async function dailyEndOptions(service, { localStartDate, timeZone } = {}) {
|
|
169
|
+
const duration = rentalDuration(service);
|
|
170
|
+
if (!duration || duration.unit !== "DAY") {
|
|
171
|
+
throw new Error("dailyEndOptions expects a DAY-unit rental — use listEndOptions for hourly.");
|
|
172
|
+
}
|
|
173
|
+
const serviceId = service._id || service.id;
|
|
174
|
+
const includeResourceTypeIds = rentalResourceTypeIds(service);
|
|
175
|
+
const startDay = localStartDate.slice(0, 10);
|
|
176
|
+
const dayAfter = (isoDay, n) => {
|
|
177
|
+
const d = new Date(`${isoDay}T00:00:00Z`);
|
|
178
|
+
d.setUTCDate(d.getUTCDate() + n);
|
|
179
|
+
return d.toISOString().slice(0, 10);
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const out = [];
|
|
183
|
+
for (let days = 1; days <= duration.max; days++) {
|
|
184
|
+
const dayToCheck = dayAfter(startDay, days - 1);
|
|
185
|
+
const { slots } = await listAvailableSlots(serviceId, {
|
|
186
|
+
fromLocalDate: `${dayToCheck}T00:00:00`,
|
|
187
|
+
toLocalDate: `${dayAfter(dayToCheck, 1)}T00:00:00`,
|
|
188
|
+
timeZone,
|
|
189
|
+
limit: 1,
|
|
190
|
+
includeResourceTypeIds,
|
|
191
|
+
});
|
|
192
|
+
if (!slots.length) break; // a gap — every longer option is unbookable too
|
|
193
|
+
if (days >= duration.min) out.push({ days, localEndDate: `${dayAfter(startDay, days)}T00:00:00` });
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The price for a chosen length, from the server.
|
|
200
|
+
*
|
|
201
|
+
* ⚠️ Never compute this client-side. A rental is charged as a RATE — per hour or per day — and
|
|
202
|
+
* hourly is calculated at per-minute granularity, so a 90-minute rental of a $10/hour room is
|
|
203
|
+
* $15. Multiplying a displayed base price by a rounded number of hours quietly disagrees with
|
|
204
|
+
* what the customer is charged at checkout.
|
|
205
|
+
*
|
|
206
|
+
* ⚠️ Omitting the local dates or the time zone does NOT error — it returns a duration-blind
|
|
207
|
+
* price, which is the same trap in a quieter form.
|
|
208
|
+
* https://dev.wix.com/docs/api-reference/business-solutions/bookings/pricing/pricing-api/preview-price.md
|
|
209
|
+
* @param {string} serviceId
|
|
210
|
+
* @param {{ localStartDate: string, localEndDate: string, timeZone: string, numberOfParticipants?: number }} options
|
|
211
|
+
* @returns {Promise<{ total: string, currency: string|null, raw: object }>}
|
|
212
|
+
*/
|
|
213
|
+
export async function previewRentalPrice(serviceId, { localStartDate, localEndDate, timeZone, numberOfParticipants = 1 } = {}) {
|
|
214
|
+
if (!localStartDate || !localEndDate || !timeZone) {
|
|
215
|
+
throw new Error("previewRentalPrice requires localStartDate, localEndDate and timeZone — without them the price ignores the duration.");
|
|
216
|
+
}
|
|
217
|
+
const res = await wixApiRequest("/bookings/v2/pricing/v2/pricing/preview", {
|
|
218
|
+
method: "POST",
|
|
219
|
+
body: {
|
|
220
|
+
bookingLineItems: [{ serviceId, localStartDate, localEndDate, timeZone, numberOfParticipants }],
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
const info = res?.priceInfo ?? {};
|
|
224
|
+
const total = info.totalPrice ?? info.total ?? {};
|
|
225
|
+
return {
|
|
226
|
+
total: total.formattedValue ?? (total.value != null ? String(total.value) : ""),
|
|
227
|
+
currency: total.currency ?? null,
|
|
228
|
+
raw: info,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* A rental's rate, ready to render — "$10 / hour" or "$120 / day".
|
|
234
|
+
* `servicePriceLabel` in `lib/serviceFacts.js` renders the same number as a flat price, which
|
|
235
|
+
* reads as the whole cost of the rental rather than its rate.
|
|
236
|
+
* @param {object} service
|
|
237
|
+
* @returns {string}
|
|
238
|
+
*/
|
|
239
|
+
export function rentalRateLabel(service) {
|
|
240
|
+
const duration = rentalDuration(service);
|
|
241
|
+
if (!duration) return "";
|
|
242
|
+
const price = service?.payment?.fixed?.price;
|
|
243
|
+
const money =
|
|
244
|
+
price?.formattedValue ||
|
|
245
|
+
(price?.currency && price?.value != null
|
|
246
|
+
? new Intl.NumberFormat(undefined, { style: "currency", currency: price.currency }).format(Number(price.value))
|
|
247
|
+
: "");
|
|
248
|
+
if (!money) return "";
|
|
249
|
+
return `${money} / ${duration.unit === "DAY" ? "day" : "hour"}`;
|
|
250
|
+
}
|
|
@@ -183,11 +183,13 @@ export async function countServices() {
|
|
|
183
183
|
* If you render a mixed catalog, route by `service.type` via `listSlotsForService`.
|
|
184
184
|
* Dates are LOCAL ("YYYY-MM-DDThh:mm:ss") in timeZone.
|
|
185
185
|
* https://dev.wix.com/docs/api-reference/business-solutions/bookings/time-slots/time-slots-v2/list-availability-time-slots.md
|
|
186
|
+
* For a rental (a resource-driven service), pass its resource type id(s) as `includeResourceTypeIds`
|
|
187
|
+
* so the slots come back with their bookable resources; a staff appointment can omit it.
|
|
186
188
|
* @param {string} serviceId
|
|
187
|
-
* @param {{ fromLocalDate: string, toLocalDate: string, timeZone?: string, limit?: number, cursor?: string }} options
|
|
189
|
+
* @param {{ fromLocalDate: string, toLocalDate: string, timeZone?: string, limit?: number, cursor?: string, includeResourceTypeIds?: string[] }} options
|
|
188
190
|
* @returns {Promise<{ slots: object[], nextCursor: string|null, timeZone: string|null }>}
|
|
189
191
|
*/
|
|
190
|
-
export async function listAvailableSlots(serviceId, { fromLocalDate, toLocalDate, timeZone, limit = 1000, cursor } = {}) {
|
|
192
|
+
export async function listAvailableSlots(serviceId, { fromLocalDate, toLocalDate, timeZone, limit = 1000, cursor, includeResourceTypeIds } = {}) {
|
|
191
193
|
if (!cursor && (!fromLocalDate || !toLocalDate)) {
|
|
192
194
|
throw new Error("listAvailableSlots requires fromLocalDate and toLocalDate (local 'YYYY-MM-DDThh:mm:ss').");
|
|
193
195
|
}
|
|
@@ -200,6 +202,7 @@ export async function listAvailableSlots(serviceId, { fromLocalDate, toLocalDate
|
|
|
200
202
|
toLocalDate,
|
|
201
203
|
timeZone: timeZone || defaultTimeZone(),
|
|
202
204
|
cursorPaging: { limit },
|
|
205
|
+
...(includeResourceTypeIds?.length ? { includeResourceTypeIds } : {}),
|
|
203
206
|
};
|
|
204
207
|
const res = await wixApiRequest("/_api/service-availability/v2/time-slots", { method: "POST", body });
|
|
205
208
|
return {
|
|
@@ -287,7 +290,11 @@ export async function listEventTimeSlots(serviceIds, { fromLocalDate, toLocalDat
|
|
|
287
290
|
export async function listSlotsForService(service, options = {}) {
|
|
288
291
|
const serviceId = service?._id || service?.id;
|
|
289
292
|
if (!serviceId) throw new Error("listSlotsForService requires a service with an id.");
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
+
if (service?.type === "CLASS" || service?.type === "COURSE") return listEventTimeSlots(serviceId, options);
|
|
294
|
+
// For a rental, name its resource type(s) so the slots come back; reading them off the service
|
|
295
|
+
// keeps this call type-agnostic (a staff appointment just yields an empty list here).
|
|
296
|
+
const includeResourceTypeIds =
|
|
297
|
+
options.includeResourceTypeIds ??
|
|
298
|
+
(service?.serviceResources ?? []).map((sr) => sr.resourceType?._id ?? sr.resourceType?.id).filter(Boolean);
|
|
299
|
+
return listAvailableSlots(serviceId, { ...options, includeResourceTypeIds });
|
|
293
300
|
}
|
|
@@ -101,6 +101,47 @@ await seed.attachServiceImage(ctx, { serviceId: services[0].id, revision: servic
|
|
|
101
101
|
Both bulk calls report per-item `success`/`error` — retry only the failed items **once** with the
|
|
102
102
|
same body; don't loop and don't re-create the ones that already succeeded.
|
|
103
103
|
|
|
104
|
+
## Rentals
|
|
105
|
+
|
|
106
|
+
`seed-rentals.js` is the rentals half — the same transport, a different create order and payload.
|
|
107
|
+
Wix Rentals has no APIs of its own, so a rental is a Bookings service with rentals-specific
|
|
108
|
+
field values.
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
const seed = require("…/references/bookings/seed/seed-rentals.js");
|
|
112
|
+
await seed.setupRentals(ctx, {
|
|
113
|
+
resourceTypeName: "Meeting rooms",
|
|
114
|
+
resources: ["Room A", "Room B"], // parallel capacity = MORE RESOURCES
|
|
115
|
+
rentals: [
|
|
116
|
+
{ name: "Meeting room, hourly", description: "…", price: 25, unit: "HOUR", min: 60, max: 480 },
|
|
117
|
+
{ name: "Meeting room, daily", description: "…", price: 180, unit: "DAY", min: 1, max: 5 },
|
|
118
|
+
],
|
|
119
|
+
});
|
|
120
|
+
// → { resourceType, resources, services, ok, problems }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`price` is a RATE — per hour or per day — not the cost of a rental.
|
|
124
|
+
Hourly `min`/`max` are MINUTES (30–1440); daily are DAYS (1–8).
|
|
125
|
+
|
|
126
|
+
**`ok` must be true.** `problems` is the read-back check: a service created without `appId` or
|
|
127
|
+
without `durationRange` still returns 200 and is simply a plain Bookings service from then on.
|
|
128
|
+
|
|
129
|
+
### The four traps this seed handles
|
|
130
|
+
|
|
131
|
+
- **Install Rentals ONLY.** It provisions the Bookings infrastructure it runs on; installing
|
|
132
|
+
Wix Bookings as well is redundant.
|
|
133
|
+
- **`appId` is immutable after create.** A service created without it is a plain Bookings
|
|
134
|
+
service forever — there is no update that converts it.
|
|
135
|
+
- **Order is load-bearing: resource type → resources → services.** A service whose resource
|
|
136
|
+
type holds no resources has permanently empty availability, and nothing errors.
|
|
137
|
+
- **Rental services do not use categories.** Unlike bookings, do not create or assign one.
|
|
138
|
+
- **`serviceResources` is required** and the docs' Create Service parameter list omits it.
|
|
139
|
+
Without it the create fails `MISSING_APPOINTMENT_RESOURCES`, whose message sends you to check
|
|
140
|
+
the resource type's contents — a dead end, since it fails even when the type is populated.
|
|
141
|
+
|
|
142
|
+
Resources are seeded **24/7** (no working hours) so a multi-day rental stays one booking rather
|
|
143
|
+
than a multi-service group. Pass `workingHours` only when the brief names opening hours.
|
|
144
|
+
|
|
104
145
|
## Reference
|
|
105
146
|
If a call returns a shape you didn't expect, or you need an operation this module doesn't cover,
|
|
106
147
|
use the **`wix-docs`** skill to search + read the live Wix API reference — never guess. The
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Rentals seed helpers — run at BUILD TIME via exec_tool (NOT shipped in the app).
|
|
2
|
+
// A DELTA on seed-bookings.js: Wix Rentals has no APIs of its own, so a rental is a Bookings
|
|
3
|
+
// service carrying rentals-specific field values. Everything about the transport, images and
|
|
4
|
+
// error handling is the same; only the create order and the service payload differ.
|
|
5
|
+
//
|
|
6
|
+
// Live-verified end to end on fresh headless sites (install → resource type → resources → service →
|
|
7
|
+
// time-slots → end-options).
|
|
8
|
+
//
|
|
9
|
+
// Usage (build-time exec_tool) — prefer `setupRentals`, which runs the whole flow in the one order
|
|
10
|
+
// that works and threads the resource ids into each service for you:
|
|
11
|
+
// const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix");
|
|
12
|
+
// const seed = require("/app/.agents/skills/wix-vibe-headless/references/bookings/seed/seed-rentals.js");
|
|
13
|
+
// const ctx = { token: accessToken, siteId: WIX_METASITE_ID };
|
|
14
|
+
// await seed.setupRentals(ctx, {
|
|
15
|
+
// resourceTypeName: "Meeting rooms",
|
|
16
|
+
// resources: ["Room A", "Room B"], // capacity = MORE RESOURCES
|
|
17
|
+
// rentals: [
|
|
18
|
+
// { name: "Meeting room, hourly", description: "…", price: 25, unit: "HOUR", min: 60, max: 480 },
|
|
19
|
+
// { name: "Meeting room, daily", description: "…", price: 180, unit: "DAY", min: 1, max: 5 },
|
|
20
|
+
// ],
|
|
21
|
+
// });
|
|
22
|
+
// // Calling the steps by hand instead? ORDER IS LOAD-BEARING (resource type → resources → service),
|
|
23
|
+
// // and each rental spec must carry `resourceTypeId` AND `resourceIds` (the ids from createResources).
|
|
24
|
+
//
|
|
25
|
+
// What a bookable rental needs, and setupRentals sets every one:
|
|
26
|
+
// 1. `appId` on the service at create time (it is immutable, so set it here rather than later).
|
|
27
|
+
// 2. Resources created before the service, and their ids listed in `serviceResources[].resourceIds`.
|
|
28
|
+
// 3. No category — a rental is surfaced by its `appId`, not by a Bookings category.
|
|
29
|
+
//
|
|
30
|
+
// Images are reused from seed-bookings.js — require both.
|
|
31
|
+
// Source recipe: wix-headless/references/inline-recipes/setup-rentals.md.
|
|
32
|
+
|
|
33
|
+
const API = "https://www.wixapis.com";
|
|
34
|
+
|
|
35
|
+
/** The Wix Rentals app — adds resource types and duration ranges on top of Bookings. */
|
|
36
|
+
const RENTALS_APP_ID = "ff5d6eb1-65e4-4f9a-8b14-64d34c12cc2e";
|
|
37
|
+
|
|
38
|
+
async function req(ctx, path, { method = "POST", body } = {}) {
|
|
39
|
+
const res = await fetch(API + path, {
|
|
40
|
+
method,
|
|
41
|
+
headers: {
|
|
42
|
+
Authorization: `Bearer ${ctx.token}`,
|
|
43
|
+
"wix-site-id": ctx.siteId,
|
|
44
|
+
"Content-Type": "application/json",
|
|
45
|
+
},
|
|
46
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
47
|
+
});
|
|
48
|
+
const json = await res.json().catch(() => ({}));
|
|
49
|
+
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}: ${JSON.stringify(json).slice(0, 400)}`);
|
|
50
|
+
return json;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Install Wix Rentals. Idempotent. Rentals provisions the Bookings infrastructure it runs on, so a
|
|
55
|
+
* rental's availability comes from a Rentals-only install — Wix Bookings does not need to be added.
|
|
56
|
+
*/
|
|
57
|
+
async function installRentalsApp(ctx) {
|
|
58
|
+
try {
|
|
59
|
+
await req(ctx, "/apps-installer-service/v1/app-instance/install", {
|
|
60
|
+
body: {
|
|
61
|
+
tenant: { tenantType: "SITE", id: ctx.siteId },
|
|
62
|
+
appInstance: { appDefId: RENTALS_APP_ID, enabled: true },
|
|
63
|
+
},
|
|
64
|
+
});
|
|
65
|
+
} catch {
|
|
66
|
+
/* already installed is fine */
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A resource TYPE — the kind of thing being rented ("Meeting rooms", "Vans", "Cameras").
|
|
72
|
+
* A rental service points at one of these; the individual rentable items live inside it.
|
|
73
|
+
* @returns {Promise<{ id: string, name: string }>}
|
|
74
|
+
*/
|
|
75
|
+
async function createResourceType(ctx, name) {
|
|
76
|
+
const res = await req(ctx, "/bookings/v2/resources/resource-types", { body: { resourceType: { name } } });
|
|
77
|
+
const type = res?.resourceType ?? {};
|
|
78
|
+
return { id: type.id ?? type._id, name: type.name ?? name };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Existing resource types, so a re-run reuses one instead of making a duplicate. */
|
|
82
|
+
async function queryResourceTypes(ctx) {
|
|
83
|
+
const res = await req(ctx, "/bookings/v2/resources/resource-types/query", { body: { query: {} } });
|
|
84
|
+
return (res?.resourceTypes ?? []).map((t) => ({ id: t.id ?? t._id, name: t.name }));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Get a resource type by name, creating it only if it isn't there. Additive. */
|
|
88
|
+
async function ensureResourceType(ctx, name) {
|
|
89
|
+
const existing = (await queryResourceTypes(ctx)).find((t) => t.name === name);
|
|
90
|
+
return existing ?? createResourceType(ctx, name);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The individual rentable items inside a type — Room A, Room B, Van 1.
|
|
95
|
+
*
|
|
96
|
+
* ⚠️ PARALLEL CAPACITY COMES FROM MORE RESOURCES, not from a capacity number: two rooms that
|
|
97
|
+
* can be booked at the same time are two resources.
|
|
98
|
+
*
|
|
99
|
+
* ⚠️ Seeded 24/7 on purpose — no `workingHoursSchedules`. A resource with working hours makes a
|
|
100
|
+
* multi-day rental span several bookable windows, which Wix then models as a multi-service
|
|
101
|
+
* group booking instead of one booking. Pass `workingHours` only when the brief names opening
|
|
102
|
+
* hours and you accept that.
|
|
103
|
+
* @returns {Promise<{ id: string, name: string }[]>}
|
|
104
|
+
*/
|
|
105
|
+
async function createResources(ctx, resourceTypeId, names, { workingHours } = {}) {
|
|
106
|
+
const out = [];
|
|
107
|
+
for (const name of names) {
|
|
108
|
+
const res = await req(ctx, "/bookings/v2/resources", {
|
|
109
|
+
body: {
|
|
110
|
+
resource: {
|
|
111
|
+
name,
|
|
112
|
+
// Link the resource to its type with a top-level `typeId` (a bare GUID).
|
|
113
|
+
typeId: resourceTypeId,
|
|
114
|
+
...(workingHours ? { workingHoursSchedules: workingHours } : {}),
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
const r = res?.resource ?? {};
|
|
119
|
+
out.push({ id: r.id ?? r._id, name: r.name ?? name });
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** The Wix Rentals default booking form. Provisioned by the Rentals app; the id is the same on every site. */
|
|
125
|
+
const RENTALS_FORM_ID = "3a2ea2ce-91f4-4617-ab24-629933c0c31a";
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build one rental service payload.
|
|
129
|
+
*
|
|
130
|
+
* Field values that MAKE it a rental (all required together):
|
|
131
|
+
* type: "APPOINTMENT" — rentals are always appointment-typed
|
|
132
|
+
* appId: RENTALS_APP_ID — immutable after create
|
|
133
|
+
* durationRange — replaces sessionDurations; the two are mutually exclusive
|
|
134
|
+
* serviceResources + — which resource type AND which resources supply availability
|
|
135
|
+
* primaryResourceType
|
|
136
|
+
*
|
|
137
|
+
* Hourly bounds are MINUTES (30–1440); daily bounds are DAYS (1–8). One service = one unit
|
|
138
|
+
* type: to rent the same room by the hour AND by the day, create two services.
|
|
139
|
+
* @param {object} s spec + `resourceTypeId` and `resourceIds` (the ids from createResources).
|
|
140
|
+
*/
|
|
141
|
+
function buildRental(s) {
|
|
142
|
+
const unit = s.unit === "DAY" ? "DAY" : "HOUR";
|
|
143
|
+
const durationRange =
|
|
144
|
+
unit === "DAY"
|
|
145
|
+
? { unitType: "DAY", dayOptions: { minDurationInDays: s.min ?? 1, maxDurationInDays: s.max ?? 5 } }
|
|
146
|
+
: { unitType: "HOUR", hourOptions: { minDurationInMinutes: s.min ?? 60, maxDurationInMinutes: s.max ?? 480 } };
|
|
147
|
+
|
|
148
|
+
return {
|
|
149
|
+
type: "APPOINTMENT",
|
|
150
|
+
appId: RENTALS_APP_ID,
|
|
151
|
+
name: s.name,
|
|
152
|
+
description: s.description ?? "",
|
|
153
|
+
...(s.tagLine ? { tagLine: s.tagLine } : {}),
|
|
154
|
+
hidden: false,
|
|
155
|
+
defaultCapacity: 1,
|
|
156
|
+
// Use the Wix Rentals default booking form so checkout collects contact details.
|
|
157
|
+
form: { id: RENTALS_FORM_ID },
|
|
158
|
+
// Make the rental bookable online.
|
|
159
|
+
onlineBooking: { enabled: true, requireManualApproval: false, allowMultipleRequests: false },
|
|
160
|
+
// A rental is priced as a RATE per unit of time — the number below is per hour or per day,
|
|
161
|
+
// and Wix multiplies it by the length the customer picks.
|
|
162
|
+
payment: {
|
|
163
|
+
rateType: "FIXED",
|
|
164
|
+
fixed: { price: { value: String(s.price ?? 0), currency: s.currency ?? "USD" } },
|
|
165
|
+
options: { online: true, inPerson: false },
|
|
166
|
+
},
|
|
167
|
+
schedule: { availabilityConstraints: { durationRange } },
|
|
168
|
+
// Name the resource type AND its concrete resource ids — availability comes from the listed ids.
|
|
169
|
+
serviceResources: [{ resourceType: { id: s.resourceTypeId }, resourceIds: { values: s.resourceIds ?? [] } }],
|
|
170
|
+
// The resource type as a bare GUID string, and one of the types named in serviceResources above.
|
|
171
|
+
primaryResourceType: s.resourceTypeId,
|
|
172
|
+
locations: [{ type: "BUSINESS" }],
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Create rental services. Additive — an existing service with the same name is left alone.
|
|
178
|
+
* @returns {Promise<{ id: string, name: string, unit: string, revision: string }[]>}
|
|
179
|
+
*/
|
|
180
|
+
async function createRentals(ctx, rentals) {
|
|
181
|
+
const existing = await req(ctx, "/bookings/v2/services/query", {
|
|
182
|
+
body: { query: { filter: { appId: RENTALS_APP_ID } } },
|
|
183
|
+
}).catch(() => ({ services: [] }));
|
|
184
|
+
const byName = new Map((existing.services ?? []).map((s) => [s.name, s]));
|
|
185
|
+
|
|
186
|
+
const out = [];
|
|
187
|
+
for (const spec of rentals) {
|
|
188
|
+
const already = byName.get(spec.name);
|
|
189
|
+
if (already) {
|
|
190
|
+
out.push({
|
|
191
|
+
id: already.id ?? already._id,
|
|
192
|
+
name: already.name,
|
|
193
|
+
unit: already.schedule?.availabilityConstraints?.durationRange?.unitType ?? null,
|
|
194
|
+
revision: already.revision,
|
|
195
|
+
created: false,
|
|
196
|
+
});
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
const res = await req(ctx, "/bookings/v2/services", { body: { service: buildRental(spec) } });
|
|
200
|
+
const svc = res?.service ?? {};
|
|
201
|
+
out.push({
|
|
202
|
+
id: svc.id ?? svc._id,
|
|
203
|
+
name: svc.name ?? spec.name,
|
|
204
|
+
unit: svc.schedule?.availabilityConstraints?.durationRange?.unitType ?? null,
|
|
205
|
+
revision: svc.revision,
|
|
206
|
+
created: true,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Read the rentals back and confirm each one is actually a rental. A service created without
|
|
214
|
+
* `appId` or without `durationRange` still returns 200 — it is simply a plain Bookings service
|
|
215
|
+
* from then on, permanently, and this is the only check that catches it.
|
|
216
|
+
* @returns {Promise<{ ok: boolean, problems: string[] }>}
|
|
217
|
+
*/
|
|
218
|
+
async function verifyRentals(ctx, expectedNames) {
|
|
219
|
+
const res = await req(ctx, "/bookings/v2/services/query", {
|
|
220
|
+
body: { query: { filter: { appId: RENTALS_APP_ID } } },
|
|
221
|
+
});
|
|
222
|
+
const live = new Map((res?.services ?? []).map((s) => [s.name, s]));
|
|
223
|
+
const problems = [];
|
|
224
|
+
for (const name of expectedNames) {
|
|
225
|
+
const svc = live.get(name);
|
|
226
|
+
if (!svc) {
|
|
227
|
+
problems.push(`${name}: not returned by an appId-filtered query — it was not created as a rental`);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (!svc.schedule?.availabilityConstraints?.durationRange) {
|
|
231
|
+
problems.push(`${name}: no durationRange — created as a plain service, and appId is immutable`);
|
|
232
|
+
}
|
|
233
|
+
if (!svc.primaryResourceType) problems.push(`${name}: no primaryResourceType — availability will be empty`);
|
|
234
|
+
if (!svc.serviceResources?.some((sr) => sr.resourceIds?.values?.length))
|
|
235
|
+
problems.push(`${name}: serviceResources carry no resourceIds — availability will be empty`);
|
|
236
|
+
}
|
|
237
|
+
return { ok: problems.length === 0, problems };
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* The whole rentals seed, in the one order that works.
|
|
242
|
+
* @param {object} ctx
|
|
243
|
+
* @param {{ resourceTypeName: string, resources: string[], rentals: object[] }} plan
|
|
244
|
+
*/
|
|
245
|
+
async function setupRentals(ctx, { resourceTypeName, resources = [], rentals = [] }) {
|
|
246
|
+
await installRentalsApp(ctx);
|
|
247
|
+
const type = await ensureResourceType(ctx, resourceTypeName);
|
|
248
|
+
const created = await createResources(ctx, type.id, resources);
|
|
249
|
+
// Every rental service must list the concrete resource ids, or its availability is empty (see buildRental).
|
|
250
|
+
const resourceIds = created.map((c) => c.id);
|
|
251
|
+
const services = await createRentals(ctx, rentals.map((r) => ({ ...r, resourceTypeId: type.id, resourceIds })));
|
|
252
|
+
const check = await verifyRentals(ctx, rentals.map((r) => r.name));
|
|
253
|
+
return { resourceType: type, resources: created, services, ...check };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
module.exports = {
|
|
257
|
+
RENTALS_APP_ID,
|
|
258
|
+
installRentalsApp,
|
|
259
|
+
createResourceType,
|
|
260
|
+
queryResourceTypes,
|
|
261
|
+
ensureResourceType,
|
|
262
|
+
createResources,
|
|
263
|
+
buildRental,
|
|
264
|
+
createRentals,
|
|
265
|
+
verifyRentals,
|
|
266
|
+
setupRentals,
|
|
267
|
+
};
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Unlike the other verticals, CMS ships **no UI**. A CMS is schema-driven — every collection is
|
|
4
4
|
different — so there are no meaningful "list/detail" components to hand you. You **build the UI
|
|
5
|
-
yourself** (list, detail, home
|
|
6
|
-
**utils**.
|
|
7
|
-
|
|
5
|
+
yourself** (list, detail, home) in your own framework and design tokens, calling the shipped
|
|
6
|
+
**utils**. **A visitor-fillable form is the `forms` vertical, not `cms`** — come here only when the
|
|
7
|
+
app must read the entries back (a public gallery, a listing, a member's "my submissions"); a visitor
|
|
8
|
+
cannot read Forms submissions. Everything talks to Wix directly over the public `WIX_CLIENT_ID`
|
|
9
|
+
(anonymous visitor token) using the official Wix Data endpoints — never hand-build a Wix Data URL,
|
|
10
|
+
never mock items.
|
|
8
11
|
|
|
9
12
|
## What ships (utils only)
|
|
10
13
|
|