@secmia/openui-flow 4.0.1 → 4.2.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/AGENTS.md +24 -0
- package/README.md +188 -1
- package/dist/index.d.mts +141 -8
- package/dist/index.d.ts +141 -8
- package/dist/index.js +590 -147
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +588 -146
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -1
package/dist/index.mjs
CHANGED
|
@@ -26,6 +26,8 @@ var defaultRequirements = [
|
|
|
26
26
|
DefaultAppRequirements.EMAIL_VERIFIED,
|
|
27
27
|
DefaultAppRequirements.HAS_PASSWORD,
|
|
28
28
|
DefaultAppRequirements.HAS_FIRST_NAME,
|
|
29
|
+
DefaultAppRequirements.HAS_LAST_NAME,
|
|
30
|
+
DefaultAppRequirements.HAS_JOB_TITLE,
|
|
29
31
|
DefaultAppRequirements.ACCEPTED_TOS
|
|
30
32
|
];
|
|
31
33
|
var initialContext = {
|
|
@@ -71,20 +73,66 @@ var defaultRequirementResolvers = {
|
|
|
71
73
|
};
|
|
72
74
|
function createRequirementGraph(requirements, resolvers, options) {
|
|
73
75
|
const graph = [];
|
|
76
|
+
const requirementSet = new Set(requirements);
|
|
77
|
+
const resolverBackedRequirements = new Set(
|
|
78
|
+
requirements.filter((requirement) => Boolean(resolvers[requirement]))
|
|
79
|
+
);
|
|
74
80
|
for (const requirement of requirements) {
|
|
75
81
|
const resolver = resolvers[requirement];
|
|
76
82
|
if (!resolver) {
|
|
77
83
|
continue;
|
|
78
84
|
}
|
|
85
|
+
const dependsOn = options?.dependencies?.[requirement] ?? [];
|
|
86
|
+
for (const dependency of dependsOn) {
|
|
87
|
+
if (!requirementSet.has(dependency)) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`Invalid dependency "${String(dependency)}" referenced by requirement "${String(requirement)}".`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (!resolverBackedRequirements.has(dependency)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Dependency "${String(dependency)}" referenced by requirement "${String(requirement)}" has no resolver.`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
79
98
|
graph.push({
|
|
80
99
|
requirement,
|
|
81
100
|
step: resolver.step,
|
|
82
101
|
isMet: resolver.isMet,
|
|
83
102
|
when: options?.conditions?.[requirement],
|
|
84
103
|
priority: options?.priorities?.[requirement] ?? 0,
|
|
85
|
-
dependsOn
|
|
104
|
+
dependsOn
|
|
86
105
|
});
|
|
87
106
|
}
|
|
107
|
+
const byRequirement = new Map(
|
|
108
|
+
graph.map((node) => [node.requirement, node])
|
|
109
|
+
);
|
|
110
|
+
const visited = /* @__PURE__ */ new Set();
|
|
111
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
112
|
+
const dfs = (requirement, stack) => {
|
|
113
|
+
if (visited.has(requirement)) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (visiting.has(requirement)) {
|
|
117
|
+
const cycleStart = stack.indexOf(requirement);
|
|
118
|
+
const cyclePath = [...stack.slice(cycleStart), requirement].map((value) => String(value)).join(" -> ");
|
|
119
|
+
throw new Error(`Circular dependency detected in requirement graph: ${cyclePath}`);
|
|
120
|
+
}
|
|
121
|
+
visiting.add(requirement);
|
|
122
|
+
const node = byRequirement.get(requirement);
|
|
123
|
+
if (node?.dependsOn?.length) {
|
|
124
|
+
for (const dep of node.dependsOn) {
|
|
125
|
+
if (byRequirement.has(dep)) {
|
|
126
|
+
dfs(dep, [...stack, requirement]);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
visiting.delete(requirement);
|
|
131
|
+
visited.add(requirement);
|
|
132
|
+
};
|
|
133
|
+
for (const node of graph) {
|
|
134
|
+
dfs(node.requirement, []);
|
|
135
|
+
}
|
|
88
136
|
return graph;
|
|
89
137
|
}
|
|
90
138
|
function createDefaultRequirementGraph(options) {
|
|
@@ -100,13 +148,63 @@ function createDefaultRequirementGraph(options) {
|
|
|
100
148
|
});
|
|
101
149
|
}
|
|
102
150
|
function sortGraph(graph) {
|
|
103
|
-
|
|
104
|
-
|
|
151
|
+
const entries = graph.map((node, index) => ({ node, index }));
|
|
152
|
+
const byRequirement = new Map(entries.map((entry) => [entry.node.requirement, entry]));
|
|
153
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
154
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
indegree.set(entry.node.requirement, 0);
|
|
157
|
+
adjacency.set(entry.node.requirement, []);
|
|
158
|
+
}
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
const current = entry.node.requirement;
|
|
161
|
+
for (const dependency of entry.node.dependsOn ?? []) {
|
|
162
|
+
const depEntry = byRequirement.get(dependency);
|
|
163
|
+
if (!depEntry) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const outgoing = adjacency.get(depEntry.node.requirement);
|
|
167
|
+
if (outgoing) {
|
|
168
|
+
outgoing.push(current);
|
|
169
|
+
}
|
|
170
|
+
indegree.set(current, (indegree.get(current) ?? 0) + 1);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const compare = (left, right) => {
|
|
174
|
+
const byPriority = (right.node.priority ?? 0) - (left.node.priority ?? 0);
|
|
105
175
|
if (byPriority !== 0) {
|
|
106
176
|
return byPriority;
|
|
107
177
|
}
|
|
108
|
-
|
|
109
|
-
|
|
178
|
+
const byRequirementName = String(left.node.requirement).localeCompare(String(right.node.requirement));
|
|
179
|
+
if (byRequirementName !== 0) {
|
|
180
|
+
return byRequirementName;
|
|
181
|
+
}
|
|
182
|
+
return left.index - right.index;
|
|
183
|
+
};
|
|
184
|
+
const ready = entries.filter((entry) => (indegree.get(entry.node.requirement) ?? 0) === 0).sort(compare);
|
|
185
|
+
const ordered = [];
|
|
186
|
+
while (ready.length > 0) {
|
|
187
|
+
const current = ready.shift();
|
|
188
|
+
if (!current) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
ordered.push(current.node);
|
|
192
|
+
for (const dependent of adjacency.get(current.node.requirement) ?? []) {
|
|
193
|
+
const next = (indegree.get(dependent) ?? 0) - 1;
|
|
194
|
+
indegree.set(dependent, next);
|
|
195
|
+
if (next === 0) {
|
|
196
|
+
const dependentEntry = byRequirement.get(dependent);
|
|
197
|
+
if (dependentEntry) {
|
|
198
|
+
ready.push(dependentEntry);
|
|
199
|
+
ready.sort(compare);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (ordered.length !== graph.length) {
|
|
205
|
+
throw new Error("Unable to topologically sort requirement graph. Check for dependency cycles.");
|
|
206
|
+
}
|
|
207
|
+
return ordered;
|
|
110
208
|
}
|
|
111
209
|
async function evaluateNextStep(context, graph, completeStep) {
|
|
112
210
|
for (const node of sortGraph(graph)) {
|
|
@@ -147,6 +245,10 @@ async function getMissingRequirements(context, graph) {
|
|
|
147
245
|
|
|
148
246
|
// src/AdaptiveFlow.tsx
|
|
149
247
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
248
|
+
var defaultOAuthProviders = [
|
|
249
|
+
{ id: "google", label: "Continue with Google" },
|
|
250
|
+
{ id: "apple", label: "Continue with Apple" }
|
|
251
|
+
];
|
|
150
252
|
var defaultStepTitle = {
|
|
151
253
|
COLLECT_EMAIL: "Enter your email",
|
|
152
254
|
VERIFY_OTP: "Verify your email",
|
|
@@ -155,6 +257,14 @@ var defaultStepTitle = {
|
|
|
155
257
|
COLLECT_TOS: "Accept terms",
|
|
156
258
|
COMPLETE: "Done"
|
|
157
259
|
};
|
|
260
|
+
var builtInDefaultSteps = /* @__PURE__ */ new Set([
|
|
261
|
+
"COLLECT_EMAIL",
|
|
262
|
+
"VERIFY_OTP",
|
|
263
|
+
"COLLECT_PASSWORD",
|
|
264
|
+
"COLLECT_PROFILE",
|
|
265
|
+
"COLLECT_TOS",
|
|
266
|
+
"COMPLETE"
|
|
267
|
+
]);
|
|
158
268
|
var styleSlots = [
|
|
159
269
|
"shell",
|
|
160
270
|
"headerRow",
|
|
@@ -175,14 +285,24 @@ var styleSlots = [
|
|
|
175
285
|
"oauthButton"
|
|
176
286
|
];
|
|
177
287
|
function mergeContext(current, patch) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
288
|
+
const mergeValue = (baseValue, patchValue) => {
|
|
289
|
+
if (Array.isArray(baseValue) || Array.isArray(patchValue)) {
|
|
290
|
+
return patchValue;
|
|
291
|
+
}
|
|
292
|
+
const baseIsObject = Boolean(baseValue) && typeof baseValue === "object";
|
|
293
|
+
const patchIsObject = Boolean(patchValue) && typeof patchValue === "object";
|
|
294
|
+
if (!baseIsObject || !patchIsObject) {
|
|
295
|
+
return patchValue === void 0 ? baseValue : patchValue;
|
|
184
296
|
}
|
|
297
|
+
const baseObject = baseValue;
|
|
298
|
+
const patchObject = patchValue;
|
|
299
|
+
const result = { ...baseObject };
|
|
300
|
+
for (const key of Object.keys(patchObject)) {
|
|
301
|
+
result[key] = mergeValue(baseObject[key], patchObject[key]);
|
|
302
|
+
}
|
|
303
|
+
return result;
|
|
185
304
|
};
|
|
305
|
+
return mergeValue(current, patch);
|
|
186
306
|
}
|
|
187
307
|
function withDefaults(initialValue) {
|
|
188
308
|
if (!initialValue) {
|
|
@@ -194,7 +314,65 @@ function toError(error) {
|
|
|
194
314
|
if (error instanceof Error) {
|
|
195
315
|
return error;
|
|
196
316
|
}
|
|
197
|
-
|
|
317
|
+
if (typeof error === "string") {
|
|
318
|
+
return new Error(error);
|
|
319
|
+
}
|
|
320
|
+
if (error && typeof error === "object") {
|
|
321
|
+
const maybeError = error;
|
|
322
|
+
const message = typeof maybeError.message === "string" && maybeError.message.trim().length > 0 ? maybeError.message : "Unknown error while processing adaptive flow";
|
|
323
|
+
const normalized = new Error(message);
|
|
324
|
+
if (typeof maybeError.code === "string" && maybeError.code.trim().length > 0) {
|
|
325
|
+
normalized.name = maybeError.code;
|
|
326
|
+
}
|
|
327
|
+
normalized.cause = error;
|
|
328
|
+
return normalized;
|
|
329
|
+
}
|
|
330
|
+
return new Error(`Unknown error while processing adaptive flow: ${String(error)}`);
|
|
331
|
+
}
|
|
332
|
+
function sleep(ms) {
|
|
333
|
+
return new Promise((resolve) => {
|
|
334
|
+
setTimeout(resolve, ms);
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
function normalizeDelay(delay, fallback) {
|
|
338
|
+
if (typeof delay !== "number" || Number.isNaN(delay) || delay < 0) {
|
|
339
|
+
return fallback;
|
|
340
|
+
}
|
|
341
|
+
return delay;
|
|
342
|
+
}
|
|
343
|
+
function computeRetryDelay(policy, attempt) {
|
|
344
|
+
if (policy?.delay) {
|
|
345
|
+
return normalizeDelay(policy.delay(attempt), 0);
|
|
346
|
+
}
|
|
347
|
+
const initialDelayMs = normalizeDelay(policy?.initialDelayMs, 250);
|
|
348
|
+
const factor = typeof policy?.factor === "number" && policy.factor > 0 ? policy.factor : 2;
|
|
349
|
+
const maxDelayMs = normalizeDelay(policy?.maxDelayMs, Number.POSITIVE_INFINITY);
|
|
350
|
+
let delay = initialDelayMs * Math.pow(factor, Math.max(0, attempt - 1));
|
|
351
|
+
if (policy?.jitter) {
|
|
352
|
+
delay = delay * (0.5 + Math.random() * 0.5);
|
|
353
|
+
}
|
|
354
|
+
return Math.min(delay, maxDelayMs);
|
|
355
|
+
}
|
|
356
|
+
async function withRetry(operation, retryPolicy) {
|
|
357
|
+
if (!retryPolicy) {
|
|
358
|
+
return operation();
|
|
359
|
+
}
|
|
360
|
+
const maxAttempts = Math.max(1, Math.trunc(retryPolicy.maxAttempts ?? 3));
|
|
361
|
+
let lastError;
|
|
362
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
363
|
+
try {
|
|
364
|
+
return await operation();
|
|
365
|
+
} catch (error) {
|
|
366
|
+
lastError = error;
|
|
367
|
+
const normalized = toError(error);
|
|
368
|
+
const shouldRetry = retryPolicy.shouldRetry?.(normalized, attempt) ?? attempt < maxAttempts;
|
|
369
|
+
if (!shouldRetry || attempt === maxAttempts) {
|
|
370
|
+
throw normalized;
|
|
371
|
+
}
|
|
372
|
+
await sleep(computeRetryDelay(retryPolicy, attempt));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
throw toError(lastError);
|
|
198
376
|
}
|
|
199
377
|
function cx(...names) {
|
|
200
378
|
const value = names.filter(Boolean).join(" ").trim();
|
|
@@ -248,10 +426,78 @@ function clearPersistedState(persistence) {
|
|
|
248
426
|
}
|
|
249
427
|
storage.removeItem(persistence.key);
|
|
250
428
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
429
|
+
var FlowValidationError = class extends Error {
|
|
430
|
+
constructor(message, fieldErrors) {
|
|
431
|
+
super(message);
|
|
432
|
+
this.name = "FlowValidationError";
|
|
433
|
+
this.fieldErrors = fieldErrors;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
function toValidationError(issue, fallbackField) {
|
|
437
|
+
const field = issue.field ?? fallbackField;
|
|
438
|
+
if (field) {
|
|
439
|
+
return new FlowValidationError(issue.message, { [field]: issue.message });
|
|
440
|
+
}
|
|
441
|
+
return new FlowValidationError(issue.message, {});
|
|
442
|
+
}
|
|
443
|
+
function normalizeSchemaIssues(error, fallbackField) {
|
|
444
|
+
const fieldErrors = {};
|
|
445
|
+
const issues = error?.issues ?? [];
|
|
446
|
+
for (const issue of issues) {
|
|
447
|
+
const path = issue.path?.length ? issue.path.map(String).join(".") : fallbackField;
|
|
448
|
+
const message = issue.message?.trim() || error?.message || "Validation failed.";
|
|
449
|
+
if (path && !fieldErrors[path]) {
|
|
450
|
+
fieldErrors[path] = message;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
if (Object.keys(fieldErrors).length === 0 && fallbackField) {
|
|
454
|
+
fieldErrors[fallbackField] = error?.message || "Validation failed.";
|
|
455
|
+
}
|
|
456
|
+
return fieldErrors;
|
|
457
|
+
}
|
|
458
|
+
async function assertValid(result, fallbackField) {
|
|
459
|
+
const outcome = await result;
|
|
460
|
+
if (!outcome) {
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
if (typeof outcome === "string") {
|
|
464
|
+
const message = outcome.trim();
|
|
465
|
+
if (message.length > 0) {
|
|
466
|
+
throw new FlowValidationError(message, fallbackField ? { [fallbackField]: message } : {});
|
|
467
|
+
}
|
|
468
|
+
return;
|
|
469
|
+
}
|
|
470
|
+
if (outcome.message.trim().length > 0) {
|
|
471
|
+
throw toValidationError(outcome, fallbackField);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
async function assertSchema(schema, value, fallbackField) {
|
|
475
|
+
if (!schema) {
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
if (schema.safeParse) {
|
|
479
|
+
const result = schema.safeParse(value);
|
|
480
|
+
if (!result.success) {
|
|
481
|
+
const fieldErrors = normalizeSchemaIssues(result.error, fallbackField);
|
|
482
|
+
throw new FlowValidationError(result.error?.message || "Validation failed.", fieldErrors);
|
|
483
|
+
}
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (!schema.parse) {
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
schema.parse(value);
|
|
491
|
+
} catch (error) {
|
|
492
|
+
const normalized = error;
|
|
493
|
+
const fieldErrors = normalizeSchemaIssues(
|
|
494
|
+
{
|
|
495
|
+
message: normalized?.message,
|
|
496
|
+
issues: normalized?.issues
|
|
497
|
+
},
|
|
498
|
+
fallbackField
|
|
499
|
+
);
|
|
500
|
+
throw new FlowValidationError(normalized?.message || "Validation failed.", fieldErrors);
|
|
255
501
|
}
|
|
256
502
|
}
|
|
257
503
|
function resolveStyles(unstyled, styleOverrides) {
|
|
@@ -268,26 +514,22 @@ function resolveStyles(unstyled, styleOverrides) {
|
|
|
268
514
|
}
|
|
269
515
|
return resolved;
|
|
270
516
|
}
|
|
271
|
-
function
|
|
517
|
+
function useAdaptiveFlow({
|
|
272
518
|
adapter,
|
|
273
519
|
requirements,
|
|
274
520
|
requirementGraph,
|
|
275
521
|
requirementGraphConfig,
|
|
276
522
|
requirementResolvers,
|
|
277
523
|
completeStep,
|
|
278
|
-
stepTitles,
|
|
279
|
-
renderStep,
|
|
280
|
-
stepRegistry,
|
|
281
524
|
initialValue,
|
|
282
525
|
onComplete,
|
|
283
526
|
onError,
|
|
284
527
|
onStepTransition,
|
|
285
|
-
className,
|
|
286
|
-
classNames,
|
|
287
|
-
styles,
|
|
288
|
-
unstyled = false,
|
|
289
528
|
persistence,
|
|
290
|
-
validators
|
|
529
|
+
validators,
|
|
530
|
+
schemas,
|
|
531
|
+
oauthProviders,
|
|
532
|
+
retryPolicy
|
|
291
533
|
}) {
|
|
292
534
|
const normalizedRequirements = React.useMemo(
|
|
293
535
|
() => requirements ?? defaultRequirements,
|
|
@@ -321,20 +563,68 @@ function AdaptiveFlow({
|
|
|
321
563
|
requirementGraphConfig?.dependencies
|
|
322
564
|
]
|
|
323
565
|
);
|
|
324
|
-
const
|
|
566
|
+
const runtimeReducer = (state, action) => {
|
|
567
|
+
switch (action.type) {
|
|
568
|
+
case "evaluated":
|
|
569
|
+
return {
|
|
570
|
+
...state,
|
|
571
|
+
step: action.step,
|
|
572
|
+
missingRequirements: action.missingRequirements,
|
|
573
|
+
transitions: [...state.transitions, action.transition].slice(-100)
|
|
574
|
+
};
|
|
575
|
+
case "set_busy":
|
|
576
|
+
return { ...state, busy: action.busy };
|
|
577
|
+
case "set_message":
|
|
578
|
+
return { ...state, message: action.message };
|
|
579
|
+
case "set_error":
|
|
580
|
+
return { ...state, errorMessage: action.errorMessage };
|
|
581
|
+
case "set_field_errors":
|
|
582
|
+
return { ...state, fieldErrors: action.fieldErrors };
|
|
583
|
+
case "start_job":
|
|
584
|
+
return { ...state, busy: true, errorMessage: null, fieldErrors: {} };
|
|
585
|
+
case "set_oauth_pending":
|
|
586
|
+
return { ...state, oauthPendingProvider: action.provider };
|
|
587
|
+
case "set_hydrated":
|
|
588
|
+
return { ...state, persistenceHydrated: action.hydrated };
|
|
589
|
+
default:
|
|
590
|
+
return state;
|
|
591
|
+
}
|
|
592
|
+
};
|
|
325
593
|
const [context, setContext] = React.useState(() => withDefaults(initialValue));
|
|
326
|
-
const [
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
594
|
+
const [runtime, dispatch] = React.useReducer(runtimeReducer, {
|
|
595
|
+
step: normalizedCompleteStep,
|
|
596
|
+
missingRequirements: [],
|
|
597
|
+
transitions: [],
|
|
598
|
+
busy: false,
|
|
599
|
+
message: null,
|
|
600
|
+
errorMessage: null,
|
|
601
|
+
fieldErrors: {},
|
|
602
|
+
oauthPendingProvider: null,
|
|
603
|
+
persistenceHydrated: !persistence
|
|
604
|
+
});
|
|
605
|
+
const {
|
|
606
|
+
step,
|
|
607
|
+
missingRequirements,
|
|
608
|
+
transitions,
|
|
609
|
+
busy,
|
|
610
|
+
message,
|
|
611
|
+
errorMessage,
|
|
612
|
+
fieldErrors,
|
|
613
|
+
oauthPendingProvider,
|
|
614
|
+
persistenceHydrated
|
|
615
|
+
} = runtime;
|
|
334
616
|
const attemptByStepRef = React.useRef({});
|
|
335
617
|
const previousStepRef = React.useRef(null);
|
|
336
618
|
const evaluationRef = React.useRef(0);
|
|
337
619
|
const completed = React.useRef(false);
|
|
620
|
+
const reportPersistenceError = React.useCallback(
|
|
621
|
+
(error, phase) => {
|
|
622
|
+
const normalized = toError(error);
|
|
623
|
+
persistence?.onError?.(normalized, phase);
|
|
624
|
+
onError?.(normalized);
|
|
625
|
+
},
|
|
626
|
+
[onError, persistence]
|
|
627
|
+
);
|
|
338
628
|
React.useEffect(() => {
|
|
339
629
|
if (!persistence) {
|
|
340
630
|
return;
|
|
@@ -345,26 +635,24 @@ function AdaptiveFlow({
|
|
|
345
635
|
setContext(mergeContext(withDefaults(initialValue), persisted.context));
|
|
346
636
|
}
|
|
347
637
|
if (persisted?.oauthPendingProvider) {
|
|
348
|
-
|
|
638
|
+
dispatch({ type: "set_oauth_pending", provider: persisted.oauthPendingProvider });
|
|
349
639
|
}
|
|
350
|
-
} catch {
|
|
640
|
+
} catch (error) {
|
|
641
|
+
reportPersistenceError(error, "read");
|
|
351
642
|
} finally {
|
|
352
|
-
|
|
643
|
+
dispatch({ type: "set_hydrated", hydrated: true });
|
|
353
644
|
}
|
|
354
|
-
}, [initialValue, persistence]);
|
|
645
|
+
}, [initialValue, persistence, reportPersistenceError]);
|
|
355
646
|
React.useEffect(() => {
|
|
356
|
-
let isCancelled = false;
|
|
357
647
|
const currentEvaluation = ++evaluationRef.current;
|
|
358
648
|
void (async () => {
|
|
359
|
-
const [
|
|
649
|
+
const [missing, next] = await Promise.all([
|
|
360
650
|
getMissingRequirements(context, graph),
|
|
361
651
|
evaluateNextStep(context, graph, normalizedCompleteStep)
|
|
362
652
|
]);
|
|
363
|
-
if (
|
|
653
|
+
if (currentEvaluation !== evaluationRef.current) {
|
|
364
654
|
return;
|
|
365
655
|
}
|
|
366
|
-
setMissingRequirements(missing2);
|
|
367
|
-
setStep(next);
|
|
368
656
|
const from = previousStepRef.current;
|
|
369
657
|
const attemptKey = String(next);
|
|
370
658
|
const nextAttempt = from === next ? (attemptByStepRef.current[attemptKey] ?? 0) + 1 : 1;
|
|
@@ -375,20 +663,23 @@ function AdaptiveFlow({
|
|
|
375
663
|
at: Date.now(),
|
|
376
664
|
attempt: nextAttempt
|
|
377
665
|
};
|
|
378
|
-
|
|
666
|
+
dispatch({
|
|
667
|
+
type: "evaluated",
|
|
668
|
+
missingRequirements: missing,
|
|
669
|
+
step: next,
|
|
670
|
+
transition
|
|
671
|
+
});
|
|
379
672
|
previousStepRef.current = next;
|
|
380
673
|
onStepTransition?.(transition, context);
|
|
381
674
|
})().catch((error) => {
|
|
382
|
-
if (
|
|
675
|
+
if (currentEvaluation !== evaluationRef.current) {
|
|
383
676
|
return;
|
|
384
677
|
}
|
|
385
678
|
const normalized = toError(error);
|
|
386
|
-
|
|
679
|
+
dispatch({ type: "set_field_errors", fieldErrors: {} });
|
|
680
|
+
dispatch({ type: "set_error", errorMessage: normalized.message });
|
|
387
681
|
onError?.(normalized);
|
|
388
682
|
});
|
|
389
|
-
return () => {
|
|
390
|
-
isCancelled = true;
|
|
391
|
-
};
|
|
392
683
|
}, [context, graph, normalizedCompleteStep, onError, onStepTransition]);
|
|
393
684
|
React.useEffect(() => {
|
|
394
685
|
if (step === normalizedCompleteStep) {
|
|
@@ -397,34 +688,46 @@ function AdaptiveFlow({
|
|
|
397
688
|
onComplete?.(context);
|
|
398
689
|
const shouldClearPersistence = persistence?.clearOnComplete ?? true;
|
|
399
690
|
if (shouldClearPersistence) {
|
|
400
|
-
|
|
691
|
+
try {
|
|
692
|
+
clearPersistedState(persistence);
|
|
693
|
+
} catch (error) {
|
|
694
|
+
reportPersistenceError(error, "clear");
|
|
695
|
+
}
|
|
401
696
|
}
|
|
402
697
|
}
|
|
403
698
|
} else {
|
|
404
699
|
completed.current = false;
|
|
405
700
|
}
|
|
406
|
-
}, [context, normalizedCompleteStep, onComplete, persistence, step]);
|
|
701
|
+
}, [context, normalizedCompleteStep, onComplete, persistence, reportPersistenceError, step]);
|
|
407
702
|
React.useEffect(() => {
|
|
408
703
|
if (!persistence || !persistenceHydrated) {
|
|
409
704
|
return;
|
|
410
705
|
}
|
|
411
706
|
try {
|
|
412
707
|
writePersistedState(persistence, { context, oauthPendingProvider });
|
|
413
|
-
} catch {
|
|
708
|
+
} catch (error) {
|
|
709
|
+
reportPersistenceError(error, "write");
|
|
414
710
|
}
|
|
415
|
-
}, [context, oauthPendingProvider, persistence, persistenceHydrated]);
|
|
711
|
+
}, [context, oauthPendingProvider, persistence, persistenceHydrated, reportPersistenceError]);
|
|
416
712
|
const run = React.useCallback(
|
|
417
713
|
async (job) => {
|
|
418
|
-
|
|
419
|
-
setErrorMessage(null);
|
|
714
|
+
dispatch({ type: "start_job" });
|
|
420
715
|
try {
|
|
421
716
|
await job();
|
|
422
717
|
} catch (error) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
718
|
+
if (error instanceof FlowValidationError) {
|
|
719
|
+
dispatch({ type: "set_field_errors", fieldErrors: error.fieldErrors });
|
|
720
|
+
if (Object.keys(error.fieldErrors).length === 0) {
|
|
721
|
+
dispatch({ type: "set_error", errorMessage: error.message });
|
|
722
|
+
onError?.(error);
|
|
723
|
+
}
|
|
724
|
+
} else {
|
|
725
|
+
const normalized = toError(error);
|
|
726
|
+
dispatch({ type: "set_error", errorMessage: normalized.message });
|
|
727
|
+
onError?.(normalized);
|
|
728
|
+
}
|
|
426
729
|
} finally {
|
|
427
|
-
|
|
730
|
+
dispatch({ type: "set_busy", busy: false });
|
|
428
731
|
}
|
|
429
732
|
},
|
|
430
733
|
[onError]
|
|
@@ -432,37 +735,35 @@ function AdaptiveFlow({
|
|
|
432
735
|
const patchContext = React.useCallback((patch) => {
|
|
433
736
|
setContext((prev) => mergeContext(prev, patch));
|
|
434
737
|
}, []);
|
|
435
|
-
const patchBaseContext = React.useCallback(
|
|
436
|
-
(patch) => {
|
|
437
|
-
patchContext(patch);
|
|
438
|
-
},
|
|
439
|
-
[patchContext]
|
|
440
|
-
);
|
|
441
738
|
React.useEffect(() => {
|
|
442
739
|
const completeOAuth = adapter?.completeOAuth;
|
|
443
740
|
if (!oauthPendingProvider || !completeOAuth) {
|
|
444
741
|
return;
|
|
445
742
|
}
|
|
446
743
|
void run(async () => {
|
|
447
|
-
const patch = await
|
|
744
|
+
const patch = await withRetry(
|
|
745
|
+
() => completeOAuth(oauthPendingProvider, context),
|
|
746
|
+
retryPolicy
|
|
747
|
+
);
|
|
448
748
|
if (patch) {
|
|
449
749
|
patchContext(patch);
|
|
450
750
|
}
|
|
451
|
-
|
|
452
|
-
|
|
751
|
+
dispatch({ type: "set_oauth_pending", provider: null });
|
|
752
|
+
dispatch({ type: "set_message", message: "OAuth sign-in completed." });
|
|
453
753
|
});
|
|
454
|
-
}, [adapter, context, oauthPendingProvider, patchContext, run]);
|
|
754
|
+
}, [adapter, context, oauthPendingProvider, patchContext, retryPolicy, run]);
|
|
455
755
|
const handleEmail = (emailInput) => {
|
|
456
756
|
const email = emailInput.trim().toLowerCase();
|
|
457
757
|
if (!email) {
|
|
458
758
|
return;
|
|
459
759
|
}
|
|
460
760
|
void run(async () => {
|
|
761
|
+
await assertSchema(schemas?.email, email, "email");
|
|
461
762
|
if (validators?.email) {
|
|
462
|
-
await assertValid(validators.email(email, { context }));
|
|
763
|
+
await assertValid(validators.email(email, { context }), "email");
|
|
463
764
|
}
|
|
464
|
-
const identity = await adapter
|
|
465
|
-
|
|
765
|
+
const identity = adapter?.lookupByEmail ? await withRetry(() => adapter.lookupByEmail(email), retryPolicy) : null;
|
|
766
|
+
patchContext({
|
|
466
767
|
email,
|
|
467
768
|
hasPassword: Boolean(identity?.hasPassword),
|
|
468
769
|
isVerified: Boolean(identity?.isVerified),
|
|
@@ -474,15 +775,23 @@ function AdaptiveFlow({
|
|
|
474
775
|
}
|
|
475
776
|
});
|
|
476
777
|
if (identity?.accountExists && identity.hasPassword) {
|
|
477
|
-
|
|
778
|
+
dispatch({ type: "set_message", message: "Welcome back. Enter your password to continue." });
|
|
478
779
|
return;
|
|
479
780
|
}
|
|
480
781
|
if (adapter?.requestOtp) {
|
|
481
|
-
await adapter.requestOtp(email);
|
|
482
|
-
|
|
782
|
+
await withRetry(() => adapter.requestOtp(email), retryPolicy);
|
|
783
|
+
dispatch({ type: "set_message", message: "We sent a 6-digit code to your inbox." });
|
|
483
784
|
} else {
|
|
484
|
-
|
|
485
|
-
|
|
785
|
+
const env = globalThis.process?.env?.NODE_ENV;
|
|
786
|
+
const isDev = env !== "production";
|
|
787
|
+
if (!isDev) {
|
|
788
|
+
throw new Error("OTP adapter is required in production. Provide adapter.requestOtp.");
|
|
789
|
+
}
|
|
790
|
+
patchContext({ isVerified: true });
|
|
791
|
+
dispatch({
|
|
792
|
+
type: "set_message",
|
|
793
|
+
message: "No OTP adapter configured. Verification was skipped in development mode."
|
|
794
|
+
});
|
|
486
795
|
}
|
|
487
796
|
});
|
|
488
797
|
};
|
|
@@ -491,14 +800,15 @@ function AdaptiveFlow({
|
|
|
491
800
|
return;
|
|
492
801
|
}
|
|
493
802
|
void run(async () => {
|
|
803
|
+
await assertSchema(schemas?.otp, code, "otp");
|
|
494
804
|
if (validators?.otp) {
|
|
495
|
-
await assertValid(validators.otp(code, { context, email: context.email }));
|
|
805
|
+
await assertValid(validators.otp(code, { context, email: context.email }), "otp");
|
|
496
806
|
}
|
|
497
807
|
if (adapter?.verifyOtp) {
|
|
498
|
-
await adapter.verifyOtp(context.email, code);
|
|
808
|
+
await withRetry(() => adapter.verifyOtp(context.email, code), retryPolicy);
|
|
499
809
|
}
|
|
500
|
-
|
|
501
|
-
|
|
810
|
+
patchContext({ isVerified: true });
|
|
811
|
+
dispatch({ type: "set_message", message: "Email verified." });
|
|
502
812
|
});
|
|
503
813
|
};
|
|
504
814
|
const handlePassword = (password) => {
|
|
@@ -506,26 +816,34 @@ function AdaptiveFlow({
|
|
|
506
816
|
return;
|
|
507
817
|
}
|
|
508
818
|
void run(async () => {
|
|
819
|
+
await assertSchema(schemas?.password, password, "password");
|
|
509
820
|
if (validators?.password) {
|
|
510
|
-
await assertValid(
|
|
821
|
+
await assertValid(
|
|
822
|
+
validators.password(password, { context, hasPassword: context.hasPassword }),
|
|
823
|
+
"password"
|
|
824
|
+
);
|
|
511
825
|
}
|
|
512
826
|
if (context.hasPassword) {
|
|
513
827
|
if (adapter?.signInWithPassword) {
|
|
514
|
-
await
|
|
828
|
+
await withRetry(
|
|
829
|
+
() => adapter.signInWithPassword(context.email, password),
|
|
830
|
+
retryPolicy
|
|
831
|
+
);
|
|
515
832
|
}
|
|
516
833
|
} else {
|
|
517
834
|
if (adapter?.createPassword) {
|
|
518
|
-
await adapter.createPassword(password);
|
|
835
|
+
await withRetry(() => adapter.createPassword(password), retryPolicy);
|
|
519
836
|
}
|
|
520
|
-
|
|
837
|
+
patchContext({ hasPassword: true });
|
|
521
838
|
}
|
|
522
|
-
|
|
839
|
+
dispatch({ type: "set_message", message: "Password step complete." });
|
|
523
840
|
});
|
|
524
841
|
};
|
|
525
842
|
const handleProfile = (profile) => {
|
|
526
843
|
void run(async () => {
|
|
844
|
+
await assertSchema(schemas?.profile, profile, "profile");
|
|
527
845
|
if (validators?.profile) {
|
|
528
|
-
await assertValid(validators.profile(profile, { context }));
|
|
846
|
+
await assertValid(validators.profile(profile, { context }), "profile");
|
|
529
847
|
}
|
|
530
848
|
const next = mergeContext(context, {
|
|
531
849
|
profile: {
|
|
@@ -535,23 +853,24 @@ function AdaptiveFlow({
|
|
|
535
853
|
}
|
|
536
854
|
});
|
|
537
855
|
if (adapter?.saveProfile) {
|
|
538
|
-
await adapter.saveProfile(next);
|
|
856
|
+
await withRetry(() => adapter.saveProfile(next), retryPolicy);
|
|
539
857
|
}
|
|
540
858
|
patchContext({ profile: next.profile });
|
|
541
|
-
|
|
859
|
+
dispatch({ type: "set_message", message: "Profile saved." });
|
|
542
860
|
});
|
|
543
861
|
};
|
|
544
862
|
const handleTos = () => {
|
|
545
863
|
void run(async () => {
|
|
864
|
+
await assertSchema(schemas?.tos, true, "tos");
|
|
546
865
|
if (validators?.tos) {
|
|
547
|
-
await assertValid(validators.tos(true, { context }));
|
|
866
|
+
await assertValid(validators.tos(true, { context }), "tos");
|
|
548
867
|
}
|
|
549
868
|
const next = mergeContext(context, { agreedToTos: true });
|
|
550
869
|
if (adapter?.acceptTos) {
|
|
551
|
-
await adapter.acceptTos(next);
|
|
870
|
+
await withRetry(() => adapter.acceptTos(next), retryPolicy);
|
|
552
871
|
}
|
|
553
|
-
|
|
554
|
-
|
|
872
|
+
patchContext({ agreedToTos: true });
|
|
873
|
+
dispatch({ type: "set_message", message: "Terms accepted." });
|
|
555
874
|
});
|
|
556
875
|
};
|
|
557
876
|
const handleOAuth = (provider) => {
|
|
@@ -560,12 +879,97 @@ function AdaptiveFlow({
|
|
|
560
879
|
return;
|
|
561
880
|
}
|
|
562
881
|
void run(async () => {
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
await startOAuth(provider, context);
|
|
882
|
+
dispatch({ type: "set_oauth_pending", provider });
|
|
883
|
+
dispatch({ type: "set_message", message: `Starting ${provider} sign-in...` });
|
|
884
|
+
await withRetry(() => startOAuth(provider, context), retryPolicy);
|
|
566
885
|
});
|
|
567
886
|
};
|
|
568
|
-
|
|
887
|
+
return {
|
|
888
|
+
context,
|
|
889
|
+
step,
|
|
890
|
+
completeStep: normalizedCompleteStep,
|
|
891
|
+
requirements: normalizedRequirements,
|
|
892
|
+
missingRequirements,
|
|
893
|
+
transitions,
|
|
894
|
+
busy,
|
|
895
|
+
message,
|
|
896
|
+
errorMessage,
|
|
897
|
+
fieldErrors,
|
|
898
|
+
setContextPatch: patchContext,
|
|
899
|
+
run,
|
|
900
|
+
handleEmail,
|
|
901
|
+
handleOtp,
|
|
902
|
+
handlePassword,
|
|
903
|
+
handleProfile,
|
|
904
|
+
handleTos,
|
|
905
|
+
handleOAuth
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function AdaptiveFlow({
|
|
909
|
+
adapter,
|
|
910
|
+
requirements,
|
|
911
|
+
requirementGraph,
|
|
912
|
+
requirementGraphConfig,
|
|
913
|
+
requirementResolvers,
|
|
914
|
+
completeStep,
|
|
915
|
+
stepTitles,
|
|
916
|
+
renderStep,
|
|
917
|
+
stepRegistry,
|
|
918
|
+
initialValue,
|
|
919
|
+
onComplete,
|
|
920
|
+
onError,
|
|
921
|
+
onStepTransition,
|
|
922
|
+
className,
|
|
923
|
+
classNames,
|
|
924
|
+
styles,
|
|
925
|
+
unstyled = false,
|
|
926
|
+
persistence,
|
|
927
|
+
validators,
|
|
928
|
+
schemas,
|
|
929
|
+
oauthProviders,
|
|
930
|
+
retryPolicy
|
|
931
|
+
}) {
|
|
932
|
+
const uiStyles = React.useMemo(() => resolveStyles(unstyled, styles), [unstyled, styles]);
|
|
933
|
+
const {
|
|
934
|
+
context,
|
|
935
|
+
step,
|
|
936
|
+
completeStep: normalizedCompleteStep,
|
|
937
|
+
requirements: normalizedRequirements,
|
|
938
|
+
missingRequirements: missing,
|
|
939
|
+
transitions,
|
|
940
|
+
busy,
|
|
941
|
+
message,
|
|
942
|
+
errorMessage,
|
|
943
|
+
fieldErrors,
|
|
944
|
+
setContextPatch: patchContext,
|
|
945
|
+
run,
|
|
946
|
+
handleEmail,
|
|
947
|
+
handleOtp,
|
|
948
|
+
handlePassword,
|
|
949
|
+
handleProfile,
|
|
950
|
+
handleTos,
|
|
951
|
+
handleOAuth
|
|
952
|
+
} = useAdaptiveFlow({
|
|
953
|
+
adapter,
|
|
954
|
+
requirements,
|
|
955
|
+
requirementGraph,
|
|
956
|
+
requirementGraphConfig,
|
|
957
|
+
requirementResolvers,
|
|
958
|
+
completeStep,
|
|
959
|
+
initialValue,
|
|
960
|
+
onComplete,
|
|
961
|
+
onError,
|
|
962
|
+
onStepTransition,
|
|
963
|
+
persistence,
|
|
964
|
+
validators,
|
|
965
|
+
schemas,
|
|
966
|
+
oauthProviders,
|
|
967
|
+
retryPolicy
|
|
968
|
+
});
|
|
969
|
+
const normalizedOAuthProviders = React.useMemo(
|
|
970
|
+
() => oauthProviders && oauthProviders.length > 0 ? oauthProviders : defaultOAuthProviders,
|
|
971
|
+
[oauthProviders]
|
|
972
|
+
);
|
|
569
973
|
const needsJobTitle = normalizedRequirements.includes("has_job_title");
|
|
570
974
|
const stepLabel = stepTitles?.[step] ?? defaultStepTitle[step] ?? step;
|
|
571
975
|
const defaultView = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
@@ -574,6 +978,7 @@ function AdaptiveFlow({
|
|
|
574
978
|
{
|
|
575
979
|
onSubmit: handleEmail,
|
|
576
980
|
disabled: busy,
|
|
981
|
+
error: fieldErrors.email,
|
|
577
982
|
styles: uiStyles,
|
|
578
983
|
classNames
|
|
579
984
|
}
|
|
@@ -584,6 +989,7 @@ function AdaptiveFlow({
|
|
|
584
989
|
onSubmit: handleOtp,
|
|
585
990
|
email: context.email ?? "your email",
|
|
586
991
|
disabled: busy,
|
|
992
|
+
error: fieldErrors.otp,
|
|
587
993
|
styles: uiStyles,
|
|
588
994
|
classNames
|
|
589
995
|
}
|
|
@@ -594,6 +1000,7 @@ function AdaptiveFlow({
|
|
|
594
1000
|
onSubmit: handlePassword,
|
|
595
1001
|
disabled: busy,
|
|
596
1002
|
hasPassword: context.hasPassword,
|
|
1003
|
+
error: fieldErrors.password,
|
|
597
1004
|
styles: uiStyles,
|
|
598
1005
|
classNames
|
|
599
1006
|
}
|
|
@@ -605,6 +1012,7 @@ function AdaptiveFlow({
|
|
|
605
1012
|
requireJobTitle: needsJobTitle,
|
|
606
1013
|
onSubmit: handleProfile,
|
|
607
1014
|
disabled: busy,
|
|
1015
|
+
errors: fieldErrors,
|
|
608
1016
|
styles: uiStyles,
|
|
609
1017
|
classNames
|
|
610
1018
|
}
|
|
@@ -614,12 +1022,13 @@ function AdaptiveFlow({
|
|
|
614
1022
|
{
|
|
615
1023
|
onSubmit: handleTos,
|
|
616
1024
|
disabled: busy,
|
|
1025
|
+
error: fieldErrors.tos,
|
|
617
1026
|
styles: uiStyles,
|
|
618
1027
|
classNames
|
|
619
1028
|
}
|
|
620
1029
|
) : null,
|
|
621
1030
|
step === normalizedCompleteStep ? /* @__PURE__ */ jsx(CompleteBlock, { styles: uiStyles, classNames }) : null,
|
|
622
|
-
step
|
|
1031
|
+
!builtInDefaultSteps.has(String(step)) && step !== normalizedCompleteStep ? /* @__PURE__ */ jsxs("div", { className: classNames?.info, style: uiStyles.info, children: [
|
|
623
1032
|
'No default renderer for step "',
|
|
624
1033
|
step,
|
|
625
1034
|
'". Provide renderStep to handle custom steps.'
|
|
@@ -631,6 +1040,7 @@ function AdaptiveFlow({
|
|
|
631
1040
|
busy,
|
|
632
1041
|
message,
|
|
633
1042
|
errorMessage,
|
|
1043
|
+
fieldErrors,
|
|
634
1044
|
missingRequirements: missing,
|
|
635
1045
|
requirements: normalizedRequirements,
|
|
636
1046
|
defaultView,
|
|
@@ -645,6 +1055,7 @@ function AdaptiveFlow({
|
|
|
645
1055
|
busy,
|
|
646
1056
|
message,
|
|
647
1057
|
errorMessage,
|
|
1058
|
+
fieldErrors,
|
|
648
1059
|
missingRequirements: missing,
|
|
649
1060
|
requirements: normalizedRequirements,
|
|
650
1061
|
setContextPatch: patchContext,
|
|
@@ -667,38 +1078,31 @@ function AdaptiveFlow({
|
|
|
667
1078
|
message ? /* @__PURE__ */ jsx("div", { className: classNames?.success, style: uiStyles.success, children: message }) : null,
|
|
668
1079
|
errorMessage ? /* @__PURE__ */ jsx("div", { className: classNames?.error, style: uiStyles.error, children: errorMessage }) : null,
|
|
669
1080
|
customView ?? registryView ?? defaultView,
|
|
670
|
-
/* @__PURE__ */
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
/* @__PURE__ */ jsx(
|
|
685
|
-
"button",
|
|
686
|
-
{
|
|
687
|
-
type: "button",
|
|
688
|
-
className: classNames?.oauthButton,
|
|
689
|
-
style: uiStyles.oauthButton,
|
|
690
|
-
disabled: busy || !adapter?.startOAuth,
|
|
691
|
-
onClick: () => {
|
|
692
|
-
handleOAuth("apple");
|
|
693
|
-
},
|
|
694
|
-
children: "Continue with Apple"
|
|
695
|
-
}
|
|
696
|
-
)
|
|
697
|
-
] })
|
|
1081
|
+
/* @__PURE__ */ jsx("div", { className: classNames?.footer, style: uiStyles.footer, children: normalizedOAuthProviders.map((provider) => /* @__PURE__ */ jsx(
|
|
1082
|
+
"button",
|
|
1083
|
+
{
|
|
1084
|
+
type: "button",
|
|
1085
|
+
className: classNames?.oauthButton,
|
|
1086
|
+
style: uiStyles.oauthButton,
|
|
1087
|
+
disabled: busy || !adapter?.startOAuth,
|
|
1088
|
+
onClick: () => {
|
|
1089
|
+
handleOAuth(provider.id);
|
|
1090
|
+
},
|
|
1091
|
+
children: provider.label
|
|
1092
|
+
},
|
|
1093
|
+
provider.id
|
|
1094
|
+
)) })
|
|
698
1095
|
] });
|
|
699
1096
|
}
|
|
700
|
-
function EmailBlock({
|
|
1097
|
+
function EmailBlock({
|
|
1098
|
+
onSubmit,
|
|
1099
|
+
disabled,
|
|
1100
|
+
error,
|
|
1101
|
+
styles,
|
|
1102
|
+
classNames
|
|
1103
|
+
}) {
|
|
701
1104
|
const [email, setEmail] = React.useState("");
|
|
1105
|
+
const errorId = "adaptive-flow-email-error";
|
|
702
1106
|
return /* @__PURE__ */ jsxs(
|
|
703
1107
|
"form",
|
|
704
1108
|
{
|
|
@@ -719,10 +1123,13 @@ function EmailBlock({ onSubmit, disabled, styles, classNames }) {
|
|
|
719
1123
|
placeholder: "Enter your email",
|
|
720
1124
|
value: email,
|
|
721
1125
|
onChange: (event) => setEmail(event.target.value),
|
|
1126
|
+
"aria-invalid": Boolean(error),
|
|
1127
|
+
"aria-describedby": error ? errorId : void 0,
|
|
722
1128
|
required: true
|
|
723
1129
|
}
|
|
724
1130
|
),
|
|
725
|
-
/* @__PURE__ */ jsx("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" })
|
|
1131
|
+
/* @__PURE__ */ jsx("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" }),
|
|
1132
|
+
error ? /* @__PURE__ */ jsx("div", { id: errorId, className: classNames?.error, style: styles.error, children: error }) : null
|
|
726
1133
|
]
|
|
727
1134
|
}
|
|
728
1135
|
);
|
|
@@ -731,10 +1138,12 @@ function OtpBlock({
|
|
|
731
1138
|
onSubmit,
|
|
732
1139
|
disabled,
|
|
733
1140
|
email,
|
|
1141
|
+
error,
|
|
734
1142
|
styles,
|
|
735
1143
|
classNames
|
|
736
1144
|
}) {
|
|
737
1145
|
const [code, setCode] = React.useState("");
|
|
1146
|
+
const errorId = "adaptive-flow-otp-error";
|
|
738
1147
|
return /* @__PURE__ */ jsxs(
|
|
739
1148
|
"form",
|
|
740
1149
|
{
|
|
@@ -760,6 +1169,8 @@ function OtpBlock({
|
|
|
760
1169
|
placeholder: "Enter 6-digit code",
|
|
761
1170
|
value: code,
|
|
762
1171
|
onChange: (event) => setCode(event.target.value.replace(/\D/g, "").slice(0, 6)),
|
|
1172
|
+
"aria-invalid": Boolean(error),
|
|
1173
|
+
"aria-describedby": error ? errorId : void 0,
|
|
763
1174
|
required: true,
|
|
764
1175
|
maxLength: 6,
|
|
765
1176
|
pattern: "[0-9]{6}"
|
|
@@ -774,7 +1185,8 @@ function OtpBlock({
|
|
|
774
1185
|
type: "submit",
|
|
775
1186
|
children: "Verify"
|
|
776
1187
|
}
|
|
777
|
-
)
|
|
1188
|
+
),
|
|
1189
|
+
error ? /* @__PURE__ */ jsx("div", { id: errorId, className: classNames?.error, style: styles.error, children: error }) : null
|
|
778
1190
|
] })
|
|
779
1191
|
]
|
|
780
1192
|
}
|
|
@@ -784,10 +1196,12 @@ function PasswordBlock({
|
|
|
784
1196
|
onSubmit,
|
|
785
1197
|
disabled,
|
|
786
1198
|
hasPassword,
|
|
1199
|
+
error,
|
|
787
1200
|
styles,
|
|
788
1201
|
classNames
|
|
789
1202
|
}) {
|
|
790
1203
|
const [password, setPassword] = React.useState("");
|
|
1204
|
+
const errorId = "adaptive-flow-password-error";
|
|
791
1205
|
return /* @__PURE__ */ jsxs(
|
|
792
1206
|
"form",
|
|
793
1207
|
{
|
|
@@ -808,11 +1222,14 @@ function PasswordBlock({
|
|
|
808
1222
|
placeholder: hasPassword ? "Enter your password" : "Create a password",
|
|
809
1223
|
value: password,
|
|
810
1224
|
onChange: (event) => setPassword(event.target.value),
|
|
1225
|
+
"aria-invalid": Boolean(error),
|
|
1226
|
+
"aria-describedby": error ? errorId : void 0,
|
|
811
1227
|
required: true,
|
|
812
1228
|
minLength: 8
|
|
813
1229
|
}
|
|
814
1230
|
),
|
|
815
|
-
/* @__PURE__ */ jsx("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" })
|
|
1231
|
+
/* @__PURE__ */ jsx("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" }),
|
|
1232
|
+
error ? /* @__PURE__ */ jsx("div", { id: errorId, className: classNames?.error, style: styles.error, children: error }) : null
|
|
816
1233
|
]
|
|
817
1234
|
}
|
|
818
1235
|
);
|
|
@@ -822,12 +1239,17 @@ function ProfileBlock({
|
|
|
822
1239
|
disabled,
|
|
823
1240
|
defaultValue,
|
|
824
1241
|
requireJobTitle,
|
|
1242
|
+
errors,
|
|
825
1243
|
styles,
|
|
826
1244
|
classNames
|
|
827
1245
|
}) {
|
|
828
1246
|
const [firstName, setFirstName] = React.useState(defaultValue.firstName ?? "");
|
|
829
1247
|
const [lastName, setLastName] = React.useState(defaultValue.lastName ?? "");
|
|
830
1248
|
const [jobTitle, setJobTitle] = React.useState(defaultValue.jobTitle ?? "");
|
|
1249
|
+
const firstNameError = errors?.firstName ?? errors?.["profile.firstName"];
|
|
1250
|
+
const lastNameError = errors?.lastName ?? errors?.["profile.lastName"];
|
|
1251
|
+
const jobTitleError = errors?.jobTitle ?? errors?.["profile.jobTitle"];
|
|
1252
|
+
const profileError = errors?.profile;
|
|
831
1253
|
return /* @__PURE__ */ jsxs(
|
|
832
1254
|
"form",
|
|
833
1255
|
{
|
|
@@ -853,6 +1275,7 @@ function ProfileBlock({
|
|
|
853
1275
|
placeholder: "First name",
|
|
854
1276
|
value: firstName,
|
|
855
1277
|
onChange: (event) => setFirstName(event.target.value),
|
|
1278
|
+
"aria-invalid": Boolean(firstNameError),
|
|
856
1279
|
required: true
|
|
857
1280
|
}
|
|
858
1281
|
),
|
|
@@ -866,23 +1289,31 @@ function ProfileBlock({
|
|
|
866
1289
|
placeholder: "Last name",
|
|
867
1290
|
value: lastName,
|
|
868
1291
|
onChange: (event) => setLastName(event.target.value),
|
|
1292
|
+
"aria-invalid": Boolean(lastNameError),
|
|
869
1293
|
required: true
|
|
870
1294
|
}
|
|
871
1295
|
)
|
|
872
1296
|
] }),
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1297
|
+
firstNameError ? /* @__PURE__ */ jsx("div", { className: classNames?.error, style: styles.error, children: firstNameError }) : null,
|
|
1298
|
+
lastNameError ? /* @__PURE__ */ jsx("div", { className: classNames?.error, style: styles.error, children: lastNameError }) : null,
|
|
1299
|
+
requireJobTitle ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1300
|
+
/* @__PURE__ */ jsx(
|
|
1301
|
+
"input",
|
|
1302
|
+
{
|
|
1303
|
+
className: classNames?.input,
|
|
1304
|
+
style: styles.input,
|
|
1305
|
+
type: "text",
|
|
1306
|
+
autoComplete: "organization-title",
|
|
1307
|
+
placeholder: "Job title",
|
|
1308
|
+
value: jobTitle,
|
|
1309
|
+
onChange: (event) => setJobTitle(event.target.value),
|
|
1310
|
+
"aria-invalid": Boolean(jobTitleError),
|
|
1311
|
+
required: true
|
|
1312
|
+
}
|
|
1313
|
+
),
|
|
1314
|
+
jobTitleError ? /* @__PURE__ */ jsx("div", { className: classNames?.error, style: styles.error, children: jobTitleError }) : null
|
|
1315
|
+
] }) : null,
|
|
1316
|
+
profileError ? /* @__PURE__ */ jsx("div", { className: classNames?.error, style: styles.error, children: profileError }) : null,
|
|
886
1317
|
/* @__PURE__ */ jsx("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" })
|
|
887
1318
|
]
|
|
888
1319
|
}
|
|
@@ -891,6 +1322,7 @@ function ProfileBlock({
|
|
|
891
1322
|
function TosBlock({
|
|
892
1323
|
onSubmit,
|
|
893
1324
|
disabled,
|
|
1325
|
+
error,
|
|
894
1326
|
styles,
|
|
895
1327
|
classNames
|
|
896
1328
|
}) {
|
|
@@ -908,9 +1340,18 @@ function TosBlock({
|
|
|
908
1340
|
},
|
|
909
1341
|
children: [
|
|
910
1342
|
/* @__PURE__ */ jsxs("label", { className: classNames?.checkboxRow, style: styles.checkboxRow, children: [
|
|
911
|
-
/* @__PURE__ */ jsx(
|
|
1343
|
+
/* @__PURE__ */ jsx(
|
|
1344
|
+
"input",
|
|
1345
|
+
{
|
|
1346
|
+
type: "checkbox",
|
|
1347
|
+
checked,
|
|
1348
|
+
onChange: (event) => setChecked(event.target.checked),
|
|
1349
|
+
"aria-invalid": Boolean(error)
|
|
1350
|
+
}
|
|
1351
|
+
),
|
|
912
1352
|
/* @__PURE__ */ jsx("span", { children: "I agree to the Terms of Service and Privacy Policy." })
|
|
913
1353
|
] }),
|
|
1354
|
+
error ? /* @__PURE__ */ jsx("div", { className: classNames?.error, style: styles.error, children: error }) : null,
|
|
914
1355
|
/* @__PURE__ */ jsx(
|
|
915
1356
|
"button",
|
|
916
1357
|
{
|
|
@@ -930,7 +1371,7 @@ function CompleteBlock({
|
|
|
930
1371
|
classNames
|
|
931
1372
|
}) {
|
|
932
1373
|
return /* @__PURE__ */ jsxs("div", { className: classNames?.complete, style: styles.complete, children: [
|
|
933
|
-
/* @__PURE__ */ jsx("strong", { children: "You are
|
|
1374
|
+
/* @__PURE__ */ jsx("strong", { children: "You are all set." }),
|
|
934
1375
|
/* @__PURE__ */ jsx("p", { className: classNames?.caption, style: styles.caption, children: "All requirements are complete." })
|
|
935
1376
|
] });
|
|
936
1377
|
}
|
|
@@ -1068,6 +1509,7 @@ export {
|
|
|
1068
1509
|
defaultRequirements,
|
|
1069
1510
|
evaluateNextStep,
|
|
1070
1511
|
getMissingRequirements,
|
|
1071
|
-
initialContext
|
|
1512
|
+
initialContext,
|
|
1513
|
+
useAdaptiveFlow
|
|
1072
1514
|
};
|
|
1073
1515
|
//# sourceMappingURL=index.mjs.map
|