@wix/agent-skills 1.19.2 → 1.20.1
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/references/SERVICE_PLUGIN.md +14 -1
- package/skills/wix-app/references/editor-react-component/ANIMATED-COMPONENTS.md +7 -6
- package/skills/wix-app/references/service-plugin/ADDITIONAL-FEES.md +4 -0
- package/skills/wix-app/references/service-plugin/AVAILABILITY-TIME-SLOTS-CONFIGURATION.md +118 -0
- package/skills/wix-app/references/service-plugin/BOOKING-POLICY.md +65 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-PRICING.md +71 -0
- package/skills/wix-app/references/service-plugin/BOOKINGS-STAFF-SORTING.md +11 -3
- package/skills/wix-app/references/service-plugin/BOOKINGS-VALIDATION.md +137 -0
- package/skills/wix-app/references/service-plugin/DISCOUNT-TRIGGERS.md +23 -16
- package/skills/wix-app/references/service-plugin/GIFT-CARDS.md +4 -0
- package/skills/wix-app/references/service-plugin/PAYMENT-SETTINGS.md +4 -2
- package/skills/wix-app/references/service-plugin/REALTIME-PERMISSIONS.md +45 -0
- package/skills/wix-app/references/service-plugin/SHIPPING-RATES.md +15 -0
- package/skills/wix-app/references/service-plugin/TOOLS_PROVIDER.md +4 -0
- package/skills/wix-app/references/service-plugin/VALIDATIONS.md +4 -0
- package/skills/wix-headless/references/CAPABILITIES.md +2 -2
- package/skills/wix-headless/references/inline-recipes/experience-store.md +4 -3
- package/skills/wix-headless/references/inline-recipes/how-to-code-a-store.md +98 -18
- package/skills/wix-headless/references/managed/DEPLOYMENT.md +8 -8
- package/skills/wix-headless-fast/SKILL.md +200 -34
- package/skills/wix-headless-fast/entry/skill.md +10 -2
- package/skills/wix-headless-fast/install/attach.mjs +253 -0
- package/skills/wix-headless-fast/install/deploy.mjs +109 -9
- package/skills/wix-headless-fast/install/fast-path.mjs +28 -31
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +224 -60
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
- package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
- package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
- package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +179 -33
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
- package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
- package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +236 -62
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
- package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
- package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +239 -83
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
- package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
- package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
- package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
- package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +214 -26
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
- package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
- package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
- package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +314 -63
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
- package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
- package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
- package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +238 -59
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
- package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +212 -51
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
- package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
- package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +240 -47
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
- package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
- package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
- package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
- package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +55 -1
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
- package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
- package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
- package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +371 -81
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +37 -3
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +266 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +136 -0
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -130
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -182
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +53 -136
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +309 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +73 -230
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +62 -4
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +21 -2
- package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
- package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +25 -5
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +24 -0
- package/skills/wix-manage/SKILL.md +4 -1
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
- package/skills/wix-manage/references/events/create-wix-event.md +46 -0
- package/skills/wix-manage/references/events/manage-wix-events.md +51 -1
- package/skills/wix-manage/references/google-ads/manage-campaign-lifecycle.md +6 -6
- package/skills/wix-manage/references/seo/manage-seo-suggestions.md +351 -0
- package/skills/wix-vibe-headless/install/deploy.cjs +33 -2
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed-with-install.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44-ecom-light-seed.md +13 -4
- package/skills/wix-vibe-headless/platforms/base44-ecom-light.md +1 -1
- package/skills/wix-vibe-headless/platforms/base44.md +19 -4
- package/skills/wix-vibe-headless/platforms/generic.md +6 -0
- package/skills/wix-vibe-headless/references/_shared/nav/nav.tanstack.js +30 -0
- package/skills/wix-vibe-headless/references/_shared/routing.md +86 -0
- package/skills/wix-vibe-headless/references/blog/INSTRUCTIONS.md +44 -0
- package/skills/wix-vibe-headless/references/blog/app/components/PostCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/components/PostChips.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/CategoryPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/PostDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/blog/app/pages/TagPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/bookings/app/components/ServiceCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/bookings/app/pages/ServiceDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/events/app/components/EventCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/events/app/pages/EventDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/INSTRUCTIONS.md +42 -0
- package/skills/wix-vibe-headless/references/members/app/components/MemberMenu.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/components/RequireAuth.jsx +1 -1
- package/skills/wix-vibe-headless/references/members/app/pages/Login.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/portfolio/app/components/CollectionCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/components/ProjectCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/CollectionPage.jsx +1 -1
- package/skills/wix-vibe-headless/references/portfolio/app/pages/ProjectDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/INSTRUCTIONS.md +43 -0
- package/skills/wix-vibe-headless/references/pricing-plans/app/components/PlanCard.jsx +1 -1
- package/skills/wix-vibe-headless/references/pricing-plans/app/pages/PlanDetail.jsx +1 -1
- package/skills/wix-vibe-headless/references/restaurants/INSTRUCTIONS.md +36 -0
- package/skills/wix-vibe-headless/references/shared/app/lib/nav.js +9 -0
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +42 -41
- package/skills/wix-vibe-headless/references/storefront/app/hooks/useProductCard.js +7 -1
- package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
- package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
- package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
|
@@ -1,117 +1,35 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
1
|
+
// React binding of the registration store (wix/events/registration-store.ts) — the whole
|
|
2
|
+
// registration state machine for one event lives there, framework-free, branched on
|
|
3
|
+
// event.registrationType: TICKETING loads the tier picker and checkout() reserves → redirects to
|
|
4
|
+
// Wix's hosted checkout; RSVP is the built-in name+email form submitted in place. All correctness
|
|
5
|
+
// (the visitor-public tier read, the reservation payload, the redirect callbacks, rsvpV2) lives
|
|
6
|
+
// in the data layer; you own how it looks. One store per event — a new event id gets a fresh one.
|
|
7
|
+
import { useEffect, useRef, useSyncExternalStore } from "react";
|
|
8
|
+
import {
|
|
9
|
+
createRegistrationStore,
|
|
10
|
+
type RegistrationState,
|
|
11
|
+
type RegistrationStore,
|
|
12
|
+
} from "../../wix/events/registration-store";
|
|
13
|
+
import type { EventDetail } from "../../wix/events/types";
|
|
9
14
|
|
|
10
|
-
export
|
|
11
|
-
|
|
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
|
-
}
|
|
15
|
+
export type UseEventRegistration = RegistrationState &
|
|
16
|
+
Pick<RegistrationStore, "setQuantity" | "setRsvpValue" | "checkout" | "rsvp">;
|
|
34
17
|
|
|
35
18
|
export function useEventRegistration(event: EventDetail): UseEventRegistration {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
const [submitting, setSubmitting] = useState(false);
|
|
41
|
-
const [confirmed, setConfirmed] = useState<RegistrationResult | null>(null);
|
|
42
|
-
const [error, setError] = useState<string | null>(null);
|
|
43
|
-
|
|
19
|
+
const key = `${event.id}:${event.registrationType}`;
|
|
20
|
+
const ref = useRef<{ key: string; store: RegistrationStore } | null>(null);
|
|
21
|
+
if (!ref.current || ref.current.key !== key) ref.current = { key, store: createRegistrationStore(event) };
|
|
22
|
+
const store = ref.current.store;
|
|
44
23
|
useEffect(() => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
24
|
+
store.start();
|
|
25
|
+
return () => store.stop();
|
|
26
|
+
}, [store]);
|
|
27
|
+
const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
96
28
|
return {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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,
|
|
29
|
+
...state,
|
|
30
|
+
setQuantity: store.setQuantity,
|
|
31
|
+
setRsvpValue: store.setRsvpValue,
|
|
32
|
+
checkout: store.checkout,
|
|
33
|
+
rsvp: store.rsvp,
|
|
116
34
|
};
|
|
117
35
|
}
|
|
@@ -1,59 +1,24 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
import
|
|
1
|
+
// React binding of the events store (wix/events/events-store.ts) — the listing state machine
|
|
2
|
+
// lives there, framework-free; this hook subscribes to one instance per mounted listing and
|
|
3
|
+
// exposes its state and actions under one name. SSR-friendly: pass server-fetched data as
|
|
4
|
+
// `initialEvents` (Astro frontmatter / server component) and no client fetch happens; a SPA
|
|
5
|
+
// passes nothing. Astro islands and React SPAs use this; a static page, Vue, or Svelte uses the
|
|
6
|
+
// store directly.
|
|
7
|
+
import { useEffect, useRef, useSyncExternalStore } from "react";
|
|
8
|
+
import { createEventsStore, type EventsState, type EventsStore, type EventsStoreOptions } from "../../wix/events/events-store";
|
|
8
9
|
|
|
9
|
-
export
|
|
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
|
-
}
|
|
10
|
+
export type UseEventsOptions = EventsStoreOptions;
|
|
22
11
|
|
|
23
|
-
export
|
|
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);
|
|
12
|
+
export type UseEvents = EventsState & Pick<EventsStore, "setActiveCategoryId">;
|
|
27
13
|
|
|
14
|
+
export function useEvents(options: UseEventsOptions = {}): UseEvents {
|
|
15
|
+
const ref = useRef<EventsStore | null>(null);
|
|
16
|
+
if (!ref.current) ref.current = createEventsStore(options);
|
|
17
|
+
const store = ref.current;
|
|
28
18
|
useEffect(() => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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 };
|
|
19
|
+
store.start();
|
|
20
|
+
return () => store.stop();
|
|
21
|
+
}, [store]);
|
|
22
|
+
const state = useSyncExternalStore(store.subscribe, store.getState, store.getState);
|
|
23
|
+
return { ...state, setActiveCategoryId: store.setActiveCategoryId };
|
|
59
24
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// Event rules and DTO mapping — transport-agnostic, imported by BOTH transports: ./events.ts (the
|
|
2
|
+
// SDK, managed Astro and React) and the REST twin in references/events/rest/events.ts (fetch, a
|
|
3
|
+
// static site or a port to another language). Every rule about statuses, fieldsets, prices,
|
|
4
|
+
// dates, images and categories lives HERE, once. A raw event may come from the SDK (`_id`,
|
|
5
|
+
// mainImage as a wix:image string) or from REST (`id`, mainImage as { id, url }); the mappers
|
|
6
|
+
// accept both. Imports are type-only so a strip to JS emits no imports.
|
|
7
|
+
import type { EventDetail, EventSummary, RegistrationType } from "./types";
|
|
8
|
+
|
|
9
|
+
/** A raw Events V3 entity as either transport returns it. */
|
|
10
|
+
export type Raw = Record<string, any>;
|
|
11
|
+
/** Media value + size → https URL. Injected: the SDK transport scales through @wix/sdk, REST through the URL form. */
|
|
12
|
+
export type ImgSrc = (value: any, width: number, height: number) => string;
|
|
13
|
+
|
|
14
|
+
/** The Wix Events app id (reference only — frontend calls need no app-id constant). */
|
|
15
|
+
export const EVENTS_APP_ID = "140603ad-af8d-84a5-2c80-a0f60cb47351";
|
|
16
|
+
|
|
17
|
+
export const rawId = (raw: Raw | undefined | null): string => raw?._id ?? raw?.id ?? "";
|
|
18
|
+
|
|
19
|
+
// Fieldsets are opt-in: without REGISTRATION there's no registration type to branch on;
|
|
20
|
+
// without CATEGORIES the category names never arrive; DETAILS carries the formatted date
|
|
21
|
+
// and mainImage; TEXTS carries the long description; URLS the Wix event page url.
|
|
22
|
+
export const LIST_FIELDS = ["DETAILS", "REGISTRATION", "CATEGORIES"] as const;
|
|
23
|
+
export const DETAIL_FIELDS = ["DETAILS", "TEXTS", "REGISTRATION", "URLS", "CATEGORIES"] as const;
|
|
24
|
+
|
|
25
|
+
/** Only these are listed and linked — a past or canceled event isn't registerable. */
|
|
26
|
+
export const LIVE_STATUSES = ["UPCOMING", "STARTED"] as const;
|
|
27
|
+
export const START_DATE_FIELD = "dateAndTimeSettings.startDate";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The Query Events body for the listing: live statuses, soonest first, a positive limit.
|
|
31
|
+
* `paging.limit` defaults to 0 — a bare query answers `total: 2, events: []` with no error — so
|
|
32
|
+
* the limit is always sent. The SDK builder spells the same three parts.
|
|
33
|
+
*/
|
|
34
|
+
export function listQuery({ limit = 100 } = {}): Raw {
|
|
35
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 1000) throw new Error("limit must be between 1 and 1000.");
|
|
36
|
+
return {
|
|
37
|
+
filter: { status: { $in: [...LIVE_STATUSES] } },
|
|
38
|
+
sort: [{ fieldName: START_DATE_FIELD, order: "ASC" }],
|
|
39
|
+
paging: { limit },
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** "From €45" (ticketed), "Free" (RSVP / free tickets), "" when nothing applies. */
|
|
44
|
+
export function lowestPriceLabel(reg: Raw): string {
|
|
45
|
+
const type = reg.type ?? reg.initialType;
|
|
46
|
+
if (type === "RSVP") return "Free";
|
|
47
|
+
if (type !== "TICKETING") return "";
|
|
48
|
+
const lowest: Raw | undefined = reg.tickets?.lowestPrice;
|
|
49
|
+
if (!lowest) return "";
|
|
50
|
+
if (Number(lowest.value ?? 0) === 0) return "Free";
|
|
51
|
+
if (lowest.formattedValue) return `From ${lowest.formattedValue}`;
|
|
52
|
+
try {
|
|
53
|
+
return `From ${new Intl.NumberFormat(undefined, { style: "currency", currency: lowest.currency || "USD" }).format(Number(lowest.value))}`;
|
|
54
|
+
} catch {
|
|
55
|
+
return `From ${lowest.value} ${lowest.currency ?? ""}`.trim();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// event.description is Ricos rich content ({ nodes: [...] }), NOT a string — calling string
|
|
60
|
+
// methods on it crashes the page. Extract plain paragraphs; detailedDescription (legacy plain
|
|
61
|
+
// text) is the fallback.
|
|
62
|
+
export function toParagraphs(rich: Raw | undefined | null, legacy: string | undefined): string[] {
|
|
63
|
+
const collect = (nodes: Raw[] | undefined): string =>
|
|
64
|
+
(nodes ?? [])
|
|
65
|
+
.map((n: Raw) => (typeof n.textData?.text === "string" ? n.textData.text : collect(n.nodes)))
|
|
66
|
+
.join("");
|
|
67
|
+
const out: string[] = [];
|
|
68
|
+
for (const node of rich?.nodes ?? []) {
|
|
69
|
+
const text = collect([node]).trim();
|
|
70
|
+
if (text) out.push(text);
|
|
71
|
+
}
|
|
72
|
+
if (!out.length && legacy) out.push(legacy);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* The main image in the form imgSrc scales: the SDK carries it as a `wix:image://` string; REST
|
|
78
|
+
* as `{ id, url, altText }` — rebuilt into the id form so both transports resolve one URL shape.
|
|
79
|
+
*/
|
|
80
|
+
export function mainImageOf(raw: Raw): unknown {
|
|
81
|
+
const m = raw.mainImage;
|
|
82
|
+
if (m && typeof m === "object" && typeof m.id === "string" && m.id) {
|
|
83
|
+
return `wix:image://v1/${m.id}/${encodeURIComponent(String(m.altText || "image"))}`;
|
|
84
|
+
}
|
|
85
|
+
return m;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function toSummary(raw: Raw, imgSrc: ImgSrc): EventSummary {
|
|
89
|
+
const reg: Raw = raw.registration ?? {};
|
|
90
|
+
const dts: Raw = raw.dateAndTimeSettings ?? {};
|
|
91
|
+
return {
|
|
92
|
+
id: rawId(raw), // _id on the SDK, id on REST — never a bare .id on the SDK (undefined there)
|
|
93
|
+
slug: raw.slug ?? "",
|
|
94
|
+
title: raw.title ?? "",
|
|
95
|
+
shortDescription: raw.shortDescription ?? "",
|
|
96
|
+
dateLabel: dts.formatted?.dateAndTime ?? "",
|
|
97
|
+
startDateIso: dts.startDate ? new Date(dts.startDate).toISOString() : "",
|
|
98
|
+
locationName: raw.location?.name ?? "",
|
|
99
|
+
locationType: raw.location?.locationTbd
|
|
100
|
+
? "TBD"
|
|
101
|
+
: ((raw.location?.type as "VENUE" | "ONLINE") ?? "TBD"),
|
|
102
|
+
imageUrl: imgSrc(mainImageOf(raw), 1200, 800),
|
|
103
|
+
// `type` is the current flavor (can become EXTERNAL later); initialType is the immutable
|
|
104
|
+
// seeded one — read type first.
|
|
105
|
+
registrationType: (reg.type ?? reg.initialType ?? "NONE") as RegistrationType,
|
|
106
|
+
priceLabel: lowestPriceLabel(reg),
|
|
107
|
+
// The API reports tickets.soldOut true on an RSVP event too (it has no tickets at all) — sold
|
|
108
|
+
// out is a ticketing signal only.
|
|
109
|
+
soldOut: (reg.type ?? reg.initialType) === "TICKETING" && reg.tickets?.soldOut === true,
|
|
110
|
+
// `categories` is absent on the typed Event (an SDK type gap) — the Raw boundary reads
|
|
111
|
+
// the runtime field the CATEGORIES fieldset populates.
|
|
112
|
+
categories: ((raw.categories?.categories ?? []) as Raw[])
|
|
113
|
+
.map((c) => ({ id: rawId(c), name: c.name ?? "" }))
|
|
114
|
+
.filter((c) => c.id),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function toDetail(raw: Raw, imgSrc: ImgSrc): EventDetail {
|
|
119
|
+
const summary = toSummary(raw, imgSrc);
|
|
120
|
+
const reg: Raw = raw.registration ?? {};
|
|
121
|
+
return {
|
|
122
|
+
...summary,
|
|
123
|
+
aboutParagraphs: toParagraphs(raw.description, raw.detailedDescription),
|
|
124
|
+
// Only OPEN_* statuses (OPEN_RSVP, OPEN_TICKETS, OPEN_EXTERNAL, …) accept registrations.
|
|
125
|
+
registrationOpen: typeof reg.status === "string" && reg.status.startsWith("OPEN_"),
|
|
126
|
+
rsvpResponseType: reg.rsvp?.responseType === "YES_AND_NO" ? "YES_AND_NO" : "YES_ONLY",
|
|
127
|
+
externalUrl: reg.external?.url ?? "",
|
|
128
|
+
addToCalendar: { google: raw.calendarUrls?.google ?? "", ics: raw.calendarUrls?.ics ?? "" },
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// The events listing as a framework-free store — the logic behind useEvents, usable from React
|
|
2
|
+
// (useEvents wraps it with useSyncExternalStore), from a static page or Vue/Svelte (subscribe and
|
|
3
|
+
// render), or as the specification for a port. State, actions, subscribe/getState, emit after
|
|
4
|
+
// every change.
|
|
5
|
+
//
|
|
6
|
+
// SSR-friendly: seed with `initialEvents` and they render at once with no client fetch; a SPA
|
|
7
|
+
// passes nothing and `start()` fetches. The category menu is DERIVED from the loaded events (the
|
|
8
|
+
// categories management API is admin-scope — visitors can't query it); filtering is client-side
|
|
9
|
+
// over the live list, which is small by definition (only UPCOMING/STARTED events are listed).
|
|
10
|
+
//
|
|
11
|
+
// One store per mounted listing (a page can hold the index and a "next up" rail):
|
|
12
|
+
// createEventsStore(), not a singleton.
|
|
13
|
+
import { fetchEvents } from "./events";
|
|
14
|
+
import type { EventSummary } from "./types";
|
|
15
|
+
|
|
16
|
+
export interface EventsStoreOptions {
|
|
17
|
+
initialEvents?: EventSummary[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Everything a listing surface renders from. Read it with getState() or through a subscription. */
|
|
21
|
+
export interface EventsState {
|
|
22
|
+
/** null while the first load is in flight — render skeletons, not an empty state. */
|
|
23
|
+
events: EventSummary[] | null;
|
|
24
|
+
/** Unique assigned categories in listing order — render a filter bar only when > 1. */
|
|
25
|
+
categories: { id: string; name: string }[];
|
|
26
|
+
activeCategoryId: string | null;
|
|
27
|
+
error: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface EventsStore {
|
|
31
|
+
getState(): EventsState;
|
|
32
|
+
subscribe(listener: () => void): () => void;
|
|
33
|
+
/** Fetch when no `initialEvents` were given. Call once when mounted (a browser). */
|
|
34
|
+
start(): void;
|
|
35
|
+
/** Stop reacting; drop a late response. */
|
|
36
|
+
stop(): void;
|
|
37
|
+
setActiveCategoryId(id: string | null): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createEventsStore({ initialEvents }: EventsStoreOptions = {}): EventsStore {
|
|
41
|
+
let all: EventSummary[] | null = initialEvents ?? null;
|
|
42
|
+
let activeCategoryId: string | null = null;
|
|
43
|
+
let error: string | null = null;
|
|
44
|
+
let started = false;
|
|
45
|
+
const listeners = new Set<() => void>();
|
|
46
|
+
let snapshot: EventsState | null = null;
|
|
47
|
+
const emit = () => {
|
|
48
|
+
snapshot = null;
|
|
49
|
+
for (const fn of listeners) fn();
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
function getState(): EventsState {
|
|
53
|
+
if (snapshot) return snapshot;
|
|
54
|
+
const seen = new Map<string, string>();
|
|
55
|
+
for (const e of all ?? []) for (const c of e.categories) if (!seen.has(c.id)) seen.set(c.id, c.name);
|
|
56
|
+
snapshot = {
|
|
57
|
+
events: all === null ? null : activeCategoryId ? all.filter((e) => e.categories.some((c) => c.id === activeCategoryId)) : all,
|
|
58
|
+
categories: [...seen.entries()].map(([id, name]) => ({ id, name })),
|
|
59
|
+
activeCategoryId,
|
|
60
|
+
error,
|
|
61
|
+
};
|
|
62
|
+
return snapshot;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
getState,
|
|
67
|
+
subscribe(fn) {
|
|
68
|
+
listeners.add(fn);
|
|
69
|
+
return () => listeners.delete(fn);
|
|
70
|
+
},
|
|
71
|
+
start() {
|
|
72
|
+
if (started) return;
|
|
73
|
+
started = true;
|
|
74
|
+
if (initialEvents) return;
|
|
75
|
+
fetchEvents()
|
|
76
|
+
.then((list) => {
|
|
77
|
+
if (!started) return;
|
|
78
|
+
all = list;
|
|
79
|
+
emit();
|
|
80
|
+
})
|
|
81
|
+
.catch((e) => {
|
|
82
|
+
if (!started) return;
|
|
83
|
+
all = [];
|
|
84
|
+
error = e instanceof Error ? e.message : String(e);
|
|
85
|
+
emit();
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
stop() {
|
|
89
|
+
started = false;
|
|
90
|
+
},
|
|
91
|
+
setActiveCategoryId(id) {
|
|
92
|
+
if (id === activeCategoryId) return;
|
|
93
|
+
activeCategoryId = id;
|
|
94
|
+
emit();
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -1,99 +1,18 @@
|
|
|
1
|
-
// Event reads (Wix Events V3, the `wixEventsV2` module) — the only file that touches
|
|
2
|
-
// event entities. Everything it returns is a plain DTO from ./types.
|
|
3
|
-
//
|
|
1
|
+
// Event reads (Wix Events V3, the `wixEventsV2` module) over the SDK — the only file that touches
|
|
2
|
+
// raw event entities on this transport. Everything it returns is a plain DTO from ./types. The
|
|
3
|
+
// rules and mappers live in ./events-core (shared with the REST twin in references/events/rest/);
|
|
4
|
+
// this file is the transport only. Copy as-is; extend by adding functions, not by editing these.
|
|
4
5
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/events/event-management/events-v3/query-events.md
|
|
5
6
|
// docs: https://dev.wix.com/docs/api-reference/business-solutions/events/event-management/events-v3/get-event-by-slug.md
|
|
6
7
|
import { wixEventsV2 } from "@wix/events";
|
|
7
8
|
import { wixModule } from "../sdk";
|
|
8
9
|
import { imgSrc } from "../media";
|
|
9
|
-
import
|
|
10
|
+
import { DETAIL_FIELDS, EVENTS_APP_ID, LIST_FIELDS, LIVE_STATUSES, START_DATE_FIELD, rawId, toDetail, toSummary, type Raw } from "./events-core";
|
|
11
|
+
import type { EventDetail, EventSummary } from "./types";
|
|
10
12
|
|
|
11
|
-
|
|
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
|
-
}
|
|
13
|
+
export { EVENTS_APP_ID };
|
|
55
14
|
|
|
56
|
-
|
|
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
|
-
}
|
|
15
|
+
const events = wixModule(wixEventsV2);
|
|
97
16
|
|
|
98
17
|
/** List live (UPCOMING/STARTED) events, soonest first, mapped to grid-ready DTOs. */
|
|
99
18
|
export async function fetchEvents({ limit = 100 } = {}): Promise<EventSummary[]> {
|
|
@@ -102,11 +21,11 @@ export async function fetchEvents({ limit = 100 } = {}): Promise<EventSummary[]>
|
|
|
102
21
|
// which also returns zero events with no error.
|
|
103
22
|
const res = await events
|
|
104
23
|
.queryEvents({ fields: LIST_FIELDS as any })
|
|
105
|
-
.in("status", [
|
|
106
|
-
.ascending(
|
|
24
|
+
.in("status", [...LIVE_STATUSES]) // never list or link a past event — it isn't registerable
|
|
25
|
+
.ascending(START_DATE_FIELD as any)
|
|
107
26
|
.limit(limit)
|
|
108
27
|
.find();
|
|
109
|
-
return (res.items ?? []).map((e: Raw) => toSummary(e));
|
|
28
|
+
return (res.items ?? []).map((e: Raw) => toSummary(e, imgSrc));
|
|
110
29
|
}
|
|
111
30
|
|
|
112
31
|
/** Fetch one event by its URL slug. Null when not found. */
|
|
@@ -115,7 +34,7 @@ export async function fetchEventBySlug(slug: string): Promise<EventDetail | null
|
|
|
115
34
|
// getEventBySlug returns a WRAPPED { event } — unlike getEvent below.
|
|
116
35
|
const res: Raw = await events.getEventBySlug(slug, { fields: DETAIL_FIELDS as any });
|
|
117
36
|
const raw = res?.event;
|
|
118
|
-
return raw ? toDetail(raw as Raw) : null;
|
|
37
|
+
return raw ? toDetail(raw as Raw, imgSrc) : null;
|
|
119
38
|
} catch {
|
|
120
39
|
return null;
|
|
121
40
|
}
|
|
@@ -129,7 +48,7 @@ export async function fetchEventBySlug(slug: string): Promise<EventDetail | null
|
|
|
129
48
|
export async function fetchEventById(eventId: string): Promise<EventDetail | null> {
|
|
130
49
|
try {
|
|
131
50
|
const raw: Raw = await events.getEvent(eventId, { fields: DETAIL_FIELDS as any });
|
|
132
|
-
return raw
|
|
51
|
+
return rawId(raw) ? toDetail(raw, imgSrc) : null;
|
|
133
52
|
} catch {
|
|
134
53
|
return null;
|
|
135
54
|
}
|