Skip to content

Commit ec5922f

Browse files
committed
chore(davinci-client): add PhoneNumberExtensionCollector
1 parent 77ecd01 commit ec5922f

13 files changed

Lines changed: 504 additions & 93 deletions

File tree

e2e/davinci-app/components/object-value.ts

Lines changed: 90 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import type {
88
DeviceAuthenticationCollector,
99
DeviceRegistrationCollector,
1010
PhoneNumberCollector,
11+
PhoneNumberExtensionCollector,
12+
PhoneNumberExtensionInputValue,
13+
PhoneNumberInputValue,
1114
Updater,
1215
} from '@forgerock/davinci-client/types';
1316

@@ -19,11 +22,16 @@ import type {
1922
*/
2023
export default function objectValueComponent(
2124
formEl: HTMLFormElement,
22-
collector: DeviceRegistrationCollector | DeviceAuthenticationCollector | PhoneNumberCollector,
25+
collector:
26+
| DeviceRegistrationCollector
27+
| DeviceAuthenticationCollector
28+
| PhoneNumberCollector
29+
| PhoneNumberExtensionCollector,
2330
updater:
2431
| Updater<DeviceRegistrationCollector>
2532
| Updater<DeviceAuthenticationCollector>
26-
| Updater<PhoneNumberCollector>,
33+
| Updater<PhoneNumberCollector>
34+
| Updater<PhoneNumberExtensionCollector>,
2735
submitForm: () => void,
2836
) {
2937
if (
@@ -61,7 +69,7 @@ export default function objectValueComponent(
6169
buttonEl.textContent = option.label;
6270
formEl.appendChild(buttonEl);
6371
}
64-
} else {
72+
} else if (collector.type === 'PhoneNumberCollector') {
6573
const phoneLabel = document.createElement('label');
6674
phoneLabel.textContent = collector.output.label || 'Phone Number';
6775
phoneLabel.className = 'object-options-title';
@@ -73,6 +81,9 @@ export default function objectValueComponent(
7381
phoneInput.setAttribute('name', 'phone-number-input');
7482
phoneInput.setAttribute('placeholder', 'Enter phone number');
7583

84+
formEl.appendChild(phoneLabel);
85+
formEl.appendChild(phoneInput);
86+
7687
// Add change event listener
7788
phoneInput.addEventListener('change', (event) => {
7889
// Properly type the event target
@@ -84,14 +95,85 @@ export default function objectValueComponent(
8495
return;
8596
}
8697

87-
updater({
98+
const phoneNumberInputValue: PhoneNumberInputValue = {
8899
phoneNumber: selectedValue,
89100
countryCode: collector.output.value?.countryCode || '',
90-
extension: collector.output.value?.extension || '',
91-
} as any);
101+
};
102+
const phoneNumberUpdater = updater as Updater<PhoneNumberCollector>;
103+
phoneNumberUpdater(phoneNumberInputValue);
92104
});
105+
} else if (collector.type === 'PhoneNumberExtensionCollector') {
106+
const phoneLabel = document.createElement('label');
107+
phoneLabel.textContent = collector.output.label || 'Phone Number';
108+
phoneLabel.className = 'object-options-title';
109+
phoneLabel.setAttribute('for', 'phone-number-input-1');
93110

94-
formEl.appendChild(phoneLabel);
95-
formEl.appendChild(phoneInput);
111+
const phoneInput = document.createElement('input');
112+
phoneInput.setAttribute('type', 'tel');
113+
phoneInput.setAttribute('id', 'phone-number-input-1');
114+
phoneInput.setAttribute('name', 'phone-number-input-1');
115+
phoneInput.setAttribute('placeholder', 'Enter phone number');
116+
117+
const extensionLabel = document.createElement('label');
118+
extensionLabel.textContent = collector.output.options.extensionLabel || 'Extension';
119+
extensionLabel.className = 'object-options-title';
120+
extensionLabel.setAttribute('for', 'extension-input-1');
121+
122+
const extensionInput = document.createElement('input');
123+
extensionInput.setAttribute('type', 'text');
124+
extensionInput.setAttribute('id', 'extension-input-1');
125+
extensionInput.setAttribute('name', 'extension-input-1');
126+
extensionInput.setAttribute('placeholder', 'Enter extension');
127+
128+
const divEl = document.createElement('div');
129+
divEl.style = 'display: flex; gap: 8px;';
130+
divEl.appendChild(phoneLabel);
131+
divEl.appendChild(phoneInput);
132+
divEl.appendChild(extensionLabel);
133+
divEl.appendChild(extensionInput);
134+
135+
formEl.appendChild(divEl);
136+
137+
const phoneNumberExtensionUpdater = updater as Updater<PhoneNumberExtensionCollector>;
138+
139+
// Add change event listener for phone number input
140+
phoneInput.addEventListener('change', (event) => {
141+
const target = event.target as HTMLInputElement;
142+
const phoneValue = target.value;
143+
const extensionValue = extensionInput.value;
144+
145+
if (!phoneValue) {
146+
console.error('No value found for phone number');
147+
return;
148+
}
149+
150+
const phoneNumberExtensionInputValue: PhoneNumberExtensionInputValue = {
151+
phoneNumber: phoneValue,
152+
countryCode: collector.output.value?.countryCode || '',
153+
extension: extensionValue,
154+
};
155+
156+
phoneNumberExtensionUpdater(phoneNumberExtensionInputValue);
157+
});
158+
159+
// Add change event listener for extension input
160+
extensionInput.addEventListener('change', (event) => {
161+
const target = event.target as HTMLInputElement;
162+
const extensionValue = target.value;
163+
const phoneValue = phoneInput.value;
164+
165+
if (!extensionValue) {
166+
console.error('No value found for extension');
167+
return;
168+
}
169+
170+
const phoneNumberExtensionInputValue: PhoneNumberExtensionInputValue = {
171+
phoneNumber: phoneValue,
172+
countryCode: collector.output.value?.countryCode || '',
173+
extension: extensionValue,
174+
};
175+
176+
phoneNumberExtensionUpdater(phoneNumberExtensionInputValue);
177+
});
96178
}
97179
}

e2e/davinci-app/main.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,10 @@ const urlParams = new URLSearchParams(window.location.search);
250250
formEl, // You can ignore this; it's just for rendering
251251
collector, // This is the plain object of the collector
252252
);
253-
} else if (collector.type === 'PhoneNumberCollector') {
253+
} else if (
254+
collector.type === 'PhoneNumberCollector' ||
255+
collector.type === 'PhoneNumberExtensionCollector'
256+
) {
254257
objectValueComponent(
255258
formEl, // You can ignore this; it's just for rendering
256259
collector, // This is the plain object of the collector

e2e/davinci-suites/src/form-fields.test.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ test('Should render form fields', async ({ page }) => {
3131
await page.locator('#combobox-field-key-3').check();
3232
await page.locator('#combobox-field-key-2').uncheck();
3333

34-
await page.locator('#phone-number-input').fill('1234567890');
34+
await page.locator('#phone-number-input-1').fill('1234567890');
35+
await page.locator('#extension-input-1').fill('7890');
3536

3637
await expect(page.getByRole('button', { name: 'Flow Button' })).toBeVisible();
3738
await expect(page.getByRole('button', { name: 'Flow Link' })).toBeVisible();
@@ -56,7 +57,7 @@ test('Should render form fields', async ({ page }) => {
5657
'phone-field': {
5758
phoneNumber: '1234567890',
5859
countryCode: 'GB',
59-
extension: '4321',
60+
extension: '7890',
6061
},
6162
});
6263
});

packages/davinci-client/src/lib/client.store.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import type {
4646
MultiValueCollectors,
4747
FidoRegistrationInputValue,
4848
FidoAuthenticationInputValue,
49+
PhoneNumberExtensionInputValue,
4950
} from './collector.types.js';
5051
import type {
5152
InitFlow,
@@ -338,6 +339,7 @@ export async function davinci<ActionType extends ActionTypes = ActionTypes>({
338339
| string
339340
| string[]
340341
| PhoneNumberInputValue
342+
| PhoneNumberExtensionInputValue
341343
| FidoRegistrationInputValue
342344
| FidoAuthenticationInputValue,
343345
index?: number,

packages/davinci-client/src/lib/collector.types.test-d.ts

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ import type {
2828
QrCodeCollector,
2929
AgreementCollector,
3030
PhoneNumberCollector,
31-
ObjectOptionsCollectorWithObjectValue,
31+
PhoneNumberExtensionCollector,
32+
ObjectValueCollectorWithObjectValue,
3233
InferValueObjectCollectorType,
3334
PhoneNumberInputValue,
3435
PhoneNumberOutputValue,
35-
PhoneNumberOptions,
36+
PhoneNumberExtensionInputValue,
37+
PhoneNumberExtensionOutputValue,
3638
} from './collector.types.js';
3739

3840
describe('Collector Types', () => {
@@ -374,16 +376,15 @@ describe('Collector Types', () => {
374376
name: '',
375377
input: {
376378
key: '',
377-
value: { countryCode: '', phoneNumber: '', extension: '' },
379+
value: { countryCode: '', phoneNumber: '' },
378380
type: '',
379381
validation: null,
380382
},
381383
output: {
382384
key: '',
383385
label: '',
384386
type: '',
385-
options: { showExtension: false },
386-
value: { countryCode: '', phoneNumber: '', extension: '' },
387+
value: { countryCode: '', phoneNumber: '' },
387388
},
388389
};
389390

@@ -392,13 +393,52 @@ describe('Collector Types', () => {
392393
});
393394

394395
describe('ObjectValueCollector Types', () => {
396+
it('should correctly infer PhoneNumberExtensionCollector Type', () => {
397+
const tCollector: InferValueObjectCollectorType<'PhoneNumberExtensionCollector'> = {
398+
category: 'ObjectValueCollector',
399+
error: null,
400+
type: 'PhoneNumberExtensionCollector',
401+
id: '',
402+
name: '',
403+
input: {
404+
key: '',
405+
value: { countryCode: '', phoneNumber: '', extension: '' },
406+
type: '',
407+
validation: null,
408+
},
409+
output: {
410+
key: '',
411+
label: '',
412+
type: '',
413+
options: { extensionLabel: '' },
414+
value: {},
415+
},
416+
};
417+
418+
expectTypeOf(tCollector).toEqualTypeOf<PhoneNumberExtensionCollector>();
419+
});
420+
421+
it('should validate PhoneNumberExtensionCollector structure', () => {
422+
expectTypeOf<PhoneNumberExtensionCollector>()
423+
.toHaveProperty('category')
424+
.toEqualTypeOf<'ObjectValueCollector'>();
425+
expectTypeOf<PhoneNumberExtensionCollector>()
426+
.toHaveProperty('type')
427+
.toEqualTypeOf<'PhoneNumberExtensionCollector'>();
428+
expectTypeOf<
429+
PhoneNumberExtensionCollector['input']['value']
430+
>().toEqualTypeOf<PhoneNumberExtensionInputValue>();
431+
expectTypeOf<
432+
PhoneNumberExtensionCollector['output']['value']
433+
>().toEqualTypeOf<PhoneNumberExtensionOutputValue>();
434+
});
435+
395436
it('should validate PhoneNumberCollector structure', () => {
396437
expectTypeOf<PhoneNumberCollector>().toEqualTypeOf<
397-
ObjectOptionsCollectorWithObjectValue<
438+
ObjectValueCollectorWithObjectValue<
398439
'PhoneNumberCollector',
399440
PhoneNumberInputValue,
400-
PhoneNumberOutputValue,
401-
PhoneNumberOptions
441+
PhoneNumberOutputValue
402442
>
403443
>();
404444
expectTypeOf<PhoneNumberCollector>()
@@ -408,7 +448,6 @@ describe('Collector Types', () => {
408448
.toHaveProperty('type')
409449
.toEqualTypeOf<'PhoneNumberCollector'>();
410450
expectTypeOf<PhoneNumberCollector['input']['value']>().toEqualTypeOf<PhoneNumberInputValue>();
411-
expectTypeOf<PhoneNumberCollector['output']['options']>().toEqualTypeOf<PhoneNumberOptions>();
412451
});
413452

414453
it('should validate PhoneNumberCollector base type constraints', () => {
@@ -420,15 +459,14 @@ describe('Collector Types', () => {
420459
name: 'Test',
421460
input: {
422461
key: 'phone',
423-
value: { countryCode: '+1', phoneNumber: '5555555555', extension: '' },
462+
value: { countryCode: '+1', phoneNumber: '5555555555' },
424463
type: 'string',
425464
validation: null,
426465
},
427466
output: {
428467
key: 'phone',
429468
label: 'Phone Number',
430469
type: 'phone',
431-
options: { showExtension: true },
432470
value: { countryCode: '+1', phoneNumber: '5555555555' },
433471
},
434472
};

0 commit comments

Comments
 (0)