@wix/agent-skills 1.7.0 → 1.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -7
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +2 -0
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +2 -1
- package/skills/wix-app/references/STORES_VERSIONING.md +1 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +14 -0
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +11 -6
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +3 -17
- package/skills/wix-app/references/editor-react-component/SSR.md +119 -0
- package/skills/wix-design-system/SKILL.md +5 -1
- package/skills/wix-headless/SKILL.md +55 -32
- package/skills/wix-headless/references/BUILD-astro.md +244 -0
- package/skills/wix-headless/references/BUILD-own-build.md +167 -0
- package/skills/wix-headless/references/BUILD.md +50 -252
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +94 -34
- package/skills/wix-headless/references/DISCOVERY-connect.md +55 -0
- package/skills/wix-headless/references/DISCOVERY-create.md +238 -0
- package/skills/wix-headless/references/DISCOVERY.md +41 -422
- package/skills/wix-headless/references/PLAN-connect.md +19 -0
- package/skills/wix-headless/references/PLAN-create.md +17 -0
- package/skills/wix-headless/references/PLAN.md +37 -27
- package/skills/wix-headless/references/SEED.md +27 -18
- package/skills/wix-headless/references/SETUP.md +32 -213
- package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +5 -4
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +45 -0
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +58 -0
- package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +5 -5
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +10 -10
- package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +12 -13
- package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +10 -12
- package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +8 -8
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +47 -27
- package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +7 -8
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +5 -5
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +5 -19
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +2 -2
- package/skills/wix-headless/references/astro/gift-cards/PAGES.md +1 -1
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +6 -6
- package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +4 -4
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +3 -3
- package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +3 -3
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +9 -9
- package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +5 -4
- package/skills/wix-headless/references/astro/templates/Footer.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Layout.astro +1 -1
- package/skills/wix-headless/references/astro/templates/Navigation.astro +1 -1
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +1 -1
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +233 -0
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +136 -0
- package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +25 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +54 -0
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +45 -0
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +49 -0
- package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +245 -0
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +356 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +133 -0
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +73 -0
- package/skills/wix-headless/references/astro/templates/global.css +2 -2
- package/skills/wix-headless/references/astro/templates/index.astro +1 -1
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +19 -9
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/bookings/FLOW.md +185 -0
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +90 -0
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +295 -0
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +4 -2
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +27 -1
- package/skills/wix-headless/references/commands/known-apps.json +2 -1
- package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +142 -0
- package/skills/wix-headless/references/custom/INSTRUCTIONS.md +71 -21
- package/skills/wix-headless/references/custom/blog/WIRING.md +52 -0
- package/skills/wix-headless/references/custom/bookings/WIRING.md +146 -0
- package/skills/wix-headless/references/custom/cms/WIRING.md +97 -0
- package/skills/wix-headless/references/custom/ecom/WIRING.md +67 -0
- package/skills/wix-headless/references/custom/forms/WIRING.md +101 -0
- package/skills/wix-headless/references/custom/stores/WIRING.md +80 -0
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +5 -5
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +2 -2
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +3 -3
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +16 -15
- package/skills/wix-headless/references/seed-recipes.md +2 -2
- package/skills/wix-headless/references/shared/AGENTS_MD.md +47 -0
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +3 -5
- package/skills/wix-headless/references/shared/DESIGN_MD.md +85 -0
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +29 -28
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +87 -27
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +2 -2
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +24 -313
- package/skills/wix-headless/references/shared/STYLING.md +8 -18
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +39 -9
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +17 -14
- package/skills/wix-headless/references/verticals/_schema.json +1 -1
- package/skills/wix-headless/references/verticals/_schema.md +3 -3
- package/skills/wix-headless/references/verticals/blog.md +1 -1
- package/skills/wix-headless/references/verticals/bookings.md +39 -0
- package/skills/wix-headless/references/verticals/cms.md +1 -1
- package/skills/wix-headless/references/verticals/ecom.md +2 -2
- package/skills/wix-headless/references/verticals/gift-cards.md +1 -1
- package/skills/wix-headless/references/verticals/stores.md +1 -1
- package/skills/wix-headless/scripts/check-manifest.mjs +143 -6
- package/skills/wix-headless/scripts/compose.mjs +540 -0
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +128 -51
- package/skills/wix-headless/scripts/flatten.sh +61 -0
- package/skills/wix-headless/scripts/generate-images.mjs +242 -0
- package/skills/wix-headless/scripts/scaffold.sh +47 -16
- package/skills/wix-headless/scripts/write-decorative-json.mjs +62 -0
- package/skills/wix-manage/SKILL.md +13 -1
- package/skills/wix-manage/references/app-installation/install-wix-apps.md +3 -27
- package/skills/wix-manage/references/blog/how-to-create-blog-posts.md +1 -1
- package/skills/wix-manage/references/bookings/create-appointment-service.md +266 -0
- package/skills/wix-manage/references/bookings/create-booking-service-from-prompt.md +37 -0
- package/skills/wix-manage/references/bookings/create-class-service.md +245 -0
- package/skills/wix-manage/references/bookings/create-course-service.md +249 -0
- package/skills/wix-manage/references/cms/cms-data-items-crud.md +3 -2
- package/skills/wix-manage/references/cms/cms-references-and-relationships.md +1 -1
- package/skills/wix-manage/references/cms/cms-schema-management.md +2 -2
- package/skills/wix-manage/references/domains/domain-search-and-purchase.md +10 -7
- package/skills/wix-manage/references/forms/create-form.md +49 -1
- package/skills/wix-manage/references/stores/bulk-create-products-with-options.md +1 -1
- package/skills/wix-manage/references/stores/create-product-from-image.md +154 -2
- package/skills/wix-manage/references/stores/setup-online-store-catalog-v3.md +1 -1
- package/skills/wix-headless/references/astro/COMPOSE.md +0 -142
- package/skills/wix-headless/scripts/finalize-run-json.mjs +0 -141
- package/skills/wix-headless/scripts/init-site-json.mjs +0 -101
- package/skills/wix-headless/scripts/preview.sh +0 -45
- package/skills/wix-headless/scripts/release.sh +0 -73
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
/* src/styles/components-bookings.css
|
|
2
|
+
*
|
|
3
|
+
* Bookings-pack component CSS. Pre-copied by the orchestrator before the build
|
|
4
|
+
* wave (it is brand-agnostic var(--token) CSS). The designer's global.css does
|
|
5
|
+
* NOT publish these classes — they live here. See references/shared/STYLING.md
|
|
6
|
+
* § "Component-specific CSS is owned by the component, not the designer".
|
|
7
|
+
*
|
|
8
|
+
* Class groups:
|
|
9
|
+
* - .service-card / .service-grid / .service-card-* → ServiceCard.astro, services/index.astro
|
|
10
|
+
* - .availability-* / .time-slot* → AvailabilityCalendar.tsx (week calendar + slots)
|
|
11
|
+
* - .booking-* → BookingForm.tsx (schema-driven form)
|
|
12
|
+
*
|
|
13
|
+
* Primary actions (a selected slot, the confirm button) use var(--color-accent)
|
|
14
|
+
* so the flow matches the site's primary-action styling.
|
|
15
|
+
*
|
|
16
|
+
* Direct var(--token) only (no @apply). Fallback chains for sometimes-published
|
|
17
|
+
* tokens, e.g. var(--color-paper-warm, var(--color-paper, var(--color-cream))).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/* ── Service grid + card ────────────────────────────────────────────────────── */
|
|
21
|
+
.service-grid {
|
|
22
|
+
display: grid;
|
|
23
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
24
|
+
gap: var(--spacing-xl);
|
|
25
|
+
transition: opacity 200ms ease;
|
|
26
|
+
}
|
|
27
|
+
body[data-navigating="true"] .service-grid {
|
|
28
|
+
opacity: 0.55;
|
|
29
|
+
pointer-events: none;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.service-card {
|
|
33
|
+
position: relative;
|
|
34
|
+
display: flex;
|
|
35
|
+
flex-direction: column;
|
|
36
|
+
background: var(--color-paper, var(--color-cream));
|
|
37
|
+
border-radius: var(--radius-xl);
|
|
38
|
+
overflow: hidden;
|
|
39
|
+
text-decoration: none;
|
|
40
|
+
color: inherit;
|
|
41
|
+
transition: transform 280ms ease, box-shadow 280ms ease;
|
|
42
|
+
box-shadow: var(--shadow-card, 0 4px 16px -8px rgba(0, 0, 0, 0.15));
|
|
43
|
+
}
|
|
44
|
+
.service-card:hover {
|
|
45
|
+
transform: translateY(-4px);
|
|
46
|
+
}
|
|
47
|
+
.service-card:hover .service-card-image img {
|
|
48
|
+
transform: scale(1.04);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.service-card-image {
|
|
52
|
+
aspect-ratio: 3 / 2;
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
background: var(--color-paper-warm, var(--color-surface-secondary, var(--color-cream)));
|
|
55
|
+
}
|
|
56
|
+
.service-card-image img {
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 100%;
|
|
59
|
+
object-fit: cover;
|
|
60
|
+
transition: transform 600ms ease;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.service-card-meta {
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: column;
|
|
66
|
+
gap: var(--spacing-xs);
|
|
67
|
+
padding: var(--spacing-md) var(--spacing-md) var(--spacing-lg);
|
|
68
|
+
flex: 1;
|
|
69
|
+
}
|
|
70
|
+
.service-card-name {
|
|
71
|
+
margin: 0;
|
|
72
|
+
font-family: var(--font-display);
|
|
73
|
+
font-size: 1.125rem;
|
|
74
|
+
line-height: 1.25;
|
|
75
|
+
color: var(--color-ink);
|
|
76
|
+
}
|
|
77
|
+
.service-card-tagline {
|
|
78
|
+
margin: 0;
|
|
79
|
+
font-family: var(--font-body);
|
|
80
|
+
font-size: 0.875rem;
|
|
81
|
+
color: var(--color-ink-soft, var(--color-mute));
|
|
82
|
+
line-height: 1.45;
|
|
83
|
+
}
|
|
84
|
+
.service-card-price {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: var(--spacing-sm);
|
|
88
|
+
margin-top: auto;
|
|
89
|
+
padding-top: var(--spacing-sm);
|
|
90
|
+
font-family: var(--font-body);
|
|
91
|
+
font-size: 0.875rem;
|
|
92
|
+
color: var(--color-mute);
|
|
93
|
+
}
|
|
94
|
+
.service-card-price span + span::before {
|
|
95
|
+
content: "·";
|
|
96
|
+
padding-right: var(--spacing-sm);
|
|
97
|
+
color: var(--color-rule);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/* ── Availability — week calendar ───────────────────────────────────────────── */
|
|
101
|
+
.availability-calendar {
|
|
102
|
+
display: flex;
|
|
103
|
+
flex-direction: column;
|
|
104
|
+
gap: var(--spacing-lg);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.availability-week-nav {
|
|
108
|
+
display: flex;
|
|
109
|
+
align-items: center;
|
|
110
|
+
justify-content: space-between;
|
|
111
|
+
gap: var(--spacing-md);
|
|
112
|
+
padding: var(--spacing-sm) 0;
|
|
113
|
+
border-bottom: 1px solid var(--color-rule);
|
|
114
|
+
}
|
|
115
|
+
.availability-week-label {
|
|
116
|
+
font-family: var(--font-display);
|
|
117
|
+
font-size: 1rem;
|
|
118
|
+
color: var(--color-ink);
|
|
119
|
+
flex: 1;
|
|
120
|
+
text-align: center;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.availability-nav-btn {
|
|
124
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
125
|
+
background: transparent;
|
|
126
|
+
border: 1px solid var(--color-rule);
|
|
127
|
+
border-radius: var(--radius-sm);
|
|
128
|
+
color: var(--color-ink);
|
|
129
|
+
font-family: var(--font-body);
|
|
130
|
+
font-size: 0.875rem;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
transition: background-color 150ms ease, border-color 150ms ease;
|
|
133
|
+
}
|
|
134
|
+
.availability-nav-btn:hover:not(:disabled) {
|
|
135
|
+
background: var(--color-paper-warm, var(--color-cream));
|
|
136
|
+
border-color: var(--color-ink);
|
|
137
|
+
}
|
|
138
|
+
.availability-nav-btn:disabled {
|
|
139
|
+
opacity: 0.35;
|
|
140
|
+
cursor: not-allowed;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* The 7-day strip. */
|
|
144
|
+
.availability-day-strip {
|
|
145
|
+
display: grid;
|
|
146
|
+
grid-template-columns: repeat(7, 1fr);
|
|
147
|
+
gap: var(--spacing-sm);
|
|
148
|
+
}
|
|
149
|
+
.availability-day {
|
|
150
|
+
display: flex;
|
|
151
|
+
flex-direction: column;
|
|
152
|
+
align-items: center;
|
|
153
|
+
gap: 2px;
|
|
154
|
+
padding: var(--spacing-sm) 0;
|
|
155
|
+
border: 1px solid var(--color-rule);
|
|
156
|
+
border-radius: var(--radius-sm);
|
|
157
|
+
background: var(--color-paper, var(--color-cream));
|
|
158
|
+
color: var(--color-ink);
|
|
159
|
+
font-family: var(--font-body);
|
|
160
|
+
cursor: pointer;
|
|
161
|
+
transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
162
|
+
}
|
|
163
|
+
.availability-day-name {
|
|
164
|
+
font-size: 0.7rem;
|
|
165
|
+
text-transform: uppercase;
|
|
166
|
+
letter-spacing: 0.04em;
|
|
167
|
+
color: var(--color-mute);
|
|
168
|
+
}
|
|
169
|
+
.availability-day-num {
|
|
170
|
+
font-size: 1rem;
|
|
171
|
+
font-weight: 600;
|
|
172
|
+
}
|
|
173
|
+
.availability-day--has-slots:hover {
|
|
174
|
+
border-color: var(--color-accent);
|
|
175
|
+
}
|
|
176
|
+
.availability-day--empty {
|
|
177
|
+
opacity: 0.4;
|
|
178
|
+
cursor: not-allowed;
|
|
179
|
+
}
|
|
180
|
+
.availability-day--selected {
|
|
181
|
+
background: var(--color-accent);
|
|
182
|
+
border-color: var(--color-accent);
|
|
183
|
+
color: var(--color-paper);
|
|
184
|
+
}
|
|
185
|
+
.availability-day--selected .availability-day-name {
|
|
186
|
+
color: var(--color-paper);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.availability-slots {
|
|
190
|
+
display: grid;
|
|
191
|
+
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
|
192
|
+
gap: var(--spacing-sm);
|
|
193
|
+
}
|
|
194
|
+
.availability-loading,
|
|
195
|
+
.availability-empty,
|
|
196
|
+
.availability-error {
|
|
197
|
+
font-family: var(--font-body);
|
|
198
|
+
font-size: 0.9375rem;
|
|
199
|
+
color: var(--color-ink-soft, var(--color-mute));
|
|
200
|
+
padding: var(--spacing-lg) 0;
|
|
201
|
+
text-align: center;
|
|
202
|
+
}
|
|
203
|
+
.availability-empty {
|
|
204
|
+
display: flex;
|
|
205
|
+
flex-direction: column;
|
|
206
|
+
align-items: center;
|
|
207
|
+
gap: var(--spacing-md);
|
|
208
|
+
}
|
|
209
|
+
.availability-error {
|
|
210
|
+
color: var(--color-error, var(--color-accent));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/* ── Time-slot buttons ──────────────────────────────────────────────────────── */
|
|
214
|
+
.time-slot {
|
|
215
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
216
|
+
border: 1px solid var(--color-rule);
|
|
217
|
+
border-radius: var(--radius-sm);
|
|
218
|
+
background: var(--color-paper, var(--color-cream));
|
|
219
|
+
color: var(--color-ink);
|
|
220
|
+
font-family: var(--font-body);
|
|
221
|
+
font-size: 0.875rem;
|
|
222
|
+
text-align: center;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
225
|
+
}
|
|
226
|
+
.time-slot-time {
|
|
227
|
+
font-weight: 500;
|
|
228
|
+
}
|
|
229
|
+
.time-slot--available {
|
|
230
|
+
border-color: var(--color-ink);
|
|
231
|
+
}
|
|
232
|
+
.time-slot--available:hover {
|
|
233
|
+
background: var(--color-accent);
|
|
234
|
+
border-color: var(--color-accent);
|
|
235
|
+
color: var(--color-paper);
|
|
236
|
+
}
|
|
237
|
+
.time-slot--selected {
|
|
238
|
+
background: var(--color-accent);
|
|
239
|
+
border-color: var(--color-accent);
|
|
240
|
+
color: var(--color-paper);
|
|
241
|
+
font-weight: 600;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/* ── Booking form (schema-driven) ───────────────────────────────────────────── */
|
|
245
|
+
.booking-form {
|
|
246
|
+
display: flex;
|
|
247
|
+
flex-direction: column;
|
|
248
|
+
gap: var(--spacing-lg);
|
|
249
|
+
padding: var(--spacing-xl);
|
|
250
|
+
border: 1px solid var(--color-rule);
|
|
251
|
+
border-radius: var(--radius-md);
|
|
252
|
+
background: var(--color-paper-warm, var(--color-paper, var(--color-cream)));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.booking-form-summary {
|
|
256
|
+
display: flex;
|
|
257
|
+
flex-direction: column;
|
|
258
|
+
gap: var(--spacing-xs);
|
|
259
|
+
padding-bottom: var(--spacing-md);
|
|
260
|
+
border-bottom: 1px solid var(--color-rule);
|
|
261
|
+
}
|
|
262
|
+
.booking-form-summary strong {
|
|
263
|
+
font-family: var(--font-display);
|
|
264
|
+
font-size: 1.125rem;
|
|
265
|
+
color: var(--color-ink);
|
|
266
|
+
}
|
|
267
|
+
.booking-form-summary span {
|
|
268
|
+
font-family: var(--font-body);
|
|
269
|
+
font-size: 0.9375rem;
|
|
270
|
+
color: var(--color-ink-soft, var(--color-mute));
|
|
271
|
+
}
|
|
272
|
+
.booking-form-change {
|
|
273
|
+
align-self: flex-start;
|
|
274
|
+
margin-top: var(--spacing-xs);
|
|
275
|
+
background: none;
|
|
276
|
+
border: none;
|
|
277
|
+
padding: 0;
|
|
278
|
+
font-family: var(--font-body);
|
|
279
|
+
font-size: 0.8125rem;
|
|
280
|
+
color: var(--color-mute);
|
|
281
|
+
text-decoration: underline;
|
|
282
|
+
cursor: pointer;
|
|
283
|
+
}
|
|
284
|
+
.booking-form-change:hover {
|
|
285
|
+
color: var(--color-ink);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.booking-field {
|
|
289
|
+
display: flex;
|
|
290
|
+
flex-direction: column;
|
|
291
|
+
gap: var(--spacing-xs);
|
|
292
|
+
}
|
|
293
|
+
.booking-label {
|
|
294
|
+
font-family: var(--font-display);
|
|
295
|
+
font-size: 0.75rem;
|
|
296
|
+
text-transform: uppercase;
|
|
297
|
+
letter-spacing: 0.05em;
|
|
298
|
+
color: var(--color-ink-soft, var(--color-mute));
|
|
299
|
+
}
|
|
300
|
+
.booking-required {
|
|
301
|
+
color: var(--color-accent);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.booking-input {
|
|
305
|
+
width: 100%;
|
|
306
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
307
|
+
font-family: var(--font-body);
|
|
308
|
+
font-size: 0.9375rem;
|
|
309
|
+
border: 1px solid var(--color-rule);
|
|
310
|
+
border-radius: var(--radius-sm);
|
|
311
|
+
background: var(--color-paper, var(--color-cream));
|
|
312
|
+
color: var(--color-ink);
|
|
313
|
+
transition: border-color 150ms ease;
|
|
314
|
+
box-sizing: border-box;
|
|
315
|
+
}
|
|
316
|
+
.booking-input:focus {
|
|
317
|
+
outline: none;
|
|
318
|
+
border-color: var(--color-accent);
|
|
319
|
+
}
|
|
320
|
+
textarea.booking-input {
|
|
321
|
+
resize: vertical;
|
|
322
|
+
min-height: 6rem;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.booking-error {
|
|
326
|
+
font-family: var(--font-body);
|
|
327
|
+
font-size: 0.875rem;
|
|
328
|
+
color: var(--color-error, var(--color-accent));
|
|
329
|
+
padding: var(--spacing-sm) var(--spacing-md);
|
|
330
|
+
border: 1px solid var(--color-error, var(--color-accent));
|
|
331
|
+
border-radius: var(--radius-sm);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.booking-cta {
|
|
335
|
+
display: inline-flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: center;
|
|
338
|
+
width: 100%;
|
|
339
|
+
padding: var(--spacing-md) var(--spacing-xl);
|
|
340
|
+
font-family: var(--font-display);
|
|
341
|
+
font-size: 0.9375rem;
|
|
342
|
+
font-weight: 500;
|
|
343
|
+
background: var(--color-accent);
|
|
344
|
+
color: var(--color-paper);
|
|
345
|
+
border: 1px solid var(--color-accent);
|
|
346
|
+
border-radius: var(--radius-sm);
|
|
347
|
+
cursor: pointer;
|
|
348
|
+
transition: opacity 150ms ease;
|
|
349
|
+
}
|
|
350
|
+
.booking-cta:hover:not(:disabled) {
|
|
351
|
+
opacity: 0.9;
|
|
352
|
+
}
|
|
353
|
+
.booking-cta:disabled {
|
|
354
|
+
opacity: 0.5;
|
|
355
|
+
cursor: not-allowed;
|
|
356
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
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 { forms } from "@wix/forms";
|
|
9
|
+
import { auth } from "@wix/essentials";
|
|
10
|
+
|
|
11
|
+
const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
12
|
+
const { slug } = Astro.params;
|
|
13
|
+
|
|
14
|
+
let service: any = null;
|
|
15
|
+
try {
|
|
16
|
+
// Ambient SSR via @wix/essentials. Single service by slug: the .eq() builder
|
|
17
|
+
// chain on mainSlug.name + appId (the form the @wix/bookings SDK expects).
|
|
18
|
+
const result = await auth
|
|
19
|
+
.elevate(services.queryServices)({ conditionalFields: ["STAFF_MEMBER_DETAILS"] })
|
|
20
|
+
.eq("mainSlug.name", slug)
|
|
21
|
+
.eq("appId", BOOKING_APP_ID)
|
|
22
|
+
.limit(1)
|
|
23
|
+
.find();
|
|
24
|
+
service = result.items?.[0] ?? null;
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error(`[services/${slug}] detail query failed:`, err);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!service) return Astro.redirect("/services", 302);
|
|
30
|
+
|
|
31
|
+
// Booking-form SCHEMA — the form belongs to the service (service.form._id) in the
|
|
32
|
+
// bookings namespace. Read it server-side and pass the field list to the island.
|
|
33
|
+
// Same renderer the forms vertical uses (references/astro/forms/CONTACT_FORM.md):
|
|
34
|
+
// keep the field's `target` — that is the key createBooking expects in formSubmission.
|
|
35
|
+
let formFields: any[] = [];
|
|
36
|
+
try {
|
|
37
|
+
const formId = service.form?._id;
|
|
38
|
+
if (formId) {
|
|
39
|
+
const res: any = await auth.elevate(forms.getForm)(formId);
|
|
40
|
+
const form = res?.form ?? res;
|
|
41
|
+
// Only render field types we can submit as a simple value. Skip complex,
|
|
42
|
+
// object-valued fields (e.g. a multi-line ADDRESS) — they carry no string
|
|
43
|
+
// `componentType`, and sending a string for them fails createBooking with
|
|
44
|
+
// "must be object". The booking enforces only the contact basics
|
|
45
|
+
// (first_name/last_name/email), so skipping optional complex fields is safe.
|
|
46
|
+
const RENDERABLE = ["TEXT_INPUT", "PHONE_INPUT", "DROPDOWN"];
|
|
47
|
+
formFields = (form?.formFields ?? [])
|
|
48
|
+
.filter(
|
|
49
|
+
(f: any) =>
|
|
50
|
+
f.fieldType === "INPUT" &&
|
|
51
|
+
!f.hidden &&
|
|
52
|
+
RENDERABLE.includes(f.inputOptions?.stringOptions?.componentType),
|
|
53
|
+
)
|
|
54
|
+
.map((f: any) => {
|
|
55
|
+
const opts = f.inputOptions;
|
|
56
|
+
const str = opts?.stringOptions;
|
|
57
|
+
const label =
|
|
58
|
+
str?.textInputOptions?.label ??
|
|
59
|
+
str?.dropdownOptions?.label ??
|
|
60
|
+
str?.phoneInputOptions?.label ??
|
|
61
|
+
"";
|
|
62
|
+
return {
|
|
63
|
+
label,
|
|
64
|
+
target: opts?.target ?? "",
|
|
65
|
+
required: opts?.required ?? false,
|
|
66
|
+
componentType: str?.componentType ?? "TEXT_INPUT",
|
|
67
|
+
identifier: f.identifier ?? "",
|
|
68
|
+
options: str?.dropdownOptions?.options?.map((o: any) => ({
|
|
69
|
+
value: o.value ?? o.label ?? "",
|
|
70
|
+
label: o.label ?? o.value ?? "",
|
|
71
|
+
})),
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(`[services/${slug}] booking form schema failed:`, err);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const formatPrice = (p: any): string | undefined => {
|
|
80
|
+
if (!p || p.rateType === "NO_FEE") return undefined;
|
|
81
|
+
if (p.rateType === "FIXED" && p.fixed?.price) {
|
|
82
|
+
const { value, currency } = p.fixed.price;
|
|
83
|
+
if (!value) return undefined;
|
|
84
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency: currency ?? "USD" }).format(Number(value));
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
};
|
|
88
|
+
const price = formatPrice(service.payment);
|
|
89
|
+
const duration = service.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
|
|
90
|
+
const imageUrl = service.media?.mainMedia?.image?.url ?? service.media?.items?.[0]?.image?.url;
|
|
91
|
+
const serviceName = service.name ?? "Service";
|
|
92
|
+
const seoTags = service.seoData?.tags ?? [];
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
<!--
|
|
96
|
+
Width: use `container-reading` (max-width: var(--container-prose) + auto margins),
|
|
97
|
+
NOT a bare `max-w-<size>` — in this Tailwind v4 setup an un-emitted max-w-<size>
|
|
98
|
+
key falls back to the spacing scale (e.g. max-w-4xl → ~7rem, one word per line).
|
|
99
|
+
-->
|
|
100
|
+
<Layout title={serviceName} description={service.tagLine ?? service.description}>
|
|
101
|
+
<SeoTags tags={seoTags} slot="head" />
|
|
102
|
+
<section class="container-reading px-lg py-4xl">
|
|
103
|
+
<a href="/services" class="inline-block text-sm text-mute hover:text-ink mb-xl">← All services</a>
|
|
104
|
+
|
|
105
|
+
<!-- Optional banner image — only when the service has media (image-less is the
|
|
106
|
+
common seed case; rendering a fixed placeholder collapses the layout). -->
|
|
107
|
+
{imageUrl && (
|
|
108
|
+
<img src={imageUrl} alt={serviceName} class="w-full h-64 object-cover rounded-lg mb-xl" />
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<div class="flex flex-col gap-md mb-3xl">
|
|
112
|
+
<hr class="editorial-rule w-16 ml-0" />
|
|
113
|
+
<h1 class="font-display text-4xl md:text-5xl leading-tight">{serviceName}</h1>
|
|
114
|
+
{service.tagLine && <p class="text-lg text-mute leading-relaxed max-w-prose">{service.tagLine}</p>}
|
|
115
|
+
<div class="flex items-center gap-md text-sm text-mute">
|
|
116
|
+
<span>{duration} min</span>
|
|
117
|
+
{price && <span class="text-ink font-medium">{price}</span>}
|
|
118
|
+
</div>
|
|
119
|
+
{service.description && <p class="text-ink leading-relaxed max-w-prose">{service.description}</p>}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<section class="border-t border-rule pt-2xl">
|
|
123
|
+
<h2 class="font-display text-3xl leading-tight mb-xl">Book this service</h2>
|
|
124
|
+
<ServiceBookingFlow
|
|
125
|
+
service={service}
|
|
126
|
+
serviceName={serviceName}
|
|
127
|
+
serviceType={service.type ?? "APPOINTMENT"}
|
|
128
|
+
fields={formFields}
|
|
129
|
+
client:only="react"
|
|
130
|
+
/>
|
|
131
|
+
</section>
|
|
132
|
+
</section>
|
|
133
|
+
</Layout>
|
|
@@ -0,0 +1,73 @@
|
|
|
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
|
+
// Wix Bookings app id — the base filter must scope queryServices to it.
|
|
11
|
+
const BOOKING_APP_ID = "13d21c63-b5ec-5912-8397-c3a5ddb27a97";
|
|
12
|
+
|
|
13
|
+
let serviceList: any[] = [];
|
|
14
|
+
try {
|
|
15
|
+
// Ambient @wix/astro SSR client via @wix/essentials (same pattern as CMS pages).
|
|
16
|
+
// Do NOT build createClient({ auth: OAuthStrategy({ clientId: import.meta.env.* }) }) —
|
|
17
|
+
// that env var is client-only and undefined at server render → 500.
|
|
18
|
+
// Pass the request object (filter must include appId) + conditionalFields, then .find().
|
|
19
|
+
const result = await auth
|
|
20
|
+
.elevate(services.queryServices)({
|
|
21
|
+
query: { filter: { appId: BOOKING_APP_ID }, paging: { limit: 100 } },
|
|
22
|
+
conditionalFields: ["STAFF_MEMBER_DETAILS"],
|
|
23
|
+
})
|
|
24
|
+
.find();
|
|
25
|
+
serviceList = (result.items ?? []).filter((s: any) => !s.hidden);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error("[services] listing query failed:", err);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const getSlug = (s: any): string => s.mainSlug?.name ?? s.supportedSlugs?.[0]?.name ?? s._id;
|
|
31
|
+
const formatPrice = (s: any): string | undefined => {
|
|
32
|
+
const p = s.payment;
|
|
33
|
+
if (!p || p.rateType === "NO_FEE") return undefined;
|
|
34
|
+
if (p.rateType === "FIXED" && p.fixed?.price) {
|
|
35
|
+
const { value, currency } = p.fixed.price;
|
|
36
|
+
if (!value) return undefined;
|
|
37
|
+
return new Intl.NumberFormat("en-US", { style: "currency", currency: currency ?? "USD" }).format(Number(value));
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
};
|
|
41
|
+
const getDuration = (s: any): number => s.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
<Layout title="Services">
|
|
45
|
+
<section class="max-w-6xl mx-auto px-lg py-4xl">
|
|
46
|
+
<header class="flex flex-col gap-md max-w-prose mb-3xl">
|
|
47
|
+
<hr class="editorial-rule w-16 ml-0" />
|
|
48
|
+
<h1 class="font-display text-4xl md:text-5xl leading-tight">Our Services</h1>
|
|
49
|
+
<!-- Optional brand tagline -->
|
|
50
|
+
</header>
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
serviceList.length === 0 ? (
|
|
54
|
+
<p class="text-mute">Services coming soon — check back shortly.</p>
|
|
55
|
+
) : (
|
|
56
|
+
<div class="service-grid">
|
|
57
|
+
{serviceList.map((s) => (
|
|
58
|
+
<ServiceCard
|
|
59
|
+
id={s._id}
|
|
60
|
+
slug={getSlug(s)}
|
|
61
|
+
name={s.name ?? "Untitled"}
|
|
62
|
+
tagLine={s.tagLine ?? s.description}
|
|
63
|
+
durationMinutes={getDuration(s)}
|
|
64
|
+
price={formatPrice(s)}
|
|
65
|
+
imageUrl={s.media?.mainMedia?.image?.url ?? s.media?.items?.[0]?.image?.url}
|
|
66
|
+
type={s.type ?? "APPOINTMENT"}
|
|
67
|
+
/>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
</section>
|
|
73
|
+
</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
|
|
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 (
|
|
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
|
|
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.
|
|
@@ -120,9 +120,8 @@ Use these node types to build rich, varied content:
|
|
|
120
120
|
- LIST_ITEM nodes contain PARAGRAPH nodes (not TEXT directly)
|
|
121
121
|
- Mix at least 3 different node types per post for visual variety
|
|
122
122
|
- Create all 3 posts in sequence (one `curl` call per post)
|
|
123
|
-
- **Best practice:** include all fields (title, content, media) in the initial creation call to avoid needing a re-publish
|
|
124
123
|
|
|
125
|
-
> **Re-publish after PATCH:** If you update a published post (e.g.,
|
|
124
|
+
> **Re-publish after PATCH:** If you update a published post (e.g., the image agent adds a cover image via PATCH after creation), it becomes `hasUnpublishedChanges: true`. The agent that PATCHes must call `POST /blog/v3/draft-posts/{draftPostId}/publish` to re-publish. (This seed scope does not attach media — see Step 3.)
|
|
126
125
|
|
|
127
126
|
## Step 4: Verify
|
|
128
127
|
|
|
@@ -139,13 +138,24 @@ body: {
|
|
|
139
138
|
|
|
140
139
|
Confirm 3 posts are returned. Report post titles to the user.
|
|
141
140
|
|
|
142
|
-
|
|
141
|
+
## Step 5: Return Results
|
|
143
142
|
|
|
144
|
-
|
|
143
|
+
Emit a structured JSON block at the end of your completion message per `../shared/RETURN_CONTRACT.md`. Do **not** write a sidecar file. Include the created posts' `id` and `title` so the image agent can attach cover images by ID:
|
|
145
144
|
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
145
|
+
```json
|
|
146
|
+
{
|
|
147
|
+
"status": "complete",
|
|
148
|
+
"phase": "blog-seed",
|
|
149
|
+
"scope": "seed",
|
|
150
|
+
"summary": "Created and published {n} on-brand blog posts",
|
|
151
|
+
"data": {
|
|
152
|
+
"blogPosts": [
|
|
153
|
+
{ "id": "<uuid>", "title": "<title>" }
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
"files": [],
|
|
157
|
+
"errors": []
|
|
158
|
+
}
|
|
151
159
|
```
|
|
160
|
+
|
|
161
|
+
The JSON block MUST be the last content in your message.
|
|
@@ -5,7 +5,7 @@ description: "Implements blog vertical via Wix Blog app — @wix/blog SDK, @wix/
|
|
|
5
5
|
|
|
6
6
|
# Blog Implementer
|
|
7
7
|
|
|
8
|
-
Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, prompt-inlined inputs (
|
|
8
|
+
Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routing, REST auth + doc lookups, prompt-inlined inputs (read only your `.wix/seeded.json` slice), return contract, style conventions, and common failure modes.
|
|
9
9
|
|
|
10
10
|
## Scope routing
|
|
11
11
|
|
|
@@ -13,13 +13,13 @@ Extends `references/shared/IMPLEMENTER.md`. Read that file first for phase routi
|
|
|
13
13
|
|-------|-------|-----------|
|
|
14
14
|
| `seed` | Seed (post seeding via REST with Ricos content) | `./BLOG_CONTENT.md` |
|
|
15
15
|
| `components` | Components (blog service module, RicosViewer, consts, astro.config sitemap) | `../astro/blog/BLOG_SETUP.md` |
|
|
16
|
-
| `pages` | Pages (listing, detail, RSS, BlogPost layout,
|
|
16
|
+
| `pages` | Pages (listing, detail, RSS, BlogPost layout — all under `src/pages/blog/*`; no shared-shell patch, so blog merges into one private build agent) | `../astro/blog/BLOG_PAGES.md` |
|
|
17
17
|
|
|
18
18
|
Cover images are generated by the shared image agent (`image-phase-2-entity` scope) AFTER `seed` completes — not by this agent.
|
|
19
19
|
|
|
20
20
|
## Files this vertical creates / contributes
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
The `components` and `pages` scopes enumerate their own file lists — see `../astro/blog/BLOG_SETUP.md` (components) and `../astro/blog/BLOG_PAGES.md` (pages).
|
|
23
23
|
|
|
24
24
|
## Blog-specific failure modes
|
|
25
25
|
|