@wix/agent-skills 1.19.2 → 1.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/wix-app/references/SERVICE_PLUGIN.md +14 -1
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +7 -6
- package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
- package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
- package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
- package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
- package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
- package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
- package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
- package/skills/wix-headless/references/CAPABILITIES.md +2 -2
- package/skills/wix-headless/references/inline-recipes/experience-store.md +4 -3
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +98 -18
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
- package/skills/wix-headless-fast/SKILL.md +200 -34
- package/skills/wix-headless-fast/entry/skill.md +10 -2
- package/skills/wix-headless-fast/install/attach.mjs +253 -0
- package/skills/wix-headless-fast/install/deploy.mjs +109 -9
- package/skills/wix-headless-fast/install/fast-path.mjs +28 -31
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +224 -60
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
- package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
- package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
- package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +179 -33
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
- package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
- package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +236 -62
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
- package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
- package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +239 -83
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
- package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
- package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
- package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +214 -26
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
- package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
- package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
- package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +314 -63
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
- package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
- package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +238 -59
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
- package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +212 -51
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
- package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
- package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +240 -47
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
- package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
- package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
- package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +55 -1
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
- package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
- package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
- package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +371 -81
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +136 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -130
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -182
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +53 -136
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +309 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +73 -230
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
- package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
- package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +25 -5
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +24 -0
- package/skills/wix-manage/SKILL.md +4 -1
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
- package/skills/wix-manage/references/events/create-wix-event.md +46 -0
- package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
- package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
- package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
- package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44.md +19 -4
- package/skills/wix-vibe-headless/platforms/generic.md +6 -0
- package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
- package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
- package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
- package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +42 -41
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +7 -1
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
- package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
// Table reservations (@wix/table-reservations) — the only file that touches raw
|
|
2
|
-
// entities
|
|
1
|
+
// Table reservations (@wix/table-reservations) over the SDK — the only file that touches raw
|
|
2
|
+
// reservation entities on this transport. Rules and mappers live in ./reservations-core (shared
|
|
3
|
+
// with the REST twin in references/restaurants/rest/); this file is the transport only. Copy
|
|
4
|
+
// as-is; extend by calling these exports, never by editing them.
|
|
3
5
|
//
|
|
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
6
|
// getTimeSlots takes POSITIONAL args and a Date (not an ISO string); reserveReservation takes
|
|
7
7
|
// THREE positional args (id, reservee, revision) and the only exit from HELD is reserve.
|
|
8
8
|
// Failures are loud — surface the message, don't swallow it.
|
|
@@ -12,20 +12,14 @@
|
|
|
12
12
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/reserve-reservation.md
|
|
13
13
|
import { reservationLocations, timeSlots, reservations as reservationsModule } from "@wix/table-reservations";
|
|
14
14
|
import { wixModule } from "../sdk";
|
|
15
|
-
import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
ReservationLocationInfo,
|
|
19
|
-
ReservationReservee,
|
|
20
|
-
ReservationSlot,
|
|
21
|
-
} from "./types";
|
|
15
|
+
import { availableSlots, reserveeBody, toConfirmation, toHold, toLocations } from "./reservations-core";
|
|
16
|
+
import type { Raw } from "./menu-core";
|
|
17
|
+
import type { ReservationConfirmation, ReservationHold, ReservationLocationInfo, ReservationReservee, ReservationSlot } from "./types";
|
|
22
18
|
|
|
23
19
|
const locationsApi = wixModule(reservationLocations);
|
|
24
20
|
const timeSlotsApi = wixModule(timeSlots);
|
|
25
21
|
const reservationsApi = wixModule(reservationsModule);
|
|
26
22
|
|
|
27
|
-
type Raw = Record<string, any>;
|
|
28
|
-
|
|
29
23
|
/**
|
|
30
24
|
* Active (non-archived) reservation locations, default first. [] when Table Reservations
|
|
31
25
|
* isn't set up. `onlineReservationsEnabled: false` means the premium-gated toggle is off —
|
|
@@ -33,41 +27,7 @@ type Raw = Record<string, any>;
|
|
|
33
27
|
*/
|
|
34
28
|
export async function fetchReservationLocations(): Promise<ReservationLocationInfo[]> {
|
|
35
29
|
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
|
-
};
|
|
30
|
+
return toLocations(res.reservationLocations ?? []);
|
|
71
31
|
}
|
|
72
32
|
|
|
73
33
|
/**
|
|
@@ -82,35 +42,21 @@ export async function fetchReservationSlots(
|
|
|
82
42
|
{ slotsBefore = 6, slotsAfter = 6 }: { slotsBefore?: number; slotsAfter?: number } = {},
|
|
83
43
|
): Promise<ReservationSlot[]> {
|
|
84
44
|
// 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
|
-
|
|
87
|
-
slotsAfter,
|
|
88
|
-
});
|
|
89
|
-
return (res.timeSlots ?? [])
|
|
90
|
-
.filter((s: Raw) => s.status === "AVAILABLE")
|
|
91
|
-
.map((s: Raw) => toSlot(s));
|
|
45
|
+
const res: Raw = await timeSlotsApi.getTimeSlots(locationId, new Date(aroundIso), partySize, { slotsBefore, slotsAfter });
|
|
46
|
+
return availableSlots(res.timeSlots ?? []);
|
|
92
47
|
}
|
|
93
48
|
|
|
94
49
|
/**
|
|
95
50
|
* Hold a slot for 10 minutes while the visitor enters their details. The returned hold
|
|
96
51
|
* carries the { reservationId, revision } that completeReservation NEEDS — keep both.
|
|
97
52
|
*/
|
|
98
|
-
export async function holdReservation(
|
|
99
|
-
locationId: string,
|
|
100
|
-
startIso: string,
|
|
101
|
-
partySize: number,
|
|
102
|
-
): Promise<ReservationHold> {
|
|
53
|
+
export async function holdReservation(locationId: string, startIso: string, partySize: number): Promise<ReservationHold> {
|
|
103
54
|
const res: Raw = await reservationsApi.createHeldReservation({
|
|
104
55
|
reservationLocationId: locationId,
|
|
105
56
|
startDate: new Date(startIso),
|
|
106
57
|
partySize,
|
|
107
58
|
});
|
|
108
|
-
|
|
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 };
|
|
59
|
+
return toHold(res, startIso, partySize);
|
|
114
60
|
}
|
|
115
61
|
|
|
116
62
|
/**
|
|
@@ -122,25 +68,7 @@ export async function completeReservation(
|
|
|
122
68
|
hold: Pick<ReservationHold, "reservationId" | "revision">,
|
|
123
69
|
reservee: ReservationReservee,
|
|
124
70
|
): Promise<ReservationConfirmation> {
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
};
|
|
71
|
+
const body = reserveeBody(reservee);
|
|
72
|
+
const res: Raw = await reservationsApi.reserveReservation(hold.reservationId, body, hold.revision);
|
|
73
|
+
return toConfirmation(res);
|
|
146
74
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// Menu reads over REST (Wix Restaurants Menus V1) — the twin of app/wix/restaurants/menu.ts. Same
|
|
2
|
+
// export, same MenuData tree; the rules and the id-array stitching come from menu-core (the SAME
|
|
3
|
+
// file the SDK transport uses, deployed flat next to this one by deploy.mjs --stack static), so
|
|
4
|
+
// this file is only the transport: one GET per list, literal paths and query params. Every call
|
|
5
|
+
// here is safe from a browser with a visitor token. Porting: keep the paths, keep the params, port
|
|
6
|
+
// the core once. Entities carry `id` here (the SDK spells it `_id`); the core accepts both.
|
|
7
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/menus/list-menus.md
|
|
8
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/menus/items/items/list-items.md
|
|
9
|
+
import { wixRequest } from "./client.js";
|
|
10
|
+
import { imgSrc } from "./media.js";
|
|
11
|
+
import { anyLabels, assembleMenus, collectGroupIds, collectModifierIds, collectVariantIds, type Raw } from "./menu-core.js";
|
|
12
|
+
import type { MenuData } from "./types.js";
|
|
13
|
+
|
|
14
|
+
const VISIBLE = { onlyVisible: "true" };
|
|
15
|
+
const get = (path: string, query?: Record<string, string | readonly string[]>) => wixRequest<Raw>(path, { method: "GET", query });
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The site's full menu tree, assembled and display-ordered — the one entry point for every menu
|
|
19
|
+
* surface; [] when no menus exist. Three list reads, then the referenced variants, modifier groups,
|
|
20
|
+
* and labels, then the groups' modifiers (an array query param repeats the key: ?variantIds=a&variantIds=b).
|
|
21
|
+
* GET /restaurants/menus-menu/v1/menus?onlyVisible=true
|
|
22
|
+
* GET /restaurants/menus-section/v1/sections?onlyVisible=true
|
|
23
|
+
* GET /restaurants/menus-item/v1/items?onlyVisible=true
|
|
24
|
+
* GET /restaurants/item-variants/v1/variants?variantIds=…
|
|
25
|
+
* GET /restaurants/item-modifier-group/v1/modifier-groups?modifierGroupIds=…
|
|
26
|
+
* GET /restaurants/item-modifiers/v1/modifiers?modifierIds=…
|
|
27
|
+
* GET /restaurants/item-labels/v1/labels
|
|
28
|
+
*/
|
|
29
|
+
export async function fetchMenus(): Promise<MenuData[]> {
|
|
30
|
+
const [menusRes, sectionsRes, itemsRes] = await Promise.all([
|
|
31
|
+
get("/restaurants/menus-menu/v1/menus", VISIBLE),
|
|
32
|
+
get("/restaurants/menus-section/v1/sections", VISIBLE),
|
|
33
|
+
get("/restaurants/menus-item/v1/items", VISIBLE),
|
|
34
|
+
]);
|
|
35
|
+
const menus: Raw[] = menusRes?.menus ?? [];
|
|
36
|
+
if (!menus.length) return [];
|
|
37
|
+
const items: Raw[] = itemsRes?.items ?? [];
|
|
38
|
+
const variantIds = collectVariantIds(items);
|
|
39
|
+
const groupIds = collectGroupIds(items);
|
|
40
|
+
const [variantsRes, groupsRes, labelsRes] = await Promise.all([
|
|
41
|
+
variantIds.length ? get("/restaurants/item-variants/v1/variants", { variantIds }) : Promise.resolve({ variants: [] } as Raw),
|
|
42
|
+
groupIds.length ? get("/restaurants/item-modifier-group/v1/modifier-groups", { modifierGroupIds: groupIds }) : Promise.resolve({ modifierGroups: [] } as Raw),
|
|
43
|
+
anyLabels(items) ? get("/restaurants/item-labels/v1/labels") : Promise.resolve({ labels: [] } as Raw),
|
|
44
|
+
]);
|
|
45
|
+
const groups: Raw[] = groupsRes?.modifierGroups ?? [];
|
|
46
|
+
const modifierIds = collectModifierIds(groups);
|
|
47
|
+
const modifiersRes: Raw = modifierIds.length ? await get("/restaurants/item-modifiers/v1/modifiers", { modifierIds }) : { modifiers: [] };
|
|
48
|
+
return assembleMenus(
|
|
49
|
+
{
|
|
50
|
+
menus,
|
|
51
|
+
sections: sectionsRes?.sections ?? [],
|
|
52
|
+
items,
|
|
53
|
+
variants: variantsRes?.variants ?? [],
|
|
54
|
+
groups,
|
|
55
|
+
modifiers: modifiersRes?.modifiers ?? [],
|
|
56
|
+
labels: labelsRes?.labels ?? [],
|
|
57
|
+
},
|
|
58
|
+
imgSrc,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Online ordering over REST (Restaurants Orders + Wix eCom Cart V2) — the twin of
|
|
2
|
+
// app/wix/restaurants/ordering.ts. Same exports, same OrderCart/OrderLine DTOs; rules and mappers
|
|
3
|
+
// from ordering-core (the SAME file the SDK transport uses, deployed flat next to this one). The
|
|
4
|
+
// request shapes are exact and rewriting them is how carts break: the Orders app id, and options
|
|
5
|
+
// { operationId, menuId, sectionId } — all three, no variantId. Failures are loud: a missing
|
|
6
|
+
// operation, an unavailable line, an empty order at checkout all throw — surface the message.
|
|
7
|
+
// All calls run with the visitor token: the cart is the token's (see ./client).
|
|
8
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/online-orders/operations/list-operations.md
|
|
9
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/online-orders/fulfillment-methods/list-fulfillment-methods.md
|
|
10
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/e-commerce/purchase-flow/cart-v2/add-line-items-to-current-cart.md
|
|
11
|
+
// docs: https://dev.wix.com/docs/api-reference/business-management/headless/redirects/create-redirect-session.md
|
|
12
|
+
import { WixApiError, wixRequest } from "./client.js";
|
|
13
|
+
import { imgSrc } from "./media.js";
|
|
14
|
+
import {
|
|
15
|
+
RESTAURANTS_ORDERS_APP_ID,
|
|
16
|
+
assertOrderAdded,
|
|
17
|
+
assertOrderCheckoutable,
|
|
18
|
+
assertOrderContext,
|
|
19
|
+
estimateSubtotal,
|
|
20
|
+
orderCatalogItem,
|
|
21
|
+
pickOperationId,
|
|
22
|
+
rawId,
|
|
23
|
+
toFulfillmentMethods,
|
|
24
|
+
toOrderCart,
|
|
25
|
+
} from "./ordering-core.js";
|
|
26
|
+
import type { Raw } from "./menu-core.js";
|
|
27
|
+
import type { FulfillmentMethodInfo, OrderCart } from "./types.js";
|
|
28
|
+
|
|
29
|
+
export { RESTAURANTS_ORDERS_APP_ID };
|
|
30
|
+
|
|
31
|
+
const CART = "/ecom/v2/carts/current";
|
|
32
|
+
|
|
33
|
+
// The ordering operation is site config — resolve once, reuse for every add.
|
|
34
|
+
// GET /restaurants-operations/v1/operations
|
|
35
|
+
let operationIdPromise: Promise<string | null> | null = null;
|
|
36
|
+
|
|
37
|
+
/** The operation to order through (ENABLED, else default, else first) — null when the site has no online ordering. */
|
|
38
|
+
export function resolveOperationId(): Promise<string | null> {
|
|
39
|
+
operationIdPromise ??= wixRequest<Raw>("/restaurants-operations/v1/operations", { method: "GET" })
|
|
40
|
+
.then((res) => pickOperationId(res?.operations ?? []))
|
|
41
|
+
.catch(() => {
|
|
42
|
+
operationIdPromise = null; // transient failure — allow a retry on the next call
|
|
43
|
+
return null;
|
|
44
|
+
});
|
|
45
|
+
return operationIdPromise;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Enabled pickup/delivery methods, for display. GET /fulfillment-methods/v1/fulfillment-methods */
|
|
49
|
+
export async function fetchFulfillmentMethods(): Promise<FulfillmentMethodInfo[]> {
|
|
50
|
+
try {
|
|
51
|
+
const res = await wixRequest<Raw>("/fulfillment-methods/v1/fulfillment-methods", { method: "GET" });
|
|
52
|
+
return toFulfillmentMethods(res?.fulfillmentMethods ?? []);
|
|
53
|
+
} catch {
|
|
54
|
+
return []; // display nicety — ordering still works without the list
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// A line's attributes.image may come back absent — join the menu items' images by the line's
|
|
59
|
+
// catalogItemId, once per item, cached for the session. GET /restaurants/menus-item/v1/items?itemIds=…
|
|
60
|
+
const lineImageCache = new Map<string, string>();
|
|
61
|
+
async function fillLineImages(cart: OrderCart, raws: Raw[]): Promise<void> {
|
|
62
|
+
const wanted = new Map<string, number[]>();
|
|
63
|
+
raws.forEach((raw, i) => {
|
|
64
|
+
if (cart.lines[i].imageUrl) return;
|
|
65
|
+
const itemId = raw.source?.catalogReference?.catalogItemId;
|
|
66
|
+
if (!itemId) return;
|
|
67
|
+
const cached = lineImageCache.get(itemId);
|
|
68
|
+
if (cached !== undefined) { cart.lines[i].imageUrl = cached; return; }
|
|
69
|
+
wanted.set(itemId, [...(wanted.get(itemId) ?? []), i]);
|
|
70
|
+
});
|
|
71
|
+
if (!wanted.size) return;
|
|
72
|
+
try {
|
|
73
|
+
const res = await wixRequest<Raw>("/restaurants/menus-item/v1/items", { method: "GET", query: { itemIds: [...wanted.keys()] } });
|
|
74
|
+
for (const item of (res?.items ?? []) as Raw[]) {
|
|
75
|
+
const url = imgSrc(item.image, 300, 300);
|
|
76
|
+
lineImageCache.set(rawId(item), url);
|
|
77
|
+
for (const i of wanted.get(rawId(item)) ?? []) cart.lines[i].imageUrl = url;
|
|
78
|
+
}
|
|
79
|
+
} catch {
|
|
80
|
+
/* images are a nicety — the cart stays correct without them */
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function readCartWithSubtotal(raw: Raw | null): Promise<OrderCart> {
|
|
85
|
+
// Only estimate a cart WITH lines — on an absent/empty cart the endpoint returns 404.
|
|
86
|
+
if (!raw?.lineItems?.length) return toOrderCart(raw, "", imgSrc);
|
|
87
|
+
// The after-discount subtotal comes from the estimate, never from hand-summing lines; fees, tax,
|
|
88
|
+
// and delivery resolve at checkout. POST /ecom/v2/carts/current/estimate {}
|
|
89
|
+
let subtotal = "";
|
|
90
|
+
try {
|
|
91
|
+
subtotal = estimateSubtotal(await wixRequest<Raw>(`${CART}/estimate`, { body: {} }), raw);
|
|
92
|
+
} catch {
|
|
93
|
+
/* the estimate is a display nicety — the cart itself is still valid */
|
|
94
|
+
}
|
|
95
|
+
const cart = toOrderCart(raw, subtotal, imgSrc);
|
|
96
|
+
await fillLineImages(cart, raw.lineItems as Raw[]);
|
|
97
|
+
return cart;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// GET /ecom/v2/carts/current — a 404 means no cart yet.
|
|
101
|
+
async function getCurrentCartRaw(): Promise<Raw | null> {
|
|
102
|
+
try {
|
|
103
|
+
return (await wixRequest<Raw>(CART, { method: "GET" }))?.cart ?? null;
|
|
104
|
+
} catch (e) {
|
|
105
|
+
if (e instanceof WixApiError && e.status === 404) return null;
|
|
106
|
+
throw e;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** The visitor's current order cart. An empty cart (not an error) when none exists yet. */
|
|
111
|
+
export async function fetchOrderCart(): Promise<OrderCart> {
|
|
112
|
+
return readCartWithSubtotal(await getCurrentCartRaw());
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Add a menu item to the current order. `menuId` and `sectionId` are the ids of the menu and
|
|
117
|
+
* section the item is RENDERED UNDER — thread them from the render context (the fetchMenus tree).
|
|
118
|
+
* Throws when ordering isn't configured or the line is refused (a refused add still returns 200).
|
|
119
|
+
* POST /ecom/v2/carts/current/add-line-items
|
|
120
|
+
* { catalogItems: [{ quantity, catalogReference: { catalogItemId, appId: <Orders app>, options: { operationId, menuId, sectionId } } }] }
|
|
121
|
+
*/
|
|
122
|
+
export async function addToOrder(
|
|
123
|
+
itemId: string,
|
|
124
|
+
{ menuId, sectionId }: { menuId: string; sectionId: string },
|
|
125
|
+
quantity = 1,
|
|
126
|
+
): Promise<OrderCart> {
|
|
127
|
+
const operationId = await resolveOperationId();
|
|
128
|
+
if (!operationId) throw new Error("Online ordering isn't available right now.");
|
|
129
|
+
assertOrderContext(itemId, menuId, sectionId);
|
|
130
|
+
const res = await wixRequest<Raw>(`${CART}/add-line-items`, {
|
|
131
|
+
body: { catalogItems: [orderCatalogItem(itemId, { operationId, menuId, sectionId }, quantity)] },
|
|
132
|
+
});
|
|
133
|
+
assertOrderAdded(res?.cart, itemId);
|
|
134
|
+
return readCartWithSubtotal(res?.cart ?? null);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Change a line's quantity. `lineItemId` is OrderLine.lineItemId, never the menu item id.
|
|
139
|
+
* POST /ecom/v2/carts/current/update-line-items { lineItems: [{ lineItemId, quantity: { newQuantity } }] }
|
|
140
|
+
*/
|
|
141
|
+
export async function updateOrderQuantity(lineItemId: string, quantity: number): Promise<OrderCart> {
|
|
142
|
+
const res = await wixRequest<Raw>(`${CART}/update-line-items`, { body: { lineItems: [{ lineItemId, quantity: { newQuantity: quantity } }] } });
|
|
143
|
+
return readCartWithSubtotal(res?.cart ?? null);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Remove a line by OrderLine.lineItemId. POST /ecom/v2/carts/current/remove-line-items { lineItemIds } */
|
|
147
|
+
export async function removeOrderLine(lineItemId: string): Promise<OrderCart> {
|
|
148
|
+
const res = await wixRequest<Raw>(`${CART}/remove-line-items`, { body: { lineItemIds: [lineItemId] } });
|
|
149
|
+
return readCartWithSubtotal(res?.cart ?? null);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Start the Wix-hosted checkout for the current order and return the URL to navigate the FULL
|
|
154
|
+
* document to. The cart's id IS the checkout id; fulfillment (pickup/delivery + time) and payment
|
|
155
|
+
* are collected on the hosted page. `origin` must be the site's real https origin as registered on
|
|
156
|
+
* the OAuth app's allowed domains (browser: window.location.origin) — an unlisted or http origin
|
|
157
|
+
* 403s on return.
|
|
158
|
+
* POST /headless/v1/redirect-session { ecomCheckout: { checkoutId }, callbacks: { postFlowUrl, thankYouPageUrl } }
|
|
159
|
+
*/
|
|
160
|
+
export async function orderCheckoutUrl(origin: string = typeof window !== "undefined" ? window.location.origin : ""): Promise<string> {
|
|
161
|
+
const raw = await getCurrentCartRaw();
|
|
162
|
+
assertOrderCheckoutable(raw);
|
|
163
|
+
const res = await wixRequest<Raw>("/headless/v1/redirect-session", {
|
|
164
|
+
body: { ecomCheckout: { checkoutId: rawId(raw) }, callbacks: origin ? { postFlowUrl: `${origin}/`, thankYouPageUrl: `${origin}/` } : {} },
|
|
165
|
+
});
|
|
166
|
+
const url = res?.redirectSession?.fullUrl;
|
|
167
|
+
if (!url) throw new Error("Checkout couldn't start: no redirect URL returned.");
|
|
168
|
+
return url;
|
|
169
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// Table reservations over REST — the twin of app/wix/restaurants/reservations.ts. Same exports,
|
|
2
|
+
// same DTOs; rules and mappers from reservations-core (the SAME file the SDK transport uses,
|
|
3
|
+
// deployed flat next to this one). The flow is location → AVAILABLE slots → hold → reserve; a
|
|
4
|
+
// reservation is a hold, not a purchase — no cart, no checkout. Every call here is a visitor's own
|
|
5
|
+
// action with the visitor token. The hold and reserve calls are premium-gated on the site: a
|
|
6
|
+
// non-premium site answers 428 "site must be premium" — surface it, don't retry.
|
|
7
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/time-slots/get-time-slots.md
|
|
8
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-held-reservation.md
|
|
9
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/reserve-reservation.md
|
|
10
|
+
import { wixRequest } from "./client.js";
|
|
11
|
+
import { availableSlots, reserveeBody, toConfirmation, toHold, toLocations } from "./reservations-core.js";
|
|
12
|
+
import type { Raw } from "./menu-core.js";
|
|
13
|
+
import type { ReservationConfirmation, ReservationHold, ReservationLocationInfo, ReservationReservee, ReservationSlot } from "./types.js";
|
|
14
|
+
|
|
15
|
+
const RESERVATIONS = "/table-reservations/reservations/v1";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Active (non-archived) locations, default first; [] when Table Reservations isn't set up.
|
|
19
|
+
* `onlineReservationsEnabled: false` → the premium-gated toggle is off; render an honest notice.
|
|
20
|
+
* GET /table-reservations/reservation-locations/v1/reservation-locations
|
|
21
|
+
*/
|
|
22
|
+
export async function fetchReservationLocations(): Promise<ReservationLocationInfo[]> {
|
|
23
|
+
const res = await wixRequest<Raw>("/table-reservations/reservation-locations/v1/reservation-locations", { method: "GET" });
|
|
24
|
+
return toLocations(res?.reservationLocations ?? []);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* AVAILABLE slots around a moment for a party size (UNAVAILABLE / NON_WORKING_HOURS dropped).
|
|
29
|
+
* POST /table-reservations/reservations/v1/time-slots { reservationLocationId, date, partySize, slotsBefore, slotsAfter }
|
|
30
|
+
*/
|
|
31
|
+
export async function fetchReservationSlots(
|
|
32
|
+
locationId: string,
|
|
33
|
+
aroundIso: string,
|
|
34
|
+
partySize: number,
|
|
35
|
+
{ slotsBefore = 6, slotsAfter = 6 }: { slotsBefore?: number; slotsAfter?: number } = {},
|
|
36
|
+
): Promise<ReservationSlot[]> {
|
|
37
|
+
const res = await wixRequest<Raw>(`${RESERVATIONS}/time-slots`, {
|
|
38
|
+
body: { reservationLocationId: locationId, date: new Date(aroundIso).toISOString(), partySize, slotsBefore, slotsAfter },
|
|
39
|
+
});
|
|
40
|
+
return availableSlots(res?.timeSlots ?? []);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Hold a slot for 10 minutes while the visitor enters their details; the hold's { reservationId, revision } feed completeReservation.
|
|
45
|
+
* POST /table-reservations/reservations/v1/reservations/hold { reservationDetails: { reservationLocationId, startDate, partySize } }
|
|
46
|
+
*/
|
|
47
|
+
export async function holdReservation(locationId: string, startIso: string, partySize: number): Promise<ReservationHold> {
|
|
48
|
+
const res = await wixRequest<Raw>(`${RESERVATIONS}/reservations/hold`, {
|
|
49
|
+
body: { reservationDetails: { reservationLocationId: locationId, startDate: new Date(startIso).toISOString(), partySize } },
|
|
50
|
+
});
|
|
51
|
+
return toHold(res, startIso, partySize);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Complete a held reservation. firstName and phone (E.164) are REQUIRED; an expired hold can't be
|
|
56
|
+
* reserved — start a fresh hold.
|
|
57
|
+
* POST /table-reservations/reservations/v1/reservations/{reservationId}/reserve { revision, reservee: { firstName, phone, lastName?, email? } }
|
|
58
|
+
*/
|
|
59
|
+
export async function completeReservation(
|
|
60
|
+
hold: Pick<ReservationHold, "reservationId" | "revision">,
|
|
61
|
+
reservee: ReservationReservee,
|
|
62
|
+
): Promise<ReservationConfirmation> {
|
|
63
|
+
const body = reserveeBody(reservee);
|
|
64
|
+
const res = await wixRequest<Raw>(`${RESERVATIONS}/reservations/${encodeURIComponent(hold.reservationId)}/reserve`, {
|
|
65
|
+
body: { revision: hold.revision, reservee: body },
|
|
66
|
+
});
|
|
67
|
+
return toConfirmation(res);
|
|
68
|
+
}
|
|
@@ -67,6 +67,14 @@ restaurant that takes orders and reservations:
|
|
|
67
67
|
**seeding is strictly additive — never delete or overwrite existing content**; ask first
|
|
68
68
|
if a cleanup seems needed.
|
|
69
69
|
|
|
70
|
+
## Supplied content
|
|
71
|
+
|
|
72
|
+
The general rules are in `references/shared/SUPPLIED-CONTENT.md`. For a restaurant the user hands
|
|
73
|
+
over the menu, often as a PDF or a photo. Each menu becomes an entry in `menus`, each heading a
|
|
74
|
+
`section`, each dish an `item` with `name`, `description`, `price` and, when they supplied one, its
|
|
75
|
+
photo as `imageUrl`. The restaurant's address, hours and party sizes go into `ordering` and
|
|
76
|
+
`reservations` only when the user stated them; otherwise leave those out and say so.
|
|
77
|
+
|
|
70
78
|
## Escape hatch — individual functions
|
|
71
79
|
`setupRestaurants` composes exported steps — `installMenusApp`, `installOrdersApp`,
|
|
72
80
|
`installTableReservationsApp`, `removeSampleMenu`, `createMenu`, `importImage`,
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Supplied content — when the user brings their own
|
|
2
|
+
|
|
3
|
+
When the brief supplies the content in any form — a CSV, JSON or spreadsheet, a list typed in the
|
|
4
|
+
prompt, a PDF or an image of a price list or a menu, a folder of photos with a text file beside
|
|
5
|
+
it, a link to a page that lists it — that source is the seed plan. The vertical's default count
|
|
6
|
+
("start with 3", "a few posts") applies only to catalogs you draft yourself.
|
|
7
|
+
|
|
8
|
+
Read it however it needs to be read: parse the file (a quoted comma inside a description is
|
|
9
|
+
common), open the PDF, fetch the page, look at the images. Then map, don't author:
|
|
10
|
+
|
|
11
|
+
- Every entry in the source becomes an entry in the plan. Names, titles, descriptions, prices,
|
|
12
|
+
dates verbatim; never rename, reprice, reword, reorder or add.
|
|
13
|
+
- Column and field names vary; map by meaning to the fields the vertical's `SEED.md` plan
|
|
14
|
+
shape defines. Anything the plan has no field for is dropped — say which columns you dropped.
|
|
15
|
+
- Their images only: an image URL column → the plan's image URL field, verified with `curl -sI`
|
|
16
|
+
→ 200; a local file → the plan's image path field. An entry with no image is seeded without one
|
|
17
|
+
and listed in your summary. **Never an `imagePrompt` beside supplied content** — these are their
|
|
18
|
+
things, not a mood board.
|
|
19
|
+
- Currency, time zone, address: only when the source or the brief states them.
|
|
20
|
+
- Show the user the count you read next to the count you seeded, and the mapping you applied.
|
|
21
|
+
|
|
22
|
+
The vertical's `SEED.md` says what an entry is for that vertical and which of its plan fields the
|
|
23
|
+
usual columns land in.
|
|
@@ -23,5 +23,59 @@ export function imgSrc(value: MediaLike, width = 600, height = 600): string {
|
|
|
23
23
|
if (typeof v === "string" && v.startsWith("wix:image://")) {
|
|
24
24
|
return media.getScaledToFillImageUrl(v, width, height, {});
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
if (typeof v !== "string") return "";
|
|
27
|
+
// An absolute Wix media URL (bare, or already carrying a /v1/fill/ segment at some other size):
|
|
28
|
+
// re-issue it through the scaler at the requested size, so every image path lands on one shape.
|
|
29
|
+
const m = v.match(/^https:\/\/static\.wixstatic\.com\/media\/([^/?#]+)/);
|
|
30
|
+
if (m) return `https://static.wixstatic.com/media/${m[1]}/v1/fill/w_${width},h_${height},al_c,q_90/${m[1]}`;
|
|
31
|
+
return v;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Responsive candidates for `srcset`: the same image at several widths through Wix's scaler, so a
|
|
36
|
+
* card never downloads a hero-sized file. Pair with `sizes`:
|
|
37
|
+
* <img src={imgSrc(m, 640, 640)} srcSet={imgSrcSet(m)} sizes="(min-width: 1024px) 25vw, 50vw"
|
|
38
|
+
* width={640} height={640} loading="lazy" alt={…} />
|
|
39
|
+
* Accepts every form an image reaches you in: a raw `wix:image://` id, an ALREADY-RESOLVED Wix URL
|
|
40
|
+
* (what the DTOs carry — `imageUrl`, `gallery[]`; its `/v1/fill/w_…,h_…` segment is rewritten per
|
|
41
|
+
* width), or any other https URL (returned as the single candidate). `ratio` is height/width
|
|
42
|
+
* (1 = square). "" only when there is no image at all — still set `src` as well.
|
|
43
|
+
*/
|
|
44
|
+
export function imgSrcSet(value: MediaLike, widths: number[] = [320, 480, 640, 960], ratio = 1): string {
|
|
45
|
+
const raw = typeof value === "object" && value !== null ? (value.image ?? value.url ?? "") : (value ?? "");
|
|
46
|
+
if (typeof raw !== "string" || !raw) return "";
|
|
47
|
+
if (raw.startsWith("wix:image://")) {
|
|
48
|
+
return widths.map((w) => `${imgSrc(value, w, Math.round(w * ratio))} ${w}w`).join(", ");
|
|
49
|
+
}
|
|
50
|
+
// A resolved Wix URL: …/v1/fill/w_800,h_800,al_c,… — swap the size for each width.
|
|
51
|
+
const FILL = /\/v1\/fill\/w_\d+,h_\d+/;
|
|
52
|
+
if (/static\.wixstatic\.com\//.test(raw) && FILL.test(raw)) {
|
|
53
|
+
return widths.map((w) => `${raw.replace(FILL, `/v1/fill/w_${w},h_${Math.round(w * ratio)}`)} ${w}w`).join(", ");
|
|
54
|
+
}
|
|
55
|
+
return raw; // one candidate, never an empty srcset for a real image
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Everything an <img> needs for one Wix image, so a tile can never ship `srcSet` without `src`:
|
|
60
|
+
* <img {...imgAttrs(p.imageUrl, "(min-width: 1024px) 25vw, 50vw")} alt={p.name} />
|
|
61
|
+
* `sizes` is the width the image renders at (a CSS length or media-query list); `ratio` is
|
|
62
|
+
* height/width. Returns an empty object when there is no image — render your placeholder then.
|
|
63
|
+
*/
|
|
64
|
+
export function imgAttrs(value: MediaLike, sizes: string, ratio = 1): { src: string; srcSet: string; sizes: string; loading: "lazy"; decoding: "async" } | Record<string, never> {
|
|
65
|
+
const src = imgSrc(value, 640, Math.round(640 * ratio));
|
|
66
|
+
if (!src) return {};
|
|
67
|
+
return { src, srcSet: imgSrcSet(value, undefined, ratio), sizes, loading: "lazy", decoding: "async" };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The identity of a media value BEFORE scaling — de-duplicate galleries on this, never on a
|
|
72
|
+
* resolved URL (two scaled URLs of one photo differ in their size parameters).
|
|
73
|
+
*/
|
|
74
|
+
export function mediaKey(value: MediaLike): string {
|
|
75
|
+
const v = typeof value === "object" && value !== null ? (value.image ?? value.url ?? "") : (value ?? "");
|
|
76
|
+
if (!v) return "";
|
|
77
|
+
// The file id, whichever form the value takes — a raw id and a resolved URL of one photo share it.
|
|
78
|
+
if (v.startsWith("wix:image://")) return v.slice("wix:image://v1/".length).split("/")[0].split("#")[0];
|
|
79
|
+
const m = v.match(/^https:\/\/static\.wixstatic\.com\/media\/([^/?#]+)/);
|
|
80
|
+
return m ? m[1] : v;
|
|
27
81
|
}
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
// docs: https://dev.wix.com/docs/sdk/core-modules/sdk/oauth-strategy.md
|
|
16
16
|
// docs: https://dev.wix.com/docs/go-headless/wix-managed-headless/authentication/about-the-astro-integration.md
|
|
17
17
|
import { createClient, OAuthStrategy, EMPTY_TOKENS } from "@wix/sdk";
|
|
18
|
+
import { fetchWithAuth as ambientFetch } from "@wix/sdk/context";
|
|
18
19
|
import type { IOAuthStrategy, TokenStorage, Tokens } from "@wix/sdk";
|
|
19
20
|
import { WIX_CLIENT_ID } from "./config";
|
|
20
21
|
|
|
@@ -66,6 +67,18 @@ export function wixModule<T>(module: T): T {
|
|
|
66
67
|
: module;
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* An authenticated fetch of a Wix REST endpoint under the SAME auth as the SDK modules (ambient
|
|
72
|
+
* on managed Astro, the shared client elsewhere) — for the few APIs whose generated SDK module is
|
|
73
|
+
* too heavy to bundle (the Forms schema module alone is 15 MB: server-side it pushes a Wix
|
|
74
|
+
* deploy past its size limit, client-side every visitor downloads it). Same visitor identity,
|
|
75
|
+
* same token, no second mint. `path` is the wixapis.com path; returns the raw Response.
|
|
76
|
+
*/
|
|
77
|
+
export function wixFetch(path: string, init?: RequestInit): Promise<Response> {
|
|
78
|
+
const url = path.startsWith("http") ? path : `https://www.wixapis.com${path}`;
|
|
79
|
+
return client ? client.fetchWithAuth(url, init) : ambientFetch(url, init);
|
|
80
|
+
}
|
|
81
|
+
|
|
69
82
|
/**
|
|
70
83
|
* The shared data client's manual-mode auth strategy, or null under ambient auth. The
|
|
71
84
|
* Members vertical deliberately owns a separate explicit client for its custom in-app login;
|