@wix/agent-skills 1.6.0 → 1.8.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.
Files changed (195) hide show
  1. package/README.md +5 -9
  2. package/package.json +1 -1
  3. package/skills/wix-app/SKILL.md +51 -42
  4. package/skills/wix-app/references/BACKEND_EVENT.md +10 -65
  5. package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +101 -406
  6. package/skills/wix-app/references/DASHBOARD_MENU_PLUGIN.md +14 -111
  7. package/skills/wix-app/references/DASHBOARD_MODAL.md +56 -205
  8. package/skills/wix-app/references/DASHBOARD_PAGE.md +19 -98
  9. package/skills/wix-app/references/DASHBOARD_PLUGIN.md +6 -108
  10. package/skills/wix-app/references/DATA_COLLECTION.md +113 -198
  11. package/skills/wix-app/references/EMBEDDED_SCRIPT.md +41 -268
  12. package/skills/wix-app/references/EXTENSION_REGISTRATION.md +27 -76
  13. package/skills/wix-app/references/SERVICE_PLUGIN.md +35 -188
  14. package/skills/wix-app/references/SITE_PLUGIN.md +8 -92
  15. package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
  16. package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +0 -44
  17. package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +19 -330
  18. package/skills/wix-app/references/dashboard-menu-plugin/bookings-slots.md +0 -12
  19. package/skills/wix-app/references/dashboard-menu-plugin/ecommerce-slots.md +7 -0
  20. package/skills/wix-app/references/dashboard-menu-plugin/restaurants-slots.md +0 -12
  21. package/skills/wix-app/references/dashboard-plugin/SLOTS.md +3 -15
  22. package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +2 -18
  23. package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +2 -42
  24. package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +1 -11
  25. package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +1 -11
  26. package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +2 -40
  27. package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +2 -19
  28. package/skills/wix-app/references/service-plugin/VALIDATIONS.md +2 -18
  29. package/skills/wix-app/references/site-plugin/SLOTS.md +17 -68
  30. package/skills/wix-design-system/SKILL.md +4 -0
  31. package/skills/wix-headless/SKILL.md +131 -386
  32. package/skills/wix-headless/references/BUILD-astro.md +246 -0
  33. package/skills/wix-headless/references/BUILD-own-build.md +167 -0
  34. package/skills/wix-headless/references/BUILD.md +105 -0
  35. package/skills/wix-headless/references/DESIGN_SYSTEM.md +175 -0
  36. package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
  37. package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
  38. package/skills/wix-headless/references/DISCOVERY.md +50 -306
  39. package/skills/wix-headless/references/PLAN-connect.md +19 -0
  40. package/skills/wix-headless/references/PLAN-create.md +17 -0
  41. package/skills/wix-headless/references/PLAN.md +92 -0
  42. package/skills/wix-headless/references/SEED.md +214 -0
  43. package/skills/wix-headless/references/SETUP.md +73 -248
  44. package/skills/wix-headless/references/{blog → astro/blog}/BLOG_PAGES.md +6 -5
  45. package/skills/wix-headless/references/{blog → astro/blog}/BLOG_SETUP.md +2 -2
  46. package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +337 -0
  47. package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +428 -0
  48. package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +214 -0
  49. package/skills/wix-headless/references/{cms → astro/cms}/FAQ_KNOWLEDGE_BASE.md +14 -12
  50. package/skills/wix-headless/references/{cms → astro/cms}/PORTFOLIO.md +15 -16
  51. package/skills/wix-headless/references/{cms → astro/cms}/RESOURCE_LIBRARY.md +13 -15
  52. package/skills/wix-headless/references/{cms → astro/cms}/TEAM_DIRECTORY.md +11 -11
  53. package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +354 -0
  54. package/skills/wix-headless/references/{ecom → astro/ecom}/CART_PAGES.md +8 -9
  55. package/skills/wix-headless/references/{ecom → astro/ecom}/CART_WIRING.md +6 -6
  56. package/skills/wix-headless/references/{ecom → astro/ecom}/COMPONENTS_CSS.md +6 -6
  57. package/skills/wix-headless/references/{forms → astro/forms}/CONTACT_FORM.md +18 -32
  58. package/skills/wix-headless/references/{gift-cards → astro/gift-cards}/COMPONENTS.md +3 -3
  59. package/skills/wix-headless/references/{gift-cards → astro/gift-cards}/PAGES.md +2 -2
  60. package/skills/wix-headless/references/{stores → astro/stores}/BACK_IN_STOCK.md +7 -7
  61. package/skills/wix-headless/references/{stores → astro/stores}/CATEGORY_PAGES.md +12 -13
  62. package/skills/wix-headless/references/{stores → astro/stores}/COMPONENTS_CSS.md +7 -7
  63. package/skills/wix-headless/references/{stores → astro/stores}/HOME_AND_NAV.md +11 -9
  64. package/skills/wix-headless/references/{stores → astro/stores}/PRODUCT_PAGES.md +11 -9
  65. package/skills/wix-headless/references/{stores → astro/stores}/SHARED_WIRING.md +8 -7
  66. package/skills/wix-headless/references/astro/templates/Footer.astro +27 -0
  67. package/skills/wix-headless/references/astro/templates/Layout.astro +132 -0
  68. package/skills/wix-headless/references/astro/templates/Navigation.astro +38 -0
  69. package/skills/wix-headless/references/astro/templates/astro.config.mjs +41 -0
  70. package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +218 -0
  71. package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +194 -0
  72. package/skills/wix-headless/references/astro/templates/bookings/ManageBooking.tsx +84 -0
  73. package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
  74. package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +50 -0
  75. package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
  76. package/skills/wix-headless/references/astro/templates/bookings/api/confirm-booking.ts +34 -0
  77. package/skills/wix-headless/references/astro/templates/bookings/api/waitlist.ts +74 -0
  78. package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +111 -0
  79. package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +413 -0
  80. package/skills/wix-headless/references/astro/templates/bookings/manage-booking.astro +76 -0
  81. package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +81 -0
  82. package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +65 -0
  83. package/skills/wix-headless/references/astro/templates/ecom/components-ecom.css +302 -0
  84. package/skills/wix-headless/references/astro/templates/global.css +286 -0
  85. package/skills/wix-headless/references/astro/templates/index.astro +56 -0
  86. package/skills/wix-headless/{templates → references/astro/templates}/stores/back-in-stock.ts +1 -1
  87. package/skills/wix-headless/{templates → references/astro/templates}/stores/components-stores.css +138 -33
  88. package/skills/wix-headless/references/blog/BLOG_CONTENT.md +28 -19
  89. package/skills/wix-headless/references/blog/INSTRUCTIONS.md +6 -6
  90. package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +106 -0
  91. package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
  92. package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +35 -232
  93. package/skills/wix-headless/references/cms/INSTRUCTIONS.md +39 -8
  94. package/skills/wix-headless/references/commands/known-apps.json +2 -1
  95. package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
  96. package/skills/wix-headless/references/custom/INSTRUCTIONS.md +88 -0
  97. package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
  98. package/skills/wix-headless/references/custom/bookings/WIRING.md +88 -0
  99. package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
  100. package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
  101. package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
  102. package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
  103. package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +12 -12
  104. package/skills/wix-headless/references/forms/INSTRUCTIONS.md +6 -6
  105. package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +13 -13
  106. package/skills/wix-headless/references/images/INSTRUCTIONS.md +58 -60
  107. package/skills/wix-headless/references/seed-recipes.md +49 -0
  108. package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
  109. package/skills/wix-headless/references/shared/AUTHENTICATION.md +108 -0
  110. package/skills/wix-headless/references/shared/BUILD_NOISE.md +2 -2
  111. package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
  112. package/skills/wix-headless/references/shared/DOCS_SEARCH.md +41 -0
  113. package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +40 -39
  114. package/skills/wix-headless/references/shared/IMPLEMENTER.md +92 -41
  115. package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +3 -3
  116. package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +32 -312
  117. package/skills/wix-headless/references/shared/STYLING.md +48 -21
  118. package/skills/wix-headless/references/stores/INSTRUCTIONS.md +59 -29
  119. package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +31 -28
  120. package/skills/wix-headless/references/verticals/_schema.json +31 -117
  121. package/skills/wix-headless/references/verticals/_schema.md +60 -150
  122. package/skills/wix-headless/references/verticals/blog.md +5 -99
  123. package/skills/wix-headless/references/verticals/bookings.md +41 -0
  124. package/skills/wix-headless/references/verticals/cms.md +9 -100
  125. package/skills/wix-headless/references/verticals/ecom.md +6 -77
  126. package/skills/wix-headless/references/verticals/forms.md +5 -85
  127. package/skills/wix-headless/references/verticals/gift-cards.md +6 -103
  128. package/skills/wix-headless/references/verticals/stores.md +10 -144
  129. package/skills/wix-headless/scripts/check-manifest.mjs +148 -11
  130. package/skills/wix-headless/scripts/compose.mjs +540 -0
  131. package/skills/wix-headless/scripts/emit-design-tokens.mjs +136 -53
  132. package/skills/wix-headless/scripts/flatten.sh +61 -0
  133. package/skills/wix-headless/scripts/generate-images.mjs +242 -0
  134. package/skills/wix-headless/scripts/patch-decorative-slots.mjs +67 -43
  135. package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +3 -3
  136. package/skills/wix-headless/scripts/scaffold.sh +113 -27
  137. package/skills/wix-headless/scripts/seed-utilities.sh +50 -12
  138. package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
  139. package/skills/wix-manage/SKILL.md +4 -8
  140. package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +2 -0
  141. package/skills/wix-manage/references/cms/cms-data-items-crud.md +4 -0
  142. package/skills/wix-manage/references/cms/cms-references-and-relationships.md +2 -0
  143. package/skills/wix-manage/references/cms/cms-schema-management.md +4 -0
  144. package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
  145. package/skills/wix-manage/references/ecommerce/api-shipping.md +443 -0
  146. package/skills/wix-manage/references/ecommerce/flow-add-free-shipping.md +5 -6
  147. package/skills/wix-manage/references/ecommerce/flow-fix-coverage-gaps.md +4 -5
  148. package/skills/wix-manage/references/ecommerce/flow-optimize-shipping-rates.md +4 -5
  149. package/skills/wix-manage/references/ecommerce/goal-increase-aov.md +1 -1
  150. package/skills/wix-manage/references/ecommerce/goal-reduce-cart-abandonment.md +1 -2
  151. package/skills/wix-manage/references/ecommerce/guardrail-shipping-health.md +2 -2
  152. package/skills/wix-manage/references/ecommerce/recipe-apply-shipping-recommendations.md +4 -5
  153. package/skills/wix-manage/references/ecommerce/recommend-ecommerce-strategy.md +7 -7
  154. package/skills/wix-manage/references/ecommerce/setup-discount-rules.md +209 -72
  155. package/skills/wix-manage/references/ecommerce/setup-store-pickup-location.md +9 -10
  156. package/skills/wix-manage/references/ecommerce/skill-graph.md +4 -4
  157. package/skills/wix-manage/references/forms/create-form.md +50 -0
  158. package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +2 -0
  159. package/skills/wix-manage/references/stores/create-product-from-image.md +91 -38
  160. package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +2 -0
  161. package/skills/wix-app/references/custom-element-widget/default-thumbnail.png +0 -0
  162. package/skills/wix-app/references/data-collection/extension-template.ts +0 -107
  163. package/skills/wix-headless/references/ORCHESTRATION.md +0 -269
  164. package/skills/wix-headless/references/commands/install-app.md +0 -42
  165. package/skills/wix-headless/references/commands/mcp-bootstrap.md +0 -28
  166. package/skills/wix-headless/references/designer/INSTRUCTIONS.md +0 -828
  167. package/skills/wix-headless/references/shared/MCP_PREFIX.md +0 -105
  168. package/skills/wix-headless/scripts/copy-utility-templates.mjs +0 -153
  169. package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
  170. package/skills/wix-headless/scripts/init-site-json.mjs +0 -63
  171. package/skills/wix-headless/scripts/preview.sh +0 -45
  172. package/skills/wix-headless/scripts/release.sh +0 -73
  173. package/skills/wix-headless/templates/ecom/components-ecom.css +0 -215
  174. package/skills/wix-manage/references/events/list-events.md +0 -203
  175. /package/skills/wix-headless/{templates → references/astro/templates}/ecom/CartBadge.tsx +0 -0
  176. /package/skills/wix-headless/{templates → references/astro/templates}/ecom/CartView.tsx +0 -0
  177. /package/skills/wix-headless/{templates → references/astro/templates}/ecom/cart.astro +0 -0
  178. /package/skills/wix-headless/{templates → references/astro/templates}/ecom/discounts.ts +0 -0
  179. /package/skills/wix-headless/{templates → references/astro/templates}/ecom/thank-you.astro +0 -0
  180. /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/GiftCardPurchase.tsx +0 -0
  181. /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/_home-teaser-snippet.astro +0 -0
  182. /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/_nav-snippet.astro +0 -0
  183. /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/components-gift-cards.css +0 -0
  184. /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/gift-cards.astro +0 -0
  185. /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/gift-cards.ts +0 -0
  186. /package/skills/wix-headless/{templates → references/astro/templates}/stores/AddToCartButton.tsx +0 -0
  187. /package/skills/wix-headless/{templates → references/astro/templates}/stores/BackInStockForm.tsx +0 -0
  188. /package/skills/wix-headless/{templates → references/astro/templates}/stores/CategoryRail.astro +0 -0
  189. /package/skills/wix-headless/{templates → references/astro/templates}/stores/ProductCard.astro +0 -0
  190. /package/skills/wix-headless/{templates → references/astro/templates}/stores/ProductPurchase.tsx +0 -0
  191. /package/skills/wix-headless/{templates → references/astro/templates}/stores/SeoTags.astro +0 -0
  192. /package/skills/wix-headless/{templates → references/astro/templates}/stores/categories.ts +0 -0
  193. /package/skills/wix-headless/{templates → references/astro/templates}/stores/category/[slug].astro +0 -0
  194. /package/skills/wix-headless/{templates → references/astro/templates}/stores/products/[slug].astro +0 -0
  195. /package/skills/wix-headless/{templates → references/astro/templates}/stores/products/index.astro +0 -0
@@ -0,0 +1,45 @@
1
+ ---
2
+ // ServiceCard.astro — static card for the services listing grid.
3
+ // Props in, HTML out; no client-side interactivity. Works for APPOINTMENT and
4
+ // CLASS services. Classes (.service-card, .service-card-image,
5
+ // .service-card-meta, .service-card-price, …) are owned by
6
+ // src/styles/components-bookings.css (pre-copied by the orchestrator).
7
+ interface Props {
8
+ id: string;
9
+ slug: string;
10
+ name: string;
11
+ tagLine?: string;
12
+ durationMinutes: number;
13
+ price?: string; // formatted string, e.g. "$75.00" — already formatted by caller
14
+ currency?: string;
15
+ imageUrl?: string;
16
+ type: "APPOINTMENT" | "CLASS";
17
+ }
18
+
19
+ const { slug, name, tagLine, durationMinutes, price, imageUrl } = Astro.props;
20
+ ---
21
+
22
+ <a href={`/services/${slug}`} class="service-card">
23
+ {
24
+ imageUrl ? (
25
+ <div class="service-card-image">
26
+ <img src={imageUrl} alt={name} loading="lazy" />
27
+ </div>
28
+ ) : (
29
+ <div
30
+ class="service-card-image service-card-image--placeholder"
31
+ aria-hidden="true"
32
+ style="background-color: var(--color-surface-secondary, var(--color-paper-warm, var(--color-cream)));"
33
+ />
34
+ )
35
+ }
36
+
37
+ <div class="service-card-meta">
38
+ <h3 class="service-card-name">{name}</h3>
39
+ {tagLine && <p class="service-card-tagline">{tagLine}</p>}
40
+ <div class="service-card-price">
41
+ <span>{durationMinutes} min</span>
42
+ {price && <span>{price}</span>}
43
+ </div>
44
+ </div>
45
+ </a>
@@ -0,0 +1,34 @@
1
+ import type { APIRoute } from "astro";
2
+ import { auth } from "@wix/essentials";
3
+ import { bookings } from "@wix/bookings";
4
+
5
+ // POST /api/confirm-booking { bookingId, revision }
6
+ //
7
+ // createBooking() leaves the booking CREATED — which does NOT occupy a seat, so
8
+ // the class/appointment stays bookable and OVERBOOKS (remainingCapacity never
9
+ // drops). confirmBooking() promotes it to CONFIRMED (holds the seat); with
10
+ // flowControlSettings.checkAvailabilityValidation it REJECTS the confirm if the
11
+ // session filled up since the slot was picked — the real overbooking guard.
12
+ // confirmBooking requires Manage Bookings, so elevate (app identity) here;
13
+ // confirmBooking is a real SDK method, so auth.elevate(...) works directly.
14
+
15
+ const json = (ok: boolean, status: number, extra: Record<string, unknown> = {}) =>
16
+ new Response(JSON.stringify({ ok, ...extra }), { status, headers: { "Content-Type": "application/json" } });
17
+
18
+ export const POST: APIRoute = async ({ request }) => {
19
+ let body: any;
20
+ try { body = await request.json(); } catch { return json(false, 400, { error: "Invalid request body." }); }
21
+ const { bookingId, revision } = body ?? {};
22
+ if (!bookingId || !revision) return json(false, 400, { error: "Missing bookingId or revision." });
23
+
24
+ try {
25
+ const res = await auth.elevate(bookings.confirmBooking)(bookingId, String(revision), {
26
+ paymentStatus: "NOT_PAID", // pay-on-site reservation
27
+ flowControlSettings: { checkAvailabilityValidation: true },
28
+ });
29
+ return json(true, 200, { status: res.booking?.status });
30
+ } catch (err) {
31
+ console.error("[api/confirm-booking] failed:", err);
32
+ return json(false, 409, { error: "unavailable" }); // most likely: session just filled up
33
+ }
34
+ };
@@ -0,0 +1,74 @@
1
+ import type { APIRoute } from "astro";
2
+ import { auth, httpClient } from "@wix/essentials";
3
+
4
+ // POST /api/waitlist { sessionId, totalParticipants, contactDetails:{firstName,lastName,email,phone} }
5
+ //
6
+ // Native Wix Bookings waitlist. There is NO v2/v3 waitlist API — createBooking
7
+ // can't waitlist; the only join is the legacy v1 Register To Waitlist
8
+ // (POST /bookings/v1/waitlist/register), which needs Manage Bookings + a Wix
9
+ // contactId. So this runs server-side, elevated to app identity. CLASS only.
10
+ //
11
+ // `sessionId` is the v1-encoded session id (the `waitingResource`); the browser
12
+ // gets it from the SESSION_CAPACITY_EXCEEDED error createBooking returns for a
13
+ // full session ("…on session <id>").
14
+ //
15
+ // IMPORTANT: auth.elevate() only grants permissions for a real SDK function. The
16
+ // waitlist + Contacts calls are raw REST (no SDK wrapper), so elevate the
17
+ // authenticated fetch ITSELF — NOT a closure that calls it (a closure runs
18
+ // un-elevated/visitor and 403s).
19
+
20
+ const json = (ok: boolean, status: number, extra: Record<string, unknown> = {}) =>
21
+ new Response(JSON.stringify({ ok, ...extra }), { status, headers: { "Content-Type": "application/json" } });
22
+
23
+ export const POST: APIRoute = async ({ request }) => {
24
+ let body: any;
25
+ try { body = await request.json(); } catch { return json(false, 400, { error: "Invalid request body." }); }
26
+ const { sessionId, totalParticipants = 1, contactDetails } = body ?? {};
27
+ const email: string | undefined = contactDetails?.email;
28
+ if (!sessionId || !email) return json(false, 400, { error: "Missing sessionId or email." });
29
+
30
+ const ef = auth.elevate(httpClient.fetchWithAuth as typeof fetch) as typeof fetch;
31
+ const api = (url: string, payload: unknown) =>
32
+ ef(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) });
33
+
34
+ try {
35
+ // 1. Resolve a contactId (reuse by email; create if absent).
36
+ let contactId: string | undefined;
37
+ const q = await api("https://www.wixapis.com/contacts/v4/contacts/query", {
38
+ query: { filter: { "info.emails.email": email } },
39
+ });
40
+ contactId = ((await q.json().catch(() => ({}))) as any).contacts?.[0]?.id;
41
+ if (!contactId) {
42
+ const c = await api("https://www.wixapis.com/contacts/v4/contacts", {
43
+ info: {
44
+ name: { first: contactDetails.firstName, last: contactDetails.lastName || undefined },
45
+ emails: { items: [{ email, primary: true }] },
46
+ ...(contactDetails.phone ? { phones: { items: [{ phone: contactDetails.phone, primary: true }] } } : {}),
47
+ },
48
+ });
49
+ contactId = ((await c.json().catch(() => ({}))) as any).contact?.id;
50
+ }
51
+ if (!contactId) throw new Error("could not resolve contactId");
52
+
53
+ // 2. Register on the native waitlist.
54
+ const reg = await api("https://www.wixapis.com/bookings/v1/waitlist/register", {
55
+ waitingResource: sessionId,
56
+ formInfo: {
57
+ paymentSelection: [{ rateLabel: "general", numberOfParticipants: totalParticipants }],
58
+ contactDetails: {
59
+ contactId,
60
+ firstName: contactDetails.firstName,
61
+ lastName: contactDetails.lastName || undefined,
62
+ email,
63
+ phone: contactDetails.phone || undefined,
64
+ },
65
+ },
66
+ });
67
+ const rj: any = await reg.json().catch(() => ({}));
68
+ if (!reg.ok || !rj.registrationId) throw new Error(`register ${reg.status}: ${JSON.stringify(rj).slice(0, 200)}`);
69
+ return json(true, 200, { registrationId: rj.registrationId });
70
+ } catch (err) {
71
+ console.error("[api/waitlist] failed:", err);
72
+ return json(false, 502, { error: "Waitlist registration failed." });
73
+ }
74
+ };
@@ -0,0 +1,111 @@
1
+ ---
2
+ // src/pages/booking-confirmation.astro — post-booking confirmation.
3
+ //
4
+ // Shows the actual booking status (so the user can see it was actually
5
+ // confirmed/approved — not just an optimistic "you're booked") and lets them
6
+ // cancel inline. It re-fetches the booking with the anonymous action token: the
7
+ // server elevates (app identity) to mint a per-booking token, then reads it with
8
+ // the no-auth `bookingsGetBookingAnonymously`. Falls back to the redirect params
9
+ // if the lookup fails. Cancel is handled by the same <ManageBooking> island used
10
+ // on /manage-booking.
11
+ import Layout from "../layouts/Layout.astro";
12
+ import ManageBooking from "../components/ManageBooking";
13
+ import { auth } from "@wix/essentials";
14
+ import { bookings } from "@wix/bookings";
15
+
16
+ const bookingId = Astro.url.searchParams.get("bookingId");
17
+ const startDateParam = Astro.url.searchParams.get("startDate");
18
+ const serviceParam = Astro.url.searchParams.get("service") ?? "your service";
19
+
20
+ let token: string | null = null;
21
+ let booking: any = null;
22
+ let canCancel = false;
23
+ if (bookingId) {
24
+ try {
25
+ const tokenRes = await auth.elevate(bookings.getAnonymousActionToken)(bookingId);
26
+ token = tokenRes.token ?? null;
27
+ if (token) {
28
+ const res = await bookings.bookingsGetBookingAnonymously(token);
29
+ booking = res.booking ?? null;
30
+ canCancel = !!res.allowedAnonymousActions?.cancel;
31
+ }
32
+ } catch (err) {
33
+ console.error("[booking-confirmation] status lookup failed:", err);
34
+ }
35
+ }
36
+
37
+ const status = (booking?.status as string | undefined) ?? null; // CONFIRMED | PENDING | …
38
+ const isPending = status === "PENDING" || status === "PENDING_APPROVAL";
39
+ const serviceName = booking?.bookedEntity?.title ?? serviceParam;
40
+ const startDate =
41
+ (booking?.startDate as string | undefined) ??
42
+ booking?.bookedEntity?.slot?.startDate ??
43
+ startDateParam ??
44
+ null;
45
+ const revision = (booking?.revision as string | undefined) ?? "";
46
+ const formattedDate = startDate
47
+ ? new Date(startDate).toLocaleString("en-US", {
48
+ weekday: "long", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit",
49
+ })
50
+ : null;
51
+ ---
52
+
53
+ <!-- `max-w-prose` (built-in literal = 65ch), not a bare max-w-<size> that could
54
+ collapse to the spacing scale. -->
55
+ <Layout title={isPending ? "Booking received" : "Booking confirmed"}>
56
+ <section class="max-w-prose mx-auto px-lg py-4xl text-center">
57
+ <div class="w-16 h-16 rounded-full flex items-center justify-center mx-auto mb-xl"
58
+ style="background-color: var(--color-ink);" aria-hidden="true">
59
+ <span class="text-2xl" style="color: var(--color-paper);">{isPending ? "⏳" : "✓"}</span>
60
+ </div>
61
+ <hr class="editorial-rule w-16 mx-auto mb-lg" />
62
+ <h1 class="font-display text-4xl md:text-5xl leading-tight mb-md">
63
+ {isPending ? "Booking received" : "Thank you for booking"}
64
+ </h1>
65
+
66
+ {
67
+ isPending ? (
68
+ <p class="text-lg text-mute leading-relaxed mb-md">
69
+ Your request for <strong class="text-ink">{serviceName}</strong>
70
+ {formattedDate ? <> on <strong class="text-ink">{formattedDate}</strong></> : null} is
71
+ <strong class="text-ink"> awaiting confirmation</strong>. Return here anytime to check status or cancel below.
72
+ </p>
73
+ ) : formattedDate ? (
74
+ <p class="text-lg text-mute leading-relaxed mb-md">
75
+ Your spot in <strong class="text-ink">{serviceName}</strong> is <strong class="text-ink">confirmed</strong> for{" "}
76
+ <strong class="text-ink">{formattedDate}</strong>.
77
+ </p>
78
+ ) : bookingId ? (
79
+ <p class="text-lg text-mute leading-relaxed mb-md">
80
+ Your spot in <strong class="text-ink">{serviceName}</strong> is <strong class="text-ink">confirmed</strong>.
81
+ </p>
82
+ ) : (
83
+ <p class="text-lg text-mute leading-relaxed mb-md">Looking to book? Browse our services.</p>
84
+ )
85
+ }
86
+
87
+ {/* Inline cancel via ManageBooking (same anonymous-token flow as /manage-booking). */}
88
+ {
89
+ token && bookingId && (
90
+ <div class="mb-xl flex flex-col items-center gap-md">
91
+ <ManageBooking
92
+ token={token}
93
+ revision={revision}
94
+ serviceName={serviceName}
95
+ startDate={startDate}
96
+ status={status}
97
+ canCancel={canCancel}
98
+ showSummary={false}
99
+ client:only="react"
100
+ />
101
+ </div>
102
+ )
103
+ }
104
+
105
+ <div class="flex flex-col gap-md items-center">
106
+ <a href="/services" class="inline-block font-display text-base px-xl py-md rounded-sm"
107
+ style="background-color: var(--color-ink); color: var(--color-paper);">Browse more services</a>
108
+ <a href="/" class="text-sm text-mute hover:text-ink">Return home</a>
109
+ </div>
110
+ </section>
111
+ </Layout>
@@ -0,0 +1,413 @@
1
+ /* src/styles/components-bookings.css
2
+ *
3
+ * Bookings-pack component CSS. Pre-copied by the orchestrator at Step 4.5
4
+ * before Phase 3 Components dispatches. Do NOT write or modify this file
5
+ * from a Phase 3 or Phase 4 subagent — it is already on disk.
6
+ *
7
+ * This file is the SINGLE source of truth for bookings-component classes.
8
+ * The designer's global.css does NOT publish .service-card, .service-grid,
9
+ * .availability-calendar, .time-slot, or .booking-form — those all live
10
+ * here. See references/shared/STYLING.md § "Component-specific CSS is
11
+ * owned by the component, not the designer".
12
+ *
13
+ * Class groups in this file:
14
+ * - .service-card / .service-grid / .service-card-* → ServiceCard.astro,
15
+ * services/index.astro, index.astro home teaser
16
+ * - .availability-calendar / .time-slot / .time-slot-* / .availability-*
17
+ * → AvailabilityCalendar.tsx date picker + slot grid
18
+ * - .booking-form / .booking-field / .booking-* → BookingForm.tsx
19
+ * details form + submit button
20
+ *
21
+ * Rules use direct CSS (`var(--token)`) only — no `@apply`. Tailwind v4
22
+ * `@apply` in a non-entrypoint file only resolves utilities whose theme keys
23
+ * the designer's `@theme` block declares. Direct `var(--color-ink)` /
24
+ * `var(--spacing-sm)` calls work against any designer-published token set as
25
+ * long as the core scales exist (they always do). Use fallback chains
26
+ * (`var(--color-paper-warm, var(--color-paper, var(--color-cream)))`) for
27
+ * sometimes-published tokens.
28
+ */
29
+
30
+ /* =========================================================================
31
+ Service grid + card — owned here, NOT in global.css.
32
+ .service-card uses overflow: hidden + border-radius, so inner padding
33
+ is the responsibility of THIS file (whoever sets the radius owns the
34
+ inset). The image is clipped by the card's overflow; content needs
35
+ padding-inline so text doesn't sit flush against the rounded edge.
36
+ ========================================================================= */
37
+ .service-grid {
38
+ display: grid;
39
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
40
+ gap: var(--spacing-xl);
41
+ transition: opacity 200ms ease;
42
+ }
43
+ body[data-navigating="true"] .service-grid {
44
+ opacity: 0.55;
45
+ pointer-events: none;
46
+ }
47
+
48
+ .service-card {
49
+ position: relative;
50
+ display: flex;
51
+ flex-direction: column;
52
+ background: var(--color-paper, var(--color-cream));
53
+ border-radius: var(--radius-xl);
54
+ overflow: hidden;
55
+ text-decoration: none;
56
+ color: inherit;
57
+ transition: transform 280ms ease, box-shadow 280ms ease;
58
+ box-shadow: var(--shadow-card, 0 4px 16px -8px rgba(0, 0, 0, 0.15));
59
+ }
60
+ .service-card:hover {
61
+ transform: translateY(-4px);
62
+ }
63
+ .service-card:hover .service-card-image img {
64
+ transform: scale(1.04);
65
+ }
66
+
67
+ .service-card-image {
68
+ aspect-ratio: 3 / 2;
69
+ overflow: hidden;
70
+ background: var(--color-paper-warm, var(--color-surface-secondary, var(--color-cream)));
71
+ }
72
+ .service-card-image img {
73
+ width: 100%;
74
+ height: 100%;
75
+ object-fit: cover;
76
+ transition: transform 600ms ease;
77
+ }
78
+ .service-card-image--placeholder {
79
+ /* fallback when no image is provided */
80
+ }
81
+
82
+ .service-card-meta {
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: var(--spacing-xs);
86
+ padding: var(--spacing-md) var(--spacing-md) var(--spacing-lg);
87
+ flex: 1;
88
+ }
89
+
90
+ .service-card-name {
91
+ margin: 0;
92
+ font-family: var(--font-display);
93
+ font-size: 1.125rem;
94
+ line-height: 1.25;
95
+ color: var(--color-ink);
96
+ }
97
+
98
+ .service-card-tagline {
99
+ margin: 0;
100
+ font-family: var(--font-body);
101
+ font-size: 0.875rem;
102
+ color: var(--color-ink-soft, var(--color-mute));
103
+ line-height: 1.45;
104
+ }
105
+
106
+ .service-card-price {
107
+ display: flex;
108
+ align-items: center;
109
+ gap: var(--spacing-sm);
110
+ margin-top: auto;
111
+ padding-top: var(--spacing-sm);
112
+ font-family: var(--font-body);
113
+ font-size: 0.875rem;
114
+ color: var(--color-mute);
115
+ }
116
+ .service-card-price span + span::before {
117
+ content: "·";
118
+ padding-right: var(--spacing-sm);
119
+ color: var(--color-rule);
120
+ }
121
+
122
+ /* =========================================================================
123
+ Availability calendar — date navigator + slot grid.
124
+ Owned here, NOT in global.css.
125
+ ========================================================================= */
126
+ .availability-calendar {
127
+ display: flex;
128
+ flex-direction: column;
129
+ gap: var(--spacing-lg);
130
+ }
131
+
132
+ .availability-date-nav {
133
+ display: flex;
134
+ align-items: center;
135
+ justify-content: space-between;
136
+ gap: var(--spacing-md);
137
+ padding: var(--spacing-sm) 0;
138
+ border-bottom: 1px solid var(--color-rule);
139
+ }
140
+
141
+ .availability-date-label {
142
+ font-family: var(--font-display);
143
+ font-size: 1rem;
144
+ color: var(--color-ink);
145
+ flex: 1;
146
+ text-align: center;
147
+ }
148
+
149
+ .availability-nav-btn {
150
+ padding: var(--spacing-sm) var(--spacing-md);
151
+ background: transparent;
152
+ border: 1px solid var(--color-rule);
153
+ border-radius: var(--radius-sm);
154
+ color: var(--color-ink);
155
+ font-family: var(--font-body);
156
+ font-size: 0.875rem;
157
+ cursor: pointer;
158
+ transition: background-color 150ms ease, border-color 150ms ease;
159
+ }
160
+ .availability-nav-btn:hover:not(:disabled) {
161
+ background: var(--color-paper-warm, var(--color-cream));
162
+ border-color: var(--color-ink);
163
+ }
164
+ .availability-nav-btn:disabled {
165
+ opacity: 0.35;
166
+ cursor: not-allowed;
167
+ }
168
+
169
+ .availability-slots {
170
+ display: grid;
171
+ grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
172
+ gap: var(--spacing-sm);
173
+ }
174
+
175
+ .availability-loading,
176
+ .availability-empty,
177
+ .availability-error {
178
+ font-family: var(--font-body);
179
+ font-size: 0.9375rem;
180
+ color: var(--color-ink-soft, var(--color-mute));
181
+ padding: var(--spacing-lg) 0;
182
+ text-align: center;
183
+ }
184
+ .availability-error {
185
+ color: var(--color-error, var(--color-accent));
186
+ }
187
+
188
+ /* =========================================================================
189
+ Time slot buttons — individual slot items in the grid.
190
+ Three states: available (default), selected, unavailable.
191
+ ========================================================================= */
192
+ .time-slot {
193
+ padding: var(--spacing-sm) var(--spacing-md);
194
+ border: 1px solid var(--color-rule);
195
+ border-radius: var(--radius-sm);
196
+ background: var(--color-paper, var(--color-cream));
197
+ color: var(--color-ink);
198
+ font-family: var(--font-body);
199
+ font-size: 0.875rem;
200
+ text-align: center;
201
+ cursor: pointer;
202
+ transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
203
+ }
204
+ .time-slot--available {
205
+ border-color: var(--color-ink);
206
+ }
207
+ .time-slot--available:hover {
208
+ background: var(--color-ink);
209
+ color: var(--color-paper);
210
+ }
211
+ .time-slot--selected {
212
+ background: var(--color-ink);
213
+ color: var(--color-paper);
214
+ border-color: var(--color-ink);
215
+ font-weight: 600;
216
+ }
217
+ .time-slot--unavailable {
218
+ opacity: 0.3;
219
+ cursor: not-allowed;
220
+ border-color: var(--color-rule);
221
+ }
222
+
223
+ /* =========================================================================
224
+ Booking form — rendered after a slot is selected.
225
+ Owned here, NOT in global.css.
226
+ ========================================================================= */
227
+ .booking-form {
228
+ display: flex;
229
+ flex-direction: column;
230
+ gap: var(--spacing-lg);
231
+ padding: var(--spacing-xl);
232
+ border: 1px solid var(--color-rule);
233
+ border-radius: var(--radius-md);
234
+ background: var(--color-paper-warm, var(--color-paper, var(--color-cream)));
235
+ }
236
+
237
+ .booking-form-header {
238
+ display: flex;
239
+ flex-direction: column;
240
+ gap: var(--spacing-xs);
241
+ }
242
+ .booking-form-title {
243
+ font-family: var(--font-display);
244
+ font-size: 1.25rem;
245
+ color: var(--color-ink);
246
+ margin: 0;
247
+ }
248
+ .booking-form-subtitle {
249
+ font-family: var(--font-body);
250
+ font-size: 0.875rem;
251
+ color: var(--color-ink-soft, var(--color-mute));
252
+ margin: 0;
253
+ }
254
+
255
+ .booking-field {
256
+ display: flex;
257
+ flex-direction: column;
258
+ gap: var(--spacing-xs);
259
+ }
260
+ .booking-field-row {
261
+ display: grid;
262
+ grid-template-columns: 1fr 1fr;
263
+ gap: var(--spacing-md);
264
+ }
265
+ @media (max-width: 480px) {
266
+ .booking-field-row {
267
+ grid-template-columns: 1fr;
268
+ }
269
+ }
270
+
271
+ .booking-label {
272
+ font-family: var(--font-display);
273
+ font-size: 0.75rem;
274
+ text-transform: uppercase;
275
+ letter-spacing: 0.05em;
276
+ color: var(--color-ink-soft, var(--color-mute));
277
+ }
278
+ .booking-label--required::after {
279
+ content: " *";
280
+ color: var(--color-accent);
281
+ }
282
+
283
+ .booking-input,
284
+ .booking-textarea {
285
+ width: 100%;
286
+ padding: var(--spacing-sm) var(--spacing-md);
287
+ font-family: var(--font-body);
288
+ font-size: 0.9375rem;
289
+ border: 1px solid var(--color-rule);
290
+ border-radius: var(--radius-sm);
291
+ background: var(--color-paper, var(--color-cream));
292
+ color: var(--color-ink);
293
+ transition: border-color 150ms ease;
294
+ box-sizing: border-box;
295
+ }
296
+ .booking-input:focus,
297
+ .booking-textarea:focus {
298
+ outline: none;
299
+ border-color: var(--color-ink);
300
+ }
301
+ .booking-textarea {
302
+ resize: vertical;
303
+ min-height: 6rem;
304
+ }
305
+
306
+ .booking-submit {
307
+ display: inline-flex;
308
+ align-items: center;
309
+ justify-content: center;
310
+ padding: var(--spacing-md) var(--spacing-xl);
311
+ font-family: var(--font-display);
312
+ font-size: 0.9375rem;
313
+ font-weight: 500;
314
+ background: var(--color-ink);
315
+ color: var(--color-paper);
316
+ border: 1px solid var(--color-ink);
317
+ border-radius: var(--radius-sm);
318
+ cursor: pointer;
319
+ width: 100%;
320
+ margin-top: var(--spacing-sm);
321
+ transition: background-color 150ms ease, box-shadow 150ms ease;
322
+ }
323
+ .booking-submit:hover:not(:disabled) {
324
+ background: var(--color-ink-soft, var(--color-ink));
325
+ }
326
+ .booking-submit:disabled {
327
+ opacity: 0.5;
328
+ cursor: not-allowed;
329
+ }
330
+
331
+ .booking-cancel {
332
+ display: block;
333
+ text-align: center;
334
+ font-family: var(--font-body);
335
+ font-size: 0.875rem;
336
+ color: var(--color-mute);
337
+ cursor: pointer;
338
+ background: none;
339
+ border: none;
340
+ text-decoration: underline;
341
+ padding: 0;
342
+ margin-top: var(--spacing-xs);
343
+ }
344
+ .booking-cancel:hover {
345
+ color: var(--color-ink);
346
+ }
347
+
348
+ .booking-error {
349
+ font-family: var(--font-body);
350
+ font-size: 0.875rem;
351
+ color: var(--color-error, var(--color-accent));
352
+ padding: var(--spacing-sm) var(--spacing-md);
353
+ border: 1px solid var(--color-error, var(--color-accent));
354
+ border-radius: var(--radius-sm);
355
+ background: rgba(255, 0, 0, 0.04);
356
+ }
357
+
358
+ /* ── Slot capacity (CLASS) + full→waitlist ─────────────────────────────────── */
359
+ .time-slot {
360
+ display: flex;
361
+ flex-direction: column;
362
+ align-items: center;
363
+ gap: 2px;
364
+ line-height: 1.2;
365
+ }
366
+ .time-slot-time {
367
+ font-weight: 500;
368
+ }
369
+ .time-slot-capacity {
370
+ font-size: 0.7rem;
371
+ letter-spacing: 0.04em;
372
+ text-transform: uppercase;
373
+ color: var(--color-mute, var(--color-secondary));
374
+ }
375
+ .time-slot--full {
376
+ opacity: 0.85;
377
+ border-style: dashed;
378
+ }
379
+ .time-slot--full .time-slot-capacity {
380
+ color: var(--color-accent);
381
+ }
382
+ .booking-form-note {
383
+ font-family: var(--font-body);
384
+ font-size: 0.875rem;
385
+ color: var(--color-mute, var(--color-secondary));
386
+ margin-top: var(--spacing-xs);
387
+ }
388
+
389
+ /* ── Manage booking page ─────────────────────────────────────────────────────── */
390
+ .manage-booking {
391
+ display: flex;
392
+ flex-direction: column;
393
+ gap: var(--spacing-lg);
394
+ align-items: flex-start;
395
+ }
396
+ .manage-booking-summary,
397
+ .manage-booking-status {
398
+ font-family: var(--font-body);
399
+ font-size: 1.05rem;
400
+ color: var(--color-ink, var(--color-primary));
401
+ }
402
+ .manage-booking-note {
403
+ font-family: var(--font-body);
404
+ font-size: 0.875rem;
405
+ color: var(--color-mute, var(--color-secondary));
406
+ }
407
+ .manage-booking-link {
408
+ color: var(--color-ink, var(--color-primary));
409
+ text-decoration: underline;
410
+ }
411
+ .manage-booking-link:hover {
412
+ color: var(--color-accent);
413
+ }