@wix/agent-skills 1.8.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +2 -1
- 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 +1 -1
- package/skills/wix-headless/references/BUILD-astro.md +5 -7
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +6 -16
- package/skills/wix-headless/references/SETUP.md +1 -1
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +41 -333
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +77 -428
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +2 -2
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +7 -10
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +2 -2
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +7 -7
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +4 -4
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +2 -2
- package/skills/wix-headless/references/astro/templates/Footer.astro +4 -4
- package/skills/wix-headless/references/astro/templates/Navigation.astro +4 -4
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +298 -142
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +104 -162
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +35 -14
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +11 -2
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +23 -85
- package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +245 -0
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +209 -198
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +114 -19
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +131 -15
- package/skills/wix-headless/references/astro/templates/ecom/CartView.tsx +3 -3
- package/skills/wix-headless/references/astro/templates/ecom/cart.astro +3 -3
- package/skills/wix-headless/references/astro/templates/ecom/components-ecom.css +17 -17
- package/skills/wix-headless/references/astro/templates/ecom/thank-you.astro +3 -3
- package/skills/wix-headless/references/astro/templates/gift-cards/_home-teaser-snippet.astro +4 -4
- package/skills/wix-headless/references/astro/templates/gift-cards/components-gift-cards.css +19 -18
- package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.astro +3 -3
- package/skills/wix-headless/references/astro/templates/global.css +17 -15
- package/skills/wix-headless/references/astro/templates/index.astro +4 -4
- package/skills/wix-headless/references/astro/templates/stores/components-stores.css +31 -31
- package/skills/wix-headless/references/astro/templates/stores/products/[slug].astro +4 -4
- package/skills/wix-headless/references/bookings/FLOW.md +246 -0
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +65 -76
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +60 -0
- package/skills/wix-headless/references/custom/bookings/WIRING.md +169 -60
- package/skills/wix-headless/references/shared/DESIGN_MD.md +2 -3
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +4 -4
- package/skills/wix-headless/references/shared/STYLING.md +15 -18
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +2 -2
- package/skills/wix-headless/references/verticals/bookings.md +9 -9
- package/skills/wix-headless/scripts/compose.mjs +4 -19
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +0 -3
- package/skills/wix-manage/SKILL.md +12 -0
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -27
- package/skills/wix-manage/references/bookings/create-appointment-service.md +254 -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 +233 -0
- package/skills/wix-manage/references/bookings/create-course-service.md +237 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +2 -1
- package/skills/wix-manage/references/cms/cms-schema-management.md +1 -1
- package/skills/wix-manage/references/stores/create-product-from-image.md +154 -2
- package/skills/wix-headless/references/astro/templates/bookings/ManageBooking.tsx +0 -84
- package/skills/wix-headless/references/astro/templates/bookings/api/confirm-booking.ts +0 -34
- package/skills/wix-headless/references/astro/templates/bookings/api/waitlist.ts +0 -74
- package/skills/wix-headless/references/astro/templates/bookings/manage-booking.astro +0 -76
|
@@ -1,428 +1,77 @@
|
|
|
1
|
-
# Bookings Pages —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
// Duration: lives at service.schedule.availabilityConstraints.sessionDurations[0]
|
|
79
|
-
const getDuration = (service: any): number =>
|
|
80
|
-
service.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
|
|
81
|
-
---
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
### Template
|
|
85
|
-
|
|
86
|
-
```astro
|
|
87
|
-
<Layout title="Services">
|
|
88
|
-
<main class="py-4xl">
|
|
89
|
-
<section class="container mx-auto px-lg">
|
|
90
|
-
<header class="mb-3xl">
|
|
91
|
-
<h1 class="font-display text-4xl mb-sm">Our Services</h1>
|
|
92
|
-
<!-- Optional tagline from brand -->
|
|
93
|
-
</header>
|
|
94
|
-
|
|
95
|
-
{serviceList.length === 0 ? (
|
|
96
|
-
<p class="text-secondary">Services coming soon — check back shortly.</p>
|
|
97
|
-
) : (
|
|
98
|
-
<div class="service-grid">
|
|
99
|
-
{serviceList.map((service) => (
|
|
100
|
-
<ServiceCard
|
|
101
|
-
id={service._id}
|
|
102
|
-
slug={getSlug(service)}
|
|
103
|
-
name={service.name ?? 'Untitled'}
|
|
104
|
-
tagLine={service.tagLine}
|
|
105
|
-
durationMinutes={getDuration(service)}
|
|
106
|
-
price={formatPrice(service)}
|
|
107
|
-
imageUrl={service.media?.mainMedia?.image?.url ?? service.media?.items?.[0]?.image?.url}
|
|
108
|
-
type={service.type ?? 'APPOINTMENT'}
|
|
109
|
-
/>
|
|
110
|
-
))}
|
|
111
|
-
</div>
|
|
112
|
-
)}
|
|
113
|
-
</section>
|
|
114
|
-
</main>
|
|
115
|
-
</Layout>
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
---
|
|
119
|
-
|
|
120
|
-
## Page 2 — `/services/[slug].astro`
|
|
121
|
-
|
|
122
|
-
### Frontmatter
|
|
123
|
-
|
|
124
|
-
```typescript
|
|
125
|
-
---
|
|
126
|
-
// File is at src/pages/services/[slug].astro
|
|
127
|
-
// ../../ from src/pages/services/ → src/
|
|
128
|
-
import Layout from '../../layouts/Layout.astro';
|
|
129
|
-
import ServiceBookingFlow from '../../components/ServiceBookingFlow';
|
|
130
|
-
import { services } from '@wix/bookings';
|
|
131
|
-
import { auth } from '@wix/essentials';
|
|
132
|
-
|
|
133
|
-
const { slug } = Astro.params;
|
|
134
|
-
|
|
135
|
-
// SSR via @wix/essentials — same ambient pattern as the listing page above and
|
|
136
|
-
// the CMS pages. No createClient/OAuthStrategy, no import.meta.env (those 500 at
|
|
137
|
-
// server render — see the listing-page note).
|
|
138
|
-
let service: any = null;
|
|
139
|
-
try {
|
|
140
|
-
// Query builder + `.find()` (the object form returns 0 items). Fetch the small
|
|
141
|
-
// catalog and match the slug in JS — robust without relying on a nested-field
|
|
142
|
-
// (`mainSlug.name`) builder filter.
|
|
143
|
-
const result = await auth.elevate(services.queryServices)()
|
|
144
|
-
.limit(100)
|
|
145
|
-
.find();
|
|
146
|
-
service =
|
|
147
|
-
(result.items ?? []).find(
|
|
148
|
-
(s: any) => (s.mainSlug?.name ?? s.supportedSlugs?.[0]?.name) === slug,
|
|
149
|
-
) ?? null;
|
|
150
|
-
} catch (err) {
|
|
151
|
-
console.error(`[services/${slug}] query failed:`, err);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!service) {
|
|
155
|
-
return Astro.redirect('/services', 302);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Services V2: flat fields — `name`, `tagLine`, not `info.name`, `info.tagLine`
|
|
159
|
-
// Price uses `value` (string), not `amount`
|
|
160
|
-
const formatPrice = (payment: any): string | undefined => {
|
|
161
|
-
if (!payment || payment.rateType === 'NO_FEE') return undefined;
|
|
162
|
-
if (payment.rateType === 'FIXED' && payment.fixed?.price) {
|
|
163
|
-
const { value, currency } = payment.fixed.price;
|
|
164
|
-
if (!value) return undefined;
|
|
165
|
-
return new Intl.NumberFormat('en-US', { style: 'currency', currency: currency ?? 'USD' }).format(Number(value));
|
|
166
|
-
}
|
|
167
|
-
return undefined;
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const price = formatPrice(service.payment);
|
|
171
|
-
// Duration: service.schedule.availabilityConstraints.sessionDurations[0]
|
|
172
|
-
const duration = service.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
|
|
173
|
-
// Media: service.media.mainMedia.image.url (V2 shape)
|
|
174
|
-
const imageUrl = service.media?.mainMedia?.image?.url ?? service.media?.items?.[0]?.image?.url;
|
|
175
|
-
---
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
### Template
|
|
179
|
-
|
|
180
|
-
```astro
|
|
181
|
-
<!--
|
|
182
|
-
Width: use `container-reading` (max-width: var(--container-prose) + auto margins),
|
|
183
|
-
NOT a bare `max-w-<size>`. In this Tailwind v4 setup any `max-w-<size>` whose key
|
|
184
|
-
the Composer didn't emit in @theme `--container-*` silently falls back to the
|
|
185
|
-
`--spacing-*` scale — e.g. `max-w-4xl` → ~7rem, a ~112px column (one word per line).
|
|
186
|
-
`container-reading` is a project-owned utility that always resolves. See STYLING.md.
|
|
187
|
-
-->
|
|
188
|
-
<Layout title={service.name ?? 'Service'}>
|
|
189
|
-
<main class="py-4xl">
|
|
190
|
-
<div class="container-reading px-lg">
|
|
191
|
-
<!-- Back link -->
|
|
192
|
-
<a href="/services" class="text-secondary hover:text-primary text-sm mb-xl block">
|
|
193
|
-
← All Services
|
|
194
|
-
</a>
|
|
195
|
-
|
|
196
|
-
<!-- Optional banner image — render ONLY when the service has media. The
|
|
197
|
-
default seed is text-only, so most services have no image; rendering a
|
|
198
|
-
fixed-ratio placeholder there collapses to a thin bar and crowds the
|
|
199
|
-
text. Single full-width column when image-less. -->
|
|
200
|
-
{imageUrl && (
|
|
201
|
-
<img src={imageUrl} alt={service.name} class="w-full h-64 object-cover rounded-lg mb-xl" />
|
|
202
|
-
)}
|
|
203
|
-
|
|
204
|
-
<!-- Service hero — single column -->
|
|
205
|
-
<div class="flex flex-col gap-md mb-3xl">
|
|
206
|
-
{/* V2 flat fields: name, tagLine, description (not info.name etc.) */}
|
|
207
|
-
<h1 class="font-display text-4xl">{service.name}</h1>
|
|
208
|
-
{service.tagLine && (
|
|
209
|
-
<p class="text-secondary text-lg">{service.tagLine}</p>
|
|
210
|
-
)}
|
|
211
|
-
<div class="flex gap-md items-center text-sm">
|
|
212
|
-
<span class="font-medium">{duration} min</span>
|
|
213
|
-
{price && <span class="text-primary font-bold text-lg">{price}</span>}
|
|
214
|
-
</div>
|
|
215
|
-
{service.description && (
|
|
216
|
-
<p class="text-base leading-relaxed">{service.description}</p>
|
|
217
|
-
)}
|
|
218
|
-
</div>
|
|
219
|
-
|
|
220
|
-
<!-- Booking flow — client-side islands -->
|
|
221
|
-
<section class="border-t border-[--color-border] pt-2xl">
|
|
222
|
-
<h2 class="font-display text-2xl mb-xl">Book This Service</h2>
|
|
223
|
-
<ServiceBookingFlow
|
|
224
|
-
serviceId={service._id}
|
|
225
|
-
serviceName={service.name ?? 'this service'}
|
|
226
|
-
serviceType={service.type ?? 'APPOINTMENT'}
|
|
227
|
-
client:only="react"
|
|
228
|
-
/>
|
|
229
|
-
</section>
|
|
230
|
-
</div>
|
|
231
|
-
</main>
|
|
232
|
-
</Layout>
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
### ServiceBookingFlow coordinator
|
|
236
|
-
|
|
237
|
-
Because `AvailabilityCalendar` and `BookingForm` share state (the selected slot) and need to transition between each other, write a thin coordinator island `ServiceBookingFlow.tsx` that manages this state:
|
|
238
|
-
|
|
239
|
-
```typescript
|
|
240
|
-
// src/components/ServiceBookingFlow.tsx
|
|
241
|
-
import { useState } from 'react';
|
|
242
|
-
import AvailabilityCalendar from './AvailabilityCalendar';
|
|
243
|
-
import BookingForm from './BookingForm';
|
|
244
|
-
|
|
245
|
-
interface Props {
|
|
246
|
-
serviceId: string;
|
|
247
|
-
serviceName: string;
|
|
248
|
-
serviceType: 'APPOINTMENT' | 'CLASS'; // threaded into both islands
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
export default function ServiceBookingFlow({ serviceId, serviceName, serviceType }: Props) {
|
|
252
|
-
const [selectedSlot, setSelectedSlot] = useState<any>(null);
|
|
253
|
-
|
|
254
|
-
const handleSuccess = (bookingId: string, startDate?: string) => {
|
|
255
|
-
const params = new URLSearchParams({ bookingId });
|
|
256
|
-
if (startDate) params.set('startDate', startDate);
|
|
257
|
-
if (serviceName) params.set('service', serviceName);
|
|
258
|
-
window.location.href = `/booking-confirmation?${params.toString()}`;
|
|
259
|
-
};
|
|
260
|
-
|
|
261
|
-
if (!selectedSlot) {
|
|
262
|
-
return (
|
|
263
|
-
<AvailabilityCalendar
|
|
264
|
-
serviceId={serviceId}
|
|
265
|
-
serviceName={serviceName}
|
|
266
|
-
serviceType={serviceType}
|
|
267
|
-
onSlotSelected={setSelectedSlot}
|
|
268
|
-
/>
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
<BookingForm
|
|
274
|
-
serviceId={serviceId}
|
|
275
|
-
serviceName={serviceName}
|
|
276
|
-
serviceType={serviceType}
|
|
277
|
-
slot={selectedSlot}
|
|
278
|
-
onSuccess={handleSuccess}
|
|
279
|
-
onCancel={() => setSelectedSlot(null)}
|
|
280
|
-
/>
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
```
|
|
284
|
-
|
|
285
|
-
Add `ServiceBookingFlow` to the files you own and include it in your pre-return assertion.
|
|
286
|
-
|
|
287
|
-
---
|
|
288
|
-
|
|
289
|
-
## Page 3 — `/booking-confirmation.astro`
|
|
290
|
-
|
|
291
|
-
### Behavior — show the real status + cancel inline
|
|
292
|
-
|
|
293
|
-
The confirmation page **re-fetches the booking** to show its **actual status** (`CONFIRMED` vs `PENDING` approval) — so the user sees whether the booking was approved, not just an optimistic "you're booked" — and embeds the cancel action. It re-uses the **anonymous-action-token** flow: the server elevates (app identity) to mint a per-booking token (`getAnonymousActionToken`), then reads it with the no-auth `bookingsGetBookingAnonymously`, falling back to the redirect params (`?bookingId=...&startDate=...&service=...`) if the lookup fails. Cancel is the same `<ManageBooking>` island used by `/manage-booking` (pass `showSummary={false}` to avoid duplicating the headline). See the canonical template `booking-confirmation.astro`.
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
// frontmatter (essentials):
|
|
297
|
-
const bookingId = Astro.url.searchParams.get('bookingId');
|
|
298
|
-
const { token } = bookingId ? await auth.elevate(bookings.getAnonymousActionToken)(bookingId) : { token: null };
|
|
299
|
-
const { booking, allowedAnonymousActions } = token ? await bookings.bookingsGetBookingAnonymously(token) : {};
|
|
300
|
-
const status = booking?.status ?? null; // CONFIRMED | PENDING | …
|
|
301
|
-
const isPending = status === 'PENDING' || status === 'PENDING_APPROVAL';
|
|
302
|
-
// Render: "You're booked / confirmed" vs "Booking received — awaiting confirmation" by status,
|
|
303
|
-
// a Status badge, and <ManageBooking … canCancel={!!allowedAnonymousActions?.cancel} showSummary={false} />.
|
|
304
|
-
```
|
|
305
|
-
|
|
306
|
-
> **Two different `elevate`s, don't conflate them:** `@wix/essentials` `auth.elevate(fn)` works in `@wix/astro` SSR (used here to mint the token and across the listing/detail/CMS pages); the unavailable one is `wixClient.auth.elevate` (the instance method on a hand-built `createClient({ auth: OAuthStrategy(...) })`).
|
|
307
|
-
|
|
308
|
-
### Template
|
|
309
|
-
|
|
310
|
-
Use the canonical template verbatim and adapt copy/styling: `<SKILL_ROOT>/references/astro/templates/bookings/booking-confirmation.astro`. It renders a status-aware headline ("You're booked / confirmed" vs "Booking received — awaiting confirmation"), a Status badge, and the embedded `<ManageBooking … showSummary={false} />` cancel — wrapped in `max-w-prose` (a built-in literal; never a bare `max-w-<size>`, which can fall through to the spacing scale).
|
|
311
|
-
|
|
312
|
-
---
|
|
313
|
-
|
|
314
|
-
## Server endpoints + manage-booking page (verified live)
|
|
315
|
-
|
|
316
|
-
Some bookings operations require **Manage Bookings** scope and so **cannot** run from the browser `OAuthStrategy` client — they must be Astro **API routes** (`output: "server"`) that elevate to app identity. Elevation rule (important): `auth.elevate()` only grants permissions for a real **SDK function**; for raw REST with no SDK wrapper, elevate the authenticated fetch itself.
|
|
317
|
-
|
|
318
|
-
### `src/pages/api/confirm-booking.ts` — hold the seat (always, for create flows)
|
|
319
|
-
`createBooking` returns `CREATED`, which holds nothing. Confirm to occupy the seat + validate availability:
|
|
320
|
-
```typescript
|
|
321
|
-
import { auth } from "@wix/essentials";
|
|
322
|
-
import { bookings } from "@wix/bookings";
|
|
323
|
-
// POST { bookingId, revision }
|
|
324
|
-
await auth.elevate(bookings.confirmBooking)(bookingId, String(revision), {
|
|
325
|
-
paymentStatus: "NOT_PAID",
|
|
326
|
-
flowControlSettings: { checkAvailabilityValidation: true },
|
|
327
|
-
});
|
|
328
|
-
```
|
|
329
|
-
|
|
330
|
-
### `src/pages/api/waitlist.ts` — native waitlist (v1; no v2/v3 exists)
|
|
331
|
-
Register on the native waitlist when a session is full. Resolve a `contactId` (Contacts API, by email → create if missing), then `POST /bookings/v1/waitlist/register`. All via the **elevated authenticated fetch** (raw REST, so elevate `fetchWithAuth`, not a closure):
|
|
332
|
-
```typescript
|
|
333
|
-
import { auth, httpClient } from "@wix/essentials";
|
|
334
|
-
const ef = auth.elevate(httpClient.fetchWithAuth as typeof fetch) as typeof fetch;
|
|
335
|
-
const api = (url: string, body: unknown) =>
|
|
336
|
-
ef(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body) });
|
|
337
|
-
|
|
338
|
-
// 1. contactId — query then create
|
|
339
|
-
let contactId = (await (await api("https://www.wixapis.com/contacts/v4/contacts/query",
|
|
340
|
-
{ query: { filter: { "info.emails.email": email } } })).json()).contacts?.[0]?.id;
|
|
341
|
-
if (!contactId) contactId = (await (await api("https://www.wixapis.com/contacts/v4/contacts",
|
|
342
|
-
{ info: { name: { first }, emails: { items: [{ email, primary: true }] } } })).json()).contact?.id;
|
|
343
|
-
|
|
344
|
-
// 2. register — waitingResource is the v1 session id from the SESSION_CAPACITY_EXCEEDED message
|
|
345
|
-
await api("https://www.wixapis.com/bookings/v1/waitlist/register", {
|
|
346
|
-
waitingResource: sessionId,
|
|
347
|
-
formInfo: {
|
|
348
|
-
paymentSelection: [{ rateLabel: "general", numberOfParticipants: totalParticipants }],
|
|
349
|
-
contactDetails: { contactId, firstName: first, email },
|
|
350
|
-
},
|
|
351
|
-
});
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
### `src/pages/manage-booking.astro` + `ManageBooking.tsx` — view/cancel (anonymous-token flow)
|
|
355
|
-
A plain visitor token can't list/cancel bookings (`queryExtendedBookings`/`cancelBooking` are APP/MEMBER only). The supported visitor path is the **anonymous action token**: the page (SSR) elevates to **mint** a per-booking token, then the no-auth anonymous methods act on it (the token can safely go to the browser):
|
|
356
|
-
```typescript
|
|
357
|
-
// manage-booking.astro frontmatter — bookingId from ?bookingId=
|
|
358
|
-
const { token } = await auth.elevate(bookings.getAnonymousActionToken)(bookingId); // APP identity → elevate
|
|
359
|
-
const { booking, allowedAnonymousActions } = await bookings.bookingsGetBookingAnonymously(token); // no auth
|
|
360
|
-
// Pass token + booking.revision + allowedAnonymousActions.cancel to a <ManageBooking client:only> island.
|
|
361
|
-
```
|
|
362
|
-
In the island (no-auth, visitor client OK):
|
|
363
|
-
```typescript
|
|
364
|
-
await wixClient.bookings.bookingsCancelBookingAnonymously(token, revision); // cancel
|
|
365
|
-
```
|
|
366
|
-
Reschedule-in-place uses `bookingsRescheduleBookingAnonymously(token, slot, { revision })`, but the CLASS `V2Slot` shape isn't fully pinned — prefer **cancel-and-rebook** (link to the service) unless you've verified the class slot shape live.
|
|
367
|
-
|
|
368
|
-
Link to it from the confirmation page: `/manage-booking?bookingId=<id>&service=<name>`.
|
|
369
|
-
|
|
370
|
-
---
|
|
371
|
-
|
|
372
|
-
## Navigation contribution
|
|
373
|
-
|
|
374
|
-
After writing your three route files, patch `src/layouts/Navigation.astro` to add the Services nav link. Insert at the `<!-- nav:links -->` marker:
|
|
375
|
-
|
|
376
|
-
```astro
|
|
377
|
-
<!-- nav:links -->
|
|
378
|
-
<a href="/services">Services</a>
|
|
379
|
-
```
|
|
380
|
-
|
|
381
|
-
Follow the marker discipline rules in `references/shared/IMPLEMENTER.md` — preserve the marker line, insert after it, do not disturb adjacent content.
|
|
382
|
-
|
|
383
|
-
---
|
|
384
|
-
|
|
385
|
-
## Home page contribution
|
|
386
|
-
|
|
387
|
-
Patch `src/pages/index.astro` to add a brief services teaser section. Insert at the `<!-- home:bookings -->` marker (emitted by `compose.mjs` when bookings is loaded). You are a **shell-patcher** (BUILD-astro § build wave, chain B) — `index.astro`/`Navigation.astro` patches are serialized against the other shell-patching verticals. The teaser should:
|
|
388
|
-
- Show the first 3 services (query the live SDK like `/services` does; your `seeded.bookings` slice tells you what exists)
|
|
389
|
-
- Include a "View All Services" CTA linking to `/services`
|
|
390
|
-
- Use `<ServiceCard>` for each service entry
|
|
391
|
-
|
|
392
|
-
If the `<!-- home:bookings -->` marker is absent from the file, skip the home contribution — do NOT invent a new insertion point.
|
|
393
|
-
|
|
394
|
-
---
|
|
395
|
-
|
|
396
|
-
## Pre-return file-existence assertion
|
|
397
|
-
|
|
398
|
-
Before returning `status: "complete"`, verify all of these files exist on disk:
|
|
399
|
-
- `src/pages/services/index.astro`
|
|
400
|
-
- `src/pages/services/[slug].astro`
|
|
401
|
-
- `src/pages/booking-confirmation.astro`
|
|
402
|
-
- `src/pages/manage-booking.astro`
|
|
403
|
-
- `src/components/ServiceBookingFlow.tsx`
|
|
404
|
-
|
|
405
|
-
Missing file → return `status: "partial"` with `errors: [{ code: "PHASE4_FILE_MISSING", path: "<path>" }]`.
|
|
406
|
-
|
|
407
|
-
---
|
|
408
|
-
|
|
409
|
-
## Return contract
|
|
410
|
-
|
|
411
|
-
```json
|
|
412
|
-
{
|
|
413
|
-
"status": "complete",
|
|
414
|
-
"phase": "pages",
|
|
415
|
-
"vertical": "bookings",
|
|
416
|
-
"files": [
|
|
417
|
-
"src/pages/services/index.astro",
|
|
418
|
-
"src/pages/services/[slug].astro",
|
|
419
|
-
"src/pages/booking-confirmation.astro",
|
|
420
|
-
"src/pages/manage-booking.astro",
|
|
421
|
-
"src/components/ServiceBookingFlow.tsx"
|
|
422
|
-
],
|
|
423
|
-
"contributions": [
|
|
424
|
-
{ "file": "src/layouts/Navigation.astro", "marker": "nav:links" },
|
|
425
|
-
{ "file": "src/pages/index.astro", "marker": "home:bookings" }
|
|
426
|
-
]
|
|
427
|
-
}
|
|
428
|
-
```
|
|
1
|
+
# Bookings Pages — the SSR routes (astro)
|
|
2
|
+
|
|
3
|
+
The `pages` scope of the bookings vertical. You write the route files. The
|
|
4
|
+
**logic** is in `../../bookings/FLOW.md` (read it first); this doc is the astro
|
|
5
|
+
wiring + the gotchas. Read `references/shared/IMPLEMENTER.md` +
|
|
6
|
+
`references/shared/STYLING.md` first.
|
|
7
|
+
|
|
8
|
+
> **Start from the templates — don't re-author the SSR data access.** Examples at
|
|
9
|
+
> `<SKILL_ROOT>/references/astro/templates/bookings/`: `ServiceCard.astro`,
|
|
10
|
+
> `services/index.astro`, `services/[slug].astro`, `booking-confirmation.astro`.
|
|
11
|
+
> Read and adapt (copy, headings, styling); keep the SDK query shapes.
|
|
12
|
+
|
|
13
|
+
## Pages you write
|
|
14
|
+
|
|
15
|
+
| File | From template | Role |
|
|
16
|
+
|------|---------------|------|
|
|
17
|
+
| `src/pages/services/index.astro` | `…/services/index.astro` | SSR catalog grid (SEO). `queryServices` via ambient `@wix/essentials`. |
|
|
18
|
+
| `src/components/ServiceCard.astro` | `…/ServiceCard.astro` | static service card for the grid. |
|
|
19
|
+
| `src/pages/services/[slug].astro` | `…/services/[slug].astro` | SSR detail (SEO) + mounts `<ServiceBookingFlow client:only="react">`. Also fetches the **booking-form schema** and passes `fields` to the island. |
|
|
20
|
+
| `src/pages/booking-confirmation.astro` | `…/booking-confirmation.astro` | confirmation — renders from the `service`/`startDate` query params (no re-fetch). |
|
|
21
|
+
|
|
22
|
+
Plus the nav/home links (shell chain — see below).
|
|
23
|
+
|
|
24
|
+
## astro-specific rules
|
|
25
|
+
|
|
26
|
+
1. **SSR reads use the ambient `@wix/essentials` client — never `OAuthStrategy`.**
|
|
27
|
+
`const result = await auth.elevate(services.queryServices)({ query: { filter: { appId: BOOKING_APP_ID }, paging: { limit: 100 } }, conditionalFields: ["STAFF_MEMBER_DETAILS"] }).find();`
|
|
28
|
+
Building `createClient({ auth: OAuthStrategy({ clientId: import.meta.env.* }) })`
|
|
29
|
+
in frontmatter 500s in the deployed runtime (the env var is client-only).
|
|
30
|
+
Always include **`appId`** in the filter. Single service by slug: the
|
|
31
|
+
`.eq("mainSlug.name", slug).eq("appId", BOOKING_APP_ID).limit(1).find()` chain.
|
|
32
|
+
The same elevation works for `services.queryLocations` and
|
|
33
|
+
`categoriesV2.queryCategories` (used by the optional catalog filters, below).
|
|
34
|
+
1b. **Optional catalog filters (location + category) — auto-skip; see `../../bookings/FLOW.md` §7.**
|
|
35
|
+
The catalog page reads `?locationId`/`?category` from the request URL and merges
|
|
36
|
+
them into the **same** `queryServices` filter (location → `locations.business.id`
|
|
37
|
+
$hasSome, or the synthetic `locations.type` mapping; category → `category.id` $eq).
|
|
38
|
+
It SSRs `queryLocations()` (show the selector only when business+custom+customer
|
|
39
|
+
count > 1) and `queryCategories().find()` (non-fatal — render without the bar on
|
|
40
|
+
failure; show only when > 1 category). The chosen location is carried into the
|
|
41
|
+
detail link (`ServiceCard` `locationId`/`locationType` props) so availability is
|
|
42
|
+
scoped to it. Reference: `…/templates/bookings/services/index.astro`.
|
|
43
|
+
2. **The detail page fetches the booking-form schema server-side.** Read
|
|
44
|
+
`service.form._id`'s form via `@wix/forms` (`auth.elevate(forms.getForm)(formId)`),
|
|
45
|
+
map `formFields` to `{ label, target, required, componentType, identifier, options }`,
|
|
46
|
+
and pass the array as `fields` into `<ServiceBookingFlow>`. **Filter to fields with a
|
|
47
|
+
recognized string `componentType`** (`TEXT_INPUT`/`PHONE_INPUT`/`DROPDOWN`) — skip
|
|
48
|
+
complex object-valued fields (e.g. the default form's multi-line `ADDRESS`), or
|
|
49
|
+
`createBooking` fails with "must be object". Pass the full `service` too — the
|
|
50
|
+
booking step reads its payment/policy. Same field-mapping as `../forms/CONTACT_FORM.md`.
|
|
51
|
+
Also pass, through to `<ServiceBookingFlow>`: `service.staffMemberDetails?.staffMembers`
|
|
52
|
+
(the staff picker — the service was queried with `STAFF_MEMBER_DETAILS`); the
|
|
53
|
+
service's **business `locations`** — sourced from `auth.elevate(services.queryLocations)()`
|
|
54
|
+
(the site's real business locations) intersected with the service's own location ids,
|
|
55
|
+
NOT from `service.locations` alone (its ids can be ones the availability engine doesn't
|
|
56
|
+
recognize → 0 slots) — so the calendar scopes availability to one location and avoids
|
|
57
|
+
duplicate per-location slots (FLOW.md §7); and the `?locationId` read from the request
|
|
58
|
+
URL (the picker's default).
|
|
59
|
+
3. **Only the read pages are SSR.** The detail page SSRs the service info for SEO,
|
|
60
|
+
then the booking UI runs in the `client:only` island. Do not SSR availability.
|
|
61
|
+
4. **Confirmation renders from query params.** The booking step redirects with
|
|
62
|
+
`?service=…&startDate=…`; render those. Do not re-fetch a stranger's booking
|
|
63
|
+
from this public page.
|
|
64
|
+
5. **Two dirs deep** (`services/[slug].astro`) → import with `../../`, not `../../../`.
|
|
65
|
+
|
|
66
|
+
## Shell chain — nav + home (this scope patches shared files)
|
|
67
|
+
|
|
68
|
+
- **Navigation** — at `<!-- nav:links -->` in `src/components/Navigation.astro`,
|
|
69
|
+
append a link to **`/services`** (label fitting the brand: "Book", "Services", "Appointments").
|
|
70
|
+
- **Home** — at `<!-- home:bookings -->` in `src/pages/index.astro`, add a short,
|
|
71
|
+
brand-voiced CTA linking to **`/services`** (heading + a line + a button-styled
|
|
72
|
+
link). Use the site's primary-action token (`--color-accent`). Do not list
|
|
73
|
+
specific services or hardcode slugs — the catalog renders them live.
|
|
74
|
+
- Append at the markers; keep the markers; never replace other packs' insertions.
|
|
75
|
+
|
|
76
|
+
## Return
|
|
77
|
+
`{ status, phase, scope: "pages", summary, data, files, errors }`. Run the pre-return file-existence assertion (`../../bookings/INSTRUCTIONS.md`). If `index.astro`/`Navigation.astro` lacks its marker, return `status: "partial"` with the coded error.
|
|
@@ -180,7 +180,7 @@ Key details:
|
|
|
180
180
|
|
|
181
181
|
### 3. FAQ Page (`src/pages/faq/index.astro`)
|
|
182
182
|
|
|
183
|
-
**Layout width:** Center the page column with `container-reading` (from designer `global.css`) or `max-w-6xl mx-auto px-
|
|
183
|
+
**Layout width:** Center the page column with `container-reading` (from designer `global.css`) or `max-w-6xl mx-auto px-6` when `--container-6xl` exists in `@theme`. **Do not** use `max-w-3xl` unless `--container-3xl` is declared in `@theme` — with only a spacing scale, Tailwind maps `max-w-3xl` to ~5rem and the FAQ collapses to one word per line. See `references/shared/STYLING.md` § "Prose / reading width".
|
|
184
184
|
|
|
185
185
|
```astro
|
|
186
186
|
---
|
|
@@ -195,7 +195,7 @@ const categories = [...grouped.entries()].map(([name, items]) => ({ name, items
|
|
|
195
195
|
|
|
196
196
|
<Layout title="FAQ">
|
|
197
197
|
<main>
|
|
198
|
-
<div class="container-reading px-
|
|
198
|
+
<div class="container-reading px-6">
|
|
199
199
|
<h1>Frequently Asked Questions</h1>
|
|
200
200
|
<p>Find answers to common questions below, or use the search to find what you need.</p>
|
|
201
201
|
|
|
@@ -33,7 +33,7 @@ No REST calls required. Page-design scopes are frontend-only — no `curl`, no M
|
|
|
33
33
|
| `home` | build wave | `src/pages/index.astro` (composes pack home-sections + brand hero/CTA into the Composer-written shell) |
|
|
34
34
|
| `static` | build wave | `src/pages/about.astro`, `src/pages/faq.astro` |
|
|
35
35
|
| `store-pages` | build wave | `src/pages/products/index.astro`, `src/pages/products/[slug].astro`, `src/components/ProductCard.astro`, `src/pages/cart.astro`, `src/pages/thank-you.astro` |
|
|
36
|
-
| `services-pages` | build wave | `src/pages/services/index.astro`, `src/pages/services/[slug].astro`, `src/pages/booking-confirmation.astro
|
|
36
|
+
| `services-pages` | build wave | `src/pages/services/index.astro`, `src/pages/services/[slug].astro`, `src/pages/booking-confirmation.astro` |
|
|
37
37
|
| `blog-pages` | build wave | `src/pages/blog/index.astro`, `src/pages/blog/[slug].astro` |
|
|
38
38
|
| `contact-page` | build wave | `src/pages/contact.astro` |
|
|
39
39
|
|
|
@@ -102,8 +102,8 @@ All page scopes share common inputs and rules. Scope-specific details follow.
|
|
|
102
102
|
</section>
|
|
103
103
|
```
|
|
104
104
|
8. **Comments in frontmatter.** Use `//` or `/* */` — never HTML `<!-- -->` comments in `.astro` frontmatter (it's TypeScript, not HTML). HTML comments in the template section are fine.
|
|
105
|
-
9. **Responsive.** All pages must work at mobile (320px), tablet (768px), and desktop (1024px+). Use Tailwind responsive prefixes (`md:`, `lg:`) and
|
|
106
|
-
10. **Use Tailwind utility classes in templates.** For layout, spacing, typography, and responsive design — use utility classes directly in the markup. Contract classes are still required for components referenced by Phase 3/4 agents. Mix both: `<div class="product-grid grid grid-cols-1 md:grid-cols-3 gap-
|
|
105
|
+
9. **Responsive.** All pages must work at mobile (320px), tablet (768px), and desktop (1024px+). Use Tailwind responsive prefixes (`md:`, `lg:`) and Tailwind's numeric spacing scale (`gap-4`, `py-24`, …).
|
|
106
|
+
10. **Use Tailwind utility classes in templates.** For layout, spacing, typography, and responsive design — use utility classes directly in the markup. Contract classes are still required for components referenced by Phase 3/4 agents. Mix both: `<div class="product-grid grid grid-cols-1 md:grid-cols-3 gap-6">`. Always use brand `@theme` tokens (e.g., `bg-bark`, `text-cream`) — never default Tailwind colors.
|
|
107
107
|
11. **No duplicate files.** If two scopes list the same file (rare), the scope whose prompt explicitly names it as "owned" takes precedence. If unclear, write it and note the overlap in your return.
|
|
108
108
|
12. **Do NOT write to `src/styles/components-<pack>.css` or `src/styles/global.css`** — `components-<pack>.css` is pre-copied from the skill template by the orchestrator (build-wave pre-batch); `global.css` is owned by the Composer. Writing to either causes double-write conflicts.
|
|
109
109
|
|
|
@@ -230,7 +230,7 @@ All store-facing pages designed together for visual coherence.
|
|
|
230
230
|
|
|
231
231
|
All bookings-facing pages designed together for visual coherence. **Canonical templates exist** at `<SKILL_ROOT>/references/astro/templates/bookings/` — adapt them (copy, headings, styling); do not re-author the SDK/booking-flow wiring (`references/astro/bookings/SERVICES_PAGES.md` is the wiring spec).
|
|
232
232
|
|
|
233
|
-
**Files:** `src/pages/services/index.astro`, `src/pages/services/[slug].astro`, `src/pages/booking-confirmation.astro
|
|
233
|
+
**Files:** `src/pages/services/index.astro`, `src/pages/services/[slug].astro`, `src/pages/booking-confirmation.astro`
|
|
234
234
|
|
|
235
235
|
**Services listing (`/services`):**
|
|
236
236
|
- Page heading + optional brand tagline
|
|
@@ -240,13 +240,10 @@ All bookings-facing pages designed together for visual coherence. **Canonical te
|
|
|
240
240
|
**Service detail (`/services/[slug]`):**
|
|
241
241
|
- `container-reading` width (never a bare `max-w-<size>`); optional banner image **only when the service has media** (image-less is the common seed case — no fixed-ratio placeholder)
|
|
242
242
|
- Title, tagline, duration + price row, description
|
|
243
|
-
- "Book this service" section mounting the `ServiceBookingFlow` island (`client:only="react"`, threads `serviceType`) — calendar slots use `time-slot` classes (`--available`/`--selected
|
|
243
|
+
- "Book this service" section mounting the `ServiceBookingFlow` island (`client:only="react"`, threads `serviceType`) — the week calendar uses the `availability-*` classes (week strip + `availability-day`), slots use `time-slot` classes (`--available`/`--selected`), and the form uses the `booking-*` family
|
|
244
244
|
|
|
245
245
|
**Booking confirmation (`/booking-confirmation`):**
|
|
246
|
-
- `max-w-prose`, centered;
|
|
247
|
-
|
|
248
|
-
**Manage booking (`/manage-booking`):**
|
|
249
|
-
- `container-reading`; booking summary + cancel via the `manage-booking` class family
|
|
246
|
+
- `max-w-prose`, centered; "Thank you for booking" headline + the service name and time (read from the `service`/`startDate` query params) + a link back to `/services`. No re-fetch, no cancel UI (manage/cancel is out of scope).
|
|
250
247
|
|
|
251
248
|
### Scope: `blog-pages`
|
|
252
249
|
|
|
@@ -320,7 +317,7 @@ The JSON block MUST be the **last** content in your message — the parent parse
|
|
|
320
317
|
| Author a `.tsx` React island in a page scope | Phase 3 Components writes the islands; the route only **mounts** them (`client:load`) |
|
|
321
318
|
| Ship hardcoded data arrays as the final page | Query live SDK data in frontmatter (per the vertical reference) and bind it; hardcoded shapes are only a fallback for empty queries |
|
|
322
319
|
| Write `global.css` or `@theme` tokens | The Composer owns `global.css`; you consume the published token contract |
|
|
323
|
-
| Invent global semantic classes for layout/spacing/typography (`.featured-section`, `.page-header`) | Use Tailwind utilities derived from `@theme` tokens at the call site (`<section class="py-
|
|
320
|
+
| Invent global semantic classes for layout/spacing/typography (`.featured-section`, `.page-header`) | Use Tailwind utilities derived from `@theme` tokens at the call site (`<section class="py-24">`); see `../../shared/STYLING.md` |
|
|
324
321
|
| Override `global.css` rules from page `<style>` blocks | Pages can add co-located styles for one-off decoration; never override Composer-owned global classes |
|
|
325
322
|
| HTML `<!-- comment -->` in `.astro` frontmatter | Use `//` or `/* */` — frontmatter is TypeScript |
|
|
326
323
|
| Use external placeholder image services (picsum, unsplash) | Use colored `<div>` placeholders with `data-decorative-slot` — the orchestrator injects Image Phase 1 URLs later |
|
|
@@ -22,7 +22,7 @@ Files this agent MUST NOT touch:
|
|
|
22
22
|
|
|
23
23
|
Read the design tokens from `.wix/design-tokens.css` (on disk, gate-verified present before the wave).
|
|
24
24
|
|
|
25
|
-
For layout/spacing/typography, compose Tailwind utilities derived from `@theme` tokens at the call site (`<div class="grid grid-cols-1 md:grid-cols-[1fr_22rem] gap-
|
|
25
|
+
For layout/spacing/typography, compose Tailwind utilities derived from `@theme` tokens at the call site (`<div class="grid grid-cols-1 md:grid-cols-[1fr_22rem] gap-12">`). For published global semantic classes (`cart-summary`, `cart-total`, `checkout-btn`, `cart-empty` — the compound + interactive-state patterns), use the ACTUAL class name verbatim. Row-internal class names (`cart-item-qty`, `qty-btn`, etc.) come from the template — keep them as-is and live in `components-ecom.css`. See `references/shared/STYLING.md` and `references/shared/IMPLEMENTER.md` § "Styling: tokens-first, classes as exception".
|
|
26
26
|
|
|
27
27
|
## Template files
|
|
28
28
|
|
|
@@ -61,7 +61,7 @@ Full cart display with two-column layout, quantity editing, remove, unavailable
|
|
|
61
61
|
Class names used (template provides them — do not invent):
|
|
62
62
|
- **Global semantic classes** (in `global.css`, designer-owned): `cart-summary`, `cart-total`, `checkout-btn`, `cart-empty` — compound patterns + interactive states
|
|
63
63
|
- **Row-internal classes** (in `components-ecom.css`, written by this scope): `cart-item-qty`, `qty-btn`, `qty-value`, `cart-item-unavailable`, `cart-item-actions`, `cart-item-prices`, `cart-item-full-price`, `cart-item-unit-price`, `cart-item-line-total`, `cart-item-remove`, `cart-item-image-link`, `cart-item-name-link`, `cart-discount`, `cart-discount-name`, `cart-discount-amount`, `cart-applied-discounts`, `cart-applied-discounts-name`, `cart-item-option`, `cart-item-modifiers`
|
|
64
|
-
- **Layout/spacing in markup as utilities**: `cart-grid` → `<div class="grid grid-cols-1 md:grid-cols-[1fr_22rem] gap-
|
|
64
|
+
- **Layout/spacing in markup as utilities**: `cart-grid` → `<div class="grid grid-cols-1 md:grid-cols-[1fr_22rem] gap-12 md:gap-16 md:items-start">`; `cart-items` → `<div class="flex flex-col gap-6">`; `cart-item` → `<div class="flex gap-6 pb-6 border-b border-rule last:border-0">`; `cart-subtotal` → `<div class="flex justify-between items-baseline">`; `cart-item-image` → utilities; `cart-item-info` → utilities; `cart-item-name` → `class="font-display text-lg leading-tight"`
|
|
65
65
|
|
|
66
66
|
### 2. `src/components/CartBadge.tsx`
|
|
67
67
|
|