@myazahq/kyc-sdk-react-native 2.0.2 → 2.1.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/README.md +111 -8
- package/android/build.gradle +6 -0
- package/android/src/main/AndroidManifest.xml +14 -4
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaEmrtd.kt +358 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +88 -1
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaRectDetector.kt +34 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +118 -0
- package/android/src/main/java/com/margelo/nitro/myazakyc/Jp2Decoder.kt +117 -0
- package/app.plugin.js +102 -13
- package/ios/HybridMyazaEmrtd.swift +447 -0
- package/ios/HybridMyazaFaceDetector.swift +104 -2
- package/ios/HybridMyazaRectDetector.swift +103 -0
- package/ios/HybridMyazaTextRecognizer.swift +111 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/KycSdkReactNative+autolinking.cmake +87 -0
- package/nitrogen/generated/android/KycSdkReactNative+autolinking.gradle +27 -0
- package/nitrogen/generated/android/KycSdkReactNativeOnLoad.cpp +102 -0
- package/nitrogen/generated/android/KycSdkReactNativeOnLoad.hpp +34 -0
- package/nitrogen/generated/android/c++/JDetectedRect.hpp +81 -0
- package/nitrogen/generated/android/c++/JEmrtdApduResponse.hpp +61 -0
- package/nitrogen/generated/android/c++/JEmrtdTagInfo.hpp +61 -0
- package/nitrogen/generated/android/c++/JFaceResult.hpp +113 -0
- package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.cpp +176 -0
- package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.hpp +75 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +60 -0
- package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.cpp +60 -0
- package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.hpp +63 -0
- package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +80 -0
- package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +64 -0
- package/nitrogen/generated/android/c++/JTextResult.hpp +76 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/DetectedRect.kt +81 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdApduResponse.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdTagInfo.kt +56 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/FaceResult.kt +121 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaEmrtdSpec.kt +103 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +55 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaRectDetectorSpec.kt +55 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +60 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/KycSdkReactNativeOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/TextResult.kt +51 -0
- package/nitrogen/generated/ios/KycSdkReactNative+autolinking.rb +62 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +143 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +423 -0
- package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Umbrella.hpp +81 -0
- package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.mm +57 -0
- package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.swift +62 -0
- package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.hpp +184 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +87 -0
- package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.hpp +87 -0
- package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +98 -0
- package/nitrogen/generated/ios/swift/DetectedRect.swift +59 -0
- package/nitrogen/generated/ios/swift/EmrtdApduResponse.swift +34 -0
- package/nitrogen/generated/ios/swift/EmrtdTagInfo.swift +34 -0
- package/nitrogen/generated/ios/swift/FaceResult.swift +99 -0
- package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_EmrtdApduResponse.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_EmrtdTagInfo.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_TextResult.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec.swift +67 -0
- package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec_cxx.swift +317 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +143 -0
- package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec.swift +56 -0
- package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec_cxx.swift +143 -0
- package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +57 -0
- package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +162 -0
- package/nitrogen/generated/ios/swift/TextResult.swift +35 -0
- package/nitrogen/generated/shared/c++/DetectedRect.hpp +107 -0
- package/nitrogen/generated/shared/c++/EmrtdApduResponse.hpp +87 -0
- package/nitrogen/generated/shared/c++/EmrtdTagInfo.hpp +87 -0
- package/nitrogen/generated/shared/c++/FaceResult.hpp +139 -0
- package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.cpp +33 -0
- package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.hpp +80 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.cpp +21 -0
- package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.hpp +67 -0
- package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +22 -0
- package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +70 -0
- package/nitrogen/generated/shared/c++/TextResult.hpp +84 -0
- package/package.json +16 -4
- package/src/MyazaKYC.tsx +115 -22
- package/src/capture/documentIdentity.ts +153 -0
- package/src/capture/documentSignals.ts +177 -0
- package/src/capture/documentTextGate.ts +171 -0
- package/src/capture/hints.ts +57 -0
- package/src/capture/index.ts +24 -0
- package/src/capture/rectDetector.ts +84 -0
- package/src/capture/rectGate.ts +91 -0
- package/src/capture/useAutoCapture.ts +198 -0
- package/src/components/CameraViewfinder.tsx +226 -70
- package/src/components/ContactCodeEntry.tsx +139 -0
- package/src/components/CountryField.tsx +87 -0
- package/src/components/CountryFlag.tsx +15 -15
- package/src/components/DialCodePicker.tsx +186 -0
- package/src/components/DocumentCropper.tsx +1 -1
- package/src/components/DocumentReview.tsx +142 -0
- package/src/components/DocumentReviewSide.tsx +135 -0
- package/src/components/DocumentReviewZoom.tsx +119 -0
- package/src/components/ExpiryCountdown.tsx +52 -0
- package/src/components/FlashOverlay.tsx +156 -0
- package/src/components/Icon.tsx +67 -1
- package/src/components/KycFlow.tsx +66 -94
- package/src/components/KycSheet.tsx +148 -18
- package/src/components/MediaSourceSheet.tsx +163 -0
- package/src/components/MyazaButton.tsx +14 -2
- package/src/components/MyazaDateField.tsx +240 -0
- package/src/components/MyazaInput.tsx +73 -26
- package/src/components/MyazaSelect.tsx +283 -0
- package/src/components/MyazaWordmark.tsx +33 -0
- package/src/components/OptionRow.tsx +132 -0
- package/src/components/PhoneNumberInput.tsx +149 -0
- package/src/components/PoweredBy.tsx +89 -0
- package/src/components/ReadyPrimer.tsx +149 -0
- package/src/components/StepView.tsx +91 -0
- package/src/components/Typography.tsx +18 -3
- package/src/components/VerifiedNotice.tsx +38 -0
- package/src/components/WhatsAppIcon.tsx +29 -0
- package/src/components/WorkflowGate.tsx +128 -0
- package/src/components/brand-font.ts +113 -0
- package/src/components/flashHoleGeometry.ts +35 -0
- package/src/components/flow/FatalConfigError.tsx +56 -0
- package/src/components/fonts.ts +2 -19
- package/src/components/readyPrimerContent.ts +52 -0
- package/src/components/runtime.tsx +38 -43
- package/src/components/stepHeaderMeta.tsx +87 -0
- package/src/components/theme-provider.tsx +89 -0
- package/src/components/useWorkflowMount.ts +116 -0
- package/src/components/viewfinder/CardGuide.tsx +60 -0
- package/src/components/viewfinder/DocumentGhost.tsx +149 -0
- package/src/components/viewfinder/ImmersiveBottomBar.tsx +172 -0
- package/src/components/viewfinder/ImmersiveControls.tsx +127 -0
- package/src/components/viewfinder/ImmersiveGuide.tsx +174 -0
- package/src/components/viewfinder/ImmersiveOverlay.tsx +79 -0
- package/src/components/viewfinder/ViewfinderControls.tsx +154 -0
- package/src/config/brand.ts +97 -0
- package/src/config/business.ts +179 -0
- package/src/config/businessSteps.ts +105 -0
- package/src/config/contact.ts +159 -0
- package/src/config/countryNames.g.ts +254 -0
- package/src/config/currencyFlags.ts +144 -0
- package/src/config/font-resolve.ts +78 -0
- package/src/config/idTypes.ts +96 -1
- package/src/config/keyPeople.ts +152 -0
- package/src/config/phone.ts +39 -0
- package/src/config/proofOfAddress.ts +75 -0
- package/src/config/questionnaire.ts +153 -0
- package/src/config/regions.ts +118 -0
- package/src/config/registrationHint.ts +87 -0
- package/src/config/stepOrder.ts +118 -0
- package/src/config/theme.ts +38 -2
- package/src/config/workflowMerge.ts +124 -0
- package/src/emrtd/bac.ts +128 -0
- package/src/emrtd/bytes.ts +127 -0
- package/src/emrtd/crypto.ts +163 -0
- package/src/emrtd/der.ts +192 -0
- package/src/emrtd/dg2.ts +111 -0
- package/src/emrtd/files.ts +215 -0
- package/src/emrtd/index.ts +30 -0
- package/src/emrtd/mrzKey.ts +54 -0
- package/src/emrtd/native.ts +79 -0
- package/src/emrtd/optionalRead.ts +42 -0
- package/src/emrtd/read.ts +224 -0
- package/src/emrtd/retry.ts +98 -0
- package/src/emrtd/secureMessaging.ts +178 -0
- package/src/emrtd/session.ts +200 -0
- package/src/emrtd/stages.ts +122 -0
- package/src/index.ts +110 -0
- package/src/lib/calendar.ts +71 -0
- package/src/lib/use-keyboard-inset.ts +34 -0
- package/src/liveness/challengeManager.ts +27 -1
- package/src/liveness/faceContinuity.ts +153 -0
- package/src/liveness/flashDetector.ts +145 -0
- package/src/liveness/flashReadyGate.ts +106 -0
- package/src/liveness/flashRunner.ts +118 -0
- package/src/liveness/integritySignals.ts +60 -0
- package/src/liveness/livenessState.ts +139 -0
- package/src/liveness/speech.ts +19 -1
- package/src/liveness/types.ts +48 -1
- package/src/liveness/useFlashPhase.ts +78 -0
- package/src/liveness/useLiveness.ts +260 -75
- package/src/liveness/visionCameraFaceDetector.ts +14 -0
- package/src/mrz/extract.ts +101 -0
- package/src/mrz/parse.ts +178 -0
- package/src/mrz/textRecognizer.ts +149 -0
- package/src/screens/ApplicantRoleStep.tsx +331 -0
- package/src/screens/BusinessDetailsStep.tsx +212 -0
- package/src/screens/BusinessDocumentSlot.tsx +167 -0
- package/src/screens/BusinessDocumentsStep.tsx +213 -0
- package/src/screens/BusinessKeyPeopleStep.tsx +151 -0
- package/src/screens/BusinessKeyPersonRow.tsx +170 -0
- package/src/screens/CompanyInfoFields.tsx +83 -0
- package/src/screens/ConsentStep.tsx +68 -68
- package/src/screens/ContactChannelChoice.tsx +133 -0
- package/src/screens/ContactCodeStep.tsx +130 -0
- package/src/screens/ContactDestinationField.tsx +70 -0
- package/src/screens/ContactFooterNote.tsx +34 -0
- package/src/screens/ContactVerificationStep.tsx +199 -0
- package/src/screens/ContactVerifiedPanel.tsx +36 -0
- package/src/screens/CountrySelectStep.tsx +210 -0
- package/src/screens/DocumentCaptureStep.tsx +156 -132
- package/src/screens/IdInputStep.tsx +13 -5
- package/src/screens/IdTypeStep.tsx +74 -33
- package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
- package/src/screens/LivenessStep.tsx +155 -247
- package/src/screens/MrzScanView.tsx +173 -0
- package/src/screens/NfcStep.tsx +290 -0
- package/src/screens/ProofOfAddressStep.tsx +238 -0
- package/src/screens/QuestionnaireField.tsx +219 -0
- package/src/screens/QuestionnaireMoneyField.tsx +134 -0
- package/src/screens/QuestionnaireStep.tsx +76 -0
- package/src/screens/SubmittedStep.tsx +5 -0
- package/src/screens/consent/model.ts +109 -0
- package/src/screens/document/CameraPhase.tsx +150 -0
- package/src/screens/document/RequiredPill.tsx +60 -0
- package/src/screens/liveness/LightingBanner.tsx +50 -0
- package/src/screens/liveness/LivenessOutcome.tsx +109 -0
- package/src/screens/liveness/ProgressDots.tsx +67 -0
- package/src/screens/liveness/SelfiePreview.tsx +39 -0
- package/src/screens/liveness/constants.ts +31 -0
- package/src/screens/liveness/index.ts +8 -0
- package/src/screens/liveness/resolveGuidance.ts +27 -0
- package/src/screens/liveness/useSelfieUpload.ts +107 -0
- package/src/screens/nfc/NfcMrzPrompt.tsx +40 -0
- package/src/screens/nfc/NfcReadBody.tsx +63 -0
- package/src/screens/nfc/NfcReadProgress.tsx +96 -0
- package/src/screens/nfc/NfcScanIllustration.tsx +244 -0
- package/src/screens/nfc/NfcScannedSummary.tsx +74 -0
- package/src/screens/nfc/NfcSuccessPanel.tsx +162 -0
- package/src/screens/nfc/availability.ts +73 -0
- package/src/screens/nfc/readErrorMessage.ts +93 -0
- package/src/screens/nfc/useChipPortrait.ts +58 -0
- package/src/screens/nfc/useNfcAvailability.ts +82 -0
- package/src/screens/useFlashHole.ts +105 -0
- package/src/screens/useFlashSequence.ts +96 -0
- package/src/screens/usePoaAttach.ts +121 -0
- package/src/services/api-types.ts +164 -0
- package/src/services/api-verify-types.ts +99 -0
- package/src/services/api.ts +72 -102
- package/src/services/cardCrop.ts +25 -10
- package/src/services/contactErrors.ts +43 -0
- package/src/services/deviceMetadata.ts +1 -1
- package/src/services/documentPicker.ts +20 -0
- package/src/services/fingerprint-sources.ts +129 -0
- package/src/services/fingerprint.ts +101 -0
- package/src/services/haptics.ts +38 -0
- package/src/services/mediaCompress.ts +7 -2
- package/src/services/resolveUrl.ts +2 -2
- package/src/services/workflowGate.ts +138 -0
- package/src/specs/MyazaEmrtd.nitro.ts +114 -0
- package/src/specs/MyazaFaceDetector.nitro.ts +10 -0
- package/src/specs/MyazaRectDetector.nitro.ts +50 -0
- package/src/specs/MyazaTextRecognizer.nitro.ts +48 -0
- package/src/store/derive.ts +225 -0
- package/src/store/kycStore.ts +250 -149
- package/src/store/state.ts +253 -0
- package/src/store/submit.ts +101 -0
- package/src/store/submitApplicant.ts +81 -0
- package/src/types/appearance.ts +113 -0
- package/src/types/business.ts +131 -0
- package/src/types/config.ts +150 -124
- package/src/types/id-types.ts +57 -0
- package/src/types/verification.ts +5 -0
- package/src/types/workflow.ts +141 -0
- package/src/utils/amountFormat.ts +73 -0
- package/src/utils/tokens.ts +4 -2
- package/src/__tests__/cardCrop.test.ts +0 -39
- package/src/__tests__/deviceMetadata.test.ts +0 -34
- package/src/__tests__/errors.test.ts +0 -34
- package/src/__tests__/flow.test.ts +0 -61
- package/src/__tests__/gestureDetector.test.ts +0 -37
- package/src/__tests__/liveness.test.ts +0 -112
- package/src/__tests__/resolveUrl.test.ts +0 -64
- package/src/__tests__/validators.test.ts +0 -38
|
@@ -2,16 +2,35 @@ import React, { useMemo } from 'react';
|
|
|
2
2
|
import { Pressable, View } from 'react-native';
|
|
3
3
|
|
|
4
4
|
import { radius, spacing } from '../config/theme';
|
|
5
|
-
import { ID_TYPES } from '../config/idTypes';
|
|
5
|
+
import { ID_TYPES, resolveIdTypeDefinition } from '../config/idTypes';
|
|
6
6
|
import type { IdType, IdTypeDefinition } from '../types/config';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
useEffectiveCountry,
|
|
9
|
+
useKyc,
|
|
10
|
+
useKycConfig,
|
|
11
|
+
useKycStore,
|
|
12
|
+
useTheme,
|
|
13
|
+
} from '../components/runtime';
|
|
8
14
|
import { MyazaText } from '../components/Typography';
|
|
9
|
-
import { MyazaButton } from '../components/MyazaButton';
|
|
10
15
|
import { MyazaPulseLoader } from '../components/MyazaPulseLoader';
|
|
11
16
|
import { Icon, type IconName } from '../components/Icon';
|
|
12
17
|
|
|
13
18
|
// ID-type picker — 1:1 with the Flutter SDK's IdTypeScreen: a list of cards
|
|
14
|
-
// (icon tile + label +
|
|
19
|
+
// (icon tile + label + forward chevron) that ADVANCE ON TAP.
|
|
20
|
+
//
|
|
21
|
+
// No Continue button and no radio, matching Flutter and the web SDK. It is a
|
|
22
|
+
// single-select list with nothing else on the step to confirm, so Continue only
|
|
23
|
+
// restated a decision already made; and country-select already advances on tap,
|
|
24
|
+
// so a second list that behaved differently taught users one rule then broke
|
|
25
|
+
// it. A mis-tap costs one Back. The affordance is therefore a "go to the next
|
|
26
|
+
// step" chevron — a radio would imply a select-then-confirm this flow does not
|
|
27
|
+
// have.
|
|
28
|
+
//
|
|
29
|
+
// The advance reads the TAPPED value through the store, never the
|
|
30
|
+
// `selectedIdType` bound at render: that closure is a step behind inside the
|
|
31
|
+
// handler (and empty on the first selection), which would route a document ID
|
|
32
|
+
// to the number-only step. Going through `store.getState()` is what makes the
|
|
33
|
+
// write visible to `nextStep()`, since zustand commits synchronously.
|
|
15
34
|
|
|
16
35
|
const ICON_FOR: Record<string, IconName> = {
|
|
17
36
|
bvn: 'landmark', // Bank Verification Number → bank/landmark
|
|
@@ -31,25 +50,54 @@ const ICON_FOR: Record<string, IconName> = {
|
|
|
31
50
|
export function IdTypeStep(): React.ReactElement {
|
|
32
51
|
const { colors } = useTheme();
|
|
33
52
|
const config = useKycConfig();
|
|
53
|
+
const country = useEffectiveCountry();
|
|
54
|
+
const store = useKycStore();
|
|
34
55
|
const serverConfig = useKyc((s) => s.serverConfig);
|
|
35
56
|
const selectedIdType = useKyc((s) => s.selectedIdType);
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
|
|
58
|
+
// Select AND advance in one tap. Both go through the store so `nextStep`
|
|
59
|
+
// routes off the value just written — a document ID to document-capture, a
|
|
60
|
+
// number-only ID to id-input.
|
|
61
|
+
const pick = (key: IdType): void => {
|
|
62
|
+
store.getState().setIdType(key);
|
|
63
|
+
store.getState().nextStep();
|
|
64
|
+
};
|
|
38
65
|
|
|
39
66
|
const available = useMemo<IdTypeDefinition[]>(() => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
67
|
+
// In a multi-region flow the PICKED country's own `idTypes` narrow the
|
|
68
|
+
// list; otherwise the top-level prop applies. Absent or empty means every
|
|
69
|
+
// granted type, which is the server's own "unset = all" semantic.
|
|
70
|
+
const perCountry = (config.countries ?? []).find(
|
|
71
|
+
(e) => e.country.toUpperCase() === country.toUpperCase(),
|
|
72
|
+
)?.idTypes;
|
|
73
|
+
const keys = (perCountry?.length ? perCountry : config.idTypes) as
|
|
74
|
+
| readonly string[]
|
|
75
|
+
| undefined;
|
|
76
|
+
const allowed = (key: string): boolean => !keys?.length || keys.includes(key);
|
|
77
|
+
|
|
78
|
+
// Built from the SERVER rows, not the curated table. The curated table only
|
|
79
|
+
// knows the five gov-DB countries; a Global-Documents flow offers ~240, and
|
|
80
|
+
// filtering against the table rendered an empty list — "no ID types are
|
|
81
|
+
// enabled" — for every country outside it. The row carries the label and
|
|
82
|
+
// capture shape, so an uncurated country renders correctly from it.
|
|
44
83
|
if (serverConfig.status === 'ready') {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
84
|
+
return serverConfig.idTypes
|
|
85
|
+
.filter((row) => row.country === country && allowed(row.idType))
|
|
86
|
+
.map((row) =>
|
|
87
|
+
resolveIdTypeDefinition(country, row.idType, {
|
|
88
|
+
label: row.label,
|
|
89
|
+
requiresDocumentCapture: row.requiresDocumentCapture,
|
|
90
|
+
scanSides: row.scanSides,
|
|
91
|
+
supportsNfc: row.supportsNfc,
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
49
94
|
}
|
|
50
95
|
if (serverConfig.status === 'loading') return [];
|
|
51
|
-
|
|
52
|
-
|
|
96
|
+
// Config failed to load: fall back to the curated list. The server still
|
|
97
|
+
// rejects anything actually disabled, so this is permissive, not unsafe.
|
|
98
|
+
const table = ID_TYPES as Record<string, readonly IdTypeDefinition[] | undefined>;
|
|
99
|
+
return (table[country.toUpperCase()] ?? []).filter((t) => allowed(t.key));
|
|
100
|
+
}, [country, config.idTypes, config.countries, serverConfig]);
|
|
53
101
|
|
|
54
102
|
if (serverConfig.status === 'loading') {
|
|
55
103
|
return (
|
|
@@ -84,7 +132,9 @@ export function IdTypeStep(): React.ReactElement {
|
|
|
84
132
|
return (
|
|
85
133
|
<Pressable
|
|
86
134
|
key={t.key}
|
|
87
|
-
onPress={() =>
|
|
135
|
+
onPress={() => pick(t.key)}
|
|
136
|
+
accessibilityRole="button"
|
|
137
|
+
accessibilityLabel={t.label}
|
|
88
138
|
style={{
|
|
89
139
|
marginBottom: spacing.sm,
|
|
90
140
|
borderRadius: radius.md,
|
|
@@ -118,25 +168,16 @@ export function IdTypeStep(): React.ReactElement {
|
|
|
118
168
|
{t.label}
|
|
119
169
|
</MyazaText>
|
|
120
170
|
<View style={{ width: spacing.md }} />
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
alignItems: 'center',
|
|
129
|
-
justifyContent: 'center',
|
|
130
|
-
}}
|
|
131
|
-
>
|
|
132
|
-
{selected ? <View style={{ width: 12, height: 12, borderRadius: 6, backgroundColor: colors.primary }} /> : null}
|
|
133
|
-
</View>
|
|
171
|
+
{/* Forward chevron, not a radio: the tap advances, so the icon has
|
|
172
|
+
to promise the next step rather than a pending confirmation. */}
|
|
173
|
+
<Icon
|
|
174
|
+
name="chevron-right"
|
|
175
|
+
size={20}
|
|
176
|
+
color={selected ? colors.primary : colors.gray400}
|
|
177
|
+
/>
|
|
134
178
|
</Pressable>
|
|
135
179
|
);
|
|
136
180
|
})}
|
|
137
|
-
|
|
138
|
-
<View style={{ height: spacing.lg }} />
|
|
139
|
-
<MyazaButton label="Continue" onPress={selectedIdType ? nextStep : undefined} disabled={!selectedIdType} />
|
|
140
181
|
</View>
|
|
141
182
|
);
|
|
142
183
|
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Pressable, Share, View } from 'react-native';
|
|
3
|
+
import * as Clipboard from 'expo-clipboard';
|
|
4
|
+
|
|
5
|
+
import { radius, spacing } from '../config/theme';
|
|
6
|
+
import { useKyc, useTheme } from '../components/runtime';
|
|
7
|
+
import { MyazaText } from '../components/Typography';
|
|
8
|
+
import { Icon } from '../components/Icon';
|
|
9
|
+
import { CountryFlag } from '../components/CountryFlag';
|
|
10
|
+
import { regionCountryName } from '../config/regions';
|
|
11
|
+
import { KEY_PERSON_ROLE_LABELS } from '../config/keyPeople';
|
|
12
|
+
import type { KeyPersonInvite } from '../services/api-types';
|
|
13
|
+
import type { ApplicantRole, KeyPersonRole } from '../types/business';
|
|
14
|
+
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// "Awaiting users" — the KYB success screen's people section. Every person the
|
|
17
|
+
// review is waiting on, grouped by role, each with a status pill; pending
|
|
18
|
+
// people carry a shareable verification link, and the APPLICANT (who verified
|
|
19
|
+
// in-flow) appears with a green Submitted pill so the picture is complete.
|
|
20
|
+
// Mirrors the web SDK's KeyPeopleInviteLinks 1:1 — mobile shares via the
|
|
21
|
+
// native sheet (reaches WhatsApp/SMS, still offers Copy inside).
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
type AwaitRole = ApplicantRole;
|
|
25
|
+
|
|
26
|
+
interface AwaitRow {
|
|
27
|
+
name: string;
|
|
28
|
+
role: AwaitRole;
|
|
29
|
+
pct: string | null;
|
|
30
|
+
country: string | null;
|
|
31
|
+
status: 'submitted' | 'pending';
|
|
32
|
+
inviteUrl?: string;
|
|
33
|
+
isApplicant?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const ROLE_LABELS: Record<AwaitRole, string> = {
|
|
37
|
+
...KEY_PERSON_ROLE_LABELS,
|
|
38
|
+
authorized_representative: 'Authorized representative',
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const SECTIONS: Array<{ role: AwaitRole; label: string }> = [
|
|
42
|
+
{ role: 'beneficial_owner', label: 'UBOS' },
|
|
43
|
+
{ role: 'director', label: 'DIRECTORS' },
|
|
44
|
+
{ role: 'signatory', label: 'SIGNATORIES' },
|
|
45
|
+
{ role: 'shareholder', label: 'SHAREHOLDERS' },
|
|
46
|
+
{ role: 'authorized_representative', label: 'REPRESENTATIVES' },
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
export function KeyPeopleInviteLinks({
|
|
50
|
+
invites,
|
|
51
|
+
}: {
|
|
52
|
+
invites: KeyPersonInvite[];
|
|
53
|
+
}): React.ReactElement | null {
|
|
54
|
+
const { colors } = useTheme();
|
|
55
|
+
const app = useKyc((s) => s.businessApplication);
|
|
56
|
+
if (invites.length === 0) return null;
|
|
57
|
+
|
|
58
|
+
const rows: AwaitRow[] = [];
|
|
59
|
+
|
|
60
|
+
// The applicant themselves — verified in-flow, nothing more to do.
|
|
61
|
+
if (app.applicantRole) {
|
|
62
|
+
const self =
|
|
63
|
+
app.applicantKeyPersonIndex !== null ? app.keyPeople[app.applicantKeyPersonIndex] : null;
|
|
64
|
+
rows.push({
|
|
65
|
+
name: (self?.name ?? app.applicantName).trim() || 'You',
|
|
66
|
+
role: (self?.role ?? app.applicantRole) as AwaitRole,
|
|
67
|
+
pct: self?.ownershipPct.trim() || null,
|
|
68
|
+
country: self?.country.trim() ? self.country.trim().toUpperCase() : null,
|
|
69
|
+
status: 'submitted',
|
|
70
|
+
isApplicant: true,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Everyone the server minted a link for — enriched from the entered rows.
|
|
75
|
+
for (const invite of invites) {
|
|
76
|
+
const entered = app.keyPeople.find((p) => p.name.trim() === invite.name.trim());
|
|
77
|
+
rows.push({
|
|
78
|
+
name: invite.name,
|
|
79
|
+
role: (entered?.role ?? 'director') as AwaitRole,
|
|
80
|
+
pct: entered?.ownershipPct.trim() || null,
|
|
81
|
+
country: entered?.country.trim() ? entered.country.trim().toUpperCase() : null,
|
|
82
|
+
status: 'pending',
|
|
83
|
+
inviteUrl: invite.inviteUrl,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
<View style={{ width: '100%', marginTop: spacing.lg }}>
|
|
89
|
+
<MyazaText variant="bodySmall" color={colors.textMuted} style={{ textAlign: 'center', marginBottom: spacing.md }}>
|
|
90
|
+
To complete the review, the people below must verify their identity with a KYC check.
|
|
91
|
+
Anyone with an email on file has already been sent their link.
|
|
92
|
+
</MyazaText>
|
|
93
|
+
|
|
94
|
+
{SECTIONS.map(({ role, label }) => {
|
|
95
|
+
const group = rows.filter((r) => r.role === role);
|
|
96
|
+
if (group.length === 0) return null;
|
|
97
|
+
return (
|
|
98
|
+
<View key={role} style={{ marginBottom: spacing.sm }}>
|
|
99
|
+
<MyazaText
|
|
100
|
+
variant="bodySmall"
|
|
101
|
+
color={colors.textMuted}
|
|
102
|
+
style={{ fontWeight: '600', letterSpacing: 1.2, marginBottom: spacing.xs + 2 }}
|
|
103
|
+
>
|
|
104
|
+
{label}
|
|
105
|
+
</MyazaText>
|
|
106
|
+
{group.map((row, i) => (
|
|
107
|
+
<AwaitCard key={`${role}-${i}`} row={row} />
|
|
108
|
+
))}
|
|
109
|
+
</View>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
|
|
113
|
+
<MyazaText variant="bodySmall" color={colors.textMuted} style={{ textAlign: 'center' }}>
|
|
114
|
+
Links are valid for 14 days.
|
|
115
|
+
</MyazaText>
|
|
116
|
+
</View>
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function AwaitCard({ row }: { row: AwaitRow }): React.ReactElement {
|
|
121
|
+
const { colors } = useTheme();
|
|
122
|
+
const submitted = row.status === 'submitted';
|
|
123
|
+
const [copied, setCopied] = useState(false);
|
|
124
|
+
|
|
125
|
+
const share = (): void => {
|
|
126
|
+
if (!row.inviteUrl) return;
|
|
127
|
+
Share.share({ message: row.inviteUrl }).catch(() => undefined);
|
|
128
|
+
};
|
|
129
|
+
const copy = (): void => {
|
|
130
|
+
if (!row.inviteUrl) return;
|
|
131
|
+
Clipboard.setStringAsync(row.inviteUrl)
|
|
132
|
+
.then(() => {
|
|
133
|
+
setCopied(true);
|
|
134
|
+
setTimeout(() => setCopied(false), 2000);
|
|
135
|
+
})
|
|
136
|
+
.catch(() => undefined);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<View
|
|
141
|
+
style={{
|
|
142
|
+
backgroundColor: colors.backgroundSecondary,
|
|
143
|
+
borderRadius: radius.md,
|
|
144
|
+
padding: spacing.md,
|
|
145
|
+
marginBottom: spacing.sm,
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
<View style={{ flexDirection: 'row', alignItems: 'flex-start' }}>
|
|
149
|
+
<View style={{ flex: 1, minWidth: 0, marginRight: spacing.sm }}>
|
|
150
|
+
<MyazaText variant="bodyMedium" numberOfLines={1} style={{ fontWeight: '700' }}>
|
|
151
|
+
{row.name}
|
|
152
|
+
{row.isApplicant ? (
|
|
153
|
+
<MyazaText variant="bodySmall" color={colors.textMuted}>
|
|
154
|
+
{' (you)'}
|
|
155
|
+
</MyazaText>
|
|
156
|
+
) : null}
|
|
157
|
+
</MyazaText>
|
|
158
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', flexWrap: 'wrap', marginTop: 2 }}>
|
|
159
|
+
<MyazaText variant="bodySmall" color={colors.textMuted}>
|
|
160
|
+
{ROLE_LABELS[row.role]}
|
|
161
|
+
{row.pct ? ` · ${row.pct}%` : ''}
|
|
162
|
+
{row.country ? ' · ' : ''}
|
|
163
|
+
</MyazaText>
|
|
164
|
+
{row.country ? (
|
|
165
|
+
<>
|
|
166
|
+
<CountryFlag country={row.country} size={14} />
|
|
167
|
+
<MyazaText variant="bodySmall" color={colors.textMuted}>
|
|
168
|
+
{` ${regionCountryName(row.country)}`}
|
|
169
|
+
</MyazaText>
|
|
170
|
+
</>
|
|
171
|
+
) : null}
|
|
172
|
+
</View>
|
|
173
|
+
</View>
|
|
174
|
+
<View
|
|
175
|
+
style={{
|
|
176
|
+
borderRadius: radius.full,
|
|
177
|
+
paddingHorizontal: spacing.sm + 4,
|
|
178
|
+
paddingVertical: spacing.xs,
|
|
179
|
+
backgroundColor: submitted ? colors.successBg : colors.primary100,
|
|
180
|
+
}}
|
|
181
|
+
>
|
|
182
|
+
<MyazaText
|
|
183
|
+
variant="bodySmall"
|
|
184
|
+
color={submitted ? colors.success : colors.primary}
|
|
185
|
+
style={{ fontWeight: '700', fontSize: 11, letterSpacing: 0.6 }}
|
|
186
|
+
>
|
|
187
|
+
{submitted ? 'SUBMITTED' : 'KYC PENDING'}
|
|
188
|
+
</MyazaText>
|
|
189
|
+
</View>
|
|
190
|
+
</View>
|
|
191
|
+
|
|
192
|
+
{row.status === 'pending' && row.inviteUrl ? (
|
|
193
|
+
// Both actions, since it's mobile: Copy for pasting anywhere, Share
|
|
194
|
+
// for the native sheet (WhatsApp/SMS directly).
|
|
195
|
+
<View style={{ flexDirection: 'row', marginTop: spacing.sm + 4 }}>
|
|
196
|
+
<Pressable
|
|
197
|
+
onPress={copy}
|
|
198
|
+
accessibilityRole="button"
|
|
199
|
+
accessibilityLabel={`Copy ${row.name}'s verification link`}
|
|
200
|
+
style={{
|
|
201
|
+
flex: 1,
|
|
202
|
+
flexDirection: 'row',
|
|
203
|
+
alignItems: 'center',
|
|
204
|
+
justifyContent: 'center',
|
|
205
|
+
backgroundColor: colors.primary100,
|
|
206
|
+
borderRadius: radius.full,
|
|
207
|
+
paddingVertical: spacing.sm + 2,
|
|
208
|
+
marginRight: spacing.sm,
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
<Icon name={copied ? 'check' : 'copy'} size={15} color={colors.primary} />
|
|
212
|
+
<MyazaText variant="bodySmall" color={colors.primary} style={{ fontWeight: '700', marginLeft: spacing.xs + 2 }}>
|
|
213
|
+
{copied ? 'Copied' : 'Copy link'}
|
|
214
|
+
</MyazaText>
|
|
215
|
+
</Pressable>
|
|
216
|
+
<Pressable
|
|
217
|
+
onPress={share}
|
|
218
|
+
accessibilityRole="button"
|
|
219
|
+
accessibilityLabel={`Share ${row.name}'s verification link`}
|
|
220
|
+
style={{
|
|
221
|
+
flex: 1,
|
|
222
|
+
flexDirection: 'row',
|
|
223
|
+
alignItems: 'center',
|
|
224
|
+
justifyContent: 'center',
|
|
225
|
+
backgroundColor: colors.primary100,
|
|
226
|
+
borderRadius: radius.full,
|
|
227
|
+
paddingVertical: spacing.sm + 2,
|
|
228
|
+
}}
|
|
229
|
+
>
|
|
230
|
+
<Icon name="share" size={15} color={colors.primary} />
|
|
231
|
+
<MyazaText variant="bodySmall" color={colors.primary} style={{ fontWeight: '700', marginLeft: spacing.xs + 2 }}>
|
|
232
|
+
Share
|
|
233
|
+
</MyazaText>
|
|
234
|
+
</Pressable>
|
|
235
|
+
</View>
|
|
236
|
+
) : null}
|
|
237
|
+
</View>
|
|
238
|
+
);
|
|
239
|
+
}
|