@wix/agent-skills 1.15.0 → 1.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +4 -1
- package/skills/wix-app/references/APP_TOOLS.md +134 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +1 -1
- package/skills/wix-app/references/DASHBOARD_PAGE.md +0 -10
- package/skills/wix-app/references/DATA_COLLECTION.md +6 -4
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +28 -7
- package/skills/wix-app/references/SERVICE_PLUGIN.md +12 -10
- package/skills/wix-app/references/STORES_VERSIONING.md +9 -211
- package/skills/wix-app/references/app-tools/TOOLS.md +75 -0
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +5 -0
- package/skills/wix-app/references/dashboard-page/DASHBOARD_API.md +2 -2
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW-REFERENCE.md +172 -0
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +138 -0
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +172 -0
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +29 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +1 -1
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +57 -0
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +33 -2
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +5 -3
- package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +18 -1
- package/skills/wix-app/references/editor-react-component/PARTS.md +15 -2
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +4 -3
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +13 -9
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +4 -0
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +3 -1
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +4 -0
- package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +54 -0
- package/skills/wix-app/references/stores/CATEGORIES.md +22 -0
- package/skills/wix-app/references/stores/GET_PRODUCT.md +99 -0
- package/skills/wix-app/references/stores/INVENTORY.md +37 -0
- package/skills/wix-app/references/stores/QUERY.md +119 -0
- package/skills/wix-app/references/stores/WRITE.md +55 -0
- package/skills/wix-docs/SKILL.md +35 -3
- package/skills/wix-headless/SKILL.md +9 -2
- package/skills/wix-headless/entry/bootstrap.mjs +2 -2
- package/skills/wix-headless/entry/skill.md +0 -2
- package/skills/wix-headless/references/CAPABILITIES.md +6 -4
- package/skills/wix-headless/references/CONTENT.md +36 -0
- package/skills/wix-headless/references/DESIGN.md +54 -0
- package/skills/wix-headless/references/DISCOVERY.md +25 -1
- package/skills/wix-headless/references/FEEDBACK.md +16 -5
- package/skills/wix-headless/references/IMAGE_GENERATION.md +10 -0
- package/skills/wix-headless/references/SDK_HANDOFF.md +2 -0
- package/skills/wix-headless/references/SEED.md +3 -1
- package/skills/wix-headless/references/SETUP.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +3 -2
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +42 -2
- package/skills/wix-headless/references/inline-recipes/setup-bookings.md +5 -1
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +1 -1
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +33 -8
- package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +3 -1
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-experiences.md +73 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +38 -2
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +20 -1
- package/skills/wix-headless/references/inline-recipes/setup-restaurants.md +1 -1
- package/skills/wix-headless/references/managed/CONNECT.md +2 -2
- package/skills/wix-headless/references/managed/CREATE.md +15 -1
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +47 -0
- package/skills/wix-headless/references/self-managed/DEPLOYMENT.md +8 -0
- package/skills/wix-headless/references/stripe/DEPLOYMENT.md +8 -0
- package/skills/wix-manage/SKILL.md +131 -12
- package/skills/wix-manage/references/analytics/analytics-dashboard-navigation.md +41 -0
- package/skills/wix-manage/references/analytics/query-site-analytics.md +66 -9
- package/skills/wix-manage/references/app-installation/app-installation-dashboard-navigation.md +28 -0
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -0
- package/skills/wix-manage/references/blog/blog-dashboard-navigation.md +56 -0
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +31 -6
- package/skills/wix-manage/references/bookings/booking-service-policy-setup.md +3 -1
- package/skills/wix-manage/references/bookings/bookings-dashboard-navigation.md +63 -0
- package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +6 -5
- package/skills/wix-manage/references/bookings/create-course-service.md +30 -5
- package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +85 -42
- package/skills/wix-manage/references/cms/cms-dashboard-navigation.md +44 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +31 -3
- package/skills/wix-manage/references/cms/cms-publishing-flow.md +287 -0
- package/skills/wix-manage/references/cms/cms-schema-management.md +33 -1
- package/skills/wix-manage/references/contacts/contacts-dashboard-navigation.md +46 -0
- package/skills/wix-manage/references/contacts/create-a-contact.md +82 -0
- package/skills/wix-manage/references/contacts/update-a-contact.md +119 -0
- package/skills/wix-manage/references/dashboard-navigation/dashboard-navigation.md +82 -0
- package/skills/wix-manage/references/dashboard-navigation/routes.json +1733 -0
- package/skills/wix-manage/references/domains/domains-dashboard-navigation.md +32 -0
- package/skills/wix-manage/references/ecommerce/ecom-pricing.md +4 -4
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
- package/skills/wix-manage/references/ecommerce/gift-cards/ecom-gift-cards-goal-sell-gift-cards.md +338 -0
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-discount-rule.md +42 -78
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-bundle-and-save.md +1 -1
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-stock-mover.md +1 -1
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-flow-upsell-boost.md +1 -1
- package/skills/wix-manage/references/ecommerce/recommend-ecommerce-strategy.md +90 -92
- package/skills/wix-manage/references/events/create-wix-event.md +176 -0
- package/skills/wix-manage/references/events/manage-wix-events.md +174 -0
- package/skills/wix-manage/references/forms/forms-dashboard-navigation.md +44 -0
- package/skills/wix-manage/references/get-paid/get-paid-dashboard-navigation.md +52 -0
- package/skills/wix-manage/references/get-paid/how-to-setup-wix-payments.md +30 -7
- package/skills/wix-manage/references/google-ads/billing-and-payment.md +80 -0
- package/skills/wix-manage/references/google-ads/create-performance-max-campaign.md +212 -0
- package/skills/wix-manage/references/google-ads/get-campaign-suggestions.md +110 -0
- package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +25 -0
- package/skills/wix-manage/references/google-ads/install-and-create-account.md +155 -0
- package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +63 -0
- package/skills/wix-manage/references/google-ads/query-campaign-analytics.md +155 -0
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +225 -144
- package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +19 -10
- package/skills/wix-manage/references/marketing/marketing-dashboard-navigation.md +52 -0
- package/skills/wix-manage/references/pricing-plans/create-and-update-pricing-plans.md +99 -1
- package/skills/wix-manage/references/pricing-plans/pricing-plans-dashboard-navigation.md +45 -0
- package/skills/wix-manage/references/restaurants/restaurants-dashboard-navigation.md +68 -0
- package/skills/wix-manage/references/restaurants/wix-restaurants-setup.md +94 -125
- package/skills/wix-manage/references/site-properties/change-payment-currency-site-properties.md +48 -9
- package/skills/wix-manage/references/site-properties/site-properties-dashboard-navigation.md +31 -0
- package/skills/wix-manage/references/sites/create-headless-site.md +52 -0
- package/skills/wix-manage/references/sites/create-site-from-template.md +2 -25
- package/skills/wix-manage/references/sites/site-import.md +243 -0
- package/skills/wix-manage/references/sites/sites-dashboard-navigation.md +29 -0
- package/skills/wix-manage/references/stores/add-store-pages-to-site.md +17 -1
- package/skills/wix-manage/references/stores/create-product-from-image.md +8 -6
- package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +16 -10
- package/skills/wix-manage/references/stores/find-products-query-and-search-catalog-v3.md +26 -8
- package/skills/wix-manage/references/stores/stores-dashboard-navigation.md +65 -0
- package/skills/wix-manage/references/stores/update-product-with-options.md +39 -7
- package/skills/wix-replatform/.publish-manifest.json +8 -0
- package/skills/wix-replatform/SKILL.md +693 -0
- package/skills/wix-replatform/lib/artifact-freshness.js +215 -0
- package/skills/wix-replatform/lib/completion-report.js +200 -0
- package/skills/wix-replatform/lib/config-env.js +59 -0
- package/skills/wix-replatform/lib/execution-manifest.js +185 -0
- package/skills/wix-replatform/lib/execution-state.js +173 -0
- package/skills/wix-replatform/lib/import-recovery.js +313 -0
- package/skills/wix-replatform/lib/local-state.js +642 -0
- package/skills/wix-replatform/lib/orchestration-preflight.js +143 -0
- package/skills/wix-replatform/lib/orchestration-router.js +122 -0
- package/skills/wix-replatform/lib/orchestration-state.js +455 -0
- package/skills/wix-replatform/lib/progress-log.js +224 -0
- package/skills/wix-replatform/lib/progress-log.sh +140 -0
- package/skills/wix-replatform/lib/url-preservation-state.js +391 -0
- package/skills/wix-replatform/resources/rp-discovery/SKILL.md +184 -0
- package/skills/wix-replatform/resources/rp-execute-import/SKILL.md +529 -0
- package/skills/wix-replatform/resources/rp-execute-setup/SKILL.md +316 -0
- package/skills/wix-replatform/resources/rp-execute-setup/scripts/mint-token.sh +64 -0
- package/skills/wix-replatform/resources/rp-import-codegen/SKILL.md +1114 -0
- package/skills/wix-replatform/resources/rp-mapper/SKILL.md +430 -0
- package/skills/wix-replatform/resources/rp-mapper/lib/mapping-resolve.js +217 -0
- package/skills/wix-replatform/resources/rp-mapper/scripts/resolve-mapping.js +148 -0
- package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +378 -0
- package/skills/wix-replatform/resources/rp-setup-discovery/SKILL.md +183 -0
- package/skills/wix-replatform/resources/rp-source-csv/SKILL.md +478 -0
- package/skills/wix-replatform/resources/rp-source-csv/lib/canonical-record.js +136 -0
- package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fileset.js +0 -0
- package/skills/wix-replatform/resources/rp-source-csv/lib/csv-fingerprint.js +310 -0
- package/skills/wix-replatform/resources/rp-source-csv/lib/csv-layout.js +1081 -0
- package/skills/wix-replatform/resources/rp-source-csv/lib/csv-parse.js +618 -0
- package/skills/wix-replatform/resources/rp-source-csv/scripts/csv-discovery.js +949 -0
- package/skills/wix-replatform/resources/rp-source-csv/source-schema.example.json +196 -0
- package/skills/wix-replatform/resources/rp-source-csv/vendors/bigcommerce.json +66 -0
- package/skills/wix-replatform/resources/rp-source-csv/vendors/magento.json +60 -0
- package/skills/wix-replatform/resources/rp-source-csv/vendors/shopify.json +95 -0
- package/skills/wix-replatform/resources/rp-source-csv/vendors/woocommerce.json +82 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/SKILL.md +256 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-http.js +249 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/lib/wp-route-classifier.js +444 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/scripts/wp-discovery.js +774 -0
- package/skills/wix-replatform/resources/rp-source-wordpress/source-schema.example.json +71 -0
- package/skills/wix-replatform/resources/rp-target-wix/SKILL.md +632 -0
- package/skills/wix-replatform/resources/rp-target-wix/SPEC-product-reviews-and-blog-authorship.md +330 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/README.md +21 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/domain.json +19 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/category.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/comment.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/post.json +39 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/blog/entities/tag.json +35 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/domain.json +14 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/collection.json +35 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/data-item.json +36 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/cms/entities/reference.json +35 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/domain.json +14 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/contact.json +131 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/email-subscription.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/crm/entities/label.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/domain.json +21 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-line-item.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order-refund.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/ecom/entities/order.json +97 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/index.json +528 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/domain.json +14 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-file.json +38 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/media/entities/media-folder.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/domain.json +13 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/badge.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/members/entities/member.json +53 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/schema.json +84 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/domain.json +25 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/category.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/coupon.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/inventory-item.json +38 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product-tag.json +37 -0
- package/skills/wix-replatform/resources/rp-target-wix/domains/stores/entities/product.json +90 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/audit-summary.js +64 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/contract-ledger.js +141 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/domain-knowledge.js +384 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/ndjson.js +260 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/stores-verification.js +238 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-build.js +237 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-target-spec.js +238 -0
- package/skills/wix-replatform/resources/rp-target-wix/lib/wix-writers.js +2219 -0
- package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge-validate.js +31 -0
- package/skills/wix-replatform/resources/rp-target-wix/scripts/domain-knowledge.js +100 -0
- package/skills/wix-replatform/resources/rp-target-wix/scripts/verify-stores.js +107 -0
- package/skills/wix-replatform/resources/rp-telemetry/SKILL.md +344 -0
- package/skills/wix-replatform/resources/rp-telemetry/lib/bi-sink.js +250 -0
- package/skills/wix-replatform/resources/rp-telemetry/lib/telemetry-recorder.js +1927 -0
- package/skills/wix-replatform/resources/rp-telemetry/scripts/meter-from-audit.js +83 -0
- package/skills/wix-replatform/resources/rp-telemetry/scripts/rp-telemetry.js +141 -0
- package/skills/wix-replatform/scripts/artifact-freshness.js +76 -0
- package/skills/wix-replatform/scripts/execution-manifest-validate.js +43 -0
- package/skills/wix-replatform/scripts/execution-state-prepare.js +128 -0
- package/skills/wix-replatform/scripts/orchestration-preflight.js +48 -0
- package/skills/wix-replatform/scripts/orchestration-route.js +46 -0
- package/skills/wix-replatform/scripts/orchestration-state.js +245 -0
- package/skills/wix-vibe-headless/SKILL.md +54 -25
- package/skills/wix-vibe-headless/install/deploy.cjs +95 -0
- package/skills/wix-vibe-headless/install/pin-agents-md.cjs +34 -0
- package/skills/wix-vibe-headless/platforms/README.md +26 -0
- package/skills/wix-vibe-headless/platforms/base44.md +177 -0
- package/skills/wix-vibe-headless/platforms/generic.md +122 -0
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +230 -119
- package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +43 -0
- package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +25 -0
- package/skills/wix-vibe-headless/references/blog/app/components/PostGrid.jsx +15 -0
- package/skills/wix-vibe-headless/references/blog/app/context/TaxonomyContext.jsx +35 -0
- package/skills/wix-vibe-headless/references/blog/app/hooks/usePostDetail.js +38 -0
- package/skills/wix-vibe-headless/references/blog/app/pages/Blog.jsx +40 -0
- package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +54 -0
- package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +65 -0
- package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +50 -0
- package/skills/wix-vibe-headless/references/blog/seed/SEED.md +103 -0
- package/skills/wix-vibe-headless/references/blog/seed/seed-blog.js +270 -0
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +260 -145
- package/skills/wix-vibe-headless/references/bookings/app/components/BookingForm.jsx +65 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +47 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/ServiceGrid.jsx +13 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/SlotPicker.jsx +126 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js +108 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useServices.js +58 -0
- package/skills/wix-vibe-headless/references/bookings/app/lib/serviceFacts.js +95 -0
- package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +94 -0
- package/skills/wix-vibe-headless/references/bookings/app/pages/Services.jsx +89 -0
- package/skills/wix-vibe-headless/references/bookings/{wix-bookings-checkout.js → app/rest/wix-bookings-checkout.js} +1 -1
- package/skills/wix-vibe-headless/references/bookings/{wix-bookings-services.js → app/rest/wix-bookings-services.js} +90 -12
- package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +117 -0
- package/skills/wix-vibe-headless/references/bookings/seed/seed-bookings.js +333 -0
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +169 -147
- package/skills/wix-vibe-headless/references/cms/app/collection.config.js +31 -0
- package/skills/wix-vibe-headless/references/cms/app/lib/wixImage.js +18 -0
- package/skills/wix-vibe-headless/references/cms/{wix-cms.js → app/rest/wix-cms.js} +47 -2
- package/skills/wix-vibe-headless/references/cms/seed/SEED.md +145 -0
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +228 -131
- package/skills/wix-vibe-headless/references/events/app/components/CategoryFilter.jsx +23 -0
- package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +43 -0
- package/skills/wix-vibe-headless/references/events/app/components/EventGrid.jsx +13 -0
- package/skills/wix-vibe-headless/references/events/app/components/EventRegistration.jsx +29 -0
- package/skills/wix-vibe-headless/references/events/app/components/RsvpForm.jsx +57 -0
- package/skills/wix-vibe-headless/references/events/app/components/TicketPicker.jsx +78 -0
- package/skills/wix-vibe-headless/references/events/app/hooks/useEventDetail.js +27 -0
- package/skills/wix-vibe-headless/references/events/app/hooks/useEventsList.js +54 -0
- package/skills/wix-vibe-headless/references/events/app/hooks/useRsvpForm.js +40 -0
- package/skills/wix-vibe-headless/references/events/app/hooks/useTicketing.js +81 -0
- package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +48 -0
- package/skills/wix-vibe-headless/references/events/app/pages/Events.jsx +36 -0
- package/skills/wix-vibe-headless/references/events/{wix-events-browse.js → app/rest/wix-events-browse.js} +3 -2
- package/skills/wix-vibe-headless/references/events/seed/SEED.md +107 -0
- package/skills/wix-vibe-headless/references/events/seed/seed-events.js +251 -0
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +276 -188
- package/skills/wix-vibe-headless/references/members/app/components/LoginForm.jsx +101 -0
- package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +33 -0
- package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +21 -0
- package/skills/wix-vibe-headless/references/members/app/components/SocialButtons.jsx +29 -0
- package/skills/wix-vibe-headless/references/members/app/context/MemberContext.jsx +54 -0
- package/skills/wix-vibe-headless/references/members/app/hooks/useLoginForm.js +77 -0
- package/skills/wix-vibe-headless/references/members/app/pages/Account.jsx +36 -0
- package/skills/wix-vibe-headless/references/members/app/pages/Callback.jsx +32 -0
- package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +35 -0
- package/skills/wix-vibe-headless/references/members/{wix-members-auth.js → app/rest/wix-members-auth.js} +6 -3
- package/skills/wix-vibe-headless/references/members/install/Register.jsx +22 -0
- package/skills/wix-vibe-headless/references/members/seed/SEED.md +22 -0
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +233 -104
- package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +31 -0
- package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionGrid.jsx +15 -0
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +39 -0
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectGrid.jsx +15 -0
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectMedia.jsx +29 -0
- package/skills/wix-vibe-headless/references/portfolio/app/hooks/useCollectionProjects.js +35 -0
- package/skills/wix-vibe-headless/references/portfolio/app/hooks/usePortfolioGallery.js +27 -0
- package/skills/wix-vibe-headless/references/portfolio/app/hooks/useProjectDetail.js +25 -0
- package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +39 -0
- package/skills/wix-vibe-headless/references/portfolio/app/pages/Portfolio.jsx +27 -0
- package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +60 -0
- package/skills/wix-vibe-headless/references/portfolio/{wix-portfolio.js → app/rest/wix-portfolio.js} +84 -55
- package/skills/wix-vibe-headless/references/portfolio/seed/SEED.md +94 -0
- package/skills/wix-vibe-headless/references/portfolio/seed/seed-portfolio.js +236 -0
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +240 -117
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/OrderCard.jsx +35 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +48 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanGrid.jsx +16 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanPrice.jsx +35 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/useMyPlans.js +25 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/usePlanDetail.js +28 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/hooks/usePlans.js +30 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/MyPlans.jsx +30 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +53 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/Plans.jsx +28 -0
- package/skills/wix-vibe-headless/references/pricing-plans/seed/SEED.md +86 -0
- package/skills/wix-vibe-headless/references/pricing-plans/seed/seed-pricing-plans.js +251 -0
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +248 -124
- package/skills/wix-vibe-headless/references/restaurants/app/components/ItemDialog.jsx +81 -0
- package/skills/wix-vibe-headless/references/restaurants/app/components/MenuItemCard.jsx +71 -0
- package/skills/wix-vibe-headless/references/restaurants/app/components/MenuList.jsx +43 -0
- package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartButton.jsx +21 -0
- package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +60 -0
- package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +63 -0
- package/skills/wix-vibe-headless/references/restaurants/app/hooks/useItemOrder.js +32 -0
- package/skills/wix-vibe-headless/references/restaurants/app/hooks/useReservation.js +73 -0
- package/skills/wix-vibe-headless/references/restaurants/app/pages/Menu.jsx +28 -0
- package/skills/wix-vibe-headless/references/restaurants/app/pages/Reservations.jsx +102 -0
- package/skills/wix-vibe-headless/references/restaurants/seed/SEED.md +180 -0
- package/skills/wix-vibe-headless/references/restaurants/seed/seed-restaurants.js +452 -0
- package/skills/wix-vibe-headless/references/shared/app/components/WixManageBanner.jsx +56 -0
- package/skills/wix-vibe-headless/references/shared/{wix-client.js → app/rest/wix-client.js} +6 -8
- package/skills/wix-vibe-headless/references/shared/app/rest/wix-config.js +6 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +263 -148
- package/skills/wix-vibe-headless/references/storefront/app/components/CartButton.jsx +20 -0
- package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +150 -0
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductCard.jsx +167 -0
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGallery.jsx +51 -0
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGrid.jsx +54 -0
- package/skills/wix-vibe-headless/references/storefront/app/components/VariantPicker.jsx +119 -0
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +71 -0
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +109 -0
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useShop.js +98 -0
- package/skills/wix-vibe-headless/references/storefront/app/lib/storeImage.js +27 -0
- package/skills/wix-vibe-headless/references/storefront/app/pages/ProductDetail.jsx +139 -0
- package/skills/wix-vibe-headless/references/storefront/app/pages/Shop.jsx +75 -0
- package/skills/wix-vibe-headless/references/storefront/{wix-store-cart.js → app/rest/wix-store-cart.js} +1 -1
- package/skills/wix-vibe-headless/references/storefront/{wix-store-catalog.js → app/rest/wix-store-catalog.js} +25 -2
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +111 -0
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.js +299 -0
- /package/skills/wix-vibe-headless/references/blog/{wix-blog.js → app/rest/wix-blog.js} +0 -0
- /package/skills/wix-vibe-headless/references/events/{wix-events-registration.js → app/rest/wix-events-registration.js} +0 -0
- /package/skills/wix-vibe-headless/references/pricing-plans/{wix-pricing-plans.js → app/rest/wix-pricing-plans.js} +0 -0
- /package/skills/wix-vibe-headless/references/restaurants/{wix-restaurants-menu.js → app/rest/wix-restaurants-menu.js} +0 -0
- /package/skills/wix-vibe-headless/references/restaurants/{wix-restaurants-ordering.js → app/rest/wix-restaurants-ordering.js} +0 -0
- /package/skills/wix-vibe-headless/references/restaurants/{wix-restaurants-reservations.js → app/rest/wix-restaurants-reservations.js} +0 -0
|
@@ -9,25 +9,55 @@ import { wixApiRequest } from "./wix-client.js";
|
|
|
9
9
|
*
|
|
10
10
|
* id {string}, type "APPOINTMENT"|"CLASS"|"COURSE", name {string}, tagLine {string},
|
|
11
11
|
* description {string}, hidden {boolean},
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
12
|
+
* defaultCapacity {number} — bookable seats per session; 1 for a typical appointment,
|
|
13
|
+
* media.mainMedia.image { id, url, width, height, altText } — the PRIMARY service image (what the
|
|
14
|
+
* seed sets and the card shows); media.items {array} is the gallery, media.coverMedia the cover.
|
|
15
|
+
* Read mainMedia first, fall back to items[0]/coverMedia. url may be a bare Wix media handle —
|
|
16
|
+
* pass through mediaUrl() before rendering,
|
|
17
|
+
* mainSlug.name {string} — merchant-facing slug ("swedish-massage"). This client routes on the
|
|
18
|
+
* service id; switching to slugs means changing the shipped route too,
|
|
19
|
+
* payment.rateType "FIXED"|"CUSTOM"|"VARIED"|"NO_FEE"|"SUBSCRIPTION" — branch on this before
|
|
20
|
+
* reading a price. Use servicePriceLabel() from lib/serviceFacts.js instead of payment.fixed
|
|
21
|
+
* directly, or VARIED and NO_FEE services render with no price,
|
|
22
|
+
* payment.fixed.price { value, currency, formattedValue } — value + currency are always
|
|
23
|
+
* present; formattedValue is optional (may be missing), so format the price from
|
|
24
|
+
* value+currency and use formattedValue only when present,
|
|
25
|
+
* payment.varied { defaultPrice, minPrice, maxPrice } — Money each, same shape as fixed.price,
|
|
26
|
+
* payment.options { online, inPerson, deposit, pricingPlan } — how a customer may PAY. `online`
|
|
27
|
+
* here means "can pay online", NOT that the service happens online; don't build a location
|
|
28
|
+
* label from it,
|
|
29
|
+
* conferencing.enabled {boolean} — a video service; this is the real "online" signal,
|
|
30
|
+
* schedule.id {string},
|
|
31
|
+
* schedule.availabilityConstraints.sessionDurations {number[]} — session minutes for an
|
|
32
|
+
* APPOINTMENT, empty for CLASS/COURSE; see serviceDuration(),
|
|
33
|
+
* onlineBooking { enabled, requireManualApproval } — requireManualApproval means the business
|
|
34
|
+
* confirms the booking after the fact, so the CTA is a request, not a confirmation,
|
|
35
|
+
* category { id, name } — a service with no category is not visible on the live site,
|
|
36
|
+
* staffMemberIds {string[]},
|
|
37
|
+
* locations {array} — [{ id, type "BUSINESS"|"CUSTOMER"|"CUSTOM", calculatedAddress }]. The type is
|
|
38
|
+
* where the service happens; calculatedAddress is empty for CUSTOMER and often absent until the
|
|
39
|
+
* business configures a location. See serviceLocationLabel(),
|
|
19
40
|
* bookingPolicy.participantsPolicy { enabled {boolean}, maxParticipantsPerBooking {number} }
|
|
20
41
|
* — the MOST participants a single booking may reserve. Cap the participant selector at this
|
|
21
42
|
* value (for a CLASS, also bound by the slot's remainingCapacity). It is commonly 1, in which
|
|
22
43
|
* case there is no participant choice at all — book exactly 1. Sending totalParticipants above
|
|
23
44
|
* this makes createBooking fail.
|
|
45
|
+
* bookingPolicy.reschedulePolicy / .cancellationPolicy { enabled, limitLatestReschedule /
|
|
46
|
+
* limitLatestCancellation, latestRescheduleInMinutes / latestCancellationInMinutes } — see
|
|
47
|
+
* serviceRescheduleNote(),
|
|
24
48
|
*
|
|
25
49
|
* TimeSlot (Time Slots V2, appointments):
|
|
26
50
|
* serviceId {string}, localStartDate {string}, localEndDate {string} — "YYYY-MM-DDThh:mm:ss" (no zone),
|
|
27
51
|
* bookable {boolean}, scheduleId {string},
|
|
28
52
|
* location { id, name, formattedAddress, locationType "BUSINESS"|"CUSTOM"|"CUSTOMER" },
|
|
29
53
|
* totalCapacity {number}, remainingCapacity {number},
|
|
30
|
-
*
|
|
54
|
+
* bookableCapacity {number} — CLASS slots: seats still bookable after policy limits, which can be
|
|
55
|
+
* lower than remainingCapacity,
|
|
56
|
+
* bookingPolicyViolations { tooEarlyToBook, tooLateToBook, bookOnlineDisabled } — why a listed slot
|
|
57
|
+
* still isn't bookable; only reachable if you drop the `bookable: true` filter,
|
|
58
|
+
* availableResources {array} — staff. getAvailableSlot populates it for an APPOINTMENT; a
|
|
59
|
+
* CLASS/COURSE slot already carries it from the list call,
|
|
60
|
+
* eventInfo { eventId, eventTitle } — CLASS/COURSE only; those slots have no scheduleId
|
|
31
61
|
* Full model: https://dev.wix.com/docs/api-reference/business-solutions/bookings/time-slots/time-slots-v2/list-availability-time-slots.md
|
|
32
62
|
*/
|
|
33
63
|
|
|
@@ -79,6 +109,48 @@ export async function queryServices({ limit = 100, offset = 0 } = {}) {
|
|
|
79
109
|
return { services, total, nextOffset: loaded < total ? loaded : null };
|
|
80
110
|
}
|
|
81
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Query visitor-visible services in one category, for a category-filtered listing.
|
|
114
|
+
* The filter key is the dotted path `category.id` — a bare `categoryId` is rejected as an unknown
|
|
115
|
+
* field.
|
|
116
|
+
* @param {string} categoryId
|
|
117
|
+
* @param {{ limit?: number, offset?: number }} [options]
|
|
118
|
+
* @returns {Promise<{ services: object[], total: number, nextOffset: number|null }>}
|
|
119
|
+
*/
|
|
120
|
+
export async function queryServicesByCategory(categoryId, { limit = 100, offset = 0 } = {}) {
|
|
121
|
+
const res = await wixApiRequest("/bookings/v2/services/query", {
|
|
122
|
+
method: "POST",
|
|
123
|
+
body: {
|
|
124
|
+
query: {
|
|
125
|
+
filter: { hidden: false, "category.id": categoryId },
|
|
126
|
+
paging: { limit, offset },
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const services = res?.services ?? [];
|
|
131
|
+
const total = res?.pagingMetadata?.total ?? services.length;
|
|
132
|
+
const loaded = offset + services.length;
|
|
133
|
+
return { services, total, nextOffset: loaded < total ? loaded : null };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The categories that visitor-visible services actually belong to, in the order Wix sorts them.
|
|
138
|
+
* Derived from a services page rather than the Categories API, so the menu can never list a category
|
|
139
|
+
* with nothing bookable in it.
|
|
140
|
+
* @param {object[]} services
|
|
141
|
+
* @returns {{ id: string, name: string }[]}
|
|
142
|
+
*/
|
|
143
|
+
export function categoriesOf(services) {
|
|
144
|
+
const byId = new Map();
|
|
145
|
+
for (const s of services || []) {
|
|
146
|
+
const c = s?.category;
|
|
147
|
+
if (c?.id && !byId.has(c.id)) byId.set(c.id, { id: c.id, name: c.name, sortOrder: c.sortOrder ?? 0 });
|
|
148
|
+
}
|
|
149
|
+
return [...byId.values()]
|
|
150
|
+
.sort((a, b) => a.sortOrder - b.sortOrder)
|
|
151
|
+
.map(({ id, name }) => ({ id, name }));
|
|
152
|
+
}
|
|
153
|
+
|
|
82
154
|
/**
|
|
83
155
|
* Fetch a single service by its GUID. Returns null if not found.
|
|
84
156
|
* @param {string} serviceId
|
|
@@ -113,7 +185,7 @@ export async function countServices() {
|
|
|
113
185
|
* https://dev.wix.com/docs/api-reference/business-solutions/bookings/time-slots/time-slots-v2/list-availability-time-slots.md
|
|
114
186
|
* @param {string} serviceId
|
|
115
187
|
* @param {{ fromLocalDate: string, toLocalDate: string, timeZone?: string, limit?: number, cursor?: string }} options
|
|
116
|
-
* @returns {Promise<{ slots: object[], nextCursor: string|null }>}
|
|
188
|
+
* @returns {Promise<{ slots: object[], nextCursor: string|null, timeZone: string|null }>}
|
|
117
189
|
*/
|
|
118
190
|
export async function listAvailableSlots(serviceId, { fromLocalDate, toLocalDate, timeZone, limit = 1000, cursor } = {}) {
|
|
119
191
|
if (!cursor && (!fromLocalDate || !toLocalDate)) {
|
|
@@ -133,6 +205,9 @@ export async function listAvailableSlots(serviceId, { fromLocalDate, toLocalDate
|
|
|
133
205
|
return {
|
|
134
206
|
slots: res?.timeSlots ?? [],
|
|
135
207
|
nextCursor: res?.cursorPagingMetadata?.cursors?.next ?? null,
|
|
208
|
+
// The zone the returned localStartDate strings are in — label the picker with this, so the times
|
|
209
|
+
// on screen and the zone named beside them can't disagree.
|
|
210
|
+
timeZone: res?.timeZone ?? null,
|
|
136
211
|
};
|
|
137
212
|
}
|
|
138
213
|
|
|
@@ -171,7 +246,7 @@ export async function getAvailableSlot(serviceId, { localStartDate, localEndDate
|
|
|
171
246
|
* https://dev.wix.com/docs/api-reference/business-solutions/bookings/time-slots/time-slots-v2/list-event-time-slots.md
|
|
172
247
|
* @param {string|string[]} serviceIds One service id or several (the endpoint takes a plural list).
|
|
173
248
|
* @param {{ fromLocalDate: string, toLocalDate: string, timeZone?: string, limit?: number, cursor?: string }} options
|
|
174
|
-
* @returns {Promise<{ slots: object[], nextCursor: string|null }>}
|
|
249
|
+
* @returns {Promise<{ slots: object[], nextCursor: string|null, timeZone: string|null }>}
|
|
175
250
|
*/
|
|
176
251
|
export async function listEventTimeSlots(serviceIds, { fromLocalDate, toLocalDate, timeZone, limit = 1000, cursor } = {}) {
|
|
177
252
|
if (!cursor && (!fromLocalDate || !toLocalDate)) {
|
|
@@ -191,7 +266,10 @@ export async function listEventTimeSlots(serviceIds, { fromLocalDate, toLocalDat
|
|
|
191
266
|
const res = await wixApiRequest("/_api/service-availability/v2/time-slots/event", { method: "POST", body });
|
|
192
267
|
return {
|
|
193
268
|
slots: res?.timeSlots ?? [],
|
|
194
|
-
|
|
269
|
+
// This endpoint nests the cursor under `pagingMetadata`, where the appointment one uses
|
|
270
|
+
// `cursorPagingMetadata` — reading only the latter silently disables paging for classes.
|
|
271
|
+
nextCursor: res?.pagingMetadata?.cursors?.next ?? res?.cursorPagingMetadata?.cursors?.next ?? null,
|
|
272
|
+
timeZone: res?.timeZone ?? null,
|
|
195
273
|
};
|
|
196
274
|
}
|
|
197
275
|
|
|
@@ -204,7 +282,7 @@ export async function listEventTimeSlots(serviceIds, { fromLocalDate, toLocalDat
|
|
|
204
282
|
* here — the per-slot booking flow applies to APPOINTMENT and CLASS.
|
|
205
283
|
* @param {{ _id?: string, id?: string, type?: string }} service A service from queryServices.
|
|
206
284
|
* @param {{ fromLocalDate: string, toLocalDate: string, timeZone?: string, limit?: number, cursor?: string }} options
|
|
207
|
-
* @returns {Promise<{ slots: object[], nextCursor: string|null }>}
|
|
285
|
+
* @returns {Promise<{ slots: object[], nextCursor: string|null, timeZone: string|null }>}
|
|
208
286
|
*/
|
|
209
287
|
export async function listSlotsForService(service, options = {}) {
|
|
210
288
|
const serviceId = service?._id || service?.id;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Bookings — seeding
|
|
2
|
+
|
|
3
|
+
Seed a Wix Bookings catalog by **calling `seed-bookings.js`** — don't hand-write the REST calls.
|
|
4
|
+
It's a build-time module (run via `exec_tool`, not shipped in the app) that abstracts every Wix
|
|
5
|
+
Bookings seed operation. Load it and call **`setupBookings` — the one-call path** — with plain data.
|
|
6
|
+
|
|
7
|
+
> **Transcribed from `wix-headless/references/inline-recipes/setup-bookings.md` — NOT yet live-verified.**
|
|
8
|
+
|
|
9
|
+
```js
|
|
10
|
+
// build-time exec_tool
|
|
11
|
+
const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix"); // Base44 (generic: use $TOKEN)
|
|
12
|
+
const fs = require("fs");
|
|
13
|
+
// exec_tool's require can return EMPTY exports for these build-time modules — load the file itself:
|
|
14
|
+
const seed = (() => { const m = { exports: {} };
|
|
15
|
+
new Function("module", "exports", "require", fs.readFileSync("/app/.agents/skills/wix-vibe-headless/references/bookings/seed/seed-bookings.js", "utf8"))(m, m.exports, require);
|
|
16
|
+
return m.exports; })();
|
|
17
|
+
const ctx = { token: accessToken, siteId: WIX_METASITE_ID };
|
|
18
|
+
|
|
19
|
+
// ONE call: install → resolve staff → categories → services → CLASS sessions → images, all in the
|
|
20
|
+
// correct order with ids kept in memory (no hand-threading of scheduleId/resourceId). `category` is
|
|
21
|
+
// a NAME (resolved to an id internally). Free service: set free:true, omit price. CLASS `sessions`
|
|
22
|
+
// are local "YYYY-MM-DDThh:mm:ss".
|
|
23
|
+
const result = await seed.setupBookings(ctx, {
|
|
24
|
+
services: [
|
|
25
|
+
// `imageUrl` per service is optional and attached IN this one call. Pass a plain url — the module
|
|
26
|
+
// imports it into Wix Media for you (bookings binds a service image by the Wix Media file id, not a
|
|
27
|
+
// url). Use the FINAL https://media.base44.com/... url from the COMPLETED generate_image (it runs in
|
|
28
|
+
// the background while you build — wait for it), never a still-generating /__generating__/<id>.png.
|
|
29
|
+
{ type: "APPOINTMENT", name: "Consultation", description: "…", tagLine: "…", price: 75, duration: 60, category: "Our Services", imageUrl: "https://media.base44.com/…" },
|
|
30
|
+
{ type: "CLASS", name: "Morning Yoga", description: "…", price: 20, capacity: 20, category: "Our Services",
|
|
31
|
+
sessions: [{ start: "2026-08-10T09:00:00", end: "2026-08-10T10:00:00" }] },
|
|
32
|
+
],
|
|
33
|
+
// staffResourceId defaults to the fresh install's owner (queryStaff()[0].resourceId).
|
|
34
|
+
});
|
|
35
|
+
// result: { services:[...], categories:[{id,name}], resourceId, sessionsScheduled, imagesAttached }
|
|
36
|
+
|
|
37
|
+
// fallback (finer control / re-attach): import the url to Wix Media, then patch one service after the fact.
|
|
38
|
+
// const file = await seed.importImage(ctx, imageUrl); // → { id, url } (Wix Media file id + wixstatic url)
|
|
39
|
+
// await seed.attachServiceImage(ctx, { serviceId: s.id, revision: s.revision, image: { id: file.id, url: file.url, width: 1024, height: 1024 } });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Seeding is additive — never delete or overwrite existing content.** Don't clean up, don't remove
|
|
43
|
+
"sample" data, don't reset. Just add.
|
|
44
|
+
|
|
45
|
+
## What the shipped UI renders from this
|
|
46
|
+
|
|
47
|
+
Pass these and the catalog reads like a real one; omit them and the pages have visible holes:
|
|
48
|
+
|
|
49
|
+
- **`sessions` on every CLASS.** A class's bookable times ARE its sessions, so a class seeded without
|
|
50
|
+
them shows "No available times in this range" — which reads as broken, not empty. Schedule a few
|
|
51
|
+
weeks out. An APPOINTMENT needs none: its slots come from staff working hours, which a fresh
|
|
52
|
+
install's owner already has.
|
|
53
|
+
- **`duration`** (APPOINTMENT, minutes) → the card's `60 min · 1-to-1` line and the DURATION cell on
|
|
54
|
+
the service page. A class has no service-level duration and shows CAPACITY instead, so pass
|
|
55
|
+
`capacity` there.
|
|
56
|
+
- **`tagLine`** → the card's second line. One line about the service, not a copy of `description`.
|
|
57
|
+
- **`price`**, or **`free: true`** for a no-fee service, which the UI labels "Free".
|
|
58
|
+
|
|
59
|
+
Two things this module can't set, so don't try: **varied pricing** (a "From €85" service needs
|
|
60
|
+
`payment.varied`) and **`conferencing`** (the UI's "Online" location label reads that flag — seeded
|
|
61
|
+
services all sit at the business location, which shows as "In person").
|
|
62
|
+
|
|
63
|
+
## Escape hatch — individual functions
|
|
64
|
+
Reach for the functions below only when the one-call `setupBookings` doesn't fit (partial re-seed,
|
|
65
|
+
custom staff/ordering). `setupBookings` is built from them, in this order:
|
|
66
|
+
|
|
67
|
+
```js
|
|
68
|
+
await seed.installBookingsApp(ctx);
|
|
69
|
+
const staff = await seed.queryStaffWithRetry(ctx); // → [{resourceId,id,name}]; polls (fresh install provisions the owner async); use resourceId, NOT id
|
|
70
|
+
const cats = await seed.createCategories(ctx, ["Our Services"]); // → [{id,name}]; every service needs a categoryId
|
|
71
|
+
const services = await seed.createServices(ctx, [ // → [{id,slug,revision,type,scheduleId}]
|
|
72
|
+
{ type: "APPOINTMENT", name: "Consultation", price: 75, duration: 60, categoryId: cats[0].id, staffMemberIds: [staff[0].resourceId] },
|
|
73
|
+
{ type: "CLASS", name: "Morning Yoga", price: 20, capacity: 20, categoryId: cats[0].id },
|
|
74
|
+
]);
|
|
75
|
+
// CLASS only: schedule sessions with each class's returned scheduleId (local wall-clock times)
|
|
76
|
+
await seed.scheduleClassSessions(ctx, [
|
|
77
|
+
{ scheduleId: services[1].scheduleId, resourceId: staff[0].resourceId, start: "2026-08-10T09:00:00", end: "2026-08-10T10:00:00", capacity: 20 },
|
|
78
|
+
]);
|
|
79
|
+
// images (optional): import the url to Wix Media first (bookings binds by file id), then attach.
|
|
80
|
+
// Use the FINAL https://media.base44.com/... url only (never a /__generating__/ placeholder).
|
|
81
|
+
const file = await seed.importImage(ctx, imageUrl); // → { id, url } (Wix Media file id + wixstatic url)
|
|
82
|
+
await seed.attachServiceImage(ctx, { serviceId: services[0].id, revision: services[0].revision, image: { id: file.id, url: file.url, width: 1024, height: 1024 } });
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Functions
|
|
86
|
+
| fn | does |
|
|
87
|
+
|---|---|
|
|
88
|
+
| `setupBookings(ctx, {services, staffResourceId?})` | **one-call**: install → staff → categories → services → CLASS sessions → images |
|
|
89
|
+
| `installBookingsApp(ctx)` | install the Wix Bookings app on the site |
|
|
90
|
+
| `queryStaff(ctx)` | `[{resourceId,id,name}]` — one shot; use `resourceId`, not `id` |
|
|
91
|
+
| `queryStaffWithRetry(ctx)` | STEP 1 — same, but polls until the default owner provisions after a fresh install (avoids `MISSING_APPOINTMENT_RESOURCES`) |
|
|
92
|
+
| `createStaff(ctx, [{name,description}])` | create named staff → `[{resourceId,id,name}]` (omit email/phone) |
|
|
93
|
+
| `queryCategories(ctx)` | `[{id,name}]` — reuse a category created earlier this run |
|
|
94
|
+
| `createCategories(ctx, names)` | STEP 2 — every service needs a `category.id` or it's invisible → `[{id,name}]`; reuses an existing same-named category (idempotent, no dupes on re-run) |
|
|
95
|
+
| `createServices(ctx, services)` | STEP 3 — one bulk create (APPOINTMENT/CLASS mixed) → `[{id,slug,revision,type,scheduleId,success,error}]` |
|
|
96
|
+
| `scheduleClassSessions(ctx, sessions)` | STEP 4 (CLASS only) — one bulk Calendar-Events-V3 create → `[{id,success,error}]` |
|
|
97
|
+
| `getService(ctx, serviceId)` | fetch a service (current `revision`; confirm an image landed) |
|
|
98
|
+
| `importImage(ctx, url)` | import an external url into Wix Media → `{id,url}` (file id + wixstatic url); bookings binds by this file id |
|
|
99
|
+
| `attachServiceImage(ctx, {serviceId,revision,image})` | optional — patch `media.mainMedia`/`coverMedia` (revision-checked); `image.id` MUST be a Wix Media file id from `importImage` |
|
|
100
|
+
|
|
101
|
+
Both bulk calls report per-item `success`/`error` — retry only the failed items **once** with the
|
|
102
|
+
same body; don't loop and don't re-create the ones that already succeeded.
|
|
103
|
+
|
|
104
|
+
## Reference
|
|
105
|
+
If a call returns a shape you didn't expect, or you need an operation this module doesn't cover,
|
|
106
|
+
use the **`wix-docs`** skill to search + read the live Wix API reference — never guess. The
|
|
107
|
+
authoritative source recipe is `wix-headless/references/inline-recipes/setup-bookings.md`.
|
|
108
|
+
|
|
109
|
+
Read a method's page before writing its call: it carries the exact body shape, the required
|
|
110
|
+
permission scope, and the response envelope.
|
|
111
|
+
- Install a Wix app onto the site: https://dev.wix.com/docs/api-reference/business-management/app-installation/app-installation/install-app.md
|
|
112
|
+
- Import an image into Wix Media: https://dev.wix.com/docs/api-reference/assets/media/media-manager/files/import-file.md
|
|
113
|
+
- Bulk Create Services: https://dev.wix.com/docs/api-reference/business-solutions/bookings/services/services-v2/bulk-create-services.md
|
|
114
|
+
- Create Category: https://dev.wix.com/docs/api-reference/business-solutions/bookings/services/categories-v2/create-category.md
|
|
115
|
+
- Create Staff Member: https://dev.wix.com/docs/api-reference/business-solutions/bookings/staff-members/staff-members/create-staff-member.md
|
|
116
|
+
- Query Staff Members: https://dev.wix.com/docs/api-reference/business-solutions/bookings/staff-members/staff-members/query-staff-members.md
|
|
117
|
+
- Bulk Create Event (calendar sessions): https://dev.wix.com/docs/api-reference/business-management/calendar/events-v3/bulk-create-event.md
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
// Bookings seed helpers — run at BUILD TIME via exec_tool (NOT shipped in the app).
|
|
2
|
+
// The agent requires this and calls the functions with plain data; all Wix Bookings
|
|
3
|
+
// request/response mechanics (the flat Services-V2 payload, the resourceId-vs-staff-id trap,
|
|
4
|
+
// the category-visibility invariant, the { event: {…} } session wrapper, the silent media.image
|
|
5
|
+
// drop) live here, once.
|
|
6
|
+
//
|
|
7
|
+
// **NOT yet live-verified — transcribed from setup-bookings.md.**
|
|
8
|
+
//
|
|
9
|
+
// Usage (build-time exec_tool):
|
|
10
|
+
// const { accessToken } = await base44.asServiceRole.connectors.getConnection("wix"); // Base44 (generic: use $TOKEN)
|
|
11
|
+
// const seed = require("/app/.agents/skills/wix-vibe-headless/references/bookings/seed/seed-bookings.js");
|
|
12
|
+
// const ctx = { token: accessToken, siteId: WIX_METASITE_ID };
|
|
13
|
+
//
|
|
14
|
+
// await seed.installBookingsApp(ctx); // if the site doesn't have Wix Bookings yet
|
|
15
|
+
//
|
|
16
|
+
// // ORDER MATTERS: resolve a staff resource (STEP 1) and a category (STEP 2) BEFORE services (STEP 3).
|
|
17
|
+
// const staff = await seed.queryStaffWithRetry(ctx); // polls: fresh install provisions the owner async
|
|
18
|
+
// const resourceId = staff[0].resourceId; // NB: resourceId, NOT staff id
|
|
19
|
+
// const cats = await seed.createCategories(ctx, ["Our Services"]);
|
|
20
|
+
// const services = await seed.createServices(ctx, [
|
|
21
|
+
// { type: "APPOINTMENT", name: "Consultation", description: "…", tagLine: "…",
|
|
22
|
+
// price: 75, duration: 60, categoryId: cats[0].id, staffMemberIds: [resourceId] },
|
|
23
|
+
// { type: "CLASS", name: "Morning Yoga", description: "…", capacity: 20,
|
|
24
|
+
// price: 20, categoryId: cats[0].id },
|
|
25
|
+
// ]);
|
|
26
|
+
// // STEP 4 (CLASS only): needs each class's returned scheduleId.
|
|
27
|
+
// await seed.scheduleClassSessions(ctx, services.filter(s => s.type === "CLASS").map(s => ({
|
|
28
|
+
// scheduleId: s.scheduleId, resourceId, start: "2026-08-10T09:00:00", end: "2026-08-10T10:00:00", capacity: 20,
|
|
29
|
+
// })));
|
|
30
|
+
// // optional — import the image url to Wix Media first (bookings binds by file id), then patch (revision-checked).
|
|
31
|
+
// // const file = await seed.importImage(ctx, imageUrl); // → { id, url } (Wix Media file id + wixstatic url)
|
|
32
|
+
// // await seed.attachServiceImage(ctx, { serviceId: s.id, revision: s.revision, image: { id: file.id, url: file.url, width: 1024, height: 1024 } });
|
|
33
|
+
//
|
|
34
|
+
// If any call fails with a shape the caller didn't expect, or you need an operation this module
|
|
35
|
+
// doesn't cover, fall back to the wix-docs skill (search + read the live Wix API reference) —
|
|
36
|
+
// never guess. Source recipe (authoritative): wix-headless/references/inline-recipes/setup-bookings.md.
|
|
37
|
+
|
|
38
|
+
const API = "https://www.wixapis.com";
|
|
39
|
+
// Wix Bookings app id (from the recipe's "API surfaces" note).
|
|
40
|
+
const BOOKINGS_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
41
|
+
|
|
42
|
+
async function req(ctx, path, { method = "POST", body } = {}) {
|
|
43
|
+
const res = await fetch(API + path, {
|
|
44
|
+
method,
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${ctx.token}`,
|
|
47
|
+
"wix-site-id": ctx.siteId,
|
|
48
|
+
"Content-Type": "application/json",
|
|
49
|
+
},
|
|
50
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
51
|
+
});
|
|
52
|
+
const json = await res.json().catch(() => ({}));
|
|
53
|
+
if (!res.ok) throw new Error(`${method} ${path} -> ${res.status}: ${JSON.stringify(json).slice(0, 400)}`);
|
|
54
|
+
return json;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// name -> url slug fallback when item.mainSlug.name is absent (lowercase, non-alphanumerics -> hyphens, dedupe)
|
|
58
|
+
function slugify(name) {
|
|
59
|
+
return String(name || "")
|
|
60
|
+
.toLowerCase()
|
|
61
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
62
|
+
.replace(/^-+|-+$/g, "");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// plain service -> flat Services V2 create object. price omitted / free:true -> NO_FEE.
|
|
66
|
+
// sessionDurations is APPOINTMENT-only; staffMemberIds is required-non-empty for APPOINTMENT, omitted for CLASS.
|
|
67
|
+
function buildService(s) {
|
|
68
|
+
const isAppointment = s.type === "APPOINTMENT";
|
|
69
|
+
const free = s.free === true || s.price == null;
|
|
70
|
+
const out = {
|
|
71
|
+
type: s.type,
|
|
72
|
+
name: s.name,
|
|
73
|
+
description: s.description,
|
|
74
|
+
tagLine: s.tagLine,
|
|
75
|
+
defaultCapacity: s.capacity ?? (isAppointment ? 1 : undefined), // required for ALL types
|
|
76
|
+
onlineBooking: { enabled: true, requireManualApproval: false, allowMultipleRequests: false },
|
|
77
|
+
payment: free
|
|
78
|
+
? { rateType: "NO_FEE", options: { online: false, inPerson: true } }
|
|
79
|
+
: {
|
|
80
|
+
rateType: "FIXED",
|
|
81
|
+
fixed: { price: { value: String(s.price), currency: s.currency ?? "USD" } }, // value is a STRING; site currency wins
|
|
82
|
+
options: { online: true, inPerson: false },
|
|
83
|
+
},
|
|
84
|
+
category: { id: s.categoryId }, // mandatory for live-site visibility
|
|
85
|
+
locations: [{ type: "BUSINESS" }], // never OWNER_BUSINESS on the services endpoint
|
|
86
|
+
};
|
|
87
|
+
if (isAppointment) {
|
|
88
|
+
out.schedule = { availabilityConstraints: { sessionDurations: [s.duration] } }; // APPOINTMENT only, minutes
|
|
89
|
+
out.staffMemberIds = s.staffMemberIds; // resourceId(s) from STEP 1 — non-empty or MISSING_APPOINTMENT_RESOURCES
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---- exported operations ----
|
|
95
|
+
|
|
96
|
+
async function installBookingsApp(ctx) {
|
|
97
|
+
return req(ctx, "/apps-installer-service/v1/app-instance/install", { body: {
|
|
98
|
+
tenant: { tenantType: "SITE", id: ctx.siteId },
|
|
99
|
+
appInstance: { appDefId: BOOKINGS_APP_ID, enabled: true },
|
|
100
|
+
} });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// The default "Business Owner" is a RESOURCE, not a service — it won't appear here.
|
|
104
|
+
async function listServices(ctx) {
|
|
105
|
+
const r = await req(ctx, "/bookings/v2/services/query", { body: { query: { paging: { limit: 100 } } } });
|
|
106
|
+
return (r.services ?? []).map((s) => ({ id: s.id, name: s.name }));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// STEP 1: resolve staff resources. Returns [{ resourceId, id, name }].
|
|
110
|
+
// ⚠️ staffMemberIds takes resourceId, NOT the staff id — read staffMember.resourceId.
|
|
111
|
+
async function queryStaff(ctx) {
|
|
112
|
+
const r = await req(ctx, "/bookings/v1/staff-members/query", {
|
|
113
|
+
body: { query: {}, fields: ["RESOURCE_DETAILS"] },
|
|
114
|
+
});
|
|
115
|
+
return (r.staffMembers ?? []).map((m) => ({ resourceId: m.resourceId, id: m.id, name: m.name }));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// installBookingsApp → provisioning is async (Wix signals completion via the App-Instance-Installed
|
|
119
|
+
// webhook, which an exec script can't receive). The default "Business Owner" resource isn't queryable
|
|
120
|
+
// in the same tick, so queryStaff returns [] (or throws "Business schedule not found") until it lands.
|
|
121
|
+
// Poll so the one-call path resolves the owner instead of failing APPOINTMENT services with
|
|
122
|
+
// MISSING_APPOINTMENT_RESOURCES. Already-provisioned sites return on the first try (no added latency).
|
|
123
|
+
async function queryStaffWithRetry(ctx, { tries = 10, delayMs = 2000 } = {}) {
|
|
124
|
+
let lastErr;
|
|
125
|
+
for (let i = 0; i < tries; i++) {
|
|
126
|
+
try { const s = await queryStaff(ctx); if (s.length) return s; }
|
|
127
|
+
catch (e) { lastErr = e; }
|
|
128
|
+
if (i < tries - 1) await new Promise((r) => setTimeout(r, delayMs));
|
|
129
|
+
}
|
|
130
|
+
if (lastErr) throw lastErr; // never provisioned → surface the real error rather than a silent []
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// STEP 1 (optional): create named staff when the request names stylists/providers. staff: [{ name, description }].
|
|
135
|
+
// ⚠️ Omit email/phone unless you have a real value — V1 rejects empty strings. Returns [{ resourceId, id, name }].
|
|
136
|
+
async function createStaff(ctx, staff) {
|
|
137
|
+
const out = [];
|
|
138
|
+
for (const m of staff) {
|
|
139
|
+
const body = { staffMember: { name: m.name, description: m.description } };
|
|
140
|
+
const r = await req(ctx, "/bookings/v1/staff-members", { body });
|
|
141
|
+
out.push({ resourceId: r.staffMember?.resourceId, id: r.staffMember?.id, name: r.staffMember?.name });
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// STEP 2: query existing categories (to reuse one created earlier this run). Returns [{ id, name }].
|
|
147
|
+
async function queryCategories(ctx) {
|
|
148
|
+
const r = await req(ctx, "/bookings/v2/categories/query", { body: { query: {} } });
|
|
149
|
+
return (r.categories ?? []).map((c) => ({ id: c.id, name: c.name }));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// STEP 2: create categories — every service needs a category.id or it's invisible on the live site.
|
|
153
|
+
// Independent (no shared revision, unlike Stores categories); looped here one call per category.
|
|
154
|
+
async function createCategories(ctx, names) {
|
|
155
|
+
// Reuse an existing same-named category instead of creating a duplicate. Bookings categories aren't
|
|
156
|
+
// unique-by-name, so a re-run (or a partial-failure retry — categories are created before services)
|
|
157
|
+
// would otherwise pile up dupes. Keeps the seed additive-but-idempotent.
|
|
158
|
+
const byName = new Map((await queryCategories(ctx)).map((c) => [c.name, c]));
|
|
159
|
+
const out = [];
|
|
160
|
+
for (const name of names) {
|
|
161
|
+
let cat = byName.get(name);
|
|
162
|
+
if (!cat) {
|
|
163
|
+
const r = await req(ctx, "/bookings/v2/categories", { body: { category: { name } } });
|
|
164
|
+
cat = { id: r.category?.id, name };
|
|
165
|
+
byName.set(name, cat);
|
|
166
|
+
}
|
|
167
|
+
out.push(cat);
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* STEP 3: bulk-create services (up to 100), one call. APPOINTMENT and CLASS may be mixed.
|
|
174
|
+
* MUST run AFTER staff (STEP 1) and category (STEP 2) are resolved.
|
|
175
|
+
* @param services [{ type:"APPOINTMENT"|"CLASS", name, description, tagLine?, categoryId,
|
|
176
|
+
* price?, currency?, free?, duration?(APPOINTMENT minutes), capacity?, staffMemberIds?(resourceIds, APPOINTMENT) }]
|
|
177
|
+
* @returns [{ id, slug, revision, type, scheduleId, index, success, error }]
|
|
178
|
+
* scheduleId feeds scheduleClassSessions (CLASS); revision feeds attachServiceImage.
|
|
179
|
+
* Retry only the items where success===false, ONCE, with the same body — don't loop, don't re-create successes.
|
|
180
|
+
*/
|
|
181
|
+
async function createServices(ctx, services) {
|
|
182
|
+
const body = { services: services.map(buildService), returnEntity: true };
|
|
183
|
+
const r = await req(ctx, "/bookings/v2/bulk/services/create", { body });
|
|
184
|
+
return (r.results ?? []).map((res, i) => {
|
|
185
|
+
const item = res.item ?? {};
|
|
186
|
+
return {
|
|
187
|
+
id: item.id ?? res.itemMetadata?.id,
|
|
188
|
+
slug: item.mainSlug?.name ?? slugify(item.name ?? services[i]?.name),
|
|
189
|
+
revision: item.revision,
|
|
190
|
+
type: item.type ?? services[i]?.type,
|
|
191
|
+
scheduleId: item.schedule?.id,
|
|
192
|
+
index: res.itemMetadata?.originalIndex ?? i,
|
|
193
|
+
success: res.itemMetadata?.success ?? false,
|
|
194
|
+
error: res.itemMetadata?.error,
|
|
195
|
+
};
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* STEP 4 (CLASS only): schedule sessions in one bulk Calendar-Events-V3 call. Skip for APPOINTMENT.
|
|
201
|
+
* @param sessions [{ scheduleId(CLASS item.schedule.id), resourceId(from STEP 1), start, end, capacity? }]
|
|
202
|
+
* start/end are LOCAL wall-clock "YYYY-MM-DDThh:mm:ss" (no Z), today-or-future.
|
|
203
|
+
* @returns [{ id, index, success, error }] — events send no returnEntity, so ids come from itemMetadata only.
|
|
204
|
+
* Retry only failed events once.
|
|
205
|
+
*/
|
|
206
|
+
async function scheduleClassSessions(ctx, sessions) {
|
|
207
|
+
const body = {
|
|
208
|
+
events: sessions.map((s) => ({
|
|
209
|
+
event: {
|
|
210
|
+
scheduleId: s.scheduleId,
|
|
211
|
+
type: "CLASS",
|
|
212
|
+
start: { localDate: s.start },
|
|
213
|
+
end: { localDate: s.end },
|
|
214
|
+
resources: [{ id: s.resourceId, permissionRole: "WRITER" }], // non-empty + WRITER, else UNKNOWN_ROLE 400
|
|
215
|
+
...(s.capacity != null ? { totalCapacity: s.capacity } : {}),
|
|
216
|
+
},
|
|
217
|
+
})),
|
|
218
|
+
};
|
|
219
|
+
const r = await req(ctx, "/calendar/v3/bulk/events/create", { body });
|
|
220
|
+
return (r.results ?? []).map((res, i) => ({
|
|
221
|
+
id: res.itemMetadata?.id,
|
|
222
|
+
index: res.itemMetadata?.originalIndex ?? i,
|
|
223
|
+
success: res.itemMetadata?.success ?? false,
|
|
224
|
+
error: res.itemMetadata?.error,
|
|
225
|
+
}));
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Fetch a service (for its current revision before an image patch, or to confirm an image landed).
|
|
229
|
+
async function getService(ctx, serviceId) {
|
|
230
|
+
return req(ctx, `/bookings/v2/services/${serviceId}`, { method: "GET" });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Import an external image URL into Wix Media → { id, url }. Bookings binds a service image by the
|
|
235
|
+
* Wix Media file **id** (`mainMedia.image.id`), NOT a url — so an external url (e.g. a base44
|
|
236
|
+
* generate_image result) MUST be imported first; the raw url as the id stores (200) but renders
|
|
237
|
+
* nothing. `id` is the wixstatic file id (`<hash>~mv2.png`), `url` the permanent wixstatic url.
|
|
238
|
+
*/
|
|
239
|
+
async function importImage(ctx, url, displayName = "image.png") {
|
|
240
|
+
const r = await req(ctx, "/site-media/v1/files/import", { body: { url, mimeType: "image/png", displayName } });
|
|
241
|
+
const f = r.file || r;
|
|
242
|
+
if (!f?.id) throw new Error(`import-file returned no file id: ${JSON.stringify(r).slice(0, 200)}`);
|
|
243
|
+
return { id: f.id, url: f.url };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Attach an image (optional). Writes under media.mainMedia + media.coverMedia; revision-checked.
|
|
248
|
+
* @param it { serviceId, revision, image: { id, url, width, height } } — `image.id` MUST be a Wix
|
|
249
|
+
* Media file id (from importImage), never a raw external url.
|
|
250
|
+
* ⚠️ Writing under media.image (not mainMedia/coverMedia) returns 200 but SILENTLY drops the image — a 200
|
|
251
|
+
* is not proof; confirm with getService and check media.mainMedia is populated. Never block on failure.
|
|
252
|
+
*/
|
|
253
|
+
async function attachServiceImage(ctx, it) {
|
|
254
|
+
return req(ctx, `/bookings/v2/services/${it.serviceId}`, {
|
|
255
|
+
method: "PATCH",
|
|
256
|
+
body: {
|
|
257
|
+
service: {
|
|
258
|
+
id: it.serviceId,
|
|
259
|
+
revision: it.revision,
|
|
260
|
+
media: { mainMedia: { image: it.image }, coverMedia: { image: it.image } },
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* ONE-CALL seed: install → resolve staff → categories → services → CLASS sessions → images, in the
|
|
268
|
+
* correct order, keeping ids in memory (no hand-threading of scheduleId/resourceId across exec
|
|
269
|
+
* calls). DEFAULT path — call it once instead of the individual functions.
|
|
270
|
+
*
|
|
271
|
+
* @param plan {{
|
|
272
|
+
* services: [{ type:"APPOINTMENT"|"CLASS", name, description, tagLine?, price?, free?, duration?,
|
|
273
|
+
* capacity?, category?(name), staffMemberIds?,
|
|
274
|
+
* sessions?: [{ start, end, capacity? }], // CLASS only; local "YYYY-MM-DDThh:mm:ss"
|
|
275
|
+
* imageUrl? }], // a plain image url; imported to Wix Media here, optional
|
|
276
|
+
* staffResourceId?: string, // defaults to the fresh install's owner (queryStaff()[0].resourceId)
|
|
277
|
+
* }}
|
|
278
|
+
* @returns { services:[...createServices], categories:[{id,name}], resourceId, sessionsScheduled, imagesAttached }
|
|
279
|
+
*/
|
|
280
|
+
async function setupBookings(ctx, { services = [], staffResourceId } = {}) {
|
|
281
|
+
await installBookingsApp(ctx);
|
|
282
|
+
|
|
283
|
+
let resourceId = staffResourceId;
|
|
284
|
+
if (!resourceId) {
|
|
285
|
+
const staff = await queryStaffWithRetry(ctx); // fresh install provisions the default owner async — poll
|
|
286
|
+
resourceId = staff[0]?.resourceId;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const catNames = [...new Set(services.map((s) => s.category).filter(Boolean))];
|
|
290
|
+
const cats = catNames.length ? await createCategories(ctx, catNames) : [];
|
|
291
|
+
const catIdByName = new Map(cats.map((c) => [c.name, c.id]));
|
|
292
|
+
|
|
293
|
+
const created = await createServices(ctx, services.map((s) => ({
|
|
294
|
+
type: s.type, name: s.name, description: s.description, tagLine: s.tagLine,
|
|
295
|
+
price: s.price, free: s.free, duration: s.duration, capacity: s.capacity,
|
|
296
|
+
categoryId: s.category ? catIdByName.get(s.category) : undefined,
|
|
297
|
+
staffMemberIds: s.staffMemberIds ?? (s.type === "APPOINTMENT" && resourceId ? [resourceId] : undefined),
|
|
298
|
+
})));
|
|
299
|
+
|
|
300
|
+
const sessions = [];
|
|
301
|
+
created.forEach((c, i) => {
|
|
302
|
+
const plan = services[i];
|
|
303
|
+
if (c.type === "CLASS" && Array.isArray(plan?.sessions)) {
|
|
304
|
+
for (const ses of plan.sessions) {
|
|
305
|
+
sessions.push({ scheduleId: c.scheduleId, resourceId, start: ses.start, end: ses.end, capacity: ses.capacity ?? plan.capacity });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
const scheduled = sessions.length ? await scheduleClassSessions(ctx, sessions) : [];
|
|
310
|
+
|
|
311
|
+
let imagesAttached = 0;
|
|
312
|
+
for (let i = 0; i < created.length; i++) {
|
|
313
|
+
const url = services[i]?.imageUrl; // a plain image url (e.g. base44 generate_image) — imported here
|
|
314
|
+
if (!url || !created[i]?.id) continue;
|
|
315
|
+
try {
|
|
316
|
+
const file = await importImage(ctx, url, `${created[i].slug || "service"}.png`); // → Wix Media file id
|
|
317
|
+
await attachServiceImage(ctx, {
|
|
318
|
+
serviceId: created[i].id, revision: created[i].revision,
|
|
319
|
+
image: { id: file.id, url: file.url, width: 1024, height: 1024 },
|
|
320
|
+
});
|
|
321
|
+
imagesAttached++;
|
|
322
|
+
} catch { /* never block on image failure — leave the service text-only */ }
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return { services: created, categories: cats, resourceId, sessionsScheduled: scheduled.length, imagesAttached };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = {
|
|
329
|
+
setupBookings,
|
|
330
|
+
installBookingsApp, listServices,
|
|
331
|
+
queryStaff, queryStaffWithRetry, createStaff, queryCategories, createCategories,
|
|
332
|
+
createServices, scheduleClassSessions, getService, importImage, attachServiceImage,
|
|
333
|
+
};
|