@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.
- package/README.md +3 -7
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +2 -0
- package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
- package/skills/wix-design-system/SKILL.md +4 -0
- package/skills/wix-headless/SKILL.md +55 -32
- package/skills/wix-headless/references/BUILD-astro.md +246 -0
- package/skills/wix-headless/references/BUILD-own-build.md +167 -0
- package/skills/wix-headless/references/BUILD.md +50 -252
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +94 -34
- package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
- package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
- package/skills/wix-headless/references/DISCOVERY.md +41 -422
- package/skills/wix-headless/references/PLAN-connect.md +19 -0
- package/skills/wix-headless/references/PLAN-create.md +17 -0
- package/skills/wix-headless/references/PLAN.md +37 -27
- package/skills/wix-headless/references/SEED.md +27 -18
- package/skills/wix-headless/references/SETUP.md +32 -213
- package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +5 -4
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +337 -0
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +428 -0
- package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +5 -5
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +10 -10
- package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +12 -13
- package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +10 -12
- package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +8 -8
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +50 -27
- package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +7 -8
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +5 -5
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +5 -19
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
- package/skills/wix-headless/references/astro/gift-cards/PAGES.md +1 -1
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +6 -6
- package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +4 -4
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +3 -3
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +9 -9
- package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +5 -4
- package/skills/wix-headless/references/astro/templates/Footer.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Layout.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Navigation.astro +1 -1
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +1 -1
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +218 -0
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +194 -0
- package/skills/wix-headless/references/astro/templates/bookings/ManageBooking.tsx +84 -0
- package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +50 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
- package/skills/wix-headless/references/astro/templates/bookings/api/confirm-booking.ts +34 -0
- package/skills/wix-headless/references/astro/templates/bookings/api/waitlist.ts +74 -0
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +111 -0
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +413 -0
- package/skills/wix-headless/references/astro/templates/bookings/manage-booking.astro +76 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +81 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +65 -0
- package/skills/wix-headless/references/astro/templates/global.css +2 -2
- package/skills/wix-headless/references/astro/templates/index.astro +1 -1
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +19 -9
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +106 -0
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +4 -2
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +27 -1
- package/skills/wix-headless/references/commands/known-apps.json +2 -1
- package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
- package/skills/wix-headless/references/custom/INSTRUCTIONS.md +71 -21
- package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
- package/skills/wix-headless/references/custom/bookings/WIRING.md +88 -0
- package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
- package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
- package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
- package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +5 -5
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +2 -2
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +16 -15
- package/skills/wix-headless/references/seed-recipes.md +2 -2
- package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +3 -5
- package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +29 -28
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +87 -27
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +2 -2
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +24 -313
- package/skills/wix-headless/references/shared/STYLING.md +8 -18
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +39 -9
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +17 -14
- package/skills/wix-headless/references/verticals/_schema.json +1 -1
- package/skills/wix-headless/references/verticals/_schema.md +3 -3
- package/skills/wix-headless/references/verticals/blog.md +1 -1
- package/skills/wix-headless/references/verticals/bookings.md +41 -0
- package/skills/wix-headless/references/verticals/cms.md +1 -1
- package/skills/wix-headless/references/verticals/ecom.md +2 -2
- package/skills/wix-headless/references/verticals/gift-cards.md +1 -1
- package/skills/wix-headless/references/verticals/stores.md +1 -1
- package/skills/wix-headless/scripts/check-manifest.mjs +143 -6
- package/skills/wix-headless/scripts/compose.mjs +540 -0
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +128 -51
- package/skills/wix-headless/scripts/flatten.sh +61 -0
- package/skills/wix-headless/scripts/generate-images.mjs +242 -0
- package/skills/wix-headless/scripts/scaffold.sh +47 -16
- package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
- package/skills/wix-manage/SKILL.md +1 -1
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +1 -1
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +1 -1
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +1 -1
- package/skills/wix-manage/references/cms/cms-schema-management.md +1 -1
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
- package/skills/wix-manage/references/forms/create-form.md +49 -1
- package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1 -1
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +1 -1
- package/skills/wix-headless/references/astro/COMPOSE.md +0 -142
- package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
- package/skills/wix-headless/scripts/init-site-json.mjs +0 -101
- package/skills/wix-headless/scripts/preview.sh +0 -45
- 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>
|