@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.js
CHANGED
|
@@ -40,7 +40,8 @@ __export(index_exports, {
|
|
|
40
40
|
defaultRequirements: () => defaultRequirements,
|
|
41
41
|
evaluateNextStep: () => evaluateNextStep,
|
|
42
42
|
getMissingRequirements: () => getMissingRequirements,
|
|
43
|
-
initialContext: () => initialContext
|
|
43
|
+
initialContext: () => initialContext,
|
|
44
|
+
useAdaptiveFlow: () => useAdaptiveFlow
|
|
44
45
|
});
|
|
45
46
|
module.exports = __toCommonJS(index_exports);
|
|
46
47
|
|
|
@@ -70,6 +71,8 @@ var defaultRequirements = [
|
|
|
70
71
|
DefaultAppRequirements.EMAIL_VERIFIED,
|
|
71
72
|
DefaultAppRequirements.HAS_PASSWORD,
|
|
72
73
|
DefaultAppRequirements.HAS_FIRST_NAME,
|
|
74
|
+
DefaultAppRequirements.HAS_LAST_NAME,
|
|
75
|
+
DefaultAppRequirements.HAS_JOB_TITLE,
|
|
73
76
|
DefaultAppRequirements.ACCEPTED_TOS
|
|
74
77
|
];
|
|
75
78
|
var initialContext = {
|
|
@@ -115,20 +118,66 @@ var defaultRequirementResolvers = {
|
|
|
115
118
|
};
|
|
116
119
|
function createRequirementGraph(requirements, resolvers, options) {
|
|
117
120
|
const graph = [];
|
|
121
|
+
const requirementSet = new Set(requirements);
|
|
122
|
+
const resolverBackedRequirements = new Set(
|
|
123
|
+
requirements.filter((requirement) => Boolean(resolvers[requirement]))
|
|
124
|
+
);
|
|
118
125
|
for (const requirement of requirements) {
|
|
119
126
|
const resolver = resolvers[requirement];
|
|
120
127
|
if (!resolver) {
|
|
121
128
|
continue;
|
|
122
129
|
}
|
|
130
|
+
const dependsOn = options?.dependencies?.[requirement] ?? [];
|
|
131
|
+
for (const dependency of dependsOn) {
|
|
132
|
+
if (!requirementSet.has(dependency)) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Invalid dependency "${String(dependency)}" referenced by requirement "${String(requirement)}".`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (!resolverBackedRequirements.has(dependency)) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
`Dependency "${String(dependency)}" referenced by requirement "${String(requirement)}" has no resolver.`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
123
143
|
graph.push({
|
|
124
144
|
requirement,
|
|
125
145
|
step: resolver.step,
|
|
126
146
|
isMet: resolver.isMet,
|
|
127
147
|
when: options?.conditions?.[requirement],
|
|
128
148
|
priority: options?.priorities?.[requirement] ?? 0,
|
|
129
|
-
dependsOn
|
|
149
|
+
dependsOn
|
|
130
150
|
});
|
|
131
151
|
}
|
|
152
|
+
const byRequirement = new Map(
|
|
153
|
+
graph.map((node) => [node.requirement, node])
|
|
154
|
+
);
|
|
155
|
+
const visited = /* @__PURE__ */ new Set();
|
|
156
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
157
|
+
const dfs = (requirement, stack) => {
|
|
158
|
+
if (visited.has(requirement)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (visiting.has(requirement)) {
|
|
162
|
+
const cycleStart = stack.indexOf(requirement);
|
|
163
|
+
const cyclePath = [...stack.slice(cycleStart), requirement].map((value) => String(value)).join(" -> ");
|
|
164
|
+
throw new Error(`Circular dependency detected in requirement graph: ${cyclePath}`);
|
|
165
|
+
}
|
|
166
|
+
visiting.add(requirement);
|
|
167
|
+
const node = byRequirement.get(requirement);
|
|
168
|
+
if (node?.dependsOn?.length) {
|
|
169
|
+
for (const dep of node.dependsOn) {
|
|
170
|
+
if (byRequirement.has(dep)) {
|
|
171
|
+
dfs(dep, [...stack, requirement]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
visiting.delete(requirement);
|
|
176
|
+
visited.add(requirement);
|
|
177
|
+
};
|
|
178
|
+
for (const node of graph) {
|
|
179
|
+
dfs(node.requirement, []);
|
|
180
|
+
}
|
|
132
181
|
return graph;
|
|
133
182
|
}
|
|
134
183
|
function createDefaultRequirementGraph(options) {
|
|
@@ -144,13 +193,63 @@ function createDefaultRequirementGraph(options) {
|
|
|
144
193
|
});
|
|
145
194
|
}
|
|
146
195
|
function sortGraph(graph) {
|
|
147
|
-
|
|
148
|
-
|
|
196
|
+
const entries = graph.map((node, index) => ({ node, index }));
|
|
197
|
+
const byRequirement = new Map(entries.map((entry) => [entry.node.requirement, entry]));
|
|
198
|
+
const indegree = /* @__PURE__ */ new Map();
|
|
199
|
+
const adjacency = /* @__PURE__ */ new Map();
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
indegree.set(entry.node.requirement, 0);
|
|
202
|
+
adjacency.set(entry.node.requirement, []);
|
|
203
|
+
}
|
|
204
|
+
for (const entry of entries) {
|
|
205
|
+
const current = entry.node.requirement;
|
|
206
|
+
for (const dependency of entry.node.dependsOn ?? []) {
|
|
207
|
+
const depEntry = byRequirement.get(dependency);
|
|
208
|
+
if (!depEntry) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const outgoing = adjacency.get(depEntry.node.requirement);
|
|
212
|
+
if (outgoing) {
|
|
213
|
+
outgoing.push(current);
|
|
214
|
+
}
|
|
215
|
+
indegree.set(current, (indegree.get(current) ?? 0) + 1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const compare = (left, right) => {
|
|
219
|
+
const byPriority = (right.node.priority ?? 0) - (left.node.priority ?? 0);
|
|
149
220
|
if (byPriority !== 0) {
|
|
150
221
|
return byPriority;
|
|
151
222
|
}
|
|
152
|
-
|
|
153
|
-
|
|
223
|
+
const byRequirementName = String(left.node.requirement).localeCompare(String(right.node.requirement));
|
|
224
|
+
if (byRequirementName !== 0) {
|
|
225
|
+
return byRequirementName;
|
|
226
|
+
}
|
|
227
|
+
return left.index - right.index;
|
|
228
|
+
};
|
|
229
|
+
const ready = entries.filter((entry) => (indegree.get(entry.node.requirement) ?? 0) === 0).sort(compare);
|
|
230
|
+
const ordered = [];
|
|
231
|
+
while (ready.length > 0) {
|
|
232
|
+
const current = ready.shift();
|
|
233
|
+
if (!current) {
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
ordered.push(current.node);
|
|
237
|
+
for (const dependent of adjacency.get(current.node.requirement) ?? []) {
|
|
238
|
+
const next = (indegree.get(dependent) ?? 0) - 1;
|
|
239
|
+
indegree.set(dependent, next);
|
|
240
|
+
if (next === 0) {
|
|
241
|
+
const dependentEntry = byRequirement.get(dependent);
|
|
242
|
+
if (dependentEntry) {
|
|
243
|
+
ready.push(dependentEntry);
|
|
244
|
+
ready.sort(compare);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (ordered.length !== graph.length) {
|
|
250
|
+
throw new Error("Unable to topologically sort requirement graph. Check for dependency cycles.");
|
|
251
|
+
}
|
|
252
|
+
return ordered;
|
|
154
253
|
}
|
|
155
254
|
async function evaluateNextStep(context, graph, completeStep) {
|
|
156
255
|
for (const node of sortGraph(graph)) {
|
|
@@ -191,6 +290,10 @@ async function getMissingRequirements(context, graph) {
|
|
|
191
290
|
|
|
192
291
|
// src/AdaptiveFlow.tsx
|
|
193
292
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
293
|
+
var defaultOAuthProviders = [
|
|
294
|
+
{ id: "google", label: "Continue with Google" },
|
|
295
|
+
{ id: "apple", label: "Continue with Apple" }
|
|
296
|
+
];
|
|
194
297
|
var defaultStepTitle = {
|
|
195
298
|
COLLECT_EMAIL: "Enter your email",
|
|
196
299
|
VERIFY_OTP: "Verify your email",
|
|
@@ -199,6 +302,14 @@ var defaultStepTitle = {
|
|
|
199
302
|
COLLECT_TOS: "Accept terms",
|
|
200
303
|
COMPLETE: "Done"
|
|
201
304
|
};
|
|
305
|
+
var builtInDefaultSteps = /* @__PURE__ */ new Set([
|
|
306
|
+
"COLLECT_EMAIL",
|
|
307
|
+
"VERIFY_OTP",
|
|
308
|
+
"COLLECT_PASSWORD",
|
|
309
|
+
"COLLECT_PROFILE",
|
|
310
|
+
"COLLECT_TOS",
|
|
311
|
+
"COMPLETE"
|
|
312
|
+
]);
|
|
202
313
|
var styleSlots = [
|
|
203
314
|
"shell",
|
|
204
315
|
"headerRow",
|
|
@@ -219,14 +330,24 @@ var styleSlots = [
|
|
|
219
330
|
"oauthButton"
|
|
220
331
|
];
|
|
221
332
|
function mergeContext(current, patch) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
333
|
+
const mergeValue = (baseValue, patchValue) => {
|
|
334
|
+
if (Array.isArray(baseValue) || Array.isArray(patchValue)) {
|
|
335
|
+
return patchValue;
|
|
336
|
+
}
|
|
337
|
+
const baseIsObject = Boolean(baseValue) && typeof baseValue === "object";
|
|
338
|
+
const patchIsObject = Boolean(patchValue) && typeof patchValue === "object";
|
|
339
|
+
if (!baseIsObject || !patchIsObject) {
|
|
340
|
+
return patchValue === void 0 ? baseValue : patchValue;
|
|
228
341
|
}
|
|
342
|
+
const baseObject = baseValue;
|
|
343
|
+
const patchObject = patchValue;
|
|
344
|
+
const result = { ...baseObject };
|
|
345
|
+
for (const key of Object.keys(patchObject)) {
|
|
346
|
+
result[key] = mergeValue(baseObject[key], patchObject[key]);
|
|
347
|
+
}
|
|
348
|
+
return result;
|
|
229
349
|
};
|
|
350
|
+
return mergeValue(current, patch);
|
|
230
351
|
}
|
|
231
352
|
function withDefaults(initialValue) {
|
|
232
353
|
if (!initialValue) {
|
|
@@ -238,7 +359,65 @@ function toError(error) {
|
|
|
238
359
|
if (error instanceof Error) {
|
|
239
360
|
return error;
|
|
240
361
|
}
|
|
241
|
-
|
|
362
|
+
if (typeof error === "string") {
|
|
363
|
+
return new Error(error);
|
|
364
|
+
}
|
|
365
|
+
if (error && typeof error === "object") {
|
|
366
|
+
const maybeError = error;
|
|
367
|
+
const message = typeof maybeError.message === "string" && maybeError.message.trim().length > 0 ? maybeError.message : "Unknown error while processing adaptive flow";
|
|
368
|
+
const normalized = new Error(message);
|
|
369
|
+
if (typeof maybeError.code === "string" && maybeError.code.trim().length > 0) {
|
|
370
|
+
normalized.name = maybeError.code;
|
|
371
|
+
}
|
|
372
|
+
normalized.cause = error;
|
|
373
|
+
return normalized;
|
|
374
|
+
}
|
|
375
|
+
return new Error(`Unknown error while processing adaptive flow: ${String(error)}`);
|
|
376
|
+
}
|
|
377
|
+
function sleep(ms) {
|
|
378
|
+
return new Promise((resolve) => {
|
|
379
|
+
setTimeout(resolve, ms);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
function normalizeDelay(delay, fallback) {
|
|
383
|
+
if (typeof delay !== "number" || Number.isNaN(delay) || delay < 0) {
|
|
384
|
+
return fallback;
|
|
385
|
+
}
|
|
386
|
+
return delay;
|
|
387
|
+
}
|
|
388
|
+
function computeRetryDelay(policy, attempt) {
|
|
389
|
+
if (policy?.delay) {
|
|
390
|
+
return normalizeDelay(policy.delay(attempt), 0);
|
|
391
|
+
}
|
|
392
|
+
const initialDelayMs = normalizeDelay(policy?.initialDelayMs, 250);
|
|
393
|
+
const factor = typeof policy?.factor === "number" && policy.factor > 0 ? policy.factor : 2;
|
|
394
|
+
const maxDelayMs = normalizeDelay(policy?.maxDelayMs, Number.POSITIVE_INFINITY);
|
|
395
|
+
let delay = initialDelayMs * Math.pow(factor, Math.max(0, attempt - 1));
|
|
396
|
+
if (policy?.jitter) {
|
|
397
|
+
delay = delay * (0.5 + Math.random() * 0.5);
|
|
398
|
+
}
|
|
399
|
+
return Math.min(delay, maxDelayMs);
|
|
400
|
+
}
|
|
401
|
+
async function withRetry(operation, retryPolicy) {
|
|
402
|
+
if (!retryPolicy) {
|
|
403
|
+
return operation();
|
|
404
|
+
}
|
|
405
|
+
const maxAttempts = Math.max(1, Math.trunc(retryPolicy.maxAttempts ?? 3));
|
|
406
|
+
let lastError;
|
|
407
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
408
|
+
try {
|
|
409
|
+
return await operation();
|
|
410
|
+
} catch (error) {
|
|
411
|
+
lastError = error;
|
|
412
|
+
const normalized = toError(error);
|
|
413
|
+
const shouldRetry = retryPolicy.shouldRetry?.(normalized, attempt) ?? attempt < maxAttempts;
|
|
414
|
+
if (!shouldRetry || attempt === maxAttempts) {
|
|
415
|
+
throw normalized;
|
|
416
|
+
}
|
|
417
|
+
await sleep(computeRetryDelay(retryPolicy, attempt));
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
throw toError(lastError);
|
|
242
421
|
}
|
|
243
422
|
function cx(...names) {
|
|
244
423
|
const value = names.filter(Boolean).join(" ").trim();
|
|
@@ -292,10 +471,78 @@ function clearPersistedState(persistence) {
|
|
|
292
471
|
}
|
|
293
472
|
storage.removeItem(persistence.key);
|
|
294
473
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
474
|
+
var FlowValidationError = class extends Error {
|
|
475
|
+
constructor(message, fieldErrors) {
|
|
476
|
+
super(message);
|
|
477
|
+
this.name = "FlowValidationError";
|
|
478
|
+
this.fieldErrors = fieldErrors;
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
function toValidationError(issue, fallbackField) {
|
|
482
|
+
const field = issue.field ?? fallbackField;
|
|
483
|
+
if (field) {
|
|
484
|
+
return new FlowValidationError(issue.message, { [field]: issue.message });
|
|
485
|
+
}
|
|
486
|
+
return new FlowValidationError(issue.message, {});
|
|
487
|
+
}
|
|
488
|
+
function normalizeSchemaIssues(error, fallbackField) {
|
|
489
|
+
const fieldErrors = {};
|
|
490
|
+
const issues = error?.issues ?? [];
|
|
491
|
+
for (const issue of issues) {
|
|
492
|
+
const path = issue.path?.length ? issue.path.map(String).join(".") : fallbackField;
|
|
493
|
+
const message = issue.message?.trim() || error?.message || "Validation failed.";
|
|
494
|
+
if (path && !fieldErrors[path]) {
|
|
495
|
+
fieldErrors[path] = message;
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (Object.keys(fieldErrors).length === 0 && fallbackField) {
|
|
499
|
+
fieldErrors[fallbackField] = error?.message || "Validation failed.";
|
|
500
|
+
}
|
|
501
|
+
return fieldErrors;
|
|
502
|
+
}
|
|
503
|
+
async function assertValid(result, fallbackField) {
|
|
504
|
+
const outcome = await result;
|
|
505
|
+
if (!outcome) {
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
if (typeof outcome === "string") {
|
|
509
|
+
const message = outcome.trim();
|
|
510
|
+
if (message.length > 0) {
|
|
511
|
+
throw new FlowValidationError(message, fallbackField ? { [fallbackField]: message } : {});
|
|
512
|
+
}
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (outcome.message.trim().length > 0) {
|
|
516
|
+
throw toValidationError(outcome, fallbackField);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async function assertSchema(schema, value, fallbackField) {
|
|
520
|
+
if (!schema) {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
if (schema.safeParse) {
|
|
524
|
+
const result = schema.safeParse(value);
|
|
525
|
+
if (!result.success) {
|
|
526
|
+
const fieldErrors = normalizeSchemaIssues(result.error, fallbackField);
|
|
527
|
+
throw new FlowValidationError(result.error?.message || "Validation failed.", fieldErrors);
|
|
528
|
+
}
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (!schema.parse) {
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
schema.parse(value);
|
|
536
|
+
} catch (error) {
|
|
537
|
+
const normalized = error;
|
|
538
|
+
const fieldErrors = normalizeSchemaIssues(
|
|
539
|
+
{
|
|
540
|
+
message: normalized?.message,
|
|
541
|
+
issues: normalized?.issues
|
|
542
|
+
},
|
|
543
|
+
fallbackField
|
|
544
|
+
);
|
|
545
|
+
throw new FlowValidationError(normalized?.message || "Validation failed.", fieldErrors);
|
|
299
546
|
}
|
|
300
547
|
}
|
|
301
548
|
function resolveStyles(unstyled, styleOverrides) {
|
|
@@ -312,26 +559,22 @@ function resolveStyles(unstyled, styleOverrides) {
|
|
|
312
559
|
}
|
|
313
560
|
return resolved;
|
|
314
561
|
}
|
|
315
|
-
function
|
|
562
|
+
function useAdaptiveFlow({
|
|
316
563
|
adapter,
|
|
317
564
|
requirements,
|
|
318
565
|
requirementGraph,
|
|
319
566
|
requirementGraphConfig,
|
|
320
567
|
requirementResolvers,
|
|
321
568
|
completeStep,
|
|
322
|
-
stepTitles,
|
|
323
|
-
renderStep,
|
|
324
|
-
stepRegistry,
|
|
325
569
|
initialValue,
|
|
326
570
|
onComplete,
|
|
327
571
|
onError,
|
|
328
572
|
onStepTransition,
|
|
329
|
-
className,
|
|
330
|
-
classNames,
|
|
331
|
-
styles,
|
|
332
|
-
unstyled = false,
|
|
333
573
|
persistence,
|
|
334
|
-
validators
|
|
574
|
+
validators,
|
|
575
|
+
schemas,
|
|
576
|
+
oauthProviders,
|
|
577
|
+
retryPolicy
|
|
335
578
|
}) {
|
|
336
579
|
const normalizedRequirements = React.useMemo(
|
|
337
580
|
() => requirements ?? defaultRequirements,
|
|
@@ -365,20 +608,68 @@ function AdaptiveFlow({
|
|
|
365
608
|
requirementGraphConfig?.dependencies
|
|
366
609
|
]
|
|
367
610
|
);
|
|
368
|
-
const
|
|
611
|
+
const runtimeReducer = (state, action) => {
|
|
612
|
+
switch (action.type) {
|
|
613
|
+
case "evaluated":
|
|
614
|
+
return {
|
|
615
|
+
...state,
|
|
616
|
+
step: action.step,
|
|
617
|
+
missingRequirements: action.missingRequirements,
|
|
618
|
+
transitions: [...state.transitions, action.transition].slice(-100)
|
|
619
|
+
};
|
|
620
|
+
case "set_busy":
|
|
621
|
+
return { ...state, busy: action.busy };
|
|
622
|
+
case "set_message":
|
|
623
|
+
return { ...state, message: action.message };
|
|
624
|
+
case "set_error":
|
|
625
|
+
return { ...state, errorMessage: action.errorMessage };
|
|
626
|
+
case "set_field_errors":
|
|
627
|
+
return { ...state, fieldErrors: action.fieldErrors };
|
|
628
|
+
case "start_job":
|
|
629
|
+
return { ...state, busy: true, errorMessage: null, fieldErrors: {} };
|
|
630
|
+
case "set_oauth_pending":
|
|
631
|
+
return { ...state, oauthPendingProvider: action.provider };
|
|
632
|
+
case "set_hydrated":
|
|
633
|
+
return { ...state, persistenceHydrated: action.hydrated };
|
|
634
|
+
default:
|
|
635
|
+
return state;
|
|
636
|
+
}
|
|
637
|
+
};
|
|
369
638
|
const [context, setContext] = React.useState(() => withDefaults(initialValue));
|
|
370
|
-
const [
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
639
|
+
const [runtime, dispatch] = React.useReducer(runtimeReducer, {
|
|
640
|
+
step: normalizedCompleteStep,
|
|
641
|
+
missingRequirements: [],
|
|
642
|
+
transitions: [],
|
|
643
|
+
busy: false,
|
|
644
|
+
message: null,
|
|
645
|
+
errorMessage: null,
|
|
646
|
+
fieldErrors: {},
|
|
647
|
+
oauthPendingProvider: null,
|
|
648
|
+
persistenceHydrated: !persistence
|
|
649
|
+
});
|
|
650
|
+
const {
|
|
651
|
+
step,
|
|
652
|
+
missingRequirements,
|
|
653
|
+
transitions,
|
|
654
|
+
busy,
|
|
655
|
+
message,
|
|
656
|
+
errorMessage,
|
|
657
|
+
fieldErrors,
|
|
658
|
+
oauthPendingProvider,
|
|
659
|
+
persistenceHydrated
|
|
660
|
+
} = runtime;
|
|
378
661
|
const attemptByStepRef = React.useRef({});
|
|
379
662
|
const previousStepRef = React.useRef(null);
|
|
380
663
|
const evaluationRef = React.useRef(0);
|
|
381
664
|
const completed = React.useRef(false);
|
|
665
|
+
const reportPersistenceError = React.useCallback(
|
|
666
|
+
(error, phase) => {
|
|
667
|
+
const normalized = toError(error);
|
|
668
|
+
persistence?.onError?.(normalized, phase);
|
|
669
|
+
onError?.(normalized);
|
|
670
|
+
},
|
|
671
|
+
[onError, persistence]
|
|
672
|
+
);
|
|
382
673
|
React.useEffect(() => {
|
|
383
674
|
if (!persistence) {
|
|
384
675
|
return;
|
|
@@ -389,26 +680,24 @@ function AdaptiveFlow({
|
|
|
389
680
|
setContext(mergeContext(withDefaults(initialValue), persisted.context));
|
|
390
681
|
}
|
|
391
682
|
if (persisted?.oauthPendingProvider) {
|
|
392
|
-
|
|
683
|
+
dispatch({ type: "set_oauth_pending", provider: persisted.oauthPendingProvider });
|
|
393
684
|
}
|
|
394
|
-
} catch {
|
|
685
|
+
} catch (error) {
|
|
686
|
+
reportPersistenceError(error, "read");
|
|
395
687
|
} finally {
|
|
396
|
-
|
|
688
|
+
dispatch({ type: "set_hydrated", hydrated: true });
|
|
397
689
|
}
|
|
398
|
-
}, [initialValue, persistence]);
|
|
690
|
+
}, [initialValue, persistence, reportPersistenceError]);
|
|
399
691
|
React.useEffect(() => {
|
|
400
|
-
let isCancelled = false;
|
|
401
692
|
const currentEvaluation = ++evaluationRef.current;
|
|
402
693
|
void (async () => {
|
|
403
|
-
const [
|
|
694
|
+
const [missing, next] = await Promise.all([
|
|
404
695
|
getMissingRequirements(context, graph),
|
|
405
696
|
evaluateNextStep(context, graph, normalizedCompleteStep)
|
|
406
697
|
]);
|
|
407
|
-
if (
|
|
698
|
+
if (currentEvaluation !== evaluationRef.current) {
|
|
408
699
|
return;
|
|
409
700
|
}
|
|
410
|
-
setMissingRequirements(missing2);
|
|
411
|
-
setStep(next);
|
|
412
701
|
const from = previousStepRef.current;
|
|
413
702
|
const attemptKey = String(next);
|
|
414
703
|
const nextAttempt = from === next ? (attemptByStepRef.current[attemptKey] ?? 0) + 1 : 1;
|
|
@@ -419,20 +708,23 @@ function AdaptiveFlow({
|
|
|
419
708
|
at: Date.now(),
|
|
420
709
|
attempt: nextAttempt
|
|
421
710
|
};
|
|
422
|
-
|
|
711
|
+
dispatch({
|
|
712
|
+
type: "evaluated",
|
|
713
|
+
missingRequirements: missing,
|
|
714
|
+
step: next,
|
|
715
|
+
transition
|
|
716
|
+
});
|
|
423
717
|
previousStepRef.current = next;
|
|
424
718
|
onStepTransition?.(transition, context);
|
|
425
719
|
})().catch((error) => {
|
|
426
|
-
if (
|
|
720
|
+
if (currentEvaluation !== evaluationRef.current) {
|
|
427
721
|
return;
|
|
428
722
|
}
|
|
429
723
|
const normalized = toError(error);
|
|
430
|
-
|
|
724
|
+
dispatch({ type: "set_field_errors", fieldErrors: {} });
|
|
725
|
+
dispatch({ type: "set_error", errorMessage: normalized.message });
|
|
431
726
|
onError?.(normalized);
|
|
432
727
|
});
|
|
433
|
-
return () => {
|
|
434
|
-
isCancelled = true;
|
|
435
|
-
};
|
|
436
728
|
}, [context, graph, normalizedCompleteStep, onError, onStepTransition]);
|
|
437
729
|
React.useEffect(() => {
|
|
438
730
|
if (step === normalizedCompleteStep) {
|
|
@@ -441,34 +733,46 @@ function AdaptiveFlow({
|
|
|
441
733
|
onComplete?.(context);
|
|
442
734
|
const shouldClearPersistence = persistence?.clearOnComplete ?? true;
|
|
443
735
|
if (shouldClearPersistence) {
|
|
444
|
-
|
|
736
|
+
try {
|
|
737
|
+
clearPersistedState(persistence);
|
|
738
|
+
} catch (error) {
|
|
739
|
+
reportPersistenceError(error, "clear");
|
|
740
|
+
}
|
|
445
741
|
}
|
|
446
742
|
}
|
|
447
743
|
} else {
|
|
448
744
|
completed.current = false;
|
|
449
745
|
}
|
|
450
|
-
}, [context, normalizedCompleteStep, onComplete, persistence, step]);
|
|
746
|
+
}, [context, normalizedCompleteStep, onComplete, persistence, reportPersistenceError, step]);
|
|
451
747
|
React.useEffect(() => {
|
|
452
748
|
if (!persistence || !persistenceHydrated) {
|
|
453
749
|
return;
|
|
454
750
|
}
|
|
455
751
|
try {
|
|
456
752
|
writePersistedState(persistence, { context, oauthPendingProvider });
|
|
457
|
-
} catch {
|
|
753
|
+
} catch (error) {
|
|
754
|
+
reportPersistenceError(error, "write");
|
|
458
755
|
}
|
|
459
|
-
}, [context, oauthPendingProvider, persistence, persistenceHydrated]);
|
|
756
|
+
}, [context, oauthPendingProvider, persistence, persistenceHydrated, reportPersistenceError]);
|
|
460
757
|
const run = React.useCallback(
|
|
461
758
|
async (job) => {
|
|
462
|
-
|
|
463
|
-
setErrorMessage(null);
|
|
759
|
+
dispatch({ type: "start_job" });
|
|
464
760
|
try {
|
|
465
761
|
await job();
|
|
466
762
|
} catch (error) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
763
|
+
if (error instanceof FlowValidationError) {
|
|
764
|
+
dispatch({ type: "set_field_errors", fieldErrors: error.fieldErrors });
|
|
765
|
+
if (Object.keys(error.fieldErrors).length === 0) {
|
|
766
|
+
dispatch({ type: "set_error", errorMessage: error.message });
|
|
767
|
+
onError?.(error);
|
|
768
|
+
}
|
|
769
|
+
} else {
|
|
770
|
+
const normalized = toError(error);
|
|
771
|
+
dispatch({ type: "set_error", errorMessage: normalized.message });
|
|
772
|
+
onError?.(normalized);
|
|
773
|
+
}
|
|
470
774
|
} finally {
|
|
471
|
-
|
|
775
|
+
dispatch({ type: "set_busy", busy: false });
|
|
472
776
|
}
|
|
473
777
|
},
|
|
474
778
|
[onError]
|
|
@@ -476,37 +780,35 @@ function AdaptiveFlow({
|
|
|
476
780
|
const patchContext = React.useCallback((patch) => {
|
|
477
781
|
setContext((prev) => mergeContext(prev, patch));
|
|
478
782
|
}, []);
|
|
479
|
-
const patchBaseContext = React.useCallback(
|
|
480
|
-
(patch) => {
|
|
481
|
-
patchContext(patch);
|
|
482
|
-
},
|
|
483
|
-
[patchContext]
|
|
484
|
-
);
|
|
485
783
|
React.useEffect(() => {
|
|
486
784
|
const completeOAuth = adapter?.completeOAuth;
|
|
487
785
|
if (!oauthPendingProvider || !completeOAuth) {
|
|
488
786
|
return;
|
|
489
787
|
}
|
|
490
788
|
void run(async () => {
|
|
491
|
-
const patch = await
|
|
789
|
+
const patch = await withRetry(
|
|
790
|
+
() => completeOAuth(oauthPendingProvider, context),
|
|
791
|
+
retryPolicy
|
|
792
|
+
);
|
|
492
793
|
if (patch) {
|
|
493
794
|
patchContext(patch);
|
|
494
795
|
}
|
|
495
|
-
|
|
496
|
-
|
|
796
|
+
dispatch({ type: "set_oauth_pending", provider: null });
|
|
797
|
+
dispatch({ type: "set_message", message: "OAuth sign-in completed." });
|
|
497
798
|
});
|
|
498
|
-
}, [adapter, context, oauthPendingProvider, patchContext, run]);
|
|
799
|
+
}, [adapter, context, oauthPendingProvider, patchContext, retryPolicy, run]);
|
|
499
800
|
const handleEmail = (emailInput) => {
|
|
500
801
|
const email = emailInput.trim().toLowerCase();
|
|
501
802
|
if (!email) {
|
|
502
803
|
return;
|
|
503
804
|
}
|
|
504
805
|
void run(async () => {
|
|
806
|
+
await assertSchema(schemas?.email, email, "email");
|
|
505
807
|
if (validators?.email) {
|
|
506
|
-
await assertValid(validators.email(email, { context }));
|
|
808
|
+
await assertValid(validators.email(email, { context }), "email");
|
|
507
809
|
}
|
|
508
|
-
const identity = await adapter
|
|
509
|
-
|
|
810
|
+
const identity = adapter?.lookupByEmail ? await withRetry(() => adapter.lookupByEmail(email), retryPolicy) : null;
|
|
811
|
+
patchContext({
|
|
510
812
|
email,
|
|
511
813
|
hasPassword: Boolean(identity?.hasPassword),
|
|
512
814
|
isVerified: Boolean(identity?.isVerified),
|
|
@@ -518,15 +820,23 @@ function AdaptiveFlow({
|
|
|
518
820
|
}
|
|
519
821
|
});
|
|
520
822
|
if (identity?.accountExists && identity.hasPassword) {
|
|
521
|
-
|
|
823
|
+
dispatch({ type: "set_message", message: "Welcome back. Enter your password to continue." });
|
|
522
824
|
return;
|
|
523
825
|
}
|
|
524
826
|
if (adapter?.requestOtp) {
|
|
525
|
-
await adapter.requestOtp(email);
|
|
526
|
-
|
|
827
|
+
await withRetry(() => adapter.requestOtp(email), retryPolicy);
|
|
828
|
+
dispatch({ type: "set_message", message: "We sent a 6-digit code to your inbox." });
|
|
527
829
|
} else {
|
|
528
|
-
|
|
529
|
-
|
|
830
|
+
const env = globalThis.process?.env?.NODE_ENV;
|
|
831
|
+
const isDev = env !== "production";
|
|
832
|
+
if (!isDev) {
|
|
833
|
+
throw new Error("OTP adapter is required in production. Provide adapter.requestOtp.");
|
|
834
|
+
}
|
|
835
|
+
patchContext({ isVerified: true });
|
|
836
|
+
dispatch({
|
|
837
|
+
type: "set_message",
|
|
838
|
+
message: "No OTP adapter configured. Verification was skipped in development mode."
|
|
839
|
+
});
|
|
530
840
|
}
|
|
531
841
|
});
|
|
532
842
|
};
|
|
@@ -535,14 +845,15 @@ function AdaptiveFlow({
|
|
|
535
845
|
return;
|
|
536
846
|
}
|
|
537
847
|
void run(async () => {
|
|
848
|
+
await assertSchema(schemas?.otp, code, "otp");
|
|
538
849
|
if (validators?.otp) {
|
|
539
|
-
await assertValid(validators.otp(code, { context, email: context.email }));
|
|
850
|
+
await assertValid(validators.otp(code, { context, email: context.email }), "otp");
|
|
540
851
|
}
|
|
541
852
|
if (adapter?.verifyOtp) {
|
|
542
|
-
await adapter.verifyOtp(context.email, code);
|
|
853
|
+
await withRetry(() => adapter.verifyOtp(context.email, code), retryPolicy);
|
|
543
854
|
}
|
|
544
|
-
|
|
545
|
-
|
|
855
|
+
patchContext({ isVerified: true });
|
|
856
|
+
dispatch({ type: "set_message", message: "Email verified." });
|
|
546
857
|
});
|
|
547
858
|
};
|
|
548
859
|
const handlePassword = (password) => {
|
|
@@ -550,26 +861,34 @@ function AdaptiveFlow({
|
|
|
550
861
|
return;
|
|
551
862
|
}
|
|
552
863
|
void run(async () => {
|
|
864
|
+
await assertSchema(schemas?.password, password, "password");
|
|
553
865
|
if (validators?.password) {
|
|
554
|
-
await assertValid(
|
|
866
|
+
await assertValid(
|
|
867
|
+
validators.password(password, { context, hasPassword: context.hasPassword }),
|
|
868
|
+
"password"
|
|
869
|
+
);
|
|
555
870
|
}
|
|
556
871
|
if (context.hasPassword) {
|
|
557
872
|
if (adapter?.signInWithPassword) {
|
|
558
|
-
await
|
|
873
|
+
await withRetry(
|
|
874
|
+
() => adapter.signInWithPassword(context.email, password),
|
|
875
|
+
retryPolicy
|
|
876
|
+
);
|
|
559
877
|
}
|
|
560
878
|
} else {
|
|
561
879
|
if (adapter?.createPassword) {
|
|
562
|
-
await adapter.createPassword(password);
|
|
880
|
+
await withRetry(() => adapter.createPassword(password), retryPolicy);
|
|
563
881
|
}
|
|
564
|
-
|
|
882
|
+
patchContext({ hasPassword: true });
|
|
565
883
|
}
|
|
566
|
-
|
|
884
|
+
dispatch({ type: "set_message", message: "Password step complete." });
|
|
567
885
|
});
|
|
568
886
|
};
|
|
569
887
|
const handleProfile = (profile) => {
|
|
570
888
|
void run(async () => {
|
|
889
|
+
await assertSchema(schemas?.profile, profile, "profile");
|
|
571
890
|
if (validators?.profile) {
|
|
572
|
-
await assertValid(validators.profile(profile, { context }));
|
|
891
|
+
await assertValid(validators.profile(profile, { context }), "profile");
|
|
573
892
|
}
|
|
574
893
|
const next = mergeContext(context, {
|
|
575
894
|
profile: {
|
|
@@ -579,23 +898,24 @@ function AdaptiveFlow({
|
|
|
579
898
|
}
|
|
580
899
|
});
|
|
581
900
|
if (adapter?.saveProfile) {
|
|
582
|
-
await adapter.saveProfile(next);
|
|
901
|
+
await withRetry(() => adapter.saveProfile(next), retryPolicy);
|
|
583
902
|
}
|
|
584
903
|
patchContext({ profile: next.profile });
|
|
585
|
-
|
|
904
|
+
dispatch({ type: "set_message", message: "Profile saved." });
|
|
586
905
|
});
|
|
587
906
|
};
|
|
588
907
|
const handleTos = () => {
|
|
589
908
|
void run(async () => {
|
|
909
|
+
await assertSchema(schemas?.tos, true, "tos");
|
|
590
910
|
if (validators?.tos) {
|
|
591
|
-
await assertValid(validators.tos(true, { context }));
|
|
911
|
+
await assertValid(validators.tos(true, { context }), "tos");
|
|
592
912
|
}
|
|
593
913
|
const next = mergeContext(context, { agreedToTos: true });
|
|
594
914
|
if (adapter?.acceptTos) {
|
|
595
|
-
await adapter.acceptTos(next);
|
|
915
|
+
await withRetry(() => adapter.acceptTos(next), retryPolicy);
|
|
596
916
|
}
|
|
597
|
-
|
|
598
|
-
|
|
917
|
+
patchContext({ agreedToTos: true });
|
|
918
|
+
dispatch({ type: "set_message", message: "Terms accepted." });
|
|
599
919
|
});
|
|
600
920
|
};
|
|
601
921
|
const handleOAuth = (provider) => {
|
|
@@ -604,12 +924,97 @@ function AdaptiveFlow({
|
|
|
604
924
|
return;
|
|
605
925
|
}
|
|
606
926
|
void run(async () => {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
await startOAuth(provider, context);
|
|
927
|
+
dispatch({ type: "set_oauth_pending", provider });
|
|
928
|
+
dispatch({ type: "set_message", message: `Starting ${provider} sign-in...` });
|
|
929
|
+
await withRetry(() => startOAuth(provider, context), retryPolicy);
|
|
610
930
|
});
|
|
611
931
|
};
|
|
612
|
-
|
|
932
|
+
return {
|
|
933
|
+
context,
|
|
934
|
+
step,
|
|
935
|
+
completeStep: normalizedCompleteStep,
|
|
936
|
+
requirements: normalizedRequirements,
|
|
937
|
+
missingRequirements,
|
|
938
|
+
transitions,
|
|
939
|
+
busy,
|
|
940
|
+
message,
|
|
941
|
+
errorMessage,
|
|
942
|
+
fieldErrors,
|
|
943
|
+
setContextPatch: patchContext,
|
|
944
|
+
run,
|
|
945
|
+
handleEmail,
|
|
946
|
+
handleOtp,
|
|
947
|
+
handlePassword,
|
|
948
|
+
handleProfile,
|
|
949
|
+
handleTos,
|
|
950
|
+
handleOAuth
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
function AdaptiveFlow({
|
|
954
|
+
adapter,
|
|
955
|
+
requirements,
|
|
956
|
+
requirementGraph,
|
|
957
|
+
requirementGraphConfig,
|
|
958
|
+
requirementResolvers,
|
|
959
|
+
completeStep,
|
|
960
|
+
stepTitles,
|
|
961
|
+
renderStep,
|
|
962
|
+
stepRegistry,
|
|
963
|
+
initialValue,
|
|
964
|
+
onComplete,
|
|
965
|
+
onError,
|
|
966
|
+
onStepTransition,
|
|
967
|
+
className,
|
|
968
|
+
classNames,
|
|
969
|
+
styles,
|
|
970
|
+
unstyled = false,
|
|
971
|
+
persistence,
|
|
972
|
+
validators,
|
|
973
|
+
schemas,
|
|
974
|
+
oauthProviders,
|
|
975
|
+
retryPolicy
|
|
976
|
+
}) {
|
|
977
|
+
const uiStyles = React.useMemo(() => resolveStyles(unstyled, styles), [unstyled, styles]);
|
|
978
|
+
const {
|
|
979
|
+
context,
|
|
980
|
+
step,
|
|
981
|
+
completeStep: normalizedCompleteStep,
|
|
982
|
+
requirements: normalizedRequirements,
|
|
983
|
+
missingRequirements: missing,
|
|
984
|
+
transitions,
|
|
985
|
+
busy,
|
|
986
|
+
message,
|
|
987
|
+
errorMessage,
|
|
988
|
+
fieldErrors,
|
|
989
|
+
setContextPatch: patchContext,
|
|
990
|
+
run,
|
|
991
|
+
handleEmail,
|
|
992
|
+
handleOtp,
|
|
993
|
+
handlePassword,
|
|
994
|
+
handleProfile,
|
|
995
|
+
handleTos,
|
|
996
|
+
handleOAuth
|
|
997
|
+
} = useAdaptiveFlow({
|
|
998
|
+
adapter,
|
|
999
|
+
requirements,
|
|
1000
|
+
requirementGraph,
|
|
1001
|
+
requirementGraphConfig,
|
|
1002
|
+
requirementResolvers,
|
|
1003
|
+
completeStep,
|
|
1004
|
+
initialValue,
|
|
1005
|
+
onComplete,
|
|
1006
|
+
onError,
|
|
1007
|
+
onStepTransition,
|
|
1008
|
+
persistence,
|
|
1009
|
+
validators,
|
|
1010
|
+
schemas,
|
|
1011
|
+
oauthProviders,
|
|
1012
|
+
retryPolicy
|
|
1013
|
+
});
|
|
1014
|
+
const normalizedOAuthProviders = React.useMemo(
|
|
1015
|
+
() => oauthProviders && oauthProviders.length > 0 ? oauthProviders : defaultOAuthProviders,
|
|
1016
|
+
[oauthProviders]
|
|
1017
|
+
);
|
|
613
1018
|
const needsJobTitle = normalizedRequirements.includes("has_job_title");
|
|
614
1019
|
const stepLabel = stepTitles?.[step] ?? defaultStepTitle[step] ?? step;
|
|
615
1020
|
const defaultView = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -618,6 +1023,7 @@ function AdaptiveFlow({
|
|
|
618
1023
|
{
|
|
619
1024
|
onSubmit: handleEmail,
|
|
620
1025
|
disabled: busy,
|
|
1026
|
+
error: fieldErrors.email,
|
|
621
1027
|
styles: uiStyles,
|
|
622
1028
|
classNames
|
|
623
1029
|
}
|
|
@@ -628,6 +1034,7 @@ function AdaptiveFlow({
|
|
|
628
1034
|
onSubmit: handleOtp,
|
|
629
1035
|
email: context.email ?? "your email",
|
|
630
1036
|
disabled: busy,
|
|
1037
|
+
error: fieldErrors.otp,
|
|
631
1038
|
styles: uiStyles,
|
|
632
1039
|
classNames
|
|
633
1040
|
}
|
|
@@ -638,6 +1045,7 @@ function AdaptiveFlow({
|
|
|
638
1045
|
onSubmit: handlePassword,
|
|
639
1046
|
disabled: busy,
|
|
640
1047
|
hasPassword: context.hasPassword,
|
|
1048
|
+
error: fieldErrors.password,
|
|
641
1049
|
styles: uiStyles,
|
|
642
1050
|
classNames
|
|
643
1051
|
}
|
|
@@ -649,6 +1057,7 @@ function AdaptiveFlow({
|
|
|
649
1057
|
requireJobTitle: needsJobTitle,
|
|
650
1058
|
onSubmit: handleProfile,
|
|
651
1059
|
disabled: busy,
|
|
1060
|
+
errors: fieldErrors,
|
|
652
1061
|
styles: uiStyles,
|
|
653
1062
|
classNames
|
|
654
1063
|
}
|
|
@@ -658,12 +1067,13 @@ function AdaptiveFlow({
|
|
|
658
1067
|
{
|
|
659
1068
|
onSubmit: handleTos,
|
|
660
1069
|
disabled: busy,
|
|
1070
|
+
error: fieldErrors.tos,
|
|
661
1071
|
styles: uiStyles,
|
|
662
1072
|
classNames
|
|
663
1073
|
}
|
|
664
1074
|
) : null,
|
|
665
1075
|
step === normalizedCompleteStep ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CompleteBlock, { styles: uiStyles, classNames }) : null,
|
|
666
|
-
step
|
|
1076
|
+
!builtInDefaultSteps.has(String(step)) && step !== normalizedCompleteStep ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: classNames?.info, style: uiStyles.info, children: [
|
|
667
1077
|
'No default renderer for step "',
|
|
668
1078
|
step,
|
|
669
1079
|
'". Provide renderStep to handle custom steps.'
|
|
@@ -675,6 +1085,7 @@ function AdaptiveFlow({
|
|
|
675
1085
|
busy,
|
|
676
1086
|
message,
|
|
677
1087
|
errorMessage,
|
|
1088
|
+
fieldErrors,
|
|
678
1089
|
missingRequirements: missing,
|
|
679
1090
|
requirements: normalizedRequirements,
|
|
680
1091
|
defaultView,
|
|
@@ -689,6 +1100,7 @@ function AdaptiveFlow({
|
|
|
689
1100
|
busy,
|
|
690
1101
|
message,
|
|
691
1102
|
errorMessage,
|
|
1103
|
+
fieldErrors,
|
|
692
1104
|
missingRequirements: missing,
|
|
693
1105
|
requirements: normalizedRequirements,
|
|
694
1106
|
setContextPatch: patchContext,
|
|
@@ -711,38 +1123,31 @@ function AdaptiveFlow({
|
|
|
711
1123
|
message ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: classNames?.success, style: uiStyles.success, children: message }) : null,
|
|
712
1124
|
errorMessage ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: classNames?.error, style: uiStyles.error, children: errorMessage }) : null,
|
|
713
1125
|
customView ?? registryView ?? defaultView,
|
|
714
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
729
|
-
"button",
|
|
730
|
-
{
|
|
731
|
-
type: "button",
|
|
732
|
-
className: classNames?.oauthButton,
|
|
733
|
-
style: uiStyles.oauthButton,
|
|
734
|
-
disabled: busy || !adapter?.startOAuth,
|
|
735
|
-
onClick: () => {
|
|
736
|
-
handleOAuth("apple");
|
|
737
|
-
},
|
|
738
|
-
children: "Continue with Apple"
|
|
739
|
-
}
|
|
740
|
-
)
|
|
741
|
-
] })
|
|
1126
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: classNames?.footer, style: uiStyles.footer, children: normalizedOAuthProviders.map((provider) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1127
|
+
"button",
|
|
1128
|
+
{
|
|
1129
|
+
type: "button",
|
|
1130
|
+
className: classNames?.oauthButton,
|
|
1131
|
+
style: uiStyles.oauthButton,
|
|
1132
|
+
disabled: busy || !adapter?.startOAuth,
|
|
1133
|
+
onClick: () => {
|
|
1134
|
+
handleOAuth(provider.id);
|
|
1135
|
+
},
|
|
1136
|
+
children: provider.label
|
|
1137
|
+
},
|
|
1138
|
+
provider.id
|
|
1139
|
+
)) })
|
|
742
1140
|
] });
|
|
743
1141
|
}
|
|
744
|
-
function EmailBlock({
|
|
1142
|
+
function EmailBlock({
|
|
1143
|
+
onSubmit,
|
|
1144
|
+
disabled,
|
|
1145
|
+
error,
|
|
1146
|
+
styles,
|
|
1147
|
+
classNames
|
|
1148
|
+
}) {
|
|
745
1149
|
const [email, setEmail] = React.useState("");
|
|
1150
|
+
const errorId = "adaptive-flow-email-error";
|
|
746
1151
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
747
1152
|
"form",
|
|
748
1153
|
{
|
|
@@ -763,10 +1168,13 @@ function EmailBlock({ onSubmit, disabled, styles, classNames }) {
|
|
|
763
1168
|
placeholder: "Enter your email",
|
|
764
1169
|
value: email,
|
|
765
1170
|
onChange: (event) => setEmail(event.target.value),
|
|
1171
|
+
"aria-invalid": Boolean(error),
|
|
1172
|
+
"aria-describedby": error ? errorId : void 0,
|
|
766
1173
|
required: true
|
|
767
1174
|
}
|
|
768
1175
|
),
|
|
769
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" })
|
|
1176
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" }),
|
|
1177
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: errorId, className: classNames?.error, style: styles.error, children: error }) : null
|
|
770
1178
|
]
|
|
771
1179
|
}
|
|
772
1180
|
);
|
|
@@ -775,10 +1183,12 @@ function OtpBlock({
|
|
|
775
1183
|
onSubmit,
|
|
776
1184
|
disabled,
|
|
777
1185
|
email,
|
|
1186
|
+
error,
|
|
778
1187
|
styles,
|
|
779
1188
|
classNames
|
|
780
1189
|
}) {
|
|
781
1190
|
const [code, setCode] = React.useState("");
|
|
1191
|
+
const errorId = "adaptive-flow-otp-error";
|
|
782
1192
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
783
1193
|
"form",
|
|
784
1194
|
{
|
|
@@ -804,6 +1214,8 @@ function OtpBlock({
|
|
|
804
1214
|
placeholder: "Enter 6-digit code",
|
|
805
1215
|
value: code,
|
|
806
1216
|
onChange: (event) => setCode(event.target.value.replace(/\D/g, "").slice(0, 6)),
|
|
1217
|
+
"aria-invalid": Boolean(error),
|
|
1218
|
+
"aria-describedby": error ? errorId : void 0,
|
|
807
1219
|
required: true,
|
|
808
1220
|
maxLength: 6,
|
|
809
1221
|
pattern: "[0-9]{6}"
|
|
@@ -818,7 +1230,8 @@ function OtpBlock({
|
|
|
818
1230
|
type: "submit",
|
|
819
1231
|
children: "Verify"
|
|
820
1232
|
}
|
|
821
|
-
)
|
|
1233
|
+
),
|
|
1234
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: errorId, className: classNames?.error, style: styles.error, children: error }) : null
|
|
822
1235
|
] })
|
|
823
1236
|
]
|
|
824
1237
|
}
|
|
@@ -828,10 +1241,12 @@ function PasswordBlock({
|
|
|
828
1241
|
onSubmit,
|
|
829
1242
|
disabled,
|
|
830
1243
|
hasPassword,
|
|
1244
|
+
error,
|
|
831
1245
|
styles,
|
|
832
1246
|
classNames
|
|
833
1247
|
}) {
|
|
834
1248
|
const [password, setPassword] = React.useState("");
|
|
1249
|
+
const errorId = "adaptive-flow-password-error";
|
|
835
1250
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
836
1251
|
"form",
|
|
837
1252
|
{
|
|
@@ -852,11 +1267,14 @@ function PasswordBlock({
|
|
|
852
1267
|
placeholder: hasPassword ? "Enter your password" : "Create a password",
|
|
853
1268
|
value: password,
|
|
854
1269
|
onChange: (event) => setPassword(event.target.value),
|
|
1270
|
+
"aria-invalid": Boolean(error),
|
|
1271
|
+
"aria-describedby": error ? errorId : void 0,
|
|
855
1272
|
required: true,
|
|
856
1273
|
minLength: 8
|
|
857
1274
|
}
|
|
858
1275
|
),
|
|
859
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" })
|
|
1276
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" }),
|
|
1277
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { id: errorId, className: classNames?.error, style: styles.error, children: error }) : null
|
|
860
1278
|
]
|
|
861
1279
|
}
|
|
862
1280
|
);
|
|
@@ -866,12 +1284,17 @@ function ProfileBlock({
|
|
|
866
1284
|
disabled,
|
|
867
1285
|
defaultValue,
|
|
868
1286
|
requireJobTitle,
|
|
1287
|
+
errors,
|
|
869
1288
|
styles,
|
|
870
1289
|
classNames
|
|
871
1290
|
}) {
|
|
872
1291
|
const [firstName, setFirstName] = React.useState(defaultValue.firstName ?? "");
|
|
873
1292
|
const [lastName, setLastName] = React.useState(defaultValue.lastName ?? "");
|
|
874
1293
|
const [jobTitle, setJobTitle] = React.useState(defaultValue.jobTitle ?? "");
|
|
1294
|
+
const firstNameError = errors?.firstName ?? errors?.["profile.firstName"];
|
|
1295
|
+
const lastNameError = errors?.lastName ?? errors?.["profile.lastName"];
|
|
1296
|
+
const jobTitleError = errors?.jobTitle ?? errors?.["profile.jobTitle"];
|
|
1297
|
+
const profileError = errors?.profile;
|
|
875
1298
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
876
1299
|
"form",
|
|
877
1300
|
{
|
|
@@ -897,6 +1320,7 @@ function ProfileBlock({
|
|
|
897
1320
|
placeholder: "First name",
|
|
898
1321
|
value: firstName,
|
|
899
1322
|
onChange: (event) => setFirstName(event.target.value),
|
|
1323
|
+
"aria-invalid": Boolean(firstNameError),
|
|
900
1324
|
required: true
|
|
901
1325
|
}
|
|
902
1326
|
),
|
|
@@ -910,23 +1334,31 @@ function ProfileBlock({
|
|
|
910
1334
|
placeholder: "Last name",
|
|
911
1335
|
value: lastName,
|
|
912
1336
|
onChange: (event) => setLastName(event.target.value),
|
|
1337
|
+
"aria-invalid": Boolean(lastNameError),
|
|
913
1338
|
required: true
|
|
914
1339
|
}
|
|
915
1340
|
)
|
|
916
1341
|
] }),
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
1342
|
+
firstNameError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: classNames?.error, style: styles.error, children: firstNameError }) : null,
|
|
1343
|
+
lastNameError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: classNames?.error, style: styles.error, children: lastNameError }) : null,
|
|
1344
|
+
requireJobTitle ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
1345
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1346
|
+
"input",
|
|
1347
|
+
{
|
|
1348
|
+
className: classNames?.input,
|
|
1349
|
+
style: styles.input,
|
|
1350
|
+
type: "text",
|
|
1351
|
+
autoComplete: "organization-title",
|
|
1352
|
+
placeholder: "Job title",
|
|
1353
|
+
value: jobTitle,
|
|
1354
|
+
onChange: (event) => setJobTitle(event.target.value),
|
|
1355
|
+
"aria-invalid": Boolean(jobTitleError),
|
|
1356
|
+
required: true
|
|
1357
|
+
}
|
|
1358
|
+
),
|
|
1359
|
+
jobTitleError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: classNames?.error, style: styles.error, children: jobTitleError }) : null
|
|
1360
|
+
] }) : null,
|
|
1361
|
+
profileError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: classNames?.error, style: styles.error, children: profileError }) : null,
|
|
930
1362
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: classNames?.button, style: styles.button, disabled, type: "submit", children: "Continue" })
|
|
931
1363
|
]
|
|
932
1364
|
}
|
|
@@ -935,6 +1367,7 @@ function ProfileBlock({
|
|
|
935
1367
|
function TosBlock({
|
|
936
1368
|
onSubmit,
|
|
937
1369
|
disabled,
|
|
1370
|
+
error,
|
|
938
1371
|
styles,
|
|
939
1372
|
classNames
|
|
940
1373
|
}) {
|
|
@@ -952,9 +1385,18 @@ function TosBlock({
|
|
|
952
1385
|
},
|
|
953
1386
|
children: [
|
|
954
1387
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: classNames?.checkboxRow, style: styles.checkboxRow, children: [
|
|
955
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1388
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1389
|
+
"input",
|
|
1390
|
+
{
|
|
1391
|
+
type: "checkbox",
|
|
1392
|
+
checked,
|
|
1393
|
+
onChange: (event) => setChecked(event.target.checked),
|
|
1394
|
+
"aria-invalid": Boolean(error)
|
|
1395
|
+
}
|
|
1396
|
+
),
|
|
956
1397
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: "I agree to the Terms of Service and Privacy Policy." })
|
|
957
1398
|
] }),
|
|
1399
|
+
error ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: classNames?.error, style: styles.error, children: error }) : null,
|
|
958
1400
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
959
1401
|
"button",
|
|
960
1402
|
{
|
|
@@ -974,7 +1416,7 @@ function CompleteBlock({
|
|
|
974
1416
|
classNames
|
|
975
1417
|
}) {
|
|
976
1418
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: classNames?.complete, style: styles.complete, children: [
|
|
977
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "You are
|
|
1419
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "You are all set." }),
|
|
978
1420
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: classNames?.caption, style: styles.caption, children: "All requirements are complete." })
|
|
979
1421
|
] });
|
|
980
1422
|
}
|
|
@@ -1113,6 +1555,7 @@ var defaultStyles = {
|
|
|
1113
1555
|
defaultRequirements,
|
|
1114
1556
|
evaluateNextStep,
|
|
1115
1557
|
getMissingRequirements,
|
|
1116
|
-
initialContext
|
|
1558
|
+
initialContext,
|
|
1559
|
+
useAdaptiveFlow
|
|
1117
1560
|
});
|
|
1118
1561
|
//# sourceMappingURL=index.js.map
|