@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,246 +1,31 @@
|
|
|
1
|
-
// Wix Forms schema reads
|
|
2
|
-
//
|
|
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.
|
|
3
5
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
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.
|
|
8
15
|
//
|
|
9
16
|
// docs: https://dev.wix.com/docs/sdk/business-solutions/forms/forms/get-form.md
|
|
10
17
|
// docs: https://dev.wix.com/docs/sdk/business-solutions/forms/forms/list-forms.md
|
|
11
18
|
// docs: https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/about-form-fields.md
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import type {
|
|
15
|
-
FormAddressPart,
|
|
16
|
-
FormChoice,
|
|
17
|
-
FormControl,
|
|
18
|
-
FormDto,
|
|
19
|
-
FormFieldDto,
|
|
20
|
-
FormValidation,
|
|
21
|
-
} from "./types";
|
|
22
|
-
|
|
23
|
-
const forms = wixModule(formsModule);
|
|
24
|
-
|
|
25
|
-
/** The Wix Forms app namespace. Every form this vertical touches lives here. */
|
|
26
|
-
export const FORMS_NAMESPACE = "wix.form_app.form";
|
|
27
|
-
|
|
28
|
-
type Raw = Record<string, any>;
|
|
29
|
-
|
|
30
|
-
// A field's settings nest TWO levels deep, under blocks named after its own enums:
|
|
31
|
-
// inputOptions.<inputType block>.<componentType block>.label
|
|
32
|
-
// These tables resolve those names. Spelling them at a call site is one typo away from
|
|
33
|
-
// silently reading back no label at all — which is the whole reason this file exists.
|
|
34
|
-
const INPUT_BLOCK: Record<string, string> = {
|
|
35
|
-
STRING: "stringOptions",
|
|
36
|
-
NUMBER: "numberOptions",
|
|
37
|
-
BOOLEAN: "booleanOptions",
|
|
38
|
-
ARRAY: "arrayOptions",
|
|
39
|
-
ADDRESS: "addressOptions",
|
|
40
|
-
WIX_FILE: "wixFileOptions",
|
|
41
|
-
PAYMENT: "paymentOptions",
|
|
42
|
-
SCHEDULING: "schedulingOptions",
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const COMPONENT_BLOCK: Record<string, string> = {
|
|
46
|
-
TEXT_INPUT: "textInputOptions",
|
|
47
|
-
NUMBER_INPUT: "numberInputOptions",
|
|
48
|
-
RATING_INPUT: "ratingInputOptions",
|
|
49
|
-
PHONE_INPUT: "phoneInputOptions",
|
|
50
|
-
DATE_INPUT: "dateInputOptions",
|
|
51
|
-
DATE_PICKER: "datePickerOptions",
|
|
52
|
-
DATE_TIME: "dateTimeOptions",
|
|
53
|
-
TIME_INPUT: "timeInputOptions",
|
|
54
|
-
CHECKBOX: "checkboxOptions",
|
|
55
|
-
CHECKBOX_GROUP: "checkboxGroupOptions",
|
|
56
|
-
RADIO_GROUP: "radioGroupOptions",
|
|
57
|
-
DROPDOWN: "dropdownOptions",
|
|
58
|
-
TAGS: "tagsOptions",
|
|
59
|
-
MULTILINE_ADDRESS: "multilineAddressOptions",
|
|
60
|
-
FILE_UPLOAD: "fileUploadOptions",
|
|
61
|
-
SIGNATURE: "signatureOptions",
|
|
62
|
-
FIXED_PAYMENT: "fixedPaymentOptions",
|
|
63
|
-
PAYMENT_INPUT: "paymentInputOptions",
|
|
64
|
-
DONATION_INPUT: "donationInputOptions",
|
|
65
|
-
APPOINTMENT: "appointmentOptions",
|
|
66
|
-
SERVICES_DROPDOWN: "servicesDropdownOptions",
|
|
67
|
-
SERVICES_CHECKBOX_GROUP: "servicesCheckboxGroupOptions",
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// componentType → the control to render. Several field kinds share one component (short and
|
|
71
|
-
// long answer are both TEXT_INPUT), so `format` and `inputType` refine it below.
|
|
72
|
-
const CONTROL: Record<string, FormControl> = {
|
|
73
|
-
TEXT_INPUT: "text",
|
|
74
|
-
NUMBER_INPUT: "number",
|
|
75
|
-
RATING_INPUT: "rating",
|
|
76
|
-
PHONE_INPUT: "phone",
|
|
77
|
-
DATE_INPUT: "date",
|
|
78
|
-
DATE_PICKER: "date",
|
|
79
|
-
DATE_TIME: "datetime",
|
|
80
|
-
TIME_INPUT: "time",
|
|
81
|
-
CHECKBOX: "checkbox",
|
|
82
|
-
CHECKBOX_GROUP: "checkboxGroup",
|
|
83
|
-
RADIO_GROUP: "radio",
|
|
84
|
-
DROPDOWN: "select",
|
|
85
|
-
TAGS: "tags",
|
|
86
|
-
MULTILINE_ADDRESS: "address",
|
|
87
|
-
FILE_UPLOAD: "file",
|
|
88
|
-
SIGNATURE: "signature",
|
|
89
|
-
FIXED_PAYMENT: "payment",
|
|
90
|
-
PAYMENT_INPUT: "payment",
|
|
91
|
-
DONATION_INPUT: "payment",
|
|
92
|
-
SERVICES_DROPDOWN: "select",
|
|
93
|
-
SERVICES_CHECKBOX_GROUP: "checkboxGroup",
|
|
94
|
-
APPOINTMENT: "appointment",
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// An enum missing from a table means Wix added a type. Say so ONCE — a silent empty block
|
|
98
|
-
// renders a field labelled by its storage key with none of its settings.
|
|
99
|
-
const warned = new Set<string>();
|
|
100
|
-
function blockName(table: Record<string, string>, key: string | undefined, what: string): string | undefined {
|
|
101
|
-
const name = key ? table[key] : undefined;
|
|
102
|
-
if (!name && key && !warned.has(what + key)) {
|
|
103
|
-
warned.add(what + key);
|
|
104
|
-
console.warn(
|
|
105
|
-
`forms: no ${what} block mapped for "${key}" — that field renders without its label or ` +
|
|
106
|
-
`options. Add it to the table in app/wix/forms/forms.ts.`,
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
return name;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** A BOOLEAN consent field labels itself with Ricos rich content; take its plain text. */
|
|
113
|
-
function plainText(label: unknown): string {
|
|
114
|
-
if (typeof label === "string") return label;
|
|
115
|
-
const nodes = (label as Raw)?.nodes;
|
|
116
|
-
if (!Array.isArray(nodes)) return "";
|
|
117
|
-
const walk = (list: Raw[]): string =>
|
|
118
|
-
list
|
|
119
|
-
.map((n) => (n?.textData?.text ?? "") + (Array.isArray(n?.nodes) ? walk(n.nodes) : ""))
|
|
120
|
-
.join("");
|
|
121
|
-
return walk(nodes).trim();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/** `postalCode` → "Postal code". A subfield is a key; the schema carries no label for it. */
|
|
125
|
-
function humanizeSub(sub: string): string {
|
|
126
|
-
const words = sub.replace(/([A-Z])|(\d+)/g, " $1$2").toLowerCase().trim();
|
|
127
|
-
return words.charAt(0).toUpperCase() + words.slice(1);
|
|
128
|
-
}
|
|
19
|
+
import { wixFetch } from "../sdk";
|
|
20
|
+
import { FORMS_NAMESPACE, toForm, type Raw } from "./forms-core";
|
|
21
|
+
import type { FormDto } from "./types";
|
|
129
22
|
|
|
130
|
-
|
|
131
|
-
const input = raw.inputOptions ?? {};
|
|
132
|
-
const inputType: string = input.inputType ?? "";
|
|
133
|
-
const optionsBlock: Raw = input[blockName(INPUT_BLOCK, inputType, "inputType") ?? ""] ?? {};
|
|
134
|
-
const componentType: string = optionsBlock.componentType ?? "";
|
|
135
|
-
const component: Raw = optionsBlock[blockName(COMPONENT_BLOCK, componentType, "componentType") ?? ""] ?? {};
|
|
136
|
-
const rules: Raw = optionsBlock.validation ?? {};
|
|
137
|
-
|
|
138
|
-
const validation: FormValidation = {
|
|
139
|
-
...(rules.format ? { format: rules.format } : {}),
|
|
140
|
-
...(rules.minLength != null ? { minLength: rules.minLength } : {}),
|
|
141
|
-
...(rules.maxLength != null ? { maxLength: rules.maxLength } : {}),
|
|
142
|
-
...(rules.pattern ? { pattern: rules.pattern } : {}),
|
|
143
|
-
// JSON-Schema spelling everywhere else in this block; minValue/maxValue is read too since
|
|
144
|
-
// the public reference documents neither and an older schema may carry them.
|
|
145
|
-
...((rules.minimum ?? rules.minValue) != null ? { minimum: rules.minimum ?? rules.minValue } : {}),
|
|
146
|
-
...((rules.maximum ?? rules.maxValue) != null ? { maximum: rules.maximum ?? rules.maxValue } : {}),
|
|
147
|
-
...(rules.fileLimit != null ? { fileLimit: rules.fileLimit } : {}),
|
|
148
|
-
...(rules.minItems != null ? { minItems: rules.minItems } : {}),
|
|
149
|
-
...(rules.maxItems != null ? { maxItems: rules.maxItems } : {}),
|
|
150
|
-
};
|
|
151
|
-
|
|
152
|
-
// TEXT_INPUT covers short answer, long answer, email, url and phone — and a long answer is
|
|
153
|
-
// NOT flagged on the component (no `multiline`, no `numberOfLines`). Only `identifier` says
|
|
154
|
-
// TEXT_AREA, which is why it is carried on the DTO.
|
|
155
|
-
const identifier: string = raw.identifier ?? "";
|
|
156
|
-
let control: FormControl = CONTROL[componentType] ?? "unknown";
|
|
157
|
-
// A product list is a PAYMENT field wearing a CHECKBOX_GROUP component — typed by its
|
|
158
|
-
// component it would look like a multi-choice and be bound to an array of strings, when what
|
|
159
|
-
// it submits is a payment structure. The input type wins.
|
|
160
|
-
if (inputType === "PAYMENT") control = "payment";
|
|
161
|
-
if (control === "text") {
|
|
162
|
-
if (identifier === "TEXT_AREA" || component.multiline || component.numberOfLines > 1) control = "textarea";
|
|
163
|
-
else if (rules.format === "EMAIL") control = "email";
|
|
164
|
-
else if (rules.format === "URL") control = "url";
|
|
165
|
-
else if (rules.format === "PHONE") control = "phone";
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const choices: FormChoice[] = Array.isArray(component.options)
|
|
169
|
-
? component.options.map((o: Raw) => ({ value: String(o.value), label: String(o.label ?? o.value) }))
|
|
170
|
-
: [];
|
|
171
|
-
|
|
172
|
-
const addressParts: FormAddressPart[] = Object.entries((rules.fields ?? {}) as Raw)
|
|
173
|
-
// Only addressLine2 carries a visibility setting; hide it when the owner turned it off.
|
|
174
|
-
.filter(([sub]) => component.fieldSettings?.[sub]?.show !== false)
|
|
175
|
-
.map(([sub, cfg]) => ({ sub, label: humanizeSub(sub), required: (cfg as Raw)?.required ?? false }));
|
|
176
|
-
|
|
177
|
-
const target: string = input.target ?? "";
|
|
178
|
-
const label = plainText(component.label) || target;
|
|
179
|
-
|
|
180
|
-
// Every control is controlled from the first render, so each field starts at a value of the
|
|
181
|
-
// right SHAPE — [] for multi-choice and files, {} for an address, the owner's prefill else "".
|
|
182
|
-
// Wix spells the prefill `default` on most components and `defaultValue` on a rating.
|
|
183
|
-
const prefill = component.default ?? component.defaultValue;
|
|
184
|
-
const defaultValue =
|
|
185
|
-
control === "address" ? {} :
|
|
186
|
-
inputType === "ARRAY" || inputType === "WIX_FILE" ? [] :
|
|
187
|
-
// a consent checkbox is a BOOLEAN: "" would bind to `checked` as false and work by
|
|
188
|
-
// accident, but the DTO promises the shape its control writes back
|
|
189
|
-
inputType === "BOOLEAN" ? ((prefill as boolean | undefined) ?? false) :
|
|
190
|
-
(prefill as string | number | boolean | undefined) ?? "";
|
|
191
|
-
|
|
192
|
-
return {
|
|
193
|
-
target,
|
|
194
|
-
label,
|
|
195
|
-
control,
|
|
196
|
-
required: input.required ?? false,
|
|
197
|
-
...(component.placeholder ? { placeholder: String(component.placeholder) } : {}),
|
|
198
|
-
...(component.description ? { description: plainText(component.description) } : {}),
|
|
199
|
-
defaultValue,
|
|
200
|
-
choices,
|
|
201
|
-
addressParts,
|
|
202
|
-
validation,
|
|
203
|
-
...(component.defaultCountryCode || rules.phoneOptions?.allowedCountryCodes?.[0]
|
|
204
|
-
? { phoneCountry: component.defaultCountryCode ?? rules.phoneOptions.allowedCountryCodes[0] }
|
|
205
|
-
: {}),
|
|
206
|
-
identifier,
|
|
207
|
-
inputType,
|
|
208
|
-
componentType,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Display order comes from `steps[].layout`, NOT from `formFields[]` array order. Sort WITHIN
|
|
214
|
-
* each step and concatenate in step order: `row` restarts at 0 in every step, so one sort
|
|
215
|
-
* across the flattened list interleaves them. A field the owner never placed sorts LAST — it
|
|
216
|
-
* still stores values. The submit button is `fieldType: "DISPLAY"`, so filtering to INPUT
|
|
217
|
-
* drops it automatically.
|
|
218
|
-
*/
|
|
219
|
-
function orderedInputs(raw: Raw): Raw[] {
|
|
220
|
-
const order = new Map<string, number>(
|
|
221
|
-
((raw.steps ?? []) as Raw[])
|
|
222
|
-
.flatMap((s) =>
|
|
223
|
-
((s.layout?.large?.items ?? s.layout?.medium?.items ?? s.layout?.small?.items ?? []) as Raw[])
|
|
224
|
-
.slice()
|
|
225
|
-
.sort((a, b) => a.row - b.row || a.column - b.column),
|
|
226
|
-
)
|
|
227
|
-
.map((item, i) => [item.fieldId as string, i] as const),
|
|
228
|
-
);
|
|
229
|
-
return ((raw.formFields ?? []) as Raw[])
|
|
230
|
-
.filter((f) => f.fieldType === "INPUT" && !f.hidden)
|
|
231
|
-
.sort((a, b) => (order.get(a.id) ?? Infinity) - (order.get(b.id) ?? Infinity));
|
|
232
|
-
}
|
|
23
|
+
export { FORMS_NAMESPACE };
|
|
233
24
|
|
|
234
|
-
function
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
name: raw.name ?? "",
|
|
239
|
-
fields: orderedInputs(raw).map(toField),
|
|
240
|
-
// The submit wording lives on a DISPLAY field, nested under pageNavigationOptions because
|
|
241
|
-
// one control drives both multi-page navigation and the final submit.
|
|
242
|
-
submitText: submit?.displayOptions?.pageNavigationOptions?.submitText ?? "",
|
|
243
|
-
};
|
|
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;
|
|
244
29
|
}
|
|
245
30
|
|
|
246
31
|
/**
|
|
@@ -249,21 +34,21 @@ function toForm(raw: Raw): FormDto {
|
|
|
249
34
|
* that would drop real enquiries silently.
|
|
250
35
|
*/
|
|
251
36
|
export async function getForm(formId: string): Promise<FormDto> {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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);
|
|
255
41
|
}
|
|
256
42
|
|
|
257
43
|
/**
|
|
258
44
|
* Every form on the site, in the Wix Forms namespace. Use ONE call for several forms on a page
|
|
259
45
|
* rather than a getForm each.
|
|
260
46
|
*
|
|
261
|
-
*
|
|
47
|
+
* Returns only ENABLED forms — a form the owner disabled vanishes from the listing rather
|
|
262
48
|
* than erroring. That is usually right for a public site.
|
|
263
49
|
*/
|
|
264
50
|
export async function listForms(): Promise<FormDto[]> {
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
const res = (await forms.listForms(FORMS_NAMESPACE)) as Raw;
|
|
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)}`);
|
|
268
53
|
return ((res?.forms ?? []) as Raw[]).map(toForm);
|
|
269
54
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// Submission rules — transport-agnostic, imported by BOTH transports: ./submissions.ts (the SDK)
|
|
2
|
+
// and the REST twin in references/forms/rest/submissions.ts. What a visitor's values become on
|
|
3
|
+
// the wire, which statuses mean "created", how a rejection maps back onto controls, and how an
|
|
4
|
+
// attachment is uploaded — all HERE, once. A created submission may arrive wrapped (`{ submission }`,
|
|
5
|
+
// REST) or bare (SDK), with `_id` (SDK) or `id` (REST); the mapper accepts both.
|
|
6
|
+
// docs: https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/about-submission-values.md
|
|
7
|
+
import type { Raw } from "./forms-core";
|
|
8
|
+
import type { FormFieldDto, FormValues, SubmissionDto } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Statuses that mean the submission EXISTS — show the thank-you for all three. `CONFIRMED` is
|
|
12
|
+
* recorded, `PENDING` is created but not recorded yet, `PAYMENT_WAITING` is created on a form
|
|
13
|
+
* that also collects payment. Treating one as a failure invites the visitor to submit again,
|
|
14
|
+
* which costs the owner duplicate entries for a submission that already exists.
|
|
15
|
+
*
|
|
16
|
+
* An allowlist rather than a catch-all, so a status added to the enum later cannot silently
|
|
17
|
+
* render a thank-you for something that is not a submission.
|
|
18
|
+
*/
|
|
19
|
+
export const SUBMITTED_OK = new Set(["CONFIRMED", "PENDING", "PAYMENT_WAITING"]);
|
|
20
|
+
|
|
21
|
+
/** Strip visitor-added formatting from a phone number — submit this, not the raw control text. */
|
|
22
|
+
export const normalizePhone = (v: unknown): string => String(v ?? "").replace(/[\s()\-.]/g, "");
|
|
23
|
+
|
|
24
|
+
/** Uploads a File to a pre-signed URL and resolves to the value to submit for its field. */
|
|
25
|
+
export type UploadOne = (formId: string, file: File) => Promise<string>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A browser leaves `type` empty for extensions it does not recognize; the generic type keeps
|
|
29
|
+
* the upload-URL call valid (Media Manager rejects a mime type that contradicts the extension).
|
|
30
|
+
*/
|
|
31
|
+
export const uploadMimeType = (file: File): string => file.type || "application/octet-stream";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* PUT the bytes to the generated upload URL and return the value to submit for the field.
|
|
35
|
+
*
|
|
36
|
+
* The submission value IS the generated upload URL — not the CDN URL the upload responds with,
|
|
37
|
+
* and not a file id. The PUT goes to a pre-signed host with plain `fetch`, never through the SDK
|
|
38
|
+
* or the REST client: adding the visitor's Authorization header to a pre-signed URL turns a
|
|
39
|
+
* working upload into a 400.
|
|
40
|
+
*/
|
|
41
|
+
export async function putUpload(uploadUrl: string, file: File, mimeType: string = uploadMimeType(file)): Promise<string> {
|
|
42
|
+
const put = await fetch(`${uploadUrl}?filename=${encodeURIComponent(file.name)}`, {
|
|
43
|
+
method: "PUT",
|
|
44
|
+
headers: { "Content-Type": mimeType },
|
|
45
|
+
body: file,
|
|
46
|
+
});
|
|
47
|
+
if (!put.ok) {
|
|
48
|
+
// Media Manager's own codes land here: FILE_SIZE_OVER_LIMIT, UNSUPPORTED_FILE_FORMAT,
|
|
49
|
+
// MISMATCH_MIME_TYPE, ZERO_FILE_SIZE, SITE_QUOTA_EXCEEDED.
|
|
50
|
+
throw new Error(`forms: could not upload "${file.name}" (${put.status}). Check its size and type.`);
|
|
51
|
+
}
|
|
52
|
+
return uploadUrl;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Upload every File sitting in the form's values and return a copy with each file field
|
|
57
|
+
* replaced by its uploaded URL(s). Values already holding URLs (a retry after the server
|
|
58
|
+
* rejected some OTHER field) are kept as they are, so a retry never re-uploads. `uploadOne` is
|
|
59
|
+
* the transport's getMediaUploadUrl + putUpload.
|
|
60
|
+
*/
|
|
61
|
+
export async function uploadFilesWith(
|
|
62
|
+
uploadOne: UploadOne,
|
|
63
|
+
formId: string,
|
|
64
|
+
fields: FormFieldDto[],
|
|
65
|
+
values: FormValues,
|
|
66
|
+
): Promise<FormValues> {
|
|
67
|
+
const next: FormValues = { ...values };
|
|
68
|
+
for (const field of fields) {
|
|
69
|
+
if (field.control !== "file" && field.control !== "signature") continue;
|
|
70
|
+
const picked = ([] as unknown[]).concat(values[field.target] ?? []);
|
|
71
|
+
const done: string[] = [];
|
|
72
|
+
// Sequential on purpose: a visitor's uplink is the bottleneck, and a failed file should
|
|
73
|
+
// stop the submit rather than race more uploads it will throw away.
|
|
74
|
+
for (const item of picked) {
|
|
75
|
+
if (typeof item === "string" && item) done.push(item);
|
|
76
|
+
else if (typeof File !== "undefined" && item instanceof File) done.push(await uploadOne(formId, item));
|
|
77
|
+
}
|
|
78
|
+
next[field.target] = done;
|
|
79
|
+
}
|
|
80
|
+
return next;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Turn the visitor's values into the map `createSubmission` expects, keyed by each field's
|
|
85
|
+
* `target` — the same key the controls are bound to, so the keys come out right by
|
|
86
|
+
* construction with no hand-maintained list to drift.
|
|
87
|
+
*
|
|
88
|
+
* Walks the FIELDS, not the values object: a stray key can never reach the API, and a field
|
|
89
|
+
* the owner just added shows up the moment the schema does.
|
|
90
|
+
*
|
|
91
|
+
* Three value shapes: a flat value (text/choice/date), an ARRAY (multi-choice, several files),
|
|
92
|
+
* an OBJECT (an address, keyed by subfield — the shape behind `address/city` error paths). An
|
|
93
|
+
* empty optional field is OMITTED rather than sent as "": the server validates what it is given.
|
|
94
|
+
*/
|
|
95
|
+
export function toSubmissionValues(fields: FormFieldDto[], values: FormValues): Record<string, unknown> {
|
|
96
|
+
const filled = (v: unknown) => v !== undefined && v !== null && String(v).trim() !== "";
|
|
97
|
+
const out: Record<string, unknown> = {};
|
|
98
|
+
|
|
99
|
+
for (const field of fields) {
|
|
100
|
+
const raw = values?.[field.target];
|
|
101
|
+
|
|
102
|
+
if (field.control === "address") {
|
|
103
|
+
const parts: Record<string, string> = {};
|
|
104
|
+
const held = (raw ?? {}) as Record<string, unknown>;
|
|
105
|
+
for (const { sub } of field.addressParts) {
|
|
106
|
+
if (filled(held[sub])) parts[sub] = String(held[sub]).trim();
|
|
107
|
+
}
|
|
108
|
+
if (Object.keys(parts).length) out[field.target] = parts;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (field.control === "file" || field.control === "signature") {
|
|
113
|
+
// Whatever the upload produced. A stray File that never went through the upload is
|
|
114
|
+
// dropped rather than sent, since it would 400 the whole form.
|
|
115
|
+
const urls = ([] as unknown[]).concat(raw ?? []).filter((v): v is string => typeof v === "string" && !!v);
|
|
116
|
+
if (urls.length) out[field.target] = urls.length === 1 ? urls[0] : urls;
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (field.inputType === "ARRAY") {
|
|
121
|
+
const picked = (Array.isArray(raw) ? raw : []).filter(filled);
|
|
122
|
+
if (picked.length) out[field.target] = picked;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (field.control === "checkbox") {
|
|
127
|
+
// A consent checkbox submits a boolean. Unchecked AND optional is omitted; unchecked and
|
|
128
|
+
// required fails validation before it gets here.
|
|
129
|
+
if (raw === true) out[field.target] = true;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!filled(raw)) continue;
|
|
134
|
+
const value = typeof raw === "string" ? raw.trim() : raw;
|
|
135
|
+
out[field.target] =
|
|
136
|
+
field.control === "number" || field.control === "rating" ? Number(value) :
|
|
137
|
+
field.control === "phone" ? normalizePhone(value) :
|
|
138
|
+
value;
|
|
139
|
+
}
|
|
140
|
+
return out;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* The created submission as a DTO. Accepts the REST envelope (`{ submission }`) and the SDK's bare
|
|
145
|
+
* entity; throws when nothing came back or the status is not one that means "created", so a
|
|
146
|
+
* caller never shows a thank-you for something that is not a submission.
|
|
147
|
+
*/
|
|
148
|
+
export function toSubmission(created: Raw | null | undefined): SubmissionDto {
|
|
149
|
+
const submission: Raw | undefined = created?.submission ?? created;
|
|
150
|
+
const id: string | undefined = submission?._id ?? submission?.id;
|
|
151
|
+
if (!id) throw new Error("forms: submission failed (nothing returned).");
|
|
152
|
+
const status: string = submission?.status ?? "";
|
|
153
|
+
if (!SUBMITTED_OK.has(status)) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`forms: submission status is "${status}" — not one of the statuses that mean the submission ` +
|
|
156
|
+
`was created (${[...SUBMITTED_OK].join(", ")}), so do not show a success state.`,
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
return { id, status };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Pull per-field violations out of a failed create, keyed by input NAME so each message lands
|
|
164
|
+
* on its own control. `errorPath` is the field's `target`, or a nested path like
|
|
165
|
+
* `address/subdivision` — exactly how the controls are named, so it maps straight across.
|
|
166
|
+
*
|
|
167
|
+
* The documented entries arrive under `details.validationError.fieldViolations[]`, each with
|
|
168
|
+
* its own nested `data.errors[]` — two levels deeper than the docs' shape. This flattens that.
|
|
169
|
+
* The SDK error and the REST client's WixApiError both carry that block as `details`.
|
|
170
|
+
*
|
|
171
|
+
* Two rejections here are SEED bugs, not frontend bugs — fix them in `seed/SEED.md`, never
|
|
172
|
+
* by mangling the key or the value:
|
|
173
|
+
* - UNKNOWN_VALUE_ERROR on a key that IS in the schema → the field was seeded with no
|
|
174
|
+
* `validation` block, and that block is what registers the target as an accepted value.
|
|
175
|
+
* - NOT_ALLOWED_VALUE_ERROR on a choice field → the seed's `options[].value` and its
|
|
176
|
+
* validation enum disagree; the two declarations must match.
|
|
177
|
+
*/
|
|
178
|
+
export function submissionErrors(err: unknown, fields: FormFieldDto[]): Record<string, string> {
|
|
179
|
+
const body = (err as Raw)?.details ?? (err as Raw)?.body?.details ?? {};
|
|
180
|
+
const violations: Raw[] = body?.validationError?.fieldViolations ?? [];
|
|
181
|
+
const byTarget = new Map(fields.map((f) => [f.target, f]));
|
|
182
|
+
const out: Record<string, string> = {};
|
|
183
|
+
|
|
184
|
+
for (const entry of violations.flatMap((v) => v.data?.errors ?? [v])) {
|
|
185
|
+
const path: string | undefined = entry?.errorPath;
|
|
186
|
+
if (!path) continue;
|
|
187
|
+
const field = byTarget.get(path.split("/")[0]);
|
|
188
|
+
if (!field) continue;
|
|
189
|
+
// Wix's own errorMessage is the validator's internal wording — debug only.
|
|
190
|
+
console.debug("forms: server violation", path, entry.errorType, entry.errorMessage);
|
|
191
|
+
out[path] = messageFor(entry.errorType, field);
|
|
192
|
+
}
|
|
193
|
+
return out;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** errorType → visitor-facing copy, written from the field's own schema. */
|
|
197
|
+
export function messageFor(errorType: string, f: FormFieldDto): string {
|
|
198
|
+
const v = f.validation;
|
|
199
|
+
switch (errorType) {
|
|
200
|
+
case "REQUIRED_VALUE_ERROR": return `${f.label} is required.`;
|
|
201
|
+
case "MIN_LENGTH_ERROR": return `${f.label} must be at least ${v.minLength} characters.`;
|
|
202
|
+
case "MAX_LENGTH_ERROR": return `${f.label} must be at most ${v.maxLength} characters.`;
|
|
203
|
+
case "MIN_VALUE_ERROR": return `${f.label} must be ${v.minimum ?? "higher"} or more.`;
|
|
204
|
+
case "MAX_VALUE_ERROR": return `${f.label} must be ${v.maximum ?? "lower"} or less.`;
|
|
205
|
+
case "PATTERN_ERROR": return `${f.label} is not in the expected format.`;
|
|
206
|
+
case "NOT_ALLOWED_VALUE_ERROR": return `Choose one of the listed options for ${f.label}.`;
|
|
207
|
+
case "FORMAT_ERROR":
|
|
208
|
+
return v.format === "EMAIL" ? "Enter a valid email address."
|
|
209
|
+
: v.format === "PHONE" ? "Use international format, starting with +."
|
|
210
|
+
: v.format === "URL" ? "Enter a full URL starting with https://"
|
|
211
|
+
: `Please check ${f.label}.`;
|
|
212
|
+
// The enum grows: MIN/MAX_ITEMS_ERROR and DISABLED_FORM_ERROR are reachable without being
|
|
213
|
+
// listed, so an unmapped type degrades to safe copy rather than showing nothing.
|
|
214
|
+
default: return `Please check ${f.label}.`;
|
|
215
|
+
}
|
|
216
|
+
}
|