@transfergratis/react-native-sdk 0.1.24 → 0.1.26

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 (182) hide show
  1. package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +12 -5
  2. package/android/build/intermediates/aar_main_jar/debug/syncDebugLibJars/classes.jar +0 -0
  3. package/android/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt +0 -0
  4. package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
  5. package/android/build/intermediates/incremental/debug-mergeJavaRes/merge-state +0 -0
  6. package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +61 -59
  7. package/android/build/intermediates/merged_java_res/debug/mergeDebugJavaResource/feature-transfergratis-react-native-sdk.jar +0 -0
  8. package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +12 -5
  9. package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
  10. package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
  11. package/android/build/outputs/aar/transfergratis-react-native-sdk-debug.aar +0 -0
  12. package/android/build/outputs/logs/manifest-merger-debug-report.txt +26 -34
  13. package/android/src/main/AndroidManifest.xml +22 -7
  14. package/build/components/EnhancedCameraView.web.d.ts.map +1 -1
  15. package/build/components/EnhancedCameraView.web.js +76 -21
  16. package/build/components/EnhancedCameraView.web.js.map +1 -1
  17. package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts +12 -0
  18. package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts.map +1 -0
  19. package/build/components/KYCElements/AdditionalDocumentsTemplate.js +283 -0
  20. package/build/components/KYCElements/AdditionalDocumentsTemplate.js.map +1 -0
  21. package/build/components/KYCElements/EmailVerificationTemplate.d.ts +12 -0
  22. package/build/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -0
  23. package/build/components/KYCElements/EmailVerificationTemplate.js +212 -0
  24. package/build/components/KYCElements/EmailVerificationTemplate.js.map +1 -0
  25. package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  26. package/build/components/KYCElements/IDCardCapture.js +216 -14
  27. package/build/components/KYCElements/IDCardCapture.js.map +1 -1
  28. package/build/components/KYCElements/OrientationVideoCapture.d.ts +2 -0
  29. package/build/components/KYCElements/OrientationVideoCapture.d.ts.map +1 -1
  30. package/build/components/KYCElements/OrientationVideoCapture.js +2 -2
  31. package/build/components/KYCElements/OrientationVideoCapture.js.map +1 -1
  32. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts +2 -0
  33. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts.map +1 -1
  34. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js +2 -2
  35. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js.map +1 -1
  36. package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts +2 -0
  37. package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts.map +1 -1
  38. package/build/components/KYCElements/OrientationVideoCaptureFinal.js +2 -2
  39. package/build/components/KYCElements/OrientationVideoCaptureFinal.js.map +1 -1
  40. package/build/components/KYCElements/PersonalInformationTemplate.d.ts +12 -0
  41. package/build/components/KYCElements/PersonalInformationTemplate.d.ts.map +1 -0
  42. package/build/components/KYCElements/PersonalInformationTemplate.js +120 -0
  43. package/build/components/KYCElements/PersonalInformationTemplate.js.map +1 -0
  44. package/build/components/KYCElements/PhoneVerificationTemplate.d.ts +12 -0
  45. package/build/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -0
  46. package/build/components/KYCElements/PhoneVerificationTemplate.js +185 -0
  47. package/build/components/KYCElements/PhoneVerificationTemplate.js.map +1 -0
  48. package/build/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
  49. package/build/components/KYCElements/SelfieCaptureTemplate.js +7 -3
  50. package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
  51. package/build/components/KYCElements/WelcomeTemplate.js +2 -1
  52. package/build/components/KYCElements/WelcomeTemplate.js.map +1 -1
  53. package/build/components/OverLay/type.d.ts +2 -0
  54. package/build/components/OverLay/type.d.ts.map +1 -1
  55. package/build/components/OverLay/type.js.map +1 -1
  56. package/build/components/TemplateKYCExample.d.ts +10 -0
  57. package/build/components/TemplateKYCExample.d.ts.map +1 -1
  58. package/build/components/TemplateKYCExample.js +7 -30
  59. package/build/components/TemplateKYCExample.js.map +1 -1
  60. package/build/components/TemplateKYCFlowRefactored.d.ts +12 -0
  61. package/build/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  62. package/build/components/TemplateKYCFlowRefactored.js +25 -3
  63. package/build/components/TemplateKYCFlowRefactored.js.map +1 -1
  64. package/build/config/KYCConfig.d.ts +14 -0
  65. package/build/config/KYCConfig.d.ts.map +1 -0
  66. package/build/config/KYCConfig.js +26 -0
  67. package/build/config/KYCConfig.js.map +1 -0
  68. package/build/config/allowedDomains.d.ts.map +1 -1
  69. package/build/config/allowedDomains.js +4 -19
  70. package/build/config/allowedDomains.js.map +1 -1
  71. package/build/hooks/useOrientationVideo.d.ts +2 -1
  72. package/build/hooks/useOrientationVideo.d.ts.map +1 -1
  73. package/build/hooks/useOrientationVideo.js +3 -3
  74. package/build/hooks/useOrientationVideo.js.map +1 -1
  75. package/build/hooks/useTemplateKYCFlow.d.ts +18 -1
  76. package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  77. package/build/hooks/useTemplateKYCFlow.js +410 -56
  78. package/build/hooks/useTemplateKYCFlow.js.map +1 -1
  79. package/build/i18n/en/index.d.ts +42 -0
  80. package/build/i18n/en/index.d.ts.map +1 -1
  81. package/build/i18n/en/index.js +44 -2
  82. package/build/i18n/en/index.js.map +1 -1
  83. package/build/i18n/fr/index.d.ts +28 -0
  84. package/build/i18n/fr/index.d.ts.map +1 -1
  85. package/build/i18n/fr/index.js +30 -2
  86. package/build/i18n/fr/index.js.map +1 -1
  87. package/build/i18n/types.d.ts +2 -0
  88. package/build/i18n/types.d.ts.map +1 -1
  89. package/build/i18n/types.js.map +1 -1
  90. package/build/index.d.ts +1 -0
  91. package/build/index.d.ts.map +1 -1
  92. package/build/index.js +2 -0
  93. package/build/index.js.map +1 -1
  94. package/build/modules/api/CardAuthentification.d.ts +24 -3
  95. package/build/modules/api/CardAuthentification.d.ts.map +1 -1
  96. package/build/modules/api/CardAuthentification.js +90 -12
  97. package/build/modules/api/CardAuthentification.js.map +1 -1
  98. package/build/modules/api/KYCService.d.ts +17 -7
  99. package/build/modules/api/KYCService.d.ts.map +1 -1
  100. package/build/modules/api/KYCService.js +125 -37
  101. package/build/modules/api/KYCService.js.map +1 -1
  102. package/build/modules/api/SelfieVerification.d.ts +3 -1
  103. package/build/modules/api/SelfieVerification.d.ts.map +1 -1
  104. package/build/modules/api/SelfieVerification.js +17 -1
  105. package/build/modules/api/SelfieVerification.js.map +1 -1
  106. package/build/modules/api/TemplateService.d.ts +0 -1
  107. package/build/modules/api/TemplateService.d.ts.map +1 -1
  108. package/build/modules/api/TemplateService.js +3 -3
  109. package/build/modules/api/TemplateService.js.map +1 -1
  110. package/build/modules/camera/VisionCameraModule.web.d.ts.map +1 -1
  111. package/build/modules/camera/VisionCameraModule.web.js +27 -8
  112. package/build/modules/camera/VisionCameraModule.web.js.map +1 -1
  113. package/build/types/KYC.types.d.ts +130 -5
  114. package/build/types/KYC.types.d.ts.map +1 -1
  115. package/build/types/KYC.types.js.map +1 -1
  116. package/build/types/env.types.d.ts +13 -0
  117. package/build/types/env.types.d.ts.map +1 -0
  118. package/build/types/env.types.js +2 -0
  119. package/build/types/env.types.js.map +1 -0
  120. package/build/utils/cropByObb.d.ts +7 -0
  121. package/build/utils/cropByObb.d.ts.map +1 -1
  122. package/build/utils/cropByObb.js +20 -1
  123. package/build/utils/cropByObb.js.map +1 -1
  124. package/build/utils/deviceDetection.d.ts +6 -0
  125. package/build/utils/deviceDetection.d.ts.map +1 -0
  126. package/build/utils/deviceDetection.js +12 -0
  127. package/build/utils/deviceDetection.js.map +1 -0
  128. package/build/utils/platformAlert.d.ts.map +1 -1
  129. package/build/utils/platformAlert.js.map +1 -1
  130. package/build/utils/template-transformer.d.ts.map +1 -1
  131. package/build/utils/template-transformer.js +12 -0
  132. package/build/utils/template-transformer.js.map +1 -1
  133. package/build/web/WebKYCEntry.d.ts.map +1 -1
  134. package/build/web/WebKYCEntry.js +88 -38
  135. package/build/web/WebKYCEntry.js.map +1 -1
  136. package/package.json +1 -1
  137. package/plugin/build/index.d.ts +1 -0
  138. package/plugin/build/index.js +3 -1
  139. package/plugin/build/withRemovePermissions.d.ts +3 -0
  140. package/plugin/build/withRemovePermissions.js +67 -0
  141. package/plugin/build/withVisionCamera.js +3 -4
  142. package/plugin/src/index.ts +2 -1
  143. package/plugin/src/withRemovePermissions.js +85 -0
  144. package/plugin/src/withRemovePermissions.ts +83 -0
  145. package/plugin/src/withVisionCamera.js +3 -4
  146. package/plugin/src/withVisionCamera.ts +3 -4
  147. package/plugin/tsconfig.tsbuildinfo +1 -1
  148. package/plugin.js +6 -1
  149. package/src/components/EnhancedCameraView.web.tsx +76 -21
  150. package/src/components/KYCElements/AdditionalDocumentsTemplate.tsx +346 -0
  151. package/src/components/KYCElements/EmailVerificationTemplate.tsx +278 -0
  152. package/src/components/KYCElements/IDCardCapture.tsx +253 -21
  153. package/src/components/KYCElements/OrientationVideoCapture.tsx +4 -1
  154. package/src/components/KYCElements/OrientationVideoCaptureEnhanced.tsx +4 -1
  155. package/src/components/KYCElements/OrientationVideoCaptureFinal.tsx +4 -1
  156. package/src/components/KYCElements/PersonalInformationTemplate.tsx +158 -0
  157. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +253 -0
  158. package/src/components/KYCElements/SelfieCaptureTemplate.tsx +6 -3
  159. package/src/components/KYCElements/WelcomeTemplate.tsx +2 -1
  160. package/src/components/OverLay/type.ts +2 -0
  161. package/src/components/TemplateKYCExample.tsx +35 -46
  162. package/src/components/TemplateKYCFlowRefactored.tsx +46 -2
  163. package/src/config/KYCConfig.ts +34 -0
  164. package/src/config/allowedDomains.ts +7 -26
  165. package/src/hooks/useOrientationVideo.ts +5 -4
  166. package/src/hooks/useTemplateKYCFlow.tsx +443 -56
  167. package/src/i18n/en/index.ts +46 -3
  168. package/src/i18n/fr/index.ts +31 -2
  169. package/src/i18n/types.ts +2 -0
  170. package/src/index.ts +3 -0
  171. package/src/modules/api/CardAuthentification.ts +98 -12
  172. package/src/modules/api/KYCService.ts +158 -37
  173. package/src/modules/api/SelfieVerification.ts +25 -3
  174. package/src/modules/api/TemplateService.ts +4 -4
  175. package/src/modules/camera/VisionCameraModule.web.ts +30 -12
  176. package/src/types/KYC.types.ts +153 -6
  177. package/src/types/env.types.ts +13 -0
  178. package/src/utils/cropByObb.ts +20 -1
  179. package/src/utils/deviceDetection.ts +11 -0
  180. package/src/utils/platformAlert.ts +1 -0
  181. package/src/utils/template-transformer.ts +20 -8
  182. package/src/web/WebKYCEntry.tsx +123 -61
@@ -0,0 +1,278 @@
1
+ import React, { useState } from 'react';
2
+ import { View, Text, StyleSheet, TextInput, TouchableOpacity, Alert } from 'react-native';
3
+ import { TemplateComponent, LocalizedText } from '../../types/KYC.types';
4
+ import { useTemplateKYCFlowContext } from '../../hooks/useTemplateKYCFlow';
5
+ import { useI18n } from '../../hooks/useI18n';
6
+ import { Button } from '../ui/Button';
7
+ import kycService, { errorMessage } from '../../modules/api/KYCService';
8
+
9
+ interface EmailVerificationTemplateProps {
10
+ component: TemplateComponent;
11
+ value?: any;
12
+ onValueChange: (data: any) => void;
13
+ error?: string;
14
+ language?: string;
15
+ }
16
+
17
+ type VerificationStep = 'email' | 'otp';
18
+
19
+ /** RFC-style email validation: local@domain.tld */
20
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
21
+
22
+ const isValidEmail = (value: string): boolean => EMAIL_REGEX.test((value || '').trim());
23
+
24
+ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps> = ({
25
+ component,
26
+ value,
27
+ onValueChange,
28
+ error: propError,
29
+ }) => {
30
+ const { actions, getLocalizedText, state, apiKey } = useTemplateKYCFlowContext();
31
+ const { t } = useI18n();
32
+
33
+ const auth = apiKey ? { apiKey } : (state.session.token ? { token: state.session.token } : undefined);
34
+ // const config = component.config as EmailVerificationConfig; // Keep for future use
35
+
36
+ // State
37
+ const [step, setStep] = useState<VerificationStep>('email');
38
+ const [email, setEmail] = useState('');
39
+ const [otp, setOtp] = useState('');
40
+ const [localError, setLocalError] = useState<string | null>(null);
41
+ const [isSimulating, setIsSimulating] = useState(false);
42
+
43
+ const title = getLocalizedText(component.labels as LocalizedText);
44
+ const instructions = getLocalizedText(component.instructions as LocalizedText);
45
+
46
+ // Determine button text based on step
47
+ const verifyButtonText = getLocalizedText((component.ui as any).buttonText) || t('common.verify') || 'Verify';
48
+ const sendButtonText = t('common.sendCode') || 'Send Verification Code';
49
+ const buttonText = step === 'email' ? sendButtonText : verifyButtonText;
50
+
51
+ const handleSendCode = async () => {
52
+ const trimmed = email.trim();
53
+ if (!trimmed || !isValidEmail(trimmed)) {
54
+ setLocalError(t('errors.invalidEmail') || 'Please enter a valid email address');
55
+ return;
56
+ }
57
+
58
+ setLocalError(null);
59
+ setIsSimulating(true);
60
+
61
+ try {
62
+ await kycService.sendEmailVerificationCode(trimmed, auth);
63
+ setStep('otp');
64
+ } catch (err: any) {
65
+ const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send verification code');
66
+ setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
67
+ } finally {
68
+ setIsSimulating(false);
69
+ }
70
+ };
71
+
72
+ const handleVerifyCode = async () => {
73
+ if (!otp || otp.length < 4) {
74
+ setLocalError(t('errors.invalidCode') || 'Please enter the 6-digit code');
75
+ return;
76
+ }
77
+
78
+ setLocalError(null);
79
+ setIsSimulating(true);
80
+
81
+ try {
82
+ await kycService.verifyEmailCode(otp.trim(), auth);
83
+ const data = { email, otp, verified: true };
84
+ onValueChange(data);
85
+ actions.nextComponent(data);
86
+ } catch (err: any) {
87
+ const msg = errorMessage(err) ?? err?.message ?? (t('errors.wrongCode') || 'Invalid verification code');
88
+ setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
89
+ } finally {
90
+ setIsSimulating(false);
91
+ }
92
+ };
93
+
94
+ const onChangeEmail = (text: string) => {
95
+ setEmail(text);
96
+ if (localError) setLocalError(null);
97
+ };
98
+
99
+ const onChangeOtp = (text: string) => {
100
+ setOtp(text);
101
+ if (localError) setLocalError(null);
102
+ };
103
+
104
+ const handleBackToEmail = () => {
105
+ setStep('email');
106
+ setOtp('');
107
+ setLocalError(null);
108
+ };
109
+
110
+ return (
111
+ <View style={styles.container}>
112
+ <Text style={styles.title}>{title}</Text>
113
+ <Text style={styles.instructions}>
114
+ {step === 'email' ? instructions : (t('kyc.enterCodeSent') || `Please enter the code sent to ${email}`)}
115
+ </Text>
116
+
117
+ <View style={styles.contentContainer}>
118
+ {step === 'email' ? (
119
+ <View style={styles.inputContainer}>
120
+ <Text style={styles.label}>{t('common.email') || 'Email'}</Text>
121
+ <TextInput
122
+ style={styles.input}
123
+ placeholder="name@example.com"
124
+ value={email}
125
+ onChangeText={onChangeEmail}
126
+ keyboardType="email-address"
127
+ autoCapitalize="none"
128
+ autoCorrect={false}
129
+ editable={!isSimulating}
130
+ />
131
+ </View>
132
+ ) : (
133
+ <View style={styles.inputContainer}>
134
+ <Text style={styles.label}>{t('common.verificationCode') || 'Verification Code'}</Text>
135
+ <TextInput
136
+ style={styles.input}
137
+ placeholder="123456"
138
+ value={otp}
139
+ onChangeText={onChangeOtp}
140
+ keyboardType="number-pad"
141
+ maxLength={6}
142
+ editable={!isSimulating}
143
+ />
144
+ <TouchableOpacity onPress={handleBackToEmail} style={styles.changeEmailLink}>
145
+ <Text style={styles.changeEmailText}>{t('common.changeEmail') || 'Change email'}</Text>
146
+ </TouchableOpacity>
147
+ </View>
148
+ )}
149
+
150
+ {(localError || propError) && (
151
+ <Text style={styles.errorText}>{localError || propError}</Text>
152
+ )}
153
+
154
+ <Button
155
+ title={isSimulating ? (t('common.processing') || 'Processing...') : buttonText}
156
+ onPress={step === 'email' ? handleSendCode : handleVerifyCode}
157
+
158
+
159
+ disabled={
160
+ isSimulating ||
161
+ (step === 'email' ? !email : !otp)
162
+ }
163
+ />
164
+
165
+ {step === 'otp' && (
166
+ <TouchableOpacity
167
+ onPress={async () => {
168
+ if (isSimulating) return;
169
+ setLocalError(null);
170
+ setIsSimulating(true);
171
+ try {
172
+ await kycService.sendEmailVerificationCode(email.trim(), auth);
173
+ Alert.alert(
174
+ t('common.codeResent') || 'Code Resent',
175
+ t('common.codeResentMessage', { email }) || 'Code resent to ' + email
176
+ );
177
+ } catch (err: any) {
178
+ const msg = errorMessage(err) ?? err?.message ?? (t('errors.sendCodeFailed') || 'Failed to send code');
179
+ setLocalError(typeof msg === 'string' ? msg : JSON.stringify(msg));
180
+ } finally {
181
+ setIsSimulating(false);
182
+ }
183
+ }}
184
+ style={styles.resendButton}
185
+ disabled={isSimulating}
186
+ >
187
+ <Text style={styles.resendText}>{t('common.resendCode') || 'Resend Code'}</Text>
188
+ </TouchableOpacity>
189
+ )}
190
+ </View>
191
+ </View>
192
+ );
193
+ };
194
+
195
+ const styles = StyleSheet.create({
196
+ container: {
197
+ padding: 24,
198
+ backgroundColor: 'white',
199
+ borderRadius: 16,
200
+ margin: 16,
201
+ shadowColor: '#000',
202
+ shadowOffset: { width: 0, height: 4 },
203
+ shadowOpacity: 0.1,
204
+ shadowRadius: 12,
205
+ elevation: 5,
206
+ width: '95%',
207
+ },
208
+ title: {
209
+ fontSize: 24,
210
+ fontWeight: '700',
211
+ marginBottom: 8,
212
+ color: '#1a1a1a',
213
+ textAlign: 'center',
214
+ },
215
+ instructions: {
216
+ fontSize: 16,
217
+ color: '#666',
218
+ marginBottom: 32,
219
+ lineHeight: 24,
220
+ textAlign: 'center',
221
+ },
222
+ contentContainer: {
223
+ // width: '100%',
224
+ },
225
+ inputContainer: {
226
+ marginBottom: 24,
227
+ },
228
+ label: {
229
+ fontSize: 14,
230
+ fontWeight: '600',
231
+ color: '#333',
232
+ marginBottom: 8,
233
+ marginLeft: 4,
234
+ },
235
+ input: {
236
+ borderWidth: 1,
237
+ borderColor: '#e0e0e0',
238
+ padding: 16,
239
+ borderRadius: 12,
240
+ fontSize: 16,
241
+ backgroundColor: '#f8f9fa',
242
+ color: '#333',
243
+ },
244
+ errorText: {
245
+ color: '#dc2626',
246
+ marginBottom: 16,
247
+ fontSize: 14,
248
+ textAlign: 'center',
249
+ backgroundColor: '#fee2e2',
250
+ padding: 8,
251
+ borderRadius: 8,
252
+ overflow: 'hidden',
253
+ },
254
+ button: {
255
+ height: 50,
256
+ borderRadius: 12,
257
+ width: "100%"
258
+ },
259
+ changeEmailLink: {
260
+ alignSelf: 'flex-end',
261
+ marginTop: 8,
262
+ },
263
+ changeEmailText: {
264
+ color: '#2DBD60',
265
+ fontSize: 14,
266
+ fontWeight: '500',
267
+ },
268
+ resendButton: {
269
+ marginTop: 16,
270
+ alignItems: 'center',
271
+ width: "100%"
272
+ },
273
+ resendText: {
274
+ color: '#666',
275
+ fontSize: 14,
276
+ textDecorationLine: 'underline',
277
+ },
278
+ });
@@ -1,5 +1,5 @@
1
1
  import React, { useEffect, useMemo, useState } from 'react';
2
- import { View, Text, StyleSheet, Image, ScrollView } from 'react-native';
2
+ import { View, Text, StyleSheet, Image, ScrollView, Platform, Modal, TouchableOpacity, ActivityIndicator } from 'react-native';
3
3
  import { showAlert } from '../../utils/platformAlert';
4
4
  import { EnhancedCameraView } from '../EnhancedCameraView';
5
5
  import { TemplateComponent, LocalizedText, GovernmentDocumentType, ISilentCaptureResult, IBbox, GovernmentDocumentTypeShorted, GovernmentDocumentTypeBackend } from '../../types/KYC.types';
@@ -11,8 +11,9 @@ import { removeDuplicates } from '../../utils/remove-duplicate';
11
11
  import { backVerification, checkTemplateType, frontVerification } from '../../modules/api/CardAuthentification';
12
12
  import { getDocumentTypeInfo } from '../../utils/get-document-type-info';
13
13
  import pathToBase64 from '../../utils/pathToBase64';
14
- import { truncateFields } from '../../modules/api/KYCService';
15
- import { cropByObb, cropImageWithBBox, cropImageWithBBoxWithTolerance } from '../../utils/cropByObb';
14
+ import kycService, { truncateFields } from '../../modules/api/KYCService';
15
+ import { cropByObb, cropImageWithBBox, cropImageWithBBoxWithTolerance, getObbConfidence, OBB_CONFIDENCE_THRESHOLD } from '../../utils/cropByObb';
16
+ import { isMobileWeb } from '../../utils/deviceDetection';
16
17
  import { logger } from '../../utils/logger';
17
18
 
18
19
 
@@ -47,6 +48,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
47
48
  // Stocker les bbox par côté pour pouvoir restaurer les images croppées
48
49
  const [bboxBySide, setBboxBySide] = useState<Record<string, IBbox>>({});
49
50
  const [silentCaptureResult, setSilentCaptureResult] = useState<ISilentCaptureResult>({ success: false, isAnalyzing: false });
51
+ const [showQRModal, setShowQRModal] = useState(false);
50
52
 
51
53
  // Mapping des types de documents backend vers SDK
52
54
  const documentTypeMapping: Record<string, GovernmentDocumentType> = {
@@ -58,7 +60,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
58
60
  };
59
61
  // const [imageNaturalSize, setImageNaturalSize] = useState<{ width: number; height: number } | null>(null);
60
62
 
61
- const { actions, state } = useTemplateKYCFlowContext();
63
+ const { actions, state, env } = useTemplateKYCFlowContext();
62
64
 
63
65
 
64
66
 
@@ -96,6 +98,35 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
96
98
  }, [countrySelectionData]);
97
99
 
98
100
 
101
+ // Synchroniser capturedImages avec value quand les données sont chargées (ex: reprise de session)
102
+ useEffect(() => {
103
+ if (value && Object.keys(value).length > 0) {
104
+ // Vérifier si les données ont changé
105
+ const valueChanged = JSON.stringify(value) !== JSON.stringify(capturedImages);
106
+ if (valueChanged) {
107
+ logger.log("Updating capturedImages from value:", Object.keys(value));
108
+ logger.log("Value data sample:", truncateFields(value));
109
+ const updatedImages = value as Record<string, IIDCardPayload>;
110
+ setCapturedImages(updatedImages);
111
+
112
+ // Si on a des images chargées, mettre à jour silentCaptureResult pour l'affichage
113
+ Object.keys(updatedImages).forEach((side) => {
114
+ const imageData = updatedImages[side];
115
+ if (imageData?.dir) {
116
+ setSilentCaptureResult(prev => ({
117
+ ...prev,
118
+ path: imageData.dir,
119
+ success: true,
120
+ isAnalyzing: false,
121
+ mrz: imageData.mrz || '',
122
+ templatePath: imageData.templatePath || '',
123
+ }));
124
+ }
125
+ });
126
+ }
127
+ }
128
+ }, [value]);
129
+
99
130
  useEffect(() => {
100
131
  logger.log("cropImageUri", JSON.stringify(truncateFields({ box: silentCaptureResult }), null, 2));
101
132
  if (capturedImages[currentSide]?.dir && silentCaptureResult?.bbox) {
@@ -213,7 +244,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
213
244
  countrySelectionDataDocumentType: countrySelectionData?.documentType,
214
245
  docTypeToSend: selectedDocumentType?.type
215
246
  });
216
- const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type as GovernmentDocumentType, docRegion: countryData?.code || "", postfix: currentSide });
247
+ const templateType = await checkTemplateType({ path: result.path || '', docType: selectedDocumentType?.type as GovernmentDocumentType, docRegion: countryData?.code || "", postfix: currentSide }, env);
217
248
 
218
249
  if (templateType.template_path) {
219
250
  templatePath = templateType.template_path;
@@ -221,6 +252,16 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
221
252
  setSilentCaptureResult((prev) => ({ ...prev, templatePath: templatePath }));
222
253
  }
223
254
  if (templateType.card_obb) {
255
+ const obbConfidence = getObbConfidence((templateType as any).card_obb);
256
+ if (obbConfidence !== null && obbConfidence < OBB_CONFIDENCE_THRESHOLD) {
257
+ setSilentCaptureResult((prev) => ({
258
+ ...prev,
259
+ isAnalyzing: false,
260
+ success: false,
261
+ error: t('kyc.idCardCapture.cardNotFullyInFrame'),
262
+ }));
263
+ return;
264
+ }
224
265
  let bbox: IBbox | undefined;
225
266
  try {
226
267
  const crop = await cropByObb(result?.path || '', (templateType as any).card_obb);
@@ -260,7 +301,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
260
301
  };
261
302
  console.log("frontVerification params", verificationParams);
262
303
  console.log("About to call frontVerification function");
263
- const promise = frontVerification(verificationParams);
304
+ const promise = frontVerification(verificationParams, env);
264
305
  console.log("frontVerification promise created", promise);
265
306
  promise.then((mrz) => {
266
307
  logger.log("front verification result", truncateFields(mrz));
@@ -283,8 +324,9 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
283
324
  }).catch((e: any) => {
284
325
  console.log("error front verification", e);
285
326
  logger.log("error front verification", truncateFields(e));
286
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
287
- // showAlert('Erreur', e?.message || 'Erreur de détection du MRZ');
327
+ const isCardNotFullyInFrame = e?.message?.includes('entirement') || e?.message?.includes('fully in frame');
328
+ const errorMessage = isCardNotFullyInFrame ? t('kyc.idCardCapture.cardNotFullyInFrame') : (e?.message || 'Erreur de détection du MRZ');
329
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: errorMessage }));
288
330
  });
289
331
  } catch (error: any) {
290
332
  console.log("Error setting up frontVerification call", error);
@@ -304,7 +346,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
304
346
  currentSide: currentSide,
305
347
  templatePath: templatePath,
306
348
  mrzType: getCorrespondingMrzType(templatePath, backRegionMappings.regionMapping, backRegionMappings.key || '') || '',
307
- }).then((mrz) => {
349
+ }, env).then((mrz) => {
308
350
  logger.log("back verification result", truncateFields(mrz));
309
351
  const bbox = (mrz as any)?.bbox || templateBbox;
310
352
  setSilentCaptureResult((prev) => ({
@@ -320,8 +362,9 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
320
362
  }
321
363
  }).catch((e: any) => {
322
364
  logger.log("error back verification", truncateFields(e));
323
- setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: e?.message || 'Erreur de détection du MRZ' }));
324
- // showAlert('Erreur', e?.message || 'Erreur de détection du MRZ');
365
+ const isCardNotFullyInFrame = e?.message?.includes('entirement') || e?.message?.includes('fully in frame');
366
+ const errorMessage = isCardNotFullyInFrame ? t('kyc.idCardCapture.cardNotFullyInFrame') : (e?.message || 'Erreur de détection du MRZ');
367
+ setSilentCaptureResult((prev) => ({ ...prev, isAnalyzing: false, templatePath: templatePath, success: false, error: errorMessage }));
325
368
  })
326
369
  }
327
370
 
@@ -380,12 +423,82 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
380
423
  actions.showCustomStepper(!showCamera);
381
424
  }, [showCamera]);
382
425
 
426
+ // Cross-device polling logic
427
+ useEffect(() => {
428
+ if (!showQRModal || !state.session.session_id) return;
429
+
430
+ const pollInterval = setInterval(async () => {
431
+ try {
432
+ const result = await kycService.getVerificationResult(state.session.session_id);
433
+ const sessionData = result[state.session.session_id]?.data;
434
+
435
+ if (sessionData) {
436
+ // Check if verification is completed or if we have ID card data
437
+ // Since the requirement is "verification restarts", we might look for overall completion
438
+ // or we could check if user_data is populated.
439
+ // For now, let's assume if status is not PENDING/INITIALIZED it might be done.
440
+ // Or simplier: if the mobile flow completes, the session status updates.
441
+ logger.log('Polling result:', truncateFields(sessionData));
442
+
443
+ if (sessionData.verification_status === 'completed' || sessionData.verification_status === 'approved' || sessionData.verification_status === 'review') {
444
+ clearInterval(pollInterval);
445
+ setShowQRModal(false);
446
+ actions.submitVerification(); // Or handleComplete
447
+ }
448
+ }
449
+ } catch (error) {
450
+ console.error('Polling error:', error);
451
+ }
452
+ }, 5000);
453
+
454
+ return () => clearInterval(pollInterval);
455
+ }, [showQRModal, state.session.session_id, actions]);
456
+
457
+ const getQrCodeUrl = (): string => {
458
+ // Only available on web platform
459
+ if (Platform.OS !== 'web') return '';
460
+ if (typeof window === 'undefined' || !window.location || !window.location.href) return '';
461
+
462
+ try {
463
+ const currentUrl = new URL(window.location.href);
464
+ if (!currentUrl.searchParams.has('kyc_id') && state.session.session_id) {
465
+ currentUrl.searchParams.set('kyc_id', state.session.session_id);
466
+ }
467
+ currentUrl.searchParams.set('component_index', String(state.currentComponentIndex));
468
+ if (countrySelectionData?.code) {
469
+ currentUrl.searchParams.set('country', countrySelectionData.code);
470
+ if (countrySelectionData.documentType) currentUrl.searchParams.set('document_type', countrySelectionData.documentType);
471
+ if (countrySelectionData.region) currentUrl.searchParams.set('region', countrySelectionData.region);
472
+ }
473
+ return `https://api.qrserver.com/v1/create-qr-code/?size=250x250&data=${encodeURIComponent(currentUrl.toString())}`;
474
+ } catch (error) {
475
+ console.warn('Error generating QR code URL:', error);
476
+ return '';
477
+ }
478
+ };
479
+
383
480
 
384
481
 
385
482
 
386
483
 
387
484
 
388
485
 
486
+ // En reprise sur un autre appareil: afficher un chargement tant que les données de session ne sont pas restaurées
487
+ const isResumingSession = Boolean(state.session.session_id && state.currentComponentIndex > 0);
488
+ const sessionDataRestored = state.session.sessionDataRestored !== false;
489
+ if (isResumingSession && !sessionDataRestored && (!countrySelectionData || !selectedDocumentType)) {
490
+ return (
491
+ <View style={styles.root}>
492
+ <View style={[styles.container, { justifyContent: 'center', alignItems: 'center' }]}>
493
+ <ActivityIndicator size="large" color="#2DBD60" />
494
+ <Text style={[styles.description, { marginTop: 16 }]}>
495
+ {state.currentLanguage === 'en' ? 'Loading your session...' : 'Chargement de votre session...'}
496
+ </Text>
497
+ </View>
498
+ </View>
499
+ );
500
+ }
501
+
389
502
  // Vérifier si les données sont disponibles, sinon afficher un message d'erreur
390
503
  if (!countrySelectionData || !selectedDocumentType) {
391
504
  return (
@@ -423,6 +536,7 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
423
536
  showSwitchCamera={true}
424
537
  onSilentCapture={handleSilentCapture}
425
538
  silentCaptureResult={silentCaptureResult}
539
+ captureStabilizationDelayMs={3000}
426
540
  enableFlash={cameraConfig.flashMode === 'auto' || cameraConfig.flashMode === 'on'}
427
541
  overlayComponent={<IdCardOverlay
428
542
  xMin={cameraConfig.overlay.bbox.xMin}
@@ -518,15 +632,28 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
518
632
  }}
519
633
  />
520
634
  ) : null}
521
- {silentCaptureResult.path ? (<Image
522
- source={{ uri: silentCaptureResult.path }}
523
- style={{
524
- width: '100%',
525
- height: 200,
526
- borderRadius: 12,
527
- resizeMode: 'cover',
528
- }}
529
- />) : null}
635
+ {!cropImageUri && silentCaptureResult.path ? (
636
+ <Image
637
+ source={{ uri: silentCaptureResult.path }}
638
+ style={{
639
+ width: '100%',
640
+ height: 200,
641
+ borderRadius: 12,
642
+ resizeMode: 'cover',
643
+ }}
644
+ />
645
+ ) : null}
646
+ {!cropImageUri && !silentCaptureResult.path && capturedImages[currentSide]?.dir ? (
647
+ <Image
648
+ source={{ uri: capturedImages[currentSide].dir }}
649
+ style={{
650
+ width: '100%',
651
+ height: 200,
652
+ borderRadius: 12,
653
+ resizeMode: 'cover',
654
+ }}
655
+ />
656
+ ) : null}
530
657
 
531
658
  </View>
532
659
  {/* Capture button si aucune image n'a été capturée */}
@@ -582,6 +709,49 @@ export const IDCardCapture: React.FC<IDCardCaptureProps> = ({
582
709
  {error && (
583
710
  <Text style={styles.errorText}>{error}</Text>
584
711
  )}
712
+
713
+ {/* Cross-Device / Continue on Phone Button (Web Only) */}
714
+ {Platform.OS === 'web' && !isMobileWeb() && !capturedImages[currentSide]?.dir && (
715
+ <Button title={t('kyc.idCardCapture.continueOnPhone')} onPress={() => { setShowQRModal(true) }} />
716
+ )}
717
+
718
+ {/* QR Code Modal - Web Only */}
719
+ {Platform.OS === 'web' && (
720
+ <Modal
721
+ visible={showQRModal}
722
+ transparent={true}
723
+ animationType="fade"
724
+ onRequestClose={() => setShowQRModal(false)}
725
+ >
726
+ <View style={styles.modalOverlay}>
727
+ <View style={styles.modalContent}>
728
+ <Text style={styles.modalTitle}>
729
+ {t('kyc.idCardCapture.continueOnMobile')}
730
+ </Text>
731
+ <Text style={styles.modalDescription}>
732
+ {t('kyc.idCardCapture.scanQrCode')}
733
+ </Text>
734
+
735
+ {showQRModal && getQrCodeUrl() ? (
736
+ <Image
737
+ source={{ uri: getQrCodeUrl() }}
738
+ style={styles.qrCodeImage}
739
+ />
740
+ ) : null}
741
+
742
+ <TouchableOpacity
743
+ style={styles.closeButton}
744
+ onPress={() => setShowQRModal(false)}
745
+ >
746
+ <Text style={styles.closeButtonText}>
747
+ {t('common.close')}
748
+ </Text>
749
+ </TouchableOpacity>
750
+ </View>
751
+ </View>
752
+ </Modal>
753
+ )}
754
+
585
755
  </View>
586
756
  </View>
587
757
  );
@@ -613,7 +783,7 @@ const styles = StyleSheet.create({
613
783
  height: '100%',
614
784
  },
615
785
  previewContainer: {
616
- width: '100%',
786
+ width: '95%',
617
787
  backgroundColor: 'white',
618
788
  margin: 10,
619
789
  borderRadius: 10,
@@ -738,4 +908,66 @@ const styles = StyleSheet.create({
738
908
  marginTop: 8,
739
909
  textAlign: 'center',
740
910
  },
911
+ crossDeviceButton: {
912
+ marginTop: 16,
913
+ padding: 12,
914
+ alignItems: 'center',
915
+ borderWidth: 1,
916
+ borderColor: '#2DBD60',
917
+ borderRadius: 8,
918
+ backgroundColor: '#f0f9f0',
919
+ },
920
+ crossDeviceText: {
921
+ color: '#2DBD60',
922
+ fontSize: 16,
923
+ fontWeight: '600',
924
+ },
925
+ modalOverlay: {
926
+ flex: 1,
927
+ backgroundColor: 'rgba(0,0,0,0.5)',
928
+ justifyContent: 'center',
929
+ alignItems: 'center',
930
+ },
931
+ modalContent: {
932
+ backgroundColor: 'white',
933
+ borderRadius: 16,
934
+ padding: 24,
935
+ alignItems: 'center',
936
+ width: '90%',
937
+ maxWidth: 340,
938
+ shadowColor: '#000',
939
+ shadowOffset: { width: 0, height: 2 },
940
+ shadowOpacity: 0.25,
941
+ shadowRadius: 4,
942
+ elevation: 5,
943
+ },
944
+ modalTitle: {
945
+ fontSize: 20,
946
+ fontWeight: 'bold',
947
+ marginBottom: 12,
948
+ color: '#333',
949
+ },
950
+ modalDescription: {
951
+ fontSize: 16,
952
+ color: '#666',
953
+ textAlign: 'center',
954
+ marginBottom: 20,
955
+ lineHeight: 22,
956
+ },
957
+ qrCodeImage: {
958
+ width: 200,
959
+ height: 200,
960
+ marginBottom: 20,
961
+ },
962
+ closeButton: {
963
+ paddingVertical: 10,
964
+ paddingHorizontal: 20,
965
+ backgroundColor: '#f5f5f5',
966
+ borderRadius: 8,
967
+ },
968
+ closeButtonText: {
969
+ fontSize: 16,
970
+ color: '#666',
971
+ fontWeight: '600',
972
+ },
741
973
  });