@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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { radius, spacing } from '../config/theme';
|
|
5
|
+
import { useKyc, useKycConfig, useKycStore, useTheme } from '../components/runtime';
|
|
6
|
+
import { MyazaText } from '../components/Typography';
|
|
7
|
+
import { MyazaButton } from '../components/MyazaButton';
|
|
8
|
+
import { BusinessKeyPersonRow } from './BusinessKeyPersonRow';
|
|
9
|
+
import { keyPeopleMinEntries } from '../config/businessSteps';
|
|
10
|
+
import {
|
|
11
|
+
emptyKeyPerson,
|
|
12
|
+
invalidKeyPersonRows,
|
|
13
|
+
isKeyPersonRowValid,
|
|
14
|
+
MAX_KEY_PEOPLE_ROWS,
|
|
15
|
+
type KeyPersonEntry,
|
|
16
|
+
} from '../config/keyPeople';
|
|
17
|
+
import type { KeyPersonRole } from '../types/business';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Applicant-declared directors, owners and signatories.
|
|
21
|
+
//
|
|
22
|
+
// The registry lookup discovers people too, and the server reconciles the two:
|
|
23
|
+
// someone the registry names who the applicant did NOT list is flagged
|
|
24
|
+
// `undisclosed`, which is a risk signal in its own right. So this screen is not
|
|
25
|
+
// merely data entry — what the applicant chooses to omit is evidence.
|
|
26
|
+
//
|
|
27
|
+
// Layout, copy and per-row design mirror the web SDK's BusinessKeyPeopleStep
|
|
28
|
+
// (and Flutter's screen) 1:1 — the description lives in the step HEADER, the
|
|
29
|
+
// hints are dashed cards, and each person is a bordered card with selects.
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
export const businessKeyPeopleMeta = {
|
|
33
|
+
title: 'Directors & owners',
|
|
34
|
+
description:
|
|
35
|
+
"List the company's directors and owners of 25% or more. Each will receive a link to verify their identity.",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export function BusinessKeyPeopleStep(): React.ReactElement {
|
|
39
|
+
const config = useKycConfig();
|
|
40
|
+
const store = useKycStore();
|
|
41
|
+
const { colors } = useTheme();
|
|
42
|
+
const rows = useKyc((s) => s.businessApplication.keyPeople);
|
|
43
|
+
const registryCountry = useKyc((s) => s.business.country);
|
|
44
|
+
|
|
45
|
+
const minEntries = keyPeopleMinEntries(config.business);
|
|
46
|
+
const validCount = rows.filter(isKeyPersonRowValid).length;
|
|
47
|
+
const invalidRows = invalidKeyPersonRows(rows);
|
|
48
|
+
|
|
49
|
+
// Combined ownership above 100% is factually impossible — catch the typo
|
|
50
|
+
// here rather than shipping it into the registry cross-check as a doomed
|
|
51
|
+
// mismatch. Under 100% is fine (not every owner has to be listed).
|
|
52
|
+
const totalPct = rows.reduce((sum, row) => {
|
|
53
|
+
const n = Number(row.ownershipPct);
|
|
54
|
+
return row.ownershipPct.trim() !== '' && Number.isFinite(n) ? sum + n : sum;
|
|
55
|
+
}, 0);
|
|
56
|
+
const overAllocated = totalPct > 100;
|
|
57
|
+
|
|
58
|
+
const canContinue = validCount >= minEntries && invalidRows.length === 0 && !overAllocated;
|
|
59
|
+
|
|
60
|
+
const update = (index: number, patch: Partial<KeyPersonEntry>): void => {
|
|
61
|
+
store.getState().setKeyPeople(rows.map((row, i) => (i === index ? { ...row, ...patch } : row)));
|
|
62
|
+
};
|
|
63
|
+
// New rows default to the business's registry country (picked on the
|
|
64
|
+
// details step) — most directors are local, and a foreign one just switches
|
|
65
|
+
// theirs. Mirrors the web and Flutter SDKs.
|
|
66
|
+
const defaultCountry = registryCountry ?? config.business?.country ?? '';
|
|
67
|
+
const add = (): void =>
|
|
68
|
+
store.getState().setKeyPeople([...rows, { ...emptyKeyPerson(), country: defaultCountry }]);
|
|
69
|
+
const remove = (index: number): void =>
|
|
70
|
+
store.getState().setKeyPeople(rows.filter((_, i) => i !== index));
|
|
71
|
+
|
|
72
|
+
const handleContinue = (): void => {
|
|
73
|
+
if (!canContinue) return;
|
|
74
|
+
// Half-typed rows are dropped rather than submitted: an entry the user
|
|
75
|
+
// abandoned is not a person they disclosed.
|
|
76
|
+
store.getState().setKeyPeople(rows.filter(isKeyPersonRowValid));
|
|
77
|
+
store.getState().nextStep();
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// The dashed hint boxes and their copy mirror the web SDK's step 1:1.
|
|
81
|
+
const hint =
|
|
82
|
+
rows.length === 0 && minEntries === 0
|
|
83
|
+
? "You can skip this if you're unsure — we'll identify directors and owners from the official registry. Adding them here speeds up the review."
|
|
84
|
+
: minEntries > 0 && validCount < minEntries
|
|
85
|
+
? `List at least ${minEntries} ${minEntries === 1 ? 'person' : 'people'} to continue${
|
|
86
|
+
validCount > 0 ? ` (${validCount} of ${minEntries} added)` : ''
|
|
87
|
+
}.`
|
|
88
|
+
: null;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<View>
|
|
92
|
+
{hint ? (
|
|
93
|
+
<View
|
|
94
|
+
style={{
|
|
95
|
+
borderWidth: 1,
|
|
96
|
+
borderStyle: 'dashed',
|
|
97
|
+
borderColor: colors.border,
|
|
98
|
+
borderRadius: radius.sm,
|
|
99
|
+
backgroundColor: colors.backgroundSecondary,
|
|
100
|
+
padding: spacing.md,
|
|
101
|
+
}}
|
|
102
|
+
>
|
|
103
|
+
<MyazaText variant="bodySmall" color={colors.textSecondary}>
|
|
104
|
+
{hint}
|
|
105
|
+
</MyazaText>
|
|
106
|
+
</View>
|
|
107
|
+
) : null}
|
|
108
|
+
{overAllocated ? (
|
|
109
|
+
<View
|
|
110
|
+
style={{
|
|
111
|
+
borderWidth: 1,
|
|
112
|
+
borderColor: `${colors.error}4D`,
|
|
113
|
+
borderRadius: radius.sm,
|
|
114
|
+
backgroundColor: colors.errorBg,
|
|
115
|
+
padding: spacing.md,
|
|
116
|
+
marginTop: hint ? spacing.sm : 0,
|
|
117
|
+
}}
|
|
118
|
+
>
|
|
119
|
+
<MyazaText variant="bodySmall" color={colors.error}>
|
|
120
|
+
{`The ownership percentages add up to ${totalPct}% — together they can't exceed 100%.`}
|
|
121
|
+
</MyazaText>
|
|
122
|
+
</View>
|
|
123
|
+
) : null}
|
|
124
|
+
|
|
125
|
+
{rows.map((row, index) => (
|
|
126
|
+
<BusinessKeyPersonRow
|
|
127
|
+
key={index}
|
|
128
|
+
row={row}
|
|
129
|
+
index={index}
|
|
130
|
+
onChange={(patch) => update(index, patch)}
|
|
131
|
+
onRemove={() => remove(index)}
|
|
132
|
+
uboThreshold={config.business?.keyPeople?.ownershipThreshold}
|
|
133
|
+
/>
|
|
134
|
+
))}
|
|
135
|
+
|
|
136
|
+
<View style={{ height: spacing.md }} />
|
|
137
|
+
{rows.length < MAX_KEY_PEOPLE_ROWS ? (
|
|
138
|
+
<MyazaButton label="Add a person" variant="outline" leadingIcon="user-plus" onPress={add} />
|
|
139
|
+
) : (
|
|
140
|
+
<MyazaText variant="bodySmall" color={colors.textMuted} style={{ textAlign: 'center' }}>
|
|
141
|
+
{`You can list up to ${MAX_KEY_PEOPLE_ROWS} people here.`}
|
|
142
|
+
</MyazaText>
|
|
143
|
+
)}
|
|
144
|
+
|
|
145
|
+
<View style={{ height: spacing.sm }} />
|
|
146
|
+
<MyazaButton label="Continue" onPress={handleContinue} disabled={!canContinue} />
|
|
147
|
+
</View>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export type { KeyPersonRole };
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Pressable, 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 { MyazaInput } from '../components/MyazaInput';
|
|
8
|
+
import { MyazaSelect } from '../components/MyazaSelect';
|
|
9
|
+
import { CountryField } from '../components/CountryField';
|
|
10
|
+
import type { DialCodeOption } from '../components/DialCodePicker';
|
|
11
|
+
import { Icon } from '../components/Icon';
|
|
12
|
+
import { ALL_REGION_CODES, regionCountryName } from '../config/regions';
|
|
13
|
+
import { isValidContactEmail } from '../config/contact';
|
|
14
|
+
import {
|
|
15
|
+
KEY_PERSON_ROLES,
|
|
16
|
+
KEY_PERSON_ROLE_LABELS,
|
|
17
|
+
type KeyPersonEntry,
|
|
18
|
+
} from '../config/keyPeople';
|
|
19
|
+
import type { KeyPersonRole } from '../types/business';
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// One editable director/owner row — mirrors the web SDK's BusinessKeyPersonRow
|
|
23
|
+
// and the Flutter row 1:1: bordered card, "PERSON N" header with remove, then
|
|
24
|
+
// full name → role (select) → ownership % → country (full ISO select — a key
|
|
25
|
+
// person's ID can be issued anywhere, not just the registry country) → email.
|
|
26
|
+
// Validation is LIVE per field, like the web: a broken value says so as it is
|
|
27
|
+
// typed, while blank optional fields stay quiet.
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
|
|
30
|
+
/** Whether the row's role is an ownership one (% is only meaningful then). */
|
|
31
|
+
function isOwnerRole(role: KeyPersonRole): boolean {
|
|
32
|
+
return role === 'beneficial_owner' || role === 'shareholder';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Every ISO-2 we can name, alphabetical — built once, shared by all rows. */
|
|
36
|
+
let countryOptions: DialCodeOption[] | null = null;
|
|
37
|
+
function allCountryOptions(): DialCodeOption[] {
|
|
38
|
+
countryOptions ??= [...ALL_REGION_CODES]
|
|
39
|
+
.map((code) => ({ code, name: regionCountryName(code) }))
|
|
40
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
41
|
+
return countryOptions;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function BusinessKeyPersonRow({
|
|
45
|
+
row,
|
|
46
|
+
index,
|
|
47
|
+
onChange,
|
|
48
|
+
onRemove,
|
|
49
|
+
uboThreshold = 25,
|
|
50
|
+
}: {
|
|
51
|
+
row: KeyPersonEntry;
|
|
52
|
+
index: number;
|
|
53
|
+
onChange: (patch: Partial<KeyPersonEntry>) => void;
|
|
54
|
+
onRemove: () => void;
|
|
55
|
+
/** Ownership % at/above which the server treats a person as a beneficial
|
|
56
|
+
* owner (the workflow's `keyPeople.ownershipThreshold`, default 25). */
|
|
57
|
+
uboThreshold?: number;
|
|
58
|
+
}): React.ReactElement {
|
|
59
|
+
const { colors } = useTheme();
|
|
60
|
+
const options = useMemo(allCountryOptions, []);
|
|
61
|
+
|
|
62
|
+
const nameInvalid = row.name !== '' && row.name.trim().length < 2;
|
|
63
|
+
const emailInvalid = row.email.trim() !== '' && !isValidContactEmail(row.email.trim());
|
|
64
|
+
const pct = row.ownershipPct.trim();
|
|
65
|
+
const pctNum = Number(pct);
|
|
66
|
+
const pctInvalid = pct !== '' && (!Number.isFinite(pctNum) || pctNum < 0 || pctNum > 100);
|
|
67
|
+
// Surface the regulatory consequence as feedback: at/above the threshold the
|
|
68
|
+
// server escalates this person to a beneficial owner regardless of the role
|
|
69
|
+
// picked. Quiet when they already chose UBO — nothing new to say.
|
|
70
|
+
const uboHint =
|
|
71
|
+
!pctInvalid && pct !== '' && pctNum >= uboThreshold && row.role !== 'beneficial_owner';
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<View
|
|
75
|
+
style={{
|
|
76
|
+
borderWidth: 1,
|
|
77
|
+
borderColor: colors.border,
|
|
78
|
+
borderRadius: radius.sm,
|
|
79
|
+
padding: spacing.md,
|
|
80
|
+
marginTop: spacing.sm + 4,
|
|
81
|
+
}}
|
|
82
|
+
>
|
|
83
|
+
<View style={{ flexDirection: 'row', alignItems: 'center', marginBottom: spacing.sm }}>
|
|
84
|
+
<MyazaText
|
|
85
|
+
variant="bodySmall"
|
|
86
|
+
color={colors.textSecondary}
|
|
87
|
+
style={{ flex: 1, fontWeight: '600', letterSpacing: 0.6 }}
|
|
88
|
+
>
|
|
89
|
+
{`PERSON ${index + 1}`}
|
|
90
|
+
</MyazaText>
|
|
91
|
+
<Pressable
|
|
92
|
+
onPress={onRemove}
|
|
93
|
+
accessibilityRole="button"
|
|
94
|
+
accessibilityLabel={`Remove person ${index + 1}`}
|
|
95
|
+
hitSlop={8}
|
|
96
|
+
>
|
|
97
|
+
<Icon name="close" size={16} color={colors.textMuted} />
|
|
98
|
+
</Pressable>
|
|
99
|
+
</View>
|
|
100
|
+
|
|
101
|
+
<MyazaInput
|
|
102
|
+
label="Full name"
|
|
103
|
+
value={row.name}
|
|
104
|
+
onChangeText={(name) => onChange({ name })}
|
|
105
|
+
placeholder="e.g. Bola Owner"
|
|
106
|
+
// It's a person's name — start every word capitalized.
|
|
107
|
+
autoCapitalize="words"
|
|
108
|
+
error={nameInvalid ? 'Enter the person’s full name.' : null}
|
|
109
|
+
/>
|
|
110
|
+
|
|
111
|
+
<View style={{ height: spacing.sm }} />
|
|
112
|
+
<MyazaText variant="bodySmall" style={{ fontWeight: '600', marginBottom: spacing.xs }}>
|
|
113
|
+
Role
|
|
114
|
+
</MyazaText>
|
|
115
|
+
<MyazaSelect
|
|
116
|
+
value={row.role}
|
|
117
|
+
sheetTitle="Role"
|
|
118
|
+
options={KEY_PERSON_ROLES.map((role) => ({
|
|
119
|
+
value: role,
|
|
120
|
+
label: KEY_PERSON_ROLE_LABELS[role],
|
|
121
|
+
}))}
|
|
122
|
+
onChange={(role) => onChange({ role })}
|
|
123
|
+
/>
|
|
124
|
+
|
|
125
|
+
<View style={{ height: spacing.sm }} />
|
|
126
|
+
<MyazaInput
|
|
127
|
+
label="Ownership % (optional)"
|
|
128
|
+
value={row.ownershipPct}
|
|
129
|
+
onChangeText={(ownershipPct) => onChange({ ownershipPct })}
|
|
130
|
+
placeholder={isOwnerRole(row.role) ? 'e.g. 60' : '—'}
|
|
131
|
+
keyboardType="decimal-pad"
|
|
132
|
+
suffix={
|
|
133
|
+
<MyazaText variant="bodySmall" color={colors.textMuted}>
|
|
134
|
+
%
|
|
135
|
+
</MyazaText>
|
|
136
|
+
}
|
|
137
|
+
error={pctInvalid ? 'Enter a value between 0 and 100.' : null}
|
|
138
|
+
helper={
|
|
139
|
+
uboHint
|
|
140
|
+
? `At ${uboThreshold}% or more, this person counts as a beneficial owner.`
|
|
141
|
+
: undefined
|
|
142
|
+
}
|
|
143
|
+
/>
|
|
144
|
+
|
|
145
|
+
<View style={{ height: spacing.sm }} />
|
|
146
|
+
<MyazaText variant="bodySmall" style={{ fontWeight: '600', marginBottom: spacing.xs }}>
|
|
147
|
+
Country <MyazaText variant="bodySmall" color={colors.textSecondary}>(where their ID was issued)</MyazaText>
|
|
148
|
+
</MyazaText>
|
|
149
|
+
{/* The SAME sheet as the phone field's dial-code picker (keyboard-aware,
|
|
150
|
+
autofocused search, results pinned above the keys) — minus the dial
|
|
151
|
+
codes. Two country pickers that feel different would read as a bug. */}
|
|
152
|
+
<CountryField
|
|
153
|
+
value={row.country || null}
|
|
154
|
+
options={options}
|
|
155
|
+
onChange={(country) => onChange({ country })}
|
|
156
|
+
/>
|
|
157
|
+
|
|
158
|
+
<View style={{ height: spacing.sm }} />
|
|
159
|
+
<MyazaInput
|
|
160
|
+
label="Email (optional — used to send their verification link)"
|
|
161
|
+
value={row.email}
|
|
162
|
+
onChangeText={(email) => onChange({ email })}
|
|
163
|
+
placeholder="name@company.com"
|
|
164
|
+
keyboardType="email-address"
|
|
165
|
+
autoCapitalize="none"
|
|
166
|
+
error={emailInvalid ? 'Enter a valid email address.' : null}
|
|
167
|
+
/>
|
|
168
|
+
</View>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { spacing } from '../config/theme';
|
|
5
|
+
import { useTheme } from '../components/runtime';
|
|
6
|
+
import { MyazaText } from '../components/Typography';
|
|
7
|
+
import { MyazaInput } from '../components/MyazaInput';
|
|
8
|
+
import type { CompanyInfoField, CompanyInfoMode } from '../types/business';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// The company profile a KYB workflow asks for.
|
|
12
|
+
//
|
|
13
|
+
// Split from BusinessDetailsStep (200-line rule). Each field's mode comes from
|
|
14
|
+
// the workflow and must match the server's resolution exactly — a field marked
|
|
15
|
+
// optional here but required there produces a 422 the user cannot act on.
|
|
16
|
+
// Section header, captions and placeholders mirror the web SDK's
|
|
17
|
+
// BusinessCompanyInfoFields 1:1.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
export const COMPANY_FIELD_LABELS: Record<CompanyInfoField, string> = {
|
|
21
|
+
address: 'Registered address',
|
|
22
|
+
email: 'Business email',
|
|
23
|
+
phone: 'Business phone',
|
|
24
|
+
website: 'Website',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const COMPANY_FIELD_PLACEHOLDERS: Record<CompanyInfoField, string> = {
|
|
28
|
+
address: 'e.g. 12 Marina Road, Lagos',
|
|
29
|
+
email: 'hello@company.com',
|
|
30
|
+
phone: '+234 800 000 0000',
|
|
31
|
+
website: 'company.com',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export function CompanyInfoFields({
|
|
35
|
+
fields,
|
|
36
|
+
modes,
|
|
37
|
+
values,
|
|
38
|
+
missing,
|
|
39
|
+
showErrors,
|
|
40
|
+
onChange,
|
|
41
|
+
}: {
|
|
42
|
+
fields: CompanyInfoField[];
|
|
43
|
+
modes: Record<CompanyInfoField, CompanyInfoMode>;
|
|
44
|
+
values: Record<CompanyInfoField, string>;
|
|
45
|
+
missing: CompanyInfoField[];
|
|
46
|
+
showErrors: boolean;
|
|
47
|
+
onChange: (field: CompanyInfoField, value: string) => void;
|
|
48
|
+
}): React.ReactElement | null {
|
|
49
|
+
const { colors } = useTheme();
|
|
50
|
+
if (fields.length === 0) return null;
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<>
|
|
54
|
+
<View style={{ height: spacing.md }} />
|
|
55
|
+
<MyazaText variant="bodyMedium" style={{ fontWeight: '600' }}>
|
|
56
|
+
Company information
|
|
57
|
+
</MyazaText>
|
|
58
|
+
<MyazaText variant="bodySmall" color={colors.textSecondary} style={{ marginTop: 2 }}>
|
|
59
|
+
We verify these details against the official registry record.
|
|
60
|
+
</MyazaText>
|
|
61
|
+
{fields.map((field) => (
|
|
62
|
+
<View key={field}>
|
|
63
|
+
<View style={{ height: spacing.sm }} />
|
|
64
|
+
<MyazaInput
|
|
65
|
+
label={`${COMPANY_FIELD_LABELS[field]}${modes[field] === 'required' ? ' *' : ' (optional)'}`}
|
|
66
|
+
value={values[field]}
|
|
67
|
+
onChangeText={(text) => onChange(field, text)}
|
|
68
|
+
placeholder={COMPANY_FIELD_PLACEHOLDERS[field]}
|
|
69
|
+
keyboardType={
|
|
70
|
+
field === 'email' ? 'email-address' : field === 'phone' ? 'phone-pad' : 'default'
|
|
71
|
+
}
|
|
72
|
+
autoCapitalize="none"
|
|
73
|
+
error={
|
|
74
|
+
showErrors && missing.includes(field)
|
|
75
|
+
? `${COMPANY_FIELD_LABELS[field]} is required.`
|
|
76
|
+
: null
|
|
77
|
+
}
|
|
78
|
+
/>
|
|
79
|
+
</View>
|
|
80
|
+
))}
|
|
81
|
+
</>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -1,52 +1,31 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { Pressable, View } from 'react-native';
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Linking, Pressable, View } from 'react-native';
|
|
3
|
+
import Svg, { Circle, Defs, LinearGradient, Stop } from 'react-native-svg';
|
|
3
4
|
|
|
4
5
|
import { radius, spacing } from '../config/theme';
|
|
6
|
+
import { PRIVACY_URL, TERMS_URL } from '../config/brand';
|
|
5
7
|
import { useKyc, useKycConfig, useTheme } from '../components/runtime';
|
|
6
8
|
import { MyazaText } from '../components/Typography';
|
|
7
9
|
import { MyazaButton } from '../components/MyazaButton';
|
|
8
|
-
import { Icon
|
|
9
|
-
import {
|
|
10
|
+
import { Icon } from '../components/Icon';
|
|
11
|
+
import { buildConsentModel } from './consent/model';
|
|
10
12
|
|
|
11
|
-
// Consent / welcome screen — 1:1 with the
|
|
12
|
-
// shield hero, token-filled greeting, a "DURING
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
interface ProcessStep {
|
|
20
|
-
icon: IconName;
|
|
21
|
-
label: string;
|
|
22
|
-
}
|
|
13
|
+
// Consent / welcome screen — 1:1 with the web SDK's ConsentStep and the
|
|
14
|
+
// Flutter SDK's ConsentScreen: shield hero, token-filled greeting, a "DURING
|
|
15
|
+
// THIS PROCESS WE WILL" card, the derived legal notice, Continue, lock footer.
|
|
16
|
+
// Everything the screen SAYS — title, description, step list, the biometric
|
|
17
|
+
// sentence — is derived in consent/model.ts from what the flow actually does,
|
|
18
|
+
// so a KYB flow gets the business copy and never claims an ID/selfie step it
|
|
19
|
+
// doesn't run. The header title is intentionally empty for this step; the
|
|
20
|
+
// screen owns its hero.
|
|
23
21
|
|
|
24
22
|
export function ConsentStep(): React.ReactElement {
|
|
25
23
|
const { colors } = useTheme();
|
|
26
24
|
const config = useKycConfig();
|
|
27
25
|
const nextStep = useKyc((s) => s.nextStep);
|
|
28
|
-
const [agreed, setAgreed] = useState(false);
|
|
29
|
-
|
|
30
|
-
const firstName = config.userData?.firstName ?? '';
|
|
31
|
-
const title = config.consent?.title
|
|
32
|
-
? fillTokens(config.consent.title, config.userData)
|
|
33
|
-
: firstName
|
|
34
|
-
? `Welcome, ${firstName}`
|
|
35
|
-
: 'Identity Verification';
|
|
36
|
-
const description = config.consent?.description
|
|
37
|
-
? fillTokens(config.consent.description, config.userData)
|
|
38
|
-
: DEFAULT_DESCRIPTION;
|
|
39
26
|
|
|
40
|
-
const steps
|
|
41
|
-
|
|
42
|
-
{ icon: 'user', label: 'Collect basic personal information' },
|
|
43
|
-
...(config.enableDocumentCapture
|
|
44
|
-
? [{ icon: 'scan-line' as IconName, label: 'Capture a photo of your ID document' }]
|
|
45
|
-
: []),
|
|
46
|
-
...(config.enableSelfie
|
|
47
|
-
? [{ icon: 'scan-face' as IconName, label: 'Take a selfie for facial verification' }]
|
|
48
|
-
: []),
|
|
49
|
-
];
|
|
27
|
+
const { isBusiness, title, description, steps, capturesFace, recordsVideo } =
|
|
28
|
+
buildConsentModel(config);
|
|
50
29
|
|
|
51
30
|
return (
|
|
52
31
|
<View>
|
|
@@ -62,7 +41,6 @@ export function ConsentStep(): React.ReactElement {
|
|
|
62
41
|
width: 56,
|
|
63
42
|
height: 56,
|
|
64
43
|
borderRadius: 28,
|
|
65
|
-
backgroundColor: colors.primary,
|
|
66
44
|
alignItems: 'center',
|
|
67
45
|
justifyContent: 'center',
|
|
68
46
|
shadowColor: colors.primary,
|
|
@@ -72,6 +50,22 @@ export function ConsentStep(): React.ReactElement {
|
|
|
72
50
|
elevation: 6,
|
|
73
51
|
}}
|
|
74
52
|
>
|
|
53
|
+
{/* Top-left → bottom-right gradient badge, matching Flutter. A flat
|
|
54
|
+
fill was the last visual difference on this screen. The second
|
|
55
|
+
stop is the SAME primary at 70% opacity rather than a
|
|
56
|
+
pre-blended colour: composited over the sheet it produces
|
|
57
|
+
Flutter's `alphaBlend(primary@0.7, background)` exactly, and it
|
|
58
|
+
keeps working when the consumer overrides primaryColor or the
|
|
59
|
+
theme flips, which a hardcoded blend would not. */}
|
|
60
|
+
<Svg width={56} height={56} style={{ position: 'absolute' }}>
|
|
61
|
+
<Defs>
|
|
62
|
+
<LinearGradient id="myazaShieldHero" x1="0" y1="0" x2="1" y2="1">
|
|
63
|
+
<Stop offset="0" stopColor={colors.primary} stopOpacity={1} />
|
|
64
|
+
<Stop offset="1" stopColor={colors.primary} stopOpacity={0.7} />
|
|
65
|
+
</LinearGradient>
|
|
66
|
+
</Defs>
|
|
67
|
+
<Circle cx={28} cy={28} r={28} fill="url(#myazaShieldHero)" />
|
|
68
|
+
</Svg>
|
|
75
69
|
<Icon name="shield" size={28} color={colors.onPrimary} />
|
|
76
70
|
</View>
|
|
77
71
|
</View>
|
|
@@ -118,46 +112,52 @@ export function ConsentStep(): React.ReactElement {
|
|
|
118
112
|
<View style={{ height: spacing.lg }} />
|
|
119
113
|
|
|
120
114
|
{/* Consent checkbox */}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
width: 22,
|
|
134
|
-
height: 22,
|
|
135
|
-
borderRadius: radius.xs - 2,
|
|
136
|
-
backgroundColor: agreed ? colors.primary : 'transparent',
|
|
137
|
-
borderWidth: 1.5,
|
|
138
|
-
borderColor: agreed ? colors.primary : colors.gray400,
|
|
139
|
-
alignItems: 'center',
|
|
140
|
-
justifyContent: 'center',
|
|
141
|
-
marginTop: 1,
|
|
142
|
-
}}
|
|
115
|
+
{/* Consent is given by ACTING now, so the notice sits immediately above
|
|
116
|
+
the button it describes — adjacency is what makes it informed.
|
|
117
|
+
The biometric sentence is DERIVED: claiming facial recognition on a
|
|
118
|
+
flow with no selfie step would be false, and recording video without
|
|
119
|
+
saying so is the failure that actually matters. */}
|
|
120
|
+
<MyazaText variant="bodySmall" style={{ lineHeight: 18 }}>
|
|
121
|
+
By tapping Continue, you agree to the{' '}
|
|
122
|
+
<MyazaText
|
|
123
|
+
variant="bodySmall"
|
|
124
|
+
color={colors.textDark}
|
|
125
|
+
style={{ fontWeight: '600', textDecorationLine: 'underline' }}
|
|
126
|
+
onPress={() => void Linking.openURL(TERMS_URL).catch(() => undefined)}
|
|
143
127
|
>
|
|
144
|
-
|
|
145
|
-
</
|
|
146
|
-
|
|
147
|
-
<MyazaText
|
|
148
|
-
|
|
128
|
+
End User Terms
|
|
129
|
+
</MyazaText>{' '}
|
|
130
|
+
and{' '}
|
|
131
|
+
<MyazaText
|
|
132
|
+
variant="bodySmall"
|
|
133
|
+
color={colors.textDark}
|
|
134
|
+
style={{ fontWeight: '600', textDecorationLine: 'underline' }}
|
|
135
|
+
onPress={() => void Linking.openURL(PRIVACY_URL).catch(() => undefined)}
|
|
136
|
+
>
|
|
137
|
+
Privacy Policy
|
|
149
138
|
</MyazaText>
|
|
150
|
-
|
|
139
|
+
, and consent to your {isBusiness ? 'business and personal data' : 'personal data'} being
|
|
140
|
+
processed to verify your identity.
|
|
141
|
+
{capturesFace
|
|
142
|
+
? ' This includes facial recognition and recording this session.'
|
|
143
|
+
: recordsVideo
|
|
144
|
+
? ' This includes recording this session.'
|
|
145
|
+
: ''}
|
|
146
|
+
</MyazaText>
|
|
151
147
|
|
|
152
148
|
<View style={{ height: spacing.lg }} />
|
|
153
|
-
<MyazaButton label="Continue" onPress={
|
|
149
|
+
<MyazaButton label="Continue" onPress={nextStep} />
|
|
154
150
|
<View style={{ height: spacing.sm }} />
|
|
155
151
|
|
|
156
152
|
{/* Footer */}
|
|
157
153
|
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'center' }}>
|
|
158
154
|
<Icon name="lock" size={13} color={colors.textMuted} />
|
|
159
155
|
<View style={{ width: 6 }} />
|
|
160
|
-
|
|
156
|
+
{/* flexShrink so a wide brand font wraps this instead of clipping it —
|
|
157
|
+
a Text in a row without it overflows rather than wrapping. */}
|
|
158
|
+
<MyazaText variant="bodySmall" style={{ flexShrink: 1 }}>
|
|
159
|
+
Your data is encrypted and securely processed
|
|
160
|
+
</MyazaText>
|
|
161
161
|
</View>
|
|
162
162
|
</View>
|
|
163
163
|
);
|