@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.
- package/package.json +1 -1
- 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 +29 -34
- 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 +45 -4
- 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 +182 -63
- 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 +29 -58
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useProductDetail.ts +29 -163
- package/skills/wix-headless-fast/references/storefront/app/hooks/storefront/useShop.ts +26 -300
- 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 +49 -162
- 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 +47 -370
- 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/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 -9
- package/skills/wix-headless-fast/references/storefront/seed/seed-store.mjs +6 -35
- package/skills/wix-manage/references/ecommerce/ecom-shipping.md +1 -1
- package/skills/wix-vibe-headless/references/storefront/INSTRUCTIONS.md +0 -40
- 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,13 +1,14 @@
|
|
|
1
|
-
// Wix Forms submissions (@wix/forms `submissions`) —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
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
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
96
|
-
|
|
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
|
-
*
|
|
158
|
-
*
|
|
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
|
-
|
|
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,
|
|
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: "
|
|
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
|
-
|
|
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
|
};
|