@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.
- package/README.md +5 -9
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +51 -42
- package/skills/wix-app/references/BACKEND_EVENT.md +10 -65
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +101 -406
- package/skills/wix-app/references/DASHBOARD_MENU_PLUGIN.md +14 -111
- package/skills/wix-app/references/DASHBOARD_MODAL.md +56 -205
- package/skills/wix-app/references/DASHBOARD_PAGE.md +19 -98
- package/skills/wix-app/references/DASHBOARD_PLUGIN.md +6 -108
- package/skills/wix-app/references/DATA_COLLECTION.md +113 -198
- package/skills/wix-app/references/EMBEDDED_SCRIPT.md +41 -268
- package/skills/wix-app/references/EXTENSION_REGISTRATION.md +27 -76
- package/skills/wix-app/references/SERVICE_PLUGIN.md +35 -188
- package/skills/wix-app/references/SITE_PLUGIN.md +8 -92
- package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +0 -44
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +19 -330
- package/skills/wix-app/references/dashboard-menu-plugin/bookings-slots.md +0 -12
- package/skills/wix-app/references/dashboard-menu-plugin/ecommerce-slots.md +7 -0
- package/skills/wix-app/references/dashboard-menu-plugin/restaurants-slots.md +0 -12
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +3 -15
- package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +2 -18
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +2 -42
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +1 -11
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +1 -11
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +2 -40
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +2 -19
- package/skills/wix-app/references/service-plugin/VALIDATIONS.md +2 -18
- package/skills/wix-app/references/site-plugin/SLOTS.md +17 -68
- package/skills/wix-design-system/SKILL.md +4 -0
- package/skills/wix-headless/SKILL.md +131 -386
- 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 +105 -0
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +175 -0
- 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 +50 -306
- 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 +92 -0
- package/skills/wix-headless/references/SEED.md +214 -0
- package/skills/wix-headless/references/SETUP.md +73 -248
- package/skills/wix-headless/references/{blog → astro/blog}/BLOG_PAGES.md +6 -5
- package/skills/wix-headless/references/{blog → astro/blog}/BLOG_SETUP.md +2 -2
- 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 +214 -0
- package/skills/wix-headless/references/{cms → astro/cms}/FAQ_KNOWLEDGE_BASE.md +14 -12
- package/skills/wix-headless/references/{cms → astro/cms}/PORTFOLIO.md +15 -16
- package/skills/wix-headless/references/{cms → astro/cms}/RESOURCE_LIBRARY.md +13 -15
- package/skills/wix-headless/references/{cms → astro/cms}/TEAM_DIRECTORY.md +11 -11
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +354 -0
- package/skills/wix-headless/references/{ecom → astro/ecom}/CART_PAGES.md +8 -9
- package/skills/wix-headless/references/{ecom → astro/ecom}/CART_WIRING.md +6 -6
- package/skills/wix-headless/references/{ecom → astro/ecom}/COMPONENTS_CSS.md +6 -6
- package/skills/wix-headless/references/{forms → astro/forms}/CONTACT_FORM.md +18 -32
- package/skills/wix-headless/references/{gift-cards → astro/gift-cards}/COMPONENTS.md +3 -3
- package/skills/wix-headless/references/{gift-cards → astro/gift-cards}/PAGES.md +2 -2
- package/skills/wix-headless/references/{stores → astro/stores}/BACK_IN_STOCK.md +7 -7
- package/skills/wix-headless/references/{stores → astro/stores}/CATEGORY_PAGES.md +12 -13
- package/skills/wix-headless/references/{stores → astro/stores}/COMPONENTS_CSS.md +7 -7
- package/skills/wix-headless/references/{stores → astro/stores}/HOME_AND_NAV.md +11 -9
- package/skills/wix-headless/references/{stores → astro/stores}/PRODUCT_PAGES.md +11 -9
- package/skills/wix-headless/references/{stores → astro/stores}/SHARED_WIRING.md +8 -7
- package/skills/wix-headless/references/astro/templates/Footer.astro +27 -0
- package/skills/wix-headless/references/astro/templates/Layout.astro +132 -0
- package/skills/wix-headless/references/astro/templates/Navigation.astro +38 -0
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +41 -0
- 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/ecom/components-ecom.css +302 -0
- package/skills/wix-headless/references/astro/templates/global.css +286 -0
- package/skills/wix-headless/references/astro/templates/index.astro +56 -0
- package/skills/wix-headless/{templates → references/astro/templates}/stores/back-in-stock.ts +1 -1
- package/skills/wix-headless/{templates → references/astro/templates}/stores/components-stores.css +138 -33
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +28 -19
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +6 -6
- 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 +35 -232
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +39 -8
- 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 +88 -0
- 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 +12 -12
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +6 -6
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +13 -13
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +58 -60
- package/skills/wix-headless/references/seed-recipes.md +49 -0
- package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +108 -0
- package/skills/wix-headless/references/shared/BUILD_NOISE.md +2 -2
- package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
- package/skills/wix-headless/references/shared/DOCS_SEARCH.md +41 -0
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +40 -39
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +92 -41
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +3 -3
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +32 -312
- package/skills/wix-headless/references/shared/STYLING.md +48 -21
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +59 -29
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +31 -28
- package/skills/wix-headless/references/verticals/_schema.json +31 -117
- package/skills/wix-headless/references/verticals/_schema.md +60 -150
- package/skills/wix-headless/references/verticals/blog.md +5 -99
- package/skills/wix-headless/references/verticals/bookings.md +41 -0
- package/skills/wix-headless/references/verticals/cms.md +9 -100
- package/skills/wix-headless/references/verticals/ecom.md +6 -77
- package/skills/wix-headless/references/verticals/forms.md +5 -85
- package/skills/wix-headless/references/verticals/gift-cards.md +6 -103
- package/skills/wix-headless/references/verticals/stores.md +10 -144
- package/skills/wix-headless/scripts/check-manifest.mjs +148 -11
- package/skills/wix-headless/scripts/compose.mjs +540 -0
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +136 -53
- package/skills/wix-headless/scripts/flatten.sh +61 -0
- package/skills/wix-headless/scripts/generate-images.mjs +242 -0
- package/skills/wix-headless/scripts/patch-decorative-slots.mjs +67 -43
- package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +3 -3
- package/skills/wix-headless/scripts/scaffold.sh +113 -27
- package/skills/wix-headless/scripts/seed-utilities.sh +50 -12
- package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
- package/skills/wix-manage/SKILL.md +4 -8
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +2 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +4 -0
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +2 -0
- package/skills/wix-manage/references/cms/cms-schema-management.md +4 -0
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
- package/skills/wix-manage/references/ecommerce/api-shipping.md +443 -0
- package/skills/wix-manage/references/ecommerce/flow-add-free-shipping.md +5 -6
- package/skills/wix-manage/references/ecommerce/flow-fix-coverage-gaps.md +4 -5
- package/skills/wix-manage/references/ecommerce/flow-optimize-shipping-rates.md +4 -5
- package/skills/wix-manage/references/ecommerce/goal-increase-aov.md +1 -1
- package/skills/wix-manage/references/ecommerce/goal-reduce-cart-abandonment.md +1 -2
- package/skills/wix-manage/references/ecommerce/guardrail-shipping-health.md +2 -2
- package/skills/wix-manage/references/ecommerce/recipe-apply-shipping-recommendations.md +4 -5
- package/skills/wix-manage/references/ecommerce/recommend-ecommerce-strategy.md +7 -7
- package/skills/wix-manage/references/ecommerce/setup-discount-rules.md +209 -72
- package/skills/wix-manage/references/ecommerce/setup-store-pickup-location.md +9 -10
- package/skills/wix-manage/references/ecommerce/skill-graph.md +4 -4
- package/skills/wix-manage/references/forms/create-form.md +50 -0
- package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +2 -0
- package/skills/wix-manage/references/stores/create-product-from-image.md +91 -38
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +2 -0
- package/skills/wix-app/references/custom-element-widget/default-thumbnail.png +0 -0
- package/skills/wix-app/references/data-collection/extension-template.ts +0 -107
- package/skills/wix-headless/references/ORCHESTRATION.md +0 -269
- package/skills/wix-headless/references/commands/install-app.md +0 -42
- package/skills/wix-headless/references/commands/mcp-bootstrap.md +0 -28
- package/skills/wix-headless/references/designer/INSTRUCTIONS.md +0 -828
- package/skills/wix-headless/references/shared/MCP_PREFIX.md +0 -105
- package/skills/wix-headless/scripts/copy-utility-templates.mjs +0 -153
- package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
- package/skills/wix-headless/scripts/init-site-json.mjs +0 -63
- package/skills/wix-headless/scripts/preview.sh +0 -45
- package/skills/wix-headless/scripts/release.sh +0 -73
- package/skills/wix-headless/templates/ecom/components-ecom.css +0 -215
- package/skills/wix-manage/references/events/list-events.md +0 -203
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/CartBadge.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/CartView.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/cart.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/discounts.ts +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/ecom/thank-you.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/GiftCardPurchase.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/_home-teaser-snippet.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/_nav-snippet.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/components-gift-cards.css +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/gift-cards.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/gift-cards/gift-cards.ts +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/AddToCartButton.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/BackInStockForm.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/CategoryRail.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/ProductCard.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/ProductPurchase.tsx +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/SeoTags.astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/categories.ts +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/category/[slug].astro +0 -0
- /package/skills/wix-headless/{templates → references/astro/templates}/stores/products/[slug].astro +0 -0
- /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
|
+
}
|