@wix/agent-skills 1.13.0 → 1.15.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 +4 -1
- package/package.json +1 -1
- package/skills/README.md +23 -0
- package/skills/wix-app/SKILL.md +10 -2
- package/skills/wix-app/references/AUTO_PATTERNS_DASHBOARD.md +326 -0
- package/skills/wix-app/references/CODE_QUALITY.md +1 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +124 -115
- package/skills/wix-app/references/DASHBOARD_PAGE.md +3 -0
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +23 -9
- package/skills/wix-app/references/SITE_PLUGIN.md +1 -1
- package/skills/wix-app/references/auto-patterns-dashboard/action-cell.md +115 -0
- package/skills/wix-app/references/auto-patterns-dashboard/app-config-structure.md +264 -0
- package/skills/wix-app/references/auto-patterns-dashboard/app-context.md +37 -0
- package/skills/wix-app/references/auto-patterns-dashboard/bulk-actions.md +86 -0
- package/skills/wix-app/references/auto-patterns-dashboard/collection-page-actions.md +119 -0
- package/skills/wix-app/references/auto-patterns-dashboard/collection-page.md +103 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-actions-override.md +76 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-columns-override.md +82 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-components-override.md +89 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-header-override.md +124 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-sections-override.md +91 -0
- package/skills/wix-app/references/auto-patterns-dashboard/custom-slots-override.md +71 -0
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page-actions.md +70 -0
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page-view-actions.md +73 -0
- package/skills/wix-app/references/auto-patterns-dashboard/entity-page.md +98 -0
- package/skills/wix-app/references/auto-patterns-dashboard/example-patterns.json +278 -0
- package/skills/wix-app/references/auto-patterns-dashboard/pages-configuration.md +94 -0
- package/skills/wix-app/references/auto-patterns-dashboard/resolved-action.md +43 -0
- package/skills/wix-app/references/auto-patterns-dashboard/sdk-utilities.md +66 -0
- package/skills/wix-app/references/auto-patterns-dashboard/views.md +76 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +1 -14
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +54 -29
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +2 -2
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +17 -105
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +127 -0
- package/skills/wix-app/references/editor-react-component/PARTS.md +2 -1
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +15 -15
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +25 -53
- package/skills/wix-app/scripts/generate-auto-patterns.js +510 -0
- package/skills/wix-auth/SKILL.md +12 -0
- package/skills/wix-auth/references/device-flow.md +140 -0
- package/skills/wix-docs/SKILL.md +188 -0
- package/skills/wix-docs/references/API_SPEC_SEARCH.md +213 -0
- package/skills/wix-docs/references/EXTRACTING.md +120 -0
- package/skills/wix-headless/SKILL.md +84 -130
- package/skills/wix-headless/entry/bootstrap.mjs +93 -0
- package/skills/wix-headless/entry/skill.md +78 -0
- package/skills/wix-headless/references/AI_FEATURES.md +178 -0
- package/skills/wix-headless/references/CAPABILITIES.md +84 -0
- package/skills/wix-headless/references/DISCOVERY.md +34 -56
- package/skills/wix-headless/references/DOC_DISCOVERY.md +37 -0
- package/skills/wix-headless/references/FEEDBACK.md +118 -0
- package/skills/wix-headless/references/IMAGE_GENERATION.md +69 -0
- package/skills/wix-headless/references/SDK_HANDOFF.md +97 -0
- package/skills/wix-headless/references/SEED.md +44 -188
- package/skills/wix-headless/references/SETUP.md +32 -103
- package/skills/wix-headless/references/astro.md +98 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +196 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +236 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-bookings.md +279 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-cms.md +163 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-events.md +203 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +222 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +124 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +160 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +118 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-portfolio.md +132 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +124 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +188 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-reservations.md +173 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurants.md +161 -0
- package/skills/wix-headless/references/inline-recipes/setup-blog.md +175 -0
- package/skills/wix-headless/references/inline-recipes/setup-bookings.md +228 -0
- package/skills/wix-headless/references/inline-recipes/setup-cms.md +200 -0
- package/skills/wix-headless/references/inline-recipes/setup-events.md +196 -0
- package/skills/wix-headless/references/inline-recipes/setup-forms.md +174 -0
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +176 -0
- package/skills/wix-headless/references/inline-recipes/setup-portfolio.md +153 -0
- package/skills/wix-headless/references/inline-recipes/setup-pricing-plans.md +229 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-orders.md +161 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurant-reservations.md +117 -0
- package/skills/wix-headless/references/inline-recipes/setup-restaurants.md +152 -0
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +57 -0
- package/skills/wix-headless/references/managed/CONNECT.md +74 -0
- package/skills/wix-headless/references/managed/CREATE.md +130 -0
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +57 -0
- package/skills/wix-headless/references/non-astro.md +59 -0
- package/skills/wix-headless/references/self-managed/AUTHENTICATION.md +22 -0
- package/skills/wix-headless/references/self-managed/DEPLOYMENT.md +7 -0
- package/skills/wix-headless/references/stripe/AUTHENTICATION.md +55 -0
- package/skills/wix-headless/references/stripe/DEPLOYMENT.md +63 -0
- package/skills/wix-manage/SKILL.md +24 -1
- package/skills/wix-manage/references/analytics/query-site-analytics.md +241 -0
- package/skills/wix-manage/references/bookings/create-and-update-booking-services.md +1 -1
- package/skills/wix-manage/references/bookings/diagnose-availability-issues.md +265 -0
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +436 -0
- package/skills/wix-manage/references/marketing/generate-and-publish-marketing-plan.md +209 -0
- package/skills/wix-manage/references/rich-content/author-ricos-rich-content.md +249 -0
- package/skills/wix-manage/references/stores/create-product-with-options-catalog-v3.md +51 -0
- package/skills/wix-vibe-headless/SKILL.md +136 -0
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +128 -0
- package/skills/wix-vibe-headless/references/blog/wix-blog.js +217 -0
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +153 -0
- package/skills/wix-vibe-headless/references/bookings/wix-bookings-checkout.js +147 -0
- package/skills/wix-vibe-headless/references/bookings/wix-bookings-services.js +215 -0
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +154 -0
- package/skills/wix-vibe-headless/references/cms/wix-cms.js +186 -0
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +142 -0
- package/skills/wix-vibe-headless/references/events/wix-events-browse.js +135 -0
- package/skills/wix-vibe-headless/references/events/wix-events-registration.js +187 -0
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +211 -0
- package/skills/wix-vibe-headless/references/members/wix-members-auth.js +387 -0
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +111 -0
- package/skills/wix-vibe-headless/references/portfolio/wix-portfolio.js +194 -0
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +127 -0
- package/skills/wix-vibe-headless/references/pricing-plans/wix-pricing-plans.js +157 -0
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +138 -0
- package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-menu.js +224 -0
- package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-ordering.js +156 -0
- package/skills/wix-vibe-headless/references/restaurants/wix-restaurants-reservations.js +107 -0
- package/skills/wix-vibe-headless/references/shared/wix-client.js +186 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +155 -0
- package/skills/wix-vibe-headless/references/storefront/wix-store-cart.js +147 -0
- package/skills/wix-vibe-headless/references/storefront/wix-store-catalog.js +142 -0
- package/skills/wix-headless/references/BUILD-astro.md +0 -244
- package/skills/wix-headless/references/BUILD-own-build.md +0 -167
- package/skills/wix-headless/references/BUILD.md +0 -105
- package/skills/wix-headless/references/DESIGN_SYSTEM.md +0 -165
- package/skills/wix-headless/references/DISCOVERY-connect.md +0 -55
- package/skills/wix-headless/references/DISCOVERY-create.md +0 -238
- package/skills/wix-headless/references/PLAN-connect.md +0 -19
- package/skills/wix-headless/references/PLAN-create.md +0 -17
- package/skills/wix-headless/references/PLAN.md +0 -92
- package/skills/wix-headless/references/astro/blog/BLOG_PAGES.md +0 -300
- package/skills/wix-headless/references/astro/blog/BLOG_SETUP.md +0 -218
- package/skills/wix-headless/references/astro/bookings/COMPONENTS.md +0 -45
- package/skills/wix-headless/references/astro/bookings/SERVICES_PAGES.md +0 -77
- package/skills/wix-headless/references/astro/cms/CMS_FOUNDATIONS.md +0 -214
- package/skills/wix-headless/references/astro/cms/FAQ_KNOWLEDGE_BASE.md +0 -231
- package/skills/wix-headless/references/astro/cms/PORTFOLIO.md +0 -453
- package/skills/wix-headless/references/astro/cms/RESOURCE_LIBRARY.md +0 -354
- package/skills/wix-headless/references/astro/cms/TEAM_DIRECTORY.md +0 -299
- package/skills/wix-headless/references/astro/designer/INSTRUCTIONS.md +0 -351
- package/skills/wix-headless/references/astro/ecom/CART_PAGES.md +0 -119
- package/skills/wix-headless/references/astro/ecom/CART_WIRING.md +0 -103
- package/skills/wix-headless/references/astro/ecom/COMPONENTS_CSS.md +0 -132
- package/skills/wix-headless/references/astro/forms/CONTACT_FORM.md +0 -463
- package/skills/wix-headless/references/astro/gift-cards/COMPONENTS.md +0 -119
- package/skills/wix-headless/references/astro/gift-cards/PAGES.md +0 -129
- package/skills/wix-headless/references/astro/stores/BACK_IN_STOCK.md +0 -178
- package/skills/wix-headless/references/astro/stores/CATEGORY_PAGES.md +0 -85
- package/skills/wix-headless/references/astro/stores/COMPONENTS_CSS.md +0 -129
- package/skills/wix-headless/references/astro/stores/HOME_AND_NAV.md +0 -229
- package/skills/wix-headless/references/astro/stores/PRODUCT_PAGES.md +0 -215
- package/skills/wix-headless/references/astro/stores/SHARED_WIRING.md +0 -165
- package/skills/wix-headless/references/astro/templates/Footer.astro +0 -27
- package/skills/wix-headless/references/astro/templates/Layout.astro +0 -132
- package/skills/wix-headless/references/astro/templates/Navigation.astro +0 -38
- package/skills/wix-headless/references/astro/templates/astro.config.mjs +0 -41
- package/skills/wix-headless/references/astro/templates/bookings/AvailabilityCalendar.tsx +0 -374
- package/skills/wix-headless/references/astro/templates/bookings/BookingForm.tsx +0 -136
- package/skills/wix-headless/references/astro/templates/bookings/SeoTags.astro +0 -25
- package/skills/wix-headless/references/astro/templates/bookings/ServiceBookingFlow.tsx +0 -71
- package/skills/wix-headless/references/astro/templates/bookings/ServiceCard.astro +0 -54
- package/skills/wix-headless/references/astro/templates/bookings/booking-confirmation.astro +0 -49
- package/skills/wix-headless/references/astro/templates/bookings/bookingDriver.ts +0 -245
- package/skills/wix-headless/references/astro/templates/bookings/components-bookings.css +0 -424
- package/skills/wix-headless/references/astro/templates/bookings/services/[slug].astro +0 -176
- package/skills/wix-headless/references/astro/templates/bookings/services/index.astro +0 -181
- package/skills/wix-headless/references/astro/templates/ecom/CartBadge.tsx +0 -84
- package/skills/wix-headless/references/astro/templates/ecom/CartView.tsx +0 -543
- package/skills/wix-headless/references/astro/templates/ecom/cart.astro +0 -13
- package/skills/wix-headless/references/astro/templates/ecom/components-ecom.css +0 -302
- package/skills/wix-headless/references/astro/templates/ecom/discounts.ts +0 -71
- package/skills/wix-headless/references/astro/templates/ecom/thank-you.astro +0 -24
- package/skills/wix-headless/references/astro/templates/gift-cards/GiftCardPurchase.tsx +0 -172
- package/skills/wix-headless/references/astro/templates/gift-cards/_home-teaser-snippet.astro +0 -42
- package/skills/wix-headless/references/astro/templates/gift-cards/_nav-snippet.astro +0 -11
- package/skills/wix-headless/references/astro/templates/gift-cards/components-gift-cards.css +0 -220
- package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.astro +0 -51
- package/skills/wix-headless/references/astro/templates/gift-cards/gift-cards.ts +0 -75
- package/skills/wix-headless/references/astro/templates/global.css +0 -288
- package/skills/wix-headless/references/astro/templates/index.astro +0 -56
- package/skills/wix-headless/references/astro/templates/stores/AddToCartButton.tsx +0 -77
- package/skills/wix-headless/references/astro/templates/stores/BackInStockForm.tsx +0 -176
- package/skills/wix-headless/references/astro/templates/stores/CategoryRail.astro +0 -69
- package/skills/wix-headless/references/astro/templates/stores/ProductCard.astro +0 -79
- package/skills/wix-headless/references/astro/templates/stores/ProductPurchase.tsx +0 -397
- package/skills/wix-headless/references/astro/templates/stores/SeoTags.astro +0 -21
- package/skills/wix-headless/references/astro/templates/stores/back-in-stock.ts +0 -67
- package/skills/wix-headless/references/astro/templates/stores/categories.ts +0 -155
- package/skills/wix-headless/references/astro/templates/stores/category/[slug].astro +0 -130
- package/skills/wix-headless/references/astro/templates/stores/components-stores.css +0 -400
- package/skills/wix-headless/references/astro/templates/stores/products/[slug].astro +0 -128
- package/skills/wix-headless/references/astro/templates/stores/products/index.astro +0 -101
- package/skills/wix-headless/references/blog/BLOG_CONTENT.md +0 -161
- package/skills/wix-headless/references/blog/INSTRUCTIONS.md +0 -32
- package/skills/wix-headless/references/bookings/FLOW.md +0 -246
- package/skills/wix-headless/references/bookings/INSTRUCTIONS.md +0 -95
- package/skills/wix-headless/references/bookings/SERVICES_DATA.md +0 -355
- package/skills/wix-headless/references/cms/CMS_FOUNDATIONS.md +0 -176
- package/skills/wix-headless/references/cms/INSTRUCTIONS.md +0 -71
- package/skills/wix-headless/references/commands/known-apps.json +0 -6
- package/skills/wix-headless/references/custom/CONNECTION_PLAN.md +0 -142
- package/skills/wix-headless/references/custom/INSTRUCTIONS.md +0 -88
- package/skills/wix-headless/references/custom/blog/WIRING.md +0 -52
- package/skills/wix-headless/references/custom/bookings/WIRING.md +0 -197
- package/skills/wix-headless/references/custom/cms/WIRING.md +0 -97
- package/skills/wix-headless/references/custom/ecom/WIRING.md +0 -67
- package/skills/wix-headless/references/custom/forms/WIRING.md +0 -101
- package/skills/wix-headless/references/custom/stores/WIRING.md +0 -80
- package/skills/wix-headless/references/ecom/INSTRUCTIONS.md +0 -59
- package/skills/wix-headless/references/forms/INSTRUCTIONS.md +0 -43
- package/skills/wix-headless/references/gift-cards/INSTRUCTIONS.md +0 -51
- package/skills/wix-headless/references/images/INSTRUCTIONS.md +0 -357
- package/skills/wix-headless/references/seed-recipes.md +0 -49
- package/skills/wix-headless/references/shared/AGENTS_MD.md +0 -47
- package/skills/wix-headless/references/shared/AUTHENTICATION.md +0 -108
- package/skills/wix-headless/references/shared/BUILD_NOISE.md +0 -8
- package/skills/wix-headless/references/shared/DESIGN_MD.md +0 -84
- package/skills/wix-headless/references/shared/DOCS_SEARCH.md +0 -41
- package/skills/wix-headless/references/shared/IMAGE_GENERATION.md +0 -186
- package/skills/wix-headless/references/shared/IMPLEMENTER.md +0 -206
- package/skills/wix-headless/references/shared/PRODUCTION_SHARP_EDGES.md +0 -97
- package/skills/wix-headless/references/shared/RETURN_CONTRACT.md +0 -143
- package/skills/wix-headless/references/shared/STYLING.md +0 -141
- package/skills/wix-headless/references/stores/INSTRUCTIONS.md +0 -126
- package/skills/wix-headless/references/stores/PRODUCT_CATALOG_DATA.md +0 -252
- package/skills/wix-headless/references/verticals/_schema.json +0 -74
- package/skills/wix-headless/references/verticals/_schema.md +0 -95
- package/skills/wix-headless/references/verticals/blog.md +0 -31
- package/skills/wix-headless/references/verticals/bookings.md +0 -41
- package/skills/wix-headless/references/verticals/cms.md +0 -30
- package/skills/wix-headless/references/verticals/ecom.md +0 -25
- package/skills/wix-headless/references/verticals/forms.md +0 -26
- package/skills/wix-headless/references/verticals/gift-cards.md +0 -24
- package/skills/wix-headless/references/verticals/stores.md +0 -33
- package/skills/wix-headless/scripts/check-manifest.mjs +0 -335
- package/skills/wix-headless/scripts/compose.mjs +0 -525
- package/skills/wix-headless/scripts/emit-design-tokens.mjs +0 -157
- package/skills/wix-headless/scripts/flatten.sh +0 -61
- package/skills/wix-headless/scripts/generate-images.mjs +0 -242
- package/skills/wix-headless/scripts/patch-decorative-slots.mjs +0 -148
- package/skills/wix-headless/scripts/plan-entity-image-waves.mjs +0 -190
- package/skills/wix-headless/scripts/scaffold.sh +0 -139
- package/skills/wix-headless/scripts/seed-utilities.sh +0 -93
- package/skills/wix-headless/scripts/write-decorative-json.mjs +0 -62
- package/skills/wix-headless/shared-utilities/analytics.ts +0 -40
- package/skills/wix-headless/shared-utilities/ricos.ts +0 -179
- package/skills/wix-headless/shared-utilities/wix-image.ts +0 -72
|
@@ -1,424 +0,0 @@
|
|
|
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
|
-
* - .booking-filter-bar / .booking-filter-chip* → services/index.astro (location + category filters)
|
|
11
|
-
* - .availability-* / .time-slot* → AvailabilityCalendar.tsx (week calendar, slots, staff picker)
|
|
12
|
-
* - .booking-* → BookingForm.tsx (schema-driven form)
|
|
13
|
-
*
|
|
14
|
-
* Primary actions (a selected slot, the confirm button) use var(--color-accent)
|
|
15
|
-
* so the flow matches the site's primary-action styling.
|
|
16
|
-
*
|
|
17
|
-
* Direct var(--token) only (no @apply). Fallback chains for sometimes-published
|
|
18
|
-
* tokens, e.g. var(--color-paper-warm, var(--color-paper, var(--color-cream))).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/* ── Service grid + card ────────────────────────────────────────────────────── */
|
|
22
|
-
.service-grid {
|
|
23
|
-
display: grid;
|
|
24
|
-
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
25
|
-
gap: 2rem;
|
|
26
|
-
transition: opacity 200ms ease;
|
|
27
|
-
}
|
|
28
|
-
body[data-navigating="true"] .service-grid {
|
|
29
|
-
opacity: 0.55;
|
|
30
|
-
pointer-events: none;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.service-card {
|
|
34
|
-
position: relative;
|
|
35
|
-
display: flex;
|
|
36
|
-
flex-direction: column;
|
|
37
|
-
background: var(--color-paper, var(--color-cream));
|
|
38
|
-
border-radius: var(--radius-xl);
|
|
39
|
-
overflow: hidden;
|
|
40
|
-
text-decoration: none;
|
|
41
|
-
color: inherit;
|
|
42
|
-
transition: transform 280ms ease, box-shadow 280ms ease;
|
|
43
|
-
box-shadow: var(--shadow-card, 0 4px 16px -8px rgba(0, 0, 0, 0.15));
|
|
44
|
-
}
|
|
45
|
-
.service-card:hover {
|
|
46
|
-
transform: translateY(-4px);
|
|
47
|
-
}
|
|
48
|
-
.service-card:hover .service-card-image img {
|
|
49
|
-
transform: scale(1.04);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
.service-card-image {
|
|
53
|
-
aspect-ratio: 3 / 2;
|
|
54
|
-
overflow: hidden;
|
|
55
|
-
background: var(--color-paper-warm, var(--color-surface-secondary, var(--color-cream)));
|
|
56
|
-
}
|
|
57
|
-
.service-card-image img {
|
|
58
|
-
width: 100%;
|
|
59
|
-
height: 100%;
|
|
60
|
-
object-fit: cover;
|
|
61
|
-
transition: transform 600ms ease;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.service-card-meta {
|
|
65
|
-
display: flex;
|
|
66
|
-
flex-direction: column;
|
|
67
|
-
gap: 0.5rem;
|
|
68
|
-
padding: 1rem 1rem 1.5rem;
|
|
69
|
-
flex: 1;
|
|
70
|
-
}
|
|
71
|
-
.service-card-name {
|
|
72
|
-
margin: 0;
|
|
73
|
-
font-family: var(--font-display);
|
|
74
|
-
font-size: 1.125rem;
|
|
75
|
-
line-height: 1.25;
|
|
76
|
-
color: var(--color-ink);
|
|
77
|
-
}
|
|
78
|
-
.service-card-tagline {
|
|
79
|
-
margin: 0;
|
|
80
|
-
font-family: var(--font-body);
|
|
81
|
-
font-size: 0.875rem;
|
|
82
|
-
color: var(--color-ink-soft, var(--color-mute));
|
|
83
|
-
line-height: 1.45;
|
|
84
|
-
}
|
|
85
|
-
.service-card-price {
|
|
86
|
-
display: flex;
|
|
87
|
-
align-items: center;
|
|
88
|
-
gap: 0.75rem;
|
|
89
|
-
margin-top: auto;
|
|
90
|
-
padding-top: 0.75rem;
|
|
91
|
-
font-family: var(--font-body);
|
|
92
|
-
font-size: 0.875rem;
|
|
93
|
-
color: var(--color-mute);
|
|
94
|
-
}
|
|
95
|
-
.service-card-price span + span::before {
|
|
96
|
-
content: "·";
|
|
97
|
-
padding-right: 0.75rem;
|
|
98
|
-
color: var(--color-rule);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/* ── Catalog filters (location + category) ──────────────────────────────────── */
|
|
102
|
-
.booking-filter-bar {
|
|
103
|
-
display: flex;
|
|
104
|
-
flex-wrap: wrap;
|
|
105
|
-
gap: 0.75rem;
|
|
106
|
-
margin-bottom: 2rem;
|
|
107
|
-
}
|
|
108
|
-
.booking-filter-chip {
|
|
109
|
-
padding: 0.5rem 1rem;
|
|
110
|
-
border: 1px solid var(--color-rule);
|
|
111
|
-
border-radius: var(--radius-pill, 999px);
|
|
112
|
-
background: var(--color-paper, var(--color-cream));
|
|
113
|
-
color: var(--color-ink);
|
|
114
|
-
font-family: var(--font-body);
|
|
115
|
-
font-size: 0.875rem;
|
|
116
|
-
text-decoration: none;
|
|
117
|
-
cursor: pointer;
|
|
118
|
-
transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
119
|
-
}
|
|
120
|
-
.booking-filter-chip:hover {
|
|
121
|
-
border-color: var(--color-ink);
|
|
122
|
-
}
|
|
123
|
-
.booking-filter-chip--active {
|
|
124
|
-
background: var(--color-accent);
|
|
125
|
-
border-color: var(--color-accent);
|
|
126
|
-
color: var(--color-paper);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/* ── Availability — week calendar ───────────────────────────────────────────── */
|
|
130
|
-
.availability-calendar {
|
|
131
|
-
display: flex;
|
|
132
|
-
flex-direction: column;
|
|
133
|
-
gap: 1.5rem;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/* Filter row — holds the optional location + staff pickers side by side. */
|
|
137
|
-
.availability-filters {
|
|
138
|
-
display: flex;
|
|
139
|
-
flex-wrap: wrap;
|
|
140
|
-
gap: 1rem;
|
|
141
|
-
}
|
|
142
|
-
.availability-filters > * {
|
|
143
|
-
flex: 1 1 12rem;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/* Location / staff picker (shown only when there's >1 option). */
|
|
147
|
-
.availability-staff {
|
|
148
|
-
display: flex;
|
|
149
|
-
flex-direction: column;
|
|
150
|
-
gap: 0.5rem;
|
|
151
|
-
}
|
|
152
|
-
.availability-staff-label {
|
|
153
|
-
font-family: var(--font-display);
|
|
154
|
-
font-size: 0.75rem;
|
|
155
|
-
text-transform: uppercase;
|
|
156
|
-
letter-spacing: 0.05em;
|
|
157
|
-
color: var(--color-ink-soft, var(--color-mute));
|
|
158
|
-
}
|
|
159
|
-
.availability-staff-select {
|
|
160
|
-
width: 100%;
|
|
161
|
-
padding: 0.75rem 1rem;
|
|
162
|
-
font-family: var(--font-body);
|
|
163
|
-
font-size: 0.9375rem;
|
|
164
|
-
border: 1px solid var(--color-rule);
|
|
165
|
-
border-radius: var(--radius-sm);
|
|
166
|
-
background: var(--color-paper, var(--color-cream));
|
|
167
|
-
color: var(--color-ink);
|
|
168
|
-
box-sizing: border-box;
|
|
169
|
-
}
|
|
170
|
-
.availability-staff-select:focus {
|
|
171
|
-
outline: none;
|
|
172
|
-
border-color: var(--color-accent);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.availability-week-nav {
|
|
176
|
-
display: flex;
|
|
177
|
-
align-items: center;
|
|
178
|
-
justify-content: space-between;
|
|
179
|
-
gap: 1rem;
|
|
180
|
-
padding: 0.75rem 0;
|
|
181
|
-
border-bottom: 1px solid var(--color-rule);
|
|
182
|
-
}
|
|
183
|
-
.availability-week-label {
|
|
184
|
-
font-family: var(--font-display);
|
|
185
|
-
font-size: 1rem;
|
|
186
|
-
color: var(--color-ink);
|
|
187
|
-
flex: 1;
|
|
188
|
-
text-align: center;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.availability-nav-btn {
|
|
192
|
-
padding: 0.75rem 1rem;
|
|
193
|
-
background: transparent;
|
|
194
|
-
border: 1px solid var(--color-rule);
|
|
195
|
-
border-radius: var(--radius-sm);
|
|
196
|
-
color: var(--color-ink);
|
|
197
|
-
font-family: var(--font-body);
|
|
198
|
-
font-size: 0.875rem;
|
|
199
|
-
cursor: pointer;
|
|
200
|
-
transition: background-color 150ms ease, border-color 150ms ease;
|
|
201
|
-
}
|
|
202
|
-
.availability-nav-btn:hover:not(:disabled) {
|
|
203
|
-
background: var(--color-paper-warm, var(--color-cream));
|
|
204
|
-
border-color: var(--color-ink);
|
|
205
|
-
}
|
|
206
|
-
.availability-nav-btn:disabled {
|
|
207
|
-
opacity: 0.35;
|
|
208
|
-
cursor: not-allowed;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/* The 7-day strip. */
|
|
212
|
-
.availability-day-strip {
|
|
213
|
-
display: grid;
|
|
214
|
-
grid-template-columns: repeat(7, 1fr);
|
|
215
|
-
gap: 0.75rem;
|
|
216
|
-
}
|
|
217
|
-
.availability-day {
|
|
218
|
-
display: flex;
|
|
219
|
-
flex-direction: column;
|
|
220
|
-
align-items: center;
|
|
221
|
-
gap: 2px;
|
|
222
|
-
padding: 0.75rem 0;
|
|
223
|
-
border: 1px solid var(--color-rule);
|
|
224
|
-
border-radius: var(--radius-sm);
|
|
225
|
-
background: var(--color-paper, var(--color-cream));
|
|
226
|
-
color: var(--color-ink);
|
|
227
|
-
font-family: var(--font-body);
|
|
228
|
-
cursor: pointer;
|
|
229
|
-
transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
230
|
-
}
|
|
231
|
-
.availability-day-name {
|
|
232
|
-
font-size: 0.7rem;
|
|
233
|
-
text-transform: uppercase;
|
|
234
|
-
letter-spacing: 0.04em;
|
|
235
|
-
color: var(--color-mute);
|
|
236
|
-
}
|
|
237
|
-
.availability-day-num {
|
|
238
|
-
font-size: 1rem;
|
|
239
|
-
font-weight: 600;
|
|
240
|
-
}
|
|
241
|
-
.availability-day--has-slots:hover {
|
|
242
|
-
border-color: var(--color-accent);
|
|
243
|
-
}
|
|
244
|
-
.availability-day--empty {
|
|
245
|
-
opacity: 0.4;
|
|
246
|
-
cursor: not-allowed;
|
|
247
|
-
}
|
|
248
|
-
.availability-day--selected {
|
|
249
|
-
background: var(--color-accent);
|
|
250
|
-
border-color: var(--color-accent);
|
|
251
|
-
color: var(--color-paper);
|
|
252
|
-
}
|
|
253
|
-
.availability-day--selected .availability-day-name {
|
|
254
|
-
color: var(--color-paper);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.availability-slots {
|
|
258
|
-
display: grid;
|
|
259
|
-
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
|
260
|
-
gap: 0.75rem;
|
|
261
|
-
}
|
|
262
|
-
.availability-loading,
|
|
263
|
-
.availability-empty,
|
|
264
|
-
.availability-error {
|
|
265
|
-
font-family: var(--font-body);
|
|
266
|
-
font-size: 0.9375rem;
|
|
267
|
-
color: var(--color-ink-soft, var(--color-mute));
|
|
268
|
-
padding: 1.5rem 0;
|
|
269
|
-
text-align: center;
|
|
270
|
-
}
|
|
271
|
-
.availability-empty {
|
|
272
|
-
display: flex;
|
|
273
|
-
flex-direction: column;
|
|
274
|
-
align-items: center;
|
|
275
|
-
gap: 1rem;
|
|
276
|
-
}
|
|
277
|
-
.availability-error {
|
|
278
|
-
color: var(--color-error, var(--color-accent));
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
/* ── Time-slot buttons ──────────────────────────────────────────────────────── */
|
|
282
|
-
.time-slot {
|
|
283
|
-
padding: 0.75rem 1rem;
|
|
284
|
-
border: 1px solid var(--color-rule);
|
|
285
|
-
border-radius: var(--radius-sm);
|
|
286
|
-
background: var(--color-paper, var(--color-cream));
|
|
287
|
-
color: var(--color-ink);
|
|
288
|
-
font-family: var(--font-body);
|
|
289
|
-
font-size: 0.875rem;
|
|
290
|
-
text-align: center;
|
|
291
|
-
cursor: pointer;
|
|
292
|
-
transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease;
|
|
293
|
-
}
|
|
294
|
-
.time-slot-time {
|
|
295
|
-
font-weight: 500;
|
|
296
|
-
}
|
|
297
|
-
.time-slot--available {
|
|
298
|
-
border-color: var(--color-ink);
|
|
299
|
-
}
|
|
300
|
-
.time-slot--available:hover {
|
|
301
|
-
background: var(--color-accent);
|
|
302
|
-
border-color: var(--color-accent);
|
|
303
|
-
color: var(--color-paper);
|
|
304
|
-
}
|
|
305
|
-
.time-slot--selected {
|
|
306
|
-
background: var(--color-accent);
|
|
307
|
-
border-color: var(--color-accent);
|
|
308
|
-
color: var(--color-paper);
|
|
309
|
-
font-weight: 600;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
/* ── Booking form (schema-driven) ───────────────────────────────────────────── */
|
|
313
|
-
.booking-form {
|
|
314
|
-
display: flex;
|
|
315
|
-
flex-direction: column;
|
|
316
|
-
gap: 1.5rem;
|
|
317
|
-
padding: 2rem;
|
|
318
|
-
border: 1px solid var(--color-rule);
|
|
319
|
-
border-radius: var(--radius-md);
|
|
320
|
-
background: var(--color-paper-warm, var(--color-paper, var(--color-cream)));
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
.booking-form-summary {
|
|
324
|
-
display: flex;
|
|
325
|
-
flex-direction: column;
|
|
326
|
-
gap: 0.5rem;
|
|
327
|
-
padding-bottom: 1rem;
|
|
328
|
-
border-bottom: 1px solid var(--color-rule);
|
|
329
|
-
}
|
|
330
|
-
.booking-form-summary strong {
|
|
331
|
-
font-family: var(--font-display);
|
|
332
|
-
font-size: 1.125rem;
|
|
333
|
-
color: var(--color-ink);
|
|
334
|
-
}
|
|
335
|
-
.booking-form-summary span {
|
|
336
|
-
font-family: var(--font-body);
|
|
337
|
-
font-size: 0.9375rem;
|
|
338
|
-
color: var(--color-ink-soft, var(--color-mute));
|
|
339
|
-
}
|
|
340
|
-
.booking-form-change {
|
|
341
|
-
align-self: flex-start;
|
|
342
|
-
margin-top: 0.5rem;
|
|
343
|
-
background: none;
|
|
344
|
-
border: none;
|
|
345
|
-
padding: 0;
|
|
346
|
-
font-family: var(--font-body);
|
|
347
|
-
font-size: 0.8125rem;
|
|
348
|
-
color: var(--color-mute);
|
|
349
|
-
text-decoration: underline;
|
|
350
|
-
cursor: pointer;
|
|
351
|
-
}
|
|
352
|
-
.booking-form-change:hover {
|
|
353
|
-
color: var(--color-ink);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
.booking-field {
|
|
357
|
-
display: flex;
|
|
358
|
-
flex-direction: column;
|
|
359
|
-
gap: 0.5rem;
|
|
360
|
-
}
|
|
361
|
-
.booking-label {
|
|
362
|
-
font-family: var(--font-display);
|
|
363
|
-
font-size: 0.75rem;
|
|
364
|
-
text-transform: uppercase;
|
|
365
|
-
letter-spacing: 0.05em;
|
|
366
|
-
color: var(--color-ink-soft, var(--color-mute));
|
|
367
|
-
}
|
|
368
|
-
.booking-required {
|
|
369
|
-
color: var(--color-accent);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
.booking-input {
|
|
373
|
-
width: 100%;
|
|
374
|
-
padding: 0.75rem 1rem;
|
|
375
|
-
font-family: var(--font-body);
|
|
376
|
-
font-size: 0.9375rem;
|
|
377
|
-
border: 1px solid var(--color-rule);
|
|
378
|
-
border-radius: var(--radius-sm);
|
|
379
|
-
background: var(--color-paper, var(--color-cream));
|
|
380
|
-
color: var(--color-ink);
|
|
381
|
-
transition: border-color 150ms ease;
|
|
382
|
-
box-sizing: border-box;
|
|
383
|
-
}
|
|
384
|
-
.booking-input:focus {
|
|
385
|
-
outline: none;
|
|
386
|
-
border-color: var(--color-accent);
|
|
387
|
-
}
|
|
388
|
-
textarea.booking-input {
|
|
389
|
-
resize: vertical;
|
|
390
|
-
min-height: 6rem;
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
.booking-error {
|
|
394
|
-
font-family: var(--font-body);
|
|
395
|
-
font-size: 0.875rem;
|
|
396
|
-
color: var(--color-error, var(--color-accent));
|
|
397
|
-
padding: 0.75rem 1rem;
|
|
398
|
-
border: 1px solid var(--color-error, var(--color-accent));
|
|
399
|
-
border-radius: var(--radius-sm);
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.booking-cta {
|
|
403
|
-
display: inline-flex;
|
|
404
|
-
align-items: center;
|
|
405
|
-
justify-content: center;
|
|
406
|
-
width: 100%;
|
|
407
|
-
padding: 1rem 2rem;
|
|
408
|
-
font-family: var(--font-display);
|
|
409
|
-
font-size: 0.9375rem;
|
|
410
|
-
font-weight: 500;
|
|
411
|
-
background: var(--color-accent);
|
|
412
|
-
color: var(--color-paper);
|
|
413
|
-
border: 1px solid var(--color-accent);
|
|
414
|
-
border-radius: var(--radius-sm);
|
|
415
|
-
cursor: pointer;
|
|
416
|
-
transition: opacity 150ms ease;
|
|
417
|
-
}
|
|
418
|
-
.booking-cta:hover:not(:disabled) {
|
|
419
|
-
opacity: 0.9;
|
|
420
|
-
}
|
|
421
|
-
.booking-cta:disabled {
|
|
422
|
-
opacity: 0.5;
|
|
423
|
-
cursor: not-allowed;
|
|
424
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
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
|
-
// 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
|
-
|
|
20
|
-
let service: any = null;
|
|
21
|
-
try {
|
|
22
|
-
// Ambient SSR via @wix/essentials. Single service by slug: the .eq() builder
|
|
23
|
-
// chain on mainSlug.name + appId (the form the @wix/bookings SDK expects).
|
|
24
|
-
const result = await auth
|
|
25
|
-
.elevate(services.queryServices)({ conditionalFields: ["STAFF_MEMBER_DETAILS"] })
|
|
26
|
-
.eq("mainSlug.name", slug)
|
|
27
|
-
.eq("appId", BOOKING_APP_ID)
|
|
28
|
-
.limit(1)
|
|
29
|
-
.find();
|
|
30
|
-
service = result.items?.[0] ?? null;
|
|
31
|
-
} catch (err) {
|
|
32
|
-
console.error(`[services/${slug}] detail query failed:`, err);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!service) return Astro.redirect("/services", 302);
|
|
36
|
-
|
|
37
|
-
// Booking-form SCHEMA — the form belongs to the service (service.form._id) in the
|
|
38
|
-
// bookings namespace. Read it server-side and pass the field list to the island.
|
|
39
|
-
// Same renderer the forms vertical uses (references/astro/forms/CONTACT_FORM.md):
|
|
40
|
-
// keep the field's `target` — that is the key createBooking expects in formSubmission.
|
|
41
|
-
let formFields: any[] = [];
|
|
42
|
-
try {
|
|
43
|
-
const formId = service.form?._id;
|
|
44
|
-
if (formId) {
|
|
45
|
-
const res: any = await auth.elevate(forms.getForm)(formId);
|
|
46
|
-
const form = res?.form ?? res;
|
|
47
|
-
// Only render field types we can submit as a simple value. Skip complex,
|
|
48
|
-
// object-valued fields (e.g. a multi-line ADDRESS) — they carry no string
|
|
49
|
-
// `componentType`, and sending a string for them fails createBooking with
|
|
50
|
-
// "must be object". The booking enforces only the contact basics
|
|
51
|
-
// (first_name/last_name/email), so skipping optional complex fields is safe.
|
|
52
|
-
const RENDERABLE = ["TEXT_INPUT", "PHONE_INPUT", "DROPDOWN"];
|
|
53
|
-
formFields = (form?.formFields ?? [])
|
|
54
|
-
.filter(
|
|
55
|
-
(f: any) =>
|
|
56
|
-
f.fieldType === "INPUT" &&
|
|
57
|
-
!f.hidden &&
|
|
58
|
-
RENDERABLE.includes(f.inputOptions?.stringOptions?.componentType),
|
|
59
|
-
)
|
|
60
|
-
.map((f: any) => {
|
|
61
|
-
const opts = f.inputOptions;
|
|
62
|
-
const str = opts?.stringOptions;
|
|
63
|
-
const label =
|
|
64
|
-
str?.textInputOptions?.label ??
|
|
65
|
-
str?.dropdownOptions?.label ??
|
|
66
|
-
str?.phoneInputOptions?.label ??
|
|
67
|
-
"";
|
|
68
|
-
return {
|
|
69
|
-
label,
|
|
70
|
-
target: opts?.target ?? "",
|
|
71
|
-
required: opts?.required ?? false,
|
|
72
|
-
componentType: str?.componentType ?? "TEXT_INPUT",
|
|
73
|
-
identifier: f.identifier ?? "",
|
|
74
|
-
options: str?.dropdownOptions?.options?.map((o: any) => ({
|
|
75
|
-
value: o.value ?? o.label ?? "",
|
|
76
|
-
label: o.label ?? o.value ?? "",
|
|
77
|
-
})),
|
|
78
|
-
};
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
} catch (err) {
|
|
82
|
-
console.error(`[services/${slug}] booking form schema failed:`, err);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const formatPrice = (p: any): string | undefined => {
|
|
86
|
-
if (!p || p.rateType === "NO_FEE") return undefined;
|
|
87
|
-
if (p.rateType === "FIXED" && p.fixed?.price) {
|
|
88
|
-
const { value, currency } = p.fixed.price;
|
|
89
|
-
if (!value) return undefined;
|
|
90
|
-
return new Intl.NumberFormat("en-US", { style: "currency", currency: currency ?? "USD" }).format(Number(value));
|
|
91
|
-
}
|
|
92
|
-
return undefined;
|
|
93
|
-
};
|
|
94
|
-
const price = formatPrice(service.payment);
|
|
95
|
-
const duration = service.schedule?.availabilityConstraints?.sessionDurations?.[0] ?? 60;
|
|
96
|
-
const imageUrl = service.media?.mainMedia?.image?.url ?? service.media?.items?.[0]?.image?.url;
|
|
97
|
-
const serviceName = service.name ?? "Service";
|
|
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
|
-
}
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
<!--
|
|
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.
|
|
139
|
-
-->
|
|
140
|
-
<Layout title={serviceName} description={service.tagLine ?? service.description}>
|
|
141
|
-
<SeoTags tags={seoTags} slot="head" />
|
|
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>
|
|
144
|
-
|
|
145
|
-
<!-- Optional banner image — only when the service has media (image-less is the
|
|
146
|
-
common seed case; rendering a fixed placeholder collapses the layout). -->
|
|
147
|
-
{imageUrl && (
|
|
148
|
-
<img src={imageUrl} alt={serviceName} class="w-full h-64 object-cover rounded-lg mb-8" />
|
|
149
|
-
)}
|
|
150
|
-
|
|
151
|
-
<div class="flex flex-col gap-4 mb-16">
|
|
152
|
-
<hr class="editorial-rule w-16 ml-0" />
|
|
153
|
-
<h1 class="font-display text-4xl md:text-5xl leading-tight">{serviceName}</h1>
|
|
154
|
-
{service.tagLine && <p class="text-lg text-mute leading-relaxed max-w-prose">{service.tagLine}</p>}
|
|
155
|
-
<div class="flex items-center gap-4 text-sm text-mute">
|
|
156
|
-
<span>{duration} min</span>
|
|
157
|
-
{price && <span class="text-ink font-medium">{price}</span>}
|
|
158
|
-
</div>
|
|
159
|
-
{service.description && <p class="text-ink leading-relaxed max-w-prose">{service.description}</p>}
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<section class="border-t border-rule pt-12">
|
|
163
|
-
<h2 class="font-display text-3xl leading-tight mb-8">Book this service</h2>
|
|
164
|
-
<ServiceBookingFlow
|
|
165
|
-
service={service}
|
|
166
|
-
serviceName={serviceName}
|
|
167
|
-
serviceType={service.type ?? "APPOINTMENT"}
|
|
168
|
-
fields={formFields}
|
|
169
|
-
staffMembers={staffMembers}
|
|
170
|
-
locations={serviceLocations}
|
|
171
|
-
locationId={locationId}
|
|
172
|
-
client:only="react"
|
|
173
|
-
/>
|
|
174
|
-
</section>
|
|
175
|
-
</section>
|
|
176
|
-
</Layout>
|