@wix/agent-skills 1.9.0 → 1.11.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/package.json +4 -1
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +6 -16
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +1 -1
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +19 -0
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +2 -2
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +2 -2
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +7 -7
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +4 -4
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +2 -2
- package/skills/wix-headless/references/astro/templates/Footer.astro +4 -4
- package/skills/wix-headless/references/astro/templates/Navigation.astro +4 -4
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +147 -6
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +21 -4
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +11 -2
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +8 -8
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +94 -26
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +53 -10
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +128 -20
- package/skills/wix-headless/references/astro/templates/ecom/CartView.tsx +3 -3
- package/skills/wix-headless/references/astro/templates/ecom/cart.astro +3 -3
- package/skills/wix-headless/references/astro/templates/ecom/components-ecom.css +17 -17
- package/skills/wix-headless/references/astro/templates/ecom/thank-you.astro +3 -3
- package/skills/wix-headless/references/astro/templates/gift-cards/_home-teaser-snippet.astro +4 -4
- package/skills/wix-headless/references/astro/templates/gift-cards/components-gift-cards.css +19 -18
- package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.astro +3 -3
- package/skills/wix-headless/references/astro/templates/global.css +17 -15
- package/skills/wix-headless/references/astro/templates/index.astro +4 -4
- package/skills/wix-headless/references/astro/templates/stores/components-stores.css +31 -31
- package/skills/wix-headless/references/astro/templates/stores/products/[slug].astro +4 -4
- package/skills/wix-headless/references/bookings/FLOW.md +93 -32
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +8 -3
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +60 -0
- package/skills/wix-headless/references/custom/bookings/WIRING.md +60 -9
- package/skills/wix-headless/references/shared/DESIGN_MD.md +2 -3
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +4 -4
- package/skills/wix-headless/references/shared/STYLING.md +15 -18
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +2 -2
- package/skills/wix-headless/references/verticals/bookings.md +5 -3
- package/skills/wix-headless/scripts/compose.mjs +4 -19
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +0 -3
- package/skills/wix-manage/references/bookings/create-appointment-service.md +3 -28
- package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +1 -8
- package/skills/wix-manage/references/bookings/create-class-service.md +3 -28
- package/skills/wix-manage/references/bookings/create-course-service.md +3 -28
|
@@ -11,6 +11,12 @@ import { auth } from "@wix/essentials";
|
|
|
11
11
|
const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
12
12
|
const { slug } = Astro.params;
|
|
13
13
|
|
|
14
|
+
// Carried from the catalog's location selector (when the site has >1 location):
|
|
15
|
+
// the default location the availability picker scopes to. Absent on single-location
|
|
16
|
+
// sites (and on deep links — the picker then defaults to the service's first location).
|
|
17
|
+
const reqUrl = new URL(Astro.request.url);
|
|
18
|
+
const locationId = reqUrl.searchParams.get("locationId") ?? undefined;
|
|
19
|
+
|
|
14
20
|
let service: any = null;
|
|
15
21
|
try {
|
|
16
22
|
// Ambient SSR via @wix/essentials. Single service by slug: the .eq() builder
|
|
@@ -90,42 +96,79 @@ const duration = service.schedule?.availabilityConstraints?.sessionDurations?.[0
|
|
|
90
96
|
const imageUrl = service.media?.mainMedia?.image?.url ?? service.media?.items?.[0]?.image?.url;
|
|
91
97
|
const serviceName = service.name ?? "Service";
|
|
92
98
|
const seoTags = service.seoData?.tags ?? [];
|
|
99
|
+
|
|
100
|
+
// Staff for the optional staff picker — carried on the service via the
|
|
101
|
+
// STAFF_MEMBER_DETAILS conditional field (already requested in the query above).
|
|
102
|
+
// Each entry is { staffMemberId, name } — `staffMemberId` is, despite the name,
|
|
103
|
+
// the resource id (the calendar filters availability + books by it). The calendar
|
|
104
|
+
// shows the picker only when there is more than one; empty/absent → "any staff
|
|
105
|
+
// member" (the ANY_RESOURCE fallback).
|
|
106
|
+
const staffMembers = service.staffMemberDetails?.staffMembers ?? [];
|
|
107
|
+
|
|
108
|
+
// Business locations the service is offered at — sourced from `queryLocations()`
|
|
109
|
+
// (the site's real business locations, whose ids the availability engine
|
|
110
|
+
// recognizes), intersected with the service's own location ids. Availability is
|
|
111
|
+
// scoped to one of these so a multi-location service doesn't return duplicate
|
|
112
|
+
// per-location time slots (listAvailabilityTimeSlots returns one slot per location
|
|
113
|
+
// otherwise). Source from `queryLocations()`, NOT from `service.locations` alone:
|
|
114
|
+
// a service's location entry can carry an id the availability engine doesn't
|
|
115
|
+
// recognize, and scoping to it returns zero slots. When the site has no business
|
|
116
|
+
// locations this resolves to [] and the calendar fetches unscoped.
|
|
117
|
+
let serviceLocations: { _id: string; name?: string }[] = [];
|
|
118
|
+
try {
|
|
119
|
+
const locResult: any = await auth.elevate(services.queryLocations)();
|
|
120
|
+
const businessLocations = locResult?.businessLocations?.locations ?? [];
|
|
121
|
+
const serviceLocationIds = new Set(
|
|
122
|
+
(service.locations ?? [])
|
|
123
|
+
.filter((l: any) => (l.type ?? "BUSINESS") === "BUSINESS")
|
|
124
|
+
.map((l: any) => l.business?._id ?? l._id)
|
|
125
|
+
.filter(Boolean),
|
|
126
|
+
);
|
|
127
|
+
serviceLocations = businessLocations
|
|
128
|
+
.filter((l: any) => serviceLocationIds.has(l._id))
|
|
129
|
+
.map((l: any) => ({ _id: l._id, name: l.business?.name ?? "Location" }));
|
|
130
|
+
} catch (err) {
|
|
131
|
+
console.error(`[services/${slug}] queryLocations failed:`, err);
|
|
132
|
+
}
|
|
93
133
|
---
|
|
94
134
|
|
|
95
135
|
<!--
|
|
96
|
-
Width: use `container-reading` (max-width: var(--container-prose) + auto margins)
|
|
97
|
-
|
|
98
|
-
|
|
136
|
+
Width: use `container-reading` (max-width: var(--container-prose) + auto margins)
|
|
137
|
+
for a deliberate prose measure. Bare `max-w-<size>` keys resolve to Tailwind's
|
|
138
|
+
--container-* scale, which is wider than ideal for reading text.
|
|
99
139
|
-->
|
|
100
140
|
<Layout title={serviceName} description={service.tagLine ?? service.description}>
|
|
101
141
|
<SeoTags tags={seoTags} slot="head" />
|
|
102
|
-
<section class="container-reading px-
|
|
103
|
-
<a href="/services" class="inline-block text-sm text-mute hover:text-ink mb-
|
|
142
|
+
<section class="container-reading px-6 py-24">
|
|
143
|
+
<a href="/services" class="inline-block text-sm text-mute hover:text-ink mb-8">← All services</a>
|
|
104
144
|
|
|
105
145
|
<!-- Optional banner image — only when the service has media (image-less is the
|
|
106
146
|
common seed case; rendering a fixed placeholder collapses the layout). -->
|
|
107
147
|
{imageUrl && (
|
|
108
|
-
<img src={imageUrl} alt={serviceName} class="w-full h-64 object-cover rounded-lg mb-
|
|
148
|
+
<img src={imageUrl} alt={serviceName} class="w-full h-64 object-cover rounded-lg mb-8" />
|
|
109
149
|
)}
|
|
110
150
|
|
|
111
|
-
<div class="flex flex-col gap-
|
|
151
|
+
<div class="flex flex-col gap-4 mb-16">
|
|
112
152
|
<hr class="editorial-rule w-16 ml-0" />
|
|
113
153
|
<h1 class="font-display text-4xl md:text-5xl leading-tight">{serviceName}</h1>
|
|
114
154
|
{service.tagLine && <p class="text-lg text-mute leading-relaxed max-w-prose">{service.tagLine}</p>}
|
|
115
|
-
<div class="flex items-center gap-
|
|
155
|
+
<div class="flex items-center gap-4 text-sm text-mute">
|
|
116
156
|
<span>{duration} min</span>
|
|
117
157
|
{price && <span class="text-ink font-medium">{price}</span>}
|
|
118
158
|
</div>
|
|
119
159
|
{service.description && <p class="text-ink leading-relaxed max-w-prose">{service.description}</p>}
|
|
120
160
|
</div>
|
|
121
161
|
|
|
122
|
-
<section class="border-t border-rule pt-
|
|
123
|
-
<h2 class="font-display text-3xl leading-tight mb-
|
|
162
|
+
<section class="border-t border-rule pt-12">
|
|
163
|
+
<h2 class="font-display text-3xl leading-tight mb-8">Book this service</h2>
|
|
124
164
|
<ServiceBookingFlow
|
|
125
165
|
service={service}
|
|
126
166
|
serviceName={serviceName}
|
|
127
167
|
serviceType={service.type ?? "APPOINTMENT"}
|
|
128
168
|
fields={formFields}
|
|
169
|
+
staffMembers={staffMembers}
|
|
170
|
+
locations={serviceLocations}
|
|
171
|
+
locationId={locationId}
|
|
129
172
|
client:only="react"
|
|
130
173
|
/>
|
|
131
174
|
</section>
|
|
@@ -2,30 +2,93 @@
|
|
|
2
2
|
// src/pages/services/index.astro — SSR services listing grid (APPOINTMENT or CLASS).
|
|
3
3
|
// One dir deep inside src/pages → ../../ reaches src/. Adapt copy/headings to the
|
|
4
4
|
// brand; keep the SSR data wiring.
|
|
5
|
+
//
|
|
6
|
+
// Optional catalog filters (both auto-skip when there's nothing to filter):
|
|
7
|
+
// location — a location selector, shown ONLY when the site has >1 location
|
|
8
|
+
// (business + custom + customer). Mirrors the SoT's location-first
|
|
9
|
+
// flow without forcing a separate step: single-location sites never
|
|
10
|
+
// see it. The choice is carried as ?locationId (+?locationType) and
|
|
11
|
+
// forwarded into both the services query and the detail page's
|
|
12
|
+
// availability.
|
|
13
|
+
// category — a category filter bar, shown ONLY when >1 category exists. The
|
|
14
|
+
// choice is carried as ?category and merged into the services query.
|
|
15
|
+
// Both are plain links that re-SSR with query params (re-query, like the SoT) —
|
|
16
|
+
// no client JS on the catalog.
|
|
5
17
|
import Layout from "../../layouts/Layout.astro";
|
|
6
18
|
import ServiceCard from "../../components/ServiceCard.astro";
|
|
7
|
-
import { services } from "@wix/bookings";
|
|
19
|
+
import { services, categoriesV2 } from "@wix/bookings";
|
|
8
20
|
import { auth } from "@wix/essentials";
|
|
9
21
|
|
|
10
22
|
// Wix Bookings app id — the base filter must scope queryServices to it.
|
|
11
23
|
const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
12
24
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
const reqUrl = new URL(Astro.request.url);
|
|
26
|
+
const selectedCategory = reqUrl.searchParams.get("category") ?? "";
|
|
27
|
+
const selectedLocation = reqUrl.searchParams.get("locationId") ?? "";
|
|
28
|
+
const selectedLocationType = reqUrl.searchParams.get("locationType") ?? "";
|
|
29
|
+
|
|
30
|
+
// ── Services filter (appId + optional category + optional location) ─────────
|
|
31
|
+
// A real business-location id filters by locations.business.id; the synthetic
|
|
32
|
+
// "custom"/"customer" ids map to a location-TYPE filter (the SoT mapping). Built
|
|
33
|
+
// from URL params only, so it doesn't depend on the locations/categories reads.
|
|
34
|
+
const filter: Record<string, any> = { appId: BOOKING_APP_ID };
|
|
35
|
+
if (selectedCategory) filter["category.id"] = { $eq: selectedCategory };
|
|
36
|
+
if (selectedLocation === "custom") filter["locations.type"] = { $hasSome: ["CUSTOM"] };
|
|
37
|
+
else if (selectedLocation === "customer") filter["locations.type"] = { $hasSome: ["CUSTOMER"] };
|
|
38
|
+
else if (selectedLocation) filter["locations.business.id"] = { $hasSome: [selectedLocation] };
|
|
39
|
+
|
|
40
|
+
// ── Catalog reads — three INDEPENDENT queries, fetched concurrently ─────────
|
|
41
|
+
// queryServices (the filtered list) + queryLocations (selector) + queryCategories
|
|
42
|
+
// (bar) don't depend on each other, so run them in parallel (the SoT fetches
|
|
43
|
+
// services + categories with Promise.allSettled likewise) — one round-trip's
|
|
44
|
+
// latency, not three. Each is individually non-fatal: a failed read degrades only
|
|
45
|
+
// its own piece (an empty list / a hidden selector or bar), never the whole page.
|
|
46
|
+
// Ambient @wix/astro SSR client via @wix/essentials — do NOT build
|
|
47
|
+
// createClient({ auth: OAuthStrategy({ clientId: import.meta.env.* }) }); that env
|
|
48
|
+
// var is client-only and undefined at server render → 500.
|
|
49
|
+
const [servicesResult, locResult, catResult] = await Promise.all([
|
|
50
|
+
auth
|
|
51
|
+
.elevate(services.queryServices)({ query: { filter, paging: { limit: 100 } }, conditionalFields: ["STAFF_MEMBER_DETAILS"] })
|
|
52
|
+
.find()
|
|
53
|
+
.catch((err: any) => { console.error("[services] listing query failed:", err); return null; }),
|
|
54
|
+
auth.elevate(services.queryLocations)().catch((err: any) => { console.error("[services] queryLocations failed:", err); return null; }),
|
|
55
|
+
auth.elevate(categoriesV2.queryCategories)().find().catch((err: any) => { console.error("[services] queryCategories failed:", err); return null; }),
|
|
56
|
+
]);
|
|
57
|
+
|
|
58
|
+
const serviceList: any[] = (servicesResult?.items ?? []).filter((s: any) => !s.hidden);
|
|
59
|
+
|
|
60
|
+
// Locations — auto-skip the selector when ≤1 (business + custom + customer).
|
|
61
|
+
const businessLocations: any[] = locResult?.businessLocations?.locations ?? [];
|
|
62
|
+
const hasCustomLocations: boolean = locResult?.customLocations?.exists ?? false;
|
|
63
|
+
const hasCustomerLocations: boolean = locResult?.customerLocations?.exists ?? false;
|
|
64
|
+
const locationCount =
|
|
65
|
+
businessLocations.length + (hasCustomLocations ? 1 : 0) + (hasCustomerLocations ? 1 : 0);
|
|
66
|
+
const showLocationSelector = locationCount > 1;
|
|
67
|
+
|
|
68
|
+
// Categories — non-fatal; show the bar only when >1 (a fresh site has one default).
|
|
69
|
+
const categories: any[] = catResult?.items ?? [];
|
|
70
|
+
const showCategoryBar = categories.length > 1;
|
|
71
|
+
|
|
72
|
+
// Build a /services href preserving the current selection, applying overrides.
|
|
73
|
+
// Pass null to clear a param. locationType is always paired with locationId.
|
|
74
|
+
const hrefWith = (patch: Record<string, string | null>): string => {
|
|
75
|
+
const p = new URLSearchParams();
|
|
76
|
+
if (selectedCategory) p.set("category", selectedCategory);
|
|
77
|
+
if (selectedLocation) {
|
|
78
|
+
p.set("locationId", selectedLocation);
|
|
79
|
+
if (selectedLocationType) p.set("locationType", selectedLocationType);
|
|
80
|
+
}
|
|
81
|
+
for (const [k, v] of Object.entries(patch)) {
|
|
82
|
+
if (v === null) p.delete(k);
|
|
83
|
+
else p.set(k, v);
|
|
84
|
+
}
|
|
85
|
+
if (patch.locationId === null) p.delete("locationType");
|
|
86
|
+
const qs = p.toString();
|
|
87
|
+
return qs ? `/services?${qs}` : "/services";
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Is a filter narrowing the list right now? Drives the empty-state copy.
|
|
91
|
+
const filterActive = Boolean(selectedCategory || selectedLocation);
|
|
29
92
|
|
|
30
93
|
const getSlug = (s: any): string => s.mainSlug?.name ?? s.supportedSlugs?.[0]?.name ?? s._id;
|
|
31
94
|
const formatPrice = (s: any): string | undefined => {
|
|
@@ -39,19 +102,62 @@ const formatPrice = (s: any): string | undefined => {
|
|
|
39
102
|
return undefined;
|
|
40
103
|
};
|
|
41
104
|
const getDuration = (s: any): number => s.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
|
|
105
|
+
const locationName = (loc: any): string => loc?.business?.name ?? loc?.calculatedAddress?.city ?? "Location";
|
|
42
106
|
---
|
|
43
107
|
|
|
44
108
|
<Layout title="Services">
|
|
45
|
-
<section class="max-w-6xl mx-auto px-
|
|
46
|
-
<header class="flex flex-col gap-
|
|
109
|
+
<section class="max-w-6xl mx-auto px-6 py-24">
|
|
110
|
+
<header class="flex flex-col gap-4 max-w-prose mb-16">
|
|
47
111
|
<hr class="editorial-rule w-16 ml-0" />
|
|
48
112
|
<h1 class="font-display text-4xl md:text-5xl leading-tight">Our Services</h1>
|
|
49
113
|
<!-- Optional brand tagline -->
|
|
50
114
|
</header>
|
|
51
115
|
|
|
116
|
+
{
|
|
117
|
+
showLocationSelector && (
|
|
118
|
+
<nav class="booking-filter-bar" aria-label="Filter by location">
|
|
119
|
+
<a href={hrefWith({ locationId: null })} class={`booking-filter-chip${selectedLocation ? "" : " booking-filter-chip--active"}`}>All locations</a>
|
|
120
|
+
{businessLocations.map((loc: any) => (
|
|
121
|
+
<a
|
|
122
|
+
href={hrefWith({ locationId: loc._id, locationType: "BUSINESS" })}
|
|
123
|
+
class={`booking-filter-chip${selectedLocation === loc._id ? " booking-filter-chip--active" : ""}`}
|
|
124
|
+
>
|
|
125
|
+
{locationName(loc)}
|
|
126
|
+
</a>
|
|
127
|
+
))}
|
|
128
|
+
{hasCustomLocations && (
|
|
129
|
+
<a href={hrefWith({ locationId: "custom", locationType: "CUSTOM" })} class={`booking-filter-chip${selectedLocation === "custom" ? " booking-filter-chip--active" : ""}`}>Custom location</a>
|
|
130
|
+
)}
|
|
131
|
+
{hasCustomerLocations && (
|
|
132
|
+
<a href={hrefWith({ locationId: "customer", locationType: "CUSTOMER" })} class={`booking-filter-chip${selectedLocation === "customer" ? " booking-filter-chip--active" : ""}`}>My location</a>
|
|
133
|
+
)}
|
|
134
|
+
</nav>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
{
|
|
139
|
+
showCategoryBar && (
|
|
140
|
+
<nav class="booking-filter-bar" aria-label="Filter by category">
|
|
141
|
+
<a href={hrefWith({ category: null })} class={`booking-filter-chip${selectedCategory ? "" : " booking-filter-chip--active"}`}>All</a>
|
|
142
|
+
{categories.map((cat: any) => (
|
|
143
|
+
<a
|
|
144
|
+
href={hrefWith({ category: cat._id })}
|
|
145
|
+
class={`booking-filter-chip${selectedCategory === cat._id ? " booking-filter-chip--active" : ""}`}
|
|
146
|
+
>
|
|
147
|
+
{cat.name ?? "Category"}
|
|
148
|
+
</a>
|
|
149
|
+
))}
|
|
150
|
+
</nav>
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
|
|
52
154
|
{
|
|
53
155
|
serviceList.length === 0 ? (
|
|
54
|
-
<p class="text-mute">
|
|
156
|
+
<p class="text-mute">
|
|
157
|
+
{filterActive
|
|
158
|
+
? "No services match your selection — try a different filter."
|
|
159
|
+
: "Services coming soon — check back shortly."}
|
|
160
|
+
</p>
|
|
55
161
|
) : (
|
|
56
162
|
<div class="service-grid">
|
|
57
163
|
{serviceList.map((s) => (
|
|
@@ -64,6 +170,8 @@ const getDuration = (s: any): number => s.schedule?.availabilityConstraints?.ses
|
|
|
64
170
|
price={formatPrice(s)}
|
|
65
171
|
imageUrl={s.media?.mainMedia?.image?.url ?? s.media?.items?.[0]?.image?.url}
|
|
66
172
|
type={s.type ?? "APPOINTMENT"}
|
|
173
|
+
locationId={selectedLocation || undefined}
|
|
174
|
+
locationType={selectedLocationType || undefined}
|
|
67
175
|
/>
|
|
68
176
|
))}
|
|
69
177
|
</div>
|
|
@@ -354,9 +354,9 @@ export default function CartView() {
|
|
|
354
354
|
const hasUnavailable = items.some(isItemUnavailable);
|
|
355
355
|
|
|
356
356
|
return (
|
|
357
|
-
<div className="grid grid-cols-1 md:grid-cols-[1fr_22rem] gap-
|
|
357
|
+
<div className="grid grid-cols-1 md:grid-cols-[1fr_22rem] gap-12 md:gap-16 md:items-start">
|
|
358
358
|
{/* ── Left column: line items ── */}
|
|
359
|
-
<div className="flex flex-col gap-
|
|
359
|
+
<div className="flex flex-col gap-6">
|
|
360
360
|
{items.map((item) => {
|
|
361
361
|
const unavailable = isItemUnavailable(item);
|
|
362
362
|
const maxQty = item.availability?.quantityAvailable ?? 99;
|
|
@@ -367,7 +367,7 @@ export default function CartView() {
|
|
|
367
367
|
return (
|
|
368
368
|
<div
|
|
369
369
|
key={item._id}
|
|
370
|
-
className={`flex gap-
|
|
370
|
+
className={`flex gap-6 pb-6 border-b border-rule last:border-0${unavailable ? " unavailable" : ""}`}
|
|
371
371
|
>
|
|
372
372
|
{/* Product image + name link back to the product detail page
|
|
373
373
|
for available items. Unavailable lines render as plain text. */}
|
|
@@ -4,9 +4,9 @@ import CartView from "../components/CartView.tsx";
|
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
<Layout title="Cart">
|
|
7
|
-
<main class="py-
|
|
8
|
-
<div class="max-w-wide mx-auto px-
|
|
9
|
-
<h1 class="font-display text-4xl mb-
|
|
7
|
+
<main class="py-16">
|
|
8
|
+
<div class="max-w-wide mx-auto px-8">
|
|
9
|
+
<h1 class="font-display text-4xl mb-12">Your Cart</h1>
|
|
10
10
|
<CartView client:load />
|
|
11
11
|
</div>
|
|
12
12
|
</main>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* used `@apply text-bark/70 bg-tobacco …` (stale token vocabulary) and
|
|
21
21
|
* `@apply size-6` (built-in utility that needs `@reference` to be visible
|
|
22
22
|
* here), and both classes built-failed against current designer output.
|
|
23
|
-
* Direct `var(--color-ink)` / `
|
|
23
|
+
* Direct `var(--color-ink)` / `0.75rem` calls work against any
|
|
24
24
|
* designer-published token set as long as the core scales exist (they always
|
|
25
25
|
* do). Use fallback chains (`var(--color-paper-warm, var(--color-paper, var(--color-cream)))`)
|
|
26
26
|
* for sometimes-published tokens.
|
|
@@ -33,22 +33,22 @@
|
|
|
33
33
|
.cart-summary {
|
|
34
34
|
background: var(--color-paper-warm, var(--color-paper, var(--color-cream)));
|
|
35
35
|
border: 1px solid var(--color-rule);
|
|
36
|
-
padding:
|
|
36
|
+
padding: 2rem;
|
|
37
37
|
border-radius: var(--radius-md);
|
|
38
38
|
display: flex;
|
|
39
39
|
flex-direction: column;
|
|
40
|
-
gap:
|
|
40
|
+
gap: 0.75rem;
|
|
41
41
|
}
|
|
42
42
|
@media (min-width: 768px) {
|
|
43
43
|
.cart-summary {
|
|
44
44
|
position: sticky;
|
|
45
|
-
top:
|
|
45
|
+
top: 2rem;
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
.cart-empty {
|
|
50
50
|
text-align: center;
|
|
51
|
-
padding-block:
|
|
51
|
+
padding-block: 4rem;
|
|
52
52
|
color: var(--color-mute);
|
|
53
53
|
}
|
|
54
54
|
.cart-empty .checkout-btn {
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
display: inline-flex;
|
|
67
67
|
align-items: center;
|
|
68
68
|
justify-content: center;
|
|
69
|
-
padding:
|
|
69
|
+
padding: 1rem 1.5rem;
|
|
70
70
|
font-family: var(--font-display);
|
|
71
71
|
font-size: 0.9375rem;
|
|
72
72
|
font-weight: 500;
|
|
@@ -76,7 +76,7 @@
|
|
|
76
76
|
border-radius: var(--radius-sm);
|
|
77
77
|
cursor: pointer;
|
|
78
78
|
width: 100%;
|
|
79
|
-
margin-top:
|
|
79
|
+
margin-top: 1rem;
|
|
80
80
|
transition: background-color 150ms ease, box-shadow 150ms ease;
|
|
81
81
|
}
|
|
82
82
|
.checkout-btn:hover:not(:disabled) {
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
.cart-item {
|
|
124
124
|
display: flex;
|
|
125
125
|
flex-direction: row;
|
|
126
|
-
gap:
|
|
126
|
+
gap: 1rem;
|
|
127
127
|
}
|
|
128
128
|
.cart-item-image {
|
|
129
129
|
flex-shrink: 0;
|
|
@@ -171,8 +171,8 @@
|
|
|
171
171
|
.cart-item-qty {
|
|
172
172
|
display: inline-flex;
|
|
173
173
|
align-items: center;
|
|
174
|
-
gap:
|
|
175
|
-
margin-top:
|
|
174
|
+
gap: 0.75rem;
|
|
175
|
+
margin-top: 0.75rem;
|
|
176
176
|
width: fit-content;
|
|
177
177
|
}
|
|
178
178
|
.qty-btn {
|
|
@@ -221,7 +221,7 @@
|
|
|
221
221
|
.cart-item-prices {
|
|
222
222
|
display: flex;
|
|
223
223
|
align-items: center;
|
|
224
|
-
gap:
|
|
224
|
+
gap: 0.75rem;
|
|
225
225
|
}
|
|
226
226
|
.cart-item-full-price {
|
|
227
227
|
font-family: var(--font-body);
|
|
@@ -258,10 +258,10 @@
|
|
|
258
258
|
display: flex;
|
|
259
259
|
align-items: baseline;
|
|
260
260
|
justify-content: space-between;
|
|
261
|
-
gap:
|
|
261
|
+
gap: 0.75rem;
|
|
262
262
|
font-family: var(--font-body);
|
|
263
263
|
font-size: 0.875rem;
|
|
264
|
-
margin-top:
|
|
264
|
+
margin-top: 0.75rem;
|
|
265
265
|
}
|
|
266
266
|
.cart-discount-name {
|
|
267
267
|
font-size: 0.75rem;
|
|
@@ -272,8 +272,8 @@
|
|
|
272
272
|
}
|
|
273
273
|
.cart-total {
|
|
274
274
|
border-top: 1px solid var(--color-rule);
|
|
275
|
-
padding-top:
|
|
276
|
-
margin-top:
|
|
275
|
+
padding-top: 1rem;
|
|
276
|
+
margin-top: 0.75rem;
|
|
277
277
|
font-family: var(--font-display);
|
|
278
278
|
font-size: 1.25rem;
|
|
279
279
|
font-weight: 500;
|
|
@@ -290,10 +290,10 @@
|
|
|
290
290
|
display: flex;
|
|
291
291
|
align-items: baseline;
|
|
292
292
|
justify-content: space-between;
|
|
293
|
-
gap:
|
|
293
|
+
gap: 0.75rem;
|
|
294
294
|
font-family: var(--font-body);
|
|
295
295
|
font-size: 0.875rem;
|
|
296
|
-
margin-top:
|
|
296
|
+
margin-top: 0.75rem;
|
|
297
297
|
}
|
|
298
298
|
.cart-applied-discounts-name {
|
|
299
299
|
text-align: right;
|
|
@@ -5,12 +5,12 @@ const orderId = Astro.url.searchParams.get("orderId");
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<Layout title="Thank You">
|
|
8
|
-
<main class="py-
|
|
9
|
-
<div class="max-w-prose mx-auto px-
|
|
8
|
+
<main class="py-24">
|
|
9
|
+
<div class="max-w-prose mx-auto px-8 text-center flex flex-col gap-6">
|
|
10
10
|
<h1 class="font-display text-5xl">Thank You!</h1>
|
|
11
11
|
<p class="text-ink-soft">Your order has been placed successfully.</p>
|
|
12
12
|
{orderId && <p class="text-mute text-sm tracking-wide">Order ID: {orderId}</p>}
|
|
13
|
-
<a href="/products" class="btn-primary self-center mt-
|
|
13
|
+
<a href="/products" class="btn-primary self-center mt-4">Continue Shopping</a>
|
|
14
14
|
</div>
|
|
15
15
|
</main>
|
|
16
16
|
</Layout>
|
package/skills/wix-headless/references/astro/templates/gift-cards/_home-teaser-snippet.astro
CHANGED
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
during adaptation; keep the structure as-is.
|
|
19
19
|
*/}
|
|
20
20
|
{giftCardProduct && (
|
|
21
|
-
<section class="py-
|
|
22
|
-
<a href="/gift-cards" class="max-w-wide mx-auto px-
|
|
21
|
+
<section class="py-16 md:py-24">
|
|
22
|
+
<a href="/gift-cards" class="max-w-wide mx-auto px-8 grid grid-cols-1 md:grid-cols-2 gap-8 md:gap-16 items-center group">
|
|
23
23
|
<div class="aspect-[4/3] bg-cream rounded-sm overflow-hidden">
|
|
24
24
|
{giftCardImage && (
|
|
25
25
|
<img
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
/>
|
|
32
32
|
)}
|
|
33
33
|
</div>
|
|
34
|
-
<div class="flex flex-col gap-
|
|
34
|
+
<div class="flex flex-col gap-4">
|
|
35
35
|
<p class="eyebrow">A gift, considered</p>
|
|
36
36
|
<h2 class="font-display text-3xl leading-tight">{giftCardProduct.name}</h2>
|
|
37
37
|
<p class="text-ink-soft leading-relaxed">{giftCardProduct.description}</p>
|
|
38
|
-
<span class="text-sm tracking-wide uppercase mt-
|
|
38
|
+
<span class="text-sm tracking-wide uppercase mt-3">Send a gift card →</span>
|
|
39
39
|
</div>
|
|
40
40
|
</a>
|
|
41
41
|
</section>
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
* Reuses tokens declared by the designer foundation: --color-cream,
|
|
9
9
|
* --color-cream-deep, --color-ink, --color-ink-soft, --color-muted,
|
|
10
10
|
* --color-rule, --color-paper, --color-bark/espresso/gold-deep,
|
|
11
|
-
* --
|
|
12
|
-
*
|
|
11
|
+
* --radius-sm, --font-body, --font-display. (Spacing uses Tailwind's numeric
|
|
12
|
+
* scale — gap-4, py-24 — not a design token.) If a token isn't in your design
|
|
13
|
+
* system, fall back to a
|
|
13
14
|
* sibling token; do NOT invent new ones.
|
|
14
15
|
*/
|
|
15
16
|
|
|
@@ -20,8 +21,8 @@
|
|
|
20
21
|
.gift-card-form {
|
|
21
22
|
display: flex;
|
|
22
23
|
flex-direction: column;
|
|
23
|
-
gap:
|
|
24
|
-
margin-top:
|
|
24
|
+
gap: 1.5rem;
|
|
25
|
+
margin-top: 1.5rem;
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
.gift-card-fieldset {
|
|
@@ -33,14 +34,14 @@
|
|
|
33
34
|
.gift-card-amounts {
|
|
34
35
|
display: flex;
|
|
35
36
|
flex-wrap: wrap;
|
|
36
|
-
gap:
|
|
37
|
-
margin-top:
|
|
37
|
+
gap: 0.75rem;
|
|
38
|
+
margin-top: 0.75rem;
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
.gift-card-grid {
|
|
41
42
|
display: grid;
|
|
42
43
|
grid-template-columns: 1fr 1fr;
|
|
43
|
-
gap:
|
|
44
|
+
gap: 1rem;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
.gift-card-field {
|
|
@@ -101,20 +102,20 @@
|
|
|
101
102
|
.gift-card-page {
|
|
102
103
|
max-width: 80rem;
|
|
103
104
|
margin: 0 auto;
|
|
104
|
-
padding:
|
|
105
|
+
padding: 4rem 1.5rem 6rem;
|
|
105
106
|
}
|
|
106
107
|
|
|
107
108
|
.gift-card-grid-wrap {
|
|
108
109
|
display: grid;
|
|
109
110
|
grid-template-columns: 1fr;
|
|
110
|
-
gap:
|
|
111
|
+
gap: 3rem;
|
|
111
112
|
align-items: start;
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
@media (min-width: 768px) {
|
|
115
116
|
.gift-card-grid-wrap {
|
|
116
117
|
grid-template-columns: 1fr 1fr;
|
|
117
|
-
gap:
|
|
118
|
+
gap: 6rem;
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
|
@@ -139,7 +140,7 @@
|
|
|
139
140
|
}
|
|
140
141
|
|
|
141
142
|
.gift-card-body h1 {
|
|
142
|
-
margin:
|
|
143
|
+
margin: 1rem 0 1rem;
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
.gift-card-lede {
|
|
@@ -148,11 +149,11 @@
|
|
|
148
149
|
line-height: 1.7;
|
|
149
150
|
color: var(--color-ink-soft);
|
|
150
151
|
max-width: 48ch;
|
|
151
|
-
margin: 0 0
|
|
152
|
+
margin: 0 0 2rem;
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
.gift-card-fineprint {
|
|
155
|
-
margin-top:
|
|
156
|
+
margin-top: 1.5rem;
|
|
156
157
|
font-family: var(--font-body);
|
|
157
158
|
font-size: 0.8125rem;
|
|
158
159
|
line-height: 1.6;
|
|
@@ -165,14 +166,14 @@
|
|
|
165
166
|
/* -------------------------------------------------------------------------- */
|
|
166
167
|
|
|
167
168
|
.gift-card-teaser {
|
|
168
|
-
padding:
|
|
169
|
+
padding: 6rem 1.5rem;
|
|
169
170
|
background: var(--color-cream-deep, var(--color-paper, var(--color-cream)));
|
|
170
171
|
}
|
|
171
172
|
|
|
172
173
|
.gift-card-teaser-link {
|
|
173
174
|
display: grid;
|
|
174
175
|
grid-template-columns: 1fr;
|
|
175
|
-
gap:
|
|
176
|
+
gap: 3rem;
|
|
176
177
|
max-width: 80rem;
|
|
177
178
|
margin: 0 auto;
|
|
178
179
|
align-items: center;
|
|
@@ -183,7 +184,7 @@
|
|
|
183
184
|
@media (min-width: 768px) {
|
|
184
185
|
.gift-card-teaser-link {
|
|
185
186
|
grid-template-columns: 1fr 1fr;
|
|
186
|
-
gap:
|
|
187
|
+
gap: 6rem;
|
|
187
188
|
}
|
|
188
189
|
}
|
|
189
190
|
|
|
@@ -206,7 +207,7 @@
|
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
.gift-card-teaser-body h2 {
|
|
209
|
-
margin:
|
|
210
|
+
margin: 1rem 0 1rem;
|
|
210
211
|
}
|
|
211
212
|
|
|
212
213
|
.gift-card-teaser-lede {
|
|
@@ -215,5 +216,5 @@
|
|
|
215
216
|
line-height: 1.7;
|
|
216
217
|
color: var(--color-ink-soft);
|
|
217
218
|
max-width: 44ch;
|
|
218
|
-
margin: 0 0
|
|
219
|
+
margin: 0 0 1.5rem;
|
|
219
220
|
}
|
|
@@ -18,8 +18,8 @@ const heroImage = resolveWixImageUrl(product.image, 800, 800);
|
|
|
18
18
|
title={product.name}
|
|
19
19
|
description={product.description}
|
|
20
20
|
>
|
|
21
|
-
<section class="py-
|
|
22
|
-
<div class="max-w-wide mx-auto px-
|
|
21
|
+
<section class="py-16 md:py-24">
|
|
22
|
+
<div class="max-w-wide mx-auto px-8 grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24 items-center">
|
|
23
23
|
<div class="aspect-square bg-cream rounded-sm overflow-hidden">
|
|
24
24
|
{heroImage && (
|
|
25
25
|
<img
|
|
@@ -34,7 +34,7 @@ const heroImage = resolveWixImageUrl(product.image, 800, 800);
|
|
|
34
34
|
)}
|
|
35
35
|
</div>
|
|
36
36
|
|
|
37
|
-
<div class="flex flex-col gap-
|
|
37
|
+
<div class="flex flex-col gap-6">
|
|
38
38
|
<p class="eyebrow">A gift, considered</p>
|
|
39
39
|
<h1 class="font-display text-5xl leading-tight">{product.name}</h1>
|
|
40
40
|
<p class="text-ink-soft leading-relaxed">{product.description}</p>
|