@wix/agent-skills 1.15.0 → 1.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/package.json +1 -1
- package/skills/wix-app/references/DATA_COLLECTION.md +5 -3
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +2 -0
- package/skills/wix-app/references/STORES_VERSIONING.md +9 -211
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +5 -0
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +151 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +57 -0
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +6 -0
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +3 -1
- package/skills/wix-app/references/stores/CATEGORIES.md +22 -0
- package/skills/wix-app/references/stores/GET_PRODUCT.md +99 -0
- package/skills/wix-app/references/stores/INVENTORY.md +37 -0
- package/skills/wix-app/references/stores/QUERY.md +87 -0
- package/skills/wix-app/references/stores/WRITE.md +55 -0
- package/skills/wix-headless/SKILL.md +6 -1
- package/skills/wix-headless/entry/bootstrap.mjs +2 -2
- package/skills/wix-headless/entry/skill.md +0 -2
- package/skills/wix-headless/references/CAPABILITIES.md +6 -4
- package/skills/wix-headless/references/DISCOVERY.md +1 -1
- package/skills/wix-headless/references/FEEDBACK.md +16 -5
- package/skills/wix-headless/references/SDK_HANDOFF.md +2 -0
- package/skills/wix-headless/references/SEED.md +3 -1
- package/skills/wix-headless/references/SETUP.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +42 -2
- package/skills/wix-headless/references/inline-recipes/setup-bookings.md +5 -1
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +1 -1
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +21 -5
- package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +3 -1
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-experiences.md +73 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +38 -2
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +20 -1
- package/skills/wix-headless/references/inline-recipes/setup-restaurants.md +1 -1
- package/skills/wix-headless/references/managed/CONNECT.md +1 -1
- package/skills/wix-headless/references/managed/CREATE.md +1 -1
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +22 -0
- package/skills/wix-headless/references/self-managed/DEPLOYMENT.md +8 -0
- package/skills/wix-headless/references/stripe/DEPLOYMENT.md +8 -0
- package/skills/wix-manage/SKILL.md +8 -5
- package/skills/wix-manage/references/analytics/query-site-analytics.md +66 -9
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -0
- package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +6 -5
- package/skills/wix-manage/references/bookings/create-course-service.md +30 -5
- package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +76 -42
- package/skills/wix-manage/references/cms/cms-schema-management.md +6 -0
- package/skills/wix-manage/references/get-paid/how-to-setup-wix-payments.md +30 -7
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +225 -144
- package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +19 -10
- package/skills/wix-manage/references/sites/site-import.md +270 -0
- package/skills/wix-manage/references/stores/create-product-from-image.md +8 -6
- package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +16 -10
- package/skills/wix-vibe-headless/SKILL.md +6 -0
- package/skills/wix-vibe-headless/platforms/README.md +22 -0
- package/skills/wix-vibe-headless/platforms/base44.md +142 -0
- package/skills/wix-vibe-headless/platforms/generic.md +87 -0
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +5 -1
- package/skills/wix-vibe-headless/references/bookings/wix-bookings-services.js +3 -1
- package/skills/wix-vibe-headless/references/events/wix-events-browse.js +2 -1
- package/skills/wix-vibe-headless/references/portfolio/wix-portfolio.js +84 -55
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +11 -2
- package/skills/wix-vibe-headless/references/storefront/wix-store-catalog.js +5 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Stores — Get Product
|
|
2
|
+
|
|
3
|
+
## Get a single product
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
if (v === 'V3_CATALOG') {
|
|
7
|
+
const product = await productsV3.getProduct(id); // returns Product directly
|
|
8
|
+
return product;
|
|
9
|
+
}
|
|
10
|
+
const { product } = await products.getProduct(id); // V1 wraps in { product }
|
|
11
|
+
return product;
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Read product options and choices (V1 vs V3)
|
|
17
|
+
|
|
18
|
+
V1 and V3 differ in **both where choices live and what the choice fields are named**. Getting this wrong causes `TS2339` at validate time.
|
|
19
|
+
|
|
20
|
+
| | V1 | V3 |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| Options array on product | `product.productOptions` | `product.options` |
|
|
23
|
+
| Option name | `option.name` | `option.name` |
|
|
24
|
+
| Option type | `option.optionType` (`"color"` / `"drop_down"`) | `option.optionRenderType` (`"SWATCH_CHOICES"` / `"TEXT_CHOICES"`) |
|
|
25
|
+
| **Choices array** | `option.choices` ← **directly on the option** | `option.choicesSettings?.choices` ← **nested** |
|
|
26
|
+
| Choice display value | `choice.value` | `choice.name` |
|
|
27
|
+
| Choice color | `choice.value` (hex string) | `choice.colorCode` |
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
if (v === 'V3_CATALOG') {
|
|
31
|
+
const product = await productsV3.getProduct(id);
|
|
32
|
+
for (const option of product.options ?? []) {
|
|
33
|
+
const choices = option.choicesSettings?.choices ?? []; // ✅ nested — NOT option.choices / option.optionValues (TS2339)
|
|
34
|
+
for (const choice of choices) {
|
|
35
|
+
render(option.name, choice.name, choice.colorCode); // name = label; colorCode = hex for SWATCH_CHOICES
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
const res = await products.getProduct(id);
|
|
40
|
+
const product = res.product!; // product is optional in the raw type; ! matches SDK's strict-mode guarantee
|
|
41
|
+
for (const option of product.productOptions ?? []) {
|
|
42
|
+
for (const choice of option.choices ?? []) { // ✅ direct in V1
|
|
43
|
+
render(option.name, choice.value); // value = label or hex string
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**In a site plugin on a product page**: get `productId` from `widget.getProp('product-id')`, then call the appropriate version's `getProduct` — same pattern as above.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Modifiers (V1 `manageVariants=false` options)
|
|
54
|
+
|
|
55
|
+
In V3, options that **don't** create variants (V1 `manageVariants=false`) live on `product.modifiers`, not `product.options`. They use the identical `choicesSettings.choices` pattern. Reading only `product.options` silently misses these.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
if (v === 'V3_CATALOG') {
|
|
59
|
+
const product = await productsV3.getProduct(id);
|
|
60
|
+
// variant-creating options
|
|
61
|
+
for (const option of product.options ?? []) {
|
|
62
|
+
for (const choice of option.choicesSettings?.choices ?? []) {
|
|
63
|
+
render(option.name, choice.name, choice.colorCode);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// display-only / free-text customizations (formerly manageVariants=false)
|
|
67
|
+
for (const modifier of product.modifiers ?? []) {
|
|
68
|
+
for (const choice of modifier.choicesSettings?.choices ?? []) {
|
|
69
|
+
render(modifier.name, choice.name, choice.colorCode);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Variant choices (V1 object map → V3 array)
|
|
78
|
+
|
|
79
|
+
V1 `variant.choices` was a plain object map `{ "Size": "Small" }`. V3 restructures it as an array — accessing it with a key returns `undefined` silently.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
if (v === 'V3_CATALOG') {
|
|
83
|
+
const product = await productsV3.getProduct(id);
|
|
84
|
+
// variants are NOT returned by queryProducts — use getProduct or Read-Only Variants API
|
|
85
|
+
for (const variant of product.variantsInfo?.variants ?? []) {
|
|
86
|
+
for (const c of variant.choices ?? []) {
|
|
87
|
+
render(c.optionChoiceNames?.optionName, c.optionChoiceNames?.choiceName);
|
|
88
|
+
}
|
|
89
|
+
// ❌ variant.choices['Size'] — always undefined in V3 (object map is gone)
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
const res = await products.getProduct(id);
|
|
93
|
+
const product = res.product!; // product is optional in the raw type; ! matches SDK's strict-mode guarantee
|
|
94
|
+
for (const variant of product.variants ?? []) {
|
|
95
|
+
// V1: choices is { [optionName]: value }
|
|
96
|
+
render(Object.entries(variant.choices ?? {}).map(([k, v]) => `${k}:${v}`).join(', '));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Stores — Inventory
|
|
2
|
+
|
|
3
|
+
## Increment stock
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
import { inventory, inventoryItemsV3 } from '@wix/stores';
|
|
7
|
+
|
|
8
|
+
if (v === 'V3_CATALOG') {
|
|
9
|
+
// Per-variant; flat shape.
|
|
10
|
+
await inventoryItemsV3.bulkIncrementInventoryItems([
|
|
11
|
+
{ inventoryItemId, incrementBy: 5 },
|
|
12
|
+
]);
|
|
13
|
+
} else {
|
|
14
|
+
// Per-product (variant optional).
|
|
15
|
+
await inventory.incrementInventory([
|
|
16
|
+
{ productId, variantId, incrementBy: 5 },
|
|
17
|
+
]);
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
To find a V3 inventory item ID, use `inventoryItemsV3.searchInventoryItems` filtered by `productId` / `variantId`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Query inventory
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
if (v === 'V3_CATALOG') {
|
|
29
|
+
const res = await inventoryItemsV3.queryInventoryItems().eq('productId', productId).find();
|
|
30
|
+
return res.items;
|
|
31
|
+
}
|
|
32
|
+
// V1: filter is a JSON-stringified expression.
|
|
33
|
+
const res = await inventory.queryInventory({
|
|
34
|
+
query: { filter: JSON.stringify({ productId }) },
|
|
35
|
+
});
|
|
36
|
+
return res.inventoryItems;
|
|
37
|
+
```
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Stores — Query / List Products
|
|
2
|
+
|
|
3
|
+
## List products with pagination
|
|
4
|
+
|
|
5
|
+
Both versions expose a fluent query builder, but the paging method differs:
|
|
6
|
+
|
|
7
|
+
| Aspect | V1 (`products.queryProducts()`) | V3 (`productsV3.queryProducts()`) |
|
|
8
|
+
|--------|--------------------------------|----------------------------------|
|
|
9
|
+
| API style | Fluent builder: `.skip().limit().find()` | Fluent builder: `.skipTo(cursor).limit().find()` |
|
|
10
|
+
| Pagination | Offset (`.skip(n)`) | Cursor (`.skipTo(cursor)`) |
|
|
11
|
+
| Result `items` | `res.items` (V1 `Product[]`) | `res.items` (V3 `Product[]`) |
|
|
12
|
+
| Total count | `res.totalCount` | **None** — V3 only has `cursors.next` + `hasNext()` |
|
|
13
|
+
| `hasNext` | `res.hasNext()` (method) | `res.hasNext()` (method) |
|
|
14
|
+
| Next cursor | n/a | `res.cursors.next` (string) |
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { catalogVersioning, products, productsV3 } from '@wix/stores';
|
|
18
|
+
|
|
19
|
+
export interface ProductsPage {
|
|
20
|
+
products: unknown[]; // narrow at call site
|
|
21
|
+
nextCursor: string | null; // V3 only
|
|
22
|
+
hasNext: boolean;
|
|
23
|
+
totalCount: number | null; // V1 only
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function listProductsPage(
|
|
27
|
+
limit: number,
|
|
28
|
+
cursorOrSkip: string | number | undefined,
|
|
29
|
+
): Promise<ProductsPage> {
|
|
30
|
+
const v = await getVersion();
|
|
31
|
+
if (v === 'STORES_NOT_INSTALLED') {
|
|
32
|
+
return { products: [], nextCursor: null, hasNext: false, totalCount: 0 };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (v === 'V3_CATALOG') {
|
|
36
|
+
let builder = productsV3.queryProducts().limit(limit);
|
|
37
|
+
if (typeof cursorOrSkip === 'string') builder = builder.skipTo(cursorOrSkip);
|
|
38
|
+
// Do NOT chain a sort — see OVERVIEW.md gotcha #10.
|
|
39
|
+
const res = await builder.find();
|
|
40
|
+
return {
|
|
41
|
+
products: res.items,
|
|
42
|
+
nextCursor: res.cursors.next ?? null,
|
|
43
|
+
hasNext: res.hasNext(),
|
|
44
|
+
totalCount: null,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const skip = typeof cursorOrSkip === 'number' ? cursorOrSkip : 0;
|
|
49
|
+
const res = await products.queryProducts().skip(skip).limit(limit).find();
|
|
50
|
+
return {
|
|
51
|
+
products: res.items,
|
|
52
|
+
nextCursor: null,
|
|
53
|
+
hasNext: res.hasNext(),
|
|
54
|
+
totalCount: res.totalCount ?? null,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
> **Two ways to call V3 `queryProducts`:** the canonical builder shown above, and a direct-call form `productsV3.queryProducts({ cursorPaging: { limit, cursor } })` returning a `Promise<{ products, pagingMetadata }>`. Both compile and run, but the builder is more idiomatic and matches V1's shape.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Display price/stock without fetching variants
|
|
64
|
+
|
|
65
|
+
V3 `queryProducts` does not return variants. Read product-level rollup fields instead:
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
function displayPrice(p: { actualPriceRange?: { minValue?: { amount?: string }; maxValue?: { amount?: string } } }): string {
|
|
69
|
+
const min = p.actualPriceRange?.minValue?.amount;
|
|
70
|
+
const max = p.actualPriceRange?.maxValue?.amount;
|
|
71
|
+
if (!min) return '—';
|
|
72
|
+
return max && max !== min ? `${min} – ${max}` : min;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function stockLabel(status: string | undefined): string {
|
|
76
|
+
switch (status) {
|
|
77
|
+
case 'IN_STOCK': return 'In Stock';
|
|
78
|
+
case 'OUT_OF_STOCK': return 'Out of Stock';
|
|
79
|
+
case 'PARTIALLY_OUT_OF_STOCK': return 'Limited';
|
|
80
|
+
case 'PREORDER': return 'Pre-order';
|
|
81
|
+
default: return '—';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// V1 path: product.stock.inventoryStatus (same UPPER_SNAKE_CASE values)
|
|
85
|
+
// V3 path: product.inventory.availabilityStatus (adds PREORDER)
|
|
86
|
+
// SKU lives on the variant — show "—" in lists, or use Read-Only Variants API.
|
|
87
|
+
```
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Stores — Write Operations (Create / Update / Delete)
|
|
2
|
+
|
|
3
|
+
## Create a product (single-variant, with price)
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
if (v === 'V3_CATALOG') {
|
|
7
|
+
const product = await productsV3.createProduct({
|
|
8
|
+
name: 'My Product',
|
|
9
|
+
productType: 'PHYSICAL', // UPPER_CASE
|
|
10
|
+
variantsInfo: {
|
|
11
|
+
variants: [{
|
|
12
|
+
price: { actualPrice: { amount: '19.99' } }, // string, on the variant
|
|
13
|
+
sku: 'SKU-001',
|
|
14
|
+
}],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
return product;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const { product } = await products.createProduct({
|
|
21
|
+
name: 'My Product',
|
|
22
|
+
productType: 'physical', // lower-case
|
|
23
|
+
priceData: { price: 19.99 }, // number, on the product
|
|
24
|
+
sku: 'SKU-001',
|
|
25
|
+
});
|
|
26
|
+
return product;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Update a product (V3 needs `revision`)
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
if (v === 'V3_CATALOG') {
|
|
35
|
+
// Signature: updateProduct(_id, productFields). Returns Product directly.
|
|
36
|
+
const current = await productsV3.getProduct(id);
|
|
37
|
+
if (current.revision == null) throw new Error(`Product ${id} has no revision`);
|
|
38
|
+
return await productsV3.updateProduct(id, {
|
|
39
|
+
revision: current.revision, // narrowed — required under exactOptionalPropertyTypes
|
|
40
|
+
name: 'New name',
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
// V1: updateProduct(id, productFields). Returns { product }.
|
|
44
|
+
const { product } = await products.updateProduct(id, { name: 'New name' });
|
|
45
|
+
return product;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Delete a product
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
if (v === 'V3_CATALOG') await productsV3.deleteProduct(id);
|
|
54
|
+
else await products.deleteProduct(id);
|
|
55
|
+
```
|
|
@@ -91,7 +91,12 @@ If the credentials are absent, the Wix backend isn't reachable — **stop with a
|
|
|
91
91
|
4. **Handoff** (`references/SDK_HANDOFF.md`) — after Setup and Seed, **emit** the integration guide: SDK bootstrap, per-capability call shapes, the **seeded IDs**, and the `@wix/*` package list.
|
|
92
92
|
5. **Finalize deployment** (`<TYPE_DIR>/DEPLOYMENT.md`) — run the project-type's finalize steps.
|
|
93
93
|
|
|
94
|
-
**Throughout any run** — if the user asks to send feedback to Wix, complains/gets frustrated, or the
|
|
94
|
+
**Throughout any run** — if the user asks to send feedback to Wix, complains/gets frustrated, or the
|
|
95
|
+
run hits any real friction (a confusing error, a doc gap, a tooling dead end, a workaround you had to
|
|
96
|
+
invent — it doesn't need to repeat), **offer** to relay it to Wix per `references/FEEDBACK.md`. Default
|
|
97
|
+
to offering rather than waiting to be asked. Every flow's finalize step (`<TYPE_DIR>/DEPLOYMENT.md` /
|
|
98
|
+
`managed/CREATE.md` / `managed/CONNECT.md`) ends with the same checkpoint — treat it as a required
|
|
99
|
+
self-check, not optional. Send only after an explicit yes — never automatically.
|
|
95
100
|
|
|
96
101
|
**Managed create / connect / iterate** — after Discovery, hand the whole run to the managed flow:
|
|
97
102
|
- **create** → **`references/managed/CREATE.md`** (scaffold → Setup → Seed → build the frontend → release).
|
|
@@ -26,7 +26,7 @@ const AGENT_ENV = { ...process.env, AI_AGENT: process.env.AI_AGENT || 'wix-headl
|
|
|
26
26
|
|
|
27
27
|
// run a command, capture stdout+stderr (combined), return {status, out}
|
|
28
28
|
function capture(cmd, args, opts = {}) {
|
|
29
|
-
const r = spawnSync(cmd, args, { encoding: 'utf8', shell:
|
|
29
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', shell: isWin, env: AGENT_ENV, ...opts });
|
|
30
30
|
return { status: r.status ?? 1, out: `${r.stdout || ''}${r.stderr || ''}`, error: r.error };
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -43,7 +43,7 @@ function checkCli() {
|
|
|
43
43
|
// ── 2. Login (human-in-the-loop device code; forward the CLI's own events) ───
|
|
44
44
|
function login() {
|
|
45
45
|
return new Promise((resolve) => {
|
|
46
|
-
const child = spawn(WIX[0], [...WIX.slice(1), 'login'], { shell:
|
|
46
|
+
const child = spawn(WIX[0], [...WIX.slice(1), 'login'], { shell: isWin, env: AGENT_ENV });
|
|
47
47
|
const rl = readline.createInterface({ input: child.stdout });
|
|
48
48
|
let loggedIn = false;
|
|
49
49
|
// Buffer the CLI's own diagnostics (stderr + any non-event stdout chatter) so a
|
|
@@ -65,8 +65,6 @@ The script emits one JSON object per line:
|
|
|
65
65
|
Install the Wix Headless skills (`CI=1` forces plain non-interactive CLI output — keep it on every Wix CLI command):
|
|
66
66
|
|
|
67
67
|
```bash
|
|
68
|
-
CI=1 wix skills add
|
|
69
|
-
# Fallback if 'wix skills' isn't registered for this project type:
|
|
70
68
|
CI=1 npx skills@latest add wix/skills --yes
|
|
71
69
|
```
|
|
72
70
|
|
|
@@ -48,7 +48,8 @@ The verticals the skill operates end-to-end today: **stores · blog · cms · fo
|
|
|
48
48
|
- **Implementation checklist:** show the event's title, date and time, and location; show the description; show ticket types where they exist (ticketed events); a register/RSVP action; collect additional-guest names where extra guests are allowed; a confirmation after registering.
|
|
49
49
|
|
|
50
50
|
### bookings — appointments and classes
|
|
51
|
-
- **Intent:** book / appointment / schedule / class / session / reserve a slot
|
|
51
|
+
- **Intent:** book / appointment / schedule / class / session / reserve a slot — a **service or appointment with a provider** (a stylist, trainer, tutor, clinic, studio).
|
|
52
|
+
- **Not bookings — route to `restaurants` instead:** a **table at a restaurant** is a Table Reservations concern, not a bookable service; and a **special dining occasion guests reserve** — a wine tasting, cheese pairing, chef's table, or tasting-menu evening at a restaurant — is a **restaurants → Table Reservations *experience*** (see the restaurants entry), **not** a bookings service. Reach for `bookings` only when what's booked is a standalone service/class with a provider, never a seat at a restaurant.
|
|
52
53
|
- **Required site features:** a list of services; a page per service; a way to pick a time and book it.
|
|
53
54
|
- **Implementation checklist:** show each service's name, duration, and price; show the staff or provider; show the available time slots; a book action; a confirmation after booking.
|
|
54
55
|
|
|
@@ -58,9 +59,10 @@ The verticals the skill operates end-to-end today: **stores · blog · cms · fo
|
|
|
58
59
|
- **Implementation checklist:** show each plan's name, price, and billing cycle; list what each plan includes (the perks); a clear "choose plan" action; highlight a recommended tier where it makes sense.
|
|
59
60
|
|
|
60
61
|
### restaurants — menus and food
|
|
61
|
-
- **Intent:** menu / restaurant / cafe / food / dish list / order food / dine-in.
|
|
62
|
-
- **Required site features:** a menu organized into sections; each item with a name, description, and price. (
|
|
63
|
-
- **
|
|
62
|
+
- **Intent:** menu / restaurant / cafe / food / dish list / order food / dine-in / **reserve a table** / a **special dining occasion guests reserve** (wine tasting, cheese pairing, chef's table, tasting-menu evening).
|
|
63
|
+
- **Required site features:** a menu organized into sections; each item with a name, description, and price. **Add-ons, each on demand (separate apps/features; see `SETUP.md`):** online ordering, table reservations, and — **when the brief names special dining occasions** — **experiences** (a reservation that *is* a curated occasion at the restaurant, e.g. a wine-and-cheese pairing), which ride on the Table Reservations app.
|
|
64
|
+
- **Experiences vs bookings — decide here:** an **experience** is a **reservation that is itself a special dining occasion at the restaurant** — it books seats at the restaurant's own location and carries its own name, per-guest price, schedule, and booking form. Use it for pairing sessions, tastings, and chef's tables. Use the separate **`bookings`** vertical only for a **service/appointment/class with a provider** that isn't a seat at the restaurant. For anything a restaurant hosts and seats, prefer an experience.
|
|
65
|
+
- **Implementation checklist:** show the menu grouped into sections in order; each item's name, description, price, and labels (vegan, spicy…) and modifiers / variants where they exist; an order action if online ordering is wired; a reserve-a-table action if reservations are wired; **if experiences are wired, list each experience with its name, description, per-guest price, and schedule, and a reserve-seats action.**
|
|
64
66
|
|
|
65
67
|
### portfolio — showcase projects
|
|
66
68
|
- **Intent:** portfolio / showcase / gallery of projects / creative work / case studies.
|
|
@@ -27,7 +27,7 @@ For each capability, build its `intent.<cap>` block — the inputs the seed step
|
|
|
27
27
|
| events | `{ eventCount, titles: [] }` | `eventCount: 2`, titles brand-derived, future dates |
|
|
28
28
|
| bookings | `{ serviceCount, servicesNamed: [] }` | `serviceCount: 2`, brand-derived service names |
|
|
29
29
|
| pricing-plans | `{ planCount, tiersNamed: [] }` | `planCount: 2` (e.g. Basic / Pro), monthly billing |
|
|
30
|
-
| restaurants | `{ menuName, sections: [{ name, itemCount }] }` | one menu, 2 sections, `itemCount: 3` each |
|
|
30
|
+
| restaurants | `{ menuName, sections: [{ name, itemCount }], ordering?, reservations?, experiences?: [{ name }] }` | one menu, 2 sections, `itemCount: 3` each; add-ons only when the brief names them. **`experiences[]`** = special dining occasions guests reserve (wine/cheese pairing, chef's table) — a restaurant *reservation that is an experience*, **not** the `bookings` vertical (see `CAPABILITIES.md`). |
|
|
31
31
|
| portfolio | `{ collections: [{ name }], projectCount }` | one brand-derived collection, `projectCount: 3` |
|
|
32
32
|
|
|
33
33
|
Counts are deliberately small (the seed shows the shape, not a full catalog).
|
|
@@ -10,16 +10,27 @@ This is **not** support, and not for the user's own site content. It is meta-fee
|
|
|
10
10
|
|
|
11
11
|
## When to offer it (user-approved only — never auto-send)
|
|
12
12
|
|
|
13
|
-
Sending is always the user's call
|
|
14
|
-
|
|
13
|
+
Sending is always the user's call, but **offering is not** — default to offering, don't wait to be
|
|
14
|
+
asked or for the user to complain first. Offer once, in plain prose, in any of these situations; send
|
|
15
|
+
only after an explicit yes:
|
|
15
16
|
|
|
16
17
|
- The user **explicitly asks** to send feedback / report something to Wix ("tell Wix that…", "report
|
|
17
18
|
this", "send feedback"). Then just confirm the wording and send.
|
|
18
19
|
- The user **complains, is frustrated, or reports a Wix bug** in the course of the work ("this API is
|
|
19
20
|
broken", "the docs are wrong", "why is this so hard"). Acknowledge, then offer to pass it on.
|
|
20
|
-
- **The run hit
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
- **The run hit any real friction** — not just repeated failures. A single confusing error, a doc
|
|
22
|
+
that was wrong/stale/missing, an extra round trip you had to make, a workaround you had to invent,
|
|
23
|
+
or anything that cost more turns than it should have all qualify — it doesn't need to have happened
|
|
24
|
+
more than once. When you notice it, offer in the moment: *"That [error/doc gap] tripped us up —
|
|
25
|
+
want me to send it to Wix as feedback?"*
|
|
26
|
+
- **At the natural end of every run** (deployment/handoff, or wherever the skill's flow closes out),
|
|
27
|
+
do a deliberate self-check over the whole session even if nothing felt dramatic at the time — friction
|
|
28
|
+
you shrugged off or silently retried past is still signal. If the self-check turns up anything,
|
|
29
|
+
offer before you sign off rather than letting the run end without asking.
|
|
30
|
+
|
|
31
|
+
Bias toward offering over staying quiet: a redundant offer costs the user one "no thanks"; a friction
|
|
32
|
+
point that goes unreported is a signal Wix never gets. Only skip the offer when the run was genuinely
|
|
33
|
+
clean end to end.
|
|
23
34
|
|
|
24
35
|
When you offer, invite the user to add anything in their own words — whatever they give you goes
|
|
25
36
|
into the message verbatim, in its own "In the user's words" section.
|
|
@@ -45,6 +45,7 @@ For each loaded capability, give the host the **package(s)** from the map below
|
|
|
45
45
|
| restaurants — online ordering (add-on; only when the Orders app is in the run) | add `@wix/ecom` + `@wix/redirects` to the row above | `how-to-code-restaurant-orders.md` — ordering cart/checkout on top of the menu display. |
|
|
46
46
|
| portfolio | `@wix/portfolio` (+ `media` from `@wix/sdk` to resolve image strings; **no** `@wix/ecom`/`@wix/redirects` — display-only) | `how-to-code-portfolio.md` — Portfolio v1 display-only read. |
|
|
47
47
|
| restaurants — table reservations (add-on; only when the Table Reservations app is in the run) | `@wix/table-reservations` (a **distinct** package from `@wix/restaurants`; **no ecom/redirects** — a reservation is a hold, not a purchase) | `how-to-code-restaurant-reservations.md` — book-a-table hold→reserve. |
|
|
48
|
+
| restaurants — experiences (add-on; special dining occasions guests reserve — wine/cheese pairing, chef's table) | `@wix/table-reservations` (same package; the **`experiences`** namespace + `timeSlots`/`reservations`) | `how-to-code-restaurant-reservations.md` — "Booking an experience" (query experiences → scheduled slots → reserve with `experienceId`). |
|
|
48
49
|
|
|
49
50
|
> **Cross-cutting packages** (`CAPABILITIES.md` § "Cross-cutting capabilities"): these aren't per-vertical rows but get added to the package list when the run used them. If **coupons** were seeded, add `@wix/marketing`. The eCommerce checkout packages (`@wix/ecom`, `@wix/redirects`) are already folded into the stores row above — include them once when any purchase-flow vertical is present.
|
|
50
51
|
|
|
@@ -62,6 +63,7 @@ Carry only the **structural** carve-outs the coding step genuinely reads — the
|
|
|
62
63
|
Everything else the host resolves live from the queries in §3. (Static platform constants — Stores/Bookings app IDs, the Blog appDefId — and the public `clientId` are needed too, but they're identical for every site, not seed output; they're in §2/§3.)
|
|
63
64
|
|
|
64
65
|
### 5 · Integration notes (headless-auth specifics the per-module docs won't frame)
|
|
66
|
+
- **Restaurants online ordering has real-world go-live preconditions the code can't satisfy — surface them to the owner.** For a working ordering flow the site needs: **(1) a business location with a real address** (the skill sets this in `setup-restaurant-orders.md` STEP 0 — but if the brief gave no address, a placeholder was used and the owner MUST replace it, or checkout stays "testing only"); **(2) a premium plan**; **(3) a configured payment method**. State these plainly in the guide/summary — the site is not "done" for real orders until the owner completes them. (The same premium+payment note applies to any eCommerce checkout, e.g. stores.)
|
|
65
67
|
- The visitor token has **no per-user identity** → seeded **CMS collections are public/shared** across all visitors (not per-user, not cross-device). Per-user storage needs member auth.
|
|
66
68
|
- Frontend CMS access is **read**; visitor writes go through **Forms** submissions.
|
|
67
69
|
- **Blog comments are member-gated — read public, write authenticated.** Querying/rendering comments is public, so list them server-side in SSR. **Submitting** needs a logged-in member, so don't gate the form behind an upfront login check (that branch is what burns deliberation): render the form always and resolve identity at submit — POST to a backend endpoint (`src/pages/api/*.ts`) that calls `createComment` with the request session; if the caller isn't a member, redirect to the built-in `/api/auth/login?returnUrl=…`. **Do not build the comment form as a client island** — the API-endpoint + session path is the documented shape and avoids the browser-auth detour. The comment API keys (post `referenceId`, Blog appDefId, author lookup via `post.memberId`) are in `how-to-code-a-blog.md`.
|
|
@@ -4,6 +4,8 @@ For each resolved capability, create the backend content its *what* names. This
|
|
|
4
4
|
|
|
5
5
|
Use `$TOKEN` / `$SITE_ID` from the provided authentication mechanism (see `<TYPE_DIR>/AUTHENTICATION.md`). The capabilities are **independent** — no cross-capability ordering or shared data (**two exceptions:** (a) **pricing-plans depends on bookings' service IDs**, so seed bookings before pricing-plans; (b) **restaurants online ordering depends on the restaurants menu** — the ordering add-on binds each menu to an operation, so the menu must be seeded before ordering — both in §2). For each one: read its inline recipe (§1; §2 maps each capability to its `setup-*.md`), build the body from `intent.<cap>` + `brand`, execute against `wixapis.com` with the universal call shape (Bearer `$TOKEN` + `wix-site-id` — see `SETUP.md` §1), and collect the created IDs into a `seeded` map keyed by capability.
|
|
6
6
|
|
|
7
|
+
> **Cleaning up install sample data is fine on a fresh install — but never delete real content without approval.** Some recipes start by removing the app install's **default sample/mock data** (e.g. a demo "Dinner Menu", a "My Portfolio" collection, sample products). That is safe **when you just installed the app and what's present is obviously the install's own sample/demo content**. But the site may already hold **real content** (an iterate/connect re-run, or a site the owner has populated) — if what's there isn't obviously install sample data, or you're unsure, **do not delete it: ask the user for approval first.** Seeding is otherwise **additive** — create the requested content alongside what's there; deleting or overwriting existing products / collections / menus / forms / projects / media is destructive and needs the user's explicit ask or approval.
|
|
8
|
+
|
|
7
9
|
> **Concurrency is an optional optimization, never a requirement.** Seeding the capabilities **one after another always works** — that is the safe default, and an agent doing everything itself simply seeds each in turn. Because the capabilities are independent (above), **if** your runtime can run work concurrently you **may** seed several at the same time to save wall-clock. Treat this strictly as "you may," never "you must": a correct seed must never *depend* on parallelism.
|
|
8
10
|
>
|
|
9
11
|
> **These constraints hold whether you seed serially or concurrently:**
|
|
@@ -36,7 +38,7 @@ Each entry states only the entities, where their count/content comes from, the p
|
|
|
36
38
|
- **events** — *A ticketed or RSVP occasion, or upcoming events.* Recipe: **`setup-events.md`**. Each event is **either `TICKETING`** (paid — it has ticket tiers) **or `RSVP`** (free — the registration form is built-in, so don't seed form fields) per intent. For an **RSVP occasion** (a wedding, party, gathering): one RSVP event with the occasion's real details (title, a **future** date/time, location). For a **ticketed** event: the event plus its ticket tiers (default a single `"General Admission"` tier if none named). For a **listing** site: `intent.events.eventCount` events with brand-appropriate titles and **future** start dates (a default location/timezone is fine). Text-only. **Keep:** nothing for the handoff — events and tiers are discovered **live** (`queryEvents`, `getEventBySlug`, `queryAvailableTickets`); track `eventIds`/`ticketDefinitionIds` only transiently.
|
|
37
39
|
- **bookings** — *Bookable services.* Recipe: **`setup-bookings.md`**. `intent.bookings.serviceCount` services (name + short description fitting `brand`, a simple duration and price); keep the schedule minimal. **Keep:** `serviceIds[]` as **seed-time state** (pricing-plans' coverage step keys on them, below); nothing for the handoff — services are discovered **live** (`queryServices`).
|
|
38
40
|
- **pricing-plans** — *Membership tiers.* Recipe: **`setup-pricing-plans.md`**. `intent.pricing-plans.planCount` recurring plans (name, price, a monthly billing cycle) fitting `brand`. **When bookings is also in this run and the intent is a membership that covers services** (a studio/gym/class pass), also **attach the covered bookings services to the plan** — seed the bookings services first, since coverage keys on their service ids (see the recipe). **Keep:** the bookings coverage only transiently, to wire it during seeding; nothing for the handoff — plans are discovered **live** (`queryPlans`) and plan→service coverage is read **live** at checkout, never from a frozen map (see `how-to-code-pricing-plans.md`).
|
|
39
|
-
- **restaurants** — *A menu.* Recipe: **`setup-restaurants.md`** (add-ons have their own recipes, below). Create the `itemCount` items per section first (name, description, price fitting `brand`), then the `intent.restaurants.sections` referencing those item ids, then the menu referencing the section ids — **items → sections → menu** (build bottom-up: a section is created with its `itemIds`, a menu with its `sectionIds`, so each child must exist before its parent — see `setup-restaurants.md` STEP 1–3). Text-only. **Keep (seed-time only):** `menuId`, `sectionIds[]`, `itemIds[]` to build the tree; nothing goes to the handoff — the frontend reads menus **live** (`listMenus`/`listSections`/`listItems`). **Online ordering (add-on, on demand)** — only when the request calls for ordering (not just a displayed menu): install the Orders app (per `SETUP.md` §2) and follow **`setup-restaurant-orders.md`**; **seed the menu FIRST, then ordering, in the same unit** (ordering auto-provisions per menu either way — see the concurrency note above — but keeping them one unit gives the ordering verify a menu to confirm against). **Keep:** nothing for the handoff — read **live** (`listOperations`/`listFulfillmentMethods`); track `operationId`/`fulfillmentMethodIds[]` transiently. **Table reservations (add-on, on demand, INDEPENDENT of ordering/menu)** — only when the request calls for reservations: install the Table Reservations app (per `SETUP.md` §2) and follow **`setup-restaurant-reservations.md`**; **no menu dependency** (reservations bind to a location, not a menu — do NOT apply the menu-first rule) and **nothing to bulk-seed** (visitors create reservations at runtime); enabling online reservations is **premium-gated** (record the precondition, don't fail). **Keep:** nothing for the handoff — read **live** (`listReservationLocations`); track `reservationLocationId` transiently.
|
|
41
|
+
- **restaurants** — *A menu.* Recipe: **`setup-restaurants.md`** (add-ons have their own recipes, below). Create the `itemCount` items per section first (name, description, price fitting `brand`), then the `intent.restaurants.sections` referencing those item ids, then the menu referencing the section ids — **items → sections → menu** (build bottom-up: a section is created with its `itemIds`, a menu with its `sectionIds`, so each child must exist before its parent — see `setup-restaurants.md` STEP 1–3). Text-only. **Keep (seed-time only):** `menuId`, `sectionIds[]`, `itemIds[]` to build the tree; nothing goes to the handoff — the frontend reads menus **live** (`listMenus`/`listSections`/`listItems`). **Online ordering (add-on, on demand)** — only when the request calls for ordering (not just a displayed menu): install the Orders app (per `SETUP.md` §2) and follow **`setup-restaurant-orders.md`**; **seed the menu FIRST, then ordering, in the same unit** (ordering auto-provisions per menu either way — see the concurrency note above — but keeping them one unit gives the ordering verify a menu to confirm against). **Configure the business-location address (`setup-restaurant-orders.md` STEP 0) — required for ordering to work end-to-end; without it Wix limits ordering to "testing only" and checkout breaks.** If the brief gives no address, set a placeholder and flag the owner in the handoff. Completing a *paid* order additionally needs premium + a payment method (dashboard/premium — surface, don't fail). **Keep:** nothing for the handoff — read **live** (`listOperations`/`listFulfillmentMethods`); track `operationId`/`fulfillmentMethodIds[]` transiently. **Table reservations (add-on, on demand, INDEPENDENT of ordering/menu)** — only when the request calls for reservations: install the Table Reservations app (per `SETUP.md` §2) and follow **`setup-restaurant-reservations.md`**; **no menu dependency** (reservations bind to a location, not a menu — do NOT apply the menu-first rule) and **nothing to bulk-seed** (visitors create reservations at runtime); **configure the business-location address (`setup-restaurant-reservations.md` STEP 0)** — the reservation location is the site's business location, so set a real address (placeholder + owner flag if the brief names none; shared with ordering if both are present); enabling online reservations is **premium-gated** (record the precondition, don't fail). **Keep:** nothing for the handoff — read **live** (`listReservationLocations`); track `reservationLocationId` transiently. **Experiences (add-on within Table Reservations, on demand)** — only when the request names **special dining occasions guests reserve** (wine/cheese pairing, chef's table, tasting-menu evening — an *experience*, not a `bookings` service; see `CAPABILITIES.md`): they need the **Table Reservations app** (no extra install) and a configured **business-location address** (`setup-restaurant-reservations.md` STEP 0, same as reservations) and, unlike plain reservations, they **are** seeded — create one Experience per named occasion on the default reservation location, following **`setup-restaurant-experiences.md`**. Booking an experience is **premium-gated** like online reservations (record the precondition, don't fail). **Keep:** nothing for the handoff — experiences are read **live** (`queryExperiences`); track `experienceId`(s) transiently.
|
|
40
42
|
- **portfolio** — *A project showcase.* Recipe: **`setup-portfolio.md`**. Create the `intent.portfolio.collections` (title + short description fitting `brand`), then `intent.portfolio.projectCount` projects assigned to them — **collections before projects** (a project's `collectionIds` must hold real collection ids; see the recipe). Text-only by default (omit `coverImage`; images only when `imagery` is on — §1, §4). **Keep (seed-time only):** `collectionIds{<name>}` to assign projects into their collections; nothing goes to the handoff — the frontend lists collections and projects **live** (`collections.queryCollections` / `projects.queryProjects`), so owner-added content appears with no code change.
|
|
41
43
|
|
|
42
44
|
> **Cross-cutting (on demand).** `coupons` is not a standalone capability (`CAPABILITIES.md` § "Cross-cutting capabilities") — there's no app to install and it's not in `verticals[]`. If intent calls for discounts **and** a parent vertical (stores / bookings / events / pricing-plans) is in this run, create coupons scoped to that parent and add the coupon to the `seeded` map (`seeded.coupons = { couponIds[] }`). **Coupons has no inline recipe** — read the create shape from the docs (`DOC_DISCOVERY.md` is the fallback): every coupon needs a scope with a namespace and its parent vertical's app installed (<https://dev.wix.com/docs/api-reference/business-solutions/coupons/about-wix-coupons.md>), and the create-coupon spec needs name/code/start + a scope namespace + exactly one coupon-type field (<https://dev.wix.com/docs/api-reference/business-solutions/coupons/coupons/create-a-coupon.md>). eCommerce needs no seeding — it's the runtime checkout layer that rides along.
|
|
@@ -24,7 +24,7 @@ For each capability in `verticals[]`, install its app by `appDefId` (these are t
|
|
|
24
24
|
- **events** → `140603ad-af8d-84a5-2c80-a0f60cb47351`
|
|
25
25
|
- **bookings** → `13d21c63-b5ec-5912-8397-c3a5ddb27a97`
|
|
26
26
|
- **pricing-plans** → `1522827f-c56c-a5c9-2ac9-00f9e6ae12d3`
|
|
27
|
-
- **restaurants** → `b278a256-2757-4f19-9313-c05c783bec92` (Wix Restaurants **Menus** — the seedable core). Online ordering and table reservations are **separate optional apps**, install only if intent calls for them: Orders (New) → `9a5d83fd-8570-482e-81ab-cfa88942ee60`, Table Reservations → `f9c07de2-5341-40c6-b096-8eb39de391fb`.
|
|
27
|
+
- **restaurants** → `b278a256-2757-4f19-9313-c05c783bec92` (Wix Restaurants **Menus** — the seedable core). Online ordering and table reservations are **separate optional apps**, install only if intent calls for them: Orders (New) → `9a5d83fd-8570-482e-81ab-cfa88942ee60`, Table Reservations → `f9c07de2-5341-40c6-b096-8eb39de391fb`. **Experiences** (special dining occasions guests reserve — wine tastings, chef's tables; see `CAPABILITIES.md`) are a **feature of the Table Reservations app, not a separate install** — installing Table Reservations is all that's needed to create and book them (`setup-restaurant-experiences.md`).
|
|
28
28
|
- **portfolio** → `d90652a2-f5a1-4c7c-84c4-d4cdcc41f130` (Wix Portfolio; the install ships a default sample collection + projects — `setup-portfolio.md` STEP 0 cleans them)
|
|
29
29
|
- **cms** → **no install** (Wix Data is core) — skip
|
|
30
30
|
|
package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md
CHANGED
|
@@ -16,13 +16,16 @@ A contract for the **frontend code** that lets a visitor **book a table**: readi
|
|
|
16
16
|
|
|
17
17
|
## The modules and the client (read this first)
|
|
18
18
|
|
|
19
|
-
**One package: `@wix/table-reservations`** — a **distinct** package from `@wix/restaurants` (menus) and from the Orders app. It exposes
|
|
19
|
+
**One package: `@wix/table-reservations`** — a **distinct** package from `@wix/restaurants` (menus) and from the Orders app. It exposes the namespaces you use:
|
|
20
20
|
|
|
21
21
|
| Need | Package | Module (namespace) |
|
|
22
22
|
|---|---|---|
|
|
23
23
|
| The reservation location (id, party-size bounds) | `@wix/table-reservations` | `reservationLocations` |
|
|
24
24
|
| Available time slots for a date + party size | `@wix/table-reservations` | `timeSlots` |
|
|
25
25
|
| Create / hold / reserve a reservation | `@wix/table-reservations` | `reservations` |
|
|
26
|
+
| **Experiences** — special dining occasions guests reserve (only when experiences are in the run) | `@wix/table-reservations` | `experiences` |
|
|
27
|
+
|
|
28
|
+
> **Experiences** ride on this same package and booking mechanism — see the **Booking an experience** feature below. Skip that section entirely if the run has no experiences.
|
|
26
29
|
|
|
27
30
|
**No cart, no `@wix/ecom`, no `@wix/redirects`.** A basic table reservation is a **hold**, not a purchase — there's no checkout in this flow. (Deposit/prepayment reservations exist — status `PAYMENT_INFORMATION_PENDING` — but they're out of scope for the standard book-a-table flow.)
|
|
28
31
|
|
|
@@ -161,6 +164,42 @@ Docs: <https://dev.wix.com/docs/api-reference/business-solutions/restaurants/res
|
|
|
161
164
|
**Single-shot alternative:** if you collect all details up front (no "hold while they type" step), call `reservations.createReservation({ details: { reservationLocationId, startDate, partySize }, reservee })` instead — same `firstName`/`phone` requirement; it returns `RESERVED`/`REQUESTED` directly, skipping the `HELD` phase. **⚠️ The location/time/party go under a nested `details` object (NOT top-level), and this method returns the `Reservation` DIRECTLY — not wrapped in `{ reservation }`** (unlike `createHeldReservation`/`reserveReservation`). Top-level fields or reading `.reservation` off the result fails to type-check.
|
|
162
165
|
Doc: <https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-reservation.md?apiView=SDK>
|
|
163
166
|
|
|
167
|
+
### Booking an experience (only when experiences are in the run)
|
|
168
|
+
|
|
169
|
+
An **experience** is a reservation that *is* a curated dining occasion (wine/cheese pairing, chef's table). It uses the **same** booking mechanism as a table, with two differences: you **list experiences** to show and choose from, and you offer bookable times **projected from the experience's own schedule** (there is *no* experience-scoped slot API — see Gotcha C) rather than from `getScheduledTimeSlots`, then stamp the chosen `experienceId` onto the reservation. Read the shapes from the docs (`.md?apiView=SDK`); the earned drift gotchas below are what the docs won't tell you.
|
|
170
|
+
|
|
171
|
+
Flow (mirrors the docs' sample flow — <https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/experiences/sample-flows.md>):
|
|
172
|
+
|
|
173
|
+
```js
|
|
174
|
+
import { experiences, reservations } from '@wix/table-reservations';
|
|
175
|
+
|
|
176
|
+
// 1 · list experiences to display (name, price, schedule) — see gotcha A
|
|
177
|
+
const { items: exps = [] } = await experiences.queryExperiences().find();
|
|
178
|
+
const exp = exps.find((e) => e._id === chosenId);
|
|
179
|
+
|
|
180
|
+
// 2 · offer bookable times from the EXPERIENCE'S OWN schedule — do NOT call
|
|
181
|
+
// getScheduledTimeSlots for an experience (it can't scope to one — Gotcha C).
|
|
182
|
+
// queryExperiences() already returned the schedule; project the next occurrences yourself.
|
|
183
|
+
const sched = exp.configuration?.onlineReservations?.businessSchedule; // { durationInMinutes, entries[] }
|
|
184
|
+
// entries[].weeklyOptions.startDaysAndTimes[{ day, time }] → next N dates matching each weekday+time
|
|
185
|
+
// (ONE_TIME entries carry oneTimeOptions.{startDate,startTime}). Build the option list from these.
|
|
186
|
+
|
|
187
|
+
// 3 · book it — experienceId rides in details (see gotcha B); firstName + phone required
|
|
188
|
+
const done = await reservations.createReservation({
|
|
189
|
+
details: { reservationLocationId, experienceId, startDate: chosenOccurrence, partySize },
|
|
190
|
+
reservee: { firstName, lastName, email, phone },
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
Docs: <https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/experiences/query-experiences.md?apiView=SDK> · <https://dev.wix.com/docs/api-reference/business-solutions/restaurants/reservations/reservations/create-reservation.md?apiView=SDK>
|
|
194
|
+
|
|
195
|
+
**⚠️ Gotcha A — `queryExperiences()` is a QUERY BUILDER, not an object-arg method.** Call `experiences.queryExperiences().find()` (chain `.eq()`/`.limit()` etc. before `.find()` if needed) and read the array from the **result's `.items`**. Passing a query object — `queryExperiences({})` — does not type-check and returns no data; `await`-ing the un-`.find()`ed builder gives you the builder, not the results.
|
|
196
|
+
|
|
197
|
+
**⚠️ Gotcha B — `details.experienceId` is a valid REST field but the SDK `Details` type may OMIT it (version drift).** The REST API accepts `experienceId` inside `details` (the Experiences intro and the reservation's own `meta` confirm `details.experienceId`), but an installed `@wix/table-reservations` older than the Experiences feature ships a `Details` type without it — so TypeScript rejects the property. Build the reservation object and **pass it through an `any` cast** (or `// @ts-expect-error`) so `experienceId` reaches the request body; it works at runtime. Don't drop the field to satisfy the compiler — a reservation without it books a plain table, not the experience.
|
|
198
|
+
|
|
199
|
+
**⚠️ Gotcha C — there is NO experience-scoped slot fetch; project times from the experience's own `businessSchedule` instead.** The upstream `get-scheduled-time-slots` doc prose says "pass the GUID of that experience to this method," but **no such parameter exists at any layer**: the SDK (`@wix/auto_sdk_table-reservations_time-slots` ≤ 1.0.47, pulled by `@wix/table-reservations`) field-picks only `{ reservationLocationId, partySize, timeRange }`, and the REST body is those same three fields — so an `experienceId` option is **silently dropped** (an `as any` cast is a **no-op** here, unlike Gotcha B). Calling `getScheduledTimeSlots` for an experience returns the **location's default slots** (wrong day/time/duration), presented to guests as the experience's — and it **fails silently**: a populated-but-wrong list looks healthy, and the premium gate (below) stops live verification before you'd notice. Instead, **derive upcoming occurrences from the experience's own `configuration.onlineReservations.businessSchedule`** — `entries[].weeklyOptions.startDaysAndTimes[{ day, time }]` (or `oneTimeOptions`) + `durationInMinutes`, already returned by `queryExperiences()`. Treat that as a **display projection**; real capacity/conflict enforcement happens server-side when `createReservation` runs.
|
|
200
|
+
|
|
201
|
+
**⚠️ Same premium precondition** as online reservations: **creating** experiences is free, but **booking** one needs a premium site with online reservations enabled (`428 PREMIUM_ONLY` otherwise — `setup-restaurant-experiences.md`). Build the flow; treat a booking failure on a non-premium site as a provisioning precondition, not a code bug.
|
|
202
|
+
|
|
164
203
|
---
|
|
165
204
|
|
|
166
205
|
## Conclusion
|
|
@@ -169,5 +208,6 @@ A correct Table-Reservations frontend:
|
|
|
169
208
|
- uses **`_id`** (never `id`) for the location and reservation, and **defaults the `list*` array** (`= []`) so strict/`astro check` passes;
|
|
170
209
|
- calls **`getTimeSlots(locationId, new Date(date), partySize, options?)`** with **positional** args — the `date` param is a **`Date`** (not the ISO string the doc shows) — and books only **`status === 'AVAILABLE'`** slots (passing the slot's `Date` `startDate` onward);
|
|
171
210
|
- books via **`createHeldReservation({...})` → `reserveReservation(id, reservee, revision)`** (three positional args; `firstName` + `phone` required; 10-minute hold; `reserveReservation` is the only exit from `HELD`);
|
|
172
|
-
- treats **`onlineReservationsEnabled` (premium)** as a precondition — the read/book flow only works once it's on
|
|
211
|
+
- treats **`onlineReservationsEnabled` (premium)** as a precondition — the read/book flow only works once it's on;
|
|
212
|
+
- **for experiences** (when in the run): lists via **`queryExperiences().find()`** (builder → `.items`); offers times **projected from the experience's own `businessSchedule`** — there is **no** experience-scoped `getScheduledTimeSlots` (Gotcha C: the `experienceId` option is silently dropped, so that call returns the location's slots); and books via `createReservation` with **`details.experienceId`** (an `any` cast past the SDK `Details` type — Gotcha B) — same premium precondition.
|
|
173
213
|
</content>
|
|
@@ -20,7 +20,11 @@ A concise checklist for turning a freshly provisioned Wix site with the **Wix Bo
|
|
|
20
20
|
|
|
21
21
|
**⚠️ CRITICAL ORDER REQUIREMENT: resolve a staff resource (STEP 1) and a category (STEP 2) BEFORE creating services (STEP 3).** An APPOINTMENT service is rejected (`MISSING_APPOINTMENT_RESOURCES`) unless `staffMemberIds` is non-empty, and **any service without a `category.id` is invisible on the live site** (see STEP 3). For CLASS services, sessions are created **after** the service (STEP 4) because they need the service's returned `schedule.id`.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
**Check for pre-existing services first.** A freshly provisioned Wix Bookings app may ship demo/sample services (e.g. "Sample service" rows) that would otherwise appear in the live storefront alongside yours — so before STEP 1, check and clean up only if needed:
|
|
24
|
+
1. **List** — `POST https://www.wixapis.com/bookings/v2/services/query` `<AUTH>` with body `{"query": {"paging": {"limit": 100}}}`; collect every `service.id`.
|
|
25
|
+
2. **If any come back, delete them** — `DELETE https://www.wixapis.com/bookings/v2/services/<serviceId>` `<AUTH>` for each. If the list is empty, there's nothing to clean — move on.
|
|
26
|
+
|
|
27
|
+
Clean the install's **own demo services** yourself via the API — that's part of this setup, so don't leave obvious sample services in place or push that work onto the user. But do **not** assume every existing service is a sample: the site may already hold the owner's **real services** (a connect/iterate run, or an owner-populated site). If what's there isn't obviously the install's demo data, or you're unsure, **do not delete it — ask the user first** (`SEED.md`: seeding is additive; deleting real content needs the owner's approval). When they clearly are install samples, delete them before creating yours. (The default **"Business Owner" staff resource** is a resource, **not** a service — leave it; you reuse it in STEP 1.)
|
|
24
28
|
|
|
25
29
|
### STEP 1: Resolve a staff resource (REQUIRED for APPOINTMENT)
|
|
26
30
|
|
|
@@ -18,7 +18,7 @@ A concise checklist for preparing any new Wix site that uses the **Wix Forms** a
|
|
|
18
18
|
## Article: Steps for Setting Up Wix Forms
|
|
19
19
|
**YOU MUST** complete the steps in order, without requiring additional user input: clean the default form (STEP 1) **before** creating yours (STEP 2), then verify (STEP 3).
|
|
20
20
|
|
|
21
|
-
**⚠️ CRITICAL ORDER REQUIREMENT: clean
|
|
21
|
+
**⚠️ CRITICAL ORDER REQUIREMENT: clean the install's default sample form FIRST (STEP 1), before creating any form.** Listing-then-deleting before you create guarantees every id you delete is a pre-existing form, never one you just created — and it prevents a name collision (see STEP 1). **But only delete forms that are obviously the install's own default sample form:** the site may already hold the owner's **real forms** (a connect/iterate run, or an owner-populated site). If what's there isn't obviously install sample data, or you're unsure, **do not delete it — ask the user first** (`SEED.md`: seeding is additive; deleting real content needs the owner's approval).
|
|
22
22
|
|
|
23
23
|
### STEP 1: Clean — remove any pre-existing (install-default) forms
|
|
24
24
|
|