@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.
Files changed (281) hide show
  1. package/README.md +111 -8
  2. package/android/build.gradle +6 -0
  3. package/android/src/main/AndroidManifest.xml +14 -4
  4. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaEmrtd.kt +358 -0
  5. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaFaceDetector.kt +88 -1
  6. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaRectDetector.kt +34 -0
  7. package/android/src/main/java/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizer.kt +118 -0
  8. package/android/src/main/java/com/margelo/nitro/myazakyc/Jp2Decoder.kt +117 -0
  9. package/app.plugin.js +102 -13
  10. package/ios/HybridMyazaEmrtd.swift +447 -0
  11. package/ios/HybridMyazaFaceDetector.swift +104 -2
  12. package/ios/HybridMyazaRectDetector.swift +103 -0
  13. package/ios/HybridMyazaTextRecognizer.swift +111 -0
  14. package/nitrogen/generated/.gitattributes +1 -0
  15. package/nitrogen/generated/android/KycSdkReactNative+autolinking.cmake +87 -0
  16. package/nitrogen/generated/android/KycSdkReactNative+autolinking.gradle +27 -0
  17. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.cpp +102 -0
  18. package/nitrogen/generated/android/KycSdkReactNativeOnLoad.hpp +34 -0
  19. package/nitrogen/generated/android/c++/JDetectedRect.hpp +81 -0
  20. package/nitrogen/generated/android/c++/JEmrtdApduResponse.hpp +61 -0
  21. package/nitrogen/generated/android/c++/JEmrtdTagInfo.hpp +61 -0
  22. package/nitrogen/generated/android/c++/JFaceResult.hpp +113 -0
  23. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.cpp +176 -0
  24. package/nitrogen/generated/android/c++/JHybridMyazaEmrtdSpec.hpp +75 -0
  25. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.cpp +60 -0
  26. package/nitrogen/generated/android/c++/JHybridMyazaFaceDetectorSpec.hpp +63 -0
  27. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.cpp +60 -0
  28. package/nitrogen/generated/android/c++/JHybridMyazaRectDetectorSpec.hpp +63 -0
  29. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.cpp +80 -0
  30. package/nitrogen/generated/android/c++/JHybridMyazaTextRecognizerSpec.hpp +64 -0
  31. package/nitrogen/generated/android/c++/JTextResult.hpp +76 -0
  32. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/DetectedRect.kt +81 -0
  33. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdApduResponse.kt +56 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/EmrtdTagInfo.kt +56 -0
  35. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/FaceResult.kt +121 -0
  36. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaEmrtdSpec.kt +103 -0
  37. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaFaceDetectorSpec.kt +55 -0
  38. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaRectDetectorSpec.kt +55 -0
  39. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/HybridMyazaTextRecognizerSpec.kt +60 -0
  40. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/KycSdkReactNativeOnLoad.kt +35 -0
  41. package/nitrogen/generated/android/kotlin/com/margelo/nitro/myazakyc/TextResult.kt +51 -0
  42. package/nitrogen/generated/ios/KycSdkReactNative+autolinking.rb +62 -0
  43. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.cpp +143 -0
  44. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Bridge.hpp +423 -0
  45. package/nitrogen/generated/ios/KycSdkReactNative-Swift-Cxx-Umbrella.hpp +81 -0
  46. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.mm +57 -0
  47. package/nitrogen/generated/ios/KycSdkReactNativeAutolinking.swift +62 -0
  48. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.cpp +11 -0
  49. package/nitrogen/generated/ios/c++/HybridMyazaEmrtdSpecSwift.hpp +184 -0
  50. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.cpp +11 -0
  51. package/nitrogen/generated/ios/c++/HybridMyazaFaceDetectorSpecSwift.hpp +87 -0
  52. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.cpp +11 -0
  53. package/nitrogen/generated/ios/c++/HybridMyazaRectDetectorSpecSwift.hpp +87 -0
  54. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.cpp +11 -0
  55. package/nitrogen/generated/ios/c++/HybridMyazaTextRecognizerSpecSwift.hpp +98 -0
  56. package/nitrogen/generated/ios/swift/DetectedRect.swift +59 -0
  57. package/nitrogen/generated/ios/swift/EmrtdApduResponse.swift +34 -0
  58. package/nitrogen/generated/ios/swift/EmrtdTagInfo.swift +34 -0
  59. package/nitrogen/generated/ios/swift/FaceResult.swift +99 -0
  60. package/nitrogen/generated/ios/swift/Func_void.swift +46 -0
  61. package/nitrogen/generated/ios/swift/Func_void_EmrtdApduResponse.swift +46 -0
  62. package/nitrogen/generated/ios/swift/Func_void_EmrtdTagInfo.swift +46 -0
  63. package/nitrogen/generated/ios/swift/Func_void_TextResult.swift +46 -0
  64. package/nitrogen/generated/ios/swift/Func_void_std__exception_ptr.swift +46 -0
  65. package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
  66. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec.swift +67 -0
  67. package/nitrogen/generated/ios/swift/HybridMyazaEmrtdSpec_cxx.swift +317 -0
  68. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec.swift +56 -0
  69. package/nitrogen/generated/ios/swift/HybridMyazaFaceDetectorSpec_cxx.swift +143 -0
  70. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec.swift +56 -0
  71. package/nitrogen/generated/ios/swift/HybridMyazaRectDetectorSpec_cxx.swift +143 -0
  72. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec.swift +57 -0
  73. package/nitrogen/generated/ios/swift/HybridMyazaTextRecognizerSpec_cxx.swift +162 -0
  74. package/nitrogen/generated/ios/swift/TextResult.swift +35 -0
  75. package/nitrogen/generated/shared/c++/DetectedRect.hpp +107 -0
  76. package/nitrogen/generated/shared/c++/EmrtdApduResponse.hpp +87 -0
  77. package/nitrogen/generated/shared/c++/EmrtdTagInfo.hpp +87 -0
  78. package/nitrogen/generated/shared/c++/FaceResult.hpp +139 -0
  79. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.cpp +33 -0
  80. package/nitrogen/generated/shared/c++/HybridMyazaEmrtdSpec.hpp +80 -0
  81. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.cpp +21 -0
  82. package/nitrogen/generated/shared/c++/HybridMyazaFaceDetectorSpec.hpp +67 -0
  83. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.cpp +21 -0
  84. package/nitrogen/generated/shared/c++/HybridMyazaRectDetectorSpec.hpp +67 -0
  85. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.cpp +22 -0
  86. package/nitrogen/generated/shared/c++/HybridMyazaTextRecognizerSpec.hpp +70 -0
  87. package/nitrogen/generated/shared/c++/TextResult.hpp +84 -0
  88. package/package.json +16 -4
  89. package/src/MyazaKYC.tsx +115 -22
  90. package/src/capture/documentIdentity.ts +153 -0
  91. package/src/capture/documentSignals.ts +177 -0
  92. package/src/capture/documentTextGate.ts +171 -0
  93. package/src/capture/hints.ts +57 -0
  94. package/src/capture/index.ts +24 -0
  95. package/src/capture/rectDetector.ts +84 -0
  96. package/src/capture/rectGate.ts +91 -0
  97. package/src/capture/useAutoCapture.ts +198 -0
  98. package/src/components/CameraViewfinder.tsx +226 -70
  99. package/src/components/ContactCodeEntry.tsx +139 -0
  100. package/src/components/CountryField.tsx +87 -0
  101. package/src/components/CountryFlag.tsx +15 -15
  102. package/src/components/DialCodePicker.tsx +186 -0
  103. package/src/components/DocumentCropper.tsx +1 -1
  104. package/src/components/DocumentReview.tsx +142 -0
  105. package/src/components/DocumentReviewSide.tsx +135 -0
  106. package/src/components/DocumentReviewZoom.tsx +119 -0
  107. package/src/components/ExpiryCountdown.tsx +52 -0
  108. package/src/components/FlashOverlay.tsx +156 -0
  109. package/src/components/Icon.tsx +67 -1
  110. package/src/components/KycFlow.tsx +66 -94
  111. package/src/components/KycSheet.tsx +148 -18
  112. package/src/components/MediaSourceSheet.tsx +163 -0
  113. package/src/components/MyazaButton.tsx +14 -2
  114. package/src/components/MyazaDateField.tsx +240 -0
  115. package/src/components/MyazaInput.tsx +73 -26
  116. package/src/components/MyazaSelect.tsx +283 -0
  117. package/src/components/MyazaWordmark.tsx +33 -0
  118. package/src/components/OptionRow.tsx +132 -0
  119. package/src/components/PhoneNumberInput.tsx +149 -0
  120. package/src/components/PoweredBy.tsx +89 -0
  121. package/src/components/ReadyPrimer.tsx +149 -0
  122. package/src/components/StepView.tsx +91 -0
  123. package/src/components/Typography.tsx +18 -3
  124. package/src/components/VerifiedNotice.tsx +38 -0
  125. package/src/components/WhatsAppIcon.tsx +29 -0
  126. package/src/components/WorkflowGate.tsx +128 -0
  127. package/src/components/brand-font.ts +113 -0
  128. package/src/components/flashHoleGeometry.ts +35 -0
  129. package/src/components/flow/FatalConfigError.tsx +56 -0
  130. package/src/components/fonts.ts +2 -19
  131. package/src/components/readyPrimerContent.ts +52 -0
  132. package/src/components/runtime.tsx +38 -43
  133. package/src/components/stepHeaderMeta.tsx +87 -0
  134. package/src/components/theme-provider.tsx +89 -0
  135. package/src/components/useWorkflowMount.ts +116 -0
  136. package/src/components/viewfinder/CardGuide.tsx +60 -0
  137. package/src/components/viewfinder/DocumentGhost.tsx +149 -0
  138. package/src/components/viewfinder/ImmersiveBottomBar.tsx +172 -0
  139. package/src/components/viewfinder/ImmersiveControls.tsx +127 -0
  140. package/src/components/viewfinder/ImmersiveGuide.tsx +174 -0
  141. package/src/components/viewfinder/ImmersiveOverlay.tsx +79 -0
  142. package/src/components/viewfinder/ViewfinderControls.tsx +154 -0
  143. package/src/config/brand.ts +97 -0
  144. package/src/config/business.ts +179 -0
  145. package/src/config/businessSteps.ts +105 -0
  146. package/src/config/contact.ts +159 -0
  147. package/src/config/countryNames.g.ts +254 -0
  148. package/src/config/currencyFlags.ts +144 -0
  149. package/src/config/font-resolve.ts +78 -0
  150. package/src/config/idTypes.ts +96 -1
  151. package/src/config/keyPeople.ts +152 -0
  152. package/src/config/phone.ts +39 -0
  153. package/src/config/proofOfAddress.ts +75 -0
  154. package/src/config/questionnaire.ts +153 -0
  155. package/src/config/regions.ts +118 -0
  156. package/src/config/registrationHint.ts +87 -0
  157. package/src/config/stepOrder.ts +118 -0
  158. package/src/config/theme.ts +38 -2
  159. package/src/config/workflowMerge.ts +124 -0
  160. package/src/emrtd/bac.ts +128 -0
  161. package/src/emrtd/bytes.ts +127 -0
  162. package/src/emrtd/crypto.ts +163 -0
  163. package/src/emrtd/der.ts +192 -0
  164. package/src/emrtd/dg2.ts +111 -0
  165. package/src/emrtd/files.ts +215 -0
  166. package/src/emrtd/index.ts +30 -0
  167. package/src/emrtd/mrzKey.ts +54 -0
  168. package/src/emrtd/native.ts +79 -0
  169. package/src/emrtd/optionalRead.ts +42 -0
  170. package/src/emrtd/read.ts +224 -0
  171. package/src/emrtd/retry.ts +98 -0
  172. package/src/emrtd/secureMessaging.ts +178 -0
  173. package/src/emrtd/session.ts +200 -0
  174. package/src/emrtd/stages.ts +122 -0
  175. package/src/index.ts +110 -0
  176. package/src/lib/calendar.ts +71 -0
  177. package/src/lib/use-keyboard-inset.ts +34 -0
  178. package/src/liveness/challengeManager.ts +27 -1
  179. package/src/liveness/faceContinuity.ts +153 -0
  180. package/src/liveness/flashDetector.ts +145 -0
  181. package/src/liveness/flashReadyGate.ts +106 -0
  182. package/src/liveness/flashRunner.ts +118 -0
  183. package/src/liveness/integritySignals.ts +60 -0
  184. package/src/liveness/livenessState.ts +139 -0
  185. package/src/liveness/speech.ts +19 -1
  186. package/src/liveness/types.ts +48 -1
  187. package/src/liveness/useFlashPhase.ts +78 -0
  188. package/src/liveness/useLiveness.ts +260 -75
  189. package/src/liveness/visionCameraFaceDetector.ts +14 -0
  190. package/src/mrz/extract.ts +101 -0
  191. package/src/mrz/parse.ts +178 -0
  192. package/src/mrz/textRecognizer.ts +149 -0
  193. package/src/screens/ApplicantRoleStep.tsx +331 -0
  194. package/src/screens/BusinessDetailsStep.tsx +212 -0
  195. package/src/screens/BusinessDocumentSlot.tsx +167 -0
  196. package/src/screens/BusinessDocumentsStep.tsx +213 -0
  197. package/src/screens/BusinessKeyPeopleStep.tsx +151 -0
  198. package/src/screens/BusinessKeyPersonRow.tsx +170 -0
  199. package/src/screens/CompanyInfoFields.tsx +83 -0
  200. package/src/screens/ConsentStep.tsx +68 -68
  201. package/src/screens/ContactChannelChoice.tsx +133 -0
  202. package/src/screens/ContactCodeStep.tsx +130 -0
  203. package/src/screens/ContactDestinationField.tsx +70 -0
  204. package/src/screens/ContactFooterNote.tsx +34 -0
  205. package/src/screens/ContactVerificationStep.tsx +199 -0
  206. package/src/screens/ContactVerifiedPanel.tsx +36 -0
  207. package/src/screens/CountrySelectStep.tsx +210 -0
  208. package/src/screens/DocumentCaptureStep.tsx +156 -132
  209. package/src/screens/IdInputStep.tsx +13 -5
  210. package/src/screens/IdTypeStep.tsx +74 -33
  211. package/src/screens/KeyPeopleInviteLinks.tsx +239 -0
  212. package/src/screens/LivenessStep.tsx +155 -247
  213. package/src/screens/MrzScanView.tsx +173 -0
  214. package/src/screens/NfcStep.tsx +290 -0
  215. package/src/screens/ProofOfAddressStep.tsx +238 -0
  216. package/src/screens/QuestionnaireField.tsx +219 -0
  217. package/src/screens/QuestionnaireMoneyField.tsx +134 -0
  218. package/src/screens/QuestionnaireStep.tsx +76 -0
  219. package/src/screens/SubmittedStep.tsx +5 -0
  220. package/src/screens/consent/model.ts +109 -0
  221. package/src/screens/document/CameraPhase.tsx +150 -0
  222. package/src/screens/document/RequiredPill.tsx +60 -0
  223. package/src/screens/liveness/LightingBanner.tsx +50 -0
  224. package/src/screens/liveness/LivenessOutcome.tsx +109 -0
  225. package/src/screens/liveness/ProgressDots.tsx +67 -0
  226. package/src/screens/liveness/SelfiePreview.tsx +39 -0
  227. package/src/screens/liveness/constants.ts +31 -0
  228. package/src/screens/liveness/index.ts +8 -0
  229. package/src/screens/liveness/resolveGuidance.ts +27 -0
  230. package/src/screens/liveness/useSelfieUpload.ts +107 -0
  231. package/src/screens/nfc/NfcMrzPrompt.tsx +40 -0
  232. package/src/screens/nfc/NfcReadBody.tsx +63 -0
  233. package/src/screens/nfc/NfcReadProgress.tsx +96 -0
  234. package/src/screens/nfc/NfcScanIllustration.tsx +244 -0
  235. package/src/screens/nfc/NfcScannedSummary.tsx +74 -0
  236. package/src/screens/nfc/NfcSuccessPanel.tsx +162 -0
  237. package/src/screens/nfc/availability.ts +73 -0
  238. package/src/screens/nfc/readErrorMessage.ts +93 -0
  239. package/src/screens/nfc/useChipPortrait.ts +58 -0
  240. package/src/screens/nfc/useNfcAvailability.ts +82 -0
  241. package/src/screens/useFlashHole.ts +105 -0
  242. package/src/screens/useFlashSequence.ts +96 -0
  243. package/src/screens/usePoaAttach.ts +121 -0
  244. package/src/services/api-types.ts +164 -0
  245. package/src/services/api-verify-types.ts +99 -0
  246. package/src/services/api.ts +72 -102
  247. package/src/services/cardCrop.ts +25 -10
  248. package/src/services/contactErrors.ts +43 -0
  249. package/src/services/deviceMetadata.ts +1 -1
  250. package/src/services/documentPicker.ts +20 -0
  251. package/src/services/fingerprint-sources.ts +129 -0
  252. package/src/services/fingerprint.ts +101 -0
  253. package/src/services/haptics.ts +38 -0
  254. package/src/services/mediaCompress.ts +7 -2
  255. package/src/services/resolveUrl.ts +2 -2
  256. package/src/services/workflowGate.ts +138 -0
  257. package/src/specs/MyazaEmrtd.nitro.ts +114 -0
  258. package/src/specs/MyazaFaceDetector.nitro.ts +10 -0
  259. package/src/specs/MyazaRectDetector.nitro.ts +50 -0
  260. package/src/specs/MyazaTextRecognizer.nitro.ts +48 -0
  261. package/src/store/derive.ts +225 -0
  262. package/src/store/kycStore.ts +250 -149
  263. package/src/store/state.ts +253 -0
  264. package/src/store/submit.ts +101 -0
  265. package/src/store/submitApplicant.ts +81 -0
  266. package/src/types/appearance.ts +113 -0
  267. package/src/types/business.ts +131 -0
  268. package/src/types/config.ts +150 -124
  269. package/src/types/id-types.ts +57 -0
  270. package/src/types/verification.ts +5 -0
  271. package/src/types/workflow.ts +141 -0
  272. package/src/utils/amountFormat.ts +73 -0
  273. package/src/utils/tokens.ts +4 -2
  274. package/src/__tests__/cardCrop.test.ts +0 -39
  275. package/src/__tests__/deviceMetadata.test.ts +0 -34
  276. package/src/__tests__/errors.test.ts +0 -34
  277. package/src/__tests__/flow.test.ts +0 -61
  278. package/src/__tests__/gestureDetector.test.ts +0 -37
  279. package/src/__tests__/liveness.test.ts +0 -112
  280. package/src/__tests__/resolveUrl.test.ts +0 -64
  281. 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 { useKyc, useKycConfig, useTheme } from '../components/runtime';
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 + radio circle), select-on-tap, Continue to advance.
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
- const setIdType = useKyc((s) => s.setIdType);
37
- const nextStep = useKyc((s) => s.nextStep);
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
- const allForCountry = ID_TYPES[config.country] ?? [];
41
- const requested = config.idTypes?.length
42
- ? allForCountry.filter((t) => (config.idTypes as IdType[]).includes(t.key))
43
- : [...allForCountry];
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
- const granted = new Set(
46
- serverConfig.idTypes.filter((t) => t.country === config.country).map((t) => t.idType),
47
- );
48
- return requested.filter((t) => granted.has(t.key));
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
- return requested;
52
- }, [config.country, config.idTypes, serverConfig]);
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={() => setIdType(t.key)}
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
- <View
122
- style={{
123
- width: 22,
124
- height: 22,
125
- borderRadius: 11,
126
- borderWidth: 1.5,
127
- borderColor: selected ? colors.primary : colors.gray300,
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
+ }