@wix/agent-skills 1.16.3 → 1.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/wix-app/SKILL.md +115 -12
- package/skills/wix-app/references/APP_MARKET_REVIEW.md +1 -1
- package/skills/wix-app/references/BACKEND_API.md +18 -0
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +6 -0
- package/skills/wix-app/references/DASHBOARD_MODAL.md +10 -2
- package/skills/wix-app/references/DASHBOARD_PAGE.md +32 -70
- package/skills/wix-app/references/EDITOR_REACT_COMPONENT.md +145 -127
- package/skills/wix-app/references/SITE_PLUGIN.md +14 -2
- package/skills/wix-app/references/WIX_PATTERNS_DOCS.md +74 -68
- package/skills/wix-app/references/backend-event/COMMON-EVENTS.md +2 -2
- package/skills/wix-app/references/dashboard-page/COLLECTION_TOOLKIT.md +79 -0
- package/skills/wix-app/references/dashboard-page/ENTITY_PAGE_TOOLKIT.md +73 -0
- package/skills/wix-app/references/dashboard-page/PATTERNS_BUNDLE_READING.md +52 -0
- package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +184 -0
- package/skills/wix-app/references/editor-react-component/ACCESSIBILITY.md +182 -99
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +94 -49
- package/skills/wix-app/references/editor-react-component/BRANDED-COMPONENTS.md +16 -6
- package/skills/wix-app/references/editor-react-component/BROWSER-SUPPORT.md +56 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONTRACT.md +275 -0
- package/skills/wix-app/references/editor-react-component/COMPONENT-PREVIEW.md +35 -40
- package/skills/wix-app/references/editor-react-component/CSS-GUIDELINES.md +167 -445
- package/skills/wix-app/references/editor-react-component/DESIGN-STATES.md +56 -29
- package/skills/wix-app/references/editor-react-component/DIRECTIONALITY.md +53 -158
- package/skills/wix-app/references/editor-react-component/EDITOR-EXTENSION-CONFIGURATION.md +78 -0
- package/skills/wix-app/references/editor-react-component/FUNCTION-HANDLERS.md +42 -134
- package/skills/wix-app/references/editor-react-component/MANIFEST-ERRORS.md +11 -3
- package/skills/wix-app/references/editor-react-component/PARTS.md +46 -24
- package/skills/wix-app/references/editor-react-component/PROPS-VS-CSS.md +63 -179
- package/skills/wix-app/references/editor-react-component/REACT-GUIDELINES.md +79 -163
- package/skills/wix-app/references/editor-react-component/SITE-CONTEXT-HOOKS.md +36 -21
- package/skills/wix-app/references/editor-react-component/SSR.md +30 -99
- package/skills/wix-base44-connector/SKILL.md +156 -50
- package/skills/wix-base44-connector/references/CAPABILITY_MAP.md +78 -0
- package/skills/wix-base44-connector/scripts/utils.js +239 -45
- package/skills/wix-headless/entry/bootstrap.mjs +9 -2
- package/skills/wix-headless/entry/skill.md +1 -1
- package/skills/wix-headless/references/CAPABILITIES.md +9 -1
- package/skills/wix-headless/references/DESIGN.md +1 -0
- package/skills/wix-headless/references/DISCOVERY.md +2 -1
- package/skills/wix-headless/references/IMAGE_GENERATION.md +1 -0
- package/skills/wix-headless/references/SDK_HANDOFF.md +5 -2
- package/skills/wix-headless/references/SEED.md +2 -1
- package/skills/wix-headless/references/SETUP.md +1 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-blog.md +2 -2
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +68 -31
- package/skills/wix-headless/references/inline-recipes/how-to-code-forms.md +26 -10
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-astro.md +8 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-custom-login.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-members-non-astro.md +1 -1
- package/skills/wix-headless/references/inline-recipes/how-to-code-pricing-plans.md +10 -8
- package/skills/wix-headless/references/inline-recipes/how-to-code-rentals.md +254 -0
- package/skills/wix-headless/references/inline-recipes/how-to-code-restaurant-orders.md +27 -24
- package/skills/wix-headless/references/inline-recipes/setup-online-store.md +13 -0
- package/skills/wix-headless/references/inline-recipes/setup-rentals.md +254 -0
- package/skills/wix-headless/references/managed/AUTHENTICATION.md +5 -0
- package/skills/wix-headless/references/non-astro.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +166 -0
- package/skills/wix-headless-fast/entry/skill.md +99 -0
- package/skills/wix-headless-fast/install/deploy.mjs +433 -0
- package/skills/wix-headless-fast/install/fast-path.mjs +141 -0
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +137 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +137 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +113 -0
- package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +42 -0
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +123 -0
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +70 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +99 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +76 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +62 -0
- package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +99 -0
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +24 -0
- package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +16156 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +72 -0
- package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +287 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +129 -0
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +143 -0
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +119 -0
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +138 -0
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +54 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +206 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +96 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +70 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +76 -0
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +22 -0
- package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +11962 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +61 -0
- package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +302 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +179 -0
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +83 -0
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +57 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +151 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +75 -0
- package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +41 -0
- package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +11262 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +103 -0
- package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +324 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +149 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +66 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +176 -0
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +118 -0
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +117 -0
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +59 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +136 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +128 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +61 -0
- package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +12 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +80 -0
- package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +21 -0
- package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +11884 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +86 -0
- package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +276 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +150 -0
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +317 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +269 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +231 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +135 -0
- package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +10593 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +108 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +320 -0
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +63 -0
- package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +60 -0
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +155 -0
- package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +78 -0
- package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +47 -0
- package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +66 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +88 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +53 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +122 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +50 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +21 -0
- package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +42 -0
- package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +10 -0
- package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +11 -0
- package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +10329 -0
- package/skills/wix-headless-fast/references/members/seed/SEED.md +39 -0
- package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +94 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +134 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +110 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +90 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +115 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +59 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +39 -0
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +165 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +55 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +40 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +30 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +21 -0
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +29 -0
- package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +10197 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +78 -0
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +258 -0
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +133 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +58 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +126 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +37 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +39 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +75 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +45 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +30 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +40 -0
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +21 -0
- package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +11226 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +71 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +279 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +165 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +142 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +26 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +121 -0
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +55 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +43 -0
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +177 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +165 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +103 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +254 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +146 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +168 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +47 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +22 -0
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +12 -0
- package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +12560 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +81 -0
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +479 -0
- package/skills/wix-headless-fast/references/shared/CONTENT.md +22 -0
- package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +73 -0
- package/skills/wix-headless-fast/references/shared/DESIGN.md +26 -0
- package/skills/wix-headless-fast/references/shared/app/styles/global.css +52 -0
- package/skills/wix-headless-fast/references/shared/app/wix/config.ts +15 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +27 -0
- package/skills/wix-headless-fast/references/shared/app/wix/money.ts +24 -0
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +76 -0
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +46 -0
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +46 -0
- package/skills/wix-headless-fast/references/shared/seed/images.mjs +145 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +149 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +26 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +121 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductCard.tsx +100 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductDetailView.tsx +130 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ProductGrid.tsx +48 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/ShopView.tsx +66 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/VariantPicker.tsx +103 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +48 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +140 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +80 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +101 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +221 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +215 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +109 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +46 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/products/[slug].astro +56 -0
- package/skills/wix-headless-fast/references/storefront/app-astro/pages/shop.astro +23 -0
- package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +12247 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +85 -0
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +415 -0
- package/skills/wix-manage/SKILL.md +28 -2
- package/skills/wix-manage/references/bookings/booking-system-integration-gaps.md +20 -13
- package/skills/wix-manage/references/bookings/end-to-end-booking-flow.md +24 -11
- package/skills/wix-manage/references/cms/cms-ecommerce-catalog-integration.md +5 -5
- package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +141 -0
- package/skills/wix-manage/references/google-business-profile/connect-google-business-profile.md +134 -0
- package/skills/wix-manage/references/google-business-profile/manage-google-business-profile-locations.md +182 -0
- package/skills/wix-manage/references/marketing/create-and-publish-social-post.md +3 -0
- package/skills/wix-manage/references/seo/manage-seo-tags.md +315 -0
- package/skills/wix-manage/references/seo/manage-url-redirects.md +250 -0
- package/skills/wix-manage/references/sites/query-sites.md +72 -161
- package/skills/wix-manage/references/stores/create-product-catalog-v3.md +21 -6
- package/skills/wix-manage/references/stores/update-product-with-options.md +83 -0
- package/skills/wix-replatform/resources/rp-mapper/wix-business-solutions-taxonomy.md +5 -6
- package/skills/wix-vibe-headless/SKILL.md +18 -4
- package/skills/wix-vibe-headless/install/deploy.cjs +12 -4
- package/skills/wix-vibe-headless/install/pin-agents-md.cjs +2 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +16 -10
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +13 -8
- package/skills/wix-vibe-headless/platforms/base44.md +27 -62
- package/skills/wix-vibe-headless/platforms/generic.md +6 -3
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +45 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/RentalDurationPicker.jsx +67 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useRentalDuration.js +128 -0
- package/skills/wix-vibe-headless/references/bookings/app/hooks/useServiceDetail.js +5 -3
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-checkout.js +9 -8
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-rentals.js +250 -0
- package/skills/wix-vibe-headless/references/bookings/app/rest/wix-bookings-services.js +12 -5
- package/skills/wix-vibe-headless/references/bookings/seed/SEED.md +41 -0
- package/skills/wix-vibe-headless/references/bookings/seed/seed-rentals.js +267 -0
- package/skills/wix-vibe-headless/references/cms/INSTRUCTIONS.md +6 -3
- package/skills/wix-vibe-headless/references/forms/INSTRUCTIONS.md +470 -0
- package/skills/wix-vibe-headless/references/forms/app/hooks/useWixForm.js +425 -0
- package/skills/wix-vibe-headless/references/forms/app/lib/wix-form-schema-utils.js +187 -0
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms-submissions.js +238 -0
- package/skills/wix-vibe-headless/references/forms/app/rest/wix-forms.js +108 -0
- package/skills/wix-vibe-headless/references/forms/seed/SEED.md +1762 -0
- package/skills/wix-vibe-headless/references/rentals/INSTRUCTIONS.md +13 -0
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +4 -1
- package/skills/wix-vibe-headless/references/restaurants/app/components/OrderCartDrawer.jsx +23 -11
- package/skills/wix-vibe-headless/references/restaurants/app/context/OrderCartContext.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/app/rest/wix-restaurants-ordering.js +29 -31
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +152 -92
- package/skills/wix-vibe-headless/references/storefront/app/components/CartDrawer.jsx +37 -38
- package/skills/wix-vibe-headless/references/storefront/app/context/CartContext.jsx +2 -2
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductDetail.js +15 -8
- package/skills/wix-vibe-headless/references/storefront/app/lib/storeImage.js +37 -0
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-cart.js +35 -35
- package/skills/wix-vibe-headless/references/storefront/app/rest/wix-store-catalog.js +18 -6
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +30 -7
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.js +46 -10
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW-REFERENCE.md +0 -172
- package/skills/wix-app/references/editor-react-component/A11Y-REVIEW.md +0 -143
- package/skills/wix-app/references/editor-react-component/COMPONENT-API.md +0 -516
- package/skills/wix-app/references/editor-react-component/COMPONENT-CONFIGURATION.md +0 -225
- package/skills/wix-app/references/editor-react-component/EDIT-FLOW.md +0 -6
- package/skills/wix-app/references/editor-react-component/REACT-PATTERNS.md +0 -95
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductCard.jsx +0 -110
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGallery.jsx +0 -51
- package/skills/wix-vibe-headless/references/storefront/app/components/ProductGrid.jsx +0 -49
- package/skills/wix-vibe-headless/references/storefront/app/components/VariantPicker.jsx +0 -92
- package/skills/wix-vibe-headless/references/storefront/app/pages/ProductDetail.jsx +0 -139
package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// REFERENCE registration surface: branches on event.registrationType — TICKETING renders
|
|
2
|
+
// the tier picker (quantity steppers → checkout CTA), RSVP the built-in name+email form,
|
|
3
|
+
// EXTERNAL a link out, NONE/closed an honest closed state — on the @theme tokens. Correct
|
|
4
|
+
// and complete; per the skill's model you design and build your own on useEventRegistration.
|
|
5
|
+
// Mount client:only — it runs visitor-session SDK calls and redirects.
|
|
6
|
+
import { useEventRegistration } from "../../hooks/events/useEventRegistration";
|
|
7
|
+
import type { EventDetail } from "../../wix/events/types";
|
|
8
|
+
|
|
9
|
+
const input =
|
|
10
|
+
"w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none transition-shadow focus:ring-2 focus:ring-primary";
|
|
11
|
+
const cta =
|
|
12
|
+
"mt-5 rounded-full bg-primary px-8 py-3 text-sm font-semibold text-primary-foreground transition-opacity hover:opacity-90 disabled:opacity-50";
|
|
13
|
+
|
|
14
|
+
export default function EventRegistrationView({ event }: { event: EventDetail }) {
|
|
15
|
+
const {
|
|
16
|
+
tiers,
|
|
17
|
+
quantities,
|
|
18
|
+
setQuantity,
|
|
19
|
+
ticketCount,
|
|
20
|
+
canCheckout,
|
|
21
|
+
checkout,
|
|
22
|
+
rsvpValues,
|
|
23
|
+
setRsvpValue,
|
|
24
|
+
canRsvp,
|
|
25
|
+
rsvp,
|
|
26
|
+
submitting,
|
|
27
|
+
confirmed,
|
|
28
|
+
error,
|
|
29
|
+
} = useEventRegistration(event);
|
|
30
|
+
|
|
31
|
+
if (confirmed && confirmed.kind === "rsvpConfirmed") {
|
|
32
|
+
return (
|
|
33
|
+
<div className="rounded-lg border border-border bg-secondary p-8 text-center">
|
|
34
|
+
<p className="text-lg font-semibold">
|
|
35
|
+
{confirmed.status === "WAITLIST"
|
|
36
|
+
? "You're on the waitlist"
|
|
37
|
+
: confirmed.status === "NO"
|
|
38
|
+
? "Thanks for letting us know"
|
|
39
|
+
: "You're in! 🎉"}
|
|
40
|
+
</p>
|
|
41
|
+
<p className="mt-2 text-sm text-muted-foreground">
|
|
42
|
+
{confirmed.status === "WAITLIST"
|
|
43
|
+
? `${event.title} is full — we'll email you if a spot opens up.`
|
|
44
|
+
: confirmed.status === "NO"
|
|
45
|
+
? "We've recorded that you can't make it."
|
|
46
|
+
: `See you at ${event.title}${event.dateLabel ? ` — ${event.dateLabel}` : ""}. A confirmation email is on its way.`}
|
|
47
|
+
</p>
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!event.registrationOpen) {
|
|
53
|
+
return (
|
|
54
|
+
<div className="rounded-lg border border-border p-6 text-sm text-muted-foreground">
|
|
55
|
+
{event.soldOut ? "This event is sold out." : "Registration for this event is closed."}
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (event.registrationType === "EXTERNAL") {
|
|
61
|
+
return (
|
|
62
|
+
<a href={event.externalUrl} className={`${cta} inline-block no-underline`} target="_blank" rel="noreferrer">
|
|
63
|
+
Register ↗
|
|
64
|
+
</a>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (event.registrationType === "TICKETING") {
|
|
69
|
+
return (
|
|
70
|
+
<div>
|
|
71
|
+
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">Tickets</p>
|
|
72
|
+
{tiers === null ? (
|
|
73
|
+
<div className="space-y-3" aria-busy="true">
|
|
74
|
+
{Array.from({ length: 2 }, (_, i) => (
|
|
75
|
+
<div key={i} className="h-16 animate-pulse rounded-lg bg-secondary" />
|
|
76
|
+
))}
|
|
77
|
+
</div>
|
|
78
|
+
) : tiers.length === 0 ? (
|
|
79
|
+
<p className="rounded-md border border-border p-4 text-sm text-muted-foreground">
|
|
80
|
+
Tickets aren't available right now.
|
|
81
|
+
</p>
|
|
82
|
+
) : (
|
|
83
|
+
<div className="space-y-3">
|
|
84
|
+
{tiers.map((t) => {
|
|
85
|
+
const onSale = t.saleStatus === "SALE_STARTED";
|
|
86
|
+
const qty = quantities[t.id] ?? 0;
|
|
87
|
+
return (
|
|
88
|
+
<div key={t.id} className="flex items-center justify-between gap-4 rounded-lg border border-border p-4">
|
|
89
|
+
<div>
|
|
90
|
+
<p className="text-sm font-medium text-foreground">{t.name}</p>
|
|
91
|
+
{t.description && <p className="mt-0.5 text-xs text-muted-foreground">{t.description}</p>}
|
|
92
|
+
<p className="mt-1 text-sm text-foreground">{t.price}</p>
|
|
93
|
+
{!onSale && (
|
|
94
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
95
|
+
{t.saleStatus === "SALE_SCHEDULED" ? "Sale hasn't started yet" : "Sale ended"}
|
|
96
|
+
</p>
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
<div className="flex items-center gap-2">
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
aria-label={`Fewer ${t.name}`}
|
|
103
|
+
disabled={!onSale || qty === 0}
|
|
104
|
+
onClick={() => setQuantity(t.id, qty - 1)}
|
|
105
|
+
className="h-8 w-8 rounded-full border border-border text-foreground transition-colors hover:bg-secondary disabled:opacity-40"
|
|
106
|
+
>
|
|
107
|
+
−
|
|
108
|
+
</button>
|
|
109
|
+
<span className="w-6 text-center text-sm tabular-nums">{qty}</span>
|
|
110
|
+
<button
|
|
111
|
+
type="button"
|
|
112
|
+
aria-label={`More ${t.name}`}
|
|
113
|
+
disabled={!onSale || qty >= t.limitPerCheckout}
|
|
114
|
+
onClick={() => setQuantity(t.id, qty + 1)}
|
|
115
|
+
className="h-8 w-8 rounded-full border border-border text-foreground transition-colors hover:bg-secondary disabled:opacity-40"
|
|
116
|
+
>
|
|
117
|
+
+
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
);
|
|
122
|
+
})}
|
|
123
|
+
</div>
|
|
124
|
+
)}
|
|
125
|
+
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
|
|
126
|
+
<button type="button" disabled={!canCheckout || submitting} onClick={() => checkout().catch(() => {})} className={cta}>
|
|
127
|
+
{submitting ? "Reserving…" : ticketCount > 0 ? `Get ${ticketCount} ticket${ticketCount > 1 ? "s" : ""}` : "Get tickets"}
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (event.registrationType === "RSVP") {
|
|
134
|
+
return (
|
|
135
|
+
<div>
|
|
136
|
+
<p className="mb-3 text-xs font-semibold uppercase tracking-wider text-muted-foreground">RSVP</p>
|
|
137
|
+
<div className="grid max-w-md gap-3">
|
|
138
|
+
<label className="block">
|
|
139
|
+
<span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-muted-foreground">First name</span>
|
|
140
|
+
<input type="text" value={rsvpValues.firstName} onChange={(e) => setRsvpValue("firstName", e.target.value)} className={input} />
|
|
141
|
+
</label>
|
|
142
|
+
<label className="block">
|
|
143
|
+
<span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-muted-foreground">Last name</span>
|
|
144
|
+
<input type="text" value={rsvpValues.lastName} onChange={(e) => setRsvpValue("lastName", e.target.value)} className={input} />
|
|
145
|
+
</label>
|
|
146
|
+
<label className="block">
|
|
147
|
+
<span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-muted-foreground">Email</span>
|
|
148
|
+
<input type="email" value={rsvpValues.email} onChange={(e) => setRsvpValue("email", e.target.value)} className={input} />
|
|
149
|
+
</label>
|
|
150
|
+
</div>
|
|
151
|
+
{error && <p className="mt-3 text-sm text-red-600">{error}</p>}
|
|
152
|
+
<div className="flex items-center gap-3">
|
|
153
|
+
<button type="button" disabled={!canRsvp || submitting} onClick={() => rsvp(true).catch(() => {})} className={cta}>
|
|
154
|
+
{submitting ? "Sending…" : "Count me in"}
|
|
155
|
+
</button>
|
|
156
|
+
{event.rsvpResponseType === "YES_AND_NO" && (
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
disabled={!canRsvp || submitting}
|
|
160
|
+
onClick={() => rsvp(false).catch(() => {})}
|
|
161
|
+
className="mt-5 rounded-full border border-border px-6 py-3 text-sm font-medium text-foreground transition-colors hover:bg-secondary disabled:opacity-50"
|
|
162
|
+
>
|
|
163
|
+
Can't make it
|
|
164
|
+
</button>
|
|
165
|
+
)}
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return (
|
|
172
|
+
<div className="rounded-lg border border-border p-6 text-sm text-muted-foreground">
|
|
173
|
+
Registration isn't open for this event.
|
|
174
|
+
</div>
|
|
175
|
+
);
|
|
176
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// REFERENCE listing surface: category filter + events grid on the @theme tokens.
|
|
2
|
+
// Correct and complete; per the skill's model you design and build your own on useEvents.
|
|
3
|
+
import type { ComponentType, ReactNode } from "react";
|
|
4
|
+
import { useEvents } from "../../hooks/events/useEvents";
|
|
5
|
+
import type { EventSummary } from "../../wix/events/types";
|
|
6
|
+
|
|
7
|
+
export interface LinkLikeProps {
|
|
8
|
+
href: string;
|
|
9
|
+
className?: string;
|
|
10
|
+
children?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const PlainLink = ({ href, className, children }: LinkLikeProps) => (
|
|
14
|
+
<a href={href} className={className}>
|
|
15
|
+
{children}
|
|
16
|
+
</a>
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
export interface EventCardProps {
|
|
20
|
+
event: EventSummary;
|
|
21
|
+
eventHref?: (slug: string) => string;
|
|
22
|
+
LinkComponent?: ComponentType<LinkLikeProps>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function EventCard({
|
|
26
|
+
event,
|
|
27
|
+
eventHref = (slug) => `/events/${slug}`,
|
|
28
|
+
LinkComponent = PlainLink,
|
|
29
|
+
}: EventCardProps) {
|
|
30
|
+
return (
|
|
31
|
+
<LinkComponent href={eventHref(event.slug)} className="group block no-underline">
|
|
32
|
+
<div className="relative aspect-[3/2] overflow-hidden rounded-lg bg-secondary">
|
|
33
|
+
{event.imageUrl && (
|
|
34
|
+
<img
|
|
35
|
+
src={event.imageUrl}
|
|
36
|
+
alt={event.title}
|
|
37
|
+
loading="lazy"
|
|
38
|
+
className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-[1.03]"
|
|
39
|
+
/>
|
|
40
|
+
)}
|
|
41
|
+
{event.soldOut && (
|
|
42
|
+
<span className="absolute left-3 top-3 rounded-full bg-background/90 px-3 py-1 text-xs font-semibold text-foreground backdrop-blur">
|
|
43
|
+
Sold out
|
|
44
|
+
</span>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
{event.dateLabel && <p className="eyebrow mt-3">{event.dateLabel}</p>}
|
|
48
|
+
<p className="mt-1 text-sm font-medium text-foreground">{event.title}</p>
|
|
49
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
50
|
+
{event.locationType === "ONLINE" ? "Online" : event.locationName}
|
|
51
|
+
{event.priceLabel ? ` · ${event.priceLabel}` : ""}
|
|
52
|
+
</p>
|
|
53
|
+
</LinkComponent>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface EventsViewProps {
|
|
58
|
+
initialEvents?: EventSummary[];
|
|
59
|
+
emptyMessage?: string;
|
|
60
|
+
eventHref?: EventCardProps["eventHref"];
|
|
61
|
+
LinkComponent?: ComponentType<LinkLikeProps>;
|
|
62
|
+
CardComponent?: ComponentType<EventCardProps>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const pill = (active: boolean) =>
|
|
66
|
+
`rounded-full border px-4 py-1.5 text-sm font-medium transition-colors ${
|
|
67
|
+
active
|
|
68
|
+
? "border-primary bg-primary text-primary-foreground"
|
|
69
|
+
: "border-border text-foreground hover:bg-secondary"
|
|
70
|
+
}`;
|
|
71
|
+
|
|
72
|
+
export default function EventsView({
|
|
73
|
+
initialEvents,
|
|
74
|
+
emptyMessage = "No upcoming events — check back soon.",
|
|
75
|
+
eventHref,
|
|
76
|
+
LinkComponent,
|
|
77
|
+
CardComponent = EventCard,
|
|
78
|
+
}: EventsViewProps) {
|
|
79
|
+
const { events, categories, activeCategoryId, setActiveCategoryId, error } = useEvents({
|
|
80
|
+
initialEvents,
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div>
|
|
85
|
+
{categories.length > 1 && (
|
|
86
|
+
<div className="mb-8 flex flex-wrap gap-2" role="group" aria-label="Categories">
|
|
87
|
+
<button type="button" className={pill(activeCategoryId === null)} onClick={() => setActiveCategoryId(null)}>
|
|
88
|
+
All
|
|
89
|
+
</button>
|
|
90
|
+
{categories.map((c) => (
|
|
91
|
+
<button key={c.id} type="button" className={pill(activeCategoryId === c.id)} onClick={() => setActiveCategoryId(c.id)}>
|
|
92
|
+
{c.name}
|
|
93
|
+
</button>
|
|
94
|
+
))}
|
|
95
|
+
</div>
|
|
96
|
+
)}
|
|
97
|
+
{error && <p className="mb-4 text-sm text-red-600">{error}</p>}
|
|
98
|
+
{events === null ? (
|
|
99
|
+
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3" aria-busy="true">
|
|
100
|
+
{Array.from({ length: 6 }, (_, i) => (
|
|
101
|
+
<div key={i}>
|
|
102
|
+
<div className="aspect-[3/2] animate-pulse rounded-lg bg-secondary" />
|
|
103
|
+
<div className="mt-3 h-3.5 w-2/3 animate-pulse rounded bg-secondary" />
|
|
104
|
+
</div>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
) : events.length === 0 ? (
|
|
108
|
+
<p className="py-16 text-center text-muted-foreground">{emptyMessage}</p>
|
|
109
|
+
) : (
|
|
110
|
+
<div className="grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
|
|
111
|
+
{events.map((e) => (
|
|
112
|
+
<CardComponent key={e.id} event={e} eventHref={eventHref} LinkComponent={LinkComponent} />
|
|
113
|
+
))}
|
|
114
|
+
</div>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// The whole registration state machine for one event, branched on event.registrationType:
|
|
2
|
+
// TICKETING loads the tier picker and checkout() reserves → redirects to Wix's hosted
|
|
3
|
+
// checkout; RSVP is the built-in name+email form submitted in place. All correctness (the
|
|
4
|
+
// visitor-public tier read, the reservation payload, the redirect callbacks, rsvpV2) lives
|
|
5
|
+
// in the data layer — this hook orchestrates; you own how it looks.
|
|
6
|
+
import { useEffect, useMemo, useState } from "react";
|
|
7
|
+
import { fetchTicketTiers, startTicketCheckout, submitRsvp } from "../../wix/events/registration";
|
|
8
|
+
import type { EventDetail, RegistrationResult, TicketTier } from "../../wix/events/types";
|
|
9
|
+
|
|
10
|
+
export interface UseEventRegistration {
|
|
11
|
+
/** TICKETING: tiers for the picker — null while loading (skeletons), [] honest empty. Other types: []. */
|
|
12
|
+
tiers: TicketTier[] | null;
|
|
13
|
+
/** Selected quantity per tier id (0 when untouched). */
|
|
14
|
+
quantities: Record<string, number>;
|
|
15
|
+
/** Clamped to 0..limitPerCheckout; ignored for tiers not on sale (saleStatus gate). */
|
|
16
|
+
setQuantity: (tierId: string, quantity: number) => void;
|
|
17
|
+
ticketCount: number;
|
|
18
|
+
/** True when ≥ 1 ticket is selected — gate the checkout CTA on this. */
|
|
19
|
+
canCheckout: boolean;
|
|
20
|
+
/** Reserves + redirects to the Wix-hosted checkout. On "redirect" the browser is navigating. */
|
|
21
|
+
checkout: () => Promise<RegistrationResult>;
|
|
22
|
+
/** RSVP form state — the built-in fields, exactly these. */
|
|
23
|
+
rsvpValues: { firstName: string; lastName: string; email: string };
|
|
24
|
+
setRsvpValue: (field: "firstName" | "lastName" | "email", value: string) => void;
|
|
25
|
+
/** True when every RSVP field is filled — gate the RSVP CTA on this. */
|
|
26
|
+
canRsvp: boolean;
|
|
27
|
+
/** attending=false only when event.rsvpResponseType is "YES_AND_NO". */
|
|
28
|
+
rsvp: (attending?: boolean) => Promise<RegistrationResult>;
|
|
29
|
+
submitting: boolean;
|
|
30
|
+
/** Set after an RSVP completes (kind "rsvpConfirmed"; status may be "WAITLIST"). */
|
|
31
|
+
confirmed: RegistrationResult | null;
|
|
32
|
+
error: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function useEventRegistration(event: EventDetail): UseEventRegistration {
|
|
36
|
+
const ticketed = event.registrationType === "TICKETING";
|
|
37
|
+
const [tiers, setTiers] = useState<TicketTier[] | null>(ticketed ? null : []);
|
|
38
|
+
const [quantities, setQuantities] = useState<Record<string, number>>({});
|
|
39
|
+
const [rsvpValues, setRsvpValues] = useState({ firstName: "", lastName: "", email: "" });
|
|
40
|
+
const [submitting, setSubmitting] = useState(false);
|
|
41
|
+
const [confirmed, setConfirmed] = useState<RegistrationResult | null>(null);
|
|
42
|
+
const [error, setError] = useState<string | null>(null);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!ticketed) return;
|
|
46
|
+
let alive = true;
|
|
47
|
+
setTiers(null);
|
|
48
|
+
setQuantities({});
|
|
49
|
+
fetchTicketTiers(event.id)
|
|
50
|
+
.then((t) => alive && setTiers(t))
|
|
51
|
+
.catch((e) => {
|
|
52
|
+
if (!alive) return;
|
|
53
|
+
setTiers([]);
|
|
54
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
55
|
+
});
|
|
56
|
+
return () => {
|
|
57
|
+
alive = false;
|
|
58
|
+
};
|
|
59
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
60
|
+
}, [event.id, ticketed]);
|
|
61
|
+
|
|
62
|
+
const setQuantity = (tierId: string, quantity: number) => {
|
|
63
|
+
const tier = (tiers ?? []).find((t) => t.id === tierId);
|
|
64
|
+
if (!tier || tier.saleStatus !== "SALE_STARTED") return;
|
|
65
|
+
const clamped = Math.max(0, Math.min(quantity, tier.limitPerCheckout || 20));
|
|
66
|
+
setQuantities((q) => ({ ...q, [tierId]: clamped }));
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const ticketCount = useMemo(
|
|
70
|
+
() => Object.values(quantities).reduce((sum, q) => sum + q, 0),
|
|
71
|
+
[quantities],
|
|
72
|
+
);
|
|
73
|
+
const canRsvp = (["firstName", "lastName", "email"] as const).every(
|
|
74
|
+
(f) => rsvpValues[f].trim().length > 0,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
async function run(op: () => Promise<RegistrationResult>): Promise<RegistrationResult> {
|
|
78
|
+
setSubmitting(true);
|
|
79
|
+
setError(null);
|
|
80
|
+
try {
|
|
81
|
+
const result = await op();
|
|
82
|
+
if (result.kind === "redirect") {
|
|
83
|
+
window.location.href = result.url;
|
|
84
|
+
} else {
|
|
85
|
+
setConfirmed(result);
|
|
86
|
+
}
|
|
87
|
+
return result;
|
|
88
|
+
} catch (e) {
|
|
89
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
90
|
+
throw e;
|
|
91
|
+
} finally {
|
|
92
|
+
setSubmitting(false);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
tiers,
|
|
98
|
+
quantities,
|
|
99
|
+
setQuantity,
|
|
100
|
+
ticketCount,
|
|
101
|
+
canCheckout: ticketCount > 0,
|
|
102
|
+
checkout: () =>
|
|
103
|
+
run(() =>
|
|
104
|
+
startTicketCheckout(
|
|
105
|
+
event,
|
|
106
|
+
Object.entries(quantities).map(([tierId, quantity]) => ({ tierId, quantity })),
|
|
107
|
+
),
|
|
108
|
+
),
|
|
109
|
+
rsvpValues,
|
|
110
|
+
setRsvpValue: (field, value) => setRsvpValues((v) => ({ ...v, [field]: value })),
|
|
111
|
+
canRsvp,
|
|
112
|
+
rsvp: (attending = true) => run(() => submitRsvp(event.id, rsvpValues, attending ? "YES" : "NO")),
|
|
113
|
+
submitting,
|
|
114
|
+
confirmed,
|
|
115
|
+
error,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Events listing + category filter. SSR-friendly: pass server-fetched data as `initialEvents`
|
|
2
|
+
// (Astro frontmatter / server component) and no client fetch happens; a SPA passes nothing.
|
|
3
|
+
// The category menu is DERIVED from the loaded events (the categories management API is
|
|
4
|
+
// admin-scope — visitors can't query it); filtering is client-side.
|
|
5
|
+
import { useEffect, useMemo, useState } from "react";
|
|
6
|
+
import { fetchEvents } from "../../wix/events/events";
|
|
7
|
+
import type { EventSummary } from "../../wix/events/types";
|
|
8
|
+
|
|
9
|
+
export interface UseEventsOptions {
|
|
10
|
+
initialEvents?: EventSummary[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface UseEvents {
|
|
14
|
+
/** null while the first load is in flight — render skeletons, not an empty state. */
|
|
15
|
+
events: EventSummary[] | null;
|
|
16
|
+
/** Unique assigned categories in listing order — render a filter bar only when > 1. */
|
|
17
|
+
categories: { id: string; name: string }[];
|
|
18
|
+
activeCategoryId: string | null;
|
|
19
|
+
setActiveCategoryId: (id: string | null) => void;
|
|
20
|
+
error: string | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function useEvents({ initialEvents }: UseEventsOptions = {}): UseEvents {
|
|
24
|
+
const [all, setAll] = useState<EventSummary[] | null>(initialEvents ?? null);
|
|
25
|
+
const [activeCategoryId, setActiveCategoryId] = useState<string | null>(null);
|
|
26
|
+
const [error, setError] = useState<string | null>(null);
|
|
27
|
+
|
|
28
|
+
useEffect(() => {
|
|
29
|
+
let alive = true;
|
|
30
|
+
if (!initialEvents) {
|
|
31
|
+
fetchEvents()
|
|
32
|
+
.then((e) => alive && setAll(e))
|
|
33
|
+
.catch((e) => {
|
|
34
|
+
if (!alive) return;
|
|
35
|
+
setAll([]);
|
|
36
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return () => {
|
|
40
|
+
alive = false;
|
|
41
|
+
};
|
|
42
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const categories = useMemo(() => {
|
|
46
|
+
const seen = new Map<string, string>();
|
|
47
|
+
for (const e of all ?? []) for (const c of e.categories) if (!seen.has(c.id)) seen.set(c.id, c.name);
|
|
48
|
+
return [...seen.entries()].map(([id, name]) => ({ id, name }));
|
|
49
|
+
}, [all]);
|
|
50
|
+
|
|
51
|
+
const events = useMemo(() => {
|
|
52
|
+
if (all === null) return null;
|
|
53
|
+
return activeCategoryId
|
|
54
|
+
? all.filter((e) => e.categories.some((c) => c.id === activeCategoryId))
|
|
55
|
+
: all;
|
|
56
|
+
}, [all, activeCategoryId]);
|
|
57
|
+
|
|
58
|
+
return { events, categories, activeCategoryId, setActiveCategoryId, error };
|
|
59
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
// Event reads (Wix Events V3, the `wixEventsV2` module) — the only file that touches raw
|
|
2
|
+
// event entities. Everything it returns is a plain DTO from ./types. Copy as-is; extend by
|
|
3
|
+
// adding functions, not by editing these.
|
|
4
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/events/event-management/events-v3/query-events.md
|
|
5
|
+
// docs: https://dev.wix.com/docs/api-reference/business-solutions/events/event-management/events-v3/get-event-by-slug.md
|
|
6
|
+
import { wixEventsV2 } from "@wix/events";
|
|
7
|
+
import { wixModule } from "../sdk";
|
|
8
|
+
import { imgSrc } from "../media";
|
|
9
|
+
import type { EventDetail, EventSummary, RegistrationType } from "./types";
|
|
10
|
+
|
|
11
|
+
const events = wixModule(wixEventsV2);
|
|
12
|
+
|
|
13
|
+
/** The Wix Events app id (reference only — frontend calls need no app-id constant). */
|
|
14
|
+
export const EVENTS_APP_ID = "140603ad-af8d-84a5-2c80-a0f60cb47351";
|
|
15
|
+
|
|
16
|
+
type Raw = Record<string, any>;
|
|
17
|
+
|
|
18
|
+
// Fieldsets are opt-in: without REGISTRATION there's no registration type to branch on;
|
|
19
|
+
// without CATEGORIES the category names never arrive; DETAILS carries the formatted date
|
|
20
|
+
// and mainImage; TEXTS carries the long description.
|
|
21
|
+
const LIST_FIELDS = ["DETAILS", "REGISTRATION", "CATEGORIES"];
|
|
22
|
+
const DETAIL_FIELDS = ["DETAILS", "TEXTS", "REGISTRATION", "URLS", "CATEGORIES"];
|
|
23
|
+
|
|
24
|
+
function lowestPriceLabel(reg: Raw): string {
|
|
25
|
+
const type = reg.type ?? reg.initialType;
|
|
26
|
+
if (type === "RSVP") return "Free";
|
|
27
|
+
if (type !== "TICKETING") return "";
|
|
28
|
+
const lowest: Raw | undefined = reg.tickets?.lowestPrice;
|
|
29
|
+
if (!lowest) return "";
|
|
30
|
+
if (Number(lowest.value ?? 0) === 0) return "Free";
|
|
31
|
+
if (lowest.formattedValue) return `From ${lowest.formattedValue}`;
|
|
32
|
+
try {
|
|
33
|
+
return `From ${new Intl.NumberFormat(undefined, { style: "currency", currency: lowest.currency || "USD" }).format(Number(lowest.value))}`;
|
|
34
|
+
} catch {
|
|
35
|
+
return `From ${lowest.value} ${lowest.currency ?? ""}`.trim();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// event.description is Ricos rich content ({ nodes: [...] }), NOT a string — calling string
|
|
40
|
+
// methods on it crashes the page. Extract plain paragraphs; detailedDescription (legacy plain
|
|
41
|
+
// text) is the fallback.
|
|
42
|
+
function toParagraphs(rich: Raw | undefined, legacy: string | undefined): string[] {
|
|
43
|
+
const collect = (nodes: Raw[] | undefined): string =>
|
|
44
|
+
(nodes ?? [])
|
|
45
|
+
.map((n: Raw) => (typeof n.textData?.text === "string" ? n.textData.text : collect(n.nodes)))
|
|
46
|
+
.join("");
|
|
47
|
+
const out: string[] = [];
|
|
48
|
+
for (const node of rich?.nodes ?? []) {
|
|
49
|
+
const text = collect([node]).trim();
|
|
50
|
+
if (text) out.push(text);
|
|
51
|
+
}
|
|
52
|
+
if (!out.length && legacy) out.push(legacy);
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function toSummary(raw: Raw): EventSummary {
|
|
57
|
+
const reg: Raw = raw.registration ?? {};
|
|
58
|
+
const dts: Raw = raw.dateAndTimeSettings ?? {};
|
|
59
|
+
return {
|
|
60
|
+
id: raw._id ?? "", // _id, never .id — `id` is a REST-view field and is undefined here
|
|
61
|
+
slug: raw.slug ?? "",
|
|
62
|
+
title: raw.title ?? "",
|
|
63
|
+
shortDescription: raw.shortDescription ?? "",
|
|
64
|
+
dateLabel: dts.formatted?.dateAndTime ?? "",
|
|
65
|
+
startDateIso: dts.startDate ? new Date(dts.startDate).toISOString() : "",
|
|
66
|
+
locationName: raw.location?.name ?? "",
|
|
67
|
+
locationType: raw.location?.locationTbd
|
|
68
|
+
? "TBD"
|
|
69
|
+
: ((raw.location?.type as "VENUE" | "ONLINE") ?? "TBD"),
|
|
70
|
+
imageUrl: imgSrc(raw.mainImage, 1200, 800),
|
|
71
|
+
// `type` is the current flavor (can become EXTERNAL later); initialType is the immutable
|
|
72
|
+
// seeded one — read type first.
|
|
73
|
+
registrationType: (reg.type ?? reg.initialType ?? "NONE") as RegistrationType,
|
|
74
|
+
priceLabel: lowestPriceLabel(reg),
|
|
75
|
+
soldOut: reg.tickets?.soldOut === true,
|
|
76
|
+
// `categories` is absent on the typed Event (an SDK type gap) — the Raw boundary reads
|
|
77
|
+
// the runtime field the CATEGORIES fieldset populates.
|
|
78
|
+
categories: (raw.categories?.categories ?? [])
|
|
79
|
+
.map((c: Raw) => ({ id: c._id ?? "", name: c.name ?? "" }))
|
|
80
|
+
.filter((c: { id: string }) => c.id),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function toDetail(raw: Raw): EventDetail {
|
|
85
|
+
const summary = toSummary(raw);
|
|
86
|
+
const reg: Raw = raw.registration ?? {};
|
|
87
|
+
return {
|
|
88
|
+
...summary,
|
|
89
|
+
aboutParagraphs: toParagraphs(raw.description, raw.detailedDescription),
|
|
90
|
+
// Only OPEN_* statuses (OPEN_RSVP, OPEN_TICKETS, OPEN_EXTERNAL, …) accept registrations.
|
|
91
|
+
registrationOpen: typeof reg.status === "string" && reg.status.startsWith("OPEN_"),
|
|
92
|
+
rsvpResponseType: reg.rsvp?.responseType === "YES_AND_NO" ? "YES_AND_NO" : "YES_ONLY",
|
|
93
|
+
externalUrl: reg.external?.url ?? "",
|
|
94
|
+
addToCalendar: { google: raw.calendarUrls?.google ?? "", ics: raw.calendarUrls?.ics ?? "" },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** List live (UPCOMING/STARTED) events, soonest first, mapped to grid-ready DTOs. */
|
|
99
|
+
export async function fetchEvents({ limit = 100 } = {}): Promise<EventSummary[]> {
|
|
100
|
+
// The query builder, never a flat/REST `{ query: {...} }` body (the SDK silently ignores
|
|
101
|
+
// the unknown key and returns zero events). Always set a positive limit — it defaults to 0,
|
|
102
|
+
// which also returns zero events with no error.
|
|
103
|
+
const res = await events
|
|
104
|
+
.queryEvents({ fields: LIST_FIELDS as any })
|
|
105
|
+
.in("status", ["UPCOMING", "STARTED"]) // never list or link a past event — it isn't registerable
|
|
106
|
+
.ascending("dateAndTimeSettings.startDate")
|
|
107
|
+
.limit(limit)
|
|
108
|
+
.find();
|
|
109
|
+
return (res.items ?? []).map((e: Raw) => toSummary(e));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Fetch one event by its URL slug. Null when not found. */
|
|
113
|
+
export async function fetchEventBySlug(slug: string): Promise<EventDetail | null> {
|
|
114
|
+
try {
|
|
115
|
+
// getEventBySlug returns a WRAPPED { event } — unlike getEvent below.
|
|
116
|
+
const res: Raw = await events.getEventBySlug(slug, { fields: DETAIL_FIELDS as any });
|
|
117
|
+
const raw = res?.event;
|
|
118
|
+
return raw ? toDetail(raw as Raw) : null;
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Fetch one event by id — the post-checkout confirmation read (the thank-you URL carries
|
|
126
|
+
* `?orderNumber=&eventId=`). getEvent returns the Event DIRECTLY (unwrapped) — the one read
|
|
127
|
+
* that isn't `{ event }`; assuming the wrapper crashes the page.
|
|
128
|
+
*/
|
|
129
|
+
export async function fetchEventById(eventId: string): Promise<EventDetail | null> {
|
|
130
|
+
try {
|
|
131
|
+
const raw: Raw = await events.getEvent(eventId, { fields: DETAIL_FIELDS as any });
|
|
132
|
+
return raw?._id ? toDetail(raw) : null;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}
|