@myazahq/kyc-sdk-react-native 2.3.0 → 2.5.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/android/build.gradle +35 -3
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +75 -0
- package/ios/HybridMyazaFaceDetector.swift +8 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +23 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +2 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +9 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +8 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +43 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +17 -0
- package/nitrogen/generated/ios/swift/Func_void_bool.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +31 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +2 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +3 -0
- package/package.json +1 -2
- package/src/MyazaKYC.tsx +12 -0
- package/src/components/BrandBar.tsx +137 -0
- package/src/components/DashedBorder.tsx +55 -0
- package/src/components/DialCodePicker.tsx +25 -24
- package/src/components/DocumentReviewSide.tsx +34 -14
- package/src/components/Icon.tsx +14 -0
- package/src/components/KycSheet.tsx +39 -140
- package/src/components/MediaSourceSheet.tsx +7 -37
- package/src/components/MultiIdProgress.tsx +107 -0
- package/src/components/MyazaDateField.tsx +6 -20
- package/src/components/MyazaInput.tsx +23 -4
- package/src/components/MyazaSelect.tsx +17 -40
- package/src/components/OwnershipSlider.tsx +102 -0
- package/src/components/PhoneNumberInput.tsx +37 -4
- package/src/components/SandboxBanner.tsx +92 -0
- package/src/components/StaggerIn.tsx +63 -0
- package/src/components/StepHeader.tsx +15 -2
- package/src/components/StepView.tsx +26 -0
- package/src/components/glass/ChromeGlass.tsx +65 -0
- package/src/components/glass/FloatingSheet.tsx +242 -0
- package/src/components/glass/GlassSheet.tsx +50 -0
- package/src/components/glass/GlassSurface.tsx +31 -3
- package/src/components/stepHeaderMeta.tsx +3 -1
- package/src/components/viewfinder/ImmersiveBottomBar.tsx +28 -17
- package/src/components/viewfinder/ImmersiveControls.tsx +26 -14
- package/src/components/viewfinder/ViewfinderControls.tsx +29 -7
- package/src/config/business.ts +6 -6
- package/src/config/businessDetailsValidity.ts +52 -0
- package/src/config/businessPrefill.ts +72 -0
- package/src/config/businessSteps.ts +14 -3
- package/src/config/keyPeople.ts +111 -7
- package/src/config/keyPeopleCorporate.ts +19 -0
- package/src/config/keyPeopleOwnerTypes.ts +12 -0
- package/src/config/keyPeopleOwners.ts +26 -0
- package/src/config/keyPeoplePrefill.ts +138 -0
- package/src/config/keyPeopleSectionDefs.ts +65 -0
- package/src/config/keyPeopleSections.ts +167 -0
- package/src/config/registrationHint.ts +1 -1
- package/src/config/stepOrder.ts +24 -4
- package/src/config/theme.ts +8 -2
- package/src/config/website.ts +36 -0
- package/src/config/workflowMerge.ts +5 -0
- package/src/emrtd/activeAuth.ts +100 -0
- package/src/emrtd/extras.ts +76 -0
- package/src/emrtd/files.ts +5 -0
- package/src/emrtd/index.ts +1 -0
- package/src/emrtd/read.ts +8 -1
- package/src/emrtd/session.ts +41 -0
- package/src/emrtd/stages.ts +11 -0
- package/src/lib/contact-recovery.ts +44 -0
- package/src/lib/multi-id.ts +210 -0
- package/src/lib/resubmit.ts +130 -0
- package/src/lib/screen-corners.ts +50 -0
- package/src/lib/step-log.ts +24 -3
- package/src/lib/use-multi-id-plan.ts +29 -0
- package/src/liveness/useModelReady.ts +65 -0
- package/src/liveness/visionCameraFaceDetector.ts +35 -0
- package/src/screens/ApplicantRoleStep.tsx +9 -5
- package/src/screens/BusinessCheckPanel.tsx +89 -0
- package/src/screens/BusinessDetailsFields.tsx +109 -0
- package/src/screens/BusinessDetailsStep.tsx +131 -145
- package/src/screens/BusinessDocumentSlot.tsx +2 -3
- package/src/screens/BusinessDocumentsStep.tsx +1 -1
- package/src/screens/BusinessKeyPeopleStep.tsx +103 -71
- package/src/screens/BusinessPickedCard.tsx +86 -0
- package/src/screens/BusinessPickedSection.tsx +51 -0
- package/src/screens/BusinessRegistryPickers.tsx +67 -0
- package/src/screens/BusinessSandboxToggle.tsx +114 -0
- package/src/screens/BusinessSearch.tsx +199 -0
- package/src/screens/BusinessSearchResults.tsx +115 -0
- package/src/screens/CompanyInfoFields.tsx +86 -51
- package/src/screens/ConsentStep.tsx +31 -0
- package/src/screens/ContactDestinationField.tsx +1 -0
- package/src/screens/ContactVerificationStep.tsx +34 -3
- package/src/screens/IdTypeStep.tsx +12 -5
- package/src/screens/KeepLinksSheet.tsx +88 -0
- package/src/screens/KeyPeopleAwaitCard.tsx +204 -0
- package/src/screens/KeyPeopleAwaitList.tsx +126 -0
- package/src/screens/KeyPeoplePending.tsx +154 -0
- package/src/screens/KeyPeopleSection.tsx +162 -0
- package/src/screens/KeyPeopleSectionsList.tsx +103 -0
- package/src/screens/KeyPeopleUboExemption.tsx +72 -0
- package/src/screens/KeyPersonCard.tsx +36 -4
- package/src/screens/KeyPersonForm.tsx +195 -36
- package/src/screens/KeyPersonKindToggle.tsx +68 -0
- package/src/screens/KeyPersonOwners.tsx +109 -0
- package/src/screens/KeyPersonRoleChips.tsx +68 -0
- package/src/screens/KeyPersonSheet.tsx +92 -108
- package/src/screens/LivenessStep.tsx +18 -0
- package/src/screens/NfcStep.tsx +14 -0
- package/src/screens/ProofOfAddressStep.tsx +6 -3
- package/src/screens/SubmittedStep.tsx +66 -10
- package/src/screens/nfc/NfcReadProgress.tsx +1 -0
- package/src/screens/useAwaitingPeople.ts +90 -0
- package/src/services/api-types.ts +158 -2
- package/src/services/api-verify-types.ts +53 -1
- package/src/services/api.ts +104 -0
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/errors.ts +10 -2
- package/src/services/workflowGate.ts +1 -0
- package/src/specs/MyazaFaceDetector.nitro.ts +29 -0
- package/src/store/businessCheck.ts +113 -0
- package/src/store/derive.ts +15 -0
- package/src/store/kycStore.ts +241 -14
- package/src/store/serverConfig.ts +2 -0
- package/src/store/session.ts +208 -0
- package/src/store/state.ts +155 -2
- package/src/store/submit.ts +63 -13
- package/src/store/submitApplicant.ts +5 -11
- package/src/types/business.ts +23 -2
- package/src/types/config.ts +29 -3
- package/src/types/verification.ts +1 -1
- package/src/types/workflow.ts +15 -0
- package/src/screens/KeyPeopleInviteLinks.tsx +0 -239
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
|
|
3
|
+
import { isFaceModelReady, primeFaceModel } from './visionCameraFaceDetector';
|
|
4
|
+
|
|
5
|
+
// ─── Face-model readiness gate ────────────────────────────────────────────────
|
|
6
|
+
//
|
|
7
|
+
// Android fetches ML Kit's face model through Play Services rather than bundling
|
|
8
|
+
// it, which keeps ~8 MB per device out of the APK but leaves a window where
|
|
9
|
+
// detection cannot run: first launch before the download lands, or a device with
|
|
10
|
+
// no Google Play Services at all.
|
|
11
|
+
//
|
|
12
|
+
// This has to be answered BEFORE the camera opens. `detectFace` reports through
|
|
13
|
+
// `FaceResult`, where a missing model and an empty frame are both
|
|
14
|
+
// `faceCount: 0` — so gating on the per-frame result would strand the user on
|
|
15
|
+
// "position your face" indefinitely, with the SDK unable to say why. That is the
|
|
16
|
+
// single failure mode this whole gate exists to prevent.
|
|
17
|
+
//
|
|
18
|
+
// iOS is always ready (Apple Vision is a system framework), so this resolves on
|
|
19
|
+
// the first tick there and costs nothing.
|
|
20
|
+
|
|
21
|
+
export type ModelReadyState = 'ready' | 'preparing' | 'unavailable';
|
|
22
|
+
|
|
23
|
+
/** How long to wait for the model before calling it unavailable. */
|
|
24
|
+
const MODEL_WAIT_MS = 20_000;
|
|
25
|
+
/** Gap between readiness polls while the download is in flight. */
|
|
26
|
+
const POLL_INTERVAL_MS = 500;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Tracks whether on-device face detection can run.
|
|
30
|
+
*
|
|
31
|
+
* Returns `'preparing'` while Play Services fetches the model, `'ready'` once it
|
|
32
|
+
* can run, and `'unavailable'` when it could not be obtained within
|
|
33
|
+
* {@link MODEL_WAIT_MS} — no Play Services, no network, or a declined install.
|
|
34
|
+
*
|
|
35
|
+
* The flow primes the download at open (see `MyazaKYC.tsx`), so by the time the
|
|
36
|
+
* user reaches liveness this is normally already `'ready'` and no waiting screen
|
|
37
|
+
* is ever shown.
|
|
38
|
+
*/
|
|
39
|
+
export function useFaceModelReady(): ModelReadyState {
|
|
40
|
+
const [state, setState] = useState<ModelReadyState>(() =>
|
|
41
|
+
isFaceModelReady() ? 'ready' : 'preparing',
|
|
42
|
+
);
|
|
43
|
+
const startedAt = useRef(Date.now());
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (state !== 'preparing') return;
|
|
47
|
+
|
|
48
|
+
// Re-prime rather than assume the open-time call ran: the step can be
|
|
49
|
+
// reached directly in a resumed flow, and prepareModel() is a no-op once
|
|
50
|
+
// the model is present.
|
|
51
|
+
primeFaceModel();
|
|
52
|
+
|
|
53
|
+
const id = setInterval(() => {
|
|
54
|
+
if (isFaceModelReady()) {
|
|
55
|
+
setState('ready');
|
|
56
|
+
} else if (Date.now() - startedAt.current > MODEL_WAIT_MS) {
|
|
57
|
+
setState('unavailable');
|
|
58
|
+
}
|
|
59
|
+
}, POLL_INTERVAL_MS);
|
|
60
|
+
|
|
61
|
+
return () => clearInterval(id);
|
|
62
|
+
}, [state]);
|
|
63
|
+
|
|
64
|
+
return state;
|
|
65
|
+
}
|
|
@@ -45,6 +45,41 @@ const boxedDetector: BoxedHybridObject<MyazaFaceDetector> | null = detector
|
|
|
45
45
|
? NitroModules.box(detector)
|
|
46
46
|
: null;
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Start fetching the face model, so it is warm by the time liveness runs.
|
|
50
|
+
*
|
|
51
|
+
* Android fetches ML Kit's models through Play Services rather than bundling
|
|
52
|
+
* them, which keeps ~18.5 MB per device out of the APK but leaves a window on
|
|
53
|
+
* first launch where detection cannot work. Priming at flow start puts that
|
|
54
|
+
* download behind the consent and ID-type screens instead of in front of the
|
|
55
|
+
* camera.
|
|
56
|
+
*
|
|
57
|
+
* Best-effort and non-blocking, exactly like the web SDK's `primeFaceMesh()`:
|
|
58
|
+
* a failure here is not fatal, because {@link isFaceModelReady} is what the
|
|
59
|
+
* liveness step actually gates on. iOS resolves immediately (Apple Vision is a
|
|
60
|
+
* system framework).
|
|
61
|
+
*/
|
|
62
|
+
export function primeFaceModel(): void {
|
|
63
|
+
detector?.prepareModel().catch(() => {
|
|
64
|
+
/* best-effort: isFaceModelReady() is the real gate */
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Whether face detection can run right now.
|
|
70
|
+
*
|
|
71
|
+
* Checked BEFORE the camera opens. `detectFace` cannot answer this — a missing
|
|
72
|
+
* model and an empty frame are both `faceCount: 0`, so gating on the per-frame
|
|
73
|
+
* result would strand the user on "position your face" with no explanation.
|
|
74
|
+
*
|
|
75
|
+
* `true` when the native module is absent entirely (Expo Go), because that path
|
|
76
|
+
* already degrades to the existing camera-unavailable screen and should not be
|
|
77
|
+
* reported as a model problem.
|
|
78
|
+
*/
|
|
79
|
+
export function isFaceModelReady(): boolean {
|
|
80
|
+
return detector?.isModelReady() ?? true;
|
|
81
|
+
}
|
|
82
|
+
|
|
48
83
|
/**
|
|
49
84
|
* Maps the native {@link FaceResult} to a {@link LivenessFaceData}, or `null`
|
|
50
85
|
* when no face is present (`faceCount === 0`). A worklet — runs on the camera
|
|
@@ -50,11 +50,13 @@ export function ApplicantRoleStep(): React.ReactElement {
|
|
|
50
50
|
const { colors } = useTheme();
|
|
51
51
|
const stored = useKyc((s) => s.businessApplication);
|
|
52
52
|
|
|
53
|
-
// The valid entered
|
|
54
|
-
// is index-based, so the list and the submission can never disagree.
|
|
53
|
+
// The valid entered PEOPLE, keeping their ORIGINAL index — the payload flag
|
|
54
|
+
// is index-based, so the list and the submission can never disagree. A
|
|
55
|
+
// corporate shareholder is excluded: "which of these is you?" is a question
|
|
56
|
+
// about humans, and a company can never be the person filling in the form.
|
|
55
57
|
const people = stored.keyPeople
|
|
56
58
|
.map((row, index) => ({ row, index }))
|
|
57
|
-
.filter(({ row }) => isKeyPersonRowValid(row));
|
|
59
|
+
.filter(({ row }) => !row.isCorporate && isKeyPersonRowValid(row));
|
|
58
60
|
const hasPeople = people.length > 0;
|
|
59
61
|
|
|
60
62
|
const propName = [config.userData?.firstName, config.userData?.lastName]
|
|
@@ -63,13 +65,15 @@ export function ApplicantRoleStep(): React.ReactElement {
|
|
|
63
65
|
|
|
64
66
|
// Tri-state: a person's index, 'other', or nothing chosen yet. First arrival
|
|
65
67
|
// pre-selects the applicant's own entry when the userData name matches
|
|
66
|
-
// (they still confirm) —
|
|
68
|
+
// (they still confirm) — and when the name matches NOBODY listed, "I'm not
|
|
69
|
+
// one of these people" is what that fact means, so it is pre-selected
|
|
70
|
+
// instead (mirrors the web SDK). A stored choice always wins.
|
|
67
71
|
const [selection, setSelection] = useState<number | 'other' | null>(() => {
|
|
68
72
|
if (stored.applicantKeyPersonIndex !== null) return stored.applicantKeyPersonIndex;
|
|
69
73
|
if (stored.applicantRole) return 'other';
|
|
70
74
|
if (propName) {
|
|
71
75
|
const match = people.find(({ row }) => namesLooselyMatch(propName, row.name));
|
|
72
|
-
|
|
76
|
+
return match ? match.index : people.length > 0 ? 'other' : null;
|
|
73
77
|
}
|
|
74
78
|
return null;
|
|
75
79
|
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { radius, spacing } from '../config/theme';
|
|
5
|
+
import { useTheme } from '../components/runtime';
|
|
6
|
+
import { MyazaText } from '../components/Typography';
|
|
7
|
+
import { MyazaAlert } from '../components/MyazaAlert';
|
|
8
|
+
import type { BusinessCheckState } from '../store/state';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// What the register said, when it was NOT a plain yes.
|
|
12
|
+
//
|
|
13
|
+
// The check is a paid, deliberate step, so it is shown rather than run
|
|
14
|
+
// invisibly: the applicant sees a company that is not on the register caught
|
|
15
|
+
// HERE instead of after documents and a selfie. Nothing is shown for a company
|
|
16
|
+
// that WAS found — the register's answer is already in the form the applicant
|
|
17
|
+
// is looking at, editable, and its officers are the next step. Mirrors the web
|
|
18
|
+
// SDK's BusinessCheckPanel.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
export function BusinessCheckPanel({
|
|
22
|
+
check,
|
|
23
|
+
}: {
|
|
24
|
+
check: BusinessCheckState;
|
|
25
|
+
}): React.ReactElement | null {
|
|
26
|
+
// `skipped` shows nothing on purpose. The organisation could not be charged,
|
|
27
|
+
// which is not the applicant's problem and not something they can act on;
|
|
28
|
+
// the check simply happens at submission instead. `checking` shows nothing
|
|
29
|
+
// either — the loader lives inside the Continue button they just pressed.
|
|
30
|
+
if (
|
|
31
|
+
check.status === 'idle' ||
|
|
32
|
+
check.status === 'skipped' ||
|
|
33
|
+
check.status === 'found' ||
|
|
34
|
+
check.status === 'checking'
|
|
35
|
+
) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (check.status === 'not_found') {
|
|
40
|
+
return (
|
|
41
|
+
<MyazaAlert
|
|
42
|
+
variant="warning"
|
|
43
|
+
title="We could not find this business on the register"
|
|
44
|
+
message="Check the registration number and try again."
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (check.status === 'limit_reached') {
|
|
50
|
+
// Not a failure: the submission still runs its own check. What it says is
|
|
51
|
+
// "stop re-picking and look at the number", which is the only thing left
|
|
52
|
+
// that helps.
|
|
53
|
+
return (
|
|
54
|
+
<NeutralPanel
|
|
55
|
+
title="We have stopped looking this up for now"
|
|
56
|
+
body="This application has searched the register several times. Check the registration number is right; your details will still be verified when you submit."
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// unavailable — deliberately not "we could not find it": an outage is not
|
|
62
|
+
// evidence that a business is unregistered.
|
|
63
|
+
return (
|
|
64
|
+
<NeutralPanel
|
|
65
|
+
title="The register is temporarily unavailable"
|
|
66
|
+
body="You can continue, and we will check it shortly."
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function NeutralPanel({ title, body }: { title: string; body: string }): React.ReactElement {
|
|
72
|
+
const { colors } = useTheme();
|
|
73
|
+
return (
|
|
74
|
+
<View
|
|
75
|
+
style={{
|
|
76
|
+
borderWidth: 1,
|
|
77
|
+
borderColor: colors.border,
|
|
78
|
+
borderRadius: radius.sm,
|
|
79
|
+
backgroundColor: colors.backgroundSecondary,
|
|
80
|
+
padding: spacing.md,
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<MyazaText variant="label">{title}</MyazaText>
|
|
84
|
+
<MyazaText variant="bodySmall" color={colors.textSecondary} style={{ marginTop: 2 }}>
|
|
85
|
+
{body}
|
|
86
|
+
</MyazaText>
|
|
87
|
+
</View>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { MyazaInput } from '../components/MyazaInput';
|
|
5
|
+
import { spacing } from '../config/theme';
|
|
6
|
+
import { CompanyInfoFields } from './CompanyInfoFields';
|
|
7
|
+
import { isValidContactEmail } from '../config/contact';
|
|
8
|
+
import type { BusinessState } from '../store/state';
|
|
9
|
+
import type { BusinessProductDef } from '../config/business';
|
|
10
|
+
import type { RegistrationHint } from '../config/registrationHint';
|
|
11
|
+
import type { CompanyInfoField, CompanyInfoMode } from '../types/business';
|
|
12
|
+
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// The DETAILS screen of the business step: confirming what the register said.
|
|
15
|
+
//
|
|
16
|
+
// Registration number and name are editable here too — this is also where
|
|
17
|
+
// manual entry lands, for the companies no search index carries. Split from
|
|
18
|
+
// BusinessDetailsStep (200-line rule); field order mirrors the web SDK.
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
export function BusinessDetailsFields({
|
|
22
|
+
business,
|
|
23
|
+
productDef,
|
|
24
|
+
regHint,
|
|
25
|
+
numberValid,
|
|
26
|
+
formatOk,
|
|
27
|
+
requireName,
|
|
28
|
+
country,
|
|
29
|
+
modes,
|
|
30
|
+
showCompanyInfo,
|
|
31
|
+
showContactEmail,
|
|
32
|
+
onChange,
|
|
33
|
+
}: {
|
|
34
|
+
business: BusinessState;
|
|
35
|
+
productDef: BusinessProductDef;
|
|
36
|
+
regHint: RegistrationHint;
|
|
37
|
+
numberValid: boolean;
|
|
38
|
+
formatOk: boolean;
|
|
39
|
+
requireName: boolean;
|
|
40
|
+
country: string;
|
|
41
|
+
modes: Record<CompanyInfoField, CompanyInfoMode>;
|
|
42
|
+
showCompanyInfo: boolean;
|
|
43
|
+
showContactEmail: boolean;
|
|
44
|
+
onChange: <K extends keyof BusinessState>(key: K, value: BusinessState[K]) => void;
|
|
45
|
+
}): React.ReactElement {
|
|
46
|
+
const regNumber = business.registrationNumber;
|
|
47
|
+
const contactEmail = business.contactEmail;
|
|
48
|
+
const contactEmailInvalid =
|
|
49
|
+
contactEmail.trim() !== '' && !isValidContactEmail(contactEmail.trim());
|
|
50
|
+
return (
|
|
51
|
+
<View>
|
|
52
|
+
<MyazaInput
|
|
53
|
+
label={productDef.inputLabel}
|
|
54
|
+
value={regNumber}
|
|
55
|
+
onChangeText={(text) => onChange('registrationNumber', text)}
|
|
56
|
+
placeholder={regHint.placeholder}
|
|
57
|
+
autoCapitalize="characters"
|
|
58
|
+
autoCorrect={false}
|
|
59
|
+
// Live like the web SDK: a wrong CAC prefix is corrected on the spot,
|
|
60
|
+
// not discovered on Continue. The registry tip fills the same slot
|
|
61
|
+
// until there is an error to show.
|
|
62
|
+
error={
|
|
63
|
+
regNumber !== '' && !numberValid
|
|
64
|
+
? (!formatOk && regHint.formatError) ||
|
|
65
|
+
`Enter a valid ${productDef.inputLabel.toLowerCase()}.`
|
|
66
|
+
: null
|
|
67
|
+
}
|
|
68
|
+
helper={regHint.tip ?? undefined}
|
|
69
|
+
/>
|
|
70
|
+
|
|
71
|
+
<View style={{ height: spacing.md }} />
|
|
72
|
+
<MyazaInput
|
|
73
|
+
label={`Registered business name${requireName ? '' : ' (optional)'}`}
|
|
74
|
+
value={business.registrationName}
|
|
75
|
+
onChangeText={(text) => onChange('registrationName', text)}
|
|
76
|
+
placeholder="Enter the registered business name"
|
|
77
|
+
autoCorrect={false}
|
|
78
|
+
/>
|
|
79
|
+
|
|
80
|
+
{showCompanyInfo ? (
|
|
81
|
+
<>
|
|
82
|
+
<View style={{ height: spacing.md }} />
|
|
83
|
+
<CompanyInfoFields
|
|
84
|
+
values={business}
|
|
85
|
+
modes={modes}
|
|
86
|
+
country={country}
|
|
87
|
+
onChange={(field, value) => onChange(field, value)}
|
|
88
|
+
/>
|
|
89
|
+
</>
|
|
90
|
+
) : null}
|
|
91
|
+
|
|
92
|
+
{showContactEmail ? (
|
|
93
|
+
<>
|
|
94
|
+
<View style={{ height: spacing.md }} />
|
|
95
|
+
<MyazaInput
|
|
96
|
+
label="Contact email for owner verification (optional)"
|
|
97
|
+
value={contactEmail}
|
|
98
|
+
onChangeText={(text) => onChange('contactEmail', text)}
|
|
99
|
+
placeholder="admin@company.com"
|
|
100
|
+
keyboardType="email-address"
|
|
101
|
+
autoCapitalize="none"
|
|
102
|
+
helper="We'll email this address a link for your directors and owners to verify their identity."
|
|
103
|
+
error={contactEmailInvalid ? 'Enter a valid email address.' : null}
|
|
104
|
+
/>
|
|
105
|
+
</>
|
|
106
|
+
) : null}
|
|
107
|
+
</View>
|
|
108
|
+
);
|
|
109
|
+
}
|