@bacano/sdk 1.1.20 → 1.2.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/STOREFRONT_FORMS.md +7 -0
- package/STOREFRONT_MARKETING.md +103 -4
- package/index.cjs +253 -84
- package/index.js +253 -84
- package/marketing/browser-adapters.d.ts +30 -0
- package/marketing/clarity.d.ts +1 -1
- package/marketing/form-interactions.d.ts +12 -0
- package/marketing/index.d.ts +2 -0
- package/marketing/interactions.d.ts +19 -0
- package/marketing/types.d.ts +5 -2
- package/package.json +1 -1
- package/react/cta-tracking.d.ts +5 -0
- package/react/form-sections.d.ts +16 -0
- package/react/form-tracking.d.ts +11 -0
- package/react/forms.cjs +230 -93
- package/react/forms.d.ts +6 -2
- package/react/forms.js +222 -85
- package/react/marketing-provider.d.ts +2 -1
- package/react.cjs +342 -134
- package/react.d.ts +1 -0
- package/react.js +325 -117
- package/skills/bacano-web/SKILL.md +5 -0
- package/skills/bacano-web/references/forms.md +5 -0
- package/skills/bacano-web/references/interactions.md +147 -0
- package/skills/bacano-web/references/report.md +5 -0
package/react/forms.cjs
CHANGED
|
@@ -21,10 +21,25 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var forms_exports = {};
|
|
22
22
|
__export(forms_exports, {
|
|
23
23
|
BacanoForm: () => BacanoForm,
|
|
24
|
-
useBacanoForm: () => useBacanoForm
|
|
24
|
+
useBacanoForm: () => useBacanoForm,
|
|
25
|
+
useBacanoFormTracking: () => useBacanoFormTracking
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(forms_exports);
|
|
27
|
-
var
|
|
28
|
+
var import_react4 = require("react");
|
|
29
|
+
|
|
30
|
+
// libs/sdk/src/errors.ts
|
|
31
|
+
var WebsiteError = class extends Error {
|
|
32
|
+
code;
|
|
33
|
+
status;
|
|
34
|
+
retryAfterSeconds;
|
|
35
|
+
constructor(message, code = "WEBSITE_ERROR", options) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "WebsiteError";
|
|
38
|
+
this.code = code;
|
|
39
|
+
this.status = options?.status ?? null;
|
|
40
|
+
this.retryAfterSeconds = options?.retryAfterSeconds ?? null;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
28
43
|
|
|
29
44
|
// libs/sdk/src/react/form-field-control.tsx
|
|
30
45
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -114,9 +129,111 @@ function BacanoFormFieldControl(props) {
|
|
|
114
129
|
);
|
|
115
130
|
}
|
|
116
131
|
|
|
117
|
-
// libs/sdk/src/react/
|
|
132
|
+
// libs/sdk/src/react/form-sections.tsx
|
|
133
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
134
|
+
function FormFields(props) {
|
|
135
|
+
return props.fields.map((field) => {
|
|
136
|
+
const controlId = `${props.idPrefix}-${field.key}`;
|
|
137
|
+
const value = props.values[field.key] ?? (field.type === "checkbox" ? false : field.type === "multi_select" ? [] : "");
|
|
138
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: props.classNames?.field, children: [
|
|
139
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
140
|
+
"label",
|
|
141
|
+
{
|
|
142
|
+
id: `${controlId}-label`,
|
|
143
|
+
htmlFor: field.type === "radio" ? void 0 : controlId,
|
|
144
|
+
className: props.classNames?.label,
|
|
145
|
+
children: field.label
|
|
146
|
+
}
|
|
147
|
+
),
|
|
148
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
149
|
+
BacanoFormFieldControl,
|
|
150
|
+
{
|
|
151
|
+
field,
|
|
152
|
+
id: controlId,
|
|
153
|
+
value,
|
|
154
|
+
className: props.classNames?.input,
|
|
155
|
+
onChange: (nextValue) => props.onChange(field.key, nextValue)
|
|
156
|
+
}
|
|
157
|
+
),
|
|
158
|
+
field.helpText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: props.classNames?.helpText, children: field.helpText }) : null
|
|
159
|
+
] }, field.key);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function FormSections(props) {
|
|
163
|
+
return props.sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: props.classNames?.section, children: [
|
|
164
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: props.classNames?.sectionTitle, children: section.title }),
|
|
165
|
+
section.description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: props.classNames?.sectionDescription, children: section.description }) : null,
|
|
166
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
167
|
+
FormFields,
|
|
168
|
+
{
|
|
169
|
+
fields: section.fields,
|
|
170
|
+
idPrefix: props.idPrefix,
|
|
171
|
+
values: props.values,
|
|
172
|
+
classNames: props.classNames,
|
|
173
|
+
onChange: props.onChange
|
|
174
|
+
}
|
|
175
|
+
)
|
|
176
|
+
] }, section.key));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// libs/sdk/src/react/form-tracking.ts
|
|
118
180
|
var import_react2 = require("react");
|
|
119
181
|
|
|
182
|
+
// libs/sdk/src/marketing/interactions.ts
|
|
183
|
+
function newMarketingEventId() {
|
|
184
|
+
return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `evt_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// libs/sdk/src/marketing/form-interactions.ts
|
|
188
|
+
function createFormInteractionTracker(getRuntime, formKey) {
|
|
189
|
+
let visible = false;
|
|
190
|
+
let started = false;
|
|
191
|
+
let attempt = null;
|
|
192
|
+
const completedSteps = /* @__PURE__ */ new Set();
|
|
193
|
+
function emit(eventName, context = {}) {
|
|
194
|
+
try {
|
|
195
|
+
getRuntime().track({ eventName, formKey, ...context });
|
|
196
|
+
} catch {
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return {
|
|
200
|
+
setVisible(next) {
|
|
201
|
+
if (next && !visible && getRuntime().enabled) emit("form_open");
|
|
202
|
+
visible = next;
|
|
203
|
+
},
|
|
204
|
+
start() {
|
|
205
|
+
if (started || !getRuntime().enabled) return;
|
|
206
|
+
started = true;
|
|
207
|
+
emit("form_start");
|
|
208
|
+
},
|
|
209
|
+
stepComplete(stepKey) {
|
|
210
|
+
if (completedSteps.has(stepKey) || !getRuntime().enabled) return;
|
|
211
|
+
completedSteps.add(stepKey);
|
|
212
|
+
emit("form_step_complete", { stepKey });
|
|
213
|
+
},
|
|
214
|
+
beginSubmit() {
|
|
215
|
+
if (attempt) return false;
|
|
216
|
+
attempt = newMarketingEventId();
|
|
217
|
+
emit("form_submit_attempt", { eventId: attempt });
|
|
218
|
+
return true;
|
|
219
|
+
},
|
|
220
|
+
fail(errorCategory) {
|
|
221
|
+
if (!attempt) return;
|
|
222
|
+
emit("form_submit_error", { eventId: attempt, errorCategory });
|
|
223
|
+
attempt = null;
|
|
224
|
+
},
|
|
225
|
+
complete(result) {
|
|
226
|
+
if (!attempt) return;
|
|
227
|
+
if (result?.accepted && typeof result.responseId === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
|
228
|
+
result.responseId
|
|
229
|
+
)) {
|
|
230
|
+
emit("form_submit_success", { eventId: result.responseId });
|
|
231
|
+
}
|
|
232
|
+
attempt = null;
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
120
237
|
// libs/sdk/src/react/context-registry.ts
|
|
121
238
|
var import_react = require("react");
|
|
122
239
|
var REACT_CONTEXT_REGISTRY = /* @__PURE__ */ Symbol.for(
|
|
@@ -143,6 +260,37 @@ function getOrCreateReactContext(key, defaultValue) {
|
|
|
143
260
|
return context;
|
|
144
261
|
}
|
|
145
262
|
|
|
263
|
+
// libs/sdk/src/react/marketing-context.ts
|
|
264
|
+
var NOOP_MARKETING_RUNTIME = {
|
|
265
|
+
enabled: false,
|
|
266
|
+
track: () => void 0,
|
|
267
|
+
captureAttribution: () => null,
|
|
268
|
+
getAttribution: () => null,
|
|
269
|
+
destroy: () => void 0
|
|
270
|
+
};
|
|
271
|
+
var BacanoMarketingContext = getOrCreateReactContext(
|
|
272
|
+
"bacano-marketing-v1",
|
|
273
|
+
NOOP_MARKETING_RUNTIME
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
// libs/sdk/src/react/form-tracking.ts
|
|
277
|
+
function useBacanoFormTracking(formKey, options = {}) {
|
|
278
|
+
const runtime = (0, import_react2.useContext)(BacanoMarketingContext);
|
|
279
|
+
const current = (0, import_react2.useRef)(runtime);
|
|
280
|
+
current.current = runtime;
|
|
281
|
+
const tracker = (0, import_react2.useMemo)(
|
|
282
|
+
() => createFormInteractionTracker(() => current.current, formKey),
|
|
283
|
+
[formKey]
|
|
284
|
+
);
|
|
285
|
+
(0, import_react2.useEffect)(() => {
|
|
286
|
+
tracker.setVisible((options.visible ?? true) && runtime.enabled);
|
|
287
|
+
}, [tracker, options.visible, runtime.enabled]);
|
|
288
|
+
return tracker;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// libs/sdk/src/react/hooks.ts
|
|
292
|
+
var import_react3 = require("react");
|
|
293
|
+
|
|
146
294
|
// libs/sdk/src/react/context.ts
|
|
147
295
|
var BacanoReactContext = getOrCreateReactContext("bacano-client-v1", {
|
|
148
296
|
client: null,
|
|
@@ -152,11 +300,11 @@ var BacanoReactContext = getOrCreateReactContext("bacano-client-v1", {
|
|
|
152
300
|
|
|
153
301
|
// libs/sdk/src/react/hooks.ts
|
|
154
302
|
function useBacanoState() {
|
|
155
|
-
return (0,
|
|
303
|
+
return (0, import_react3.useContext)(BacanoReactContext);
|
|
156
304
|
}
|
|
157
305
|
|
|
158
306
|
// libs/sdk/src/react/forms.tsx
|
|
159
|
-
var
|
|
307
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
160
308
|
var EMPTY_FORM_FIELDS = [];
|
|
161
309
|
var EMPTY_FORM_SECTIONS = [];
|
|
162
310
|
var EMPTY_FORM_STEPS = [];
|
|
@@ -168,31 +316,35 @@ function useBacanoForm(formKey, options) {
|
|
|
168
316
|
} = useBacanoState();
|
|
169
317
|
const enabled = options?.enabled ?? true;
|
|
170
318
|
const initialForm = options?.initialForm?.key === formKey ? options.initialForm : null;
|
|
171
|
-
const [form, setForm] = (0,
|
|
172
|
-
const formRef = (0,
|
|
173
|
-
const
|
|
174
|
-
const [
|
|
175
|
-
const
|
|
319
|
+
const [form, setForm] = (0, import_react4.useState)(initialForm);
|
|
320
|
+
const formRef = (0, import_react4.useRef)(initialForm);
|
|
321
|
+
const requestVersion = (0, import_react4.useRef)(0);
|
|
322
|
+
const [loading, setLoading] = (0, import_react4.useState)(enabled && !initialForm);
|
|
323
|
+
const [error, setError] = (0, import_react4.useState)(null);
|
|
324
|
+
const load = (0, import_react4.useCallback)(
|
|
176
325
|
async (force = false) => {
|
|
177
326
|
if (!enabled || !client) return;
|
|
327
|
+
const version = ++requestVersion.current;
|
|
178
328
|
setLoading(formRef.current === null);
|
|
179
329
|
setError(null);
|
|
180
330
|
try {
|
|
181
331
|
const nextForm = await client.forms.getDefinition(formKey, { force });
|
|
332
|
+
if (version !== requestVersion.current) return;
|
|
182
333
|
formRef.current = nextForm;
|
|
183
334
|
setForm(nextForm);
|
|
184
335
|
} catch (loadError) {
|
|
336
|
+
if (version !== requestVersion.current) return;
|
|
185
337
|
if (!formRef.current) setForm(null);
|
|
186
338
|
setError(
|
|
187
339
|
loadError instanceof Error ? loadError : new Error(String(loadError))
|
|
188
340
|
);
|
|
189
341
|
} finally {
|
|
190
|
-
setLoading(false);
|
|
342
|
+
if (version === requestVersion.current) setLoading(false);
|
|
191
343
|
}
|
|
192
344
|
},
|
|
193
345
|
[client, enabled, formKey]
|
|
194
346
|
);
|
|
195
|
-
(0,
|
|
347
|
+
(0, import_react4.useEffect)(() => {
|
|
196
348
|
if (!enabled) {
|
|
197
349
|
formRef.current = null;
|
|
198
350
|
setForm(null);
|
|
@@ -201,76 +353,38 @@ function useBacanoForm(formKey, options) {
|
|
|
201
353
|
return;
|
|
202
354
|
}
|
|
203
355
|
if (client) void load();
|
|
356
|
+
return () => {
|
|
357
|
+
requestVersion.current += 1;
|
|
358
|
+
};
|
|
204
359
|
}, [client, enabled, load]);
|
|
205
360
|
return {
|
|
206
|
-
form,
|
|
207
|
-
loading: enabled && form
|
|
361
|
+
form: enabled ? form?.key === formKey ? form : initialForm : null,
|
|
362
|
+
loading: enabled && form?.key !== formKey && !initialForm && (clientLoading || loading),
|
|
208
363
|
error: clientError ?? error,
|
|
209
364
|
refresh: async () => load(true)
|
|
210
365
|
};
|
|
211
366
|
}
|
|
212
|
-
function
|
|
213
|
-
|
|
214
|
-
const controlId = `${props.idPrefix}-${field.key}`;
|
|
215
|
-
const value = props.values[field.key] ?? (field.type === "checkbox" ? false : field.type === "multi_select" ? [] : "");
|
|
216
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: props.classNames?.field, children: [
|
|
217
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
218
|
-
"label",
|
|
219
|
-
{
|
|
220
|
-
id: `${controlId}-label`,
|
|
221
|
-
htmlFor: field.type === "radio" ? void 0 : controlId,
|
|
222
|
-
className: props.classNames?.label,
|
|
223
|
-
children: field.label
|
|
224
|
-
}
|
|
225
|
-
),
|
|
226
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
227
|
-
BacanoFormFieldControl,
|
|
228
|
-
{
|
|
229
|
-
field,
|
|
230
|
-
id: controlId,
|
|
231
|
-
value,
|
|
232
|
-
className: props.classNames?.input,
|
|
233
|
-
onChange: (nextValue) => props.onChange(field.key, nextValue)
|
|
234
|
-
}
|
|
235
|
-
),
|
|
236
|
-
field.helpText ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: props.classNames?.helpText, children: field.helpText }) : null
|
|
237
|
-
] }, field.key);
|
|
238
|
-
});
|
|
239
|
-
}
|
|
240
|
-
function FormSections(props) {
|
|
241
|
-
return props.sections.map((section) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("section", { className: props.classNames?.section, children: [
|
|
242
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: props.classNames?.sectionTitle, children: section.title }),
|
|
243
|
-
section.description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: props.classNames?.sectionDescription, children: section.description }) : null,
|
|
244
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
245
|
-
FormFields,
|
|
246
|
-
{
|
|
247
|
-
fields: section.fields,
|
|
248
|
-
idPrefix: props.idPrefix,
|
|
249
|
-
values: props.values,
|
|
250
|
-
classNames: props.classNames,
|
|
251
|
-
onChange: props.onChange
|
|
252
|
-
}
|
|
253
|
-
)
|
|
254
|
-
] }, section.key));
|
|
255
|
-
}
|
|
256
|
-
function BacanoForm(props) {
|
|
257
|
-
const reactId = (0, import_react3.useId)();
|
|
367
|
+
function BacanoFormInstance(props) {
|
|
368
|
+
const reactId = (0, import_react4.useId)();
|
|
258
369
|
const idPrefix = `${props.formKey}-${reactId.replace(/:/g, "")}`;
|
|
259
370
|
const { client } = useBacanoState();
|
|
260
371
|
const { form, loading, error } = useBacanoForm(props.formKey, {
|
|
261
372
|
initialForm: props.initialForm
|
|
262
373
|
});
|
|
263
|
-
const [internalValues, setValues] = (0,
|
|
264
|
-
const [step, setStep] = (0,
|
|
265
|
-
const [submitting, setSubmitting] = (0,
|
|
266
|
-
const [submitted, setSubmitted] = (0,
|
|
267
|
-
const [submissionError, setSubmissionError] = (0,
|
|
268
|
-
const [honeypot, setHoneypot] = (0,
|
|
269
|
-
const
|
|
374
|
+
const [internalValues, setValues] = (0, import_react4.useState)({});
|
|
375
|
+
const [step, setStep] = (0, import_react4.useState)(0);
|
|
376
|
+
const [submitting, setSubmitting] = (0, import_react4.useState)(false);
|
|
377
|
+
const [submitted, setSubmitted] = (0, import_react4.useState)(false);
|
|
378
|
+
const [submissionError, setSubmissionError] = (0, import_react4.useState)(null);
|
|
379
|
+
const [honeypot, setHoneypot] = (0, import_react4.useState)("");
|
|
380
|
+
const tracking = useBacanoFormTracking(props.formKey, {
|
|
381
|
+
visible: (props.visible ?? true) && !loading && form?.status === "ACTIVE" && !submitted
|
|
382
|
+
});
|
|
383
|
+
const idempotencyKey = (0, import_react4.useRef)(crypto.randomUUID());
|
|
270
384
|
const fields = form?.definition.fields ?? EMPTY_FORM_FIELDS;
|
|
271
385
|
const sections = form?.definition.sections ?? EMPTY_FORM_SECTIONS;
|
|
272
386
|
const steps = form?.definition.steps ?? EMPTY_FORM_STEPS;
|
|
273
|
-
const fieldKeys = (0,
|
|
387
|
+
const fieldKeys = (0, import_react4.useMemo)(
|
|
274
388
|
() => new Set(
|
|
275
389
|
[
|
|
276
390
|
...fields,
|
|
@@ -288,7 +402,7 @@ function BacanoForm(props) {
|
|
|
288
402
|
([key]) => fieldKeys.has(key)
|
|
289
403
|
)
|
|
290
404
|
);
|
|
291
|
-
(0,
|
|
405
|
+
(0, import_react4.useEffect)(() => {
|
|
292
406
|
setStep(0);
|
|
293
407
|
setValues(
|
|
294
408
|
(current) => Object.fromEntries(
|
|
@@ -300,27 +414,37 @@ function BacanoForm(props) {
|
|
|
300
414
|
setHoneypot("");
|
|
301
415
|
idempotencyKey.current = crypto.randomUUID();
|
|
302
416
|
}, [fieldKeys, form?.version]);
|
|
303
|
-
if (loading) return /* @__PURE__ */ (0,
|
|
417
|
+
if (loading) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: props.loadingFallback ?? null });
|
|
304
418
|
if (error && !form)
|
|
305
|
-
return /* @__PURE__ */ (0,
|
|
306
|
-
if (!form) return /* @__PURE__ */ (0,
|
|
419
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: props.errorFallback?.(error) ?? props.unavailableFallback ?? null });
|
|
420
|
+
if (!form) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_jsx_runtime3.Fragment, { children: props.unavailableFallback ?? null });
|
|
307
421
|
if (form.status === "PAUSED") {
|
|
308
|
-
return /* @__PURE__ */ (0,
|
|
422
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: props.classNames?.message, "data-bacano-form-unavailable": true, children: props.unavailableFallback ?? form.definition.unavailableMessage });
|
|
309
423
|
}
|
|
310
424
|
if (submitted) {
|
|
311
|
-
return /* @__PURE__ */ (0,
|
|
425
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: props.classNames?.message, "data-bacano-form-success": true, children: form.definition.successMessage });
|
|
312
426
|
}
|
|
313
427
|
const isMultiStep = steps.length > 0;
|
|
314
428
|
const activeStep = isMultiStep ? steps[step] : null;
|
|
315
429
|
const isLastStep = !isMultiStep || step === steps.length - 1;
|
|
316
430
|
const setFieldValue = (key, value) => {
|
|
431
|
+
if (value !== "" && value !== false && (!Array.isArray(value) || value.length > 0))
|
|
432
|
+
tracking.start();
|
|
317
433
|
const nextValues = { ...values, [key]: value };
|
|
318
434
|
setValues(nextValues);
|
|
319
435
|
props.onValuesChange?.(nextValues);
|
|
320
436
|
};
|
|
321
437
|
const submit = async (event) => {
|
|
322
438
|
event.preventDefault();
|
|
439
|
+
if (submitting || submitted) return;
|
|
440
|
+
if (isLastStep && !tracking.beginSubmit()) return;
|
|
441
|
+
if (!event.currentTarget.checkValidity()) {
|
|
442
|
+
if (isLastStep) tracking.fail("validation");
|
|
443
|
+
event.currentTarget.reportValidity();
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
323
446
|
if (!isLastStep) {
|
|
447
|
+
if (activeStep) tracking.stepComplete(activeStep.key);
|
|
324
448
|
setStep((current) => current + 1);
|
|
325
449
|
return;
|
|
326
450
|
}
|
|
@@ -328,8 +452,12 @@ function BacanoForm(props) {
|
|
|
328
452
|
setSubmissionError(null);
|
|
329
453
|
try {
|
|
330
454
|
let responseId = null;
|
|
455
|
+
let officialResult;
|
|
331
456
|
if (props.onSubmit) {
|
|
332
|
-
await props.onSubmit(values, form);
|
|
457
|
+
officialResult = await props.onSubmit(values, form);
|
|
458
|
+
if (officialResult && !officialResult.accepted)
|
|
459
|
+
throw new Error(officialResult.message);
|
|
460
|
+
responseId = officialResult?.responseId ?? null;
|
|
333
461
|
} else {
|
|
334
462
|
if (!client) throw new Error("Bacano client is not ready");
|
|
335
463
|
const result = await client.forms.submitResponse({
|
|
@@ -341,11 +469,16 @@ function BacanoForm(props) {
|
|
|
341
469
|
honeypot
|
|
342
470
|
});
|
|
343
471
|
if (!result.accepted) throw new Error(result.message);
|
|
472
|
+
officialResult = result;
|
|
344
473
|
responseId = result.responseId;
|
|
345
474
|
}
|
|
475
|
+
tracking.complete(officialResult);
|
|
346
476
|
setSubmitted(true);
|
|
347
477
|
props.onSubmitted?.(responseId);
|
|
348
478
|
} catch (submitError) {
|
|
479
|
+
tracking.fail(
|
|
480
|
+
submitError instanceof WebsiteError && submitError.code === "NETWORK_ERROR" ? "network" : "rejected"
|
|
481
|
+
);
|
|
349
482
|
setSubmissionError(
|
|
350
483
|
submitError instanceof Error ? submitError : new Error(String(submitError))
|
|
351
484
|
);
|
|
@@ -353,15 +486,16 @@ function BacanoForm(props) {
|
|
|
353
486
|
setSubmitting(false);
|
|
354
487
|
}
|
|
355
488
|
};
|
|
356
|
-
return /* @__PURE__ */ (0,
|
|
489
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
357
490
|
"form",
|
|
358
491
|
{
|
|
492
|
+
noValidate: true,
|
|
359
493
|
className: props.classNames?.root,
|
|
360
494
|
"data-bacano-form": form.key,
|
|
361
495
|
"data-bacano-form-version": form.version,
|
|
362
496
|
onSubmit: (event) => void submit(event),
|
|
363
497
|
children: [
|
|
364
|
-
/* @__PURE__ */ (0,
|
|
498
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
365
499
|
"div",
|
|
366
500
|
{
|
|
367
501
|
"aria-hidden": "true",
|
|
@@ -373,8 +507,8 @@ function BacanoForm(props) {
|
|
|
373
507
|
clip: "rect(0 0 0 0)"
|
|
374
508
|
},
|
|
375
509
|
children: [
|
|
376
|
-
/* @__PURE__ */ (0,
|
|
377
|
-
/* @__PURE__ */ (0,
|
|
510
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("label", { htmlFor: `${form.key}-company-website`, children: "Empresa" }),
|
|
511
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
378
512
|
"input",
|
|
379
513
|
{
|
|
380
514
|
id: `${form.key}-company-website`,
|
|
@@ -388,12 +522,12 @@ function BacanoForm(props) {
|
|
|
388
522
|
]
|
|
389
523
|
}
|
|
390
524
|
),
|
|
391
|
-
/* @__PURE__ */ (0,
|
|
392
|
-
form.description ? /* @__PURE__ */ (0,
|
|
393
|
-
activeStep ? /* @__PURE__ */ (0,
|
|
394
|
-
/* @__PURE__ */ (0,
|
|
395
|
-
activeStep.description ? /* @__PURE__ */ (0,
|
|
396
|
-
/* @__PURE__ */ (0,
|
|
525
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: props.classNames?.title, children: form.name }),
|
|
526
|
+
form.description ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: props.classNames?.description, children: form.description }) : null,
|
|
527
|
+
activeStep ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: props.classNames?.step, "data-bacano-form-step": true, children: [
|
|
528
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: props.classNames?.stepTitle, children: activeStep.title }),
|
|
529
|
+
activeStep.description ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: props.classNames?.stepDescription, children: activeStep.description }) : null,
|
|
530
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
397
531
|
FormFields,
|
|
398
532
|
{
|
|
399
533
|
fields: activeStep.fields,
|
|
@@ -403,7 +537,7 @@ function BacanoForm(props) {
|
|
|
403
537
|
onChange: setFieldValue
|
|
404
538
|
}
|
|
405
539
|
),
|
|
406
|
-
/* @__PURE__ */ (0,
|
|
540
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
407
541
|
FormSections,
|
|
408
542
|
{
|
|
409
543
|
sections: activeStep.sections,
|
|
@@ -413,8 +547,8 @@ function BacanoForm(props) {
|
|
|
413
547
|
onChange: setFieldValue
|
|
414
548
|
}
|
|
415
549
|
)
|
|
416
|
-
] }) : /* @__PURE__ */ (0,
|
|
417
|
-
/* @__PURE__ */ (0,
|
|
550
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
551
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
418
552
|
FormFields,
|
|
419
553
|
{
|
|
420
554
|
fields,
|
|
@@ -424,7 +558,7 @@ function BacanoForm(props) {
|
|
|
424
558
|
onChange: setFieldValue
|
|
425
559
|
}
|
|
426
560
|
),
|
|
427
|
-
/* @__PURE__ */ (0,
|
|
561
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
428
562
|
FormSections,
|
|
429
563
|
{
|
|
430
564
|
sections,
|
|
@@ -435,8 +569,8 @@ function BacanoForm(props) {
|
|
|
435
569
|
}
|
|
436
570
|
)
|
|
437
571
|
] }),
|
|
438
|
-
/* @__PURE__ */ (0,
|
|
439
|
-
isMultiStep && step > 0 ? /* @__PURE__ */ (0,
|
|
572
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: props.classNames?.actions, children: [
|
|
573
|
+
isMultiStep && step > 0 ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
440
574
|
"button",
|
|
441
575
|
{
|
|
442
576
|
type: "button",
|
|
@@ -444,7 +578,7 @@ function BacanoForm(props) {
|
|
|
444
578
|
children: "Anterior"
|
|
445
579
|
}
|
|
446
580
|
) : null,
|
|
447
|
-
/* @__PURE__ */ (0,
|
|
581
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
448
582
|
"button",
|
|
449
583
|
{
|
|
450
584
|
type: "submit",
|
|
@@ -454,7 +588,7 @@ function BacanoForm(props) {
|
|
|
454
588
|
}
|
|
455
589
|
)
|
|
456
590
|
] }),
|
|
457
|
-
submissionError ? /* @__PURE__ */ (0,
|
|
591
|
+
submissionError ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
458
592
|
"div",
|
|
459
593
|
{
|
|
460
594
|
role: "alert",
|
|
@@ -467,3 +601,6 @@ function BacanoForm(props) {
|
|
|
467
601
|
}
|
|
468
602
|
);
|
|
469
603
|
}
|
|
604
|
+
function BacanoForm(props) {
|
|
605
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(BacanoFormInstance, { ...props }, props.formKey);
|
|
606
|
+
}
|
package/react/forms.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
|
-
import type { WebsitePublishedForm } from "../types";
|
|
2
|
+
import type { WebsiteFormResponseResult, WebsitePublishedForm } from "../types";
|
|
3
|
+
export { useBacanoFormTracking } from "./form-tracking";
|
|
3
4
|
export type BacanoFormValues = Record<string, string | string[] | boolean>;
|
|
4
5
|
export interface UseBacanoFormOptions {
|
|
5
6
|
enabled?: boolean;
|
|
@@ -33,6 +34,8 @@ export interface BacanoFormClassNames {
|
|
|
33
34
|
}
|
|
34
35
|
export interface BacanoFormProps {
|
|
35
36
|
formKey: string;
|
|
37
|
+
/** Pass the actual modal/drawer visibility when it stays mounted while closed. */
|
|
38
|
+
visible?: boolean;
|
|
36
39
|
/** Controlled values. Use together with onValuesChange; option values, not labels. */
|
|
37
40
|
values?: BacanoFormValues;
|
|
38
41
|
/** Receives the complete next values after a visitor edits a field. */
|
|
@@ -43,11 +46,12 @@ export interface BacanoFormProps {
|
|
|
43
46
|
* Optional custom submission. When omitted, the official Bacano response
|
|
44
47
|
* endpoint is used. Existing storefronts may keep a custom handler.
|
|
45
48
|
*/
|
|
46
|
-
onSubmit?(values: BacanoFormValues, form: WebsitePublishedForm): void | Promise<void>;
|
|
49
|
+
onSubmit?(values: BacanoFormValues, form: WebsitePublishedForm): void | WebsiteFormResponseResult | Promise<void | WebsiteFormResponseResult>;
|
|
47
50
|
onSubmitted?(responseId: string | null): void;
|
|
48
51
|
classNames?: BacanoFormClassNames;
|
|
49
52
|
loadingFallback?: ReactNode;
|
|
50
53
|
unavailableFallback?: ReactNode;
|
|
51
54
|
errorFallback?: (error: Error) => ReactNode;
|
|
52
55
|
}
|
|
56
|
+
/** A different form cannot inherit pending submission state from the old key. */
|
|
53
57
|
export declare function BacanoForm(props: BacanoFormProps): import("react").JSX.Element;
|