@nodii/onboarding 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +75 -2
- package/dist/chunk-7NS7ENJV.js +232 -0
- package/dist/chunk-7NS7ENJV.js.map +1 -0
- package/dist/chunk-AZE3NPSY.js +186 -0
- package/dist/chunk-AZE3NPSY.js.map +1 -0
- package/dist/chunk-BMQINH6O.js +401 -0
- package/dist/chunk-BMQINH6O.js.map +1 -0
- package/dist/chunk-F75ZJERM.js +199 -0
- package/dist/chunk-F75ZJERM.js.map +1 -0
- package/dist/chunk-FIRSA7EH.js +3 -0
- package/dist/chunk-FIRSA7EH.js.map +1 -0
- package/dist/chunk-IPAKPLIF.js +1660 -0
- package/dist/chunk-IPAKPLIF.js.map +1 -0
- package/dist/chunk-JFDQZ3AC.js +3 -0
- package/dist/chunk-JFDQZ3AC.js.map +1 -0
- package/dist/chunk-JFIONONR.js +3 -0
- package/dist/chunk-JFIONONR.js.map +1 -0
- package/dist/chunk-KQTS47FC.js +546 -0
- package/dist/chunk-KQTS47FC.js.map +1 -0
- package/dist/chunk-LVJM4CTG.js +306 -0
- package/dist/chunk-LVJM4CTG.js.map +1 -0
- package/dist/chunk-SEKNVHD3.js +59 -0
- package/dist/chunk-SEKNVHD3.js.map +1 -0
- package/dist/chunk-SIMLMQPT.js +37 -0
- package/dist/chunk-SIMLMQPT.js.map +1 -0
- package/dist/chunk-U56GQITG.js +79 -0
- package/dist/chunk-U56GQITG.js.map +1 -0
- package/dist/chunk-U6AXKGYQ.js +101 -0
- package/dist/chunk-U6AXKGYQ.js.map +1 -0
- package/dist/chunk-VH6FWDWI.js +3 -0
- package/dist/chunk-VH6FWDWI.js.map +1 -0
- package/dist/chunk-XJTJPBJB.js +191 -0
- package/dist/chunk-XJTJPBJB.js.map +1 -0
- package/dist/chunk-Z4PA6C2Z.js +3 -0
- package/dist/chunk-Z4PA6C2Z.js.map +1 -0
- package/dist/client-CqE2dF9A.d.ts +86 -0
- package/dist/consent/index.d.ts +59 -0
- package/dist/consent/index.js +7 -0
- package/dist/consent/index.js.map +1 -0
- package/dist/controller-BQb62D0N.d.ts +177 -0
- package/dist/dynamic/index.d.ts +235 -0
- package/dist/dynamic/index.js +6 -0
- package/dist/dynamic/index.js.map +1 -0
- package/dist/errors-vJkye4a0.d.ts +110 -0
- package/dist/form/index.d.ts +64 -0
- package/dist/form/index.js +5 -0
- package/dist/form/index.js.map +1 -0
- package/dist/index-Dqfa890U.d.ts +83 -0
- package/dist/index.d.ts +16 -2
- package/dist/index.js +18 -2
- package/dist/index.js.map +1 -0
- package/dist/model-ESLSEbi0.d.ts +640 -0
- package/dist/model-mnjAEtKe.d.ts +253 -0
- package/dist/recaptcha/index.d.ts +102 -0
- package/dist/recaptcha/index.js +4 -0
- package/dist/recaptcha/index.js.map +1 -0
- package/dist/render-DC7P2BGQ.js +5 -0
- package/dist/render-DC7P2BGQ.js.map +1 -0
- package/dist/schema-BrXb9gnh.d.ts +267 -0
- package/dist/signup-client-BL5DfarJ.d.ts +392 -0
- package/dist/spec/index.d.ts +17 -0
- package/dist/spec/index.js +5 -0
- package/dist/spec/index.js.map +1 -0
- package/dist/submit/index.d.ts +42 -0
- package/dist/submit/index.js +6 -0
- package/dist/submit/index.js.map +1 -0
- package/dist/viewer/index.d.ts +4 -0
- package/dist/viewer/index.js +5 -0
- package/dist/viewer/index.js.map +1 -0
- package/dist/widget/index.d.ts +262 -0
- package/dist/widget/index.js +12 -0
- package/dist/widget/index.js.map +1 -0
- package/package.json +96 -6
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { b as ConsentDocument, a as AcceptanceLevel, c as ConsentPack } from './model-mnjAEtKe.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Headless ConsentPack controller (D543 §2/§5).
|
|
5
|
+
*
|
|
6
|
+
* Framework-agnostic consent state for the signup flow's consent stage.
|
|
7
|
+
* Exposes, per document: { doc, accepted, gateSatisfied, evidence } and
|
|
8
|
+
* enforces the per-document ACCEPTANCE-LEVEL GATE (D543 §2):
|
|
9
|
+
*
|
|
10
|
+
* - checkbox — accept is enable-able immediately (basic clickwrap).
|
|
11
|
+
* - scroll_then_accept — accept is DISABLED until the rendered doc is
|
|
12
|
+
* scrolled to the end (evidence.scrolled_to_end).
|
|
13
|
+
* - open_then_accept — accept is DISABLED until the doc is opened/viewed
|
|
14
|
+
* (evidence.opened).
|
|
15
|
+
*
|
|
16
|
+
* The gate is a UX guard (D543 §5: "Client evidence is UX-only; the server
|
|
17
|
+
* re-validates"). But the controller ENFORCES it locally so a UI cannot
|
|
18
|
+
* present an accept affordance the gate hasn't unlocked, and so a headless
|
|
19
|
+
* caller cannot assemble a payload that claims an unsatisfied gate.
|
|
20
|
+
*
|
|
21
|
+
* COMPLIANCE-CRITICAL (same discipline as the stepper's consent-fabrication
|
|
22
|
+
* fix): `accept(key)` is REFUSED when the gate is not yet satisfied, and
|
|
23
|
+
* `buildConsentPayload()` THROWS if ANY required doc is not accepted at its
|
|
24
|
+
* gate. The controller NEVER fabricates acceptance or evidence — it only
|
|
25
|
+
* records what the user genuinely did. `isComplete()` is true only when every
|
|
26
|
+
* REQUIRED doc is accepted with its gate satisfied.
|
|
27
|
+
*
|
|
28
|
+
* Same subscribe/mutate contract as the dynamic stepper so the default
|
|
29
|
+
* component layer renders it and tenants can build their own UI against the
|
|
30
|
+
* exported control surface (D543 §5 headless flexibility).
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Evidence for a document's gate. Only the field relevant to the doc's
|
|
35
|
+
* acceptanceLevel is ever set true — a checkbox doc carries no evidence.
|
|
36
|
+
* Mirrors the D543 §4 submit `evidence:{scrolled_to_end?, opened?}` — these
|
|
37
|
+
* two keys stay snake_case (the ONLY snake bits in the consent wire shape).
|
|
38
|
+
*/
|
|
39
|
+
interface ConsentEvidence {
|
|
40
|
+
scrolled_to_end?: boolean;
|
|
41
|
+
opened?: boolean;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Per-document view of the consent state — the framework-agnostic "row model"
|
|
45
|
+
* a consumer binds their own rendering to (TanStack-style). NO DOM here.
|
|
46
|
+
*/
|
|
47
|
+
interface ConsentDocState {
|
|
48
|
+
/** The document descriptor (from the spec's consentPack). */
|
|
49
|
+
doc: ConsentDocument;
|
|
50
|
+
/** Stable client-side key (docType | custom:<customKey>). */
|
|
51
|
+
key: string;
|
|
52
|
+
/** The document's acceptance level, surfaced for convenience. */
|
|
53
|
+
acceptanceLevel: AcceptanceLevel;
|
|
54
|
+
/** Whether the document is required, surfaced for convenience. */
|
|
55
|
+
required: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Whether the acceptance-level GATE is satisfied — i.e. accept is
|
|
58
|
+
* enable-able. checkbox ⇒ always true; scroll ⇒ scrolled-to-end;
|
|
59
|
+
* open ⇒ opened.
|
|
60
|
+
*/
|
|
61
|
+
gateSatisfied: boolean;
|
|
62
|
+
/** Whether the user has genuinely accepted (only possible past the gate). */
|
|
63
|
+
accepted: boolean;
|
|
64
|
+
/** The evidence collected so far (drives gateSatisfied for gated levels). */
|
|
65
|
+
evidence: ConsentEvidence;
|
|
66
|
+
/**
|
|
67
|
+
* Scroll progress in [0,1] for a `scroll_then_accept` doc (0 for other
|
|
68
|
+
* levels). A consumer's scroll handler feeds this via reportScrollProgress;
|
|
69
|
+
* reaching ~1 flips gateSatisfied. Purely a UX-progress read (e.g. a
|
|
70
|
+
* progress bar); the gate itself keys on scrolledToEnd evidence.
|
|
71
|
+
*/
|
|
72
|
+
scrollProgress: number;
|
|
73
|
+
}
|
|
74
|
+
interface ConsentState {
|
|
75
|
+
/** All documents in spec order. */
|
|
76
|
+
documents: ConsentDocState[];
|
|
77
|
+
/** True iff every REQUIRED doc is accepted with its gate satisfied. */
|
|
78
|
+
complete: boolean;
|
|
79
|
+
}
|
|
80
|
+
type ConsentStateListener = (state: ConsentState) => void;
|
|
81
|
+
/**
|
|
82
|
+
* One element of the D543 §4 submit `consent.documents` array — CANONICAL
|
|
83
|
+
* camelCase (legalDocumentId, versionNo, accepted); `evidence` keys stay
|
|
84
|
+
* snake_case (scrolled_to_end / opened).
|
|
85
|
+
*/
|
|
86
|
+
interface ConsentSubmitDocument {
|
|
87
|
+
legalDocumentId: string;
|
|
88
|
+
versionNo: number;
|
|
89
|
+
accepted: true;
|
|
90
|
+
evidence: ConsentEvidence;
|
|
91
|
+
}
|
|
92
|
+
interface ConsentController {
|
|
93
|
+
getState(): ConsentState;
|
|
94
|
+
/** True iff there are no documents to consent to (stage is skippable). */
|
|
95
|
+
isEmpty(): boolean;
|
|
96
|
+
/** True iff every required doc is accepted with its gate satisfied. */
|
|
97
|
+
isComplete(): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Alias of isComplete() — TanStack-style ergonomic read: "can the consumer
|
|
100
|
+
* submit the consent step?" (all required docs accepted at their gate).
|
|
101
|
+
*/
|
|
102
|
+
canSubmit(): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Whether the given document's gate is satisfied (accept is enable-able).
|
|
105
|
+
* Unknown key ⇒ false.
|
|
106
|
+
*/
|
|
107
|
+
isGateSatisfied(key: string): boolean;
|
|
108
|
+
/**
|
|
109
|
+
* Report scroll progress in [0,1] for a `scroll_then_accept` doc — the
|
|
110
|
+
* consumer's OWN scroll handler feeds this (e.g. on its same-origin scroll
|
|
111
|
+
* container). Ratio ≥ SCROLL_END_THRESHOLD marks scrolled-to-end (opens the
|
|
112
|
+
* gate); otherwise it just updates scrollProgress for UX. Clamped to [0,1].
|
|
113
|
+
* No-op for other levels and unknown keys.
|
|
114
|
+
*/
|
|
115
|
+
reportScrollProgress(key: string, ratio: number): void;
|
|
116
|
+
/**
|
|
117
|
+
* Mark a `scroll_then_accept` doc as scrolled to the end (evidence). No-op
|
|
118
|
+
* for other levels (their gate isn't scroll-driven) and unknown keys.
|
|
119
|
+
* Equivalent to reportScrollProgress(key, 1).
|
|
120
|
+
*/
|
|
121
|
+
markScrolledToEnd(key: string): void;
|
|
122
|
+
/**
|
|
123
|
+
* Mark an `open_then_accept` doc as opened/viewed (evidence). No-op for
|
|
124
|
+
* other levels and unknown keys.
|
|
125
|
+
*/
|
|
126
|
+
markOpened(key: string): void;
|
|
127
|
+
/**
|
|
128
|
+
* Set a document's accepted flag. Accepting is REFUSED (accepted stays
|
|
129
|
+
* false) when the gate is not satisfied — a UI cannot record acceptance a
|
|
130
|
+
* user could not legitimately give. Unaccepting is always allowed.
|
|
131
|
+
*/
|
|
132
|
+
setAccepted(key: string, accepted: boolean): void;
|
|
133
|
+
/**
|
|
134
|
+
* Build the D543 §4 submit array from the ACTUAL consent state.
|
|
135
|
+
*
|
|
136
|
+
* @throws {Error} if any REQUIRED doc is not accepted with its gate
|
|
137
|
+
* satisfied — never fabricates acceptance/evidence (compliance-critical).
|
|
138
|
+
* Optional docs that were accepted ARE included; optional docs left
|
|
139
|
+
* unaccepted are omitted.
|
|
140
|
+
*/
|
|
141
|
+
buildConsentPayload(): ConsentSubmitDocument[];
|
|
142
|
+
/**
|
|
143
|
+
* Swap in a refetched consentPack after 409 consent_stale. For every doc
|
|
144
|
+
* whose (versionNo, contentHash) CHANGED, acceptance + evidence are
|
|
145
|
+
* RESET (re-acceptance required — NEVER auto-accept a changed doc, D543 §4
|
|
146
|
+
* / task §4). Unchanged docs keep their state. Returns the keys whose
|
|
147
|
+
* consent was reset so the UI can highlight what changed.
|
|
148
|
+
*/
|
|
149
|
+
applyNewPack(pack: ConsentPack | undefined): string[];
|
|
150
|
+
subscribe(listener: ConsentStateListener): () => void;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Scroll ratio at/above which a `scroll_then_accept` doc counts as
|
|
154
|
+
* scrolled-to-end. Slightly below 1 so sub-pixel rounding on the consumer's
|
|
155
|
+
* scroll math (scrollTop+clientHeight vs scrollHeight) still opens the gate.
|
|
156
|
+
*/
|
|
157
|
+
declare const SCROLL_END_THRESHOLD = 0.99;
|
|
158
|
+
/** checkbox docs have no gate; gated levels start unsatisfied. */
|
|
159
|
+
declare function initialGateSatisfied(level: AcceptanceLevel): boolean;
|
|
160
|
+
/**
|
|
161
|
+
* Create a headless consent controller for a spec's consentPack.
|
|
162
|
+
*
|
|
163
|
+
* @param pack the spec's `consentPack` (or undefined ⇒ an empty controller;
|
|
164
|
+
* the stage is skipped and `isComplete()` / `isEmpty()` are true).
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* const consent = createConsentController(spec.consentPack);
|
|
169
|
+
* consent.subscribe(render);
|
|
170
|
+
* consent.markScrolledToEnd("tos"); // scroll_then_accept gate
|
|
171
|
+
* consent.setAccepted("tos", true); // now allowed
|
|
172
|
+
* const docs = consent.buildConsentPayload(); // throws if any required unmet
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
declare function createConsentController(pack: ConsentPack | undefined): ConsentController;
|
|
176
|
+
|
|
177
|
+
export { type ConsentController as C, SCROLL_END_THRESHOLD as S, type ConsentDocState as a, type ConsentEvidence as b, type ConsentState as c, type ConsentStateListener as d, type ConsentSubmitDocument as e, createConsentController as f, initialGateSatisfied as i };
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { z } from 'zod/v3';
|
|
2
|
+
import { c as SpecStep, S as SpecField, T as TerminalStage, a as OnboardingSpec } from '../model-ESLSEbi0.js';
|
|
3
|
+
import { c as ConsentState, C as ConsentController, e as ConsentSubmitDocument } from '../controller-BQb62D0N.js';
|
|
4
|
+
export { a as ConsentDocState, b as ConsentEvidence, d as ConsentStateListener, S as SCROLL_END_THRESHOLD, f as createConsentController, i as initialGateSatisfied } from '../controller-BQb62D0N.js';
|
|
5
|
+
import { FieldError } from '../form/index.js';
|
|
6
|
+
import '../model-mnjAEtKe.js';
|
|
7
|
+
import '../schema-BrXb9gnh.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Spec → Zod validator derivation (D540 §3).
|
|
11
|
+
*
|
|
12
|
+
* Turns arbitrary SpecField descriptors from the served spec into Zod
|
|
13
|
+
* schemas for CLIENT-side UX validation. The server re-validates
|
|
14
|
+
* authoritatively (D540 §2) — client validation is a convenience layer,
|
|
15
|
+
* so the derivation is deliberately conservative:
|
|
16
|
+
*
|
|
17
|
+
* - Known `type`s map to typed schemas (see FIELD TYPE MAP below).
|
|
18
|
+
* - An UNKNOWN type falls back to z.string() (never blocks a field the
|
|
19
|
+
* server knows how to validate).
|
|
20
|
+
* - Known `validation` keys are applied (minLength, maxLength, min, max,
|
|
21
|
+
* pattern, format:"email"). Unknown validation keys are IGNORED
|
|
22
|
+
* client-side — the server enforces them.
|
|
23
|
+
* - `required:false` fields accept undefined AND "" (an empty input is
|
|
24
|
+
* "not provided").
|
|
25
|
+
* - select/radio values must be one of `options`; multiselect values
|
|
26
|
+
* must be a subset.
|
|
27
|
+
*
|
|
28
|
+
* FIELD TYPE MAP:
|
|
29
|
+
* text | textarea | tel | (unknown) → string
|
|
30
|
+
* email → string().email()
|
|
31
|
+
* number → coerce.number (DOM inputs are strings)
|
|
32
|
+
* checkbox | boolean → boolean
|
|
33
|
+
* select | radio → string ∈ options
|
|
34
|
+
* multiselect → string[] ⊆ options
|
|
35
|
+
* date → string, parseable as a date
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** Extract the comparable value of an option entry ({value}, {key}, or scalar). */
|
|
39
|
+
declare function optionValue(option: unknown): string;
|
|
40
|
+
/**
|
|
41
|
+
* Derive the full Zod schema for one SpecField.
|
|
42
|
+
* Optional fields accept undefined and "" ("not provided" in a form UI).
|
|
43
|
+
* Required fields additionally reject "" for string-typed inputs.
|
|
44
|
+
*/
|
|
45
|
+
declare function deriveFieldSchema(field: SpecField): z.ZodTypeAny;
|
|
46
|
+
/**
|
|
47
|
+
* Derive a STRICT Zod object schema for one step's fields — unknown answer
|
|
48
|
+
* keys within the step are rejected (mirrors the server's authoritative
|
|
49
|
+
* re-validation posture; a stale client must not silently carry fields the
|
|
50
|
+
* spec no longer defines).
|
|
51
|
+
*/
|
|
52
|
+
declare function deriveStepSchema(step: SpecStep): z.ZodObject<Record<string, z.ZodTypeAny>, "strict", z.ZodTypeAny, {
|
|
53
|
+
[x: string]: any;
|
|
54
|
+
}, {
|
|
55
|
+
[x: string]: any;
|
|
56
|
+
}>;
|
|
57
|
+
/**
|
|
58
|
+
* Derive the STRICT schema for the complete answers object across all
|
|
59
|
+
* steps (used at submit time and by the 409-stale re-validation).
|
|
60
|
+
*/
|
|
61
|
+
declare function deriveAnswersSchema(steps: SpecStep[]): z.ZodObject<Record<string, z.ZodTypeAny>, "strict", z.ZodTypeAny, {
|
|
62
|
+
[x: string]: any;
|
|
63
|
+
}, {
|
|
64
|
+
[x: string]: any;
|
|
65
|
+
}>;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Headless dynamic signup stepper (D540 §1/§3).
|
|
69
|
+
*
|
|
70
|
+
* The steps are GENERATED from the served spec: fields bucket by `stage`
|
|
71
|
+
* server-side → the spec's forms.signup.steps array. More configured
|
|
72
|
+
* fields ⇒ more steps. The final `activation` stage carries the
|
|
73
|
+
* service-selection inputs feeding the existing terminal-stage guards.
|
|
74
|
+
*
|
|
75
|
+
* Framework-agnostic: same subscribe/mutate contract as the Phase-1
|
|
76
|
+
* contact-form state. The default component layer renders it; framework
|
|
77
|
+
* integrations can wrap it.
|
|
78
|
+
*
|
|
79
|
+
* 409 spec_stale flow (D540 reject-stale): the CALLER (widget/submit
|
|
80
|
+
* orchestration) refetches the spec on OnboardingSpecStaleError and calls
|
|
81
|
+
* `applyNewSpec(newSpec)`. The stepper re-validates every answer against
|
|
82
|
+
* the NEW derived schema, DROPS answers whose fields no longer exist,
|
|
83
|
+
* marks `specStale: true`, and repositions to the first step with errors —
|
|
84
|
+
* the user re-confirms; the SDK NEVER silently auto-resubmits.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
type StepperStatus = "editing" | "submitting" | "success" | "error";
|
|
88
|
+
interface SignupConsent {
|
|
89
|
+
tosAccepted: boolean;
|
|
90
|
+
privacyAccepted: boolean;
|
|
91
|
+
}
|
|
92
|
+
interface SignupCredentials {
|
|
93
|
+
email: string;
|
|
94
|
+
displayName: string;
|
|
95
|
+
organisationName: string;
|
|
96
|
+
subdomain: string;
|
|
97
|
+
}
|
|
98
|
+
interface SignupStepperState {
|
|
99
|
+
/** Zero-based index of the current step. */
|
|
100
|
+
stepIndex: number;
|
|
101
|
+
stepCount: number;
|
|
102
|
+
currentStep: SpecStep | null;
|
|
103
|
+
/** Answers keyed by field key (only keys defined by the active spec). */
|
|
104
|
+
answers: Record<string, unknown>;
|
|
105
|
+
/** Derived from serviceSelection-flagged answers (see buildSubmitPayload). */
|
|
106
|
+
selectedServices: string[];
|
|
107
|
+
/** Validation errors for the CURRENT step (or full-form at submit). */
|
|
108
|
+
errors: FieldError[];
|
|
109
|
+
/** Credential/consent errors (submit-time). */
|
|
110
|
+
accountErrors: FieldError[];
|
|
111
|
+
consent: SignupConsent;
|
|
112
|
+
credentials: SignupCredentials;
|
|
113
|
+
status: StepperStatus;
|
|
114
|
+
serverError: string | null;
|
|
115
|
+
/** True after a 409-stale spec swap until the user re-validates. */
|
|
116
|
+
specStale: boolean;
|
|
117
|
+
/** The specVersion currently driving the form (echoed on submit). */
|
|
118
|
+
specVersion: string;
|
|
119
|
+
/** Set on success from the submit response. */
|
|
120
|
+
terminalStage: TerminalStage | null;
|
|
121
|
+
/**
|
|
122
|
+
* D543 ConsentPack state (per-document acceptance + gates). Empty
|
|
123
|
+
* (documents:[]) when the spec serves no consentPack — the consent stage is
|
|
124
|
+
* then skipped and does not gate the submit.
|
|
125
|
+
*/
|
|
126
|
+
consentState: ConsentState;
|
|
127
|
+
/**
|
|
128
|
+
* True after a 409 consent_stale swap until the user re-accepts the changed
|
|
129
|
+
* docs. Distinct from specStale — a doc-version bump does not necessarily
|
|
130
|
+
* change the form fields, and the banner copy differs.
|
|
131
|
+
*/
|
|
132
|
+
consentStale: boolean;
|
|
133
|
+
/** Keys of the consent docs whose acceptance was reset by the last swap. */
|
|
134
|
+
staleConsentKeys: string[];
|
|
135
|
+
/** Consent-stage errors (submit-time; e.g. a required doc not accepted). */
|
|
136
|
+
consentErrors: FieldError[];
|
|
137
|
+
}
|
|
138
|
+
type SignupStepperListener = (state: SignupStepperState) => void;
|
|
139
|
+
interface SignupStepperControl {
|
|
140
|
+
getState(): SignupStepperState;
|
|
141
|
+
setAnswer(key: string, value: unknown): void;
|
|
142
|
+
setConsent(consent: Partial<SignupConsent>): void;
|
|
143
|
+
setCredentials(credentials: Partial<SignupCredentials>): void;
|
|
144
|
+
/**
|
|
145
|
+
* The headless ConsentPack controller (D543 §5). Exposed so the default
|
|
146
|
+
* component layer AND fully custom tenant UIs drive the SAME state: mark
|
|
147
|
+
* scroll/open evidence, set acceptance, read per-doc gate status. The
|
|
148
|
+
* stepper subscribes to it internally so consent mutations re-emit stepper
|
|
149
|
+
* state (consentState/consentStale) to the widget.
|
|
150
|
+
*/
|
|
151
|
+
readonly consent: ConsentController;
|
|
152
|
+
/** Validate ONLY the current step. Returns true when clean. */
|
|
153
|
+
validateStep(): boolean;
|
|
154
|
+
/** Validate current step; advance when clean. Returns true if advanced. */
|
|
155
|
+
next(): boolean;
|
|
156
|
+
back(): void;
|
|
157
|
+
isFirstStep(): boolean;
|
|
158
|
+
isLastStep(): boolean;
|
|
159
|
+
/**
|
|
160
|
+
* Full-form validation: every step's answers + credentials + consent.
|
|
161
|
+
* Populates errors/accountErrors. Returns true when submittable.
|
|
162
|
+
*/
|
|
163
|
+
validateAll(): boolean;
|
|
164
|
+
/**
|
|
165
|
+
* The submit payload body (minus botToken, which the transport attaches).
|
|
166
|
+
* Call ONLY after validateAll() returns true.
|
|
167
|
+
*
|
|
168
|
+
* D543 §4: `consent.documents` is the per-doc consent array built from the
|
|
169
|
+
* ACTUAL consent state (omitted when the spec serves no consentPack). The
|
|
170
|
+
* booleans stay as the derived convenience. THROWS if any required consent
|
|
171
|
+
* doc is not accepted at its gate (never fabricates acceptance).
|
|
172
|
+
*/
|
|
173
|
+
buildSubmitPayload(): {
|
|
174
|
+
specVersion: string;
|
|
175
|
+
answers: Record<string, unknown>;
|
|
176
|
+
selectedServices?: string[];
|
|
177
|
+
consent: {
|
|
178
|
+
tosAccepted: true;
|
|
179
|
+
privacyAccepted: true;
|
|
180
|
+
documents?: ConsentSubmitDocument[];
|
|
181
|
+
};
|
|
182
|
+
credentials: {
|
|
183
|
+
email: string;
|
|
184
|
+
displayName?: string;
|
|
185
|
+
};
|
|
186
|
+
organisationName?: string;
|
|
187
|
+
subdomain: string;
|
|
188
|
+
};
|
|
189
|
+
/**
|
|
190
|
+
* Swap in a refetched spec after 409 spec_stale. Drops answers for
|
|
191
|
+
* removed fields, re-validates the rest, marks specStale, repositions
|
|
192
|
+
* to the first step with errors. ALSO swaps the consentPack (a spec bump
|
|
193
|
+
* may carry new doc versions) — changed docs require re-acceptance. NEVER
|
|
194
|
+
* auto-resubmits.
|
|
195
|
+
*/
|
|
196
|
+
applyNewSpec(spec: OnboardingSpec): void;
|
|
197
|
+
/** Clear the specStale flag once the user has re-confirmed. */
|
|
198
|
+
acknowledgeSpecStale(): void;
|
|
199
|
+
/**
|
|
200
|
+
* Swap in the refetched spec's consentPack after 409 consent_stale (D543
|
|
201
|
+
* §4). Resets acceptance for docs whose version/hash changed (re-acceptance
|
|
202
|
+
* required — NEVER auto-accept a changed doc), marks consentStale, records
|
|
203
|
+
* which keys were reset. NEVER auto-resubmits.
|
|
204
|
+
*/
|
|
205
|
+
applyNewConsentPack(spec: OnboardingSpec): void;
|
|
206
|
+
/** Clear the consentStale flag once the user has re-accepted. */
|
|
207
|
+
acknowledgeConsentStale(): void;
|
|
208
|
+
setSubmitting(): void;
|
|
209
|
+
setSuccess(terminalStage: TerminalStage): void;
|
|
210
|
+
setError(message: string): void;
|
|
211
|
+
/**
|
|
212
|
+
* Reset to the pristine initial state against the CURRENT spec: step 0,
|
|
213
|
+
* status "editing", answers/credentials/consent/errors cleared. This is
|
|
214
|
+
* the only transition back OUT of "success"/"error"; the widget's reset()
|
|
215
|
+
* relies on it (otherwise a post-success keystroke would re-emit
|
|
216
|
+
* status "success" and re-hide the form).
|
|
217
|
+
*/
|
|
218
|
+
reset(): void;
|
|
219
|
+
subscribe(listener: SignupStepperListener): () => void;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Create a headless stepper for the spec's signup form.
|
|
223
|
+
*
|
|
224
|
+
* @example
|
|
225
|
+
* ```ts
|
|
226
|
+
* const spec = await fetchOnboardingSpec({ baseUrl, onboarder });
|
|
227
|
+
* const stepper = createSignupStepper(spec);
|
|
228
|
+
* stepper.subscribe(render);
|
|
229
|
+
* stepper.setAnswer("company_name", "ACME");
|
|
230
|
+
* if (stepper.next()) { ... }
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
declare function createSignupStepper(spec: OnboardingSpec): SignupStepperControl;
|
|
234
|
+
|
|
235
|
+
export { ConsentController, ConsentState, ConsentSubmitDocument, type SignupConsent, type SignupCredentials, type SignupStepperControl, type SignupStepperListener, type SignupStepperState, type StepperStatus, createSignupStepper, deriveAnswersSchema, deriveFieldSchema, deriveStepSchema, optionValue };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import '../chunk-JFDQZ3AC.js';
|
|
2
|
+
export { createSignupStepper, deriveAnswersSchema, deriveFieldSchema, deriveStepSchema, optionValue } from '../chunk-KQTS47FC.js';
|
|
3
|
+
export { SCROLL_END_THRESHOLD, createConsentController, initialGateSatisfied } from '../chunk-AZE3NPSY.js';
|
|
4
|
+
import '../chunk-U56GQITG.js';
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy for the onboarding submit client.
|
|
3
|
+
*
|
|
4
|
+
* Maps HTTP status codes and server error bodies to typed errors:
|
|
5
|
+
* 403 bot_challenge_failed → OnboardingBotChallengeError
|
|
6
|
+
* 409 spec_stale (SUBMIT) → OnboardingSpecStaleError (submit endpoint only,
|
|
7
|
+
* body { error:true, code:"spec_stale",
|
|
8
|
+
* currentSpecVersion })
|
|
9
|
+
* 409 (other / LEADS) → OnboardingConflictError (generic conflict)
|
|
10
|
+
* 429 → OnboardingRateLimitError
|
|
11
|
+
* 5xx → OnboardingServerError (retryable)
|
|
12
|
+
* network / timeout → OnboardingNetworkError
|
|
13
|
+
*/
|
|
14
|
+
type OnboardingErrorKind = "bot_challenge_failed" | "spec_stale" | "spec_invalid" | "consent_stale" | "consent_incomplete" | "subdomain_taken" | "conflict" | "rate_limited" | "server_error" | "network_error" | "validation_error";
|
|
15
|
+
declare class OnboardingError extends Error {
|
|
16
|
+
readonly kind: OnboardingErrorKind;
|
|
17
|
+
readonly statusCode: number | null;
|
|
18
|
+
constructor(kind: OnboardingErrorKind, message: string, statusCode?: number | null);
|
|
19
|
+
}
|
|
20
|
+
declare class OnboardingBotChallengeError extends OnboardingError {
|
|
21
|
+
constructor(message?: string);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* A 409 that is NOT spec_stale. On the /leads path there is no spec_stale
|
|
25
|
+
* concept (the payload carries no specVersion); on /submit, a 409 that
|
|
26
|
+
* lacks `code:"spec_stale"` also lands here. Distinct from
|
|
27
|
+
* OnboardingSpecStaleError so headless consumers switching on `kind` get the
|
|
28
|
+
* correct diagnosis (a generic conflict, e.g. a D538 same-key concurrent
|
|
29
|
+
* race) rather than a "form has been updated, refresh" recovery message.
|
|
30
|
+
*/
|
|
31
|
+
declare class OnboardingConflictError extends OnboardingError {
|
|
32
|
+
constructor(statusCode?: number, message?: string);
|
|
33
|
+
}
|
|
34
|
+
declare class OnboardingSubdomainTakenError extends OnboardingError {
|
|
35
|
+
constructor();
|
|
36
|
+
}
|
|
37
|
+
declare class OnboardingRateLimitError extends OnboardingError {
|
|
38
|
+
readonly retryAfterSeconds: number | null;
|
|
39
|
+
constructor(retryAfterSeconds?: number | null);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* A /submit 409 whose body carries `code:"spec_stale"` (D540 reject-stale).
|
|
43
|
+
* The PINNED body shape (confirmed against the crm intake) is exactly:
|
|
44
|
+
* { error: true, code: "spec_stale", currentSpecVersion: string }
|
|
45
|
+
* The version is read ONLY from `currentSpecVersion` — no field hedging.
|
|
46
|
+
* Submit-endpoint only; /leads 409s are OnboardingConflictError.
|
|
47
|
+
*/
|
|
48
|
+
declare class OnboardingSpecStaleError extends OnboardingError {
|
|
49
|
+
readonly currentSpecVersion: string;
|
|
50
|
+
constructor(currentSpecVersion: string);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A /submit 409 whose body carries `code:"consent_stale"` (D543 §4 —
|
|
54
|
+
* SIBLING to spec_stale). Raised when a required legal document's live
|
|
55
|
+
* version/content_hash changed between spec-fetch and submit, so the echoed
|
|
56
|
+
* consent no longer matches the live version.
|
|
57
|
+
*
|
|
58
|
+
* PINNED body shape (D543 §4, mirroring spec_stale):
|
|
59
|
+
* { error: true, code: "consent_stale", staleDocuments?: [...] }
|
|
60
|
+
* where each staleDocuments entry identifies a changed doc so the caller can
|
|
61
|
+
* tell the user WHICH docs changed. The caller refetches the spec and
|
|
62
|
+
* REQUIRES re-acceptance of the changed docs — the SDK NEVER auto-accepts a
|
|
63
|
+
* changed doc (D543 §4). Submit-endpoint only.
|
|
64
|
+
*/
|
|
65
|
+
interface StaleConsentDocument {
|
|
66
|
+
/** The server registry id of the changed doc (D543 §3 legal_documents.id). */
|
|
67
|
+
legalDocumentId?: string;
|
|
68
|
+
/** doc_type of the changed doc, when the server includes it. */
|
|
69
|
+
docType?: string;
|
|
70
|
+
/** The new live version_no, when the server includes it. */
|
|
71
|
+
currentVersionNo?: number;
|
|
72
|
+
}
|
|
73
|
+
declare class OnboardingConsentStaleError extends OnboardingError {
|
|
74
|
+
/**
|
|
75
|
+
* The documents the server reports as changed (may be empty when the
|
|
76
|
+
* server does not enumerate them — the caller then refetches + diffs).
|
|
77
|
+
*/
|
|
78
|
+
readonly staleDocuments: StaleConsentDocument[];
|
|
79
|
+
constructor(staleDocuments?: StaleConsentDocument[]);
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* A /submit 400 whose body carries `code:"consent_incomplete"` (D543 §4).
|
|
83
|
+
* The server's authoritative re-validation found a required live document
|
|
84
|
+
* that was missing, not accepted, or whose evidence did not satisfy its
|
|
85
|
+
* acceptance_level gate. Distinct from a generic 422 so headless consumers
|
|
86
|
+
* switching on `kind` route the user back to the consent step specifically.
|
|
87
|
+
*/
|
|
88
|
+
declare class OnboardingConsentIncompleteError extends OnboardingError {
|
|
89
|
+
constructor(message?: string);
|
|
90
|
+
}
|
|
91
|
+
declare class OnboardingServerError extends OnboardingError {
|
|
92
|
+
constructor(statusCode: number, message?: string);
|
|
93
|
+
}
|
|
94
|
+
declare class OnboardingNetworkError extends OnboardingError {
|
|
95
|
+
constructor(cause?: unknown);
|
|
96
|
+
}
|
|
97
|
+
declare class OnboardingValidationError extends OnboardingError {
|
|
98
|
+
constructor(message: string);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The spec endpoint returned a body that does not conform to the LOCKED
|
|
102
|
+
* D540 §1 response shape. This is a server-side contract violation — the
|
|
103
|
+
* SDK refuses to render a form from a malformed spec (a half-parsed spec
|
|
104
|
+
* could silently drop required fields).
|
|
105
|
+
*/
|
|
106
|
+
declare class OnboardingSpecInvalidError extends OnboardingError {
|
|
107
|
+
constructor(detail: string);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { OnboardingBotChallengeError as O, type StaleConsentDocument as S, OnboardingConflictError as a, OnboardingConsentIncompleteError as b, OnboardingConsentStaleError as c, OnboardingError as d, type OnboardingErrorKind as e, OnboardingNetworkError as f, OnboardingRateLimitError as g, OnboardingServerError as h, OnboardingSpecInvalidError as i, OnboardingSpecStaleError as j, OnboardingSubdomainTakenError as k, OnboardingValidationError as l };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { C as ContactFormValues } from '../schema-BrXb9gnh.js';
|
|
2
|
+
export { L as LeadsWirePayload, c as contactFormSchema, l as leadsWireSchema, v as validateContactForm, a as validateLeadsPayload } from '../schema-BrXb9gnh.js';
|
|
3
|
+
import 'zod/v3';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Headless form state manager for the contact form.
|
|
7
|
+
*
|
|
8
|
+
* Designed to be framework-agnostic — callers supply a change callback
|
|
9
|
+
* and call the returned mutators. The widget layer uses this internally;
|
|
10
|
+
* framework integrations (React, Vue, etc.) can wrap it in their own hooks.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface FieldError {
|
|
14
|
+
path: string;
|
|
15
|
+
message: string;
|
|
16
|
+
}
|
|
17
|
+
type FormStatus = "idle" | "submitting" | "success" | "error";
|
|
18
|
+
interface ContactFormState {
|
|
19
|
+
values: ContactFormValues;
|
|
20
|
+
/** Per-field validation errors from the last validate() call. */
|
|
21
|
+
errors: FieldError[];
|
|
22
|
+
/** Cross-field / server error message. */
|
|
23
|
+
serverError: string | null;
|
|
24
|
+
status: FormStatus;
|
|
25
|
+
/** True if values have been touched at least once. */
|
|
26
|
+
dirty: boolean;
|
|
27
|
+
}
|
|
28
|
+
type ContactFormStateListener = (state: ContactFormState) => void;
|
|
29
|
+
interface ContactFormControl {
|
|
30
|
+
/** Current snapshot of form state. */
|
|
31
|
+
getState(): ContactFormState;
|
|
32
|
+
/** Update a single field value and re-validate if dirty. */
|
|
33
|
+
setValue<K extends keyof ContactFormValues>(field: K, value: ContactFormValues[K]): void;
|
|
34
|
+
/**
|
|
35
|
+
* Run full client-side validation. Returns true if valid.
|
|
36
|
+
* Also marks the form dirty so future setValue calls auto-validate.
|
|
37
|
+
*/
|
|
38
|
+
validate(): boolean;
|
|
39
|
+
/** Mark the form as submitting. Called by the submit client. */
|
|
40
|
+
setSubmitting(): void;
|
|
41
|
+
/** Mark the form as successfully submitted. */
|
|
42
|
+
setSuccess(): void;
|
|
43
|
+
/** Set a server-side error (e.g. 429 rate limited). */
|
|
44
|
+
setError(message: string): void;
|
|
45
|
+
/** Reset to initial idle state. */
|
|
46
|
+
reset(): void;
|
|
47
|
+
/** Subscribe to state changes. Returns unsubscribe function. */
|
|
48
|
+
subscribe(listener: ContactFormStateListener): () => void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Create a new headless contact form state controller.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```ts
|
|
55
|
+
* const form = createContactFormState();
|
|
56
|
+
* form.subscribe((state) => renderUI(state));
|
|
57
|
+
* form.setValue("name", "Alice");
|
|
58
|
+
* const valid = form.validate();
|
|
59
|
+
* if (valid) await submitContact(form.getState().values, botToken);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function createContactFormState(): ContactFormControl;
|
|
63
|
+
|
|
64
|
+
export { type ContactFormControl, type ContactFormState, type ContactFormStateListener, ContactFormValues, type FieldError, type FormStatus, createContactFormState };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import '../chunk-JFIONONR.js';
|
|
2
|
+
export { createContactFormState } from '../chunk-U6AXKGYQ.js';
|
|
3
|
+
export { contactFormSchema, leadsWireSchema, validateContactForm, validateLeadsPayload } from '../chunk-SIMLMQPT.js';
|
|
4
|
+
//# sourceMappingURL=index.js.map
|
|
5
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|