@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
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// bookingDriver.ts — the booking sequence in plain @wix SDK calls.
|
|
2
|
+
// Framework-agnostic: no React, no signals, no UI. Any framework's booking step
|
|
3
|
+
// imports book()/navigateToCheckout() and drives them; APPOINTMENT and CLASS
|
|
4
|
+
// share the same sequence.
|
|
5
|
+
//
|
|
6
|
+
// Mechanism = ecom Cart V2 on every path: createBooking leaves the booking
|
|
7
|
+
// CREATED, then the cart holds the seat — a paid service hands off to the
|
|
8
|
+
// Wix-hosted checkout, a free / pay-in-person service places the order directly.
|
|
9
|
+
//
|
|
10
|
+
// On @wix/astro the SDK calls run ambiently (the visitor client is provided by
|
|
11
|
+
// @wix/essentials, like the ecom CartView island) — no createClient/OAuthStrategy
|
|
12
|
+
// needed. On an own/own-build SPA, acquire a visitor client with
|
|
13
|
+
// createClient({ modules, auth: OAuthStrategy({ clientId }) }) and call the same
|
|
14
|
+
// functions through it. Keep the SDK calls, payload shapes, and the sequence;
|
|
15
|
+
// adapt only brand copy/styling in the UI that drives this module.
|
|
16
|
+
|
|
17
|
+
import { bookings } from "@wix/bookings";
|
|
18
|
+
import { createCart, calculateCart, placeOrder } from "@wix/auto_sdk_ecom_cart-v-2";
|
|
19
|
+
import { redirects } from "@wix/redirects";
|
|
20
|
+
|
|
21
|
+
// ── Constants (C:src/services/constants.ts) ────────────────────────────────
|
|
22
|
+
export const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
23
|
+
export const STAFF_MEMBER_RESOURCE_TYPE_ID = "1cd44cf8-756f-41c3-bd90-3e2ffcaf1155";
|
|
24
|
+
|
|
25
|
+
// ── Types ───────────────────────────────────────────────────────────────────
|
|
26
|
+
// FormValues = the submitted booking-form values, keyed by each field's `target`
|
|
27
|
+
// (NOT hardcoded field names). The default booking form's targets are snake_case
|
|
28
|
+
// `first_name`/`last_name`/`email`/`phone`; read them from the form schema and key
|
|
29
|
+
// the values by `target` — never hardcode the keys here.
|
|
30
|
+
export type FormValues = Record<string, unknown>;
|
|
31
|
+
|
|
32
|
+
export interface SelectedSlot {
|
|
33
|
+
serviceType: "APPOINTMENT" | "CLASS";
|
|
34
|
+
serviceId: string;
|
|
35
|
+
localStartDate: string; // local "YYYY-MM-DDTHH:mm:ss" (NOT UTC) — maps to slot.startDate
|
|
36
|
+
localEndDate: string; // " " → slot.endDate
|
|
37
|
+
timezone: string;
|
|
38
|
+
// APPOINTMENT:
|
|
39
|
+
scheduleId?: string;
|
|
40
|
+
locationId?: string; // from slot.location._id (the id field is `_id`, not `.id`)
|
|
41
|
+
locationType?: string; // "BUSINESS" | "CUSTOMER" | "CUSTOM"
|
|
42
|
+
// CLASS:
|
|
43
|
+
eventId?: string;
|
|
44
|
+
// Resource chosen on the slot (flow 5). When absent the driver emits the
|
|
45
|
+
// ANY_RESOURCE fallback so Wix auto-assigns a bookable staff member.
|
|
46
|
+
resource?: { _id: string; name?: string };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface BookParams {
|
|
50
|
+
service: any; // the @wix/bookings Service (read payment.options + bookingPolicy)
|
|
51
|
+
slot: SelectedSlot;
|
|
52
|
+
formSubmission: FormValues; // values from the @wix/forms Form, keyed by target
|
|
53
|
+
timezone: string;
|
|
54
|
+
totalParticipants?: number;
|
|
55
|
+
/** consumer override; when omitted, derived from service.payment.options */
|
|
56
|
+
selectedPaymentOption?: "ONLINE" | "OFFLINE";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const BookResultType = {
|
|
60
|
+
CheckoutRequired: "checkout_required",
|
|
61
|
+
CheckoutSkipped: "checkout_skipped",
|
|
62
|
+
} as const;
|
|
63
|
+
export type BookResult =
|
|
64
|
+
| { type: typeof BookResultType.CheckoutRequired; cartId: string }
|
|
65
|
+
| { type: typeof BookResultType.CheckoutSkipped; orderId: string };
|
|
66
|
+
|
|
67
|
+
// ── Payment derivation ───────────────────────────────────────────────────────
|
|
68
|
+
function deriveSelectedPaymentOption(service: any): "ONLINE" | "OFFLINE" {
|
|
69
|
+
const options = service?.payment?.options;
|
|
70
|
+
if (options?.online && !options?.inPerson) return "ONLINE";
|
|
71
|
+
if (!options?.online && options?.inPerson) return "OFFLINE";
|
|
72
|
+
return "ONLINE"; // both / neither → ONLINE (consumer override wins, see below)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// LocationType maps from the availability slot's type → the booking endpoint enum.
|
|
76
|
+
function mapLocationType(slotType?: string): string {
|
|
77
|
+
switch (slotType) {
|
|
78
|
+
case "BUSINESS":
|
|
79
|
+
return "OWNER_BUSINESS";
|
|
80
|
+
case "CUSTOMER":
|
|
81
|
+
return "CUSTOM";
|
|
82
|
+
case "CUSTOM":
|
|
83
|
+
return "OWNER_CUSTOM";
|
|
84
|
+
default:
|
|
85
|
+
return "OWNER_BUSINESS";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── buildBookingRequest ───────────────────────────────────────────────────────
|
|
90
|
+
function buildBookingRequest(params: BookParams) {
|
|
91
|
+
const { service, slot, formSubmission, timezone } = params;
|
|
92
|
+
const resource = slot.resource; // prefer the slot's chosen resource when present
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
booking: {
|
|
96
|
+
// Consumer override wins over the service-config heuristic.
|
|
97
|
+
selectedPaymentOption:
|
|
98
|
+
params.selectedPaymentOption ?? deriveSelectedPaymentOption(service),
|
|
99
|
+
totalParticipants: params.totalParticipants || 1,
|
|
100
|
+
bookedEntity: {
|
|
101
|
+
slot: {
|
|
102
|
+
serviceId: slot.serviceId,
|
|
103
|
+
// APPOINTMENT carries scheduleId; CLASS carries eventId (Wix derives the rest).
|
|
104
|
+
scheduleId: slot.scheduleId ?? undefined,
|
|
105
|
+
startDate: slot.localStartDate,
|
|
106
|
+
endDate: slot.localEndDate,
|
|
107
|
+
timezone,
|
|
108
|
+
eventId: slot.eventId ?? undefined,
|
|
109
|
+
// Resource: use the chosen one, else the ANY_RESOURCE fallback so Wix
|
|
110
|
+
// auto-assigns a bookable staff member. Appointment availability slots
|
|
111
|
+
// return availableResources:[] yet book fine via ANY_RESOURCE.
|
|
112
|
+
...(resource
|
|
113
|
+
? { resource: { _id: resource._id, name: resource.name } }
|
|
114
|
+
: {
|
|
115
|
+
resourceSelections: [
|
|
116
|
+
{
|
|
117
|
+
resourceTypeId: STAFF_MEMBER_RESOURCE_TYPE_ID,
|
|
118
|
+
selectionMethod: "ANY_RESOURCE",
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
}),
|
|
122
|
+
location:
|
|
123
|
+
slot.locationId || slot.locationType
|
|
124
|
+
? {
|
|
125
|
+
...(slot.locationId ? { _id: slot.locationId } : {}),
|
|
126
|
+
locationType: mapLocationType(slot.locationType),
|
|
127
|
+
}
|
|
128
|
+
: { locationType: "OWNER_BUSINESS" },
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
participantNotification: {
|
|
133
|
+
metadata: { channels: "EMAIL,SMS" },
|
|
134
|
+
notifyParticipants: true,
|
|
135
|
+
},
|
|
136
|
+
sendSmsReminder: true,
|
|
137
|
+
formSubmission,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ── buildCartRequest ──────────────────────────────────────────────────────────
|
|
142
|
+
function buildCartRequest(args: {
|
|
143
|
+
bookingIds: string[];
|
|
144
|
+
contactDetails?: any;
|
|
145
|
+
businessLocationId?: string | null;
|
|
146
|
+
}) {
|
|
147
|
+
const cart: any = { source: { channelType: "WEB" } };
|
|
148
|
+
if (args.businessLocationId)
|
|
149
|
+
cart.businessInfo = { locationId: args.businessLocationId };
|
|
150
|
+
if (args.contactDetails) {
|
|
151
|
+
cart.customerInfo = args.contactDetails.email
|
|
152
|
+
? { email: args.contactDetails.email }
|
|
153
|
+
: {};
|
|
154
|
+
if (args.contactDetails.fullAddress?.country) {
|
|
155
|
+
cart.deliveryInfo = { address: { ...args.contactDetails.fullAddress } };
|
|
156
|
+
cart.paymentInfo = {
|
|
157
|
+
billingAddress: { ...args.contactDetails.fullAddress },
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
catalogItems: args.bookingIds.map((id) => ({
|
|
163
|
+
quantity: 1,
|
|
164
|
+
catalogReference: { catalogItemId: id, appId: BOOKING_APP_ID },
|
|
165
|
+
})),
|
|
166
|
+
cart,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ── isCheckoutRequired ────────────────────────────────────────────────────────
|
|
171
|
+
function isCheckoutRequired(cart: any, summary: any, service: any): boolean {
|
|
172
|
+
if (service?.bookingPolicy?.cancellationFeePolicy?.enabled) return true;
|
|
173
|
+
const total = Number(summary?.priceSummary?.total?.amount ?? 0);
|
|
174
|
+
if (total === 0) return false;
|
|
175
|
+
if (cart?.lineItems?.[0]?.paymentConfig?.paymentOption === "FULL_PAYMENT_OFFLINE")
|
|
176
|
+
return false;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── canBook ───────────────────────────────────────────────────────────────────
|
|
181
|
+
function canBook(params: BookParams): boolean {
|
|
182
|
+
const slotOk =
|
|
183
|
+
!!params.slot &&
|
|
184
|
+
!!params.slot.localStartDate &&
|
|
185
|
+
(params.slot.serviceType === "CLASS"
|
|
186
|
+
? !!params.slot.eventId
|
|
187
|
+
: !!params.slot.scheduleId);
|
|
188
|
+
return slotOk && params.formSubmission != null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── book — the full sequence ──────────────────────────────────────────────────
|
|
192
|
+
export async function book(params: BookParams): Promise<BookResult> {
|
|
193
|
+
if (!canBook(params))
|
|
194
|
+
throw new Error("Cannot book: missing slot or form submission");
|
|
195
|
+
|
|
196
|
+
// 1. createBooking → CREATED (the cart holds the seat)
|
|
197
|
+
const req = buildBookingRequest(params);
|
|
198
|
+
const created = await bookings.createBooking(req.booking as any, {
|
|
199
|
+
participantNotification: req.participantNotification,
|
|
200
|
+
sendSmsReminder: req.sendSmsReminder,
|
|
201
|
+
formSubmission: req.formSubmission as any,
|
|
202
|
+
});
|
|
203
|
+
const bookingId = created?.booking?._id;
|
|
204
|
+
if (!bookingId) throw new Error("Failed to create booking");
|
|
205
|
+
const contactDetails = created?.booking?.contactDetails;
|
|
206
|
+
|
|
207
|
+
// 2. createCart — one catalog item per booking id (channel WEB, bookings appId)
|
|
208
|
+
const businessLocationId = params.slot.locationId ?? undefined;
|
|
209
|
+
const cart = await createCart(
|
|
210
|
+
buildCartRequest({ bookingIds: [bookingId], contactDetails, businessLocationId }),
|
|
211
|
+
);
|
|
212
|
+
const cartId = cart?._id;
|
|
213
|
+
if (!cartId) throw new Error("Failed to create cart");
|
|
214
|
+
|
|
215
|
+
// 3. calculateCart → totals (not stored on the Cart V2 entity)
|
|
216
|
+
const { cart: calculatedCart, summary } = await calculateCart(cartId);
|
|
217
|
+
if (!calculatedCart || !summary) throw new Error("Failed to calculate cart");
|
|
218
|
+
|
|
219
|
+
// 4. checkout required? → redirect (paid) ; else placeOrder (free/offline)
|
|
220
|
+
if (isCheckoutRequired(calculatedCart, summary, params.service)) {
|
|
221
|
+
return { type: BookResultType.CheckoutRequired, cartId };
|
|
222
|
+
}
|
|
223
|
+
const order = await placeOrder(cartId);
|
|
224
|
+
const orderId = order?.orderId;
|
|
225
|
+
if (!orderId) throw new Error("Failed to place order");
|
|
226
|
+
return { type: BookResultType.CheckoutSkipped, orderId };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ── navigateToCheckout ────────────────────────────────────────────────────────
|
|
230
|
+
// Paid services: hand the cart to the Wix-hosted ecom checkout. Same shape on
|
|
231
|
+
// every path — ecomCheckout.checkoutId = the cartId.
|
|
232
|
+
export async function navigateToCheckout(
|
|
233
|
+
cartId: string,
|
|
234
|
+
postFlowUrl: string,
|
|
235
|
+
): Promise<void> {
|
|
236
|
+
const { redirectSession } = await redirects.createRedirectSession({
|
|
237
|
+
ecomCheckout: { checkoutId: cartId },
|
|
238
|
+
callbacks: { postFlowUrl },
|
|
239
|
+
});
|
|
240
|
+
if (redirectSession?.fullUrl) {
|
|
241
|
+
window.location.href = redirectSession.fullUrl;
|
|
242
|
+
} else {
|
|
243
|
+
throw new Error("Failed to create redirect session");
|
|
244
|
+
}
|
|
245
|
+
}
|