@wix/agent-skills 1.20.0 → 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.
Files changed (152) hide show
  1. package/package.json +1 -1
  2. package/skills/wix-headless-fast/SKILL.md +200 -34
  3. package/skills/wix-headless-fast/entry/skill.md +10 -2
  4. package/skills/wix-headless-fast/install/attach.mjs +253 -0
  5. package/skills/wix-headless-fast/install/deploy.mjs +109 -9
  6. package/skills/wix-headless-fast/install/fast-path.mjs +29 -34
  7. package/skills/wix-headless-fast/references/blog/INSTRUCTIONS.md +224 -60
  8. package/skills/wix-headless-fast/references/blog/app/components/blog/BlogFeedView.tsx +2 -3
  9. package/skills/wix-headless-fast/references/blog/app/components/blog/PostView.tsx +2 -3
  10. package/skills/wix-headless-fast/references/blog/app/hooks/blog/useBlogFeed.ts +24 -116
  11. package/skills/wix-headless-fast/references/blog/app/hooks/blog/usePost.ts +16 -64
  12. package/skills/wix-headless-fast/references/blog/app/wix/blog/blog-feed-store.ts +137 -0
  13. package/skills/wix-headless-fast/references/blog/app/wix/blog/post-store.ts +78 -0
  14. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts-core.ts +117 -0
  15. package/skills/wix-headless-fast/references/blog/app/wix/blog/posts.ts +13 -60
  16. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy-core.ts +47 -0
  17. package/skills/wix-headless-fast/references/blog/app/wix/blog/taxonomy.ts +10 -28
  18. package/skills/wix-headless-fast/references/blog/app-astro/pages/blog/[...slug].astro +2 -4
  19. package/skills/wix-headless-fast/references/blog/rest/posts.ts +42 -0
  20. package/skills/wix-headless-fast/references/blog/rest/taxonomy.ts +62 -0
  21. package/skills/wix-headless-fast/references/blog/seed/SEED.md +9 -0
  22. package/skills/wix-headless-fast/references/bookings/INSTRUCTIONS.md +179 -33
  23. package/skills/wix-headless-fast/references/bookings/app/components/bookings/ServiceBookingView.tsx +2 -1
  24. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useBookingFlow.ts +26 -132
  25. package/skills/wix-headless-fast/references/bookings/app/hooks/bookings/useServices.ts +18 -49
  26. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-core.ts +218 -0
  27. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking-flow-store.ts +169 -0
  28. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/booking.ts +57 -142
  29. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-core.ts +92 -0
  30. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services-store.ts +90 -0
  31. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/services.ts +22 -61
  32. package/skills/wix-headless-fast/references/bookings/app/wix/bookings/types.ts +3 -1
  33. package/skills/wix-headless-fast/references/bookings/rest/booking.ts +113 -0
  34. package/skills/wix-headless-fast/references/bookings/rest/services.ts +55 -0
  35. package/skills/wix-headless-fast/references/bookings/seed/SEED.md +10 -0
  36. package/skills/wix-headless-fast/references/cms/INSTRUCTIONS.md +236 -62
  37. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useCollection.ts +19 -52
  38. package/skills/wix-headless-fast/references/cms/app/hooks/cms/useItem.ts +16 -36
  39. package/skills/wix-headless-fast/references/cms/app/wix/cms/collection-store.ts +158 -0
  40. package/skills/wix-headless-fast/references/cms/app/wix/cms/item-store.ts +93 -0
  41. package/skills/wix-headless-fast/references/cms/app/wix/cms/items-core.ts +177 -0
  42. package/skills/wix-headless-fast/references/cms/app/wix/cms/items.ts +13 -41
  43. package/skills/wix-headless-fast/references/cms/rest/items.ts +125 -0
  44. package/skills/wix-headless-fast/references/cms/seed/SEED.md +9 -0
  45. package/skills/wix-headless-fast/references/events/INSTRUCTIONS.md +239 -83
  46. package/skills/wix-headless-fast/references/events/app/components/events/EventConfirmationView.tsx +1 -1
  47. package/skills/wix-headless-fast/references/events/app/components/events/EventRegistrationView.tsx +7 -5
  48. package/skills/wix-headless-fast/references/events/app/components/events/EventsView.tsx +2 -2
  49. package/skills/wix-headless-fast/references/events/app/hooks/events/useEventRegistration.ts +28 -110
  50. package/skills/wix-headless-fast/references/events/app/hooks/events/useEvents.ts +19 -54
  51. package/skills/wix-headless-fast/references/events/app/wix/events/events-core.ts +130 -0
  52. package/skills/wix-headless-fast/references/events/app/wix/events/events-store.ts +97 -0
  53. package/skills/wix-headless-fast/references/events/app/wix/events/events.ts +13 -94
  54. package/skills/wix-headless-fast/references/events/app/wix/events/registration-core.ts +113 -0
  55. package/skills/wix-headless-fast/references/events/app/wix/events/registration-store.ts +156 -0
  56. package/skills/wix-headless-fast/references/events/app/wix/events/registration.ts +38 -69
  57. package/skills/wix-headless-fast/references/events/rest/events.ts +57 -0
  58. package/skills/wix-headless-fast/references/events/rest/registration.ts +83 -0
  59. package/skills/wix-headless-fast/references/events/seed/SEED.md +9 -0
  60. package/skills/wix-headless-fast/references/forms/INSTRUCTIONS.md +214 -26
  61. package/skills/wix-headless-fast/references/forms/app/hooks/forms/useWixForm.ts +35 -267
  62. package/skills/wix-headless-fast/references/forms/app/wix/forms/form-store.ts +332 -0
  63. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms-core.ts +238 -0
  64. package/skills/wix-headless-fast/references/forms/app/wix/forms/forms.ts +28 -243
  65. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions-core.ts +216 -0
  66. package/skills/wix-headless-fast/references/forms/app/wix/forms/submissions.ts +32 -204
  67. package/skills/wix-headless-fast/references/forms/rest/forms.ts +34 -0
  68. package/skills/wix-headless-fast/references/forms/rest/submissions.ts +56 -0
  69. package/skills/wix-headless-fast/references/forms/seed/SEED.md +8 -0
  70. package/skills/wix-headless-fast/references/forms/seed/seed-forms.mjs +15 -8
  71. package/skills/wix-headless-fast/references/members/INSTRUCTIONS.md +314 -63
  72. package/skills/wix-headless-fast/references/members/app/components/members/LoginForm.tsx +1 -1
  73. package/skills/wix-headless-fast/references/members/app/wix/members/auth-core.ts +105 -0
  74. package/skills/wix-headless-fast/references/members/app/wix/members/auth.ts +36 -65
  75. package/skills/wix-headless-fast/references/members/app/wix/members/client.ts +1 -1
  76. package/skills/wix-headless-fast/references/members/app/wix/members/members-core.ts +43 -0
  77. package/skills/wix-headless-fast/references/members/app/wix/members/members.ts +11 -32
  78. package/skills/wix-headless-fast/references/members/rest/auth.ts +215 -0
  79. package/skills/wix-headless-fast/references/members/rest/members.ts +26 -0
  80. package/skills/wix-headless-fast/references/portfolio/INSTRUCTIONS.md +238 -59
  81. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionProjectsView.tsx +2 -2
  82. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/CollectionsView.tsx +2 -2
  83. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/GalleryMedia.tsx +35 -0
  84. package/skills/wix-headless-fast/references/portfolio/app/components/portfolio/ProjectDetailView.tsx +8 -38
  85. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollectionProjects.ts +23 -50
  86. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useCollections.ts +16 -34
  87. package/skills/wix-headless-fast/references/portfolio/app/hooks/portfolio/useProjectDetail.ts +22 -49
  88. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collection-projects-store.ts +82 -0
  89. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/collections-store.ts +58 -0
  90. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio-core.ts +177 -0
  91. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/portfolio.ts +31 -118
  92. package/skills/wix-headless-fast/references/portfolio/app/wix/portfolio/project-detail-store.ts +81 -0
  93. package/skills/wix-headless-fast/references/portfolio/rest/portfolio.ts +75 -0
  94. package/skills/wix-headless-fast/references/portfolio/seed/SEED.md +8 -0
  95. package/skills/wix-headless-fast/references/pricing-plans/INSTRUCTIONS.md +212 -51
  96. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlanDetailView.tsx +9 -20
  97. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/PlansView.tsx +7 -32
  98. package/skills/wix-headless-fast/references/pricing-plans/app/components/pricing-plans/SubscribeButton.tsx +41 -0
  99. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlanPurchase.ts +12 -33
  100. package/skills/wix-headless-fast/references/pricing-plans/app/hooks/pricing-plans/usePlans.ts +17 -34
  101. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plan-purchase-store.ts +54 -0
  102. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-core.ts +106 -0
  103. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans-store.ts +64 -0
  104. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/plans.ts +7 -55
  105. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase-core.ts +43 -0
  106. package/skills/wix-headless-fast/references/pricing-plans/app/wix/pricing-plans/purchase.ts +11 -34
  107. package/skills/wix-headless-fast/references/pricing-plans/rest/plans.ts +31 -0
  108. package/skills/wix-headless-fast/references/pricing-plans/rest/purchase.ts +20 -0
  109. package/skills/wix-headless-fast/references/pricing-plans/seed/SEED.md +8 -0
  110. package/skills/wix-headless-fast/references/restaurants/INSTRUCTIONS.md +240 -47
  111. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/MenuView.tsx +2 -2
  112. package/skills/wix-headless-fast/references/restaurants/app/components/restaurants/OrderCartDrawer.tsx +2 -2
  113. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useMenus.ts +14 -35
  114. package/skills/wix-headless-fast/references/restaurants/app/hooks/restaurants/useReservation.ts +28 -171
  115. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-core.ts +155 -0
  116. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu-store.ts +89 -0
  117. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/menu.ts +23 -117
  118. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering-core.ts +124 -0
  119. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/ordering.ts +45 -114
  120. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservation-store.ts +193 -0
  121. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations-core.ts +94 -0
  122. package/skills/wix-headless-fast/references/restaurants/app/wix/restaurants/reservations.ts +15 -87
  123. package/skills/wix-headless-fast/references/restaurants/rest/menu.ts +60 -0
  124. package/skills/wix-headless-fast/references/restaurants/rest/ordering.ts +169 -0
  125. package/skills/wix-headless-fast/references/restaurants/rest/reservations.ts +68 -0
  126. package/skills/wix-headless-fast/references/restaurants/seed/SEED.md +8 -0
  127. package/skills/wix-headless-fast/references/shared/SUPPLIED-CONTENT.md +23 -0
  128. package/skills/wix-headless-fast/references/shared/app/wix/media.ts +45 -4
  129. package/skills/wix-headless-fast/references/shared/app/wix/sdk.ts +13 -0
  130. package/skills/wix-headless-fast/references/shared/rest/client.ts +155 -0
  131. package/skills/wix-headless-fast/references/shared/rest/config.ts +3 -0
  132. package/skills/wix-headless-fast/references/shared/rest/media.ts +65 -0
  133. package/skills/wix-headless-fast/references/storefront/INSTRUCTIONS.md +182 -63
  134. package/skills/wix-headless-fast/references/storefront/app/components/storefront/OptionPicker.tsx +95 -0
  135. package/skills/wix-headless-fast/references/storefront/app/components/storefront/QuickAdd.tsx +29 -58
  136. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -163
  137. package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -300
  138. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart-core.ts +121 -0
  139. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/cart.ts +49 -162
  140. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog-core.ts +309 -0
  141. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/catalog.ts +47 -370
  142. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/product-detail-store.ts +147 -0
  143. package/skills/wix-headless-fast/references/storefront/app/wix/storefront/shop-store.ts +289 -0
  144. package/skills/wix-headless-fast/references/storefront/rest/cart.ts +132 -0
  145. package/skills/wix-headless-fast/references/storefront/rest/catalog.ts +123 -0
  146. package/skills/wix-headless-fast/references/storefront/seed/SEED.md +25 -9
  147. package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +6 -35
  148. package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
  149. package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -40
  150. package/skills/wix-vibe-headless/references/storefront/seed/SEED.md +3 -0
  151. package/skills/wix-vibe-headless/references/storefront/seed/seed-store.cjs +6 -0
  152. package/skills/wix-headless-fast/install/pin-agents-md.mjs +0 -57
@@ -1,13 +1,14 @@
1
- // Wix Forms submissions (@wix/forms `submissions`) — build the submission from the visitor's
2
- // values, upload any attachments, create it, and turn a rejection into per-control messages.
3
- // Copy as-is; extend by adding functions.
1
+ // Wix Forms submissions (@wix/forms `submissions`) over the SDK — get an upload URL, create the
2
+ // submission. The rules (value shapes, accepted statuses, violation mapping, the upload PUT) live
3
+ // in ./submissions-core (shared with the REST twin in references/forms/rest/); this file is the
4
+ // transport only. Copy as-is; extend by adding functions.
4
5
  //
5
- // ⚠️ The visitor token creates the submission, despite the spec. `CreateSubmission` is listed
6
- // under the owner scope `SCOPE.DC-FORMS.MANAGE-SUBMISSIONS` and returns 200 on an anonymous
7
- // visitor: Wix grants implicit visitor access so a published site can submit its own forms.
8
- // Reaching for a backend here is the most common wrong turn on this vertical.
6
+ // The visitor token creates the submission, despite the spec. `CreateSubmission` is listed under
7
+ // the owner scope `SCOPE.DC-FORMS.MANAGE-SUBMISSIONS` and returns 200 on an anonymous visitor: Wix
8
+ // grants implicit visitor access so a published site can submit its own forms. Reaching for a
9
+ // backend here is the most common wrong turn on this vertical.
9
10
  //
10
- // ⚠️ SUBMISSIONS ARE WRITE-ONLY FROM A VISITOR. Reading them back genuinely requires the owner
11
+ // SUBMISSIONS ARE WRITE-ONLY FROM A VISITOR. Reading them back genuinely requires the owner
11
12
  // scope and 403s. The resolved create IS the confirmation — show a thank-you; the entry appears
12
13
  // in the owner's dashboard. A site that must LIST what visitors submitted needs `cms` instead.
13
14
  //
@@ -15,217 +16,44 @@
15
16
  // docs: https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/about-submission-values.md
16
17
  import { submissions as submissionsModule } from "@wix/forms";
17
18
  import { wixModule } from "../sdk";
19
+ import type { Raw } from "./forms-core";
20
+ import {
21
+ SUBMITTED_OK,
22
+ normalizePhone,
23
+ putUpload,
24
+ submissionErrors,
25
+ toSubmission,
26
+ toSubmissionValues,
27
+ uploadFilesWith,
28
+ uploadMimeType,
29
+ } from "./submissions-core";
18
30
  import type { FormFieldDto, FormValues, SubmissionDto } from "./types";
19
31
 
20
- const submissions = wixModule(submissionsModule);
21
-
22
- type Raw = Record<string, any>;
32
+ export { SUBMITTED_OK, normalizePhone, submissionErrors, toSubmissionValues };
23
33
 
24
- /**
25
- * Statuses that mean the submission EXISTS — show the thank-you for all three. `CONFIRMED` is
26
- * recorded, `PENDING` is created but not recorded yet, `PAYMENT_WAITING` is created on a form
27
- * that also collects payment. Treating one as a failure invites the visitor to submit again,
28
- * which costs the owner duplicate entries for a submission that already exists.
29
- *
30
- * An allowlist rather than a catch-all, so a status added to the enum later cannot silently
31
- * render a thank-you for something that is not a submission.
32
- */
33
- export const SUBMITTED_OK = new Set(["CONFIRMED", "PENDING", "PAYMENT_WAITING"]);
34
-
35
- /** Strip visitor-added formatting from a phone number — submit this, not the raw control text. */
36
- export const normalizePhone = (v: unknown): string => String(v ?? "").replace(/[\s()\-.]/g, "");
34
+ const submissions = wixModule(submissionsModule);
37
35
 
38
- /**
39
- * Upload one File and return the value to submit for its field.
40
- *
41
- * ⚠️ The submission value IS the generated upload URL — not the CDN URL the upload responds
42
- * with, and not a file id. Get the URL, PUT the bytes to it, then send that same URL as the
43
- * field's value.
44
- *
45
- * The PUT goes to a pre-signed host with plain `fetch`, never through the SDK: adding the
46
- * visitor's Authorization header to a pre-signed URL turns a working upload into a 400.
47
- */
36
+ /** Upload one File and return the value to submit for its field (the upload URL itself). */
48
37
  export async function uploadFile(formId: string, file: File): Promise<string> {
49
- // A browser leaves `type` empty for extensions it does not recognize; the generic type keeps
50
- // the upload-URL call valid (Media Manager rejects a mime type that contradicts the extension).
51
- const mimeType = file.type || "application/octet-stream";
38
+ const mimeType = uploadMimeType(file);
52
39
  const res = (await submissions.getMediaUploadUrl(formId, file.name, mimeType)) as Raw;
53
40
  const uploadUrl: string | undefined = res?.uploadUrl ?? res?.url;
54
41
  if (!uploadUrl) throw new Error(`forms: no upload URL for "${file.name}".`);
55
- const put = await fetch(`${uploadUrl}?filename=${encodeURIComponent(file.name)}`, {
56
- method: "PUT",
57
- headers: { "Content-Type": mimeType },
58
- body: file,
59
- });
60
- if (!put.ok) {
61
- // Media Manager's own codes land here: FILE_SIZE_OVER_LIMIT, UNSUPPORTED_FILE_FORMAT,
62
- // MISMATCH_MIME_TYPE, ZERO_FILE_SIZE, SITE_QUOTA_EXCEEDED.
63
- throw new Error(`forms: could not upload "${file.name}" (${put.status}). Check its size and type.`);
64
- }
65
- return uploadUrl;
66
- }
67
-
68
- /**
69
- * Upload every File sitting in the form's values and return a copy with each file field
70
- * replaced by its uploaded URL(s). Values already holding URLs (a retry after the server
71
- * rejected some OTHER field) are kept as they are, so a retry never re-uploads.
72
- */
73
- export async function uploadFiles(
74
- formId: string,
75
- fields: FormFieldDto[],
76
- values: FormValues,
77
- ): Promise<FormValues> {
78
- const next: FormValues = { ...values };
79
- for (const field of fields) {
80
- if (field.control !== "file" && field.control !== "signature") continue;
81
- const picked = ([] as unknown[]).concat(values[field.target] ?? []);
82
- const done: string[] = [];
83
- // Sequential on purpose: a visitor's uplink is the bottleneck, and a failed file should
84
- // stop the submit rather than race more uploads it will throw away.
85
- for (const item of picked) {
86
- if (typeof item === "string" && item) done.push(item);
87
- else if (typeof File !== "undefined" && item instanceof File) done.push(await uploadFile(formId, item));
88
- }
89
- next[field.target] = done;
90
- }
91
- return next;
42
+ return putUpload(uploadUrl, file, mimeType);
92
43
  }
93
44
 
94
- /**
95
- * Turn the visitor's values into the map `createSubmission` expects, keyed by each field's
96
- * `target` — the same key the controls are bound to, so the keys come out right by
97
- * construction with no hand-maintained list to drift.
98
- *
99
- * Walks the FIELDS, not the values object: a stray key can never reach the API, and a field
100
- * the owner just added shows up the moment the schema does.
101
- *
102
- * Three value shapes: a flat value (text/choice/date), an ARRAY (multi-choice, several files),
103
- * an OBJECT (an address, keyed by subfield — the shape behind `address/city` error paths). An
104
- * empty optional field is OMITTED rather than sent as "": the server validates what it is given.
105
- */
106
- export function toSubmissionValues(fields: FormFieldDto[], values: FormValues): Record<string, unknown> {
107
- const filled = (v: unknown) => v !== undefined && v !== null && String(v).trim() !== "";
108
- const out: Record<string, unknown> = {};
109
-
110
- for (const field of fields) {
111
- const raw = values?.[field.target];
112
-
113
- if (field.control === "address") {
114
- const parts: Record<string, string> = {};
115
- const held = (raw ?? {}) as Record<string, unknown>;
116
- for (const { sub } of field.addressParts) {
117
- if (filled(held[sub])) parts[sub] = String(held[sub]).trim();
118
- }
119
- if (Object.keys(parts).length) out[field.target] = parts;
120
- continue;
121
- }
122
-
123
- if (field.control === "file" || field.control === "signature") {
124
- // Whatever `uploadFiles` produced. A stray File that never went through the upload is
125
- // dropped rather than sent, since it would 400 the whole form.
126
- const urls = ([] as unknown[]).concat(raw ?? []).filter((v): v is string => typeof v === "string" && !!v);
127
- if (urls.length) out[field.target] = urls.length === 1 ? urls[0] : urls;
128
- continue;
129
- }
130
-
131
- if (field.inputType === "ARRAY") {
132
- const picked = (Array.isArray(raw) ? raw : []).filter(filled);
133
- if (picked.length) out[field.target] = picked;
134
- continue;
135
- }
136
-
137
- if (field.control === "checkbox") {
138
- // A consent checkbox submits a boolean. Unchecked AND optional is omitted; unchecked and
139
- // required fails validation before it gets here.
140
- if (raw === true) out[field.target] = true;
141
- continue;
142
- }
143
-
144
- if (!filled(raw)) continue;
145
- const value = typeof raw === "string" ? raw.trim() : raw;
146
- out[field.target] =
147
- field.control === "number" || field.control === "rating" ? Number(value) :
148
- field.control === "phone" ? normalizePhone(value) :
149
- value;
150
- }
151
- return out;
45
+ /** Upload every File in the form's values; returns a copy with each file field holding its URL(s). */
46
+ export async function uploadFiles(formId: string, fields: FormFieldDto[], values: FormValues): Promise<FormValues> {
47
+ return uploadFilesWith(uploadFile, formId, fields, values);
152
48
  }
153
49
 
154
50
  /**
155
51
  * Create the submission. This is the write — and the only confirmation there is.
156
52
  *
157
- * ⚠️ Let a rejection throw. `submissionErrors` below turns it into per-control messages; a
158
- * `.catch` that swallows it costs the visitor the reason their form would not send.
53
+ * Let a rejection throw. `submissionErrors` turns it into per-control messages; a `.catch`
54
+ * that swallows it costs the visitor the reason their form would not send.
159
55
  */
160
- export async function createSubmission(
161
- formId: string,
162
- values: Record<string, unknown>,
163
- ): Promise<SubmissionDto> {
56
+ export async function createSubmission(formId: string, values: Record<string, unknown>): Promise<SubmissionDto> {
164
57
  const created = (await submissions.createSubmission({ formId, submissions: values })) as Raw;
165
- const submission = created?.submission ?? created;
166
- const id: string | undefined = submission?._id ?? submission?.id;
167
- if (!id) throw new Error("forms: submission failed (nothing returned).");
168
- const status: string = submission.status ?? "";
169
- if (!SUBMITTED_OK.has(status)) {
170
- throw new Error(
171
- `forms: submission status is "${status}" — not one of the statuses that mean the submission ` +
172
- `was created (${[...SUBMITTED_OK].join(", ")}), so do not show a success state.`,
173
- );
174
- }
175
- return { id, status };
176
- }
177
-
178
- /**
179
- * Pull per-field violations out of a failed create, keyed by input NAME so each message lands
180
- * on its own control. `errorPath` is the field's `target`, or a nested path like
181
- * `address/subdivision` — exactly how the controls are named, so it maps straight across.
182
- *
183
- * The documented entries arrive under `details.validationError.fieldViolations[]`, each with
184
- * its own nested `data.errors[]` — two levels deeper than the docs' shape. This flattens that.
185
- *
186
- * ⚠️ Two rejections here are SEED bugs, not frontend bugs — fix them in `seed/SEED.md`, never
187
- * by mangling the key or the value:
188
- * • UNKNOWN_VALUE_ERROR on a key that IS in the schema → the field was seeded with no
189
- * `validation` block, and that block is what registers the target as an accepted value.
190
- * • NOT_ALLOWED_VALUE_ERROR on a choice field → the seed's `options[].value` and its
191
- * validation enum disagree; the two declarations must match.
192
- */
193
- export function submissionErrors(err: unknown, fields: FormFieldDto[]): Record<string, string> {
194
- const body = (err as Raw)?.details ?? (err as Raw)?.body?.details ?? {};
195
- const violations: Raw[] = body?.validationError?.fieldViolations ?? [];
196
- const byTarget = new Map(fields.map((f) => [f.target, f]));
197
- const out: Record<string, string> = {};
198
-
199
- for (const entry of violations.flatMap((v) => v.data?.errors ?? [v])) {
200
- const path: string | undefined = entry?.errorPath;
201
- if (!path) continue;
202
- const field = byTarget.get(path.split("/")[0]);
203
- if (!field) continue;
204
- // Wix's own errorMessage is the validator's internal wording — debug only.
205
- console.debug("forms: server violation", path, entry.errorType, entry.errorMessage);
206
- out[path] = messageFor(entry.errorType, field);
207
- }
208
- return out;
209
- }
210
-
211
- /** errorType → visitor-facing copy, written from the field's own schema. */
212
- function messageFor(errorType: string, f: FormFieldDto): string {
213
- const v = f.validation;
214
- switch (errorType) {
215
- case "REQUIRED_VALUE_ERROR": return `${f.label} is required.`;
216
- case "MIN_LENGTH_ERROR": return `${f.label} must be at least ${v.minLength} characters.`;
217
- case "MAX_LENGTH_ERROR": return `${f.label} must be at most ${v.maxLength} characters.`;
218
- case "MIN_VALUE_ERROR": return `${f.label} must be ${v.minimum ?? "higher"} or more.`;
219
- case "MAX_VALUE_ERROR": return `${f.label} must be ${v.maximum ?? "lower"} or less.`;
220
- case "PATTERN_ERROR": return `${f.label} is not in the expected format.`;
221
- case "NOT_ALLOWED_VALUE_ERROR": return `Choose one of the listed options for ${f.label}.`;
222
- case "FORMAT_ERROR":
223
- return v.format === "EMAIL" ? "Enter a valid email address."
224
- : v.format === "PHONE" ? "Use international format, starting with +."
225
- : v.format === "URL" ? "Enter a full URL starting with https://"
226
- : `Please check ${f.label}.`;
227
- // The enum grows: MIN/MAX_ITEMS_ERROR and DISABLED_FORM_ERROR are reachable without being
228
- // listed, so an unmapped type degrades to safe copy rather than showing nothing.
229
- default: return `Please check ${f.label}.`;
230
- }
58
+ return toSubmission(created);
231
59
  }
@@ -0,0 +1,34 @@
1
+ // Form-schema reads over REST — the twin of app/wix/forms/forms.ts. Same exports, same FormDto; the
2
+ // flattening rules come from forms-core (the SAME file the SDK transport uses, deployed flat next
3
+ // to this one by deploy.mjs --stack static), so this file is only the transport: one fetch per
4
+ // function. Both calls run with the visitor token — the schema read is listed under an owner scope
5
+ // and still returns 200 to an anonymous visitor, so a published site can render its own forms.
6
+ // Porting: keep the paths, keep the query, port the core once.
7
+ // docs: https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/get-form.md
8
+ // docs: https://dev.wix.com/docs/api-reference/crm/forms/form-schemas/list-forms.md
9
+ import { wixRequest } from "./client.js";
10
+ import { FORMS_NAMESPACE, toForm, type Raw } from "./forms-core.js";
11
+ import type { FormDto } from "./types.js";
12
+
13
+ export { FORMS_NAMESPACE };
14
+
15
+ /**
16
+ * One form by id. Throws (a WixApiError, 404 FORM_NOT_FOUND) when the id is wrong or the form was
17
+ * deleted — a form that cannot load is a setup problem; never render a hand-built fallback.
18
+ * GET /form-schema-service/v4/forms/{formId} → { form }
19
+ */
20
+ export async function getForm(formId: string): Promise<FormDto> {
21
+ const res = await wixRequest<Raw>(`/form-schema-service/v4/forms/${encodeURIComponent(formId)}`, { method: "GET" });
22
+ if (!res?.form) throw new Error(`forms: form "${formId}" not found.`);
23
+ return toForm(res.form);
24
+ }
25
+
26
+ /**
27
+ * Every ENABLED form in the Wix Forms namespace (a disabled form vanishes from the listing rather
28
+ * than erroring). `namespace` is required — without it the call is a 400.
29
+ * GET /form-schema-service/v4/forms?namespace=wix.form_app.form → { forms, pagingMetadata }
30
+ */
31
+ export async function listForms(): Promise<FormDto[]> {
32
+ const res = await wixRequest<Raw>("/form-schema-service/v4/forms", { method: "GET", query: { namespace: FORMS_NAMESPACE } });
33
+ return ((res?.forms ?? []) as Raw[]).map(toForm);
34
+ }
@@ -0,0 +1,56 @@
1
+ // Submissions over REST — the twin of app/wix/forms/submissions.ts. Same exports, same
2
+ // SubmissionDto; the rules come from submissions-core (the SAME file the SDK transport uses,
3
+ // deployed flat next to this one). Both calls run with the visitor token: `CreateSubmission` is
4
+ // listed under an owner scope and still returns 200 to an anonymous visitor, so a published site
5
+ // can submit its own forms. Submissions are write-only from a visitor — the resolved create IS
6
+ // the confirmation. A rejection throws a WixApiError whose `details` block `submissionErrors`
7
+ // maps onto the controls; let it throw.
8
+ // docs: https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/create-submission.md
9
+ // docs: https://dev.wix.com/docs/api-reference/crm/forms/form-submissions/get-media-upload-url.md
10
+ import { wixRequest } from "./client.js";
11
+ import type { Raw } from "./forms-core.js";
12
+ import {
13
+ SUBMITTED_OK,
14
+ normalizePhone,
15
+ putUpload,
16
+ submissionErrors,
17
+ toSubmission,
18
+ toSubmissionValues,
19
+ uploadFilesWith,
20
+ uploadMimeType,
21
+ } from "./submissions-core.js";
22
+ import type { FormFieldDto, FormValues, SubmissionDto } from "./types.js";
23
+
24
+ export { SUBMITTED_OK, normalizePhone, submissionErrors, toSubmissionValues };
25
+
26
+ const SUBMISSIONS = "/form-submission-service/v4/submissions";
27
+
28
+ /**
29
+ * Upload one File and return the value to submit for its field (the upload URL itself).
30
+ * POST /form-submission-service/v4/submissions/media-upload-url { formId, filename, mimeType } → { uploadUrl }
31
+ * then PUT the bytes to that URL with plain fetch (submissions-core.putUpload).
32
+ */
33
+ export async function uploadFile(formId: string, file: File): Promise<string> {
34
+ const mimeType = uploadMimeType(file);
35
+ const res = await wixRequest<Raw>(`${SUBMISSIONS}/media-upload-url`, { body: { formId, filename: file.name, mimeType } });
36
+ const uploadUrl: string | undefined = res?.uploadUrl;
37
+ if (!uploadUrl) throw new Error(`forms: no upload URL for "${file.name}".`);
38
+ return putUpload(uploadUrl, file, mimeType);
39
+ }
40
+
41
+ /** Upload every File in the form's values; returns a copy with each file field holding its URL(s). */
42
+ export async function uploadFiles(formId: string, fields: FormFieldDto[], values: FormValues): Promise<FormValues> {
43
+ return uploadFilesWith(uploadFile, formId, fields, values);
44
+ }
45
+
46
+ /**
47
+ * Create the submission — the write, and the only confirmation there is. `values` is
48
+ * toSubmissionValues(fields, values): every key a field `target`, every value in that field's
49
+ * shape. A 400 carries details.validationError.fieldViolations[].data.errors[] (errorPath,
50
+ * errorType) on the thrown WixApiError.
51
+ * POST /form-submission-service/v4/submissions { submission: { formId, submissions } } → { submission: { id, status, … } }
52
+ */
53
+ export async function createSubmission(formId: string, values: Record<string, unknown>): Promise<SubmissionDto> {
54
+ const created = await wixRequest<Raw>(SUBMISSIONS, { body: { submission: { formId, submissions: values } } });
55
+ return toSubmission(created);
56
+ }
@@ -106,3 +106,11 @@ Listed because a hand-rolled payload hits all of them, and each returns `200` fi
106
106
  - **`target` is immutable** and must be unique within the form: letters, digits and single
107
107
  underscores, starting with a letter.
108
108
  - **Use `formFields`, never `fields`** — the latter is the legacy API.
109
+
110
+ ## Supplied content
111
+
112
+ The general rules are in `references/shared/SUPPLIED-CONTENT.md`. For forms the user usually hands
113
+ over the form itself: a list of questions, or a PDF or screenshot of the form they use today. Each
114
+ question becomes a field, with the `kind` its answer implies (an email address → `email`, a fixed set
115
+ of answers → `select`, `radio` or `multi`, a long answer → `textarea`); a required mark becomes
116
+ `required`; the button label becomes `submitText`. Question wording verbatim.
@@ -70,18 +70,20 @@ export async function installFormsApp(ctx) {
70
70
 
71
71
  // ---- field expansion -------------------------------------------------------------------------
72
72
 
73
- // kind → { inputType, componentType, identifier, block, componentBlock, format? }
73
+ // kind → { inputType, componentType, identifier, format?, contact? }
74
74
  // The two block names are what the API nests settings under, and they are named after the
75
75
  // field's own enums — which is why they are looked up here rather than spelled at each call.
76
+ // `contact` is the per-field contactMapping: it is what makes a submission create or update the
77
+ // site's CRM contact (the older postSubmissionTriggers.upsertContact is ignored by v4).
76
78
  const KINDS = {
77
79
  text: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "TEXT_INPUT" },
78
80
  textarea: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "TEXT_AREA" },
79
- email: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "CONTACTS_EMAIL", format: "EMAIL" },
80
- phone: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "CONTACTS_PHONE", format: "PHONE" },
81
+ email: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "CONTACTS_EMAIL", format: "EMAIL", contact: { contactField: "EMAIL", emailInfo: { tag: "UNTAGGED" } } },
82
+ phone: { inputType: "STRING", componentType: "PHONE_INPUT", identifier: "CONTACTS_PHONE", format: "PHONE", contact: { contactField: "PHONE", phoneInfo: { tag: "UNTAGGED" } } },
81
83
  url: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "URL_INPUT", format: "URL" },
82
- firstName: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "CONTACTS_FIRST_NAME" },
83
- lastName: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "CONTACTS_LAST_NAME" },
84
- company: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "CONTACTS_COMPANY" },
84
+ firstName: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "CONTACTS_FIRST_NAME", contact: { contactField: "FIRST_NAME" } },
85
+ lastName: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "CONTACTS_LAST_NAME", contact: { contactField: "LAST_NAME" } },
86
+ company: { inputType: "STRING", componentType: "TEXT_INPUT", identifier: "CONTACTS_COMPANY", contact: { contactField: "COMPANY" } },
85
87
  date: { inputType: "STRING", componentType: "DATE_PICKER", identifier: "DATE_PICKER", format: "DATE" },
86
88
  number: { inputType: "NUMBER", componentType: "NUMBER_INPUT", identifier: "NUMBER_INPUT" },
87
89
  rating: { inputType: "NUMBER", componentType: "RATING_INPUT", identifier: "RATING_INPUT" },
@@ -98,7 +100,7 @@ const INPUT_BLOCK = {
98
100
  ARRAY: "arrayOptions", ADDRESS: "addressOptions", WIX_FILE: "wixFileOptions",
99
101
  };
100
102
  const COMPONENT_BLOCK = {
101
- TEXT_INPUT: "textInputOptions", NUMBER_INPUT: "numberInputOptions",
103
+ TEXT_INPUT: "textInputOptions", PHONE_INPUT: "phoneInputOptions", NUMBER_INPUT: "numberInputOptions",
102
104
  RATING_INPUT: "ratingInputOptions", DATE_PICKER: "datePickerOptions",
103
105
  CHECKBOX: "checkboxOptions", CHECKBOX_GROUP: "checkboxGroupOptions",
104
106
  RADIO_GROUP: "radioGroupOptions", DROPDOWN: "dropdownOptions",
@@ -179,6 +181,9 @@ function buildField(spec, taken) {
179
181
  inputType: kind.inputType,
180
182
  // `required` lives HERE, never inside the validation block.
181
183
  required: spec.required ?? false,
184
+ // Contact fields carry pii and the CRM mapping; without contactMapping a submission is
185
+ // stored but no contact is created or updated.
186
+ ...(kind.contact ? { pii: true, contactMapping: kind.contact } : {}),
182
187
  [INPUT_BLOCK[kind.inputType]]: {
183
188
  // `validation` is always present, even as {}, and nests under the INPUT-TYPE block —
184
189
  // not the component one. Absent, the target is not registered as an accepted value and
@@ -201,7 +206,9 @@ function buildForm(planForm) {
201
206
  identifier: "SUBMIT_BUTTON",
202
207
  fieldType: "DISPLAY",
203
208
  displayOptions: {
204
- displayFieldType: "SUBMIT_BUTTON",
209
+ // The button's identifier is SUBMIT_BUTTON; its display type is PAGE_NAVIGATION (the enum
210
+ // has no SUBMIT_BUTTON value — the create is a 400 with anything else).
211
+ displayFieldType: "PAGE_NAVIGATION",
205
212
  pageNavigationOptions: { submitText: planForm.submitText ?? "Submit" },
206
213
  },
207
214
  };