@transfergratis/react-native-sdk 0.1.24 → 0.1.25

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 (149) 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 +10 -7
  14. package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts +12 -0
  15. package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts.map +1 -0
  16. package/build/components/KYCElements/AdditionalDocumentsTemplate.js +283 -0
  17. package/build/components/KYCElements/AdditionalDocumentsTemplate.js.map +1 -0
  18. package/build/components/KYCElements/EmailVerificationTemplate.d.ts +12 -0
  19. package/build/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -0
  20. package/build/components/KYCElements/EmailVerificationTemplate.js +193 -0
  21. package/build/components/KYCElements/EmailVerificationTemplate.js.map +1 -0
  22. package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
  23. package/build/components/KYCElements/IDCardCapture.js +180 -7
  24. package/build/components/KYCElements/IDCardCapture.js.map +1 -1
  25. package/build/components/KYCElements/OrientationVideoCapture.d.ts +2 -0
  26. package/build/components/KYCElements/OrientationVideoCapture.d.ts.map +1 -1
  27. package/build/components/KYCElements/OrientationVideoCapture.js +2 -2
  28. package/build/components/KYCElements/OrientationVideoCapture.js.map +1 -1
  29. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts +2 -0
  30. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts.map +1 -1
  31. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js +2 -2
  32. package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js.map +1 -1
  33. package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts +2 -0
  34. package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts.map +1 -1
  35. package/build/components/KYCElements/OrientationVideoCaptureFinal.js +2 -2
  36. package/build/components/KYCElements/OrientationVideoCaptureFinal.js.map +1 -1
  37. package/build/components/KYCElements/PersonalInformationTemplate.d.ts +12 -0
  38. package/build/components/KYCElements/PersonalInformationTemplate.d.ts.map +1 -0
  39. package/build/components/KYCElements/PersonalInformationTemplate.js +120 -0
  40. package/build/components/KYCElements/PersonalInformationTemplate.js.map +1 -0
  41. package/build/components/KYCElements/PhoneVerificationTemplate.d.ts +12 -0
  42. package/build/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -0
  43. package/build/components/KYCElements/PhoneVerificationTemplate.js +185 -0
  44. package/build/components/KYCElements/PhoneVerificationTemplate.js.map +1 -0
  45. package/build/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
  46. package/build/components/KYCElements/SelfieCaptureTemplate.js +7 -3
  47. package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
  48. package/build/components/TemplateKYCExample.d.ts +4 -0
  49. package/build/components/TemplateKYCExample.d.ts.map +1 -1
  50. package/build/components/TemplateKYCExample.js +7 -30
  51. package/build/components/TemplateKYCExample.js.map +1 -1
  52. package/build/components/TemplateKYCFlowRefactored.d.ts +4 -0
  53. package/build/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
  54. package/build/components/TemplateKYCFlowRefactored.js +14 -2
  55. package/build/components/TemplateKYCFlowRefactored.js.map +1 -1
  56. package/build/config/KYCConfig.d.ts +14 -0
  57. package/build/config/KYCConfig.d.ts.map +1 -0
  58. package/build/config/KYCConfig.js +26 -0
  59. package/build/config/KYCConfig.js.map +1 -0
  60. package/build/config/allowedDomains.d.ts.map +1 -1
  61. package/build/config/allowedDomains.js +4 -19
  62. package/build/config/allowedDomains.js.map +1 -1
  63. package/build/hooks/useOrientationVideo.d.ts +2 -1
  64. package/build/hooks/useOrientationVideo.d.ts.map +1 -1
  65. package/build/hooks/useOrientationVideo.js +3 -3
  66. package/build/hooks/useOrientationVideo.js.map +1 -1
  67. package/build/hooks/useTemplateKYCFlow.d.ts +6 -1
  68. package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
  69. package/build/hooks/useTemplateKYCFlow.js +286 -23
  70. package/build/hooks/useTemplateKYCFlow.js.map +1 -1
  71. package/build/i18n/en/index.d.ts +40 -0
  72. package/build/i18n/en/index.d.ts.map +1 -1
  73. package/build/i18n/en/index.js +41 -1
  74. package/build/i18n/en/index.js.map +1 -1
  75. package/build/i18n/fr/index.d.ts +26 -0
  76. package/build/i18n/fr/index.d.ts.map +1 -1
  77. package/build/i18n/fr/index.js +27 -1
  78. package/build/i18n/fr/index.js.map +1 -1
  79. package/build/index.d.ts +1 -0
  80. package/build/index.d.ts.map +1 -1
  81. package/build/index.js +2 -0
  82. package/build/index.js.map +1 -1
  83. package/build/modules/api/CardAuthentification.d.ts +24 -3
  84. package/build/modules/api/CardAuthentification.d.ts.map +1 -1
  85. package/build/modules/api/CardAuthentification.js +68 -10
  86. package/build/modules/api/CardAuthentification.js.map +1 -1
  87. package/build/modules/api/KYCService.d.ts +7 -7
  88. package/build/modules/api/KYCService.d.ts.map +1 -1
  89. package/build/modules/api/KYCService.js +101 -37
  90. package/build/modules/api/KYCService.js.map +1 -1
  91. package/build/modules/api/SelfieVerification.d.ts +3 -1
  92. package/build/modules/api/SelfieVerification.d.ts.map +1 -1
  93. package/build/modules/api/SelfieVerification.js +17 -1
  94. package/build/modules/api/SelfieVerification.js.map +1 -1
  95. package/build/modules/api/TemplateService.d.ts +0 -1
  96. package/build/modules/api/TemplateService.d.ts.map +1 -1
  97. package/build/modules/api/TemplateService.js +3 -3
  98. package/build/modules/api/TemplateService.js.map +1 -1
  99. package/build/types/KYC.types.d.ts +124 -3
  100. package/build/types/KYC.types.d.ts.map +1 -1
  101. package/build/types/KYC.types.js.map +1 -1
  102. package/build/types/env.types.d.ts +13 -0
  103. package/build/types/env.types.d.ts.map +1 -0
  104. package/build/types/env.types.js +2 -0
  105. package/build/types/env.types.js.map +1 -0
  106. package/build/utils/deviceDetection.d.ts +6 -0
  107. package/build/utils/deviceDetection.d.ts.map +1 -0
  108. package/build/utils/deviceDetection.js +12 -0
  109. package/build/utils/deviceDetection.js.map +1 -0
  110. package/build/utils/platformAlert.d.ts.map +1 -1
  111. package/build/utils/platformAlert.js.map +1 -1
  112. package/build/utils/template-transformer.d.ts.map +1 -1
  113. package/build/utils/template-transformer.js +12 -0
  114. package/build/utils/template-transformer.js.map +1 -1
  115. package/build/web/WebKYCEntry.d.ts.map +1 -1
  116. package/build/web/WebKYCEntry.js +82 -38
  117. package/build/web/WebKYCEntry.js.map +1 -1
  118. package/package.json +1 -1
  119. package/plugin/build/withVisionCamera.js +3 -4
  120. package/plugin/src/withVisionCamera.js +3 -4
  121. package/plugin/src/withVisionCamera.ts +3 -4
  122. package/src/components/KYCElements/AdditionalDocumentsTemplate.tsx +346 -0
  123. package/src/components/KYCElements/EmailVerificationTemplate.tsx +264 -0
  124. package/src/components/KYCElements/IDCardCapture.tsx +216 -15
  125. package/src/components/KYCElements/OrientationVideoCapture.tsx +4 -1
  126. package/src/components/KYCElements/OrientationVideoCaptureEnhanced.tsx +4 -1
  127. package/src/components/KYCElements/OrientationVideoCaptureFinal.tsx +4 -1
  128. package/src/components/KYCElements/PersonalInformationTemplate.tsx +158 -0
  129. package/src/components/KYCElements/PhoneVerificationTemplate.tsx +253 -0
  130. package/src/components/KYCElements/SelfieCaptureTemplate.tsx +6 -3
  131. package/src/components/TemplateKYCExample.tsx +31 -46
  132. package/src/components/TemplateKYCFlowRefactored.tsx +27 -1
  133. package/src/config/KYCConfig.ts +34 -0
  134. package/src/config/allowedDomains.ts +7 -26
  135. package/src/hooks/useOrientationVideo.ts +5 -4
  136. package/src/hooks/useTemplateKYCFlow.tsx +314 -21
  137. package/src/i18n/en/index.ts +43 -2
  138. package/src/i18n/fr/index.ts +28 -1
  139. package/src/index.ts +3 -0
  140. package/src/modules/api/CardAuthentification.ts +75 -10
  141. package/src/modules/api/KYCService.ts +117 -37
  142. package/src/modules/api/SelfieVerification.ts +25 -3
  143. package/src/modules/api/TemplateService.ts +4 -4
  144. package/src/types/KYC.types.ts +146 -3
  145. package/src/types/env.types.ts +13 -0
  146. package/src/utils/deviceDetection.ts +11 -0
  147. package/src/utils/platformAlert.ts +1 -0
  148. package/src/utils/template-transformer.ts +20 -8
  149. package/src/web/WebKYCEntry.tsx +112 -61
@@ -0,0 +1,346 @@
1
+ import React, { useState } from 'react';
2
+ import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Image } from 'react-native';
3
+ import { TemplateComponent, AdditionalDocumentsConfig, 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 NativeCameraModule from '../../modules/camera/NativeCameraModule';
8
+ import { useKYCStore } from '../../stores/kycStore';
9
+ import { showAlert } from '../../utils/platformAlert';
10
+
11
+ interface AdditionalDocumentsTemplateProps {
12
+ component: TemplateComponent;
13
+ value?: any;
14
+ onValueChange: (data: any) => void;
15
+ error?: string;
16
+ language?: string;
17
+ }
18
+
19
+ interface UploadedFile {
20
+ uri: string;
21
+ path: string;
22
+ name: string;
23
+ size: number;
24
+ }
25
+
26
+ export const AdditionalDocumentsTemplate: React.FC<AdditionalDocumentsTemplateProps> = ({
27
+ component,
28
+ value,
29
+ onValueChange,
30
+ error,
31
+ language = 'en',
32
+ }) => {
33
+ const { actions, getLocalizedText } = useTemplateKYCFlowContext();
34
+ const { t } = useI18n();
35
+ const { setProcessing } = useKYCStore();
36
+ const config = component.config as AdditionalDocumentsConfig;
37
+
38
+ // State
39
+ const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]);
40
+ const [isUploading, setIsUploading] = useState(false);
41
+
42
+ const title = getLocalizedText(component.labels as LocalizedText);
43
+ const instructions = getLocalizedText(component.instructions as LocalizedText);
44
+ const buttonText = getLocalizedText((component.ui as any).buttonText) || t('common.continue');
45
+
46
+ // Parse max size from config string (e.g. "5MB" -> bytes)
47
+ const parseMaxSize = (sizeStr: string): number => {
48
+ if (!sizeStr) return 10 * 1024 * 1024; // Default 10MB
49
+ const value = parseFloat(sizeStr);
50
+ if (sizeStr.toLowerCase().includes('kb')) return value * 1024;
51
+ if (sizeStr.toLowerCase().includes('gb')) return value * 1024 * 1024 * 1024;
52
+ return value * 1024 * 1024; // Default to MB if only number or MB
53
+ };
54
+
55
+ const maxSizeBytes = parseMaxSize(config.maxSizeEach);
56
+ const maxFiles = config.maxDocuments || 3;
57
+
58
+ const pickDocument = async () => {
59
+ if (uploadedFiles.length >= maxFiles) {
60
+ showAlert(
61
+ t('common.limitReached') || 'Limit Reached',
62
+ t('errors.maxFilesReached', { max: maxFiles }) || `You can only upload ${maxFiles} files.`
63
+ );
64
+ return;
65
+ }
66
+
67
+ try {
68
+ setIsUploading(true);
69
+ setProcessing(true);
70
+
71
+ // Default allowed types since AdditionalDocumentsConfig structure is complex for categories
72
+ const allowedTypes = ['application/pdf', 'image/jpeg', 'image/png'];
73
+
74
+ const result = await NativeCameraModule.openFilePicker(allowedTypes);
75
+
76
+ if (result.success && result.uri && result.path) {
77
+ const fileName = result.path.split('/').pop() || 'Document';
78
+ const fileSize = (result as any).size || 0;
79
+
80
+ if (fileSize > maxSizeBytes) {
81
+ showAlert(
82
+ t('errors.fileTooLarge') || 'File too large',
83
+ t('errors.maxFileSize', { size: config.maxSizeEach }) || `Max file size is ${config.maxSizeEach}`
84
+ );
85
+ return;
86
+ }
87
+
88
+ const newFile: UploadedFile = {
89
+ uri: result.uri,
90
+ path: result.path,
91
+ name: fileName,
92
+ size: fileSize
93
+ };
94
+
95
+ const updatedFiles = [...uploadedFiles, newFile];
96
+ setUploadedFiles(updatedFiles);
97
+ onValueChange({ documents: updatedFiles }); // Matching expected data structure
98
+ } else if (result.error) {
99
+ // Ignore cancellation errors or show generic if needed
100
+ if (result.error !== 'Canceled') {
101
+ showAlert(t('common.error') || 'Error', result.error);
102
+ }
103
+ }
104
+ } catch (error) {
105
+ console.error('Error selecting file:', error);
106
+ showAlert(t('common.error') || 'Error', t('errors.unknownError') || 'Failed to select file');
107
+ } finally {
108
+ setIsUploading(false);
109
+ setProcessing(false);
110
+ }
111
+ };
112
+
113
+ const removeFile = (index: number) => {
114
+ const updatedFiles = uploadedFiles.filter((_, i) => i !== index);
115
+ setUploadedFiles(updatedFiles);
116
+ onValueChange({ documents: updatedFiles });
117
+ };
118
+
119
+ const handleContinue = () => {
120
+ actions.nextComponent();
121
+ };
122
+
123
+ // Helpers for UI
124
+ const getFileIcon = (fileName: string) => {
125
+ const extension = fileName.split('.').pop()?.toLowerCase();
126
+ switch (extension) {
127
+ case 'pdf': return '📄';
128
+ case 'jpg':
129
+ case 'jpeg':
130
+ case 'png': return '🖼️';
131
+ default: return '📎';
132
+ }
133
+ };
134
+
135
+ const isImageFile = (fileName: string): boolean => {
136
+ const extension = fileName.split('.').pop()?.toLowerCase();
137
+ return ['jpg', 'jpeg', 'png', 'webp'].includes(extension || '');
138
+ };
139
+
140
+ const formatFileSize = (bytes: number): string => {
141
+ if (bytes === 0) return '0 Bytes';
142
+ const k = 1024;
143
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
144
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
145
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
146
+ };
147
+
148
+ return (
149
+ <View style={styles.container}>
150
+ <Text style={styles.title}>{title}</Text>
151
+ <Text style={styles.instructions}>{instructions}</Text>
152
+
153
+ <View style={styles.uploadArea}>
154
+ <TouchableOpacity
155
+ style={[
156
+ styles.uploadButton,
157
+ isUploading && styles.uploadButtonDisabled,
158
+ { borderColor: component.ui.themeColor as string || '#2DBD60' }
159
+ ]}
160
+ onPress={pickDocument}
161
+ disabled={isUploading}
162
+ >
163
+ <Text style={styles.uploadIcon}>📁</Text>
164
+ <Text style={[
165
+ styles.uploadText,
166
+ { color: component.ui.themeColor as string || '#2DBD60' }
167
+ ]}>
168
+ {isUploading ? (t('common.processing') || 'Processing...') : (t('kyc.additionalDocs.add') || 'Select Files')}
169
+ </Text>
170
+ <Text style={styles.uploadSubtext}>
171
+ {t('kyc.additionalDocs.maxSize', { size: config.maxSizeEach }) || `Max size: ${config.maxSizeEach}`}
172
+ </Text>
173
+ </TouchableOpacity>
174
+ </View>
175
+
176
+ {uploadedFiles.length > 0 && (
177
+ <View style={styles.filesContainer}>
178
+ <Text style={styles.subTitle}>
179
+ {t('kyc.additionalDocs.uploaded') || 'Uploaded Documents'} ({uploadedFiles.length}/{maxFiles})
180
+ </Text>
181
+ <ScrollView style={styles.filesList} showsVerticalScrollIndicator={false}>
182
+ {uploadedFiles.map((file, index) => (
183
+ <View key={index} style={styles.fileItem}>
184
+ {isImageFile(file.name) ? (
185
+ <Image source={{ uri: file.uri }} style={styles.fileThumbnail} />
186
+ ) : (
187
+ <View style={styles.fileIcon}>
188
+ <Text style={styles.fileIconText}>{getFileIcon(file.name)}</Text>
189
+ </View>
190
+ )}
191
+ <View style={styles.fileInfo}>
192
+ <Text style={styles.fileName} numberOfLines={1}>{file.name}</Text>
193
+ <Text style={styles.fileSize}>{formatFileSize(file.size)}</Text>
194
+ </View>
195
+ <TouchableOpacity
196
+ style={styles.removeButton}
197
+ onPress={() => removeFile(index)}
198
+ >
199
+ <Text style={styles.removeButtonText}>✕</Text>
200
+ </TouchableOpacity>
201
+ </View>
202
+ ))}
203
+ </ScrollView>
204
+ </View>
205
+ )}
206
+
207
+ {error && <Text style={styles.errorText}>{error}</Text>}
208
+
209
+ <Button
210
+ title={buttonText}
211
+ onPress={handleContinue}
212
+ fullWidth
213
+ style={styles.button}
214
+ disabled={config.required && uploadedFiles.length < (config.minDocuments || 1)}
215
+ />
216
+ </View>
217
+ );
218
+ };
219
+
220
+ const styles = StyleSheet.create({
221
+ container: {
222
+ padding: 20,
223
+ backgroundColor: 'white',
224
+ borderRadius: 12,
225
+ margin: 16,
226
+ shadowColor: '#000',
227
+ shadowOffset: { width: 0, height: 2 },
228
+ shadowOpacity: 0.1,
229
+ shadowRadius: 4,
230
+ elevation: 3,
231
+ width: '95%',
232
+ },
233
+ title: {
234
+ fontSize: 22,
235
+ fontWeight: 'bold',
236
+ marginBottom: 10,
237
+ color: '#333',
238
+ },
239
+ instructions: {
240
+ fontSize: 16,
241
+ color: '#666',
242
+ marginBottom: 20,
243
+ lineHeight: 22,
244
+ },
245
+ subTitle: {
246
+ fontSize: 16,
247
+ fontWeight: '600',
248
+ marginBottom: 10,
249
+ color: '#333',
250
+ },
251
+ uploadArea: {
252
+ marginBottom: 20,
253
+ },
254
+ uploadButton: {
255
+ borderWidth: 2,
256
+ borderStyle: 'dashed',
257
+ borderRadius: 12,
258
+ padding: 24,
259
+ alignItems: 'center',
260
+ backgroundColor: '#f0f9f0',
261
+ },
262
+ uploadButtonDisabled: {
263
+ opacity: 0.6,
264
+ },
265
+ uploadIcon: {
266
+ fontSize: 32,
267
+ marginBottom: 8,
268
+ },
269
+ uploadText: {
270
+ fontSize: 16,
271
+ fontWeight: '600',
272
+ marginBottom: 4,
273
+ },
274
+ uploadSubtext: {
275
+ fontSize: 12,
276
+ color: '#666',
277
+ },
278
+ filesContainer: {
279
+ flex: 1,
280
+ marginBottom: 10,
281
+ },
282
+ filesList: {
283
+ maxHeight: 200,
284
+ },
285
+ fileItem: {
286
+ flexDirection: 'row',
287
+ alignItems: 'center',
288
+ padding: 10,
289
+ backgroundColor: '#f9f9f9',
290
+ borderRadius: 8,
291
+ marginBottom: 8,
292
+ borderWidth: 1,
293
+ borderColor: '#eee',
294
+ },
295
+ fileThumbnail: {
296
+ width: 36,
297
+ height: 36,
298
+ borderRadius: 4,
299
+ marginRight: 10,
300
+ },
301
+ fileIcon: {
302
+ width: 36,
303
+ height: 36,
304
+ borderRadius: 4,
305
+ backgroundColor: '#eee',
306
+ justifyContent: 'center',
307
+ alignItems: 'center',
308
+ marginRight: 10,
309
+ },
310
+ fileIconText: {
311
+ fontSize: 18,
312
+ },
313
+ fileInfo: {
314
+ flex: 1,
315
+ },
316
+ fileName: {
317
+ fontSize: 14,
318
+ fontWeight: '500',
319
+ color: '#333',
320
+ },
321
+ fileSize: {
322
+ fontSize: 12,
323
+ color: '#888',
324
+ },
325
+ removeButton: {
326
+ width: 24,
327
+ height: 24,
328
+ borderRadius: 12,
329
+ backgroundColor: '#ff4444',
330
+ justifyContent: 'center',
331
+ alignItems: 'center',
332
+ marginLeft: 8,
333
+ },
334
+ removeButtonText: {
335
+ color: 'white',
336
+ fontSize: 12,
337
+ fontWeight: 'bold',
338
+ },
339
+ errorText: {
340
+ color: 'red',
341
+ marginBottom: 10,
342
+ },
343
+ button: {
344
+ marginTop: 10,
345
+ },
346
+ });
@@ -0,0 +1,264 @@
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
+
8
+ interface EmailVerificationTemplateProps {
9
+ component: TemplateComponent;
10
+ value?: any;
11
+ onValueChange: (data: any) => void;
12
+ error?: string;
13
+ language?: string;
14
+ }
15
+
16
+ type VerificationStep = 'email' | 'otp';
17
+
18
+ export const EmailVerificationTemplate: React.FC<EmailVerificationTemplateProps> = ({
19
+ component,
20
+ value,
21
+ onValueChange,
22
+ error: propError,
23
+ }) => {
24
+ const { actions, getLocalizedText } = useTemplateKYCFlowContext();
25
+ const { t } = useI18n();
26
+ // const config = component.config as EmailVerificationConfig; // Keep for future use
27
+
28
+ // State
29
+ const [step, setStep] = useState<VerificationStep>('email');
30
+ const [email, setEmail] = useState('');
31
+ const [otp, setOtp] = useState('');
32
+ const [localError, setLocalError] = useState<string | null>(null);
33
+ const [isSimulating, setIsSimulating] = useState(false);
34
+
35
+ const title = getLocalizedText(component.labels as LocalizedText);
36
+ const instructions = getLocalizedText(component.instructions as LocalizedText);
37
+
38
+ // Determine button text based on step
39
+ const verifyButtonText = getLocalizedText((component.ui as any).buttonText) || t('common.verify') || 'Verify';
40
+ const sendButtonText = t('common.sendCode') || 'Send Verification Code';
41
+ const buttonText = step === 'email' ? sendButtonText : verifyButtonText;
42
+
43
+ const handleSendCode = () => {
44
+ if (!email || !email.includes('@')) {
45
+ setLocalError(t('errors.invalidEmail') || 'Please enter a valid email address');
46
+ return;
47
+ }
48
+
49
+ setLocalError(null);
50
+ setIsSimulating(true);
51
+
52
+ // Simulate API call to send code
53
+ setTimeout(() => {
54
+ setIsSimulating(false);
55
+ setStep('otp');
56
+ // For demo purposes, we could show an alert or toast here with the code
57
+ // But we'll just expect them to know 123456 or type anything for now if not strict
58
+ }, 1500);
59
+ };
60
+
61
+ const handleVerifyCode = () => {
62
+ if (!otp || otp.length < 4) {
63
+ setLocalError(t('errors.invalidCode') || 'Please enter the 6-digit code');
64
+ return;
65
+ }
66
+
67
+ setLocalError(null);
68
+ setIsSimulating(true);
69
+
70
+ // Simulate verification API
71
+ setTimeout(() => {
72
+ setIsSimulating(false);
73
+
74
+ // Mock validation logic
75
+ // Let's accept '123456' as the correct code or any code for testing if strictly requested?
76
+ // User said "verify with error message" implying we should support failure.
77
+ // Let's say if code is "000000" it fails, otherwise success, OR hardcode a success one.
78
+ // User said "verify with error message and next component if is a good one"
79
+ // Let's make "123456" the good one for clarity.
80
+
81
+ if (otp === '123456') {
82
+ onValueChange({ email, otp, verified: true });
83
+ actions.nextComponent();
84
+ } else {
85
+ setLocalError(t('errors.wrongCode') || 'Invalid verification code. Try 123456');
86
+ }
87
+ }, 1500);
88
+ };
89
+
90
+ const onChangeEmail = (text: string) => {
91
+ setEmail(text);
92
+ if (localError) setLocalError(null);
93
+ };
94
+
95
+ const onChangeOtp = (text: string) => {
96
+ setOtp(text);
97
+ if (localError) setLocalError(null);
98
+ };
99
+
100
+ const handleBackToEmail = () => {
101
+ setStep('email');
102
+ setOtp('');
103
+ setLocalError(null);
104
+ };
105
+
106
+ return (
107
+ <View style={styles.container}>
108
+ <Text style={styles.title}>{title}</Text>
109
+ <Text style={styles.instructions}>
110
+ {step === 'email' ? instructions : (t('kyc.enterCodeSent') || `Please enter the code sent to ${email}`)}
111
+ </Text>
112
+
113
+ <View style={styles.contentContainer}>
114
+ {step === 'email' ? (
115
+ <View style={styles.inputContainer}>
116
+ <Text style={styles.label}>{t('common.email') || 'Email'}</Text>
117
+ <TextInput
118
+ style={styles.input}
119
+ placeholder="name@example.com"
120
+ value={email}
121
+ onChangeText={onChangeEmail}
122
+ keyboardType="email-address"
123
+ autoCapitalize="none"
124
+ autoCorrect={false}
125
+ editable={!isSimulating}
126
+ />
127
+ </View>
128
+ ) : (
129
+ <View style={styles.inputContainer}>
130
+ <Text style={styles.label}>{t('common.verificationCode') || 'Verification Code'}</Text>
131
+ <TextInput
132
+ style={styles.input}
133
+ placeholder="123456"
134
+ value={otp}
135
+ onChangeText={onChangeOtp}
136
+ keyboardType="number-pad"
137
+ maxLength={6}
138
+ editable={!isSimulating}
139
+ />
140
+ <TouchableOpacity onPress={handleBackToEmail} style={styles.changeEmailLink}>
141
+ <Text style={styles.changeEmailText}>{t('common.changeEmail') || 'Change email'}</Text>
142
+ </TouchableOpacity>
143
+ </View>
144
+ )}
145
+
146
+ {(localError || propError) && (
147
+ <Text style={styles.errorText}>{localError || propError}</Text>
148
+ )}
149
+
150
+ <Button
151
+ title={isSimulating ? (t('common.processing') || 'Processing...') : buttonText}
152
+ onPress={step === 'email' ? handleSendCode : handleVerifyCode}
153
+
154
+
155
+ disabled={
156
+ isSimulating ||
157
+ (step === 'email' ? !email : !otp)
158
+ }
159
+ />
160
+
161
+ {step === 'otp' && (
162
+ <TouchableOpacity
163
+ onPress={() => {
164
+ // Resend logic
165
+ Alert.alert(
166
+ t('common.codeResent') || 'Code Resent',
167
+ t('common.codeResentMessage', { email }) || 'Code resent to ' + email
168
+ );
169
+ }}
170
+ style={styles.resendButton}
171
+ disabled={isSimulating}
172
+ >
173
+ <Text style={styles.resendText}>{t('common.resendCode') || 'Resend Code'}</Text>
174
+ </TouchableOpacity>
175
+ )}
176
+ </View>
177
+ </View>
178
+ );
179
+ };
180
+
181
+ const styles = StyleSheet.create({
182
+ container: {
183
+ padding: 24,
184
+ backgroundColor: 'white',
185
+ borderRadius: 16,
186
+ margin: 16,
187
+ shadowColor: '#000',
188
+ shadowOffset: { width: 0, height: 4 },
189
+ shadowOpacity: 0.1,
190
+ shadowRadius: 12,
191
+ elevation: 5,
192
+ width: '95%',
193
+ },
194
+ title: {
195
+ fontSize: 24,
196
+ fontWeight: '700',
197
+ marginBottom: 8,
198
+ color: '#1a1a1a',
199
+ textAlign: 'center',
200
+ },
201
+ instructions: {
202
+ fontSize: 16,
203
+ color: '#666',
204
+ marginBottom: 32,
205
+ lineHeight: 24,
206
+ textAlign: 'center',
207
+ },
208
+ contentContainer: {
209
+ // width: '100%',
210
+ },
211
+ inputContainer: {
212
+ marginBottom: 24,
213
+ },
214
+ label: {
215
+ fontSize: 14,
216
+ fontWeight: '600',
217
+ color: '#333',
218
+ marginBottom: 8,
219
+ marginLeft: 4,
220
+ },
221
+ input: {
222
+ borderWidth: 1,
223
+ borderColor: '#e0e0e0',
224
+ padding: 16,
225
+ borderRadius: 12,
226
+ fontSize: 16,
227
+ backgroundColor: '#f8f9fa',
228
+ color: '#333',
229
+ },
230
+ errorText: {
231
+ color: '#dc2626',
232
+ marginBottom: 16,
233
+ fontSize: 14,
234
+ textAlign: 'center',
235
+ backgroundColor: '#fee2e2',
236
+ padding: 8,
237
+ borderRadius: 8,
238
+ overflow: 'hidden',
239
+ },
240
+ button: {
241
+ height: 50,
242
+ borderRadius: 12,
243
+ width: "100%"
244
+ },
245
+ changeEmailLink: {
246
+ alignSelf: 'flex-end',
247
+ marginTop: 8,
248
+ },
249
+ changeEmailText: {
250
+ color: '#2DBD60',
251
+ fontSize: 14,
252
+ fontWeight: '500',
253
+ },
254
+ resendButton: {
255
+ marginTop: 16,
256
+ alignItems: 'center',
257
+ width: "100%"
258
+ },
259
+ resendText: {
260
+ color: '#666',
261
+ fontSize: 14,
262
+ textDecorationLine: 'underline',
263
+ },
264
+ });