@wix/agent-skills 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/README.md +3 -7
  2. package/package.json +1 -1
  3. package/skills/wix-app/SKILL.md +2 -0
  4. package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
  5. package/skills/wix-design-system/SKILL.md +4 -0
  6. package/skills/wix-headless/SKILL.md +55 -32
  7. package/skills/wix-headless/references/BUILD-astro.md +246 -0
  8. package/skills/wix-headless/references/BUILD-own-build.md +167 -0
  9. package/skills/wix-headless/references/BUILD.md +50 -252
  10. package/skills/wix-headless/references/DESIGN_SYSTEM.md +94 -34
  11. package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
  12. package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
  13. package/skills/wix-headless/references/DISCOVERY.md +41 -422
  14. package/skills/wix-headless/references/PLAN-connect.md +19 -0
  15. package/skills/wix-headless/references/PLAN-create.md +17 -0
  16. package/skills/wix-headless/references/PLAN.md +37 -27
  17. package/skills/wix-headless/references/SEED.md +27 -18
  18. package/skills/wix-headless/references/SETUP.md +32 -213
  19. package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +5 -4
  20. package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +337 -0
  21. package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +428 -0
  22. package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +5 -5
  23. package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +10 -10
  24. package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +12 -13
  25. package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +10 -12
  26. package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +8 -8
  27. package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +50 -27
  28. package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +7 -8
  29. package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +5 -5
  30. package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
  31. package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +5 -19
  32. package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
  33. package/skills/wix-headless/references/astro/gift-cards/PAGES.md +1 -1
  34. package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +6 -6
  35. package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +4 -4
  36. package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +3 -3
  37. package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +3 -3
  38. package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +9 -9
  39. package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +5 -4
  40. package/skills/wix-headless/references/astro/templates/Footer.astro +1 -1
  41. package/skills/wix-headless/references/astro/templates/Layout.astro +1 -1
  42. package/skills/wix-headless/references/astro/templates/Navigation.astro +1 -1
  43. package/skills/wix-headless/references/astro/templates/astro.config.mjs +1 -1
  44. package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +218 -0
  45. package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +194 -0
  46. package/skills/wix-headless/references/astro/templates/bookings/ManageBooking.tsx +84 -0
  47. package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
  48. package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +50 -0
  49. package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
  50. package/skills/wix-headless/references/astro/templates/bookings/api/confirm-booking.ts +34 -0
  51. package/skills/wix-headless/references/astro/templates/bookings/api/waitlist.ts +74 -0
  52. package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +111 -0
  53. package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +413 -0
  54. package/skills/wix-headless/references/astro/templates/bookings/manage-booking.astro +76 -0
  55. package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +81 -0
  56. package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +65 -0
  57. package/skills/wix-headless/references/astro/templates/global.css +2 -2
  58. package/skills/wix-headless/references/astro/templates/index.astro +1 -1
  59. package/skills/wix-headless/references/blog/BLOG_CONTENT.md +19 -9
  60. package/skills/wix-headless/references/blog/INSTRUCTIONS.md +3 -3
  61. package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +106 -0
  62. package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
  63. package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +4 -2
  64. package/skills/wix-headless/references/cms/INSTRUCTIONS.md +27 -1
  65. package/skills/wix-headless/references/commands/known-apps.json +2 -1
  66. package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
  67. package/skills/wix-headless/references/custom/INSTRUCTIONS.md +71 -21
  68. package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
  69. package/skills/wix-headless/references/custom/bookings/WIRING.md +88 -0
  70. package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
  71. package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
  72. package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
  73. package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
  74. package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +5 -5
  75. package/skills/wix-headless/references/forms/INSTRUCTIONS.md +2 -2
  76. package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +3 -3
  77. package/skills/wix-headless/references/images/INSTRUCTIONS.md +16 -15
  78. package/skills/wix-headless/references/seed-recipes.md +2 -2
  79. package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
  80. package/skills/wix-headless/references/shared/AUTHENTICATION.md +3 -5
  81. package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
  82. package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +29 -28
  83. package/skills/wix-headless/references/shared/IMPLEMENTER.md +87 -27
  84. package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +2 -2
  85. package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +24 -313
  86. package/skills/wix-headless/references/shared/STYLING.md +8 -18
  87. package/skills/wix-headless/references/stores/INSTRUCTIONS.md +39 -9
  88. package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +17 -14
  89. package/skills/wix-headless/references/verticals/_schema.json +1 -1
  90. package/skills/wix-headless/references/verticals/_schema.md +3 -3
  91. package/skills/wix-headless/references/verticals/blog.md +1 -1
  92. package/skills/wix-headless/references/verticals/bookings.md +41 -0
  93. package/skills/wix-headless/references/verticals/cms.md +1 -1
  94. package/skills/wix-headless/references/verticals/ecom.md +2 -2
  95. package/skills/wix-headless/references/verticals/gift-cards.md +1 -1
  96. package/skills/wix-headless/references/verticals/stores.md +1 -1
  97. package/skills/wix-headless/scripts/check-manifest.mjs +143 -6
  98. package/skills/wix-headless/scripts/compose.mjs +540 -0
  99. package/skills/wix-headless/scripts/emit-design-tokens.mjs +128 -51
  100. package/skills/wix-headless/scripts/flatten.sh +61 -0
  101. package/skills/wix-headless/scripts/generate-images.mjs +242 -0
  102. package/skills/wix-headless/scripts/scaffold.sh +47 -16
  103. package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
  104. package/skills/wix-manage/SKILL.md +1 -1
  105. package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +1 -1
  106. package/skills/wix-manage/references/cms/cms-data-items-crud.md +1 -1
  107. package/skills/wix-manage/references/cms/cms-references-and-relationships.md +1 -1
  108. package/skills/wix-manage/references/cms/cms-schema-management.md +1 -1
  109. package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
  110. package/skills/wix-manage/references/forms/create-form.md +49 -1
  111. package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1 -1
  112. package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +1 -1
  113. package/skills/wix-headless/references/astro/COMPOSE.md +0 -142
  114. package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
  115. package/skills/wix-headless/scripts/init-site-json.mjs +0 -101
  116. package/skills/wix-headless/scripts/preview.sh +0 -45
  117. package/skills/wix-headless/scripts/release.sh +0 -73
@@ -0,0 +1,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
+ }
@@ -0,0 +1,76 @@
1
+ ---
2
+ // src/pages/manage-booking.astro — per-booking manage page (view + cancel).
3
+ // Reached from the confirmation page or a shared manage link:
4
+ // /manage-booking?bookingId=<id>&service=<name>
5
+ //
6
+ // A visitor token can't list/cancel bookings (queryExtendedBookings/cancelBooking
7
+ // are APP/MEMBER identity). The supported visitor path is the ANONYMOUS ACTION
8
+ // TOKEN: the server elevates (app identity) to mint a per-booking token, then the
9
+ // no-auth bookingsGet/CancelBookingAnonymously methods act on it. Minting MUST be
10
+ // server-side; the token is then safe to hand to the browser island.
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
+
18
+ let token: string | null = null;
19
+ let booking: any = null;
20
+ let allowed = { cancel: false, reschedule: false };
21
+ let error: string | null = null;
22
+
23
+ if (!bookingId) {
24
+ error = "No booking was specified.";
25
+ } else {
26
+ try {
27
+ const tokenRes = await auth.elevate(bookings.getAnonymousActionToken)(bookingId); // APP identity → elevate
28
+ token = tokenRes.token ?? null;
29
+ if (token) {
30
+ const res = await bookings.bookingsGetBookingAnonymously(token); // no auth
31
+ booking = res.booking ?? null;
32
+ allowed = {
33
+ cancel: !!res.allowedAnonymousActions?.cancel,
34
+ reschedule: !!res.allowedAnonymousActions?.reschedule,
35
+ };
36
+ }
37
+ } catch (err) {
38
+ console.error("[manage-booking] load failed:", err);
39
+ error = "We couldn't load this booking — the link may have expired.";
40
+ }
41
+ }
42
+
43
+ const serviceName =
44
+ booking?.bookedEntity?.title ?? Astro.url.searchParams.get("service") ?? "your booking";
45
+ const startDate =
46
+ (booking?.startDate as string | undefined) ??
47
+ booking?.bookedEntity?.slot?.startDate ??
48
+ Astro.url.searchParams.get("startDate") ??
49
+ null;
50
+ const status = (booking?.status as string | undefined) ?? null;
51
+ const revision = (booking?.revision as string | undefined) ?? "";
52
+ ---
53
+
54
+ <Layout title="Manage your booking">
55
+ <section class="container-reading px-lg py-4xl">
56
+ <a href="/services" class="inline-block text-sm text-mute hover:text-ink mb-xl">← All services</a>
57
+ <hr class="editorial-rule w-16 ml-0" />
58
+ <h1 class="font-display text-4xl md:text-5xl leading-tight mb-xl">Manage your booking</h1>
59
+
60
+ {
61
+ error ? (
62
+ <p class="booking-error">{error}</p>
63
+ ) : (
64
+ <ManageBooking
65
+ token={token}
66
+ revision={revision}
67
+ serviceName={serviceName}
68
+ startDate={startDate}
69
+ status={status}
70
+ canCancel={allowed.cancel}
71
+ client:only="react"
72
+ />
73
+ )
74
+ }
75
+ </section>
76
+ </Layout>
@@ -0,0 +1,81 @@
1
+ ---
2
+ // src/pages/services/[slug].astro — SSR service detail + client-side booking flow.
3
+ // Two dirs deep inside src/ → import with ../../ (not three ../).
4
+ import Layout from "../../layouts/Layout.astro";
5
+ import SeoTags from "../../components/SeoTags.astro";
6
+ import ServiceBookingFlow from "../../components/ServiceBookingFlow";
7
+ import { services } from "@wix/bookings";
8
+ import { auth } from "@wix/essentials";
9
+
10
+ const { slug } = Astro.params;
11
+
12
+ let service: any = null;
13
+ try {
14
+ // Ambient SSR via @wix/essentials; query BUILDER (object form returns 0).
15
+ // Fetch the small catalog and match the slug in JS (robust without a
16
+ // nested-field `mainSlug.name` builder filter).
17
+ const result = await auth.elevate(services.queryServices)().limit(100).find();
18
+ service =
19
+ (result.items ?? []).find(
20
+ (s: any) => (s.mainSlug?.name ?? s.supportedSlugs?.[0]?.name) === slug,
21
+ ) ?? null;
22
+ } catch (err) {
23
+ console.error(`[services/${slug}] detail query failed:`, err);
24
+ }
25
+
26
+ if (!service) return Astro.redirect("/services", 302);
27
+
28
+ const formatPrice = (p: any): string | undefined => {
29
+ if (!p || p.rateType === "NO_FEE") return undefined;
30
+ if (p.rateType === "FIXED" && p.fixed?.price) {
31
+ const { value, currency } = p.fixed.price;
32
+ if (!value) return undefined;
33
+ return new Intl.NumberFormat("en-US", { style: "currency", currency: currency ?? "USD" }).format(Number(value));
34
+ }
35
+ return undefined;
36
+ };
37
+ const price = formatPrice(service.payment);
38
+ const duration = service.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
39
+ const imageUrl = service.media?.mainMedia?.image?.url ?? service.media?.items?.[0]?.image?.url;
40
+ const serviceName = service.name ?? "Service";
41
+ const seoTags = service.seoData?.tags ?? [];
42
+ ---
43
+
44
+ <!--
45
+ Width: use `container-reading` (max-width: var(--container-prose) + auto margins),
46
+ NOT a bare `max-w-<size>` — in this Tailwind v4 setup an un-emitted max-w-<size>
47
+ key falls back to the spacing scale (e.g. max-w-4xl → ~7rem, one word per line).
48
+ -->
49
+ <Layout title={serviceName} description={service.tagLine ?? service.description}>
50
+ <SeoTags tags={seoTags} slot="head" />
51
+ <section class="container-reading px-lg py-4xl">
52
+ <a href="/services" class="inline-block text-sm text-mute hover:text-ink mb-xl">← All services</a>
53
+
54
+ <!-- Optional banner image — only when the service has media (image-less is the
55
+ common seed case; rendering a fixed placeholder collapses the layout). -->
56
+ {imageUrl && (
57
+ <img src={imageUrl} alt={serviceName} class="w-full h-64 object-cover rounded-lg mb-xl" />
58
+ )}
59
+
60
+ <div class="flex flex-col gap-md mb-3xl">
61
+ <hr class="editorial-rule w-16 ml-0" />
62
+ <h1 class="font-display text-4xl md:text-5xl leading-tight">{serviceName}</h1>
63
+ {service.tagLine && <p class="text-lg text-mute leading-relaxed max-w-prose">{service.tagLine}</p>}
64
+ <div class="flex items-center gap-md text-sm text-mute">
65
+ <span>{duration} min</span>
66
+ {price && <span class="text-ink font-medium">{price}</span>}
67
+ </div>
68
+ {service.description && <p class="text-ink leading-relaxed max-w-prose">{service.description}</p>}
69
+ </div>
70
+
71
+ <section class="border-t border-rule pt-2xl">
72
+ <h2 class="font-display text-3xl leading-tight mb-xl">Book this service</h2>
73
+ <ServiceBookingFlow
74
+ serviceId={service._id}
75
+ serviceName={serviceName}
76
+ serviceType={service.type ?? "APPOINTMENT"}
77
+ client:only="react"
78
+ />
79
+ </section>
80
+ </section>
81
+ </Layout>
@@ -0,0 +1,65 @@
1
+ ---
2
+ // src/pages/services/index.astro — SSR services listing grid (APPOINTMENT or CLASS).
3
+ // One dir deep inside src/pages → ../../ reaches src/. Adapt copy/headings to the
4
+ // brand; keep the SSR data wiring.
5
+ import Layout from "../../layouts/Layout.astro";
6
+ import ServiceCard from "../../components/ServiceCard.astro";
7
+ import { services } from "@wix/bookings";
8
+ import { auth } from "@wix/essentials";
9
+
10
+ let serviceList: any[] = [];
11
+ try {
12
+ // Ambient @wix/astro SSR client via @wix/essentials (same pattern as CMS pages).
13
+ // Do NOT build createClient({ auth: OAuthStrategy({ clientId: import.meta.env.* }) }) —
14
+ // that env var is client-only and undefined at server render → 500.
15
+ // queryServices uses the query BUILDER; the object form returns 0 items.
16
+ const result = await auth.elevate(services.queryServices)().limit(100).find();
17
+ serviceList = (result.items ?? []).filter((s: any) => !s.hidden);
18
+ } catch (err) {
19
+ console.error("[services] listing query failed:", err);
20
+ }
21
+
22
+ const getSlug = (s: any): string => s.mainSlug?.name ?? s.supportedSlugs?.[0]?.name ?? s._id;
23
+ const formatPrice = (s: any): string | undefined => {
24
+ const p = s.payment;
25
+ if (!p || p.rateType === "NO_FEE") return undefined;
26
+ if (p.rateType === "FIXED" && p.fixed?.price) {
27
+ const { value, currency } = p.fixed.price;
28
+ if (!value) return undefined;
29
+ return new Intl.NumberFormat("en-US", { style: "currency", currency: currency ?? "USD" }).format(Number(value));
30
+ }
31
+ return undefined;
32
+ };
33
+ const getDuration = (s: any): number => s.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
34
+ ---
35
+
36
+ <Layout title="Services">
37
+ <section class="max-w-6xl mx-auto px-lg py-4xl">
38
+ <header class="flex flex-col gap-md max-w-prose mb-3xl">
39
+ <hr class="editorial-rule w-16 ml-0" />
40
+ <h1 class="font-display text-4xl md:text-5xl leading-tight">Our Services</h1>
41
+ <!-- Optional brand tagline -->
42
+ </header>
43
+
44
+ {
45
+ serviceList.length === 0 ? (
46
+ <p class="text-mute">Services coming soon — check back shortly.</p>
47
+ ) : (
48
+ <div class="service-grid">
49
+ {serviceList.map((s) => (
50
+ <ServiceCard
51
+ id={s._id}
52
+ slug={getSlug(s)}
53
+ name={s.name ?? "Untitled"}
54
+ tagLine={s.tagLine ?? s.description}
55
+ durationMinutes={getDuration(s)}
56
+ price={formatPrice(s)}
57
+ imageUrl={s.media?.mainMedia?.image?.url ?? s.media?.items?.[0]?.image?.url}
58
+ type={s.type ?? "APPOINTMENT"}
59
+ />
60
+ ))}
61
+ </div>
62
+ )
63
+ }
64
+ </section>
65
+ </Layout>
@@ -1,7 +1,7 @@
1
1
  /* ── Composer skeleton: src/styles/global.css ──────────────────────────────
2
2
  The fixed bulk below is LITERAL — do not re-author it. Composer's only edit
3
3
  is to replace the {{theme}} block with the @theme palette derived from the
4
- inlined designTokens (see COMPOSE.md § "1. global.css"). Everything outside
4
+ inlined designTokens (see compose.mjs). Everything outside
5
5
  {{theme}} references the stable token vocabulary via var(--token) / built-in
6
6
  utilities and is brand-agnostic; ship it byte-for-byte.
7
7
  ─────────────────────────────────────────────────────────────────────────── */
@@ -11,7 +11,7 @@
11
11
  @theme {
12
12
  /* {{theme}} — Composer substitutes the full palette here from designTokens:
13
13
  every --color-*, --font-*, --spacing-* (2xs..4xl), --container-*, and
14
- --radius-* line. The required-token set (COMPOSE.md § "Token contract")
14
+ --radius-* line. The required-token set (enforced by compose.mjs)
15
15
  MUST all be present, or components render unstyled. Container scale is
16
16
  separate from spacing — never alias --container-3xl to --spacing-3xl. */
17
17
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  // ── Composer skeleton: src/pages/index.astro ──────────────────────────────────
3
- // Home shell. Substitutions (see COMPOSE.md § "6. index.astro"):
3
+ // Home shell. Substitutions (see compose.mjs):
4
4
  // {{shell.heroHeadline}}, {{shell.heroSub}} — brand-voice hero copy.
5
5
  // {{shell.footerTagline}} is NOT used here.
6
6
  // {{brand.name}} — used in the brand-story copy.