@wix/agent-skills 1.20.1 → 1.22.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 +71 -113
- package/skills/wix-app/references/APP_TOOLS.md +2 -1
- package/skills/wix-app/references/BACKEND_EVENT.md +3 -3
- package/skills/wix-app/references/CUSTOM_ELEMENT_WIDGET.md +41 -152
- package/skills/wix-app/references/DASHBOARD_MODAL.md +4 -4
- package/skills/wix-app/references/DASHBOARD_PAGE.md +11 -25
- package/skills/wix-app/references/DATA_COLLECTION.md +16 -219
- package/skills/wix-app/references/SDK_MODULE_MAP.md +44 -0
- package/skills/wix-app/references/SITE_PLUGIN.md +36 -190
- package/skills/wix-app/references/app-tools/TOOLS.md +3 -1
- package/skills/wix-app/references/custom-element-widget/SETTINGS_PANEL.md +21 -0
- package/skills/wix-app/references/dashboard-page/DATA_SOURCES.md +182 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE.md +97 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_COLLECTION.md +184 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_ROUTER.md +203 -0
- package/skills/wix-app/references/dashboard-page/DRAFT_TEMPLATE_SETTINGS.md +44 -0
- package/skills/wix-app/references/dashboard-page/ECOM_NAVIGATION.md +17 -0
- package/skills/wix-app/references/dashboard-page/QUERY_AND_PAGING.md +182 -0
- package/skills/wix-app/references/dashboard-page/TABLE_STATE.md +133 -0
- package/skills/wix-app/references/dashboard-page/UX_SUCCESS_MODEL.md +10 -5
- package/skills/wix-app/references/dashboard-plugin/SLOTS.md +4 -18
- package/skills/wix-app/references/data-collection/LIFECYCLE.md +68 -0
- package/skills/wix-app/references/data-collection/PERMISSIONS.md +47 -0
- package/skills/wix-app/references/data-collection/SCHEMA.md +181 -0
- package/skills/wix-app/references/data-collection/WIX_DATA.md +38 -66
- package/skills/wix-app/references/site-plugin/EXAMPLES.md +1 -1
- package/skills/wix-headless-fast/SKILL.md +144 -221
- package/skills/wix-headless-fast/cold-start/bootstrap.mjs +156 -0
- package/skills/wix-headless-fast/{entry/skill.md → cold-start/cold-start.md} +15 -15
- package/skills/wix-headless-fast/guides/existing-site.md +76 -0
- package/skills/wix-headless-fast/guides/reference-mode.md +97 -0
- package/skills/wix-headless-fast/install/AGENTS.md +35 -0
- package/skills/wix-headless-fast/install/agents-md.mjs +39 -0
- package/skills/wix-headless-fast/install/attach.mjs +161 -86
- package/skills/wix-headless-fast/install/deploy.mjs +21 -60
- package/skills/wix-headless-fast/install/lock.mjs +55 -0
- package/skills/wix-headless-fast/install/setup.mjs +259 -0
- package/skills/wix-headless-fast/install/templates.mjs +168 -0
- package/skills/wix-manage/references/ecommerce/pricing-promotions/ecom-pricing-create-coupon.md +10 -19
- package/skills/wix-manage/references/events/create-wix-event.md +5 -1
- package/skills/wix-manage/references/google-ads/google-ads-dashboard-navigation.md +27 -0
- package/skills/wix-manage/references/google-ads/manage-campaign-success-guide.md +83 -37
- package/skills/wix-headless-fast/install/fast-path.mjs +0 -181
- package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +0 -301
- package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +0 -136
- package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +0 -112
- package/skills/wix-headless-fast/references/blog/app/components/blog/RichContent.tsx +0 -42
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +0 -31
- package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +0 -22
- package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +0 -137
- package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +0 -78
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +0 -117
- package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +0 -52
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +0 -47
- package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +0 -58
- package/skills/wix-headless-fast/references/blog/app/wix/blog/types.ts +0 -62
- package/skills/wix-headless-fast/references/blog/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +0 -97
- package/skills/wix-headless-fast/references/blog/app-astro/pages/blog.astro +0 -24
- package/skills/wix-headless-fast/references/blog/lock/astro/package-lock.json +0 -16156
- package/skills/wix-headless-fast/references/blog/rest/posts.ts +0 -42
- package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +0 -62
- package/skills/wix-headless-fast/references/blog/seed/SEED.md +0 -81
- package/skills/wix-headless-fast/references/blog/seed/seed-blog.mjs +0 -287
- package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +0 -275
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +0 -144
- package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServicesView.tsx +0 -119
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +0 -32
- package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +0 -23
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +0 -218
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +0 -169
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +0 -121
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +0 -92
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +0 -90
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +0 -57
- package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +0 -72
- package/skills/wix-headless-fast/references/bookings/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services/[slug].astro +0 -76
- package/skills/wix-headless-fast/references/bookings/app-astro/pages/services.astro +0 -22
- package/skills/wix-headless-fast/references/bookings/lock/astro/package-lock.json +0 -11962
- package/skills/wix-headless-fast/references/bookings/rest/booking.ts +0 -113
- package/skills/wix-headless-fast/references/bookings/rest/services.ts +0 -55
- package/skills/wix-headless-fast/references/bookings/seed/SEED.md +0 -71
- package/skills/wix-headless-fast/references/bookings/seed/seed-bookings.mjs +0 -302
- package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +0 -353
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +0 -50
- package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +0 -37
- package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +0 -158
- package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +0 -93
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +0 -177
- package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +0 -123
- package/skills/wix-headless-fast/references/cms/app/wix/cms/types.ts +0 -75
- package/skills/wix-headless-fast/references/cms/app-astro/layouts/SiteLayout.astro +0 -41
- package/skills/wix-headless-fast/references/cms/lock/astro/package-lock.json +0 -11262
- package/skills/wix-headless-fast/references/cms/rest/items.ts +0 -125
- package/skills/wix-headless-fast/references/cms/seed/SEED.md +0 -112
- package/skills/wix-headless-fast/references/cms/seed/seed-cms.mjs +0 -324
- package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +0 -305
- package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +0 -66
- package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +0 -178
- package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +0 -118
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +0 -35
- package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +0 -24
- package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +0 -130
- package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +0 -97
- package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +0 -55
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +0 -113
- package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +0 -156
- package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +0 -97
- package/skills/wix-headless-fast/references/events/app/wix/events/types.ts +0 -61
- package/skills/wix-headless-fast/references/events/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/events/app-astro/pages/event-confirmation.astro +0 -12
- package/skills/wix-headless-fast/references/events/app-astro/pages/events/[slug].astro +0 -80
- package/skills/wix-headless-fast/references/events/app-astro/pages/events.astro +0 -21
- package/skills/wix-headless-fast/references/events/lock/astro/package-lock.json +0 -11884
- package/skills/wix-headless-fast/references/events/rest/events.ts +0 -57
- package/skills/wix-headless-fast/references/events/rest/registration.ts +0 -83
- package/skills/wix-headless-fast/references/events/seed/SEED.md +0 -95
- package/skills/wix-headless-fast/references/events/seed/seed-events.mjs +0 -276
- package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +0 -338
- package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +0 -85
- package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +0 -332
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +0 -238
- package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +0 -54
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +0 -216
- package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +0 -59
- package/skills/wix-headless-fast/references/forms/app/wix/forms/types.ts +0 -135
- package/skills/wix-headless-fast/references/forms/lock/astro/package-lock.json +0 -10593
- package/skills/wix-headless-fast/references/forms/rest/forms.ts +0 -34
- package/skills/wix-headless-fast/references/forms/rest/submissions.ts +0 -56
- package/skills/wix-headless-fast/references/forms/seed/SEED.md +0 -116
- package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +0 -327
- package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +0 -314
- package/skills/wix-headless-fast/references/members/app/components/members/AccountView.tsx +0 -60
- package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +0 -155
- package/skills/wix-headless-fast/references/members/app/components/members/MemberMenu.tsx +0 -78
- package/skills/wix-headless-fast/references/members/app/components/members/RequireAuth.tsx +0 -47
- package/skills/wix-headless-fast/references/members/app/hooks/members/useMember.ts +0 -66
- package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +0 -105
- package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +0 -59
- package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +0 -53
- package/skills/wix-headless-fast/references/members/app/wix/members/member-store.ts +0 -122
- package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +0 -43
- package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +0 -29
- package/skills/wix-headless-fast/references/members/app/wix/members/types.ts +0 -21
- package/skills/wix-headless-fast/references/members/app-astro/layouts/SiteLayout.astro +0 -42
- package/skills/wix-headless-fast/references/members/app-astro/pages/account.astro +0 -10
- package/skills/wix-headless-fast/references/members/app-astro/pages/login.astro +0 -11
- package/skills/wix-headless-fast/references/members/lock/astro/package-lock.json +0 -10329
- package/skills/wix-headless-fast/references/members/rest/auth.ts +0 -215
- package/skills/wix-headless-fast/references/members/rest/members.ts +0 -26
- package/skills/wix-headless-fast/references/members/seed/SEED.md +0 -39
- package/skills/wix-headless-fast/references/members/seed/seed-members.mjs +0 -94
- package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +0 -313
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +0 -110
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +0 -90
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +0 -35
- package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +0 -85
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +0 -32
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +0 -21
- package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +0 -31
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +0 -82
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +0 -58
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +0 -177
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +0 -78
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +0 -81
- package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/types.ts +0 -55
- package/skills/wix-headless-fast/references/portfolio/app-astro/layouts/SiteLayout.astro +0 -40
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio/[slug].astro +0 -30
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/portfolio.astro +0 -21
- package/skills/wix-headless-fast/references/portfolio/app-astro/pages/projects/[slug].astro +0 -29
- package/skills/wix-headless-fast/references/portfolio/lock/astro/package-lock.json +0 -10197
- package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +0 -75
- package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +0 -86
- package/skills/wix-headless-fast/references/portfolio/seed/seed-portfolio.mjs +0 -258
- package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +0 -294
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +0 -47
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +0 -101
- package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +0 -41
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +0 -16
- package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +0 -22
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +0 -54
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +0 -106
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +0 -64
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +0 -27
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +0 -43
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +0 -22
- package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/types.ts +0 -30
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/layouts/SiteLayout.astro +0 -41
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans/[slug].astro +0 -40
- package/skills/wix-headless-fast/references/pricing-plans/app-astro/pages/plans.astro +0 -21
- package/skills/wix-headless-fast/references/pricing-plans/lock/astro/package-lock.json +0 -11226
- package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +0 -31
- package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +0 -20
- package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +0 -79
- package/skills/wix-headless-fast/references/pricing-plans/seed/seed-pricing-plans.mjs +0 -279
- package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +0 -358
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +0 -142
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartButton.tsx +0 -26
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +0 -121
- package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/ReservationView.tsx +0 -155
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +0 -34
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useOrderCart.ts +0 -43
- package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +0 -34
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +0 -155
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +0 -89
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +0 -71
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/order-store.ts +0 -103
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +0 -124
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +0 -185
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +0 -193
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +0 -94
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +0 -74
- package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/types.ts +0 -168
- package/skills/wix-headless-fast/references/restaurants/app-astro/layouts/SiteLayout.astro +0 -47
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/menu.astro +0 -22
- package/skills/wix-headless-fast/references/restaurants/app-astro/pages/reservations.astro +0 -12
- package/skills/wix-headless-fast/references/restaurants/lock/astro/package-lock.json +0 -12560
- package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +0 -60
- package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +0 -169
- package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +0 -68
- package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +0 -89
- package/skills/wix-headless-fast/references/restaurants/seed/seed-restaurants.mjs +0 -479
- package/skills/wix-headless-fast/references/shared/CONTENT.md +0 -22
- package/skills/wix-headless-fast/references/shared/CUSTOM_OPERATIONS.md +0 -73
- package/skills/wix-headless-fast/references/shared/DESIGN.md +0 -26
- package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +0 -23
- package/skills/wix-headless-fast/references/shared/app/styles/global.css +0 -52
- package/skills/wix-headless-fast/references/shared/app/wix/config.ts +0 -15
- package/skills/wix-headless-fast/references/shared/app/wix/media.ts +0 -81
- package/skills/wix-headless-fast/references/shared/app/wix/money.ts +0 -24
- package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +0 -89
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app/wix/media-upload/client.ts +0 -46
- package/skills/wix-headless-fast/references/shared/capabilities/media-upload/app-astro/pages/api/wix/media-upload.ts +0 -46
- package/skills/wix-headless-fast/references/shared/rest/client.ts +0 -155
- package/skills/wix-headless-fast/references/shared/rest/config.ts +0 -3
- package/skills/wix-headless-fast/references/shared/rest/media.ts +0 -65
- package/skills/wix-headless-fast/references/shared/seed/images.mjs +0 -145
- package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +0 -608
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartButton.tsx +0 -26
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/CartDrawer.tsx +0 -155
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/FilterPanel.tsx +0 -266
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +0 -95
- package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +0 -136
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useCart.ts +0 -48
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +0 -39
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +0 -34
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +0 -121
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-store.ts +0 -107
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +0 -138
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +0 -309
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +0 -133
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +0 -147
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +0 -289
- package/skills/wix-headless-fast/references/storefront/app/wix/storefront/types.ts +0 -167
- package/skills/wix-headless-fast/references/storefront/app-astro/layouts/SiteLayout.astro +0 -65
- package/skills/wix-headless-fast/references/storefront/lock/astro/package-lock.json +0 -12247
- package/skills/wix-headless-fast/references/storefront/rest/cart.ts +0 -132
- package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +0 -123
- package/skills/wix-headless-fast/references/storefront/seed/SEED.md +0 -124
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +0 -525
|
@@ -1,332 +0,0 @@
|
|
|
1
|
-
// One form as a framework-free store — the logic behind useWixForm, usable from React (useWixForm
|
|
2
|
-
// wraps it with useSyncExternalStore), from a static page or Vue/Svelte (subscribe and render), or
|
|
3
|
-
// as the specification for a port. Schema in, validated submission out, minus the markup: load the
|
|
4
|
-
// form, hold the visitor's values, validate them against the schema's own rules in our wording,
|
|
5
|
-
// upload attachments, create the submission, and map a rejection back onto the controls.
|
|
6
|
-
//
|
|
7
|
-
// It imports the data layer by names the REST twin exports identically (getForm, uploadFiles,
|
|
8
|
-
// createSubmission, submissionErrors, toSubmissionValues), so the same file runs over the SDK in
|
|
9
|
-
// Astro/React and over REST on a static page.
|
|
10
|
-
//
|
|
11
|
-
// SSR-friendly: pass a server-fetched FormDto as `initialForm` and no client fetch happens;
|
|
12
|
-
// `start()` then does nothing. Without it `start()` loads the schema. One store per mounted form:
|
|
13
|
-
// createFormStore(), not a singleton — a page can hold two forms.
|
|
14
|
-
import { getForm } from "./forms";
|
|
15
|
-
import {
|
|
16
|
-
createSubmission,
|
|
17
|
-
normalizePhone,
|
|
18
|
-
submissionErrors,
|
|
19
|
-
toSubmissionValues,
|
|
20
|
-
uploadFiles,
|
|
21
|
-
} from "./submissions";
|
|
22
|
-
import type { FormDto, FormErrors, FormFieldDto, FormValues } from "./types";
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* The key in `errors` for a message that belongs to the FORM rather than one field — a schema
|
|
26
|
-
* that failed to load, or a rejection with no per-field violations. `@` cannot appear in a
|
|
27
|
-
* form `target`, so this never collides with a field's own error.
|
|
28
|
-
*/
|
|
29
|
-
export const FORM_ERROR = "@form";
|
|
30
|
-
|
|
31
|
-
/** The empty form: every field at a value of the shape its control binds to. */
|
|
32
|
-
export function defaultValues(fields: FormFieldDto[]): FormValues {
|
|
33
|
-
const values: FormValues = {};
|
|
34
|
-
for (const f of fields) values[f.target] = Array.isArray(f.defaultValue) ? [...f.defaultValue] : f.defaultValue;
|
|
35
|
-
return values;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Check one field's value against its own schema. A plain function — usable outside the store,
|
|
40
|
-
* and the place to look when a message needs rewording.
|
|
41
|
-
*
|
|
42
|
-
* Every rule comes from the schema, never from a field's NAME. (The classic mistake is keying
|
|
43
|
-
* the email check on `target === "email"`; deriving it from `format` means an owner-added
|
|
44
|
-
* PHONE/URL/length rule is honored with no code change.)
|
|
45
|
-
*
|
|
46
|
-
* A client check LAXER than the server's is worse than none — the visitor then learns about
|
|
47
|
-
* the problem only after a round trip, in the server's wording rather than yours.
|
|
48
|
-
*/
|
|
49
|
-
export function validateValue(field: FormFieldDto, value: unknown): string {
|
|
50
|
-
const v = String(value ?? "").trim();
|
|
51
|
-
const rules = field.validation;
|
|
52
|
-
|
|
53
|
-
if (field.required && !v) return `${field.label} is required.`;
|
|
54
|
-
if (!v) return ""; // optional and empty → fine
|
|
55
|
-
|
|
56
|
-
if (field.control === "number" || field.control === "rating") {
|
|
57
|
-
// The control hands back a string, so parse before comparing: "9" > 10 is false but
|
|
58
|
-
// "9" > "10" is true.
|
|
59
|
-
const n = Number(v);
|
|
60
|
-
if (!Number.isFinite(n)) return `${field.label} must be a number.`;
|
|
61
|
-
if (rules.minimum != null && n < rules.minimum) return `${field.label} must be ${rules.minimum} or more.`;
|
|
62
|
-
if (rules.maximum != null && n > rules.maximum) return `${field.label} must be ${rules.maximum} or less.`;
|
|
63
|
-
return "";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (rules.minLength && v.length < rules.minLength)
|
|
67
|
-
return `${field.label} must be at least ${rules.minLength} characters.`;
|
|
68
|
-
if (rules.maxLength && v.length > rules.maxLength)
|
|
69
|
-
return `${field.label} must be at most ${rules.maxLength} characters.`;
|
|
70
|
-
if (rules.format === "EMAIL" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v))
|
|
71
|
-
return "Please enter a valid email address.";
|
|
72
|
-
if (rules.format === "URL" && !/^https?:\/\/.+/.test(v)) return "Please enter a valid URL.";
|
|
73
|
-
// PHONE is E.164 server-side: leading +, country code, digits. Strip formatting first —
|
|
74
|
-
// visitors add spaces, dashes and parens, and rejecting those is a UX bug, not validation.
|
|
75
|
-
if (rules.format === "PHONE" && !/^\+[1-9]\d{6,14}$/.test(normalizePhone(v)))
|
|
76
|
-
return "Use international format, starting with + and the country code.";
|
|
77
|
-
if (rules.pattern && !new RegExp(rules.pattern).test(v))
|
|
78
|
-
return `${field.label} is not in the expected format.`;
|
|
79
|
-
return "";
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* One field's error entries, keyed the way the controls are named. A plain field yields at most
|
|
84
|
-
* one (`target`); an ADDRESS yields one per failing subfield (`target/sub`).
|
|
85
|
-
*
|
|
86
|
-
* An address subfield gets the `required` check only — `country` and `subdivision` are
|
|
87
|
-
* country-dependent enums the schema does not enumerate, so their content is the server's call.
|
|
88
|
-
*/
|
|
89
|
-
export function errorsForField(field: FormFieldDto, values: FormValues): FormErrors {
|
|
90
|
-
const errors: FormErrors = {};
|
|
91
|
-
|
|
92
|
-
if (field.control === "address") {
|
|
93
|
-
const parts = (values[field.target] ?? {}) as Record<string, unknown>;
|
|
94
|
-
for (const { sub, label, required } of field.addressParts) {
|
|
95
|
-
if (required && !String(parts[sub] ?? "").trim()) errors[`${field.target}/${sub}`] = `${label} is required.`;
|
|
96
|
-
}
|
|
97
|
-
return errors;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
if (field.control === "file" || field.control === "signature") {
|
|
101
|
-
// Files are File objects, which no string rule can judge — check the count instead.
|
|
102
|
-
const picked = ([] as unknown[]).concat(values[field.target] ?? []).filter(Boolean);
|
|
103
|
-
const limit = field.validation.fileLimit;
|
|
104
|
-
if (field.required && !picked.length) errors[field.target] = `${field.label} is required.`;
|
|
105
|
-
else if (limit && picked.length > limit)
|
|
106
|
-
errors[field.target] = `Attach at most ${limit} file${limit === 1 ? "" : "s"}.`;
|
|
107
|
-
return errors;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (field.inputType === "ARRAY") {
|
|
111
|
-
const picked = (Array.isArray(values[field.target]) ? (values[field.target] as unknown[]) : []).filter(Boolean);
|
|
112
|
-
const { minItems, maxItems } = field.validation;
|
|
113
|
-
if (field.required && !picked.length) errors[field.target] = `${field.label} is required.`;
|
|
114
|
-
else if (minItems && picked.length < minItems) errors[field.target] = `Choose at least ${minItems}.`;
|
|
115
|
-
else if (maxItems && picked.length > maxItems) errors[field.target] = `Choose at most ${maxItems}.`;
|
|
116
|
-
return errors;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
if (field.control === "checkbox") {
|
|
120
|
-
if (field.required && values[field.target] !== true) errors[field.target] = `${field.label} is required.`;
|
|
121
|
-
return errors;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const message = validateValue(field, values[field.target]);
|
|
125
|
-
if (message) errors[field.target] = message;
|
|
126
|
-
return errors;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function errorsForForm(fields: FormFieldDto[], values: FormValues): FormErrors {
|
|
130
|
-
const errors: FormErrors = {};
|
|
131
|
-
for (const field of fields) Object.assign(errors, errorsForField(field, values));
|
|
132
|
-
return errors;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* Move focus to a control by input name. `namedItem` returns a RadioNodeList for a radio or
|
|
137
|
-
* checkbox group and an element for everything else — a guard checking only for an element
|
|
138
|
-
* silently skips every choice group. FOCUS, not scrollIntoView: scrolling moves the viewport and
|
|
139
|
-
* nothing else, leaving a keyboard or screen-reader user where they were.
|
|
140
|
-
*/
|
|
141
|
-
export function focusControl(formEl: HTMLFormElement | null, name: string): void {
|
|
142
|
-
const control = formEl?.elements?.namedItem?.(name) as unknown;
|
|
143
|
-
const node =
|
|
144
|
-
typeof RadioNodeList !== "undefined" && control instanceof RadioNodeList
|
|
145
|
-
? (control[0] as HTMLElement | undefined)
|
|
146
|
-
: (control as HTMLElement | undefined);
|
|
147
|
-
node?.focus?.();
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export interface FormStoreOptions {
|
|
151
|
-
/** The form to load (the seed's `formId`). Ignored when `initialForm` is given. */
|
|
152
|
-
formId: string;
|
|
153
|
-
/** Server-fetched form (Astro frontmatter) — skips the client fetch entirely. */
|
|
154
|
-
initialForm?: FormDto;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** Everything a form surface renders from. Read it with getState() or through a subscription. */
|
|
158
|
-
export interface FormState {
|
|
159
|
-
/** null while the schema is loading — render a skeleton, not an empty form. */
|
|
160
|
-
form: FormDto | null;
|
|
161
|
-
/** `target` → current value. Arrays for multi-choice and files, objects for an address. */
|
|
162
|
-
values: FormValues;
|
|
163
|
-
/** `target` (or `target/sub`) → a visitor-facing message; errors[FORM_ERROR] is form-level. */
|
|
164
|
-
errors: FormErrors;
|
|
165
|
-
/** Loading the schema, or submitting. */
|
|
166
|
-
loading: boolean;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** A submit event as the store needs it — a React SyntheticEvent or a native Event both fit. */
|
|
170
|
-
export type FormSubmitEvent = { preventDefault?: () => void; currentTarget?: unknown };
|
|
171
|
-
|
|
172
|
-
export interface FormStore {
|
|
173
|
-
getState(): FormState;
|
|
174
|
-
subscribe(listener: () => void): () => void;
|
|
175
|
-
/** Load the schema when no `initialForm` was given. Call once when mounted. */
|
|
176
|
-
start(): void;
|
|
177
|
-
/** Stop reacting; drop a late schema response. */
|
|
178
|
-
stop(): void;
|
|
179
|
-
setValues(next: FormValues | ((prev: FormValues) => FormValues)): void;
|
|
180
|
-
/** One field's value — what a control's change handler calls. */
|
|
181
|
-
setValue(target: string, value: unknown): void;
|
|
182
|
-
/** One field, one address subfield (`target/sub`), or the whole form when called with nothing. */
|
|
183
|
-
validate(target?: string): boolean;
|
|
184
|
-
/**
|
|
185
|
-
* The `onSubmit` handler. Client validation in our wording first (focus lands on the first
|
|
186
|
-
* invalid control), then uploads, then the create. Resolves TRUE when the submission was
|
|
187
|
-
* created — that IS the success signal; the values are back at the schema's defaults.
|
|
188
|
-
*/
|
|
189
|
-
submit(event?: FormSubmitEvent): Promise<boolean>;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
export function createFormStore({ formId, initialForm }: FormStoreOptions): FormStore {
|
|
193
|
-
let form: FormDto | null = initialForm ?? null;
|
|
194
|
-
// The empty form to reset to after a successful submit — the schema's own defaults.
|
|
195
|
-
let empty: FormValues = defaultValues(form?.fields ?? []);
|
|
196
|
-
let values: FormValues = empty;
|
|
197
|
-
let errors: FormErrors = {};
|
|
198
|
-
let loading = !initialForm;
|
|
199
|
-
let started = false;
|
|
200
|
-
let generation = 0;
|
|
201
|
-
const listeners = new Set<() => void>();
|
|
202
|
-
let snapshot: FormState | null = null;
|
|
203
|
-
const emit = () => { snapshot = null; for (const fn of listeners) fn(); };
|
|
204
|
-
const fields = (): FormFieldDto[] => form?.fields ?? [];
|
|
205
|
-
|
|
206
|
-
function getState(): FormState {
|
|
207
|
-
if (snapshot) return snapshot;
|
|
208
|
-
snapshot = { form, values, errors, loading };
|
|
209
|
-
return snapshot;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function adopt(loaded: FormDto): void {
|
|
213
|
-
form = loaded;
|
|
214
|
-
// Seed the controls once the schema is in: every control is controlled from the first
|
|
215
|
-
// render, so each target holds a value of the right shape before any of them mount.
|
|
216
|
-
empty = defaultValues(loaded.fields);
|
|
217
|
-
values = empty;
|
|
218
|
-
errors = {};
|
|
219
|
-
loading = false;
|
|
220
|
-
emit();
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
function setErrors(next: FormErrors): void { errors = next; emit(); }
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
getState,
|
|
227
|
-
subscribe(fn) { listeners.add(fn); return () => listeners.delete(fn); },
|
|
228
|
-
start() {
|
|
229
|
-
if (started) return;
|
|
230
|
-
started = true;
|
|
231
|
-
if (form) return; // the SSR pass already answered this
|
|
232
|
-
if (!formId) {
|
|
233
|
-
loading = false;
|
|
234
|
-
errors = { [FORM_ERROR]: "No formId — pass one from the seed's forms map." };
|
|
235
|
-
emit();
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
const id = ++generation;
|
|
239
|
-
loading = true;
|
|
240
|
-
emit();
|
|
241
|
-
getForm(formId)
|
|
242
|
-
.then((loaded) => { if (started && generation === id) adopt(loaded); })
|
|
243
|
-
.catch((e: unknown) => {
|
|
244
|
-
if (!started || generation !== id) return;
|
|
245
|
-
// Fail loudly. A form that cannot load is a setup problem — never fall back to a
|
|
246
|
-
// hand-built form, which would drop real enquiries silently.
|
|
247
|
-
loading = false;
|
|
248
|
-
errors = { [FORM_ERROR]: e instanceof Error ? e.message : "Could not load the form." };
|
|
249
|
-
emit();
|
|
250
|
-
});
|
|
251
|
-
},
|
|
252
|
-
stop() { started = false; generation++; },
|
|
253
|
-
setValues(next) {
|
|
254
|
-
values = typeof next === "function" ? next(values) : next;
|
|
255
|
-
emit();
|
|
256
|
-
},
|
|
257
|
-
setValue(target, value) {
|
|
258
|
-
values = { ...values, [target]: value };
|
|
259
|
-
emit();
|
|
260
|
-
},
|
|
261
|
-
validate(target) {
|
|
262
|
-
if (!target) {
|
|
263
|
-
const all = errorsForForm(fields(), values);
|
|
264
|
-
setErrors(all);
|
|
265
|
-
return Object.keys(all).length === 0;
|
|
266
|
-
}
|
|
267
|
-
const key = String(target);
|
|
268
|
-
const field = fields().find((f) => f.target === key.split("/")[0]);
|
|
269
|
-
if (!field) return true;
|
|
270
|
-
|
|
271
|
-
// The keys this call owns, so a re-check CLEARS what it fixed as well as flagging what it
|
|
272
|
-
// did not: one key for a plain field, or every subfield when an address itself is named.
|
|
273
|
-
const owned = key.includes("/")
|
|
274
|
-
? [key]
|
|
275
|
-
: field.control === "address"
|
|
276
|
-
? field.addressParts.map(({ sub }) => `${field.target}/${sub}`)
|
|
277
|
-
: [field.target];
|
|
278
|
-
|
|
279
|
-
const found = errorsForField(field, values);
|
|
280
|
-
const next = { ...errors };
|
|
281
|
-
for (const k of owned) {
|
|
282
|
-
delete next[k];
|
|
283
|
-
if (found[k]) next[k] = found[k];
|
|
284
|
-
}
|
|
285
|
-
setErrors(next);
|
|
286
|
-
return owned.every((k) => !found[k]);
|
|
287
|
-
},
|
|
288
|
-
async submit(event) {
|
|
289
|
-
event?.preventDefault?.();
|
|
290
|
-
// Capture the <form> NOW: React clears currentTarget once the handler returns, so reading
|
|
291
|
-
// it after the await below (to focus a server-rejected control) comes back null.
|
|
292
|
-
const formEl = (event?.currentTarget ?? null) as HTMLFormElement | null;
|
|
293
|
-
if (!form) return false;
|
|
294
|
-
const current = form;
|
|
295
|
-
const currentFields = current.fields;
|
|
296
|
-
|
|
297
|
-
// Client pass first, so the visitor gets inline feedback in OUR wording before a round trip.
|
|
298
|
-
const clientErrors = errorsForForm(currentFields, values);
|
|
299
|
-
if (Object.keys(clientErrors).length) {
|
|
300
|
-
setErrors(clientErrors);
|
|
301
|
-
focusControl(formEl, Object.keys(clientErrors)[0]);
|
|
302
|
-
return false;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
loading = true;
|
|
306
|
-
emit();
|
|
307
|
-
try {
|
|
308
|
-
// Attachments go up FIRST — a File is not something the submission API takes, and its
|
|
309
|
-
// value is the upload URL this hands back. No file fields → nothing happens here.
|
|
310
|
-
const uploaded = await uploadFiles(current.id, currentFields, values);
|
|
311
|
-
values = uploaded; // keep the URLs, so a rejection on another field never re-uploads
|
|
312
|
-
emit();
|
|
313
|
-
await createSubmission(current.id, toSubmissionValues(currentFields, uploaded));
|
|
314
|
-
errors = {};
|
|
315
|
-
values = empty; // back to the schema's defaults, ready for another
|
|
316
|
-
return true;
|
|
317
|
-
} catch (e) {
|
|
318
|
-
const mapped = submissionErrors(e, currentFields);
|
|
319
|
-
if (Object.keys(mapped).length) {
|
|
320
|
-
errors = mapped;
|
|
321
|
-
focusControl(formEl, Object.keys(mapped)[0]);
|
|
322
|
-
} else {
|
|
323
|
-
errors = { [FORM_ERROR]: e instanceof Error ? e.message : "Could not send the form. Please try again." };
|
|
324
|
-
}
|
|
325
|
-
return false;
|
|
326
|
-
} finally {
|
|
327
|
-
loading = false;
|
|
328
|
-
emit();
|
|
329
|
-
}
|
|
330
|
-
},
|
|
331
|
-
};
|
|
332
|
-
}
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
// Form-schema rules and DTO mapping — transport-agnostic, imported by BOTH transports: ./forms.ts
|
|
2
|
-
// (the SDK, managed Astro and React) and the REST twin in references/forms/rest/forms.ts (fetch, a
|
|
3
|
-
// static site or a port to another language). Every rule about how a raw Form Schemas v4 entity
|
|
4
|
-
// flattens into a FormDto lives HERE, once. A raw form may come from the SDK (`_id`) or from REST
|
|
5
|
-
// (`id`); the mappers accept both. Imports are type-only so a strip to JS emits no imports.
|
|
6
|
-
// docs: https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields.md
|
|
7
|
-
import type {
|
|
8
|
-
FormAddressPart,
|
|
9
|
-
FormChoice,
|
|
10
|
-
FormControl,
|
|
11
|
-
FormDto,
|
|
12
|
-
FormFieldDto,
|
|
13
|
-
FormValidation,
|
|
14
|
-
} from "./types";
|
|
15
|
-
|
|
16
|
-
/** A raw Form Schemas v4 entity as either transport returns it. */
|
|
17
|
-
export type Raw = Record<string, any>;
|
|
18
|
-
|
|
19
|
-
/** The Wix Forms app namespace. Every form this vertical touches lives here. */
|
|
20
|
-
export const FORMS_NAMESPACE = "wix.form_app.form";
|
|
21
|
-
|
|
22
|
-
const id = (raw: Raw | undefined): string => raw?._id ?? raw?.id ?? "";
|
|
23
|
-
|
|
24
|
-
// A field's settings nest TWO levels deep, under blocks named after its own enums:
|
|
25
|
-
// inputOptions.<inputType block>.<componentType block>.label
|
|
26
|
-
// These tables resolve those names. Spelling them at a call site is one typo away from
|
|
27
|
-
// silently reading back no label at all — which is the whole reason this file exists.
|
|
28
|
-
const INPUT_BLOCK: Record<string, string> = {
|
|
29
|
-
STRING: "stringOptions",
|
|
30
|
-
NUMBER: "numberOptions",
|
|
31
|
-
BOOLEAN: "booleanOptions",
|
|
32
|
-
ARRAY: "arrayOptions",
|
|
33
|
-
ADDRESS: "addressOptions",
|
|
34
|
-
WIX_FILE: "wixFileOptions",
|
|
35
|
-
PAYMENT: "paymentOptions",
|
|
36
|
-
SCHEDULING: "schedulingOptions",
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
const COMPONENT_BLOCK: Record<string, string> = {
|
|
40
|
-
TEXT_INPUT: "textInputOptions",
|
|
41
|
-
NUMBER_INPUT: "numberInputOptions",
|
|
42
|
-
RATING_INPUT: "ratingInputOptions",
|
|
43
|
-
PHONE_INPUT: "phoneInputOptions",
|
|
44
|
-
DATE_INPUT: "dateInputOptions",
|
|
45
|
-
DATE_PICKER: "datePickerOptions",
|
|
46
|
-
DATE_TIME: "dateTimeOptions",
|
|
47
|
-
TIME_INPUT: "timeInputOptions",
|
|
48
|
-
CHECKBOX: "checkboxOptions",
|
|
49
|
-
CHECKBOX_GROUP: "checkboxGroupOptions",
|
|
50
|
-
RADIO_GROUP: "radioGroupOptions",
|
|
51
|
-
DROPDOWN: "dropdownOptions",
|
|
52
|
-
TAGS: "tagsOptions",
|
|
53
|
-
MULTILINE_ADDRESS: "multilineAddressOptions",
|
|
54
|
-
FILE_UPLOAD: "fileUploadOptions",
|
|
55
|
-
SIGNATURE: "signatureOptions",
|
|
56
|
-
FIXED_PAYMENT: "fixedPaymentOptions",
|
|
57
|
-
PAYMENT_INPUT: "paymentInputOptions",
|
|
58
|
-
DONATION_INPUT: "donationInputOptions",
|
|
59
|
-
APPOINTMENT: "appointmentOptions",
|
|
60
|
-
SERVICES_DROPDOWN: "servicesDropdownOptions",
|
|
61
|
-
SERVICES_CHECKBOX_GROUP: "servicesCheckboxGroupOptions",
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
// componentType → the control to render. Several field kinds share one component (short and
|
|
65
|
-
// long answer are both TEXT_INPUT), so `format` and `inputType` refine it below.
|
|
66
|
-
const CONTROL: Record<string, FormControl> = {
|
|
67
|
-
TEXT_INPUT: "text",
|
|
68
|
-
NUMBER_INPUT: "number",
|
|
69
|
-
RATING_INPUT: "rating",
|
|
70
|
-
PHONE_INPUT: "phone",
|
|
71
|
-
DATE_INPUT: "date",
|
|
72
|
-
DATE_PICKER: "date",
|
|
73
|
-
DATE_TIME: "datetime",
|
|
74
|
-
TIME_INPUT: "time",
|
|
75
|
-
CHECKBOX: "checkbox",
|
|
76
|
-
CHECKBOX_GROUP: "checkboxGroup",
|
|
77
|
-
RADIO_GROUP: "radio",
|
|
78
|
-
DROPDOWN: "select",
|
|
79
|
-
TAGS: "tags",
|
|
80
|
-
MULTILINE_ADDRESS: "address",
|
|
81
|
-
FILE_UPLOAD: "file",
|
|
82
|
-
SIGNATURE: "signature",
|
|
83
|
-
FIXED_PAYMENT: "payment",
|
|
84
|
-
PAYMENT_INPUT: "payment",
|
|
85
|
-
DONATION_INPUT: "payment",
|
|
86
|
-
SERVICES_DROPDOWN: "select",
|
|
87
|
-
SERVICES_CHECKBOX_GROUP: "checkboxGroup",
|
|
88
|
-
APPOINTMENT: "appointment",
|
|
89
|
-
};
|
|
90
|
-
|
|
91
|
-
// An enum missing from a table means Wix added a type. Say so ONCE — a silent empty block
|
|
92
|
-
// renders a field labelled by its storage key with none of its settings.
|
|
93
|
-
const warned = new Set<string>();
|
|
94
|
-
function blockName(table: Record<string, string>, key: string | undefined, what: string): string | undefined {
|
|
95
|
-
const name = key ? table[key] : undefined;
|
|
96
|
-
if (!name && key && !warned.has(what + key)) {
|
|
97
|
-
warned.add(what + key);
|
|
98
|
-
console.warn(
|
|
99
|
-
`forms: no ${what} block mapped for "${key}" — that field renders without its label or ` +
|
|
100
|
-
`options. Add it to the table in wix/forms/forms-core.ts.`,
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
return name;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/** A BOOLEAN consent field labels itself with Ricos rich content; take its plain text. */
|
|
107
|
-
export function plainText(label: unknown): string {
|
|
108
|
-
if (typeof label === "string") return label;
|
|
109
|
-
const nodes = (label as Raw)?.nodes;
|
|
110
|
-
if (!Array.isArray(nodes)) return "";
|
|
111
|
-
const walk = (list: Raw[]): string =>
|
|
112
|
-
list
|
|
113
|
-
.map((n) => (n?.textData?.text ?? "") + (Array.isArray(n?.nodes) ? walk(n.nodes) : ""))
|
|
114
|
-
.join("");
|
|
115
|
-
return walk(nodes).trim();
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** `postalCode` → "Postal code". A subfield is a key; the schema carries no label for it. */
|
|
119
|
-
export function humanizeSub(sub: string): string {
|
|
120
|
-
const words = sub.replace(/([A-Z])|(\d+)/g, " $1$2").toLowerCase().trim();
|
|
121
|
-
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export function toField(raw: Raw): FormFieldDto {
|
|
125
|
-
const input = raw.inputOptions ?? {};
|
|
126
|
-
const inputType: string = input.inputType ?? "";
|
|
127
|
-
const optionsBlock: Raw = input[blockName(INPUT_BLOCK, inputType, "inputType") ?? ""] ?? {};
|
|
128
|
-
const componentType: string = optionsBlock.componentType ?? "";
|
|
129
|
-
const component: Raw = optionsBlock[blockName(COMPONENT_BLOCK, componentType, "componentType") ?? ""] ?? {};
|
|
130
|
-
const rules: Raw = optionsBlock.validation ?? {};
|
|
131
|
-
|
|
132
|
-
const validation: FormValidation = {
|
|
133
|
-
...(rules.format ? { format: rules.format } : {}),
|
|
134
|
-
...(rules.minLength != null ? { minLength: rules.minLength } : {}),
|
|
135
|
-
...(rules.maxLength != null ? { maxLength: rules.maxLength } : {}),
|
|
136
|
-
...(rules.pattern ? { pattern: rules.pattern } : {}),
|
|
137
|
-
// JSON-Schema spelling everywhere else in this block; minValue/maxValue is read too since
|
|
138
|
-
// the public reference documents neither and an older schema may carry them.
|
|
139
|
-
...((rules.minimum ?? rules.minValue) != null ? { minimum: rules.minimum ?? rules.minValue } : {}),
|
|
140
|
-
...((rules.maximum ?? rules.maxValue) != null ? { maximum: rules.maximum ?? rules.maxValue } : {}),
|
|
141
|
-
...(rules.fileLimit != null ? { fileLimit: rules.fileLimit } : {}),
|
|
142
|
-
...(rules.minItems != null ? { minItems: rules.minItems } : {}),
|
|
143
|
-
...(rules.maxItems != null ? { maxItems: rules.maxItems } : {}),
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
// TEXT_INPUT covers short answer, long answer, email, url and phone — and a long answer is
|
|
147
|
-
// NOT flagged on the component (no `multiline`, no `numberOfLines`). Only `identifier` says
|
|
148
|
-
// TEXT_AREA, which is why it is carried on the DTO.
|
|
149
|
-
const identifier: string = raw.identifier ?? "";
|
|
150
|
-
let control: FormControl = CONTROL[componentType] ?? "unknown";
|
|
151
|
-
// A product list is a PAYMENT field wearing a CHECKBOX_GROUP component — typed by its
|
|
152
|
-
// component it would look like a multi-choice and be bound to an array of strings, when what
|
|
153
|
-
// it submits is a payment structure. The input type wins.
|
|
154
|
-
if (inputType === "PAYMENT") control = "payment";
|
|
155
|
-
if (control === "text") {
|
|
156
|
-
if (identifier === "TEXT_AREA" || component.multiline || component.numberOfLines > 1) control = "textarea";
|
|
157
|
-
else if (rules.format === "EMAIL") control = "email";
|
|
158
|
-
else if (rules.format === "URL") control = "url";
|
|
159
|
-
else if (rules.format === "PHONE") control = "phone";
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const choices: FormChoice[] = Array.isArray(component.options)
|
|
163
|
-
? component.options.map((o: Raw) => ({ value: String(o.value), label: String(o.label ?? o.value) }))
|
|
164
|
-
: [];
|
|
165
|
-
|
|
166
|
-
const addressParts: FormAddressPart[] = Object.entries((rules.fields ?? {}) as Raw)
|
|
167
|
-
// Only addressLine2 carries a visibility setting; hide it when the owner turned it off.
|
|
168
|
-
.filter(([sub]) => component.fieldSettings?.[sub]?.show !== false)
|
|
169
|
-
.map(([sub, cfg]) => ({ sub, label: humanizeSub(sub), required: (cfg as Raw)?.required ?? false }));
|
|
170
|
-
|
|
171
|
-
const target: string = input.target ?? "";
|
|
172
|
-
const label = plainText(component.label) || target;
|
|
173
|
-
|
|
174
|
-
// Every control is controlled from the first render, so each field starts at a value of the
|
|
175
|
-
// right SHAPE — [] for multi-choice and files, {} for an address, the owner's prefill else "".
|
|
176
|
-
// Wix spells the prefill `default` on most components and `defaultValue` on a rating.
|
|
177
|
-
const prefill = component.default ?? component.defaultValue;
|
|
178
|
-
const defaultValue =
|
|
179
|
-
control === "address" ? {} :
|
|
180
|
-
inputType === "ARRAY" || inputType === "WIX_FILE" ? [] :
|
|
181
|
-
// a consent checkbox is a BOOLEAN: "" would bind to `checked` as false and work by
|
|
182
|
-
// accident, but the DTO promises the shape its control writes back
|
|
183
|
-
inputType === "BOOLEAN" ? ((prefill as boolean | undefined) ?? false) :
|
|
184
|
-
(prefill as string | number | boolean | undefined) ?? "";
|
|
185
|
-
|
|
186
|
-
return {
|
|
187
|
-
target,
|
|
188
|
-
label,
|
|
189
|
-
control,
|
|
190
|
-
required: input.required ?? false,
|
|
191
|
-
...(component.placeholder ? { placeholder: String(component.placeholder) } : {}),
|
|
192
|
-
...(component.description ? { description: plainText(component.description) } : {}),
|
|
193
|
-
defaultValue,
|
|
194
|
-
choices,
|
|
195
|
-
addressParts,
|
|
196
|
-
validation,
|
|
197
|
-
...(component.defaultCountryCode || rules.phoneOptions?.allowedCountryCodes?.[0]
|
|
198
|
-
? { phoneCountry: component.defaultCountryCode ?? rules.phoneOptions.allowedCountryCodes[0] }
|
|
199
|
-
: {}),
|
|
200
|
-
identifier,
|
|
201
|
-
inputType,
|
|
202
|
-
componentType,
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Display order comes from `steps[].layout`, NOT from `formFields[]` array order. Sort WITHIN
|
|
208
|
-
* each step and concatenate in step order: `row` restarts at 0 in every step, so one sort
|
|
209
|
-
* across the flattened list interleaves them. A field the owner never placed sorts LAST — it
|
|
210
|
-
* still stores values. The submit button is `fieldType: "DISPLAY"`, so filtering to INPUT
|
|
211
|
-
* drops it automatically.
|
|
212
|
-
*/
|
|
213
|
-
export function orderedInputs(raw: Raw): Raw[] {
|
|
214
|
-
const order = new Map<string, number>(
|
|
215
|
-
((raw.steps ?? []) as Raw[])
|
|
216
|
-
.flatMap((s) =>
|
|
217
|
-
((s.layout?.large?.items ?? s.layout?.medium?.items ?? s.layout?.small?.items ?? []) as Raw[])
|
|
218
|
-
.slice()
|
|
219
|
-
.sort((a, b) => a.row - b.row || a.column - b.column),
|
|
220
|
-
)
|
|
221
|
-
.map((item, i) => [item.fieldId as string, i] as const),
|
|
222
|
-
);
|
|
223
|
-
return ((raw.formFields ?? []) as Raw[])
|
|
224
|
-
.filter((f) => f.fieldType === "INPUT" && !f.hidden)
|
|
225
|
-
.sort((a, b) => (order.get(a.id) ?? Infinity) - (order.get(b.id) ?? Infinity));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export function toForm(raw: Raw): FormDto {
|
|
229
|
-
const submit = ((raw.formFields ?? []) as Raw[]).find((f) => f.identifier === "SUBMIT_BUTTON");
|
|
230
|
-
return {
|
|
231
|
-
id: id(raw),
|
|
232
|
-
name: raw.name ?? "",
|
|
233
|
-
fields: orderedInputs(raw).map(toField),
|
|
234
|
-
// The submit wording lives on a DISPLAY field, nested under pageNavigationOptions because
|
|
235
|
-
// one control drives both multi-page navigation and the final submit.
|
|
236
|
-
submitText: submit?.displayOptions?.pageNavigationOptions?.submitText ?? "",
|
|
237
|
-
};
|
|
238
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// Wix Forms schema reads — the only file that touches a raw Form on this transport. Returns flat
|
|
2
|
-
// FormDto / FormFieldDto from ./types. The flattening rules live in ./forms-core (shared with the
|
|
3
|
-
// REST twin in references/forms/rest/); this file is the transport only. Copy as-is; extend by
|
|
4
|
-
// adding functions.
|
|
5
|
-
//
|
|
6
|
-
// The reads go over `wixFetch` (the SDK's own authenticated fetch), NOT the `forms` module of
|
|
7
|
-
// @wix/forms: that generated module is 15 MB. Bundled server-side it pushes a Wix deploy past its
|
|
8
|
-
// size limit (HTTP 413 on release); bundled client-side every visitor downloads it. The
|
|
9
|
-
// submissions module (./submissions.ts) is 100 KB and stays on the SDK.
|
|
10
|
-
//
|
|
11
|
-
// The visitor token is enough, and the spec says otherwise. Every schema read is listed under
|
|
12
|
-
// the owner scope `SCOPE.FORMS.VIEW-FORM`, and returns 200 on an anonymous visitor: Wix grants
|
|
13
|
-
// implicit visitor access so a published site can render its own forms. Do NOT add a backend, a
|
|
14
|
-
// connector token, or auth.elevate to make a form load.
|
|
15
|
-
//
|
|
16
|
-
// docs: https://dev.wix.com/docs/sdk/business-solutions/forms/forms/get-form.md
|
|
17
|
-
// docs: https://dev.wix.com/docs/sdk/business-solutions/forms/forms/list-forms.md
|
|
18
|
-
// docs: https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields.md
|
|
19
|
-
import { wixFetch } from "../sdk";
|
|
20
|
-
import { FORMS_NAMESPACE, toForm, type Raw } from "./forms-core";
|
|
21
|
-
import type { FormDto } from "./types";
|
|
22
|
-
|
|
23
|
-
export { FORMS_NAMESPACE };
|
|
24
|
-
|
|
25
|
-
async function getJson(path: string): Promise<Raw> {
|
|
26
|
-
const res = await wixFetch(path);
|
|
27
|
-
if (!res.ok) throw new Error(`forms: GET ${path} failed (${res.status}).`);
|
|
28
|
-
return (await res.json()) as Raw;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Read one form by id. Throws when the id is wrong or the form was deleted — a form that
|
|
33
|
-
* cannot load is a setup problem, so fail loudly rather than rendering a hand-built fallback
|
|
34
|
-
* that would drop real enquiries silently.
|
|
35
|
-
*/
|
|
36
|
-
export async function getForm(formId: string): Promise<FormDto> {
|
|
37
|
-
// GET /form-schema-service/v4/forms/{formId} → { form }; 404 FORM_NOT_FOUND on a wrong id.
|
|
38
|
-
const res = await getJson(`/form-schema-service/v4/forms/${encodeURIComponent(formId)}`);
|
|
39
|
-
if (!res?.form) throw new Error(`forms: form "${formId}" not found.`);
|
|
40
|
-
return toForm(res.form as Raw);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Every form on the site, in the Wix Forms namespace. Use ONE call for several forms on a page
|
|
45
|
-
* rather than a getForm each.
|
|
46
|
-
*
|
|
47
|
-
* Returns only ENABLED forms — a form the owner disabled vanishes from the listing rather
|
|
48
|
-
* than erroring. That is usually right for a public site.
|
|
49
|
-
*/
|
|
50
|
-
export async function listForms(): Promise<FormDto[]> {
|
|
51
|
-
// GET /form-schema-service/v4/forms?namespace=… → { forms }; the namespace is required (400 without).
|
|
52
|
-
const res = await getJson(`/form-schema-service/v4/forms?namespace=${encodeURIComponent(FORMS_NAMESPACE)}`);
|
|
53
|
-
return ((res?.forms ?? []) as Raw[]).map(toForm);
|
|
54
|
-
}
|