Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified .yarn/install-state.gz
Binary file not shown.
44 changes: 44 additions & 0 deletions PingSampleApp/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,11 @@ PODS:
- PingBrowser (~> 2.1.0)
- PingCommons (~> 2.1.0)
- PingOrchestrate (~> 2.1.0)
- PingOneProtect (2.1.0):
- PingDavinciPlugin (~> 2.1.0)
- PingJourneyPlugin (~> 2.1.0)
- PingOneSignals (~> 5.4.0)
- PingOneSignals (5.4.0)
- PingOrchestrate (2.1.0):
- PingNetwork (~> 2.1.0)
- PingStorage (~> 2.1.0)
Expand Down Expand Up @@ -2925,6 +2930,37 @@ PODS:
- RNPingCore
- SocketRocket
- Yoga
- RNPingProtect (1.0.0):
- boost
- DoubleConversion
- fast_float
- fmt
- glog
- hermes-engine
- PingOneProtect (= 2.1.0)
- RCT-Folly
- RCT-Folly/Fabric
- RCTRequired
- RCTTypeSafety
- React-Core
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-hermes
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- RNPingCore
- SocketRocket
- Yoga
- RNPingPush (1.0.0):
- boost
- DoubleConversion
Expand Down Expand Up @@ -3302,6 +3338,7 @@ DEPENDENCIES:
- "RNPingLogger (from `../../node_modules/@ping-identity/rn-logger`)"
- "RNPingOath (from `../../node_modules/@ping-identity/rn-oath`)"
- "RNPingOidc (from `../../node_modules/@ping-identity/rn-oidc`)"
- "RNPingProtect (from `../../node_modules/@ping-identity/rn-protect`)"
- "RNPingPush (from `../../node_modules/@ping-identity/rn-push`)"
- "RNPingStorage (from `../../node_modules/@ping-identity/rn-storage`)"
- RNScreens (from `../../node_modules/react-native-screens`)
Expand Down Expand Up @@ -3351,6 +3388,8 @@ SPEC REPOS:
- PingNetwork
- PingOath
- PingOidc
- PingOneProtect
- PingOneSignals
- PingOrchestrate
- PingPush
- PingStorage
Expand Down Expand Up @@ -3540,6 +3579,8 @@ EXTERNAL SOURCES:
:path: "../../node_modules/@ping-identity/rn-oath"
RNPingOidc:
:path: "../../node_modules/@ping-identity/rn-oidc"
RNPingProtect:
:path: "../../node_modules/@ping-identity/rn-protect"
RNPingPush:
:path: "../../node_modules/@ping-identity/rn-push"
RNPingStorage:
Expand Down Expand Up @@ -3604,6 +3645,8 @@ SPEC CHECKSUMS:
PingNetwork: 97b4a783caca3ff9382ecbfe2b6da1cff10ed647
PingOath: 34d6c81f32c87a4f568fceb48b531cd1be3e9464
PingOidc: e6d28c4d8c130691872d6aa3b12a1ef498567957
PingOneProtect: 4ced7b27c9e26d1ea9b8d29775121efd738d9000
PingOneSignals: 3d14d429201435960e062e3481356b9f21fa657f
PingOrchestrate: 90365995dc6fc5bd14f78ae5c04f07d46d265523
PingPush: aef2216cade878592b6c7166a90d8b15f1c0b649
PingStorage: fdaebdd80f4359dc7eb4f780429cc6840ed01cee
Expand Down Expand Up @@ -3691,6 +3734,7 @@ SPEC CHECKSUMS:
RNPingLogger: b7dd73f42b3fea86a56fc7a35999cf4333fbde20
RNPingOath: 261b44b957975c0c036b1bed6ef953c4767b8fcc
RNPingOidc: d490892cb09a11c189b2259bca0fecfc09a109fd
RNPingProtect: cbfd801e3ce9b326dd190772ff1ba16735d4fe78
RNPingPush: 3f02ee0f3d971c0d8affdb13b8fcefc4de789de1
RNPingStorage: 2c45c6d29cfcf8e1ed8ec3e32df1443e29dabd67
RNScreens: 846d53087db560ed5fbc34feb0643adb5f9602c5
Expand Down
16 changes: 13 additions & 3 deletions PingSampleApp/ui/davinci/components/atoms/DaVinciErrorList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,20 @@ export type DaVinciErrorListProps = {
* @returns User-facing message.
*/
function describeError(error: DaVinciFieldValidationError): string {
if (error.code === 'REQUIRED') {
return 'This field is required.';
switch (error.code) {
case 'REQUIRED':
return 'This field is required.';
case 'REGEX_ERROR':
return error.message;
case 'INVALID_LENGTH':
return `Password must be between ${error.min} and ${error.max} characters.`;
case 'UNIQUE_CHARACTER':
return `Password must contain at least ${error.min} unique characters.`;
case 'MAX_REPEAT':
return `Password cannot repeat a character more than ${error.max} times.`;
case 'MIN_CHARACTERS':
return `Password must contain at least ${error.min} ${error.character} characters.`;
}
return error.message;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
*/

import React from 'react';
import { Text, View } from 'react-native';
import { View } from 'react-native';
import type { LabelCollector } from '@ping-identity/rn-davinci';
import { davinciFieldStyles } from '../../../../src/styles/davinciStyles';
import RichTextLabel from './RichTextLabel';
import type { DaVinciCollectorRendererProps } from './types';

/**
Expand All @@ -24,9 +25,11 @@ export default function DaVinciLabelField(
const labelCollector = collector as LabelCollector;
return (
<View style={davinciFieldStyles.card}>
<Text style={davinciFieldStyles.labelContent}>
{labelCollector.content}
</Text>
<RichTextLabel
text={labelCollector.content}
richContent={labelCollector.richContent}
style={davinciFieldStyles.labelContent}
/>
</View>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
* of the MIT license. See the LICENSE file for details.
*/

import React from 'react';
import React, { useState } from 'react';
import { View } from 'react-native';
import type { PasswordCollector } from '@ping-identity/rn-davinci';
import type {
DaVinciFieldValidationError,
PasswordCollector,
} from '@ping-identity/rn-davinci';
import PingTextInput from '../../../components/atoms/PingTextInput';
import DaVinciErrorList from '../atoms/DaVinciErrorList';
import DaVinciPasswordRequirements from './DaVinciPasswordRequirements';
Expand All @@ -23,7 +26,10 @@ import type { DaVinciCollectorRendererProps } from './types';
export default function DaVinciPasswordField(
props: DaVinciCollectorRendererProps,
): React.ReactElement {
const { collector, value, onChange } = props;
const { collector, value, onChange, onValidate } = props;
const [manualErrors, setManualErrors] = useState<
DaVinciFieldValidationError[] | null
>(null);
const passwordCollector = collector as PasswordCollector;
const stringValue = typeof value === 'string' ? value : '';

Expand All @@ -36,7 +42,17 @@ export default function DaVinciPasswordField(
: passwordCollector.label
}
value={stringValue}
onChangeText={onChange}
onChangeText={value => {
setManualErrors(null);
onChange(value);
}}
onBlur={() => {
onValidate(collector.key, stringValue)
.then(setManualErrors)
.catch(error => {
console.warn('[DaVinci] Password validation failed:', error);
});
}}
secureTextEntry
allowPasswordToggle
autoCapitalize="none"
Expand All @@ -48,7 +64,7 @@ export default function DaVinciPasswordField(
value={stringValue}
/>
) : null}
<DaVinciErrorList />
<DaVinciErrorList errors={manualErrors ?? undefined} />
</View>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@

import React, { useState } from 'react';
import { View } from 'react-native';
import type { PhoneNumberCollector } from '@ping-identity/rn-davinci';
import type {
DaVinciFieldValidationError,
PhoneNumberCollector,
} from '@ping-identity/rn-davinci';
import PingTextInput from '../../../components/atoms/PingTextInput';
import DaVinciErrorList from '../atoms/DaVinciErrorList';
import DaVinciFieldLabel from '../atoms/DaVinciFieldLabel';
import PickerModal from '../atoms/PickerModal';
import { davinciFieldStyles } from '../../../../src/styles/davinciStyles';
Expand Down Expand Up @@ -67,7 +71,7 @@ function countryCodeForDialCode(dialCode: string): string | undefined {
function resolveValue(
value: unknown,
collector: PhoneNumberCollector,
): { countryCode: string; phoneNumber: string } {
): { countryCode: string; phoneNumber: string; extension: string } {
let rawCode =
value &&
typeof value === 'object' &&
Expand All @@ -89,7 +93,15 @@ function resolveValue(
? (value as { phoneNumber: string }).phoneNumber
: (collector.phoneNumber ?? '');

return { countryCode: rawCode, phoneNumber };
const extension =
value &&
typeof value === 'object' &&
'extension' in value &&
typeof (value as { extension: unknown }).extension === 'string'
? (value as { extension: string }).extension
: (collector.extension ?? '');

return { countryCode: rawCode, phoneNumber, extension };
}

/**
Expand Down Expand Up @@ -123,11 +135,14 @@ function triggerLabel(isoCode: string): string {
export default function DaVinciPhoneNumberField(
props: DaVinciCollectorRendererProps,
): React.ReactElement {
const { collector, value, onChange } = props;
const { collector, value, onChange, onValidate } = props;
const phoneCollector = collector as PhoneNumberCollector;
const current = resolveValue(value, phoneCollector);

const [modalVisible, setModalVisible] = useState(false);
const [manualErrors, setManualErrors] = useState<
DaVinciFieldValidationError[] | null
>(null);

const pickerOptions = COUNTRIES.map(c => ({
value: c.countryCode,
Expand All @@ -150,29 +165,59 @@ export default function DaVinciPhoneNumberField(
visible={modalVisible}
onOpen={() => setModalVisible(true)}
onClose={() => setModalVisible(false)}
onSelect={isoCode =>
onSelect={isoCode => {
setManualErrors(null);
onChange({
countryCode: isoCode,
phoneNumber: current.phoneNumber,
})
}
extension: current.extension,
});
}}
/>
</View>
<View style={davinciFieldStyles.phoneNumber}>
<PingTextInput
label="Phone number"
value={current.phoneNumber}
onChangeText={text =>
onChangeText={text => {
setManualErrors(null);
onChange({
countryCode: current.countryCode,
phoneNumber: text,
})
}
extension: current.extension,
});
}}
onBlur={() => {
onValidate(collector.key, current.phoneNumber)
.then(setManualErrors)
.catch(error => {
console.warn(
'[DaVinci] Phone number validation failed:',
error,
);
});
}}
keyboardType="phone-pad"
containerStyle={{ marginBottom: 0 }}
/>
</View>
</View>
{phoneCollector.showExtension ? (
<PingTextInput
label={phoneCollector.extensionLabel}
value={current.extension}
onChangeText={extension => {
setManualErrors(null);
onChange({
countryCode: current.countryCode,
phoneNumber: current.phoneNumber,
extension,
});
}}
keyboardType="phone-pad"
/>
) : null}
<DaVinciErrorList errors={manualErrors ?? undefined} />
</View>
);
}
26 changes: 21 additions & 5 deletions PingSampleApp/ui/davinci/components/molecules/DaVinciTextField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
* of the MIT license. See the LICENSE file for details.
*/

import React from 'react';
import React, { useState } from 'react';
import { View } from 'react-native';
import type { TextCollector } from '@ping-identity/rn-davinci';
import type {
DaVinciFieldValidationError,
TextCollector,
} from '@ping-identity/rn-davinci';
import PingTextInput from '../../../components/atoms/PingTextInput';
import DaVinciErrorList from '../atoms/DaVinciErrorList';
import { davinciFieldStyles } from '../../../../src/styles/davinciStyles';
Expand All @@ -22,7 +25,10 @@ import type { DaVinciCollectorRendererProps } from './types';
export default function DaVinciTextField(
props: DaVinciCollectorRendererProps,
): React.ReactElement {
const { collector, value, onChange } = props;
const { collector, value, onChange, onValidate } = props;
const [manualErrors, setManualErrors] = useState<
DaVinciFieldValidationError[] | null
>(null);
const textCollector = collector as TextCollector;
const stringValue = typeof value === 'string' ? value : textCollector.value;
const isMultiline = stringValue.length > 60;
Expand All @@ -36,12 +42,22 @@ export default function DaVinciTextField(
: textCollector.label
}
value={stringValue}
onChangeText={onChange}
onChangeText={value => {
setManualErrors(null);
onChange(value);
}}
onBlur={() => {
onValidate(collector.key, stringValue)
.then(setManualErrors)
.catch(error => {
console.warn('[DaVinci] Text validation failed:', error);
});
}}
autoCapitalize="none"
autoCorrect={false}
multiline={isMultiline}
/>
<DaVinciErrorList errors={textCollector.validation?.errors} />
<DaVinciErrorList errors={manualErrors ?? undefined} />
</View>
);
}
12 changes: 12 additions & 0 deletions PingSampleApp/ui/davinci/components/molecules/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

import type {
DaVinciFieldValidationError,
DaVinciFormValue,
DaVinciNormalizedCollector,
IdpCollector,
Expand Down Expand Up @@ -35,6 +36,17 @@ export type DaVinciCollectorRendererProps = {
* Submits the current form. Used by submit buttons and flow links.
*/
onSubmit: () => void;
/**
* Validates the current value for a collector.
*
* @param collectorKey Collector key to validate.
* @param value Current rendered value for the collector.
* @returns Validation errors for the collector.
*/
onValidate: (
collectorKey: string,
value: DaVinciFormValue,
) => Promise<DaVinciFieldValidationError[]>;
/**
* Immediately advances the flow using a specific {@link FlowCollector} key.
*
Expand Down
Loading
Loading