react-native-biometric-verifier 0.0.7 → 0.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-biometric-verifier",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "description": "A React Native module for biometric verification with face recognition and QR code scanning",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -0,0 +1,24 @@
1
+ import { useCallback } from "react";
2
+
3
+ /**
4
+ * Hook to safely execute a callback function with error handling
5
+ */
6
+ export const useSafeCallback = (callback, notifyMessage) => {
7
+ return useCallback(
8
+ (response) => {
9
+ if (typeof callback === "function") {
10
+ try {
11
+ callback(response);
12
+ } catch (err) {
13
+ console.error("Callback execution failed:", err);
14
+ if (typeof notifyMessage === "function") {
15
+ notifyMessage("Unexpected error while processing callback.", "error");
16
+ }
17
+ }
18
+ } else {
19
+ console.log("Biometric Verification Response:", response);
20
+ }
21
+ },
22
+ [callback, notifyMessage]
23
+ );
24
+ };
package/src/index.js CHANGED
@@ -1,331 +1,376 @@
1
- import React, { useState, useEffect, useRef, useCallback } from 'react';
1
+ import React, {
2
+ useState,
3
+ useEffect,
4
+ useRef,
5
+ useCallback,
6
+ useMemo,
7
+ } from "react";
2
8
  import {
3
9
  View,
4
10
  TouchableOpacity,
5
11
  Text,
6
12
  Modal,
7
- } from 'react-native';
8
- import Icon from 'react-native-vector-icons/MaterialIcons';
9
- import { useCountdown } from './hooks/useCountdown';
10
- import { useGeolocation } from './hooks/useGeolocation';
11
- import { useImageProcessing } from './hooks/useImageProcessing';
12
- import { useNotifyMessage } from './hooks/useNotifyMessage';
13
- import { getDistanceInMeters } from './utils/distanceCalculator';
13
+ InteractionManager,
14
+ } from "react-native";
15
+ import Icon from "react-native-vector-icons/MaterialIcons";
16
+ import { useCountdown } from "./hooks/useCountdown";
17
+ import { useGeolocation } from "./hooks/useGeolocation";
18
+ import { useImageProcessing } from "./hooks/useImageProcessing";
19
+ import { useNotifyMessage } from "./hooks/useNotifyMessage";
20
+ import { getDistanceInMeters } from "./utils/distanceCalculator";
14
21
  import {
15
22
  ANIMATION_STATES,
16
23
  COLORS,
17
24
  COUNTDOWN_DURATION,
18
25
  MAX_DISTANCE_METERS,
19
- } from './utils/constants';
20
- import Loader from './components/Loader';
21
- import { CountdownTimer } from './components/CountdownTimer';
22
- import { EmployeeCard } from './components/EmployeeCard';
23
- import { Notification } from './components/Notification';
24
- import { StateIndicator } from './components/StateIndicator';
25
- import { styles } from './components/styles';
26
- import { useNavigation } from '@react-navigation/native';
27
- import networkServiceCall from './utils/NetworkServiceCall';
28
- import { getLoaderGif } from './utils/getLoaderGif';
29
-
30
- const BiometricVerificationModal = React.memo(({ data, qrscan = false, callback, apiurl }) => {
31
- const navigation = useNavigation();
32
-
33
- const { countdown, startCountdown, resetCountdown } = useCountdown();
34
- const { requestLocationPermission, getCurrentLocation } = useGeolocation();
35
- const { convertImageToBase64 } = useImageProcessing();
36
- const { notification, fadeAnim, slideAnim, notifyMessage } = useNotifyMessage();
37
-
38
- const [state, setState] = useState({
39
- isLoading: false,
40
- currentStep: 'Start',
41
- employeeData: null,
42
- modalVisible: false,
43
- animationState: ANIMATION_STATES.FACE_SCAN,
44
- });
45
-
46
- const dataRef = useRef(data);
47
- const mountedRef = useRef(true);
48
-
49
- useEffect(() => {
50
- return () => {
51
- mountedRef.current = false;
52
- };
53
- }, []);
54
-
55
- useEffect(() => {
56
- dataRef.current = data;
57
- }, [data]);
58
-
59
- const updateState = useCallback((newState) => {
60
- if (mountedRef.current) {
61
- setState(prev => ({ ...prev, ...newState }));
62
- }
63
- }, []);
64
-
65
- const resetState = useCallback(() => {
66
- updateState({
67
- currentStep: 'Start',
26
+ } from "./utils/constants";
27
+ import Loader from "./components/Loader";
28
+ import { CountdownTimer } from "./components/CountdownTimer";
29
+ import { EmployeeCard } from "./components/EmployeeCard";
30
+ import { Notification } from "./components/Notification";
31
+ import { StateIndicator } from "./components/StateIndicator";
32
+ import { styles } from "./components/styles";
33
+ import { useNavigation } from "@react-navigation/native";
34
+ import networkServiceCall from "./utils/NetworkServiceCall";
35
+ import { getLoaderGif } from "./utils/getLoaderGif";
36
+ import { useSafeCallback } from "./hooks/useSafeCallback";
37
+
38
+ const BiometricVerificationModal = React.memo(
39
+ ({ data, qrscan = false, callback, apiurl }) => {
40
+ const navigation = useNavigation();
41
+
42
+ const { countdown, startCountdown, resetCountdown } = useCountdown();
43
+ const { requestLocationPermission, getCurrentLocation } = useGeolocation();
44
+ const { convertImageToBase64 } = useImageProcessing();
45
+ const { notification, fadeAnim, slideAnim, notifyMessage } =
46
+ useNotifyMessage();
47
+
48
+ const [modalVisible, setModalVisible] = useState(false);
49
+ const [state, setState] = useState({
50
+ isLoading: false,
51
+ currentStep: "Start",
68
52
  employeeData: null,
69
53
  animationState: ANIMATION_STATES.FACE_SCAN,
70
- isLoading: false,
71
- });
72
- resetCountdown();
73
- }, [resetCountdown, updateState]);
74
-
75
- const handleCountdownFinish = useCallback(() => {
76
- notifyMessage('Time is up! Please try again.', 'error');
77
- updateState({
78
- modalVisible: false,
79
- animationState: ANIMATION_STATES.ERROR,
80
54
  });
81
- resetState();
82
- if (navigation.canGoBack()) navigation.goBack();
83
- }, [navigation, notifyMessage, resetState, updateState]);
84
-
85
- const validateApiUrl = useCallback(() => {
86
- if (!apiurl || typeof apiurl !== 'string') {
87
- notifyMessage('Invalid API URL configuration.', 'error');
88
- updateState({ animationState: ANIMATION_STATES.ERROR });
89
- return false;
90
- }
91
- return true;
92
- }, [apiurl, notifyMessage, updateState]);
93
-
94
- const uploadFaceScan = useCallback(async (selfie) => {
95
- if (!validateApiUrl()) return;
96
-
97
- const currentData = dataRef.current;
98
- if (!currentData) {
99
- notifyMessage('Employee data not found.', 'error');
100
- updateState({ animationState: ANIMATION_STATES.ERROR });
101
- return;
102
- }
103
-
104
- let base64;
105
- try {
106
- base64 = await convertImageToBase64(selfie?.uri);
107
- } catch (err) {
108
- notifyMessage('Image conversion failed.', 'error');
109
- updateState({ animationState: ANIMATION_STATES.ERROR });
110
- return;
111
- }
112
-
113
- if (!base64) {
114
- notifyMessage('Failed to process image.', 'error');
115
- updateState({ animationState: ANIMATION_STATES.ERROR });
116
- return;
117
- }
118
-
119
- updateState({
120
- isLoading: true,
121
- animationState: ANIMATION_STATES.PROCESSING,
122
- });
123
-
124
- try {
125
- const body = { image: base64 };
126
- const header = { faceid: currentData };
127
- const buttonapi = `${apiurl}python/recognize`;
128
-
129
- console.log("buttonapi--------------------", buttonapi);
130
55
 
131
- const response = await networkServiceCall("POST", buttonapi, header, body);
132
-
133
- if (response?.httpstatus === 200) {
134
- notifyMessage('Identity verified successfully!', 'success');
135
- updateState({
136
- employeeData: response.data?.data || null,
137
- animationState: ANIMATION_STATES.SUCCESS,
56
+ const dataRef = useRef(data);
57
+ const mountedRef = useRef(true);
58
+ const responseRef = useRef(null);
59
+ const processedRef = useRef(false);
60
+ const lastDataRef = useRef(null);
61
+
62
+ const safeCallback = useSafeCallback(callback, notifyMessage);
63
+
64
+ /** Cleanup on unmount */
65
+ useEffect(() => {
66
+ return () => {
67
+ mountedRef.current = false;
68
+ };
69
+ }, []);
70
+
71
+ useEffect(() => {
72
+ dataRef.current = data;
73
+ }, [data]);
74
+
75
+ const updateState = useCallback((newState) => {
76
+ if (mountedRef.current) {
77
+ setState((prev) => {
78
+ const merged = { ...prev, ...newState };
79
+ return prev !== merged ? merged : prev;
138
80
  });
139
-
140
- if (qrscan) {
141
- setTimeout(() => startQRCodeScan(), 1500);
142
- } else {
143
- setTimeout(() => {
144
- try {
145
- callback?.(dataRef.current);
146
- } catch (err) {
147
- console.error("Callback execution failed:", err);
148
- notifyMessage('Unexpected error after verification.', 'error');
149
- }
150
- updateState({ modalVisible: false });
151
- resetState();
152
- }, 1500);
153
- }
154
- } else {
155
- notifyMessage(
156
- response?.data?.error?.message || 'Face not recognized. Please try again.',
157
- 'error'
158
- );
159
- updateState({ animationState: ANIMATION_STATES.ERROR });
160
81
  }
161
- } catch (error) {
162
- console.error("Face recognition API error:", error);
163
- notifyMessage('Connection error. Please check your network.', 'error');
164
- updateState({ animationState: ANIMATION_STATES.ERROR });
165
- } finally {
166
- updateState({ isLoading: false });
167
- }
168
- }, [convertImageToBase64, notifyMessage, qrscan, resetState, updateState, callback, validateApiUrl]);
169
-
170
- const handleStartFaceScan = useCallback(() => {
171
- updateState({
172
- currentStep: 'Identity Verification',
173
- animationState: ANIMATION_STATES.FACE_SCAN,
174
- });
175
- navigation.navigate('CCaptureImageWithoutEdit', {
176
- facedetection: true,
177
- cameratype: 'front',
178
- onSelect: uploadFaceScan,
179
- });
180
- }, [navigation, updateState, uploadFaceScan]);
181
-
182
- const startQRCodeScan = useCallback(() => {
183
- updateState({
184
- currentStep: 'Location Verification',
185
- animationState: ANIMATION_STATES.QR_SCAN,
186
- });
187
- navigation.navigate('CCaptureImageWithoutEdit', {
188
- hidebuttons: true,
189
- cameratype: 'back',
190
- cameramoduletype: 2,
191
- onSelect: handleQRScanned,
192
- });
193
- }, [navigation, updateState]);
194
-
195
- const handleQRScanned = useCallback(async (qrCodeData) => {
196
- if (!validateApiUrl()) return;
197
-
198
- updateState({
199
- animationState: ANIMATION_STATES.PROCESSING,
200
- isLoading: true,
201
- });
82
+ }, []);
83
+
84
+ const resetState = useCallback(() => {
85
+ console.log("🔄 Resetting biometric modal...");
86
+ setState({
87
+ isLoading: false,
88
+ currentStep: "Start",
89
+ employeeData: null,
90
+ animationState: ANIMATION_STATES.FACE_SCAN,
91
+ });
92
+ setModalVisible(false);
93
+ processedRef.current = false;
94
+ resetCountdown();
95
+ }, [resetCountdown]);
96
+
97
+ const handleProcessError = useCallback(
98
+ (message, errorObj = null) => {
99
+ if (errorObj) console.error(message, errorObj);
100
+ notifyMessage(message, "error");
101
+ updateState({
102
+ animationState: ANIMATION_STATES.ERROR,
103
+ isLoading: false,
104
+ });
105
+ setTimeout(resetState, 1200);
106
+ },
107
+ [notifyMessage, resetState, updateState]
108
+ );
202
109
 
203
- try {
204
- const hasPermission = await requestLocationPermission();
205
- if (!hasPermission) {
206
- notifyMessage('Location permission not granted.', 'error');
207
- updateState({ animationState: ANIMATION_STATES.ERROR });
208
- return;
209
- }
110
+ const handleCountdownFinish = useCallback(() => {
111
+ handleProcessError("Time is up! Please try again.");
112
+ resetState();
113
+ if (navigation.canGoBack()) navigation.goBack();
114
+ }, [handleProcessError, navigation, resetState]);
210
115
 
211
- const qrString = typeof qrCodeData === 'object' ? qrCodeData?.data : qrCodeData;
212
- if (!qrString || typeof qrString !== 'string') {
213
- notifyMessage('Invalid QR code. Please try again.', 'error');
214
- updateState({ animationState: ANIMATION_STATES.ERROR });
215
- return;
116
+ const validateApiUrl = useCallback(() => {
117
+ if (!apiurl || typeof apiurl !== "string") {
118
+ handleProcessError("Invalid API URL configuration.");
119
+ return false;
216
120
  }
121
+ return true;
122
+ }, [apiurl, handleProcessError]);
123
+
124
+ const uploadFaceScan = useCallback(
125
+ async (selfie) => {
126
+ if (!validateApiUrl()) return;
127
+ const currentData = dataRef.current;
128
+ if (!currentData) {
129
+ handleProcessError("Employee data not found.");
130
+ return;
131
+ }
217
132
 
218
- const location = await getCurrentLocation();
219
- const [latStr, lngStr] = qrString.split(',');
220
- const lat = parseFloat(latStr);
221
- const lng = parseFloat(lngStr);
133
+ updateState({
134
+ isLoading: true,
135
+ animationState: ANIMATION_STATES.PROCESSING,
136
+ });
222
137
 
223
- const validCoords = !isNaN(lat) && !isNaN(lng);
224
- const validDev = !isNaN(location?.latitude) && !isNaN(location?.longitude);
138
+ InteractionManager.runAfterInteractions(async () => {
139
+ let base64;
140
+ try {
141
+ base64 = await convertImageToBase64(selfie?.uri);
142
+ } catch (err) {
143
+ handleProcessError("Image conversion failed.", err);
144
+ return;
145
+ }
225
146
 
226
- if (validCoords && validDev) {
227
- const distance = getDistanceInMeters(lat, lng, location.latitude, location.longitude);
147
+ if (!base64) {
148
+ handleProcessError("Failed to process image.");
149
+ return;
150
+ }
228
151
 
229
- if (distance <= MAX_DISTANCE_METERS) {
230
152
  try {
231
- callback?.(dataRef.current);
232
- } catch (err) {
233
- console.error("Callback execution failed:", err);
234
- notifyMessage('Unexpected error during location verification.', 'error');
153
+ const body = { image: base64 };
154
+ const header = { faceid: currentData };
155
+ const buttonapi = `${apiurl}python/recognize`;
156
+ console.log("buttonapi", buttonapi);
157
+ const response = await networkServiceCall(
158
+ "POST",
159
+ buttonapi,
160
+ header,
161
+ body
162
+ );
163
+
164
+ if (response?.httpstatus === 200) {
165
+ responseRef.current = response;
166
+ updateState({
167
+ employeeData: response.data?.data || null,
168
+ animationState: ANIMATION_STATES.SUCCESS,
169
+ isLoading: false,
170
+ });
171
+ notifyMessage("Identity verified successfully!", "success");
172
+
173
+ if (qrscan) {
174
+ setTimeout(() => startQRCodeScan(), 1200);
175
+ } else {
176
+ safeCallback(responseRef.current);
177
+ setTimeout(() => resetState(), 1200);
178
+ }
179
+ } else {
180
+ handleProcessError(
181
+ response?.data?.error?.message ||
182
+ "Face not recognized. Please try again."
183
+ );
184
+ }
185
+ } catch (error) {
186
+ handleProcessError(
187
+ "Connection error. Please check your network.",
188
+ error
189
+ );
235
190
  }
191
+ });
192
+ },
193
+ [
194
+ convertImageToBase64,
195
+ notifyMessage,
196
+ qrscan,
197
+ resetState,
198
+ updateState,
199
+ validateApiUrl,
200
+ safeCallback,
201
+ ]
202
+ );
203
+
204
+ const handleStartFaceScan = useCallback(() => {
205
+ updateState({
206
+ currentStep: "Identity Verification",
207
+ animationState: ANIMATION_STATES.FACE_SCAN,
208
+ });
209
+ navigation.navigate("CCaptureImageWithoutEdit", {
210
+ facedetection: true,
211
+ cameratype: "front",
212
+ onSelect: uploadFaceScan,
213
+ });
214
+ }, [navigation, updateState, uploadFaceScan]);
215
+
216
+ const startQRCodeScan = useCallback(() => {
217
+ updateState({
218
+ currentStep: "Location Verification",
219
+ animationState: ANIMATION_STATES.QR_SCAN,
220
+ });
221
+ navigation.navigate("CCaptureImageWithoutEdit", {
222
+ hidebuttons: true,
223
+ cameratype: "back",
224
+ cameramoduletype: 2,
225
+ onSelect: handleQRScanned,
226
+ });
227
+ }, [navigation, updateState]);
228
+
229
+ const handleQRScanned = useCallback(
230
+ async (qrCodeData) => {
231
+ if (!validateApiUrl()) return;
236
232
 
237
- notifyMessage('Location verified successfully!', 'success');
238
- updateState({ animationState: ANIMATION_STATES.SUCCESS });
239
-
240
- setTimeout(() => {
241
- updateState({ modalVisible: false });
242
- resetState();
243
- }, 1500);
244
- } else {
245
- notifyMessage(`Location mismatch (${distance.toFixed(0)}m away).`, 'error');
246
- updateState({ animationState: ANIMATION_STATES.ERROR });
247
- resetState();
248
- }
249
- } else {
250
- notifyMessage('Invalid coordinates in QR code.', 'error');
251
- updateState({ animationState: ANIMATION_STATES.ERROR });
252
- resetState();
253
- }
254
- } catch (error) {
255
- console.error("QR scan handling failed:", error);
256
- notifyMessage('Unable to verify location. Please try again.', 'error');
257
- updateState({ animationState: ANIMATION_STATES.ERROR });
258
- resetState();
259
- } finally {
260
- updateState({ isLoading: false });
261
- }
262
- }, [callback, getCurrentLocation, notifyMessage, requestLocationPermission, resetState, updateState, validateApiUrl]);
263
-
264
- const startProcess = useCallback(() => {
265
- startCountdown(COUNTDOWN_DURATION, handleCountdownFinish);
266
- handleStartFaceScan();
267
- }, [handleCountdownFinish, handleStartFaceScan, startCountdown]);
268
-
269
- useEffect(() => {
270
- if (data) {
271
- updateState({ modalVisible: true });
272
- startProcess();
273
- }
274
- }, [data, startProcess, updateState]);
275
-
276
- return (
277
- <Modal
278
- visible={state.modalVisible}
279
- animationType="slide"
280
- transparent
281
- onRequestClose={() => {
282
- updateState({ modalVisible: false });
283
- resetState();
284
- }}
285
- statusBarTranslucent={true}
286
- >
287
- <View style={styles.modalBg}>
288
- <TouchableOpacity
289
- style={styles.close}
290
- onPress={() => {
291
- updateState({ modalVisible: false });
292
- resetState();
293
- }}
294
- accessibilityLabel="Close modal"
295
- hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
296
- >
297
- <Icon name="close" size={24} color={COLORS.light} />
298
- </TouchableOpacity>
299
-
300
- <Text style={styles.title}>Biometric Verification</Text>
301
- <Text style={styles.subTitle}>{state.currentStep}</Text>
302
-
303
- <StateIndicator state={state.animationState} size={120} />
304
-
305
- {state.employeeData && (
306
- <EmployeeCard
307
- employeeData={state.employeeData}
308
- apiurl={apiurl}
309
- />
310
- )}
233
+ updateState({
234
+ animationState: ANIMATION_STATES.PROCESSING,
235
+ isLoading: true,
236
+ });
311
237
 
312
- <Notification
313
- notification={notification}
314
- fadeAnim={fadeAnim}
315
- slideAnim={slideAnim}
316
- />
238
+ try {
239
+ const hasPermission = await requestLocationPermission();
240
+ if (!hasPermission) {
241
+ handleProcessError("Location permission not granted.");
242
+ return;
243
+ }
317
244
 
318
- <CountdownTimer
319
- duration={COUNTDOWN_DURATION}
320
- currentTime={countdown}
321
- />
245
+ const qrString =
246
+ typeof qrCodeData === "object" ? qrCodeData?.data : qrCodeData;
247
+ if (!qrString || typeof qrString !== "string") {
248
+ handleProcessError("Invalid QR code. Please try again.");
249
+ return;
250
+ }
322
251
 
323
- {state.isLoading && getLoaderGif(state.animationState, state.currentStep, apiurl) && (
324
- <Loader source={getLoaderGif(state.animationState, state.currentStep, apiurl)} />
252
+ const location = await getCurrentLocation();
253
+ const [latStr, lngStr] = qrString.split(",");
254
+ const lat = parseFloat(latStr);
255
+ const lng = parseFloat(lngStr);
256
+
257
+ const validCoords = !isNaN(lat) && !isNaN(lng);
258
+ const validDev =
259
+ !isNaN(location?.latitude) && !isNaN(location?.longitude);
260
+
261
+ if (validCoords && validDev) {
262
+ const distance = getDistanceInMeters(
263
+ lat,
264
+ lng,
265
+ location.latitude,
266
+ location.longitude
267
+ );
268
+
269
+ if (distance <= MAX_DISTANCE_METERS) {
270
+ safeCallback(responseRef.current);
271
+ notifyMessage("Location verified successfully!", "success");
272
+ updateState({
273
+ animationState: ANIMATION_STATES.SUCCESS,
274
+ isLoading: false,
275
+ });
276
+ setTimeout(() => resetState(), 1200);
277
+ } else {
278
+ handleProcessError(
279
+ `Location mismatch (${distance.toFixed(0)}m away).`
280
+ );
281
+ }
282
+ } else {
283
+ handleProcessError("Invalid coordinates in QR code.");
284
+ }
285
+ } catch (error) {
286
+ handleProcessError(
287
+ "Unable to verify location. Please try again.",
288
+ error
289
+ );
290
+ }
291
+ },
292
+ [
293
+ getCurrentLocation,
294
+ notifyMessage,
295
+ requestLocationPermission,
296
+ resetState,
297
+ updateState,
298
+ validateApiUrl,
299
+ safeCallback,
300
+ ]
301
+ );
302
+
303
+ const startProcess = useCallback(() => {
304
+ startCountdown(COUNTDOWN_DURATION, handleCountdownFinish);
305
+ handleStartFaceScan();
306
+ }, [handleCountdownFinish, handleStartFaceScan, startCountdown]);
307
+
308
+ useEffect(() => {
309
+ if (data && data !== lastDataRef.current) {
310
+ console.log("📥 New donor data received:", data);
311
+ lastDataRef.current = data;
312
+ setModalVisible(true);
313
+ startProcess();
314
+ }
315
+ }, [data, startProcess]);
316
+
317
+ const loaderSource = useMemo(
318
+ () =>
319
+ state.isLoading &&
320
+ getLoaderGif(state.animationState, state.currentStep, apiurl),
321
+ [state.isLoading, state.animationState, state.currentStep, apiurl]
322
+ );
323
+
324
+ return (
325
+ <>
326
+ {modalVisible && (
327
+ <Modal
328
+ visible={modalVisible}
329
+ animationType="slide"
330
+ transparent
331
+ onRequestClose={resetState}
332
+ statusBarTranslucent
333
+ >
334
+ <View style={styles.modalBg}>
335
+ <TouchableOpacity
336
+ style={styles.close}
337
+ onPress={resetState}
338
+ accessibilityLabel="Close modal"
339
+ hitSlop={{ top: 20, bottom: 20, left: 20, right: 20 }}
340
+ >
341
+ <Icon name="close" size={24} color={COLORS.light} />
342
+ </TouchableOpacity>
343
+
344
+ <Text style={styles.title}>Biometric Verification</Text>
345
+ <Text style={styles.subTitle}>{state.currentStep}</Text>
346
+
347
+ <StateIndicator state={state.animationState} size={120} />
348
+
349
+ {state.employeeData && (
350
+ <EmployeeCard
351
+ employeeData={state.employeeData}
352
+ apiurl={apiurl}
353
+ />
354
+ )}
355
+
356
+ <Notification
357
+ notification={notification}
358
+ fadeAnim={fadeAnim}
359
+ slideAnim={slideAnim}
360
+ />
361
+
362
+ <CountdownTimer
363
+ duration={COUNTDOWN_DURATION}
364
+ currentTime={countdown}
365
+ />
366
+
367
+ {loaderSource && <Loader source={loaderSource} />}
368
+ </View>
369
+ </Modal>
325
370
  )}
326
- </View>
327
- </Modal>
328
- );
329
- });
371
+ </>
372
+ );
373
+ }
374
+ );
330
375
 
331
376
  export default BiometricVerificationModal;