@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.
- package/android/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml +12 -5
- package/android/build/intermediates/aar_main_jar/debug/syncDebugLibJars/classes.jar +0 -0
- package/android/build/intermediates/annotations_typedef_file/debug/extractDebugAnnotations/typedefs.txt +0 -0
- package/android/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties +1 -1
- package/android/build/intermediates/incremental/debug-mergeJavaRes/merge-state +0 -0
- package/android/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt +61 -59
- package/android/build/intermediates/merged_java_res/debug/mergeDebugJavaResource/feature-transfergratis-react-native-sdk.jar +0 -0
- package/android/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml +12 -5
- package/android/build/kotlin/compileDebugKotlin/cacheable/last-build.bin +0 -0
- package/android/build/kotlin/compileDebugKotlin/local-state/build-history.bin +0 -0
- package/android/build/outputs/aar/transfergratis-react-native-sdk-debug.aar +0 -0
- package/android/build/outputs/logs/manifest-merger-debug-report.txt +26 -34
- package/android/src/main/AndroidManifest.xml +22 -7
- package/build/components/EnhancedCameraView.web.d.ts.map +1 -1
- package/build/components/EnhancedCameraView.web.js +76 -21
- package/build/components/EnhancedCameraView.web.js.map +1 -1
- package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts +12 -0
- package/build/components/KYCElements/AdditionalDocumentsTemplate.d.ts.map +1 -0
- package/build/components/KYCElements/AdditionalDocumentsTemplate.js +283 -0
- package/build/components/KYCElements/AdditionalDocumentsTemplate.js.map +1 -0
- package/build/components/KYCElements/EmailVerificationTemplate.d.ts +12 -0
- package/build/components/KYCElements/EmailVerificationTemplate.d.ts.map +1 -0
- package/build/components/KYCElements/EmailVerificationTemplate.js +212 -0
- package/build/components/KYCElements/EmailVerificationTemplate.js.map +1 -0
- package/build/components/KYCElements/IDCardCapture.d.ts.map +1 -1
- package/build/components/KYCElements/IDCardCapture.js +216 -14
- package/build/components/KYCElements/IDCardCapture.js.map +1 -1
- package/build/components/KYCElements/OrientationVideoCapture.d.ts +2 -0
- package/build/components/KYCElements/OrientationVideoCapture.d.ts.map +1 -1
- package/build/components/KYCElements/OrientationVideoCapture.js +2 -2
- package/build/components/KYCElements/OrientationVideoCapture.js.map +1 -1
- package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts +2 -0
- package/build/components/KYCElements/OrientationVideoCaptureEnhanced.d.ts.map +1 -1
- package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js +2 -2
- package/build/components/KYCElements/OrientationVideoCaptureEnhanced.js.map +1 -1
- package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts +2 -0
- package/build/components/KYCElements/OrientationVideoCaptureFinal.d.ts.map +1 -1
- package/build/components/KYCElements/OrientationVideoCaptureFinal.js +2 -2
- package/build/components/KYCElements/OrientationVideoCaptureFinal.js.map +1 -1
- package/build/components/KYCElements/PersonalInformationTemplate.d.ts +12 -0
- package/build/components/KYCElements/PersonalInformationTemplate.d.ts.map +1 -0
- package/build/components/KYCElements/PersonalInformationTemplate.js +120 -0
- package/build/components/KYCElements/PersonalInformationTemplate.js.map +1 -0
- package/build/components/KYCElements/PhoneVerificationTemplate.d.ts +12 -0
- package/build/components/KYCElements/PhoneVerificationTemplate.d.ts.map +1 -0
- package/build/components/KYCElements/PhoneVerificationTemplate.js +185 -0
- package/build/components/KYCElements/PhoneVerificationTemplate.js.map +1 -0
- package/build/components/KYCElements/SelfieCaptureTemplate.d.ts.map +1 -1
- package/build/components/KYCElements/SelfieCaptureTemplate.js +7 -3
- package/build/components/KYCElements/SelfieCaptureTemplate.js.map +1 -1
- package/build/components/KYCElements/WelcomeTemplate.js +2 -1
- package/build/components/KYCElements/WelcomeTemplate.js.map +1 -1
- package/build/components/OverLay/type.d.ts +2 -0
- package/build/components/OverLay/type.d.ts.map +1 -1
- package/build/components/OverLay/type.js.map +1 -1
- package/build/components/TemplateKYCExample.d.ts +10 -0
- package/build/components/TemplateKYCExample.d.ts.map +1 -1
- package/build/components/TemplateKYCExample.js +7 -30
- package/build/components/TemplateKYCExample.js.map +1 -1
- package/build/components/TemplateKYCFlowRefactored.d.ts +12 -0
- package/build/components/TemplateKYCFlowRefactored.d.ts.map +1 -1
- package/build/components/TemplateKYCFlowRefactored.js +25 -3
- package/build/components/TemplateKYCFlowRefactored.js.map +1 -1
- package/build/config/KYCConfig.d.ts +14 -0
- package/build/config/KYCConfig.d.ts.map +1 -0
- package/build/config/KYCConfig.js +26 -0
- package/build/config/KYCConfig.js.map +1 -0
- package/build/config/allowedDomains.d.ts.map +1 -1
- package/build/config/allowedDomains.js +4 -19
- package/build/config/allowedDomains.js.map +1 -1
- package/build/hooks/useOrientationVideo.d.ts +2 -1
- package/build/hooks/useOrientationVideo.d.ts.map +1 -1
- package/build/hooks/useOrientationVideo.js +3 -3
- package/build/hooks/useOrientationVideo.js.map +1 -1
- package/build/hooks/useTemplateKYCFlow.d.ts +18 -1
- package/build/hooks/useTemplateKYCFlow.d.ts.map +1 -1
- package/build/hooks/useTemplateKYCFlow.js +410 -56
- package/build/hooks/useTemplateKYCFlow.js.map +1 -1
- package/build/i18n/en/index.d.ts +42 -0
- package/build/i18n/en/index.d.ts.map +1 -1
- package/build/i18n/en/index.js +44 -2
- package/build/i18n/en/index.js.map +1 -1
- package/build/i18n/fr/index.d.ts +28 -0
- package/build/i18n/fr/index.d.ts.map +1 -1
- package/build/i18n/fr/index.js +30 -2
- package/build/i18n/fr/index.js.map +1 -1
- package/build/i18n/types.d.ts +2 -0
- package/build/i18n/types.d.ts.map +1 -1
- package/build/i18n/types.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.d.ts.map +1 -1
- package/build/index.js +2 -0
- package/build/index.js.map +1 -1
- package/build/modules/api/CardAuthentification.d.ts +24 -3
- package/build/modules/api/CardAuthentification.d.ts.map +1 -1
- package/build/modules/api/CardAuthentification.js +90 -12
- package/build/modules/api/CardAuthentification.js.map +1 -1
- package/build/modules/api/KYCService.d.ts +17 -7
- package/build/modules/api/KYCService.d.ts.map +1 -1
- package/build/modules/api/KYCService.js +125 -37
- package/build/modules/api/KYCService.js.map +1 -1
- package/build/modules/api/SelfieVerification.d.ts +3 -1
- package/build/modules/api/SelfieVerification.d.ts.map +1 -1
- package/build/modules/api/SelfieVerification.js +17 -1
- package/build/modules/api/SelfieVerification.js.map +1 -1
- package/build/modules/api/TemplateService.d.ts +0 -1
- package/build/modules/api/TemplateService.d.ts.map +1 -1
- package/build/modules/api/TemplateService.js +3 -3
- package/build/modules/api/TemplateService.js.map +1 -1
- package/build/modules/camera/VisionCameraModule.web.d.ts.map +1 -1
- package/build/modules/camera/VisionCameraModule.web.js +27 -8
- package/build/modules/camera/VisionCameraModule.web.js.map +1 -1
- package/build/types/KYC.types.d.ts +130 -5
- package/build/types/KYC.types.d.ts.map +1 -1
- package/build/types/KYC.types.js.map +1 -1
- package/build/types/env.types.d.ts +13 -0
- package/build/types/env.types.d.ts.map +1 -0
- package/build/types/env.types.js +2 -0
- package/build/types/env.types.js.map +1 -0
- package/build/utils/cropByObb.d.ts +7 -0
- package/build/utils/cropByObb.d.ts.map +1 -1
- package/build/utils/cropByObb.js +20 -1
- package/build/utils/cropByObb.js.map +1 -1
- package/build/utils/deviceDetection.d.ts +6 -0
- package/build/utils/deviceDetection.d.ts.map +1 -0
- package/build/utils/deviceDetection.js +12 -0
- package/build/utils/deviceDetection.js.map +1 -0
- package/build/utils/platformAlert.d.ts.map +1 -1
- package/build/utils/platformAlert.js.map +1 -1
- package/build/utils/template-transformer.d.ts.map +1 -1
- package/build/utils/template-transformer.js +12 -0
- package/build/utils/template-transformer.js.map +1 -1
- package/build/web/WebKYCEntry.d.ts.map +1 -1
- package/build/web/WebKYCEntry.js +88 -38
- package/build/web/WebKYCEntry.js.map +1 -1
- package/package.json +1 -1
- package/plugin/build/index.d.ts +1 -0
- package/plugin/build/index.js +3 -1
- package/plugin/build/withRemovePermissions.d.ts +3 -0
- package/plugin/build/withRemovePermissions.js +67 -0
- package/plugin/build/withVisionCamera.js +3 -4
- package/plugin/src/index.ts +2 -1
- package/plugin/src/withRemovePermissions.js +85 -0
- package/plugin/src/withRemovePermissions.ts +83 -0
- package/plugin/src/withVisionCamera.js +3 -4
- package/plugin/src/withVisionCamera.ts +3 -4
- package/plugin/tsconfig.tsbuildinfo +1 -1
- package/plugin.js +6 -1
- package/src/components/EnhancedCameraView.web.tsx +76 -21
- package/src/components/KYCElements/AdditionalDocumentsTemplate.tsx +346 -0
- package/src/components/KYCElements/EmailVerificationTemplate.tsx +278 -0
- package/src/components/KYCElements/IDCardCapture.tsx +253 -21
- package/src/components/KYCElements/OrientationVideoCapture.tsx +4 -1
- package/src/components/KYCElements/OrientationVideoCaptureEnhanced.tsx +4 -1
- package/src/components/KYCElements/OrientationVideoCaptureFinal.tsx +4 -1
- package/src/components/KYCElements/PersonalInformationTemplate.tsx +158 -0
- package/src/components/KYCElements/PhoneVerificationTemplate.tsx +253 -0
- package/src/components/KYCElements/SelfieCaptureTemplate.tsx +6 -3
- package/src/components/KYCElements/WelcomeTemplate.tsx +2 -1
- package/src/components/OverLay/type.ts +2 -0
- package/src/components/TemplateKYCExample.tsx +35 -46
- package/src/components/TemplateKYCFlowRefactored.tsx +46 -2
- package/src/config/KYCConfig.ts +34 -0
- package/src/config/allowedDomains.ts +7 -26
- package/src/hooks/useOrientationVideo.ts +5 -4
- package/src/hooks/useTemplateKYCFlow.tsx +443 -56
- package/src/i18n/en/index.ts +46 -3
- package/src/i18n/fr/index.ts +31 -2
- package/src/i18n/types.ts +2 -0
- package/src/index.ts +3 -0
- package/src/modules/api/CardAuthentification.ts +98 -12
- package/src/modules/api/KYCService.ts +158 -37
- package/src/modules/api/SelfieVerification.ts +25 -3
- package/src/modules/api/TemplateService.ts +4 -4
- package/src/modules/camera/VisionCameraModule.web.ts +30 -12
- package/src/types/KYC.types.ts +153 -6
- package/src/types/env.types.ts +13 -0
- package/src/utils/cropByObb.ts +20 -1
- package/src/utils/deviceDetection.ts +11 -0
- package/src/utils/platformAlert.ts +1 -0
- package/src/utils/template-transformer.ts +20 -8
- package/src/web/WebKYCEntry.tsx +123 -61
package/plugin.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
const withVisionCamera = require('./plugin/build/withVisionCamera').default;
|
|
2
2
|
const withLocation = require('./plugin/build/withLocation').default;
|
|
3
|
+
const withRemovePermissions = require('./plugin/build/withRemovePermissions').default;
|
|
3
4
|
|
|
4
|
-
// Combine
|
|
5
|
+
// Combine all plugins
|
|
5
6
|
const withCombinedPlugins = (config, props = {}) => {
|
|
6
7
|
const { visionCamera = {}, location = {} } = props;
|
|
7
8
|
|
|
9
|
+
// First apply feature plugins
|
|
8
10
|
config = withVisionCamera(config, visionCamera);
|
|
9
11
|
config = withLocation(config, location);
|
|
10
12
|
|
|
13
|
+
// Then remove unwanted permissions (must be last to clean up any permissions added by dependencies)
|
|
14
|
+
config = withRemovePermissions(config);
|
|
15
|
+
|
|
11
16
|
return config;
|
|
12
17
|
};
|
|
13
18
|
|
|
@@ -27,6 +27,7 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
27
27
|
videoDuration = 10,
|
|
28
28
|
onSilentCapture,
|
|
29
29
|
silentCaptureResult,
|
|
30
|
+
captureStabilizationDelayMs = 2500,
|
|
30
31
|
}) => {
|
|
31
32
|
const { t } = useI18n();
|
|
32
33
|
|
|
@@ -63,6 +64,24 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
63
64
|
};
|
|
64
65
|
}, [stream]);
|
|
65
66
|
|
|
67
|
+
// Build video constraints for quality (min + ideal improve Android/Samsung web quality; higher ideal for documents)
|
|
68
|
+
const getVideoConstraints = useCallback((): MediaTrackConstraints => {
|
|
69
|
+
const isHigh = quality === 'high';
|
|
70
|
+
const isMedium = quality === 'medium';
|
|
71
|
+
return {
|
|
72
|
+
facingMode: cameraType === 'front' ? 'user' : 'environment',
|
|
73
|
+
// min forces Android Chrome to use at least this resolution (avoids 640x480 default)
|
|
74
|
+
width: {
|
|
75
|
+
min: isHigh ? 1280 : isMedium ? 960 : 640,
|
|
76
|
+
ideal: isHigh ? 2560 : isMedium ? 1280 : 640,
|
|
77
|
+
},
|
|
78
|
+
height: {
|
|
79
|
+
min: isHigh ? 720 : isMedium ? 540 : 480,
|
|
80
|
+
ideal: isHigh ? 1440 : isMedium ? 720 : 480,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}, [cameraType, quality]);
|
|
84
|
+
|
|
66
85
|
const checkPermissions = async () => {
|
|
67
86
|
try {
|
|
68
87
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
@@ -70,8 +89,11 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
70
89
|
return;
|
|
71
90
|
}
|
|
72
91
|
|
|
73
|
-
//
|
|
74
|
-
const stream = await navigator.mediaDevices.getUserMedia({
|
|
92
|
+
// Use same constraints as startCamera so Android Chrome doesn't cache low resolution (e.g. 640x480)
|
|
93
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
94
|
+
video: getVideoConstraints(),
|
|
95
|
+
audio: enableVideo,
|
|
96
|
+
});
|
|
75
97
|
stream.getTracks().forEach(track => track.stop());
|
|
76
98
|
|
|
77
99
|
setHasPermission(true);
|
|
@@ -88,16 +110,32 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
88
110
|
stream.getTracks().forEach(track => track.stop());
|
|
89
111
|
}
|
|
90
112
|
|
|
91
|
-
const constraints = {
|
|
92
|
-
video:
|
|
93
|
-
facingMode: cameraType === 'front' ? 'user' : 'environment',
|
|
94
|
-
width: { ideal: quality === 'high' ? 1920 : quality === 'medium' ? 1280 : 640 },
|
|
95
|
-
height: { ideal: quality === 'high' ? 1080 : quality === 'medium' ? 720 : 480 },
|
|
96
|
-
},
|
|
113
|
+
const constraints: MediaStreamConstraints = {
|
|
114
|
+
video: getVideoConstraints(),
|
|
97
115
|
audio: enableVideo,
|
|
98
116
|
};
|
|
99
117
|
|
|
100
|
-
|
|
118
|
+
let newStream: MediaStream;
|
|
119
|
+
try {
|
|
120
|
+
newStream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
const name = err instanceof Error ? err.name : '';
|
|
123
|
+
// On some Android devices, min constraints can fail; fallback to ideal only
|
|
124
|
+
if (name === 'OverconstrainedError') {
|
|
125
|
+
const fallbackConstraints: MediaStreamConstraints = {
|
|
126
|
+
video: {
|
|
127
|
+
facingMode: cameraType === 'front' ? 'user' : 'environment',
|
|
128
|
+
width: { ideal: quality === 'high' ? 2560 : quality === 'medium' ? 1280 : 640 },
|
|
129
|
+
height: { ideal: quality === 'high' ? 1440 : quality === 'medium' ? 720 : 480 },
|
|
130
|
+
},
|
|
131
|
+
audio: enableVideo,
|
|
132
|
+
};
|
|
133
|
+
newStream = await navigator.mediaDevices.getUserMedia(fallbackConstraints);
|
|
134
|
+
} else {
|
|
135
|
+
throw err;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
101
139
|
setStream(newStream);
|
|
102
140
|
|
|
103
141
|
if (videoRef.current) {
|
|
@@ -159,11 +197,19 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
159
197
|
canvas.width = video.videoWidth;
|
|
160
198
|
canvas.height = video.videoHeight;
|
|
161
199
|
|
|
162
|
-
//
|
|
163
|
-
|
|
200
|
+
// For front camera: draw flipped so captured image matches non-mirrored preview (no mirror confusion)
|
|
201
|
+
if (cameraType === 'front') {
|
|
202
|
+
context.save();
|
|
203
|
+
context.translate(canvas.width, 0);
|
|
204
|
+
context.scale(-1, 1);
|
|
205
|
+
context.drawImage(video, 0, 0);
|
|
206
|
+
context.restore();
|
|
207
|
+
} else {
|
|
208
|
+
context.drawImage(video, 0, 0);
|
|
209
|
+
}
|
|
164
210
|
|
|
165
|
-
// Convert to base64
|
|
166
|
-
const imageDataUrl = canvas.toDataURL('image/jpeg', 0.
|
|
211
|
+
// Convert to base64 (0.95 for document/ID capture clarity; backend can exploit text better)
|
|
212
|
+
const imageDataUrl = canvas.toDataURL('image/jpeg', 0.95);
|
|
167
213
|
|
|
168
214
|
onSilentCapture?.({
|
|
169
215
|
success: true,
|
|
@@ -176,21 +222,29 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
176
222
|
error: error instanceof Error ? error.message : 'Failed to capture photo',
|
|
177
223
|
});
|
|
178
224
|
}
|
|
179
|
-
}, [isInitialized, onError, onSilentCapture]);
|
|
225
|
+
}, [isInitialized, cameraType, onError, onSilentCapture]);
|
|
180
226
|
|
|
181
227
|
|
|
182
|
-
//
|
|
228
|
+
// Stabilization delay then auto-capture every 5s; stop as soon as capture is validated (no more new captures)
|
|
183
229
|
useEffect(() => {
|
|
184
|
-
if (!showCamera || !isInitialized) {
|
|
230
|
+
if (!showCamera || !isInitialized || silentCaptureResult?.success) {
|
|
185
231
|
return;
|
|
186
232
|
}
|
|
187
233
|
|
|
188
|
-
const
|
|
234
|
+
const delayMs = Math.max(0, captureStabilizationDelayMs);
|
|
235
|
+
const intervalMs = 5000;
|
|
236
|
+
let intervalId: ReturnType<typeof setInterval> | null = null;
|
|
237
|
+
|
|
238
|
+
const timeoutId = setTimeout(() => {
|
|
189
239
|
captureSilentPhoto();
|
|
190
|
-
|
|
240
|
+
intervalId = setInterval(captureSilentPhoto, intervalMs);
|
|
241
|
+
}, delayMs);
|
|
191
242
|
|
|
192
|
-
return () =>
|
|
193
|
-
|
|
243
|
+
return () => {
|
|
244
|
+
clearTimeout(timeoutId);
|
|
245
|
+
if (intervalId) clearInterval(intervalId);
|
|
246
|
+
};
|
|
247
|
+
}, [showCamera, isInitialized, captureStabilizationDelayMs, captureSilentPhoto, silentCaptureResult?.success]);
|
|
194
248
|
|
|
195
249
|
const startVideoRecording = useCallback(async () => {
|
|
196
250
|
try {
|
|
@@ -299,7 +353,7 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
299
353
|
|
|
300
354
|
return (
|
|
301
355
|
<View style={[styles.container, style]}>
|
|
302
|
-
{/* Video element */}
|
|
356
|
+
{/* Video element; no mirror for front camera so preview matches final photo and doesn't confuse users */}
|
|
303
357
|
<video
|
|
304
358
|
ref={videoRef}
|
|
305
359
|
style={{
|
|
@@ -307,6 +361,7 @@ export const EnhancedCameraView: React.FC<EnhancedCameraViewProps> = ({
|
|
|
307
361
|
width: '100%',
|
|
308
362
|
height: '100%',
|
|
309
363
|
objectFit: 'cover',
|
|
364
|
+
transform: cameraType === 'front' ? 'scaleX(-1)' : undefined,
|
|
310
365
|
}}
|
|
311
366
|
autoPlay
|
|
312
367
|
playsInline
|
|
@@ -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
|
+
});
|