@wix/agent-skills 1.7.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 (117) hide show
  1. package/README.md +3 -7
  2. package/package.json +1 -1
  3. package/skills/wix-app/SKILL.md +2 -0
  4. package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
  5. package/skills/wix-design-system/SKILL.md +4 -0
  6. package/skills/wix-headless/SKILL.md +55 -32
  7. package/skills/wix-headless/references/BUILD-astro.md +246 -0
  8. package/skills/wix-headless/references/BUILD-own-build.md +167 -0
  9. package/skills/wix-headless/references/BUILD.md +50 -252
  10. package/skills/wix-headless/references/DESIGN_SYSTEM.md +94 -34
  11. package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
  12. package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
  13. package/skills/wix-headless/references/DISCOVERY.md +41 -422
  14. package/skills/wix-headless/references/PLAN-connect.md +19 -0
  15. package/skills/wix-headless/references/PLAN-create.md +17 -0
  16. package/skills/wix-headless/references/PLAN.md +37 -27
  17. package/skills/wix-headless/references/SEED.md +27 -18
  18. package/skills/wix-headless/references/SETUP.md +32 -213
  19. package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +5 -4
  20. package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +337 -0
  21. package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +428 -0
  22. package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +5 -5
  23. package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +10 -10
  24. package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +12 -13
  25. package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +10 -12
  26. package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +8 -8
  27. package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +50 -27
  28. package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +7 -8
  29. package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +5 -5
  30. package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
  31. package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +5 -19
  32. package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
  33. package/skills/wix-headless/references/astro/gift-cards/PAGES.md +1 -1
  34. package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +6 -6
  35. package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +4 -4
  36. package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +3 -3
  37. package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +3 -3
  38. package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +9 -9
  39. package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +5 -4
  40. package/skills/wix-headless/references/astro/templates/Footer.astro +1 -1
  41. package/skills/wix-headless/references/astro/templates/Layout.astro +1 -1
  42. package/skills/wix-headless/references/astro/templates/Navigation.astro +1 -1
  43. package/skills/wix-headless/references/astro/templates/astro.config.mjs +1 -1
  44. package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +218 -0
  45. package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +194 -0
  46. package/skills/wix-headless/references/astro/templates/bookings/ManageBooking.tsx +84 -0
  47. package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
  48. package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +50 -0
  49. package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
  50. package/skills/wix-headless/references/astro/templates/bookings/api/confirm-booking.ts +34 -0
  51. package/skills/wix-headless/references/astro/templates/bookings/api/waitlist.ts +74 -0
  52. package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +111 -0
  53. package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +413 -0
  54. package/skills/wix-headless/references/astro/templates/bookings/manage-booking.astro +76 -0
  55. package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +81 -0
  56. package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +65 -0
  57. package/skills/wix-headless/references/astro/templates/global.css +2 -2
  58. package/skills/wix-headless/references/astro/templates/index.astro +1 -1
  59. package/skills/wix-headless/references/blog/BLOG_CONTENT.md +19 -9
  60. package/skills/wix-headless/references/blog/INSTRUCTIONS.md +3 -3
  61. package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +106 -0
  62. package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
  63. package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +4 -2
  64. package/skills/wix-headless/references/cms/INSTRUCTIONS.md +27 -1
  65. package/skills/wix-headless/references/commands/known-apps.json +2 -1
  66. package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
  67. package/skills/wix-headless/references/custom/INSTRUCTIONS.md +71 -21
  68. package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
  69. package/skills/wix-headless/references/custom/bookings/WIRING.md +88 -0
  70. package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
  71. package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
  72. package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
  73. package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
  74. package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +5 -5
  75. package/skills/wix-headless/references/forms/INSTRUCTIONS.md +2 -2
  76. package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +3 -3
  77. package/skills/wix-headless/references/images/INSTRUCTIONS.md +16 -15
  78. package/skills/wix-headless/references/seed-recipes.md +2 -2
  79. package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
  80. package/skills/wix-headless/references/shared/AUTHENTICATION.md +3 -5
  81. package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
  82. package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +29 -28
  83. package/skills/wix-headless/references/shared/IMPLEMENTER.md +87 -27
  84. package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +2 -2
  85. package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +24 -313
  86. package/skills/wix-headless/references/shared/STYLING.md +8 -18
  87. package/skills/wix-headless/references/stores/INSTRUCTIONS.md +39 -9
  88. package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +17 -14
  89. package/skills/wix-headless/references/verticals/_schema.json +1 -1
  90. package/skills/wix-headless/references/verticals/_schema.md +3 -3
  91. package/skills/wix-headless/references/verticals/blog.md +1 -1
  92. package/skills/wix-headless/references/verticals/bookings.md +41 -0
  93. package/skills/wix-headless/references/verticals/cms.md +1 -1
  94. package/skills/wix-headless/references/verticals/ecom.md +2 -2
  95. package/skills/wix-headless/references/verticals/gift-cards.md +1 -1
  96. package/skills/wix-headless/references/verticals/stores.md +1 -1
  97. package/skills/wix-headless/scripts/check-manifest.mjs +143 -6
  98. package/skills/wix-headless/scripts/compose.mjs +540 -0
  99. package/skills/wix-headless/scripts/emit-design-tokens.mjs +128 -51
  100. package/skills/wix-headless/scripts/flatten.sh +61 -0
  101. package/skills/wix-headless/scripts/generate-images.mjs +242 -0
  102. package/skills/wix-headless/scripts/scaffold.sh +47 -16
  103. package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
  104. package/skills/wix-manage/SKILL.md +1 -1
  105. package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +1 -1
  106. package/skills/wix-manage/references/cms/cms-data-items-crud.md +1 -1
  107. package/skills/wix-manage/references/cms/cms-references-and-relationships.md +1 -1
  108. package/skills/wix-manage/references/cms/cms-schema-management.md +1 -1
  109. package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
  110. package/skills/wix-manage/references/forms/create-form.md +49 -1
  111. package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1 -1
  112. package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +1 -1
  113. package/skills/wix-headless/references/astro/COMPOSE.md +0 -142
  114. package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
  115. package/skills/wix-headless/scripts/init-site-json.mjs +0 -101
  116. package/skills/wix-headless/scripts/preview.sh +0 -45
  117. package/skills/wix-headless/scripts/release.sh +0 -73
@@ -0,0 +1,25 @@
1
+ ---
2
+ // SeoTags.astro — renders Wix-resolved SEO tags into <head> for service pages.
3
+ // Pass `service.seoData.tags` (Services V2) — title/meta/link/script — so the
4
+ // service detail page ships Wix's sitemap/SEO/social tags. Mirrors the stores
5
+ // SeoTags template. Mount inside the Layout's `head` slot on `/services/[slug]`.
6
+ interface SeoTag {
7
+ type?: string;
8
+ props?: Record<string, any> | null;
9
+ children?: string;
10
+ disabled?: boolean;
11
+ }
12
+ interface Props { tags?: SeoTag[]; }
13
+ const { tags = [] } = Astro.props;
14
+ const visibleTags = tags.filter((t) => !t.disabled);
15
+ ---
16
+ {
17
+ visibleTags.map((tag) => {
18
+ const props = tag.props ?? {};
19
+ if (tag.type === "title") return <title set:html={tag.children ?? ""} />;
20
+ if (tag.type === "meta") return <meta {...props} />;
21
+ if (tag.type === "link") return <link {...props} />;
22
+ if (tag.type === "script") return <script {...props} is:inline set:html={tag.children ?? ""} />;
23
+ return null;
24
+ })
25
+ }
@@ -0,0 +1,50 @@
1
+ import { useState } from "react";
2
+ import AvailabilityCalendar from "./AvailabilityCalendar";
3
+ import type { SelectedSlot } from "./AvailabilityCalendar";
4
+ import BookingForm from "./BookingForm";
5
+
6
+ // ServiceBookingFlow.tsx — client:only="react" coordinator island. Holds the
7
+ // selected-slot state shared between AvailabilityCalendar and BookingForm,
8
+ // transitions between them, and redirects to the confirmation page on success.
9
+ // serviceType MUST be threaded through so the calendar picks the right time-slots
10
+ // API and the form builds the right createBooking shape.
11
+
12
+ interface Props {
13
+ serviceId: string;
14
+ serviceName: string;
15
+ serviceType: "APPOINTMENT" | "CLASS";
16
+ }
17
+
18
+ export default function ServiceBookingFlow({ serviceId, serviceName, serviceType }: Props) {
19
+ const [selectedSlot, setSelectedSlot] = useState<SelectedSlot | null>(null);
20
+
21
+ const handleSuccess = (bookingId: string, startDate?: string) => {
22
+ const params = new URLSearchParams();
23
+ if (bookingId) params.set("bookingId", bookingId);
24
+ if (startDate) params.set("startDate", startDate);
25
+ if (serviceName) params.set("service", serviceName);
26
+ window.location.href = `/booking-confirmation?${params.toString()}`;
27
+ };
28
+
29
+ if (!selectedSlot) {
30
+ return (
31
+ <AvailabilityCalendar
32
+ serviceId={serviceId}
33
+ serviceName={serviceName}
34
+ serviceType={serviceType}
35
+ onSlotSelected={setSelectedSlot}
36
+ />
37
+ );
38
+ }
39
+
40
+ return (
41
+ <BookingForm
42
+ serviceId={serviceId}
43
+ serviceName={serviceName}
44
+ serviceType={serviceType}
45
+ slot={selectedSlot}
46
+ onSuccess={handleSuccess}
47
+ onCancel={() => setSelectedSlot(null)}
48
+ />
49
+ );
50
+ }
@@ -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>