@wix/agent-skills 1.16.3 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +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 +145 -127
- package/skills/wix-app/references/SITE_PLUGIN.md +14 -2
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +74 -68
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +2 -2
- 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/ACCESSIBILITY.md +182 -99
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +94 -49
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +16 -6
- package/skills/wix-app/references/editor-react-component/BROWSER-SUPPORT.md +56 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +275 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +35 -40
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +167 -445
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +56 -29
- package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +53 -158
- package/skills/wix-app/references/editor-react-component/EDITOR-EXTENSION-CONFIGURATION.md +78 -0
- package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +42 -134
- package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +11 -3
- package/skills/wix-app/references/editor-react-component/PARTS.md +46 -24
- package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +63 -179
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +79 -163
- package/skills/wix-app/references/editor-react-component/SITE-CONTEXT-HOOKS.md +36 -21
- package/skills/wix-app/references/editor-react-component/SSR.md +30 -99
- package/skills/wix-base44-connector/SKILL.md +156 -50
- package/skills/wix-base44-connector/references/CAPABILITY_MAP.md +78 -0
- package/skills/wix-base44-connector/scripts/utils.js +239 -45
- package/skills/wix-headless/entry/bootstrap.mjs +9 -2
- 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 +5 -2
- 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 +68 -31
- 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-pricing-plans.md +10 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-rentals.md +254 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +27 -24
- 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/managed/AUTHENTICATION.md +5 -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 +28 -2
- package/skills/wix-manage/references/bookings/booking-system-integration-gaps.md +20 -13
- package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +24 -11
- package/skills/wix-manage/references/cms/cms-ecommerce-catalog-integration.md +5 -5
- 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/seo/manage-seo-tags.md +315 -0
- package/skills/wix-manage/references/seo/manage-url-redirects.md +250 -0
- package/skills/wix-manage/references/sites/query-sites.md +72 -161
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +21 -6
- package/skills/wix-manage/references/stores/update-product-with-options.md +83 -0
- package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +5 -6
- 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 +16 -10
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +13 -8
- package/skills/wix-vibe-headless/platforms/base44.md +27 -62
- package/skills/wix-vibe-headless/platforms/generic.md +6 -3
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +45 -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-checkout.js +9 -8
- 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 +470 -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 +1762 -0
- package/skills/wix-vibe-headless/references/rentals/INSTRUCTIONS.md +13 -0
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +4 -1
- package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +23 -11
- package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +29 -31
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +152 -92
- package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +37 -38
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +2 -2
- 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-cart.js +35 -35
- 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-app/references/editor-react-component/A11Y-REVIEW-REFERENCE.md +0 -172
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +0 -143
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +0 -516
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +0 -225
- package/skills/wix-app/references/editor-react-component/EDIT-FLOW.md +0 -6
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +0 -95
- 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,165 @@
|
|
|
1
|
+
// Menu reads (Wix Restaurants Menus V1) — the only file that touches raw menu entities.
|
|
2
|
+
// Everything it returns is a plain DTO from ./types. Copy as-is; extend by adding functions,
|
|
3
|
+
// not by editing these.
|
|
4
|
+
//
|
|
5
|
+
// The hierarchy is Menu → Sections → Items wired by ID ARRAYS: display structure and order
|
|
6
|
+
// live in menu.sectionIds / section.itemIds, NOT in the list* response order — fetchMenus
|
|
7
|
+
// stitches in id-array order and drops dangling ids. Entity ids are `_id` (never `id`).
|
|
8
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/menus/list-menus.md
|
|
9
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/sections/list-sections.md
|
|
10
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/items/items/list-items.md
|
|
11
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/items/item-variants/list-variants.md
|
|
12
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/items/item-modifier-groups/list-modifier-groups.md
|
|
13
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/items/item-modifiers/list-modifiers.md
|
|
14
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/items/item-labels/list-labels.md
|
|
15
|
+
import {
|
|
16
|
+
menus as menusModule,
|
|
17
|
+
sections as sectionsModule,
|
|
18
|
+
items as itemsModule,
|
|
19
|
+
itemVariants,
|
|
20
|
+
itemModifierGroups,
|
|
21
|
+
itemModifiers,
|
|
22
|
+
itemLabels,
|
|
23
|
+
} from "@wix/restaurants";
|
|
24
|
+
import { wixModule } from "../sdk";
|
|
25
|
+
import { imgSrc } from "../media";
|
|
26
|
+
import type {
|
|
27
|
+
MenuData,
|
|
28
|
+
MenuItem,
|
|
29
|
+
MenuItemLabel,
|
|
30
|
+
MenuItemVariant,
|
|
31
|
+
MenuModifier,
|
|
32
|
+
MenuModifierGroup,
|
|
33
|
+
MenuSection,
|
|
34
|
+
} from "./types";
|
|
35
|
+
|
|
36
|
+
const menusApi = wixModule(menusModule);
|
|
37
|
+
const sectionsApi = wixModule(sectionsModule);
|
|
38
|
+
const itemsApi = wixModule(itemsModule);
|
|
39
|
+
const variantsApi = wixModule(itemVariants);
|
|
40
|
+
const groupsApi = wixModule(itemModifierGroups);
|
|
41
|
+
const modifiersApi = wixModule(itemModifiers);
|
|
42
|
+
const labelsApi = wixModule(itemLabels);
|
|
43
|
+
|
|
44
|
+
type Raw = Record<string, any>;
|
|
45
|
+
|
|
46
|
+
// Menu prices are decimal strings in the site currency with NO symbol; the response may
|
|
47
|
+
// carry a platform-formatted twin (formattedPrice) — prefer it, never invent a symbol.
|
|
48
|
+
function displayPrice(priceInfo: Raw | undefined): string | null {
|
|
49
|
+
if (!priceInfo) return null;
|
|
50
|
+
return priceInfo.formattedPrice ?? priceInfo.price ?? null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toItem(
|
|
54
|
+
raw: Raw,
|
|
55
|
+
lookups: { variantById: Map<string, Raw>; groupById: Map<string, Raw>; modifierById: Map<string, Raw>; labelById: Map<string, Raw> },
|
|
56
|
+
): MenuItem {
|
|
57
|
+
const variants: MenuItemVariant[] = (raw.priceVariants?.variants ?? [])
|
|
58
|
+
.map((v: Raw): MenuItemVariant => ({
|
|
59
|
+
variantId: v.variantId ?? "",
|
|
60
|
+
name: lookups.variantById.get(v.variantId)?.name ?? "",
|
|
61
|
+
price: displayPrice(v.priceInfo) ?? v.price ?? "",
|
|
62
|
+
}))
|
|
63
|
+
.filter((v: MenuItemVariant) => v.variantId);
|
|
64
|
+
const modifierGroups: MenuModifierGroup[] = (raw.modifierGroups ?? [])
|
|
65
|
+
.map((ref: Raw) => lookups.groupById.get(ref._id))
|
|
66
|
+
.filter(Boolean)
|
|
67
|
+
.map((g: Raw): MenuModifierGroup => ({
|
|
68
|
+
id: g._id ?? "",
|
|
69
|
+
name: g.name ?? "",
|
|
70
|
+
required: g.rule?.required === true,
|
|
71
|
+
minSelections: g.rule?.minSelections ?? 0,
|
|
72
|
+
maxSelections: g.rule?.maxSelections ?? null,
|
|
73
|
+
modifiers: (g.modifiers ?? []).map((m: Raw): MenuModifier => ({
|
|
74
|
+
id: m._id ?? "",
|
|
75
|
+
name: lookups.modifierById.get(m._id)?.name ?? "",
|
|
76
|
+
preSelected: m.preSelected === true,
|
|
77
|
+
additionalCharge: m.additionalChargeInfo?.additionalCharge ?? "0",
|
|
78
|
+
inStock: lookups.modifierById.get(m._id)?.inStock !== false,
|
|
79
|
+
})),
|
|
80
|
+
}));
|
|
81
|
+
const labels: MenuItemLabel[] = (raw.labels ?? [])
|
|
82
|
+
.map((ref: Raw) => lookups.labelById.get(ref._id))
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.map((l: Raw): MenuItemLabel => ({ id: l._id ?? "", name: l.name ?? "", iconUrl: imgSrc(l.icon, 48, 48) }));
|
|
85
|
+
const price = displayPrice(raw.priceInfo);
|
|
86
|
+
return {
|
|
87
|
+
id: raw._id ?? "",
|
|
88
|
+
name: raw.name ?? "",
|
|
89
|
+
description: raw.description ?? "",
|
|
90
|
+
price,
|
|
91
|
+
marketPrice: price === null && variants.length === 0,
|
|
92
|
+
variants,
|
|
93
|
+
imageUrl: imgSrc(raw.image, 800, 800),
|
|
94
|
+
labels,
|
|
95
|
+
modifierGroups,
|
|
96
|
+
inStock: raw.orderSettings?.inStock !== false,
|
|
97
|
+
featured: raw.featured === true,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The site's full menu tree, assembled and display-ordered: visible menus → their sections →
|
|
103
|
+
* their items, each item enriched with resolved price variants, modifier groups, and labels.
|
|
104
|
+
* The one entry point for every menu surface. [] when no menus exist (honest empty state).
|
|
105
|
+
* Caps at the APIs' 500-per-list limit — plenty for a restaurant.
|
|
106
|
+
*/
|
|
107
|
+
export async function fetchMenus(): Promise<MenuData[]> {
|
|
108
|
+
const [menusRes, sectionsRes, itemsRes]: Raw[] = await Promise.all([
|
|
109
|
+
menusApi.listMenus({ onlyVisible: true }),
|
|
110
|
+
sectionsApi.listSections({ onlyVisible: true }),
|
|
111
|
+
itemsApi.listItems({ onlyVisible: true }),
|
|
112
|
+
]);
|
|
113
|
+
const menus: Raw[] = menusRes.menus ?? [];
|
|
114
|
+
if (!menus.length) return [];
|
|
115
|
+
const sections: Raw[] = sectionsRes.sections ?? [];
|
|
116
|
+
const items: Raw[] = itemsRes.items ?? [];
|
|
117
|
+
|
|
118
|
+
const variantIds: string[] = [
|
|
119
|
+
...new Set(items.flatMap((i: Raw) => (i.priceVariants?.variants ?? []).map((v: Raw) => v.variantId)).filter(Boolean)),
|
|
120
|
+
] as string[];
|
|
121
|
+
const groupIds: string[] = [
|
|
122
|
+
...new Set(items.flatMap((i: Raw) => (i.modifierGroups ?? []).map((g: Raw) => g._id)).filter(Boolean)),
|
|
123
|
+
] as string[];
|
|
124
|
+
const hasLabels = items.some((i: Raw) => (i.labels ?? []).length > 0);
|
|
125
|
+
|
|
126
|
+
const [variantsRes, groupsRes, labelsRes]: Raw[] = await Promise.all([
|
|
127
|
+
variantIds.length ? variantsApi.listVariants({ variantIds }) : Promise.resolve({ variants: [] }),
|
|
128
|
+
groupIds.length ? groupsApi.listModifierGroups({ modifierGroupIds: groupIds }) : Promise.resolve({ modifierGroups: [] }),
|
|
129
|
+
hasLabels ? labelsApi.listLabels() : Promise.resolve({ labels: [] }),
|
|
130
|
+
]);
|
|
131
|
+
const groups: Raw[] = groupsRes.modifierGroups ?? [];
|
|
132
|
+
const modifierIds: string[] = [
|
|
133
|
+
...new Set(groups.flatMap((g: Raw) => (g.modifiers ?? []).map((m: Raw) => m._id)).filter(Boolean)),
|
|
134
|
+
] as string[];
|
|
135
|
+
const modifiersRes: Raw = modifierIds.length
|
|
136
|
+
? await modifiersApi.listModifiers({ modifierIds })
|
|
137
|
+
: { modifiers: [] };
|
|
138
|
+
|
|
139
|
+
const byId = (arr: Raw[]) => new Map<string, Raw>(arr.map((e: Raw) => [e._id, e]));
|
|
140
|
+
const lookups = {
|
|
141
|
+
variantById: byId(variantsRes.variants ?? []),
|
|
142
|
+
groupById: byId(groups),
|
|
143
|
+
modifierById: byId(modifiersRes.modifiers ?? []),
|
|
144
|
+
labelById: byId(labelsRes.labels ?? []),
|
|
145
|
+
};
|
|
146
|
+
const sectionById = byId(sections);
|
|
147
|
+
const itemById = new Map<string, MenuItem>(items.map((i: Raw) => [i._id, toItem(i, lookups)]));
|
|
148
|
+
|
|
149
|
+
return menus.map((m: Raw): MenuData => ({
|
|
150
|
+
id: m._id ?? "",
|
|
151
|
+
name: m.name ?? "",
|
|
152
|
+
description: m.description ?? "",
|
|
153
|
+
slug: m.urlQueryParam ?? "",
|
|
154
|
+
sections: (m.sectionIds ?? [])
|
|
155
|
+
.map((sid: string) => sectionById.get(sid))
|
|
156
|
+
.filter(Boolean)
|
|
157
|
+
.map((s: Raw): MenuSection => ({
|
|
158
|
+
id: s._id ?? "",
|
|
159
|
+
name: s.name ?? "",
|
|
160
|
+
description: s.description ?? "",
|
|
161
|
+
imageUrl: imgSrc(s.image, 800, 800),
|
|
162
|
+
items: (s.itemIds ?? []).map((iid: string) => itemById.get(iid)).filter(Boolean) as MenuItem[],
|
|
163
|
+
})),
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// Client-side order-cart state — a module-scope store, deliberately NOT a React context.
|
|
2
|
+
// A context can't span Astro islands (each island is its own React root); a module singleton
|
|
3
|
+
// is shared by every island in the page bundle, and works identically in a single-root SPA.
|
|
4
|
+
// Consume it through useOrderCart() (hooks/), or subscribe directly.
|
|
5
|
+
import type { OrderCart } from "./types";
|
|
6
|
+
import {
|
|
7
|
+
addToOrder as apiAdd,
|
|
8
|
+
fetchOrderCart,
|
|
9
|
+
orderCheckoutUrl,
|
|
10
|
+
removeOrderLine as apiRemove,
|
|
11
|
+
resolveOperationId,
|
|
12
|
+
updateOrderQuantity as apiUpdate,
|
|
13
|
+
} from "./ordering";
|
|
14
|
+
|
|
15
|
+
export interface OrderCartState {
|
|
16
|
+
cart: OrderCart | null;
|
|
17
|
+
/** null while resolving; false → no ordering operation configured (show "unavailable"). */
|
|
18
|
+
ordering: boolean | null;
|
|
19
|
+
/** True while any cart operation is in flight. */
|
|
20
|
+
busy: boolean;
|
|
21
|
+
/** Last failed operation's message — render it; a new operation clears it. */
|
|
22
|
+
error: string | null;
|
|
23
|
+
/** Order drawer visibility (OrderCartButton opens it, OrderCartDrawer renders by it). */
|
|
24
|
+
open: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const EMPTY: OrderCartState = { cart: null, ordering: null, busy: false, error: null, open: false };
|
|
28
|
+
|
|
29
|
+
let state: OrderCartState = EMPTY;
|
|
30
|
+
const listeners = new Set<() => void>();
|
|
31
|
+
let loaded = false;
|
|
32
|
+
|
|
33
|
+
function setState(patch: Partial<OrderCartState>): void {
|
|
34
|
+
state = { ...state, ...patch };
|
|
35
|
+
for (const l of listeners) l();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getOrderCartState(): OrderCartState {
|
|
39
|
+
return state;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function subscribeOrderCart(listener: () => void): () => void {
|
|
43
|
+
listeners.add(listener);
|
|
44
|
+
// First subscriber triggers the initial load (browser only — SSR renders the empty state).
|
|
45
|
+
if (!loaded && typeof window !== "undefined") {
|
|
46
|
+
loaded = true;
|
|
47
|
+
void refreshOrderCart();
|
|
48
|
+
void resolveOperationId().then((id) => setState({ ordering: id !== null }));
|
|
49
|
+
}
|
|
50
|
+
return () => listeners.delete(listener);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function run(op: () => Promise<OrderCart>): Promise<void> {
|
|
54
|
+
setState({ busy: true, error: null });
|
|
55
|
+
try {
|
|
56
|
+
setState({ cart: await op(), busy: false });
|
|
57
|
+
} catch (e) {
|
|
58
|
+
setState({ busy: false, error: e instanceof Error ? e.message : String(e) });
|
|
59
|
+
throw e;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function refreshOrderCart(): Promise<void> {
|
|
64
|
+
try {
|
|
65
|
+
setState({ cart: await fetchOrderCart() });
|
|
66
|
+
} catch {
|
|
67
|
+
/* no cart yet — leave the empty state */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Add a menu item to the order and open the drawer. Throws (and sets .error) on refusal. */
|
|
72
|
+
export async function addOrderLine(
|
|
73
|
+
itemId: string,
|
|
74
|
+
context: { menuId: string; sectionId: string },
|
|
75
|
+
quantity = 1,
|
|
76
|
+
): Promise<void> {
|
|
77
|
+
await run(() => apiAdd(itemId, context, quantity));
|
|
78
|
+
setState({ open: true });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function updateOrderLineQuantity(lineItemId: string, quantity: number): Promise<void> {
|
|
82
|
+
await run(() => apiUpdate(lineItemId, quantity));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function removeLineFromOrder(lineItemId: string): Promise<void> {
|
|
86
|
+
await run(() => apiRemove(lineItemId));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Navigate to the Wix-hosted checkout. Never hand-build a checkout URL. */
|
|
90
|
+
export async function goToOrderCheckout(): Promise<void> {
|
|
91
|
+
setState({ busy: true, error: null });
|
|
92
|
+
try {
|
|
93
|
+
const url = await orderCheckoutUrl();
|
|
94
|
+
window.location.href = url;
|
|
95
|
+
} catch (e) {
|
|
96
|
+
setState({ busy: false, error: e instanceof Error ? e.message : String(e) });
|
|
97
|
+
throw e;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function setOrderCartOpen(open: boolean): void {
|
|
102
|
+
setState({ open });
|
|
103
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// Online ordering (Restaurants Orders + Wix eCom Cart V2) — the only file that touches the
|
|
2
|
+
// raw cart and operation entities. Copy as-is; extend by calling these exports, never by
|
|
3
|
+
// editing them. The request shapes are exact and rewriting them is how carts break:
|
|
4
|
+
// - the catalogReference appId is the ORDERS app (9a5d83fd-…), never the Stores id;
|
|
5
|
+
// - options MUST carry operationId + menuId + sectionId (all three) — the restaurant
|
|
6
|
+
// analog of the store's variantId; there is NO variantId here;
|
|
7
|
+
// - modifier/variant selections on the cart line are NOT sent — that options shape isn't
|
|
8
|
+
// documented for client add-to-cart (display modifiers; send quantity only).
|
|
9
|
+
// Failures are loud: throws on a missing ordering operation, unavailable lines, and an empty
|
|
10
|
+
// cart at checkout — surface the message, don't swallow it.
|
|
11
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/online-orders/operations/list-operations.md
|
|
12
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/online-orders/fulfillment-methods/list-fulfillment-methods.md
|
|
13
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/items/items/list-items.md
|
|
14
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/get-current-cart.md
|
|
15
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/add-line-items-to-current-cart.md
|
|
16
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/update-line-items.md
|
|
17
|
+
// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
|
|
18
|
+
import { operations as operationsModule, fulfillmentMethods as fulfillmentModule, items as itemsModule } from "@wix/restaurants";
|
|
19
|
+
import { currentCartV2 } from "@wix/ecom";
|
|
20
|
+
import { redirects as redirectsModule } from "@wix/redirects";
|
|
21
|
+
import { wixModule } from "../sdk";
|
|
22
|
+
import { imgSrc } from "../media";
|
|
23
|
+
import { formatMoney } from "../money";
|
|
24
|
+
import type { FulfillmentMethodInfo, OrderCart, OrderLine } from "./types";
|
|
25
|
+
|
|
26
|
+
const operationsApi = wixModule(operationsModule);
|
|
27
|
+
const fulfillmentApi = wixModule(fulfillmentModule);
|
|
28
|
+
const itemsApi = wixModule(itemsModule);
|
|
29
|
+
const cartApi = wixModule(currentCartV2);
|
|
30
|
+
const redirects = wixModule(redirectsModule);
|
|
31
|
+
|
|
32
|
+
/** The Restaurants Orders app id — every restaurant cart line's catalogReference.appId. */
|
|
33
|
+
export const RESTAURANTS_ORDERS_APP_ID = "9a5d83fd-8570-482e-81ab-cfa88942ee60";
|
|
34
|
+
|
|
35
|
+
type Raw = Record<string, any>;
|
|
36
|
+
|
|
37
|
+
// The ordering operation is site config — resolve once, reuse for every add.
|
|
38
|
+
let operationIdPromise: Promise<string | null> | null = null;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The id of the operation to order through (ENABLED, else default, else first) — null when
|
|
42
|
+
* the site has no online ordering configured (show an "ordering unavailable" state).
|
|
43
|
+
*/
|
|
44
|
+
export function resolveOperationId(): Promise<string | null> {
|
|
45
|
+
operationIdPromise ??= operationsApi
|
|
46
|
+
.listOperations()
|
|
47
|
+
.then((res: Raw) => {
|
|
48
|
+
const ops: Raw[] = res.operations ?? [];
|
|
49
|
+
const op =
|
|
50
|
+
ops.find((o: Raw) => o.onlineOrderingStatus === "ENABLED") ?? ops.find((o: Raw) => o.default) ?? ops[0];
|
|
51
|
+
return op?._id ?? null;
|
|
52
|
+
})
|
|
53
|
+
.catch(() => {
|
|
54
|
+
operationIdPromise = null; // transient failure — allow a retry on the next call
|
|
55
|
+
return null;
|
|
56
|
+
});
|
|
57
|
+
return operationIdPromise;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Enabled pickup/delivery methods, for display — the buyer picks one on the hosted checkout. */
|
|
61
|
+
export async function fetchFulfillmentMethods(): Promise<FulfillmentMethodInfo[]> {
|
|
62
|
+
try {
|
|
63
|
+
const res: Raw = await fulfillmentApi.listFulfillmentMethods();
|
|
64
|
+
return (res.fulfillmentMethods ?? [])
|
|
65
|
+
.filter((m: Raw) => m.enabled === true)
|
|
66
|
+
.map((m: Raw): FulfillmentMethodInfo => ({
|
|
67
|
+
id: m._id ?? "",
|
|
68
|
+
type: m.type === "DELIVERY" ? "DELIVERY" : "PICKUP",
|
|
69
|
+
name: m.name ?? "",
|
|
70
|
+
fee: m.fee ?? "0",
|
|
71
|
+
minOrderPrice: m.minOrderPrice ?? "0",
|
|
72
|
+
}))
|
|
73
|
+
.filter((m: FulfillmentMethodInfo) => m.id);
|
|
74
|
+
} catch {
|
|
75
|
+
return []; // display nicety — ordering still works without the list
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The V2 cart doesn't reliably return line-item images — join them from the menu items by
|
|
80
|
+
// the line's catalogItemId and cache for the session.
|
|
81
|
+
const lineImageCache = new Map<string, string>();
|
|
82
|
+
|
|
83
|
+
async function fillLineImages(lines: OrderLine[], raws: Raw[]): Promise<void> {
|
|
84
|
+
const wanted = new Map<string, number[]>(); // itemId -> line indexes
|
|
85
|
+
raws.forEach((raw: Raw, i: number) => {
|
|
86
|
+
if (lines[i].imageUrl) return;
|
|
87
|
+
const itemId = raw.source?.catalogReference?.catalogItemId;
|
|
88
|
+
if (!itemId) return;
|
|
89
|
+
const cached = lineImageCache.get(itemId);
|
|
90
|
+
if (cached !== undefined) {
|
|
91
|
+
lines[i].imageUrl = cached;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
wanted.set(itemId, [...(wanted.get(itemId) ?? []), i]);
|
|
95
|
+
});
|
|
96
|
+
if (!wanted.size) return;
|
|
97
|
+
try {
|
|
98
|
+
const res: Raw = await itemsApi.listItems({ itemIds: [...wanted.keys()] });
|
|
99
|
+
for (const item of (res.items ?? []) as Raw[]) {
|
|
100
|
+
const url = imgSrc(item.image, 300, 300);
|
|
101
|
+
lineImageCache.set(item._id, url);
|
|
102
|
+
for (const i of wanted.get(item._id) ?? []) lines[i].imageUrl = url;
|
|
103
|
+
}
|
|
104
|
+
} catch {
|
|
105
|
+
/* images are a nicety — the cart stays correct without them */
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function cartCurrency(raw: Raw | null): string {
|
|
110
|
+
return raw?.customerInfo?.currencyCode ?? raw?.businessInfo?.currencyCode ?? "";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function toLine(raw: Raw, currency: string): OrderLine {
|
|
114
|
+
const descriptionLines: string[] = (raw.attributes?.descriptionLines ?? [])
|
|
115
|
+
.map((d: Raw) => {
|
|
116
|
+
const label = d.name?.original ?? "";
|
|
117
|
+
const value = d.plainText?.original ?? d.colorInfo?.original ?? "";
|
|
118
|
+
return label && value ? `${label}: ${value}` : value || label;
|
|
119
|
+
})
|
|
120
|
+
.filter(Boolean);
|
|
121
|
+
return {
|
|
122
|
+
lineItemId: raw._id ?? "",
|
|
123
|
+
itemName: raw.name?.original ?? "",
|
|
124
|
+
quantity: raw.quantityInfo?.confirmedQuantity ?? 0,
|
|
125
|
+
unitPrice: formatMoney(raw.pricing?.unitPrice, currency),
|
|
126
|
+
linePrice: formatMoney(raw.pricing?.totalPrice, currency),
|
|
127
|
+
imageUrl: imgSrc(raw.attributes?.image, 300, 300),
|
|
128
|
+
descriptionLines,
|
|
129
|
+
status: raw.status ?? "IN_STOCK",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function toCart(raw: Raw | null, subtotal: string): OrderCart {
|
|
134
|
+
const currency = cartCurrency(raw);
|
|
135
|
+
const lines = (raw?.lineItems ?? []).map((l: Raw) => toLine(l, currency));
|
|
136
|
+
return {
|
|
137
|
+
lines,
|
|
138
|
+
itemCount: lines.reduce((sum: number, l: OrderLine) => sum + l.quantity, 0),
|
|
139
|
+
subtotal,
|
|
140
|
+
currency,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function readCartWithSubtotal(raw: Raw | null): Promise<OrderCart> {
|
|
145
|
+
if (!raw?.lineItems?.length) return toCart(raw, "");
|
|
146
|
+
// The authoritative after-discount subtotal comes from the cart estimate, never from
|
|
147
|
+
// hand-summing line items (fees, tax, and delivery resolve at checkout).
|
|
148
|
+
let subtotal = "";
|
|
149
|
+
try {
|
|
150
|
+
const estimate: Raw = await cartApi.estimateCurrentCart();
|
|
151
|
+
subtotal = formatMoney(estimate?.summary?.priceSummary?.subtotal, cartCurrency(raw));
|
|
152
|
+
} catch {
|
|
153
|
+
/* estimate is a display nicety — the cart itself is still valid */
|
|
154
|
+
}
|
|
155
|
+
const cart = toCart(raw, subtotal);
|
|
156
|
+
await fillLineImages(cart.lines, raw.lineItems as Raw[]);
|
|
157
|
+
return cart;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Read the visitor's current order cart. An empty cart (not an error) when none exists yet. */
|
|
161
|
+
export async function fetchOrderCart(): Promise<OrderCart> {
|
|
162
|
+
try {
|
|
163
|
+
const { cart } = await cartApi.getCurrentCart();
|
|
164
|
+
return readCartWithSubtotal(cart as Raw);
|
|
165
|
+
} catch {
|
|
166
|
+
return toCart(null, "");
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Add a menu item to the current order. `menuId` and `sectionId` are the ids of the menu and
|
|
172
|
+
* section the item is RENDERED UNDER — thread them from the render context (the fetchMenus
|
|
173
|
+
* tree), never re-derive them. Throws when ordering isn't configured or the line is refused.
|
|
174
|
+
*/
|
|
175
|
+
export async function addToOrder(
|
|
176
|
+
itemId: string,
|
|
177
|
+
{ menuId, sectionId }: { menuId: string; sectionId: string },
|
|
178
|
+
quantity = 1,
|
|
179
|
+
): Promise<OrderCart> {
|
|
180
|
+
const operationId = await resolveOperationId();
|
|
181
|
+
if (!operationId) throw new Error("Online ordering isn't available right now.");
|
|
182
|
+
if (!itemId || !menuId || !sectionId) throw new Error("addToOrder needs the item, menu, and section ids.");
|
|
183
|
+
|
|
184
|
+
const { cart } = await cartApi.addLineItemsToCurrentCart({
|
|
185
|
+
catalogItems: [
|
|
186
|
+
{
|
|
187
|
+
quantity,
|
|
188
|
+
catalogReference: {
|
|
189
|
+
catalogItemId: itemId,
|
|
190
|
+
appId: RESTAURANTS_ORDERS_APP_ID,
|
|
191
|
+
options: { operationId, menuId, sectionId },
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// V2 nests the reference under `source` — a top-level lineItem.catalogReference no longer exists.
|
|
198
|
+
const line = ((cart as Raw)?.lineItems ?? []).find(
|
|
199
|
+
(l: Raw) => l.source?.catalogReference?.catalogItemId === itemId,
|
|
200
|
+
);
|
|
201
|
+
if (line?.status && line.status !== "IN_STOCK") {
|
|
202
|
+
throw new Error(`This dish isn't available right now (${line.status.toLowerCase().replace(/_/g, " ")}).`);
|
|
203
|
+
}
|
|
204
|
+
if (!line || line.quantityInfo?.confirmedQuantity === 0) {
|
|
205
|
+
throw new Error("The dish couldn't be added to the order — please try again.");
|
|
206
|
+
}
|
|
207
|
+
return readCartWithSubtotal(cart as Raw);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Change a line's quantity. `lineItemId` is OrderLine.lineItemId, never the menu item id. */
|
|
211
|
+
export async function updateOrderQuantity(lineItemId: string, quantity: number): Promise<OrderCart> {
|
|
212
|
+
const { cart } = await cartApi.updateLineItemsInCurrentCart({
|
|
213
|
+
lineItems: [{ lineItemId, quantity: { newQuantity: quantity } }],
|
|
214
|
+
});
|
|
215
|
+
return readCartWithSubtotal(cart as Raw);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Remove a line from the order by OrderLine.lineItemId. */
|
|
219
|
+
export async function removeOrderLine(lineItemId: string): Promise<OrderCart> {
|
|
220
|
+
const { cart } = await cartApi.removeLineItemsFromCurrentCart([lineItemId]);
|
|
221
|
+
return readCartWithSubtotal(cart as Raw);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Start the Wix-hosted checkout for the current order and return the URL to navigate to.
|
|
226
|
+
* The cart's id IS the checkout id — no separate checkout-creation call. Fulfillment
|
|
227
|
+
* (pickup/delivery + time) and payment are collected on the hosted page. Call from the
|
|
228
|
+
* browser: the return origin must be window.location.origin (https), never a server-derived
|
|
229
|
+
* request origin (http behind the proxy → the return redirect 403s).
|
|
230
|
+
*/
|
|
231
|
+
export async function orderCheckoutUrl(): Promise<string> {
|
|
232
|
+
const { cart } = await cartApi.getCurrentCart();
|
|
233
|
+
const raw = cart as Raw;
|
|
234
|
+
const lines: Raw[] = raw?.lineItems ?? [];
|
|
235
|
+
if (!lines.length) throw new Error("Your order is empty.");
|
|
236
|
+
const unavailable = lines.filter((l) => l.status && l.status !== "IN_STOCK");
|
|
237
|
+
if (unavailable.length) {
|
|
238
|
+
const names = unavailable.map((l) => l.name?.original).filter(Boolean).join(", ");
|
|
239
|
+
throw new Error(`Some dishes are no longer available: ${names}.`);
|
|
240
|
+
}
|
|
241
|
+
if (!raw?._id) throw new Error("Checkout couldn't start: the order has no id.");
|
|
242
|
+
|
|
243
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "";
|
|
244
|
+
const session = await redirects.createRedirectSession({
|
|
245
|
+
ecomCheckout: { checkoutId: raw._id },
|
|
246
|
+
callbacks: {
|
|
247
|
+
postFlowUrl: origin ? `${origin}/` : undefined,
|
|
248
|
+
thankYouPageUrl: origin ? `${origin}/` : undefined,
|
|
249
|
+
},
|
|
250
|
+
});
|
|
251
|
+
const url = session?.redirectSession?.fullUrl;
|
|
252
|
+
if (!url) throw new Error("Checkout couldn't start: no redirect URL returned.");
|
|
253
|
+
return url;
|
|
254
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// Table reservations (@wix/table-reservations) — the only file that touches raw reservation
|
|
2
|
+
// entities. Copy as-is; extend by calling these exports, never by editing them.
|
|
3
|
+
//
|
|
4
|
+
// The flow is location → AVAILABLE slots → hold (10-minute temporary reservation) → reserve
|
|
5
|
+
// with the visitor's details. A reservation is a hold, not a purchase — no cart, no checkout.
|
|
6
|
+
// getTimeSlots takes POSITIONAL args and a Date (not an ISO string); reserveReservation takes
|
|
7
|
+
// THREE positional args (id, reservee, revision) and the only exit from HELD is reserve.
|
|
8
|
+
// Failures are loud — surface the message, don't swallow it.
|
|
9
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservation-locations/list-reservation-locations.md
|
|
10
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/time-slots/get-time-slots.md
|
|
11
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-held-reservation.md
|
|
12
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/reserve-reservation.md
|
|
13
|
+
import { reservationLocations, timeSlots, reservations as reservationsModule } from "@wix/table-reservations";
|
|
14
|
+
import { wixModule } from "../sdk";
|
|
15
|
+
import type {
|
|
16
|
+
ReservationConfirmation,
|
|
17
|
+
ReservationHold,
|
|
18
|
+
ReservationLocationInfo,
|
|
19
|
+
ReservationReservee,
|
|
20
|
+
ReservationSlot,
|
|
21
|
+
} from "./types";
|
|
22
|
+
|
|
23
|
+
const locationsApi = wixModule(reservationLocations);
|
|
24
|
+
const timeSlotsApi = wixModule(timeSlots);
|
|
25
|
+
const reservationsApi = wixModule(reservationsModule);
|
|
26
|
+
|
|
27
|
+
type Raw = Record<string, any>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Active (non-archived) reservation locations, default first. [] when Table Reservations
|
|
31
|
+
* isn't set up. `onlineReservationsEnabled: false` means the premium-gated toggle is off —
|
|
32
|
+
* slots and booking won't work; render an honest "reservations aren't open yet" state.
|
|
33
|
+
*/
|
|
34
|
+
export async function fetchReservationLocations(): Promise<ReservationLocationInfo[]> {
|
|
35
|
+
const res: Raw = await locationsApi.listReservationLocations();
|
|
36
|
+
return (res.reservationLocations ?? [])
|
|
37
|
+
.filter((l: Raw) => l.archived !== true)
|
|
38
|
+
.map((l: Raw): ReservationLocationInfo => {
|
|
39
|
+
const online = l.configuration?.onlineReservations ?? {};
|
|
40
|
+
return {
|
|
41
|
+
id: l._id ?? "",
|
|
42
|
+
default: l.default === true,
|
|
43
|
+
partySizeMin: online.partySize?.min ?? 1,
|
|
44
|
+
partySizeMax: online.partySize?.max ?? 20,
|
|
45
|
+
approvalMode: online.approval?.mode ?? "AUTOMATIC",
|
|
46
|
+
onlineReservationsEnabled: online.onlineReservationsEnabled === true,
|
|
47
|
+
};
|
|
48
|
+
})
|
|
49
|
+
.filter((l: ReservationLocationInfo) => l.id)
|
|
50
|
+
.sort((a: ReservationLocationInfo, b: ReservationLocationInfo) => Number(b.default) - Number(a.default));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toSlot(raw: Raw): ReservationSlot {
|
|
54
|
+
const start: Date = raw.startDate instanceof Date ? raw.startDate : new Date(raw.startDate);
|
|
55
|
+
let label = "";
|
|
56
|
+
let dayKey = "";
|
|
57
|
+
try {
|
|
58
|
+
label = start.toLocaleTimeString(undefined, { hour: "numeric", minute: "2-digit" });
|
|
59
|
+
const pad = (n: number) => String(n).padStart(2, "0");
|
|
60
|
+
dayKey = `${start.getFullYear()}-${pad(start.getMonth() + 1)}-${pad(start.getDate())}`;
|
|
61
|
+
} catch {
|
|
62
|
+
/* label/dayKey stay "" — startIso still books */
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
startIso: start.toISOString(),
|
|
66
|
+
label,
|
|
67
|
+
dayKey,
|
|
68
|
+
durationMinutes: raw.duration ?? 0,
|
|
69
|
+
manualApproval: raw.manualApproval === true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* AVAILABLE reservation slots around a moment for a party size — UNAVAILABLE and
|
|
75
|
+
* NON_WORKING_HOURS slots are already filtered out (offering them makes the hold fail).
|
|
76
|
+
* `aroundIso` anchors the fan-out: slotsBefore/slotsAfter extra slots on each side.
|
|
77
|
+
*/
|
|
78
|
+
export async function fetchReservationSlots(
|
|
79
|
+
locationId: string,
|
|
80
|
+
aroundIso: string,
|
|
81
|
+
partySize: number,
|
|
82
|
+
{ slotsBefore = 6, slotsAfter = 6 }: { slotsBefore?: number; slotsAfter?: number } = {},
|
|
83
|
+
): Promise<ReservationSlot[]> {
|
|
84
|
+
// The date param is a Date — the SDK types it as Date, not the ISO string the docs show.
|
|
85
|
+
const res: Raw = await timeSlotsApi.getTimeSlots(locationId, new Date(aroundIso), partySize, {
|
|
86
|
+
slotsBefore,
|
|
87
|
+
slotsAfter,
|
|
88
|
+
});
|
|
89
|
+
return (res.timeSlots ?? [])
|
|
90
|
+
.filter((s: Raw) => s.status === "AVAILABLE")
|
|
91
|
+
.map((s: Raw) => toSlot(s));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Hold a slot for 10 minutes while the visitor enters their details. The returned hold
|
|
96
|
+
* carries the { reservationId, revision } that completeReservation NEEDS — keep both.
|
|
97
|
+
*/
|
|
98
|
+
export async function holdReservation(
|
|
99
|
+
locationId: string,
|
|
100
|
+
startIso: string,
|
|
101
|
+
partySize: number,
|
|
102
|
+
): Promise<ReservationHold> {
|
|
103
|
+
const res: Raw = await reservationsApi.createHeldReservation({
|
|
104
|
+
reservationLocationId: locationId,
|
|
105
|
+
startDate: new Date(startIso),
|
|
106
|
+
partySize,
|
|
107
|
+
});
|
|
108
|
+
const reservationId = res.reservation?._id;
|
|
109
|
+
const revision = res.reservation?.revision;
|
|
110
|
+
if (!reservationId || !revision) {
|
|
111
|
+
throw new Error("That time couldn't be held — it may have just been taken. Pick another slot.");
|
|
112
|
+
}
|
|
113
|
+
return { reservationId, revision, startIso, partySize };
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Complete a held reservation with the visitor's details. firstName and phone (E.164, e.g.
|
|
118
|
+
* "+15551234567") are REQUIRED. A hold expires after 10 minutes — on failure, start a fresh
|
|
119
|
+
* hold; never try to update a HELD reservation by other means.
|
|
120
|
+
*/
|
|
121
|
+
export async function completeReservation(
|
|
122
|
+
hold: Pick<ReservationHold, "reservationId" | "revision">,
|
|
123
|
+
reservee: ReservationReservee,
|
|
124
|
+
): Promise<ReservationConfirmation> {
|
|
125
|
+
if (!reservee.firstName?.trim() || !reservee.phone?.trim()) {
|
|
126
|
+
throw new Error("First name and phone number are required.");
|
|
127
|
+
}
|
|
128
|
+
const res: Raw = await reservationsApi.reserveReservation(
|
|
129
|
+
hold.reservationId,
|
|
130
|
+
{
|
|
131
|
+
firstName: reservee.firstName.trim(),
|
|
132
|
+
phone: reservee.phone.trim(),
|
|
133
|
+
...(reservee.lastName?.trim() ? { lastName: reservee.lastName.trim() } : {}),
|
|
134
|
+
...(reservee.email?.trim() ? { email: reservee.email.trim() } : {}),
|
|
135
|
+
},
|
|
136
|
+
hold.revision,
|
|
137
|
+
);
|
|
138
|
+
const reservation = res.reservation;
|
|
139
|
+
if (!reservation?._id) {
|
|
140
|
+
throw new Error("The reservation couldn't be completed — the hold may have expired. Start over.");
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
reservationId: reservation._id,
|
|
144
|
+
status: reservation.status === "REQUESTED" ? "REQUESTED" : "RESERVED",
|
|
145
|
+
};
|
|
146
|
+
}
|