@wix/agent-skills 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -7
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +2 -0
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +2 -1
- package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +14 -0
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +11 -6
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +3 -17
- package/skills/wix-app/references/editor-react-component/SSR.md +119 -0
- package/skills/wix-design-system/SKILL.md +5 -1
- package/skills/wix-headless/SKILL.md +55 -32
- package/skills/wix-headless/references/BUILD-astro.md +244 -0
- package/skills/wix-headless/references/BUILD-own-build.md +167 -0
- package/skills/wix-headless/references/BUILD.md +50 -252
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +94 -34
- package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
- package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
- package/skills/wix-headless/references/DISCOVERY.md +41 -422
- package/skills/wix-headless/references/PLAN-connect.md +19 -0
- package/skills/wix-headless/references/PLAN-create.md +17 -0
- package/skills/wix-headless/references/PLAN.md +37 -27
- package/skills/wix-headless/references/SEED.md +27 -18
- package/skills/wix-headless/references/SETUP.md +32 -213
- package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +5 -4
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +45 -0
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +58 -0
- package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +5 -5
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +10 -10
- package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +12 -13
- package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +10 -12
- package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +8 -8
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +47 -27
- package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +7 -8
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +5 -5
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +5 -19
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
- package/skills/wix-headless/references/astro/gift-cards/PAGES.md +1 -1
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +6 -6
- package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +4 -4
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +3 -3
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +9 -9
- package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +5 -4
- package/skills/wix-headless/references/astro/templates/Footer.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Layout.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Navigation.astro +1 -1
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +1 -1
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +233 -0
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +136 -0
- package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +54 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +49 -0
- package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +245 -0
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +356 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +133 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +73 -0
- package/skills/wix-headless/references/astro/templates/global.css +2 -2
- package/skills/wix-headless/references/astro/templates/index.astro +1 -1
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +19 -9
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/bookings/FLOW.md +185 -0
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +90 -0
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +4 -2
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +27 -1
- package/skills/wix-headless/references/commands/known-apps.json +2 -1
- package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
- package/skills/wix-headless/references/custom/INSTRUCTIONS.md +71 -21
- package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
- package/skills/wix-headless/references/custom/bookings/WIRING.md +146 -0
- package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
- package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
- package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
- package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +5 -5
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +2 -2
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +16 -15
- package/skills/wix-headless/references/seed-recipes.md +2 -2
- package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +3 -5
- package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +29 -28
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +87 -27
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +2 -2
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +24 -313
- package/skills/wix-headless/references/shared/STYLING.md +8 -18
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +39 -9
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +17 -14
- package/skills/wix-headless/references/verticals/_schema.json +1 -1
- package/skills/wix-headless/references/verticals/_schema.md +3 -3
- package/skills/wix-headless/references/verticals/blog.md +1 -1
- package/skills/wix-headless/references/verticals/bookings.md +39 -0
- package/skills/wix-headless/references/verticals/cms.md +1 -1
- package/skills/wix-headless/references/verticals/ecom.md +2 -2
- package/skills/wix-headless/references/verticals/gift-cards.md +1 -1
- package/skills/wix-headless/references/verticals/stores.md +1 -1
- package/skills/wix-headless/scripts/check-manifest.mjs +143 -6
- package/skills/wix-headless/scripts/compose.mjs +540 -0
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +128 -51
- package/skills/wix-headless/scripts/flatten.sh +61 -0
- package/skills/wix-headless/scripts/generate-images.mjs +242 -0
- package/skills/wix-headless/scripts/scaffold.sh +47 -16
- package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
- package/skills/wix-manage/SKILL.md +13 -1
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -27
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +1 -1
- package/skills/wix-manage/references/bookings/create-appointment-service.md +266 -0
- package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +37 -0
- package/skills/wix-manage/references/bookings/create-class-service.md +245 -0
- package/skills/wix-manage/references/bookings/create-course-service.md +249 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +3 -2
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +1 -1
- package/skills/wix-manage/references/cms/cms-schema-management.md +2 -2
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
- package/skills/wix-manage/references/forms/create-form.md +49 -1
- package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1 -1
- package/skills/wix-manage/references/stores/create-product-from-image.md +154 -2
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +1 -1
- package/skills/wix-headless/references/astro/COMPOSE.md +0 -142
- package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
- package/skills/wix-headless/scripts/init-site-json.mjs +0 -101
- package/skills/wix-headless/scripts/preview.sh +0 -45
- package/skills/wix-headless/scripts/release.sh +0 -73
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Bookings — the booking flow (framework-agnostic logic)
|
|
2
|
+
|
|
3
|
+
The booking logic — the step model, the shared state, and the exact `@wix` SDK
|
|
4
|
+
sequence — is the **same on every frontend** (astro, react/vite, vue, plain HTML).
|
|
5
|
+
This file is that shared logic. The per-framework wiring layers on top:
|
|
6
|
+
|
|
7
|
+
- **astro** (`frontendBuild: wix`): `../astro/bookings/SERVICES_PAGES.md` (SSR read pages) + `../astro/bookings/COMPONENTS.md` (the client islands).
|
|
8
|
+
- **own / static** (`frontendBuild: own`/`none`): `../custom/bookings/WIRING.md`.
|
|
9
|
+
|
|
10
|
+
**Code examples are React** (`../astro/templates/bookings/*.tsx` + `bookingDriver.ts`).
|
|
11
|
+
React is directly usable on astro islands and on react/vite. **On any other
|
|
12
|
+
framework, translate the examples** — the SDK calls and payloads are identical;
|
|
13
|
+
only the UI idiom (`useState`/`useEffect` → a store / `onMounted` / signals) and
|
|
14
|
+
the client acquisition differ (see "Client identity" below).
|
|
15
|
+
|
|
16
|
+
The flow is **appointment booking**. CLASS sign-up reuses the same sequence with
|
|
17
|
+
an event-slot branch (noted inline) and is a fast-follow, not v1. **Location and
|
|
18
|
+
staff selection are likewise a fast-follow (a separate PR); add-ons are out of
|
|
19
|
+
scope.** v1 threads a single service selection — the shared state below holds no
|
|
20
|
+
location / staff / add-on choice yet.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. The step model
|
|
25
|
+
|
|
26
|
+
A single customer journey, five steps, holding one shared selection:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
1. Catalog list bookable services → pick a service
|
|
30
|
+
2. Slots week calendar of availability for that service → pick a time slot
|
|
31
|
+
3. Details the service's booking form (collect contact) → submit
|
|
32
|
+
4. Book run the SDK sequence (§3)
|
|
33
|
+
5a. paid → redirect to the Wix-hosted checkout → returns to the confirmation page
|
|
34
|
+
5b. free/offline → place the order → confirmation page
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**The catalog is the entry point but optional** — a visitor can land directly on a
|
|
38
|
+
service page and start at the Slots step. Every step rehydrates from the URL (§2),
|
|
39
|
+
so deep links work.
|
|
40
|
+
|
|
41
|
+
Mapping the steps onto pages/routes is the **target framework's choice** — translate
|
|
42
|
+
the step model to whatever router the framework uses. The skill recommends the routes
|
|
43
|
+
`/services`, `/services/[slug]`, `/booking-confirmation` (advertised in
|
|
44
|
+
`../verticals/bookings.md`). On astro specifically, SSR the catalog + detail pages
|
|
45
|
+
for SEO and run slots + form + book in a `client:only` island.
|
|
46
|
+
|
|
47
|
+
## 2. Shared booking state
|
|
48
|
+
|
|
49
|
+
Steps 1–4 build up one selection (this is exactly `BookParams` + `SelectedSlot`
|
|
50
|
+
in `../astro/templates/bookings/bookingDriver.ts`):
|
|
51
|
+
|
|
52
|
+
- **`service`** — the chosen service object (the booking step reads `service.payment` + `service.bookingPolicy`).
|
|
53
|
+
- **`slot`** — `{ serviceType, serviceId, localStartDate, localEndDate, timezone, scheduleId?, eventId?, locationId?, locationType? }`.
|
|
54
|
+
- **`formSubmission`** — the booking-form values, **keyed by each field's `target`** (§4).
|
|
55
|
+
|
|
56
|
+
Hold it however the framework prefers (React state lifted to a coordinator, a
|
|
57
|
+
store, route loaders + a provider, query-params). **Two rules that bite:**
|
|
58
|
+
|
|
59
|
+
1. **Persist the selection before navigating.** When a step lives on its own
|
|
60
|
+
route, write the selection into the shared state (or the URL) *before* moving
|
|
61
|
+
on, and let the next step **rehydrate from the slug/URL** on direct load /
|
|
62
|
+
refresh. A persistent provider that does not re-init on in-app navigation will
|
|
63
|
+
otherwise show an empty next step.
|
|
64
|
+
2. **The slug is the carry-across key.** `serviceSlug` (the service's
|
|
65
|
+
`mainSlug.name`) identifies the service across the slots + details steps;
|
|
66
|
+
resolve the full service from it when state is lost.
|
|
67
|
+
|
|
68
|
+
## 3. The booking SDK sequence — `bookingDriver.ts`
|
|
69
|
+
|
|
70
|
+
The whole booking — `createBooking → createCart → calculateCart → checkout-or-place`
|
|
71
|
+
— is in **`../astro/templates/bookings/bookingDriver.ts`**, in plain `@wix` SDK
|
|
72
|
+
calls (no React, no UI). **Use it as-is** (it is framework-agnostic); the booking
|
|
73
|
+
step calls `book(params)` then branches on the result. Do not re-author the
|
|
74
|
+
sequence — the payload shapes are exact and easy to get subtly wrong.
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
book({ service, slot, formSubmission, timezone }) →
|
|
78
|
+
createBooking(...) // booking lands CREATED — the cart holds the seat
|
|
79
|
+
createCart(...) // one catalog item per bookingId, appId = BOOKING_APP_ID, channel WEB
|
|
80
|
+
calculateCart(cartId) // → { cart, summary }; totals on summary.priceSummary.total.amount
|
|
81
|
+
isCheckoutRequired(...) ?
|
|
82
|
+
→ { CheckoutRequired, cartId } // paid → navigateToCheckout(cartId, postFlowUrl) → Wix-hosted checkout
|
|
83
|
+
: placeOrder(cartId) → { CheckoutSkipped, orderId } // free / pay-in-person → confirmation
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Key facts the driver encodes (do not deviate):
|
|
87
|
+
- **No `confirmBooking`** — `confirmBooking` is the classic Bookings server-side
|
|
88
|
+
confirm step (it moves a booking from `CREATED` to `CONFIRMED`). Here the **ecom
|
|
89
|
+
cart holds the seat** instead — `placeOrder` (free/offline) or the hosted checkout
|
|
90
|
+
(paid) drives confirmation — so a client-only site completes the whole flow with no
|
|
91
|
+
server elevation.
|
|
92
|
+
- **ANY_RESOURCE fallback (staff)** — v1 has **no staff picker**, so `createBooking`
|
|
93
|
+
always sends `resourceSelections:[{ resourceTypeId:"1cd44cf8-…", selectionMethod:"ANY_RESOURCE" }]`
|
|
94
|
+
and Wix auto-assigns a bookable staff resource (appointment slots return
|
|
95
|
+
`availableResources:[]` yet book fine this way). (Explicit staff selection is a
|
|
96
|
+
fast-follow.)
|
|
97
|
+
- **Checkout decision** — `isCheckoutRequired`: if the service's booking policy charges
|
|
98
|
+
a **cancellation fee** (`service.bookingPolicy.cancellationFeePolicy.enabled`) →
|
|
99
|
+
checkout (a card must be on file); else total 0 → place; else `FULL_PAYMENT_OFFLINE`
|
|
100
|
+
→ place; else checkout.
|
|
101
|
+
- **Redirect shape** — paid bookings hand the cart to the Wix-hosted **ecom**
|
|
102
|
+
checkout: `createRedirectSession({ ecomCheckout: { checkoutId: cartId }, callbacks:{ postFlowUrl } })`.
|
|
103
|
+
|
|
104
|
+
### Browse + availability (the read calls the steps make)
|
|
105
|
+
|
|
106
|
+
Mirror what the Bookings SDK does (`services.queryServices` is the raw SDK, not a component):
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
// Catalog (list) — the filter MUST include appId; pass conditionalFields, then .find():
|
|
110
|
+
services.queryServices({
|
|
111
|
+
query: { filter: { appId: BOOKING_APP_ID }, paging: { limit: 100 } },
|
|
112
|
+
conditionalFields: ["STAFF_MEMBER_DETAILS"],
|
|
113
|
+
}).find(); // → result.items ; filter out s.hidden
|
|
114
|
+
|
|
115
|
+
// Single service by slug — the .eq() builder chain:
|
|
116
|
+
services.queryServices({ conditionalFields: ["STAFF_MEMBER_DETAILS"] })
|
|
117
|
+
.eq("mainSlug.name", slug).eq("appId", BOOKING_APP_ID).limit(1).find();
|
|
118
|
+
|
|
119
|
+
// Availability (APPOINTMENT) — serviceId is a single GUID STRING:
|
|
120
|
+
availabilityTimeSlots.listAvailabilityTimeSlots({
|
|
121
|
+
serviceId, fromLocalDate, toLocalDate, timeZone, bookable: true, cursorPaging: { limit: 100 },
|
|
122
|
+
}); // slots: result.timeSlots[] (localStartDate/localEndDate/scheduleId at top level)
|
|
123
|
+
|
|
124
|
+
// Availability (CLASS) — different namespace, PLURAL serviceIds; slots carry eventInfo.eventId, no scheduleId:
|
|
125
|
+
eventTimeSlots.listEventTimeSlots({ serviceIds: [serviceId], fromLocalDate, toLocalDate, timeZone, includeNonBookable: false });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`fromLocalDate`/`toLocalDate` are **local** date strings `YYYY-MM-DDThh:mm:ss`
|
|
129
|
+
(no `Z`) with an explicit `timeZone`.
|
|
130
|
+
|
|
131
|
+
## 4. The booking form — schema-driven (`@wix/forms`)
|
|
132
|
+
|
|
133
|
+
The booking form is a **`@wix/forms` form** attached to the service
|
|
134
|
+
(`service.form._id`, namespace `wix.bookings.v2.bookings`). Render it
|
|
135
|
+
**schema-driven** — read the field list and render inputs by field type — the
|
|
136
|
+
**same renderer the forms vertical uses** (`../astro/forms/CONTACT_FORM.md`):
|
|
137
|
+
|
|
138
|
+
1. Fetch the form by `service.form._id` (`@wix/forms` `getForm`; server-side/elevated
|
|
139
|
+
where the framework allows). Read `form.formFields`.
|
|
140
|
+
2. Keep `fieldType === "INPUT" && !hidden` **and** a recognized string
|
|
141
|
+
`componentType` (`TEXT_INPUT` / `PHONE_INPUT` / `DROPDOWN`). For each, take
|
|
142
|
+
`inputOptions.target`, `inputOptions.required`, `inputOptions.stringOptions.componentType`,
|
|
143
|
+
and the label from `inputOptions.stringOptions.{textInputOptions|dropdownOptions|phoneInputOptions}.label`.
|
|
144
|
+
**Skip complex, object-valued fields** — e.g. a multi-line `ADDRESS` — they carry
|
|
145
|
+
no string `componentType`; rendering them as a text input sends a string and
|
|
146
|
+
`createBooking` rejects it with **"must be object"**. The booking enforces only
|
|
147
|
+
the contact basics (`first_name`/`last_name`/`email`), so omitting an optional
|
|
148
|
+
complex field is safe (do **not** default unknown field types to a text input).
|
|
149
|
+
3. Render generic inputs by `componentType`: `TEXT_INPUT` → text input,
|
|
150
|
+
`DROPDOWN` → select, `PHONE_INPUT` → `type=tel`; treat `identifier === "TEXT_AREA"`
|
|
151
|
+
(or `target` containing `message`) as a textarea (its `componentType` is still `TEXT_INPUT`).
|
|
152
|
+
4. Collect the values into an object **keyed by each field's `target`** — that is
|
|
153
|
+
`formSubmission`. Pass it to `book()`. Only include fields the visitor filled;
|
|
154
|
+
never send a value for a field you didn't render.
|
|
155
|
+
|
|
156
|
+
**Do not** submit `contactDetails`, and **do not** hardcode field names — key by
|
|
157
|
+
`target`. The default booking form's targets are snake_case `first_name` /
|
|
158
|
+
`last_name` / `email` / `phone`. Reference example: `../astro/templates/bookings/BookingForm.tsx`.
|
|
159
|
+
|
|
160
|
+
## 5. The day-calendar (slots step)
|
|
161
|
+
|
|
162
|
+
Show a **week calendar**, not a flat list of every slot: a 7-day strip with
|
|
163
|
+
week navigation → the picked day's times. A flat grid with time-only labels
|
|
164
|
+
leaves the visitor unable to tell which day a slot is on. Fetch availability for
|
|
165
|
+
the visible window (`fromLocalDate`/`toLocalDate` = the week bounds), group slots
|
|
166
|
+
by calendar day, and offer a **"check next availability"** action that probes
|
|
167
|
+
forward when a week is empty. Reference example:
|
|
168
|
+
`../astro/templates/bookings/AvailabilityCalendar.tsx`.
|
|
169
|
+
|
|
170
|
+
## 6. Client identity (per framework)
|
|
171
|
+
|
|
172
|
+
The SDK calls are identical everywhere; only how you get the client differs:
|
|
173
|
+
- **astro** — SSR read pages use the ambient `@wix/essentials` client
|
|
174
|
+
(`auth.elevate(services.queryServices)(...)`); browser islands call the `@wix`
|
|
175
|
+
modules **ambiently** too (the `@wix/astro` visitor client, like the ecom
|
|
176
|
+
`CartView` island) — no `createClient`/`OAuthStrategy`.
|
|
177
|
+
- **own / static** — acquire a visitor client once:
|
|
178
|
+
`createClient({ modules, auth: OAuthStrategy({ clientId: appId }) })` and call
|
|
179
|
+
the same functions on it. CDN imports for `none`, bundled for `own`.
|
|
180
|
+
|
|
181
|
+
## 7. Out of v1
|
|
182
|
+
|
|
183
|
+
Waitlist, on-site manage/cancel, location selector, staff filter, payment
|
|
184
|
+
breakdown, and CLASS sign-up are out of v1. Show bookable slots only;
|
|
185
|
+
post-booking self-service is handled by the Wix-hosted flow / member area.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: bookings-implementer
|
|
3
|
+
description: "Implements the Wix Bookings vertical — services catalog, a service detail page with a week-calendar availability picker, a schema-driven booking form, and confirmation. The booking runs client-side via the @wix SDK (ecom Cart V2 — no confirmBooking). Scopes: seed, components, pages. Extends references/shared/IMPLEMENTER.md."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Bookings Implementer
|
|
7
|
+
|
|
8
|
+
Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, the `.wix/seeded.json` read pattern, the return contract, style conventions, and common failure modes.
|
|
9
|
+
|
|
10
|
+
## The logic lives in one place
|
|
11
|
+
|
|
12
|
+
The booking **logic** — the step model, the shared selection state, the exact
|
|
13
|
+
`@wix` SDK sequence, and the schema-driven form — is framework-agnostic and lives
|
|
14
|
+
in **`./FLOW.md`**. Read it first. The astro guides below are the astro *wiring* of
|
|
15
|
+
that logic; the code examples under `<SKILL_ROOT>/references/astro/templates/bookings/`
|
|
16
|
+
are React (the astro islands use them directly). For an own/static build, the wiring guide is
|
|
17
|
+
`../custom/bookings/WIRING.md` (same logic, client-side `@wix/sdk`).
|
|
18
|
+
|
|
19
|
+
## Scope routing
|
|
20
|
+
|
|
21
|
+
| Scope | Phase | Reference |
|
|
22
|
+
|-------|-------|-----------|
|
|
23
|
+
| `seed` | Seed — create services (and staff) via the Wix Bookings REST API | `./SERVICES_DATA.md` |
|
|
24
|
+
| `components` | Components — the client islands (week calendar, schema-driven form, the flow coordinator) | `./FLOW.md` + `../astro/bookings/COMPONENTS.md` |
|
|
25
|
+
| `pages` | Pages — `/services` listing, `/services/[slug]` detail, `/booking-confirmation`, + nav/home links | `./FLOW.md` + `../astro/bookings/SERVICES_PAGES.md` |
|
|
26
|
+
|
|
27
|
+
## Templates — read and adapt (don't invent)
|
|
28
|
+
|
|
29
|
+
Canonical examples live at `<SKILL_ROOT>/references/astro/templates/bookings/`.
|
|
30
|
+
Your `components` and `pages` scopes **read these and adapt them** — adapt brand
|
|
31
|
+
copy, headings, and styling; keep the SDK calls, payload shapes, and the data
|
|
32
|
+
flow (re-authoring the SDK wiring from scratch is the main source of API-shape bugs).
|
|
33
|
+
|
|
34
|
+
Components (`components` scope — TSX only, no CSS):
|
|
35
|
+
- `<SKILL_ROOT>/references/astro/templates/bookings/AvailabilityCalendar.tsx` — the week calendar (week strip → the day's slots; APPOINTMENT via `availabilityTimeSlots`, CLASS via `eventTimeSlots`).
|
|
36
|
+
- `<SKILL_ROOT>/references/astro/templates/bookings/BookingForm.tsx` — the schema-driven form (renders the `@wix/forms` field list, keys values by `target`) that drives `bookingDriver.book()`.
|
|
37
|
+
- `<SKILL_ROOT>/references/astro/templates/bookings/ServiceBookingFlow.tsx` — the coordinator island (holds the selected slot; calendar → form → redirect).
|
|
38
|
+
|
|
39
|
+
Pages (`pages` scope):
|
|
40
|
+
- `<SKILL_ROOT>/references/astro/templates/bookings/ServiceCard.astro`
|
|
41
|
+
- `<SKILL_ROOT>/references/astro/templates/bookings/services/index.astro`, `<SKILL_ROOT>/references/astro/templates/bookings/services/[slug].astro`
|
|
42
|
+
- `<SKILL_ROOT>/references/astro/templates/bookings/booking-confirmation.astro`
|
|
43
|
+
|
|
44
|
+
### Pre-copied by the orchestrator (do NOT write these yourself)
|
|
45
|
+
Mechanical, brand-agnostic — the orchestrator copies them before dispatch (BUILD-astro.md § build wave). Rely on them at the listed paths:
|
|
46
|
+
- `src/components/bookingDriver.ts` ← `<SKILL_ROOT>/references/astro/templates/bookings/bookingDriver.ts` — the booking SDK sequence (`book()`, `navigateToCheckout()`). The islands import it; never re-author it.
|
|
47
|
+
- `src/components/SeoTags.astro` ← `<SKILL_ROOT>/references/astro/templates/bookings/SeoTags.astro` — renders `service.seoData.tags`; imported by `services/[slug].astro`.
|
|
48
|
+
- `src/styles/components-bookings.css` ← `<SKILL_ROOT>/references/astro/templates/bookings/components-bookings.css` — the flow's component classes.
|
|
49
|
+
|
|
50
|
+
If a pre-copied file is missing at runtime, that's an orchestrator-side bug — return `status: "partial"` with `errors: [{code: "UTILITY_TEMPLATE_NOT_PRECOPIED", path: "<missing>"}]`; do not author your own version.
|
|
51
|
+
|
|
52
|
+
## Pre-return file-existence assertion (pages scope)
|
|
53
|
+
|
|
54
|
+
Before returning `status: "complete"` from `pages`, verify on disk:
|
|
55
|
+
- `src/pages/services/index.astro`
|
|
56
|
+
- `src/pages/services/[slug].astro`
|
|
57
|
+
- `src/pages/booking-confirmation.astro`
|
|
58
|
+
|
|
59
|
+
If any declared file is missing, return `status: "partial"` with `errors: [{ code: "PHASE4_FILE_MISSING", path: "<expected path>" }]`.
|
|
60
|
+
|
|
61
|
+
## Dependencies (Setup installs these — see SETUP.md)
|
|
62
|
+
|
|
63
|
+
`@wix/bookings @wix/essentials @wix/forms @wix/redirects @wix/auto_sdk_ecom_cart-v-2`.
|
|
64
|
+
`@wix/forms` renders the booking-form schema; `@wix/redirects` + `@wix/auto_sdk_ecom_cart-v-2` run the cart/checkout sequence.
|
|
65
|
+
|
|
66
|
+
## CSS ownership — bookings pack
|
|
67
|
+
|
|
68
|
+
Bookings-specific CSS lives in `src/styles/components-bookings.css` (pre-copied — see above), NOT in `global.css`. Classes the pack owns: `.service-card*`, `.service-grid`, `.availability-*`, `.time-slot*`, `.booking-*`. If `global.css` ships a partial rule for any of these, flag it (`{code:"GLOBAL_CSS_LEAK", class:"<name>"}`) and override in `components-bookings.css`.
|
|
69
|
+
|
|
70
|
+
## Bookings-specific failure modes
|
|
71
|
+
|
|
72
|
+
| Wrong | Right |
|
|
73
|
+
|-------|-------|
|
|
74
|
+
| Stop after `createBooking` returns | `createBooking` leaves the booking **`CREATED`**. The seat is held by the **ecom Cart V2**: continue `createCart → calculateCart → isCheckoutRequired ? hosted-checkout redirect : placeOrder`. The whole sequence is in `bookingDriver.ts` — use it. |
|
|
75
|
+
| Hardcode `selectedPaymentOption: "ONLINE"` on `createBooking` | **Derive** it from the service: `online && !inPerson → "ONLINE"`, `!online && inPerson → "OFFLINE"`, else `"ONLINE"`. A free / pay-in-person service is the only kind that reaches `placeOrder`, and booking it `ONLINE` makes the cart reject it with **`INSUFFICIENT_INVENTORY`** (`available_quantity: 0`). `bookingDriver.ts` derives this for you (don't pass `selectedPaymentOption` to `book()`). |
|
|
76
|
+
| Pass `contactDetails` to `createBooking` | Pass **`formSubmission`**, keyed by each field's **`target`** (default booking form: snake_case `first_name`/`last_name`/`email`/`phone`). `contactDetails` is what Wix derives back onto the response. |
|
|
77
|
+
| Hardcode the booking-form fields | Render the form **schema-driven**: read `service.form._id`'s fields via `@wix/forms`, render by `componentType`, key values by `target` (same renderer as `../astro/forms/CONTACT_FORM.md`). |
|
|
78
|
+
| Render every form field as a text input (default unknown types to `TEXT_INPUT`) | Render **only** fields with a recognized string `componentType` (`TEXT_INPUT`/`PHONE_INPUT`/`DROPDOWN`). **Skip complex, object-valued fields** (e.g. the default booking form's multi-line `ADDRESS`) — sending a string for them fails `createBooking` with **"must be object"**. Only `first_name`/`last_name`/`email` are enforced, so skipping optional complex fields is safe. |
|
|
79
|
+
| Call `queryServices` without an `appId` filter | Always include `appId` (`13d21c63-…`) in the filter. List: `queryServices({ query:{ filter:{ appId }, paging }, conditionalFields:["STAFF_MEMBER_DETAILS"] }).find()`. Single: the `.eq("mainSlug.name", slug).eq("appId", …).limit(1).find()` builder chain. |
|
|
80
|
+
| Build `createClient({ auth: OAuthStrategy({ clientId: import.meta.env.* }) })` in `.astro` SSR | The `*_WIX_CLIENT_ID` env var is client-only → `undefined` at server render → 500. SSR reads use the ambient `@wix/essentials`: `auth.elevate(services.queryServices)(...)`. |
|
|
81
|
+
| Build an `OAuthStrategy` client inside an astro browser island | Astro islands call the `@wix` modules **ambiently** (the `@wix/astro` visitor client, like the ecom `CartView`) — no `createClient`. (Own/static builds DO use `OAuthStrategy` — `../custom/bookings/WIRING.md`.) |
|
|
82
|
+
| Pass UTC ISO strings to `listAvailabilityTimeSlots` | Use **local** date strings `YYYY-MM-DDThh:mm:ss` (no `Z`) + a separate `timeZone`. |
|
|
83
|
+
| `availability.queryAvailability` / `import { availability }` | APPOINTMENT → `availabilityTimeSlots.listAvailabilityTimeSlots({ serviceId: <string> })`; CLASS → `eventTimeSlots.listEventTimeSlots({ serviceIds: [<string>] })`. The `availability` namespace does not exist. |
|
|
84
|
+
| Read slot time from `timeSlot.slot.startDate` | Slot fields are at the **top level**: `localStartDate`, `localEndDate`, `scheduleId` (APPOINTMENT). CLASS slots carry `eventInfo.eventId` and have **no** `scheduleId`. |
|
|
85
|
+
| Dump every slot in one flat, time-only grid | Show a **week calendar**: a day strip → the picked day's times. A flat grid hides which day a slot is on. |
|
|
86
|
+
| Read service slug from `service.slug` / name from `service.info.name` | V2 is flat: slug `service.mainSlug.name` (fallback `supportedSlugs[0].name`), `service.name`, `service.description`, `service.tagLine`; duration `service.schedule.availabilityConstraints.sessionDurations[0]`; price `service.payment.fixed.price.value` (string); media `service.media.mainMedia.image.url`. |
|
|
87
|
+
| Use `service.id` / `result.booking.id` | Entity ids are `_id` (underscore): `service._id`, `booking._id`. |
|
|
88
|
+
| Mount the calendar/form without `client:only="react"` | Availability + booking are timezone/session-specific — always `client:only="react"`. SSR only the read pages (catalog/detail) for SEO. |
|
|
89
|
+
| Omit try/catch on the booking | `createBooking` can reject (e.g. a slot taken between fetch and submit, or strict phone validation). Catch and surface a friendly message; don't crash. |
|
|
90
|
+
| Ship a location selector, staff filter, payment breakdown, CLASS sign-up, or waitlist | Out of v1 — appointment booking only. |
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# Bookings Seed — Services Data
|
|
2
|
+
|
|
3
|
+
You are seeding Wix Bookings services for a headless site. Your job:
|
|
4
|
+
1. Mint a site-scoped token once.
|
|
5
|
+
2. **If `intent.bookings.hasStaff` is `true`** — create staff members FIRST (Step 3 below), then pass their `resourceId`s on each service. **Every APPOINTMENT service needs a non-empty `staffMemberIds`** or it's rejected with `MISSING_APPOINTMENT_RESOURCES` — even when `hasStaff` is false you must pass the default **Business Owner** `resourceId` (see § "When hasStaff is false"). (CLASS services don't need a resource at create time.)
|
|
6
|
+
3. Create `intent.bookings.serviceCount` services via the Bookings V2 REST API. When staff exist, pass their `resourceId` values via `staffMemberIds`.
|
|
7
|
+
4. Return all created IDs in the standard return contract.
|
|
8
|
+
|
|
9
|
+
> **Order matters.** Steps 3 and 4 in this file are presented in the order you should execute them: staff first (when applicable), then services. The old "services-first then staff-optionally" order fails for APPOINTMENT with `MISSING_APPOINTMENT_RESOURCES`.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Step 1 — Mint the token
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
TOKEN=$(npx @wix/cli@latest token --site "<siteId>")
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Cache in subagent scratch. Every subsequent `curl` reuses it. Every call carries:
|
|
20
|
+
```
|
|
21
|
+
Authorization: Bearer $TOKEN
|
|
22
|
+
wix-site-id: <siteId>
|
|
23
|
+
Content-Type: application/json
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Step 2 — Verify Wix Bookings is installed
|
|
29
|
+
|
|
30
|
+
Query staff members as an installation check. A successful response (even an empty list) confirms Bookings is installed:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl -sS -X POST \
|
|
34
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
35
|
+
-H "wix-site-id: <siteId>" \
|
|
36
|
+
-H "Content-Type: application/json" \
|
|
37
|
+
-d '{"query":{}}' \
|
|
38
|
+
"https://www.wixapis.com/bookings/v1/staff-members/query"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- **200 OK** → proceed.
|
|
42
|
+
- **403 / "Business schedule not found"** → Bookings app is not installed. Return `status: "error"` with the response verbatim. Do not attempt to install it — the orchestrator's Setup phase handles app installation.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Step 3 — Create staff members (when `intent.bookings.hasStaff` is `true`)
|
|
47
|
+
|
|
48
|
+
Execute this step BEFORE Step 4 when staff are required. APPOINTMENT services need at least one resource to exist before they can be created.
|
|
49
|
+
|
|
50
|
+
For each staff member to create (derive names from the brand; default to 2 staff):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Build the payload — only include phone/email when non-empty.
|
|
54
|
+
# The V1 staff-members API rejects "" as an invalid email/phone.
|
|
55
|
+
curl -sS -X POST \
|
|
56
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
57
|
+
-H "wix-site-id: <siteId>" \
|
|
58
|
+
-H "Content-Type: application/json" \
|
|
59
|
+
-d '{
|
|
60
|
+
"staffMember": {
|
|
61
|
+
"name": "<First Last>",
|
|
62
|
+
"description": "<professional bio — 1–2 sentences, brand-appropriate>"
|
|
63
|
+
}
|
|
64
|
+
}' \
|
|
65
|
+
"https://www.wixapis.com/bookings/v1/staff-members"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
> **Do NOT send `"phone": ""` or `"email": ""`** — V1 validates them as format-checked fields and rejects empty strings with `is not a valid email/phone`. Omit the keys entirely when you don't have a real value. If the merchant later wants to populate phone/email, they can do it from the dashboard or via a PATCH.
|
|
69
|
+
|
|
70
|
+
**Important:** Staff inherit business working hours at creation time. For a basic seed, this is fine — the merchant can configure custom hours from the dashboard later.
|
|
71
|
+
|
|
72
|
+
Save `staffMember.id` and `staffMember.resourceId` from each response. **You will pass the `resourceId` values into the `staffMemberIds` field on each service in Step 4.**
|
|
73
|
+
|
|
74
|
+
> **Note:** `staffMemberIds` on services expects `resourceId` values (from `staffMember.resourceId`), NOT `staffMember.id`. Using `staffMember.id` here is the most common cause of "service has no provider" runtime errors.
|
|
75
|
+
|
|
76
|
+
> **Do NOT attempt custom working hours setup during seed.** The two-step custom-hours workflow (assignWorkingHoursSchedule + WORKING_HOURS events) is complex and fragile. Default inherited hours are sufficient for an initial build. If you encounter "Business schedule not found", skip staff creation and return a warning in `notes`.
|
|
77
|
+
|
|
78
|
+
### When `hasStaff` is `false`
|
|
79
|
+
|
|
80
|
+
For `serviceType: "CLASS"`, no staff/resource is needed at service-creation time — skip this step.
|
|
81
|
+
|
|
82
|
+
For `serviceType: "APPOINTMENT"` with `hasStaff: false`, you **still must pass a resource** in `staffMemberIds` — an empty `[]` is **rejected** with `MISSING_APPOINTMENT_RESOURCES` (*"service of type appointment requires at least one staff member or service resource"*). The Wix Bookings app provisions a default **Business Owner** resource at install; query it and pass **its** `resourceId`:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Get the default Business-Owner resourceId (no staff were created).
|
|
86
|
+
curl -sS -X POST -H "Authorization: Bearer $TOKEN" -H "wix-site-id: <siteId>" -H "Content-Type: application/json" \
|
|
87
|
+
-d '{"query":{}}' "https://www.wixapis.com/bookings/v1/staff-members/query"
|
|
88
|
+
# → take the staffMember named "Business Owner" and use its `resourceId` as the single
|
|
89
|
+
# entry in each APPOINTMENT service's `staffMemberIds`.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
(Do **not** rely on `[]` — the default resource does not auto-satisfy the requirement at create time. CLASS services don't need this.)
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Step 4 — Create services (Services V2 API)
|
|
97
|
+
|
|
98
|
+
**Endpoint:** `POST https://www.wixapis.com/_api/bookings/v2/services`
|
|
99
|
+
|
|
100
|
+
> **V2 payload is flat — NOT nested under `info`.** Fields are `name`, `description`, `tagLine` at the top level of the `service` object. The V1 shape (`info.name`, `info.description`) is rejected by V2. Price uses `value`, not `amount`.
|
|
101
|
+
|
|
102
|
+
For each service to create (derive names, descriptions, and prices from `brand` + `intent.bookings`):
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
curl -sS -X POST \
|
|
106
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
107
|
+
-H "wix-site-id: <siteId>" \
|
|
108
|
+
-H "Content-Type: application/json" \
|
|
109
|
+
-d '{
|
|
110
|
+
"service": {
|
|
111
|
+
"type": "<APPOINTMENT|CLASS>",
|
|
112
|
+
"name": "<service name>",
|
|
113
|
+
"description": "<brand-appropriate description>",
|
|
114
|
+
"tagLine": "<short tagline>",
|
|
115
|
+
"defaultCapacity": 1,
|
|
116
|
+
"onlineBooking": {
|
|
117
|
+
"enabled": true,
|
|
118
|
+
"requireManualApproval": false,
|
|
119
|
+
"allowMultipleRequests": false
|
|
120
|
+
},
|
|
121
|
+
"schedule": {
|
|
122
|
+
"availabilityConstraints": {
|
|
123
|
+
"sessionDurations": [<duration as integer, e.g. 60>]
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"payment": {
|
|
127
|
+
"rateType": "FIXED",
|
|
128
|
+
"fixed": {
|
|
129
|
+
"price": {
|
|
130
|
+
"value": "<price as string, e.g. \"75.00\">",
|
|
131
|
+
"currency": "USD"
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"options": {
|
|
135
|
+
"online": true,
|
|
136
|
+
"inPerson": false,
|
|
137
|
+
"deposit": false,
|
|
138
|
+
"pricingPlan": false
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
"locations": [
|
|
142
|
+
{
|
|
143
|
+
"type": "BUSINESS"
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
"staffMemberIds": [<resourceId values — staff from Step 3, OR the default Business-Owner resourceId when hasStaff is false. NEVER [] for APPOINTMENT — it 400s with MISSING_APPOINTMENT_RESOURCES>]
|
|
147
|
+
}
|
|
148
|
+
}' \
|
|
149
|
+
"https://www.wixapis.com/_api/bookings/v2/services"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
> **`locations.type` enum:** the valid values are `UNKNOWN_LOCATION_TYPE`, `CUSTOM`, `BUSINESS`, and `CUSTOMER`. Use `"BUSINESS"` for "at the business address" (the seed default). Do NOT use `"OWNER_BUSINESS"` here — that string IS valid for `createBooking.bookedEntity.slot.location.locationType` on the bookings endpoint, but the **services** endpoint rejects it. Same field name, different enum.
|
|
153
|
+
|
|
154
|
+
> **`staffMemberIds`:** pass `resourceId` values (not staffMember `id` values). For APPOINTMENT this array must be **non-empty** — when `hasStaff` is `false`, pass the default **Business Owner** `resourceId` (query it per § "When hasStaff is false"); `[]` is rejected with `MISSING_APPOINTMENT_RESOURCES` (verified live). CLASS services don't require it.
|
|
155
|
+
|
|
156
|
+
**Response shape:**
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"service": {
|
|
160
|
+
"id": "<uuid>",
|
|
161
|
+
"name": "<name>",
|
|
162
|
+
"description": "<description>",
|
|
163
|
+
"mainSlug": { "name": "<url-slug>", "custom": false },
|
|
164
|
+
"supportedSlugs": [{ "name": "<url-slug>", "custom": false }],
|
|
165
|
+
"type": "APPOINTMENT",
|
|
166
|
+
"payment": { ... },
|
|
167
|
+
"schedule": { ... }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
> **Slug:** Extract from `service.mainSlug.name`. If absent, derive from the service name: lowercase, replace spaces and non-alphanumeric chars with hyphens, deduplicate hyphens.
|
|
173
|
+
|
|
174
|
+
### Service creation guidelines
|
|
175
|
+
|
|
176
|
+
- **Count**: Create exactly `intent.bookings.serviceCount` services (default 3 when not specified).
|
|
177
|
+
- **Type**: Use `"APPOINTMENT"` for 1-on-1 services (the default); use `"CLASS"` when `intent.bookings.serviceType === "CLASS"`. For `CLASS`, set `defaultCapacity` to the max participants (e.g. `20`) instead of `1`.
|
|
178
|
+
|
|
179
|
+
> **⚠️ CLASS services need scheduled sessions before anyone can sign up.** Creating a CLASS service does **not** create any bookable sessions. The front-end lists bookable sessions via `eventTimeSlots.listEventTimeSlots()`, which returns scheduled **session events** — a freshly created CLASS service has none, so its calendar is permanently empty. **For CLASS services you MUST run Step 4b below** to schedule sessions; otherwise the class calendar is a dead end. (APPOINTMENT services don't need this — their bookable times come from staff working hours + `availabilityTimeSlots`.)
|
|
180
|
+
- **`sessionDurations`**: Required for APPOINTMENT. An array containing one integer (minutes). Do NOT specify for CLASS or COURSE services.
|
|
181
|
+
- **Names + descriptions**: Derive from `brand.description`. Examples: a yoga studio → "60-min Vinyasa Flow" (60 min), "30-min Morning Meditation" (30 min), "90-min Deep Restore" (90 min). A hair salon → "Women's Cut & Style" (60 min, $85), "Men's Haircut" (30 min, $45), "Balayage Color" (120 min, $150). Make them brand-appropriate — not generic.
|
|
182
|
+
- **Duration** (integers in minutes): Brand-appropriate. Consultation → 30; standard service → 60; premium/complex → 90–120.
|
|
183
|
+
- **Price `value`**: A string, not a number. Brand-appropriate, non-trivial. A budget studio might charge `"25.00"`; a premium clinic `"250.00"`. Default to mid-range when unclear.
|
|
184
|
+
- **Currency**: Set `"USD"` unless the brand's locale implies otherwise — **but note the site's business currency wins**: Services V2 silently stores the site-locale currency (e.g. a EUR-locale site stores `EUR` even when you send `USD`). This is not an error; the page templates format from the service's returned `currency`, so display stays correct. Don't fight it.
|
|
185
|
+
- **Fire all service creates as a parallel batch** — they are independent calls.
|
|
186
|
+
|
|
187
|
+
### Required fields summary (V2)
|
|
188
|
+
|
|
189
|
+
| Field | Required | Notes |
|
|
190
|
+
|-------|----------|-------|
|
|
191
|
+
| `type` | Yes | `APPOINTMENT`, `CLASS`, or `COURSE` |
|
|
192
|
+
| `name` | Yes | Display name |
|
|
193
|
+
| `defaultCapacity` | Yes | `1` for APPOINTMENT; participant count for CLASS |
|
|
194
|
+
| `onlineBooking.enabled` | Yes | Set to `true` for online booking |
|
|
195
|
+
| `payment.rateType` | Yes | `FIXED`, `NO_FEE`, `VARIED`, or `CUSTOM` |
|
|
196
|
+
| `payment.options.online` or `payment.options.inPerson` | Yes | At least one must be `true` |
|
|
197
|
+
| `schedule.availabilityConstraints.sessionDurations` | APPOINTMENT only | Array with one integer (minutes) |
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## Step 4b — Create class sessions (CLASS services only)
|
|
202
|
+
|
|
203
|
+
A CLASS service is bookable only once its **schedule** has scheduled session events. Sessions are **Calendar Events V3** (not part of `@wix/bookings`), created via REST. Run this for every service you created with `type: "CLASS"`; skip it entirely for APPOINTMENT.
|
|
204
|
+
|
|
205
|
+
**Endpoint:** `POST https://www.wixapis.com/calendar/v3/events`
|
|
206
|
+
|
|
207
|
+
You need two things from earlier steps:
|
|
208
|
+
- the service's **schedule id** — `service.schedule.id` from the Step 4 create response (every service has one);
|
|
209
|
+
- a **resource id** — a CLASS event requires **at least one resource** (`resources` non-empty) or it's rejected. Use a staff `resourceId` from Step 3; if no staff were created, use the default Business-Owner resource's `resourceId` (query `POST /bookings/v1/staff-members/query` with `{"query":{}}` and take the one named `Business Owner`).
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
# One session. Repeat for several upcoming dates per class (e.g. 3 over the next
|
|
213
|
+
# ~2 weeks) so the calendar isn't empty. `localDate` is local time, no `Z`.
|
|
214
|
+
curl -sS -X POST \
|
|
215
|
+
-H "Authorization: Bearer $TOKEN" -H "wix-site-id: <siteId>" -H "Content-Type: application/json" \
|
|
216
|
+
-d '{
|
|
217
|
+
"event": {
|
|
218
|
+
"scheduleId": "<service.schedule.id>",
|
|
219
|
+
"type": "CLASS",
|
|
220
|
+
"start": { "localDate": "2026-06-05T09:00:00" },
|
|
221
|
+
"end": { "localDate": "2026-06-05T10:00:00" },
|
|
222
|
+
"resources": [ { "id": "<staff resourceId>", "permissionRole": "WRITER" } ],
|
|
223
|
+
"totalCapacity": 12
|
|
224
|
+
}
|
|
225
|
+
}' \
|
|
226
|
+
"https://www.wixapis.com/calendar/v3/events"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
> **Gotchas (verified against the live API):**
|
|
230
|
+
> - `resources[].permissionRole` **must** be `"WRITER"` (or `"COMMENTER"`). Omitting it defaults to `UNKNOWN_ROLE` → `400 "resources.permissionRole must not be UNKNOWN_ROLE"`.
|
|
231
|
+
> - `resources` must be **non-empty** for CLASS — a session with no resource is rejected.
|
|
232
|
+
> - `start`/`end` use `{ "localDate": "YYYY-MM-DDThh:mm:ss" }` (local, no `Z`); seconds are ignored.
|
|
233
|
+
> - For a recurring weekly schedule instead of one-off sessions, add `event.recurrenceRule` (`{ "frequency": "WEEKLY", "interval": 1, "days": ["MONDAY"], "until": { "localDate": "..." } }` — only `WEEKLY` and a **single** day are supported; this creates a `MASTER` event and Wix auto-generates the instances). One-off sessions are simpler and sufficient for a seed.
|
|
234
|
+
> - `totalCapacity` defaults to the schedule's `defaultCapacity` if omitted; set it explicitly to the class size.
|
|
235
|
+
|
|
236
|
+
Verify by fetching slots the way the front-end does (`eventTimeSlots.listEventTimeSlots({ serviceIds: [id], … })`) or `POST /calendar/v3/events/query` filtered by `scheduleId`. Record each session's event id in your return under `seeded.bookings.services[].sessionEventIds` if you want Phase 4 to deep-link.
|
|
237
|
+
|
|
238
|
+
> **Booking-policy toggles (optional, CLASS).** Both live on the default booking policy — get it once via `POST /bookings/v1/booking-policies/query` with body `{"query":{}}` (an empty `{}` body is rejected with `query must not be empty`), then `PATCH /bookings/v1/booking-policies/<id>` with the relevant block + `fieldMask`:
|
|
239
|
+
> - **Waitlist** (so a full session offers "join waitlist"): `"waitlistPolicy": { "enabled": true, "capacity": 10, "reservationTimeInMinutes": 10 }`, `fieldMask.paths: ["waitlistPolicy"]`.
|
|
240
|
+
> - **Multi-participant** (party size > 1): `"participantsPolicy": { "maxParticipantsPerBooking": <N> }`, `fieldMask.paths: ["participantsPolicy"]`. **Defaults to `1`** — without raising it, the front-end party-size selector lets the user pick >1 but `createBooking` rejects it. Raise it (e.g. `4`) to actually enable multi-participant class booking.
|
|
241
|
+
>
|
|
242
|
+
> Skip whichever the build doesn't surface.
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Step 5 — Return contract
|
|
247
|
+
|
|
248
|
+
**Return this JSON inline as your agent return** (per `references/shared/RETURN_CONTRACT.md`) — do **NOT** write a `.wix/seed-returns/` file; the orchestrator aggregates seeder returns and is the sole writer of `.wix/seeded.json`:
|
|
249
|
+
|
|
250
|
+
```json
|
|
251
|
+
{
|
|
252
|
+
"phase": "seed-bookings",
|
|
253
|
+
"status": "ok",
|
|
254
|
+
"seeded": {
|
|
255
|
+
"services": [
|
|
256
|
+
{
|
|
257
|
+
"id": "<uuid>",
|
|
258
|
+
"slug": "<mainSlug.name>",
|
|
259
|
+
"name": "<name>",
|
|
260
|
+
"type": "APPOINTMENT",
|
|
261
|
+
"durationMinutes": 60,
|
|
262
|
+
"price": "75.00",
|
|
263
|
+
"currency": "USD"
|
|
264
|
+
}
|
|
265
|
+
],
|
|
266
|
+
"staff": [
|
|
267
|
+
{ "id": "<uuid>", "resourceId": "<uuid>", "name": "<name>" }
|
|
268
|
+
]
|
|
269
|
+
},
|
|
270
|
+
"recipeCalls": [
|
|
271
|
+
{ "url": "https://www.wixapis.com/_api/bookings/v2/services", "status": 200 }
|
|
272
|
+
]
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
- `staff` is an empty array `[]` when `intent.bookings.hasStaff` is false or Step 3 was skipped.
|
|
277
|
+
- **For `CLASS` services, schedule sessions in Step 4b** and report them (e.g. `seeded.bookings.services[].sessionEventIds`). Only if Step 4b is skipped or fails, add a `notes` entry so the orchestrator surfaces it: `"notes": ["CLASS sessions not scheduled — add session times in the Bookings dashboard before sign-up works."]`
|
|
278
|
+
- On any REST error: set `status: "error"`, include the failing call's response verbatim under `"error"`.
|
|
279
|
+
|
|
280
|
+
---
|
|
281
|
+
|
|
282
|
+
## Common failure modes
|
|
283
|
+
|
|
284
|
+
| Failure | Recovery |
|
|
285
|
+
|---------|----------|
|
|
286
|
+
| 403 on service create | Re-mint token and retry once. If still 403, Bookings app was not installed — return `status: "error"`. |
|
|
287
|
+
| 400 `"defaultCapacity is required"` | Add `"defaultCapacity": 1` to the payload (required in V2, not obvious from V1 docs). |
|
|
288
|
+
| 400 `"onlineBooking is required"` | Add `"onlineBooking": { "enabled": true }` — required in V2. |
|
|
289
|
+
| 400 on `payment.options` | At least one of `online` or `inPerson` must be `true`. Set `"inPerson": true` as fallback. |
|
|
290
|
+
| 400 `"sessionDurations is required"` | Add `"schedule": { "availabilityConstraints": { "sessionDurations": [60] } }` for APPOINTMENT types. |
|
|
291
|
+
| 400 `MISSING_APPOINTMENT_RESOURCES` on service create | An APPOINTMENT service's `staffMemberIds` must be **non-empty**. Pass the created staff `resourceId`s (Step 3), or — when `hasStaff` is false — the default **Business Owner** `resourceId` (query `/bookings/v1/staff-members/query` with `{"query":{}}`). An empty `[]` always 400s here. |
|
|
292
|
+
| 400 enum error on `locations.type` | Use `"BUSINESS"`, not `"OWNER_BUSINESS"`. The services endpoint accepts `UNKNOWN_LOCATION_TYPE`, `CUSTOM`, `BUSINESS`, `CUSTOMER`. `OWNER_BUSINESS` is valid on `createBooking.bookedEntity.slot.location.locationType` only. |
|
|
293
|
+
| 400 `is not a valid email/phone` on staff create | Don't send `"phone": ""` or `"email": ""`. Omit the keys entirely when you don't have a real value. |
|
|
294
|
+
| `mainSlug` absent in response | Derive slug from `service.name`: lowercase, replace spaces with hyphens, strip non-alphanumeric. |
|
|
295
|
+
| Staff create returns "Business schedule not found" | Skip staff creation. Return partial seeded data with `notes: ["Staff creation skipped — business schedule not found; configure via Bookings dashboard"]`. |
|
|
@@ -50,7 +50,7 @@ body: {
|
|
|
50
50
|
}
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
-
The response returns the created item's `_id` — collect these for the
|
|
53
|
+
The response returns the created item's `_id` — collect these for the seed return contract.
|
|
54
54
|
|
|
55
55
|
**Every text field must be populated in the `data` object.** Do not create items with empty `data: {}` and expect Phase 2 or the image agent to fill in text fields — they only handle their own scope (pages and images respectively).
|
|
56
56
|
|
|
@@ -70,6 +70,8 @@ body: {
|
|
|
70
70
|
}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
+
> **Bulk insert (N ≥ 2 items):** use the `wix-manage` skill's recipe — `wix-manage/references/cms/cms-data-items-crud.md` § "Bulk Insert Items" (`POST /wix-data/v2/bulk/items/insert`, up to 1000/call, one `dataCollectionId` per call → fire one bulk call per collection as a parallel batch). It also documents bulk update/patch/delete. Do not hand-roll N single inserts for a multi-item seed.
|
|
74
|
+
|
|
73
75
|
### Verify inserts with a live query (mandatory)
|
|
74
76
|
|
|
75
77
|
After inserting all items, **query each collection once** and confirm every field you sent is present in the stored `data`. A POST without errors does NOT prove the content persisted — the API has accepted insert bodies with missing fields before, and the failure is invisible until a human opens the page.
|
|
@@ -140,7 +142,7 @@ Emit a structured JSON block at the end of your completion message per `../share
|
|
|
140
142
|
{
|
|
141
143
|
"status": "complete",
|
|
142
144
|
"phase": "cms-seed",
|
|
143
|
-
"scope": "seed
|
|
145
|
+
"scope": "seed",
|
|
144
146
|
"summary": "Created {N} collections; seeded {M} items; verified every field persisted",
|
|
145
147
|
"data": {
|
|
146
148
|
"collections": [
|
|
@@ -5,7 +5,7 @@ description: "Implements structured content pages (About, FAQ, portfolios, team
|
|
|
5
5
|
|
|
6
6
|
# CMS Implementer
|
|
7
7
|
|
|
8
|
-
Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, prompt-inlined inputs (
|
|
8
|
+
Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, prompt-inlined inputs (read only your `.wix/seeded.json` slice), return contract, style conventions, and common failure modes.
|
|
9
9
|
|
|
10
10
|
## Scope routing
|
|
11
11
|
|
|
@@ -29,6 +29,32 @@ Pick based on the business type (the orchestrator names one in your prompt):
|
|
|
29
29
|
|
|
30
30
|
See `<SKILL_ROOT>/references/verticals/cms.md` frontmatter.
|
|
31
31
|
|
|
32
|
+
## Seed return
|
|
33
|
+
|
|
34
|
+
The `seed` scope emits this `data` shape in its return JSON (envelope rules in `../shared/RETURN_CONTRACT.md`). Phase 4 CMS page agents reference these collection names; the image agent attaches entity images to CMS items by ID.
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"status": "complete",
|
|
39
|
+
"phase": "cms-seed",
|
|
40
|
+
"scope": "seed",
|
|
41
|
+
"data": {
|
|
42
|
+
"collections": [
|
|
43
|
+
{
|
|
44
|
+
"name": "about-content",
|
|
45
|
+
"itemIds": ["665f3363-..."],
|
|
46
|
+
"fields": ["heading", "body", "image"]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"name": "faq",
|
|
50
|
+
"itemIds": ["abc", "def", "ghi", "jkl", "mno", "pqr"],
|
|
51
|
+
"fields": ["question", "answer", "sortOrder"]
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
32
58
|
## Page width (FAQ, About, long-form CMS)
|
|
33
59
|
|
|
34
60
|
Read `references/shared/STYLING.md` § "Prose / reading width". **Do not** wrap FAQ/About body copy in `max-w-3xl` unless `--container-3xl` exists in `src/styles/global.css` `@theme`. Prefer `container-reading`, `max-w-6xl` (when `--container-6xl` is defined), or `max-w-[48rem]`. A bare `max-w-3xl` with only a spacing scale ships a ~80px column.
|