From af9e2423780d677f541bbc9cac57a1dd1e44c1a2 Mon Sep 17 00:00:00 2001 From: AJ Ancheta <7781450+ancheetah@users.noreply.github.com> Date: Mon, 11 May 2026 11:17:12 -0400 Subject: [PATCH] feat(davinci-client): support single checkbox component --- .gitignore | 1 + e2e/davinci-app/components/boolean.ts | 57 +++++++++ e2e/davinci-app/main.ts | 3 + .../api-report/davinci-client.api.md | 112 ++++++++++++++---- .../api-report/davinci-client.types.api.md | 112 ++++++++++++++---- .../davinci-client/src/lib/client.store.ts | 1 + .../davinci-client/src/lib/client.types.ts | 42 +++---- .../src/lib/collector.types.test-d.ts | 5 +- .../davinci-client/src/lib/collector.types.ts | 53 +++++---- .../davinci-client/src/lib/collector.utils.ts | 53 ++++++++- .../davinci-client/src/lib/davinci.types.ts | 33 ++++-- .../davinci-client/src/lib/node.reducer.ts | 15 +++ .../src/lib/node.types.test-d.ts | 2 + packages/davinci-client/src/lib/node.types.ts | 2 + packages/davinci-client/tsconfig.json | 18 --- 15 files changed, 394 insertions(+), 115 deletions(-) create mode 100644 e2e/davinci-app/components/boolean.ts diff --git a/.gitignore b/.gitignore index 327a931c1a..79b099667e 100644 --- a/.gitignore +++ b/.gitignore @@ -94,6 +94,7 @@ GEMINI.md .claude/worktrees .claude/settings.local.json +.claude/skills .opensource # Polaris diff --git a/e2e/davinci-app/components/boolean.ts b/e2e/davinci-app/components/boolean.ts new file mode 100644 index 0000000000..53bd1d7ddc --- /dev/null +++ b/e2e/davinci-app/components/boolean.ts @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026 Ping Identity Corporation. All rights reserved. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ +import type { ValidatedBooleanCollector, Updater } from '@forgerock/davinci-client/types'; + +/** + * Creates a single checkbox and attaches it to the form + * @param {HTMLFormElement} formEl - The form element to attach the checkboxes to + * @param {ValidatedBooleanCollector} collector - Contains the configuration + * @param {Updater} updater - Function to call when selection changes + */ +export default function booleanComponent( + formEl: HTMLFormElement, + collector: ValidatedBooleanCollector, + updater: Updater, +) { + // Create a container for the checkboxes + const containerDiv = document.createElement('div'); + containerDiv.className = 'single-checkbox-container'; + + // Create a heading/label for the checkbox group + const groupLabel = document.createElement('div'); + groupLabel.textContent = collector.output.label || 'Single Checkbox'; + groupLabel.className = 'single-checkbox-label'; + containerDiv.appendChild(groupLabel); + + // Create checkboxes for each option + const wrapper = document.createElement('div'); + wrapper.className = 'checkbox-wrapper'; + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.id = collector.output.key; + checkbox.name = collector.output.key || 'single-checkbox-field'; + checkbox.checked = collector.output.value; + checkbox.value = 'checked'; + + const label = document.createElement('label'); + label.htmlFor = checkbox.id; + label.textContent = collector.output.label; + + // Add event listener to handle single-select behavior + checkbox.addEventListener('change', (event) => { + const target = event.target as HTMLInputElement; + updater(target.value === 'checked'); + }); + + wrapper.appendChild(checkbox); + wrapper.appendChild(label); + containerDiv.appendChild(wrapper); + + // Append the container to the form + formEl.appendChild(containerDiv); +} diff --git a/e2e/davinci-app/main.ts b/e2e/davinci-app/main.ts index 119a4dec6e..f984b59983 100644 --- a/e2e/davinci-app/main.ts +++ b/e2e/davinci-app/main.ts @@ -35,6 +35,7 @@ import fidoComponent from './components/fido.js'; import qrCodeComponent from './components/qr-code.js'; import agreementComponent from './components/agreement.js'; import pollingComponent from './components/polling.js'; +import booleanComponent from './components/boolean.js'; const loggerFn = { error: () => { @@ -294,6 +295,8 @@ const urlParams = new URLSearchParams(window.location.search); singleValueComponent(formEl, collector, davinciClient.update(collector)); } else if (collector.type === 'MultiSelectCollector') { multiValueComponent(formEl, collector, davinciClient.update(collector)); + } else if (collector.type === 'ValidatedBooleanCollector') { + booleanComponent(formEl, collector, davinciClient.update(collector)); } }); diff --git a/packages/davinci-client/api-report/davinci-client.api.md b/packages/davinci-client/api-report/davinci-client.api.md index b2528bf664..418fba89d6 100644 --- a/packages/davinci-client/api-report/davinci-client.api.md +++ b/packages/davinci-client/api-report/davinci-client.api.md @@ -178,7 +178,7 @@ export interface CollectorErrors { } // @public (undocumented) -export type Collectors = FlowCollector | PasswordCollector | TextCollector | SingleSelectCollector | IdpCollector | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | ReadOnlyCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | AgreementCollector | UnknownCollector; +export type Collectors = FlowCollector | PasswordCollector | TextCollector | ValidatedBooleanCollector | SingleSelectCollector | IdpCollector | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ReadOnlyCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | AgreementCollector | UnknownCollector; // @public export type CollectorValueType = T extends { @@ -194,6 +194,8 @@ export type CollectorValueType = T extends { } ? string : T extends { type: 'MultiSelectCollector'; } ? string[] : T extends { + type: 'ValidatedBooleanCollector'; +} ? boolean : T extends { type: 'DeviceRegistrationCollector'; } ? string : T extends { type: 'DeviceAuthenticationCollector'; @@ -212,7 +214,7 @@ export type CollectorValueType = T extends { } ? string[] : string | string[] | PhoneNumberInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; // @public (undocumented) -export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | FidoRegistrationField | FidoAuthenticationField | PollingField; +export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | PhoneNumberExtensionField | FidoRegistrationField | FidoAuthenticationField | PollingField; // @public (undocumented) export interface ContinueNode { @@ -270,7 +272,7 @@ export function davinci(input: { start: (options?: StartOptions | undefined) => Promise; update: (collector: T) => Updater; validate: (collector: SingleValueCollectors | ObjectValueCollectors | MultiValueCollectors | AutoCollectors) => Validator; - poll: (collector: PollingCollector) => Poller; + pollStatus: (collector: PollingCollector) => Poller; getClient: () => { status: "start"; } | { @@ -1032,10 +1034,10 @@ export type InferMultiValueCollectorType = T export type InferNoValueCollectorType = T extends 'ReadOnlyCollector' ? NoValueCollectorBase<'ReadOnlyCollector'> : T extends 'QrCodeCollector' ? QrCodeCollectorBase : T extends 'AgreementCollector' ? AgreementCollector : NoValueCollectorBase<'NoValueCollector'>; // @public -export type InferSingleValueCollectorType = T extends 'TextCollector' ? TextCollector : T extends 'SingleSelectCollector' ? SingleSelectCollector : T extends 'ValidatedTextCollector' ? ValidatedTextCollector : T extends 'PasswordCollector' ? PasswordCollector : SingleValueCollectorWithValue<'SingleValueCollector'> | SingleValueCollectorNoValue<'SingleValueCollector'>; +export type InferSingleValueCollectorType = T extends 'TextCollector' ? TextCollector : T extends 'SingleSelectCollector' ? SingleSelectCollector : T extends 'ValidatedTextCollector' ? ValidatedTextCollector : T extends 'PasswordCollector' ? PasswordCollector : T extends 'ValidatedBooleanCollector' ? ValidatedBooleanCollector : SingleValueCollectorWithValue<'SingleValueCollector'> | SingleValueCollectorNoValue<'SingleValueCollector'>; // @public (undocumented) -export type InferValueObjectCollectorType = T extends 'DeviceAuthenticationCollector' ? DeviceAuthenticationCollector : T extends 'DeviceRegistrationCollector' ? DeviceRegistrationCollector : T extends 'PhoneNumberCollector' ? PhoneNumberCollector : ObjectOptionsCollectorWithObjectValue<'ObjectValueCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectValueCollector'>; +export type InferValueObjectCollectorType = T extends 'DeviceAuthenticationCollector' ? DeviceAuthenticationCollector : T extends 'DeviceRegistrationCollector' ? DeviceRegistrationCollector : T extends 'PhoneNumberCollector' ? PhoneNumberCollector : T extends 'PhoneNumberExtensionCollector' ? PhoneNumberExtensionCollector : ObjectOptionsCollectorWithObjectValue<'ObjectValueCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectValueCollector'>; // @public (undocumented) export type InitFlow = () => Promise; @@ -1170,8 +1172,8 @@ value: Record; }, string>; // @public -export const nodeCollectorReducer: Reducer<(TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]> & { - getInitialState: () => (TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]; +export const nodeCollectorReducer: Reducer<(TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | ValidatedBooleanCollector | MultiSelectCollector | PhoneNumberExtensionCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]> & { + getInitialState: () => (TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | ValidatedBooleanCollector | MultiSelectCollector | PhoneNumberExtensionCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]; }; // @public (undocumented) @@ -1283,10 +1285,10 @@ export type ObjectValueAutoCollectorTypes = 'ObjectValueAutoCollector' | 'FidoRe export type ObjectValueCollector = ObjectOptionsCollectorWithObjectValue | ObjectOptionsCollectorWithStringValue | ObjectValueCollectorWithObjectValue; // @public (undocumented) -export type ObjectValueCollectors = DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | ObjectOptionsCollectorWithObjectValue<'ObjectSelectCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectSelectCollector'>; +export type ObjectValueCollectors = DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ObjectOptionsCollectorWithObjectValue<'ObjectSelectCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectSelectCollector'>; // @public -export type ObjectValueCollectorTypes = 'DeviceAuthenticationCollector' | 'DeviceRegistrationCollector' | 'PhoneNumberCollector' | 'ObjectOptionsCollector' | 'ObjectValueCollector' | 'ObjectSelectCollector'; +export type ObjectValueCollectorTypes = 'DeviceAuthenticationCollector' | 'DeviceRegistrationCollector' | 'PhoneNumberCollector' | 'PhoneNumberExtensionCollector' | 'ObjectOptionsCollector' | 'ObjectValueCollector' | 'ObjectSelectCollector'; // @public (undocumented) export interface ObjectValueCollectorWithObjectValue, OV = Record> { @@ -1328,13 +1330,68 @@ export type PasswordCollector = SingleValueCollectorNoValue<'PasswordCollector'> // @public (undocumented) export type PhoneNumberCollector = ObjectValueCollectorWithObjectValue<'PhoneNumberCollector', PhoneNumberInputValue, PhoneNumberOutputValue>; +// @public (undocumented) +export interface PhoneNumberExtensionCollector { + // (undocumented) + category: 'ObjectValueCollector'; + // (undocumented) + error: string | null; + // (undocumented) + id: string; + // (undocumented) + input: { + key: string; + value: PhoneNumberExtensionInputValue; + type: string; + validation: (ValidationRequired | ValidationPhoneNumber)[] | null; + }; + // (undocumented) + name: string; + // (undocumented) + output: { + key: string; + label: string; + type: string; + extensionLabel: string; + value: PhoneNumberExtensionOutputValue; + }; + // (undocumented) + type: 'PhoneNumberExtensionCollector'; +} + +// @public (undocumented) +export type PhoneNumberExtensionField = PhoneNumberField & { + showExtension: boolean; + extensionLabel: string; +}; + +// @public (undocumented) +export interface PhoneNumberExtensionInputValue { + // (undocumented) + countryCode: string; + // (undocumented) + extension: string; + // (undocumented) + phoneNumber: string; +} + +// @public (undocumented) +export interface PhoneNumberExtensionOutputValue { + // (undocumented) + countryCode?: string; + // (undocumented) + extension?: string; + // (undocumented) + phoneNumber?: string; +} + // @public (undocumented) export type PhoneNumberField = { type: 'PHONE_NUMBER'; key: string; label: string; - defaultCountryCode: string | null; required: boolean; + defaultCountryCode: string | null; validatePhoneNumber: boolean; }; @@ -1481,6 +1538,18 @@ export interface SelectorOption { value: string; } +// @public (undocumented) +export type SingleCheckboxField = { + type: 'SINGLE_CHECKBOX'; + inputType: 'BOOLEAN'; + key: string; + label: string; + required: boolean; + validation?: { + errorMessage: string; + }; +}; + // @public (undocumented) export type SingleSelectCollector = SingleSelectCollectorWithValue<'SingleSelectCollector'>; @@ -1588,13 +1657,13 @@ export interface SingleValueCollectorNoValue | SingleSelectCollectorWithValue<'SingleSelectCollector'> | SingleValueCollectorWithValue<'SingleValueCollector'> | SingleValueCollectorWithValue<'TextCollector'> | ValidatedSingleValueCollectorWithValue<'TextCollector'>; +export type SingleValueCollectors = PasswordCollector | SingleSelectCollector | TextCollector | ValidatedTextCollector | ValidatedBooleanCollector | SingleValueCollectorWithValue<'SingleValueCollector'>; // @public -export type SingleValueCollectorTypes = 'PasswordCollector' | 'SingleValueCollector' | 'SingleSelectCollector' | 'SingleSelectObjectCollector' | 'TextCollector' | 'ValidatedTextCollector'; +export type SingleValueCollectorTypes = 'PasswordCollector' | 'ValidatedBooleanCollector' | 'SingleValueCollector' | 'SingleSelectCollector' | 'SingleSelectObjectCollector' | 'TextCollector' | 'ValidatedTextCollector'; // @public (undocumented) -export interface SingleValueCollectorWithValue { +export interface SingleValueCollectorWithValue { // (undocumented) category: 'SingleValueCollector'; // (undocumented) @@ -1604,7 +1673,7 @@ export interface SingleValueCollectorWithValue; // @public (undocumented) export const updateCollectorValues: ActionCreatorWithPayload< { id: string; -value: string | string[] | PhoneNumberInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; +value: string | string[] | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; index?: number; }, string>; // @public export type Updater = (value: CollectorValueType, index?: number) => InternalErrorResponse | null; +// @public (undocumented) +export type ValidatedBooleanCollector = ValidatedSingleValueCollectorWithValue<'ValidatedBooleanCollector', boolean>; + // @public (undocumented) export type ValidatedField = { type: 'TEXT'; @@ -1744,7 +1816,7 @@ export type ValidatedField = { }; // @public (undocumented) -export interface ValidatedSingleValueCollectorWithValue { +export interface ValidatedSingleValueCollectorWithValue { // (undocumented) category: 'ValidatedSingleValueCollector'; // (undocumented) @@ -1754,9 +1826,9 @@ export interface ValidatedSingleValueCollectorWithValue | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | ReadOnlyCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | AgreementCollector | UnknownCollector; +export type Collectors = FlowCollector | PasswordCollector | TextCollector | ValidatedBooleanCollector | SingleSelectCollector | IdpCollector | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ReadOnlyCollector | ValidatedTextCollector | ProtectCollector | PollingCollector | FidoRegistrationCollector | FidoAuthenticationCollector | QrCodeCollector | AgreementCollector | UnknownCollector; // @public export type CollectorValueType = T extends { @@ -194,6 +194,8 @@ export type CollectorValueType = T extends { } ? string : T extends { type: 'MultiSelectCollector'; } ? string[] : T extends { + type: 'ValidatedBooleanCollector'; +} ? boolean : T extends { type: 'DeviceRegistrationCollector'; } ? string : T extends { type: 'DeviceAuthenticationCollector'; @@ -212,7 +214,7 @@ export type CollectorValueType = T extends { } ? string[] : string | string[] | PhoneNumberInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; // @public (undocumented) -export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | FidoRegistrationField | FidoAuthenticationField | PollingField; +export type ComplexValueFields = DeviceAuthenticationField | DeviceRegistrationField | PhoneNumberField | PhoneNumberExtensionField | FidoRegistrationField | FidoAuthenticationField | PollingField; // @public (undocumented) export interface ContinueNode { @@ -270,7 +272,7 @@ export function davinci(input: { start: (options?: StartOptions | undefined) => Promise; update: (collector: T) => Updater; validate: (collector: SingleValueCollectors | ObjectValueCollectors | MultiValueCollectors | AutoCollectors) => Validator; - poll: (collector: PollingCollector) => Poller; + pollStatus: (collector: PollingCollector) => Poller; getClient: () => { status: "start"; } | { @@ -1029,10 +1031,10 @@ export type InferMultiValueCollectorType = T export type InferNoValueCollectorType = T extends 'ReadOnlyCollector' ? NoValueCollectorBase<'ReadOnlyCollector'> : T extends 'QrCodeCollector' ? QrCodeCollectorBase : T extends 'AgreementCollector' ? AgreementCollector : NoValueCollectorBase<'NoValueCollector'>; // @public -export type InferSingleValueCollectorType = T extends 'TextCollector' ? TextCollector : T extends 'SingleSelectCollector' ? SingleSelectCollector : T extends 'ValidatedTextCollector' ? ValidatedTextCollector : T extends 'PasswordCollector' ? PasswordCollector : SingleValueCollectorWithValue<'SingleValueCollector'> | SingleValueCollectorNoValue<'SingleValueCollector'>; +export type InferSingleValueCollectorType = T extends 'TextCollector' ? TextCollector : T extends 'SingleSelectCollector' ? SingleSelectCollector : T extends 'ValidatedTextCollector' ? ValidatedTextCollector : T extends 'PasswordCollector' ? PasswordCollector : T extends 'ValidatedBooleanCollector' ? ValidatedBooleanCollector : SingleValueCollectorWithValue<'SingleValueCollector'> | SingleValueCollectorNoValue<'SingleValueCollector'>; // @public (undocumented) -export type InferValueObjectCollectorType = T extends 'DeviceAuthenticationCollector' ? DeviceAuthenticationCollector : T extends 'DeviceRegistrationCollector' ? DeviceRegistrationCollector : T extends 'PhoneNumberCollector' ? PhoneNumberCollector : ObjectOptionsCollectorWithObjectValue<'ObjectValueCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectValueCollector'>; +export type InferValueObjectCollectorType = T extends 'DeviceAuthenticationCollector' ? DeviceAuthenticationCollector : T extends 'DeviceRegistrationCollector' ? DeviceRegistrationCollector : T extends 'PhoneNumberCollector' ? PhoneNumberCollector : T extends 'PhoneNumberExtensionCollector' ? PhoneNumberExtensionCollector : ObjectOptionsCollectorWithObjectValue<'ObjectValueCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectValueCollector'>; // @public (undocumented) export type InitFlow = () => Promise; @@ -1167,8 +1169,8 @@ value: Record; }, string>; // @public -export const nodeCollectorReducer: Reducer<(TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]> & { - getInitialState: () => (TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | MultiSelectCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]; +export const nodeCollectorReducer: Reducer<(TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | ValidatedBooleanCollector | MultiSelectCollector | PhoneNumberExtensionCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]> & { + getInitialState: () => (TextCollector | SingleSelectCollector | ValidatedTextCollector | PasswordCollector | ValidatedBooleanCollector | MultiSelectCollector | PhoneNumberExtensionCollector | DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | IdpCollector | SubmitCollector | FlowCollector | QrCodeCollectorBase | AgreementCollector | ReadOnlyCollector | UnknownCollector | ProtectCollector | FidoRegistrationCollector | FidoAuthenticationCollector | PollingCollector | ActionCollector<"ActionCollector"> | SingleValueCollector<"SingleValueCollector">)[]; }; // @public (undocumented) @@ -1280,10 +1282,10 @@ export type ObjectValueAutoCollectorTypes = 'ObjectValueAutoCollector' | 'FidoRe export type ObjectValueCollector = ObjectOptionsCollectorWithObjectValue | ObjectOptionsCollectorWithStringValue | ObjectValueCollectorWithObjectValue; // @public (undocumented) -export type ObjectValueCollectors = DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | ObjectOptionsCollectorWithObjectValue<'ObjectSelectCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectSelectCollector'>; +export type ObjectValueCollectors = DeviceAuthenticationCollector | DeviceRegistrationCollector | PhoneNumberCollector | PhoneNumberExtensionCollector | ObjectOptionsCollectorWithObjectValue<'ObjectSelectCollector'> | ObjectOptionsCollectorWithStringValue<'ObjectSelectCollector'>; // @public -export type ObjectValueCollectorTypes = 'DeviceAuthenticationCollector' | 'DeviceRegistrationCollector' | 'PhoneNumberCollector' | 'ObjectOptionsCollector' | 'ObjectValueCollector' | 'ObjectSelectCollector'; +export type ObjectValueCollectorTypes = 'DeviceAuthenticationCollector' | 'DeviceRegistrationCollector' | 'PhoneNumberCollector' | 'PhoneNumberExtensionCollector' | 'ObjectOptionsCollector' | 'ObjectValueCollector' | 'ObjectSelectCollector'; // @public (undocumented) export interface ObjectValueCollectorWithObjectValue, OV = Record> { @@ -1325,13 +1327,68 @@ export type PasswordCollector = SingleValueCollectorNoValue<'PasswordCollector'> // @public (undocumented) export type PhoneNumberCollector = ObjectValueCollectorWithObjectValue<'PhoneNumberCollector', PhoneNumberInputValue, PhoneNumberOutputValue>; +// @public (undocumented) +export interface PhoneNumberExtensionCollector { + // (undocumented) + category: 'ObjectValueCollector'; + // (undocumented) + error: string | null; + // (undocumented) + id: string; + // (undocumented) + input: { + key: string; + value: PhoneNumberExtensionInputValue; + type: string; + validation: (ValidationRequired | ValidationPhoneNumber)[] | null; + }; + // (undocumented) + name: string; + // (undocumented) + output: { + key: string; + label: string; + type: string; + extensionLabel: string; + value: PhoneNumberExtensionOutputValue; + }; + // (undocumented) + type: 'PhoneNumberExtensionCollector'; +} + +// @public (undocumented) +export type PhoneNumberExtensionField = PhoneNumberField & { + showExtension: boolean; + extensionLabel: string; +}; + +// @public (undocumented) +export interface PhoneNumberExtensionInputValue { + // (undocumented) + countryCode: string; + // (undocumented) + extension: string; + // (undocumented) + phoneNumber: string; +} + +// @public (undocumented) +export interface PhoneNumberExtensionOutputValue { + // (undocumented) + countryCode?: string; + // (undocumented) + extension?: string; + // (undocumented) + phoneNumber?: string; +} + // @public (undocumented) export type PhoneNumberField = { type: 'PHONE_NUMBER'; key: string; label: string; - defaultCountryCode: string | null; required: boolean; + defaultCountryCode: string | null; validatePhoneNumber: boolean; }; @@ -1478,6 +1535,18 @@ export interface SelectorOption { value: string; } +// @public (undocumented) +export type SingleCheckboxField = { + type: 'SINGLE_CHECKBOX'; + inputType: 'BOOLEAN'; + key: string; + label: string; + required: boolean; + validation?: { + errorMessage: string; + }; +}; + // @public (undocumented) export type SingleSelectCollector = SingleSelectCollectorWithValue<'SingleSelectCollector'>; @@ -1585,13 +1654,13 @@ export interface SingleValueCollectorNoValue | SingleSelectCollectorWithValue<'SingleSelectCollector'> | SingleValueCollectorWithValue<'SingleValueCollector'> | SingleValueCollectorWithValue<'TextCollector'> | ValidatedSingleValueCollectorWithValue<'TextCollector'>; +export type SingleValueCollectors = PasswordCollector | SingleSelectCollector | TextCollector | ValidatedTextCollector | ValidatedBooleanCollector | SingleValueCollectorWithValue<'SingleValueCollector'>; // @public -export type SingleValueCollectorTypes = 'PasswordCollector' | 'SingleValueCollector' | 'SingleSelectCollector' | 'SingleSelectObjectCollector' | 'TextCollector' | 'ValidatedTextCollector'; +export type SingleValueCollectorTypes = 'PasswordCollector' | 'ValidatedBooleanCollector' | 'SingleValueCollector' | 'SingleSelectCollector' | 'SingleSelectObjectCollector' | 'TextCollector' | 'ValidatedTextCollector'; // @public (undocumented) -export interface SingleValueCollectorWithValue { +export interface SingleValueCollectorWithValue { // (undocumented) category: 'SingleValueCollector'; // (undocumented) @@ -1601,7 +1670,7 @@ export interface SingleValueCollectorWithValue; // @public (undocumented) export const updateCollectorValues: ActionCreatorWithPayload< { id: string; -value: string | string[] | PhoneNumberInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; +value: string | string[] | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue | FidoAuthenticationInputValue; index?: number; }, string>; // @public export type Updater = (value: CollectorValueType, index?: number) => InternalErrorResponse | null; +// @public (undocumented) +export type ValidatedBooleanCollector = ValidatedSingleValueCollectorWithValue<'ValidatedBooleanCollector', boolean>; + // @public (undocumented) export type ValidatedField = { type: 'TEXT'; @@ -1741,7 +1813,7 @@ export type ValidatedField = { }; // @public (undocumented) -export interface ValidatedSingleValueCollectorWithValue { +export interface ValidatedSingleValueCollectorWithValue { // (undocumented) category: 'ValidatedSingleValueCollector'; // (undocumented) @@ -1751,9 +1823,9 @@ export interface ValidatedSingleValueCollectorWithValue({ value: | string | string[] + | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue diff --git a/packages/davinci-client/src/lib/client.types.ts b/packages/davinci-client/src/lib/client.types.ts index 1830f6fd5c..757ed92043 100644 --- a/packages/davinci-client/src/lib/client.types.ts +++ b/packages/davinci-client/src/lib/client.types.ts @@ -44,28 +44,30 @@ export type CollectorValueType = T extends { type: 'PasswordCollector' } ? string : T extends { type: 'MultiSelectCollector' } ? string[] - : T extends { type: 'DeviceRegistrationCollector' } - ? string - : T extends { type: 'DeviceAuthenticationCollector' } + : T extends { type: 'ValidatedBooleanCollector' } + ? boolean + : T extends { type: 'DeviceRegistrationCollector' } ? string - : T extends { type: 'PhoneNumberCollector' } - ? PhoneNumberInputValue - : T extends { type: 'FidoRegistrationCollector' } - ? FidoRegistrationInputValue - : T extends { type: 'FidoAuthenticationCollector' } - ? FidoAuthenticationInputValue - : T extends { category: 'SingleValueCollector' } - ? string - : T extends { category: 'ValidatedSingleValueCollector' } + : T extends { type: 'DeviceAuthenticationCollector' } + ? string + : T extends { type: 'PhoneNumberCollector' } + ? PhoneNumberInputValue + : T extends { type: 'FidoRegistrationCollector' } + ? FidoRegistrationInputValue + : T extends { type: 'FidoAuthenticationCollector' } + ? FidoAuthenticationInputValue + : T extends { category: 'SingleValueCollector' } ? string - : T extends { category: 'MultiValueCollector' } - ? string[] - : - | string - | string[] - | PhoneNumberInputValue - | FidoRegistrationInputValue - | FidoAuthenticationInputValue; + : T extends { category: 'ValidatedSingleValueCollector' } + ? string + : T extends { category: 'MultiValueCollector' } + ? string[] + : + | string + | string[] + | PhoneNumberInputValue + | FidoRegistrationInputValue + | FidoAuthenticationInputValue; /** * Generic updater function that accepts values appropriate for the collector type. diff --git a/packages/davinci-client/src/lib/collector.types.test-d.ts b/packages/davinci-client/src/lib/collector.types.test-d.ts index ecaabcbc33..5b8580fafb 100644 --- a/packages/davinci-client/src/lib/collector.types.test-d.ts +++ b/packages/davinci-client/src/lib/collector.types.test-d.ts @@ -35,6 +35,7 @@ import type { PhoneNumberOutputValue, PhoneNumberExtensionInputValue, PhoneNumberExtensionOutputValue, + SingleSelectCollectorWithValue, } from './collector.types.js'; describe('Collector Types', () => { @@ -64,9 +65,9 @@ describe('Collector Types', () => { }>(); }); - it('should validate SingleCollector structure', () => { + it('should validate SingleSelectCollector structure', () => { expectTypeOf().toMatchTypeOf< - SingleValueCollectorWithValue<'SingleSelectCollector'> + SingleSelectCollectorWithValue<'SingleSelectCollector'> >(); expectTypeOf() .toHaveProperty('category') diff --git a/packages/davinci-client/src/lib/collector.types.ts b/packages/davinci-client/src/lib/collector.types.ts index cb9f8f1787..6c40c23bba 100644 --- a/packages/davinci-client/src/lib/collector.types.ts +++ b/packages/davinci-client/src/lib/collector.types.ts @@ -16,6 +16,7 @@ import type { FidoAuthenticationOptions, FidoRegistrationOptions } from './davin */ export type SingleValueCollectorTypes = | 'PasswordCollector' + | 'ValidatedBooleanCollector' | 'SingleValueCollector' | 'SingleSelectCollector' | 'SingleSelectObjectCollector' @@ -45,7 +46,7 @@ export interface ValidationPhoneNumber { rule: boolean; } -export interface SingleValueCollectorWithValue { +export interface SingleValueCollectorWithValue { category: 'SingleValueCollector'; error: string | null; type: T; @@ -53,18 +54,21 @@ export interface SingleValueCollectorWithValue { +export interface ValidatedSingleValueCollectorWithValue< + T extends SingleValueCollectorTypes, + V = string, +> { category: 'ValidatedSingleValueCollector'; error: string | null; type: T; @@ -72,15 +76,15 @@ export interface ValidatedSingleValueCollectorWithValue = ? ValidatedTextCollector : T extends 'PasswordCollector' ? PasswordCollector - : /** + : T extends 'ValidatedBooleanCollector' + ? ValidatedBooleanCollector + : /** * At this point, we have not passed in a collector type * or we have explicitly passed in 'SingleValueCollector' * So we can return either a SingleValueCollector with value * or without a value. **/ | SingleValueCollectorWithValue<'SingleValueCollector'> - | SingleValueCollectorNoValue<'SingleValueCollector'>; + | SingleValueCollectorNoValue<'SingleValueCollector'>; /** * SINGLE-VALUE COLLECTOR TYPES @@ -173,17 +179,22 @@ export type SingleValueCollector = | SingleValueCollectorWithValue | SingleValueCollectorNoValue; -export type SingleValueCollectors = - | SingleValueCollectorNoValue<'PasswordCollector'> - | SingleSelectCollectorWithValue<'SingleSelectCollector'> - | SingleValueCollectorWithValue<'SingleValueCollector'> - | SingleValueCollectorWithValue<'TextCollector'> - | ValidatedSingleValueCollectorWithValue<'TextCollector'>; - export type PasswordCollector = SingleValueCollectorNoValue<'PasswordCollector'>; export type TextCollector = SingleValueCollectorWithValue<'TextCollector'>; export type SingleSelectCollector = SingleSelectCollectorWithValue<'SingleSelectCollector'>; export type ValidatedTextCollector = ValidatedSingleValueCollectorWithValue<'TextCollector'>; +export type ValidatedBooleanCollector = ValidatedSingleValueCollectorWithValue< + 'ValidatedBooleanCollector', + boolean +>; + +export type SingleValueCollectors = + | PasswordCollector + | SingleSelectCollector + | TextCollector + | ValidatedTextCollector + | ValidatedBooleanCollector + | SingleValueCollectorWithValue<'SingleValueCollector'>; /** ********************************************************************* * MULTI-VALUE COLLECTORS @@ -625,10 +636,8 @@ export interface ProtectOutputValue { universalDeviceIdentification: boolean; } -export interface AttestationValue extends Omit< - PublicKeyCredential, - 'rawId' | 'response' | 'getClientExtensionResults' | 'toJSON' -> { +export interface AttestationValue + extends Omit { rawId: string; response: { // https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAttestationResponse @@ -646,10 +655,8 @@ export interface FidoRegistrationOutputValue { trigger: string; } -export interface AssertionValue extends Omit< - PublicKeyCredential, - 'rawId' | 'response' | 'getClientExtensionResults' | 'toJSON' -> { +export interface AssertionValue + extends Omit { rawId: string; response: { // https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse diff --git a/packages/davinci-client/src/lib/collector.utils.ts b/packages/davinci-client/src/lib/collector.utils.ts index 4d639d9381..0e167e6a6d 100644 --- a/packages/davinci-client/src/lib/collector.utils.ts +++ b/packages/davinci-client/src/lib/collector.utils.ts @@ -17,6 +17,7 @@ import type { InferActionCollectorType, NoValueCollectorTypes, InferNoValueCollectorType, + ValidatedBooleanCollector, ValidatedSingleValueCollectorWithValue, ValidatedTextCollector, InferValueObjectCollectorType, @@ -45,6 +46,7 @@ import type { PollingField, ReadOnlyField, RedirectField, + SingleCheckboxField, SingleSelectField, StandardField, ValidatedField, @@ -151,7 +153,7 @@ export function returnSubmitCollector(field: StandardField, idx: number) { * @returns {SingleValueCollector} The constructed SingleValueCollector object. */ export function returnSingleValueCollector< - Field extends StandardField | SingleSelectField | ValidatedField, + Field extends StandardField | SingleSelectField | ValidatedField | SingleCheckboxField, CollectorType extends SingleValueCollectorTypes = 'SingleValueCollector', >(field: Field, idx: number, collectorType: CollectorType, data?: string) { let error = ''; @@ -212,10 +214,40 @@ export function returnSingleValueCollector< options: options, }, } as InferSingleValueCollectorType<'SingleSelectCollector'>; + } else if (collectorType === 'ValidatedBooleanCollector') { + const validationArray = []; + if ('required' in field && field.required === true) { + validationArray.push({ + type: 'required', + message: + ('validation' in field && field.validation?.errorMessage) || 'Value cannot be empty', + rule: true, + }); + } + + return { + category: 'ValidatedSingleValueCollector', + error: error || null, + type: collectorType, + id: `${field.key}-${idx}`, + name: field.key, + input: { + key: field.key, + value: false, + type: field.type, + validation: validationArray, + }, + output: { + key: field.key, + label: field.label, + type: field.type, + value: false, + }, + } as InferSingleValueCollectorType<'ValidatedBooleanCollector'>; } else if ('validation' in field || 'required' in field) { const validationArray = []; - if ('validation' in field) { + if ('validation' in field && field.validation && 'regex' in field.validation) { validationArray.push({ type: 'regex', message: field.validation?.errorMessage || '', @@ -464,6 +496,16 @@ export function returnSingleSelectCollector(field: SingleSelectField, idx: numbe return returnSingleValueCollector(field, idx, 'SingleSelectCollector', data); } +/** + * @function returnValidatedBooleanCollector - Creates a ValidatedBooleanCollector object based on the provided field and index. + * @param {SingleCheckboxField} field - The field object containing key, label, type, required, and validation. + * @param {number} idx - The index to be used in the id of the ValidatedBooleanCollector. + * @returns {ValidatedBooleanCollector} The constructed ValidatedBooleanCollector object. + */ +export function returnValidatedBooleanCollector(field: SingleCheckboxField, idx: number) { + return returnSingleValueCollector(field, idx, 'ValidatedBooleanCollector'); +} + /** * @function returnProtectCollector - Creates a ProtectCollector object based on the provided field and index. * @param {DaVinciField} field - The field object containing key, label, type, and links. @@ -846,7 +888,12 @@ export function returnAgreementCollector(field: AgreementField, idx: number): Ag * @returns {function} - A "validator" function that validates the input value */ export function returnValidator( - collector: ValidatedTextCollector | ObjectValueCollectors | MultiValueCollectors | AutoCollectors, + collector: + | ValidatedTextCollector + | ValidatedBooleanCollector + | ObjectValueCollectors + | MultiValueCollectors + | AutoCollectors, ) { const rules = collector.input.validation; return (value: string | string[] | Record) => { diff --git a/packages/davinci-client/src/lib/davinci.types.ts b/packages/davinci-client/src/lib/davinci.types.ts index b73488fd3a..d95a758aca 100644 --- a/packages/davinci-client/src/lib/davinci.types.ts +++ b/packages/davinci-client/src/lib/davinci.types.ts @@ -112,6 +112,17 @@ export type ValidatedField = { }; }; +export type SingleCheckboxField = { + type: 'SINGLE_CHECKBOX'; + inputType: 'BOOLEAN'; + key: string; + label: string; + required: boolean; + validation?: { + errorMessage: string; + }; +}; + export type SingleSelectField = { inputType: 'SINGLE_SELECT'; key: string; @@ -185,10 +196,11 @@ export type ProtectField = { universalDeviceIdentification: boolean; }; -export interface FidoRegistrationOptions extends Omit< - PublicKeyCredentialCreationOptions, - 'challenge' | 'user' | 'pubKeyCredParams' | 'excludeCredentials' -> { +export interface FidoRegistrationOptions + extends Omit< + PublicKeyCredentialCreationOptions, + 'challenge' | 'user' | 'pubKeyCredParams' | 'excludeCredentials' + > { challenge: number[]; user: { id: number[]; @@ -216,10 +228,8 @@ export type FidoRegistrationField = { required: boolean; }; -export interface FidoAuthenticationOptions extends Omit< - PublicKeyCredentialRequestOptions, - 'challenge' | 'allowCredentials' -> { +export interface FidoAuthenticationOptions + extends Omit { challenge: number[]; allowCredentials?: { id: number[]; @@ -260,7 +270,12 @@ export type ComplexValueFields = export type MultiValueFields = MultiSelectField; export type ReadOnlyFields = ReadOnlyField | QrCodeField | AgreementField; export type RedirectFields = RedirectField; -export type SingleValueFields = StandardField | ValidatedField | SingleSelectField | ProtectField; +export type SingleValueFields = + | StandardField + | ValidatedField + | SingleCheckboxField + | SingleSelectField + | ProtectField; export type DaVinciField = | ComplexValueFields diff --git a/packages/davinci-client/src/lib/node.reducer.ts b/packages/davinci-client/src/lib/node.reducer.ts index 3c84780761..c7cdc95c71 100644 --- a/packages/davinci-client/src/lib/node.reducer.ts +++ b/packages/davinci-client/src/lib/node.reducer.ts @@ -19,6 +19,7 @@ import { returnIdpCollector, returnSubmitCollector, returnTextCollector, + returnValidatedBooleanCollector, returnSingleSelectCollector, returnMultiSelectCollector, returnReadOnlyCollector, @@ -36,6 +37,7 @@ import type { DaVinciField, UnknownField } from './davinci.types.js'; import type { ActionCollector, MultiSelectCollector, + ValidatedBooleanCollector, SingleSelectCollector, FlowCollector, PasswordCollector, @@ -79,6 +81,7 @@ export const updateCollectorValues = createAction<{ value: | string | string[] + | boolean | PhoneNumberInputValue | PhoneNumberExtensionInputValue | FidoRegistrationInputValue @@ -98,6 +101,7 @@ const initialCollectorValues: ( | SubmitCollector | ActionCollector<'ActionCollector'> | SingleValueCollector<'SingleValueCollector'> + | ValidatedBooleanCollector | SingleSelectCollector | MultiSelectCollector | DeviceAuthenticationCollector @@ -189,6 +193,9 @@ export const nodeCollectorReducer = createReducer(initialCollectorValues, (build | PhoneNumberExtensionOutputValue; return returnObjectValueCollector(field, idx, prefillData); } + case 'SINGLE_CHECKBOX': { + return returnValidatedBooleanCollector(field, idx); + } case 'TEXT': { const str = data as string; return returnTextCollector(field, idx, str); @@ -256,6 +263,14 @@ export const nodeCollectorReducer = createReducer(initialCollectorValues, (build collector.category === 'ValidatedSingleValueCollector' || collector.category === 'SingleValueAutoCollector' ) { + if (collector.type === 'ValidatedBooleanCollector') { + if (typeof action.payload.value !== 'boolean') { + throw new Error('Value argument must be a boolean'); + } + collector.input.value = action.payload.value; + return; + } + if (typeof action.payload.value !== 'string') { throw new Error('Value argument must be a string'); } diff --git a/packages/davinci-client/src/lib/node.types.test-d.ts b/packages/davinci-client/src/lib/node.types.test-d.ts index 86c3a4ecea..5679da1047 100644 --- a/packages/davinci-client/src/lib/node.types.test-d.ts +++ b/packages/davinci-client/src/lib/node.types.test-d.ts @@ -22,6 +22,7 @@ import { MultiSelectCollector, PasswordCollector, ReadOnlyCollector, + ValidatedBooleanCollector, SingleSelectCollector, SingleValueCollector, IdpCollector, @@ -236,6 +237,7 @@ describe('Node Types', () => { | PhoneNumberCollector | PhoneNumberExtensionCollector | ReadOnlyCollector + | ValidatedBooleanCollector | SingleSelectCollector | ValidatedTextCollector | ProtectCollector diff --git a/packages/davinci-client/src/lib/node.types.ts b/packages/davinci-client/src/lib/node.types.ts index 52759bf695..0e73fe336f 100644 --- a/packages/davinci-client/src/lib/node.types.ts +++ b/packages/davinci-client/src/lib/node.types.ts @@ -13,6 +13,7 @@ import type { IdpCollector, SubmitCollector, ActionCollector, + ValidatedBooleanCollector, SingleValueCollector, SingleSelectCollector, MultiSelectCollector, @@ -36,6 +37,7 @@ export type Collectors = | FlowCollector | PasswordCollector | TextCollector + | ValidatedBooleanCollector | SingleSelectCollector | IdpCollector | SubmitCollector diff --git a/packages/davinci-client/tsconfig.json b/packages/davinci-client/tsconfig.json index 141b4ebf5f..d904bd269b 100644 --- a/packages/davinci-client/tsconfig.json +++ b/packages/davinci-client/tsconfig.json @@ -11,24 +11,6 @@ "skipLibCheck": true }, "references": [ - { - "path": "../sdk-effects/storage" - }, - { - "path": "../sdk-utilities" - }, - { - "path": "../sdk-types" - }, - { - "path": "../sdk-effects/sdk-request-middleware" - }, - { - "path": "../sdk-effects/oidc" - }, - { - "path": "../sdk-effects/logger" - }, { "path": "./tsconfig.lib.json" },