astra-sdk-web 1.1.6 → 1.1.8
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/dist/astra-sdk.cjs.js +187 -66
- package/dist/astra-sdk.cjs.js.map +1 -1
- package/dist/astra-sdk.css +72 -0
- package/dist/astra-sdk.css.map +1 -1
- package/dist/astra-sdk.es.js +188 -67
- package/dist/astra-sdk.es.js.map +1 -1
- package/dist/components.cjs.js +187 -66
- package/dist/components.cjs.js.map +1 -1
- package/dist/components.css +72 -0
- package/dist/components.css.map +1 -1
- package/dist/components.es.js +188 -67
- package/dist/components.es.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Toast.tsx +82 -0
- package/src/features/faceScan/hooks/useFaceScan.ts +24 -0
- package/src/pages/FaceScanModal.tsx +35 -11
- package/src/services/faceMeshService.ts +49 -15
- package/src/services/kycApiService.ts +5 -1
package/dist/astra-sdk.es.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { createContext, useState, useEffect, useRef, useContext, useCallback } from 'react';
|
|
2
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
3
|
import { useNavigate } from 'react-router-dom';
|
|
4
4
|
import { FACEMESH_TESSELATION, FACEMESH_FACE_OVAL, FACEMESH_LEFT_EYE, FACEMESH_RIGHT_EYE, FACEMESH_LIPS, FaceMesh } from '@mediapipe/face_mesh';
|
|
5
5
|
import { drawConnectors, drawLandmarks } from '@mediapipe/drawing_utils';
|
|
@@ -281,7 +281,10 @@ var KycApiService = class {
|
|
|
281
281
|
if (!response.ok) {
|
|
282
282
|
const errorData = await response.json().catch(() => ({}));
|
|
283
283
|
const message = errorData?.message || `Face upload failed with status ${response.status}`;
|
|
284
|
-
|
|
284
|
+
const error = new Error(message);
|
|
285
|
+
error.statusCode = response.status;
|
|
286
|
+
error.errorData = errorData;
|
|
287
|
+
throw error;
|
|
285
288
|
}
|
|
286
289
|
const data = await response.json();
|
|
287
290
|
return data;
|
|
@@ -943,6 +946,8 @@ var FaceMeshService = class {
|
|
|
943
946
|
}
|
|
944
947
|
this.processLiveness(faceOnCanvas, w, h);
|
|
945
948
|
} else {
|
|
949
|
+
this.livenessStateRef.current.currentYaw = null;
|
|
950
|
+
this.livenessStateRef.current.currentAbsYaw = null;
|
|
946
951
|
const vid = this.videoRef.current;
|
|
947
952
|
if (vid) {
|
|
948
953
|
const vidW = Math.max(1, vid?.videoWidth || displayW);
|
|
@@ -986,6 +991,8 @@ var FaceMeshService = class {
|
|
|
986
991
|
const midX = (leftEyeOuter.x + rightEyeOuter.x) / 2;
|
|
987
992
|
const yaw = (nT.x - midX) / Math.max(1e-6, faceWidth);
|
|
988
993
|
const absYaw = Math.abs(yaw);
|
|
994
|
+
this.livenessStateRef.current.currentYaw = yaw;
|
|
995
|
+
this.livenessStateRef.current.currentAbsYaw = absYaw;
|
|
989
996
|
const xs = faceOnCanvas.map((p) => p.x), ys = faceOnCanvas.map((p) => p.y);
|
|
990
997
|
const minX = Math.min(...xs) * w, maxX = Math.max(...xs) * w;
|
|
991
998
|
const minY = Math.min(...ys) * h, maxY = Math.max(...ys) * h;
|
|
@@ -1012,11 +1019,13 @@ var FaceMeshService = class {
|
|
|
1012
1019
|
} else if (absYaw < centerThreshold) {
|
|
1013
1020
|
state.centerHold += 1;
|
|
1014
1021
|
if (state.centerHold >= holdFramesCenter) {
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
this.callbacks.onLivenessUpdate
|
|
1022
|
+
if (!state.livenessCompleted) {
|
|
1023
|
+
const newStage = "LEFT";
|
|
1024
|
+
state.stage = newStage;
|
|
1025
|
+
state.centerHold = 0;
|
|
1026
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
1027
|
+
this.callbacks.onLivenessUpdate(newStage, "Turn your face LEFT");
|
|
1028
|
+
}
|
|
1020
1029
|
}
|
|
1021
1030
|
}
|
|
1022
1031
|
} else {
|
|
@@ -1055,16 +1064,12 @@ var FaceMeshService = class {
|
|
|
1055
1064
|
state.rightHold += 1;
|
|
1056
1065
|
if (state.rightHold >= holdFramesTurn) {
|
|
1057
1066
|
state.rightHold = 0;
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
}
|
|
1065
|
-
if (this.callbacks.onCaptureTrigger) {
|
|
1066
|
-
this.callbacks.onCaptureTrigger();
|
|
1067
|
-
}
|
|
1067
|
+
state.livenessCompleted = true;
|
|
1068
|
+
const newStage = "DONE";
|
|
1069
|
+
state.stage = newStage;
|
|
1070
|
+
state.centerHold = 0;
|
|
1071
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
1072
|
+
this.callbacks.onLivenessUpdate(newStage, "Great! Now look straight at the camera");
|
|
1068
1073
|
}
|
|
1069
1074
|
}
|
|
1070
1075
|
} else {
|
|
@@ -1073,6 +1078,28 @@ var FaceMeshService = class {
|
|
|
1073
1078
|
this.callbacks.onLivenessUpdate(state.stage, yaw < -0.08 ? "You're facing left. Turn RIGHT" : "Turn a bit more RIGHT");
|
|
1074
1079
|
}
|
|
1075
1080
|
}
|
|
1081
|
+
} else if (state.stage === "DONE") {
|
|
1082
|
+
if (absYaw < centerThreshold && insideGuide) {
|
|
1083
|
+
state.centerHold += 1;
|
|
1084
|
+
if (state.centerHold >= holdFramesCenter && !state.snapTriggered) {
|
|
1085
|
+
state.snapTriggered = true;
|
|
1086
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
1087
|
+
this.callbacks.onLivenessUpdate(state.stage, "Capturing...");
|
|
1088
|
+
}
|
|
1089
|
+
if (this.callbacks.onCaptureTrigger) {
|
|
1090
|
+
this.callbacks.onCaptureTrigger();
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
} else {
|
|
1094
|
+
state.centerHold = 0;
|
|
1095
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
1096
|
+
if (!insideGuide) {
|
|
1097
|
+
this.callbacks.onLivenessUpdate(state.stage, "Center your face inside the circle");
|
|
1098
|
+
} else {
|
|
1099
|
+
this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1076
1103
|
}
|
|
1077
1104
|
}
|
|
1078
1105
|
}
|
|
@@ -1208,7 +1235,10 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1208
1235
|
snapTriggered: false,
|
|
1209
1236
|
lastResultsAt: 0,
|
|
1210
1237
|
stage: "CENTER",
|
|
1211
|
-
livenessReady: false
|
|
1238
|
+
livenessReady: false,
|
|
1239
|
+
currentYaw: null,
|
|
1240
|
+
currentAbsYaw: null,
|
|
1241
|
+
livenessCompleted: false
|
|
1212
1242
|
});
|
|
1213
1243
|
useEffect(() => {
|
|
1214
1244
|
livenessStateRef.current.centerHold = refs.centerHold.current;
|
|
@@ -1229,6 +1259,22 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1229
1259
|
}, []);
|
|
1230
1260
|
const handleFaceCapture = useCallback(async () => {
|
|
1231
1261
|
if (!videoRef.current) return;
|
|
1262
|
+
const centerThreshold = 0.05;
|
|
1263
|
+
const currentAbsYaw = livenessStateRef.current.currentAbsYaw;
|
|
1264
|
+
if (currentAbsYaw === null || currentAbsYaw === void 0) {
|
|
1265
|
+
setState((prev) => ({
|
|
1266
|
+
...prev,
|
|
1267
|
+
livenessInstruction: "Please position your face in front of the camera"
|
|
1268
|
+
}));
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
if (currentAbsYaw >= centerThreshold) {
|
|
1272
|
+
setState((prev) => ({
|
|
1273
|
+
...prev,
|
|
1274
|
+
livenessInstruction: "Please look straight at the camera before capturing"
|
|
1275
|
+
}));
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1232
1278
|
setState((prev) => ({ ...prev, loading: true }));
|
|
1233
1279
|
try {
|
|
1234
1280
|
const video = videoRef.current;
|
|
@@ -1382,18 +1428,82 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1382
1428
|
handleFaceCapture
|
|
1383
1429
|
};
|
|
1384
1430
|
}
|
|
1431
|
+
function Toast({ message, type = "info", duration = 5e3, onClose }) {
|
|
1432
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
1433
|
+
useEffect(() => {
|
|
1434
|
+
const timer = setTimeout(() => {
|
|
1435
|
+
setIsVisible(false);
|
|
1436
|
+
setTimeout(() => {
|
|
1437
|
+
if (onClose) onClose();
|
|
1438
|
+
}, 300);
|
|
1439
|
+
}, duration);
|
|
1440
|
+
return () => clearTimeout(timer);
|
|
1441
|
+
}, [duration, onClose]);
|
|
1442
|
+
const bgColor = {
|
|
1443
|
+
success: "bg-green-600",
|
|
1444
|
+
error: "bg-red-600",
|
|
1445
|
+
info: "bg-blue-600",
|
|
1446
|
+
warning: "bg-yellow-600"
|
|
1447
|
+
}[type];
|
|
1448
|
+
return /* @__PURE__ */ jsx(
|
|
1449
|
+
"div",
|
|
1450
|
+
{
|
|
1451
|
+
className: `fixed top-4 right-4 z-[10000] transition-all duration-300 ${isVisible ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2"}`,
|
|
1452
|
+
children: /* @__PURE__ */ jsxs(
|
|
1453
|
+
"div",
|
|
1454
|
+
{
|
|
1455
|
+
className: `${bgColor} text-white px-6 py-4 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px] max-w-[500px]`,
|
|
1456
|
+
children: [
|
|
1457
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx("p", { className: "m-0 text-sm font-medium", children: message }) }),
|
|
1458
|
+
/* @__PURE__ */ jsx(
|
|
1459
|
+
"button",
|
|
1460
|
+
{
|
|
1461
|
+
onClick: () => {
|
|
1462
|
+
setIsVisible(false);
|
|
1463
|
+
setTimeout(() => {
|
|
1464
|
+
if (onClose) onClose();
|
|
1465
|
+
}, 300);
|
|
1466
|
+
},
|
|
1467
|
+
className: "text-white hover:text-gray-200 transition-colors",
|
|
1468
|
+
"aria-label": "Close",
|
|
1469
|
+
children: /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1470
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1471
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1472
|
+
] })
|
|
1473
|
+
}
|
|
1474
|
+
)
|
|
1475
|
+
]
|
|
1476
|
+
}
|
|
1477
|
+
)
|
|
1478
|
+
}
|
|
1479
|
+
);
|
|
1480
|
+
}
|
|
1385
1481
|
function FaceScanModal({ onComplete }) {
|
|
1386
1482
|
const faceCanvasRef = useRef(null);
|
|
1387
1483
|
const navigate = useNavigate();
|
|
1388
1484
|
const { apiService } = useKycContext();
|
|
1389
1485
|
const [sessionError, setSessionError] = useState(null);
|
|
1486
|
+
const [toast, setToast] = useState(null);
|
|
1390
1487
|
const { videoRef, cameraReady, stopCamera } = useCamera();
|
|
1391
1488
|
const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
|
|
1392
1489
|
onFaceUpload: async (blob) => {
|
|
1393
1490
|
if (!apiService) {
|
|
1394
1491
|
throw new Error("API service not initialized");
|
|
1395
1492
|
}
|
|
1396
|
-
|
|
1493
|
+
try {
|
|
1494
|
+
await apiService.uploadFaceScan(blob);
|
|
1495
|
+
} catch (error) {
|
|
1496
|
+
const errorMessage = error?.message || "";
|
|
1497
|
+
const errorData = error?.errorData || {};
|
|
1498
|
+
if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
|
|
1499
|
+
setToast({
|
|
1500
|
+
message: "Face already registered",
|
|
1501
|
+
type: "warning"
|
|
1502
|
+
});
|
|
1503
|
+
return;
|
|
1504
|
+
}
|
|
1505
|
+
throw error;
|
|
1506
|
+
}
|
|
1397
1507
|
},
|
|
1398
1508
|
onFaceCaptureComplete: (imageData) => {
|
|
1399
1509
|
if (onComplete) {
|
|
@@ -1488,61 +1598,72 @@ function FaceScanModal({ onComplete }) {
|
|
|
1488
1598
|
/* @__PURE__ */ jsx("p", { className: "text-[#9ca3af] text-sm", children: "Redirecting to QR code page..." })
|
|
1489
1599
|
] }) }) });
|
|
1490
1600
|
}
|
|
1491
|
-
return /* @__PURE__ */
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1601
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1602
|
+
toast && /* @__PURE__ */ jsx(
|
|
1603
|
+
Toast,
|
|
1604
|
+
{
|
|
1605
|
+
message: toast.message,
|
|
1606
|
+
type: toast.type,
|
|
1607
|
+
onClose: () => setToast(null),
|
|
1608
|
+
duration: 6e3
|
|
1609
|
+
}
|
|
1610
|
+
),
|
|
1611
|
+
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black p-5 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar", children: /* @__PURE__ */ jsxs("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl mt-48", children: [
|
|
1612
|
+
/* @__PURE__ */ jsx("div", { className: "relative mb-4", children: /* @__PURE__ */ jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-white text-center", children: "Capture Face" }) }),
|
|
1613
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
1614
|
+
!state.modelLoading && state.modelLoaded && !state.livenessFailed && /* @__PURE__ */ jsx("div", { className: "bg-[#0a2315] text-[#34d399] py-3.5 px-4 rounded-xl text-sm border border-[#155e3b] text-left", children: "Face detection model loaded." }),
|
|
1615
|
+
state.modelLoading && !state.livenessFailed && /* @__PURE__ */ jsx("div", { className: "bg-[#1f2937] text-[#e5e7eb] py-3.5 px-4 rounded-xl text-sm border border-[#374151] text-left", children: "Loading face detection model..." }),
|
|
1616
|
+
/* @__PURE__ */ jsxs("div", { className: "relative w-full aspect-square rounded-full overflow-hidden bg-black", children: [
|
|
1617
|
+
/* @__PURE__ */ jsx(
|
|
1618
|
+
"video",
|
|
1619
|
+
{
|
|
1620
|
+
ref: videoRef,
|
|
1621
|
+
playsInline: true,
|
|
1622
|
+
muted: true,
|
|
1623
|
+
className: "w-full h-full block bg-black object-cover -scale-x-100 origin-center"
|
|
1624
|
+
}
|
|
1625
|
+
),
|
|
1626
|
+
/* @__PURE__ */ jsx(
|
|
1627
|
+
"canvas",
|
|
1628
|
+
{
|
|
1629
|
+
ref: faceCanvasRef,
|
|
1630
|
+
className: "absolute top-0 left-0 w-full h-full pointer-events-none z-[2]"
|
|
1631
|
+
}
|
|
1632
|
+
),
|
|
1633
|
+
/* @__PURE__ */ jsx("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", className: "absolute inset-0 pointer-events-none z-[3]", children: /* @__PURE__ */ jsx("circle", { cx: "50", cy: "50", r: "44", fill: "none", stroke: "#22c55e", strokeWidth: "2", strokeDasharray: "1 3" }) })
|
|
1634
|
+
] }),
|
|
1635
|
+
!state.livenessFailed && /* @__PURE__ */ jsxs("div", { className: "bg-gradient-to-b from-[rgba(17,24,39,0.9)] to-[rgba(17,24,39,0.6)] text-[#e5e7eb] p-4 rounded-2xl text-base border border-[#30363d]", children: [
|
|
1636
|
+
/* @__PURE__ */ jsx("div", { className: "font-bold mb-2.5 text-[22px] text-white", children: "Liveness Check" }),
|
|
1637
|
+
/* @__PURE__ */ jsx("div", { className: "mb-2.5 text-base", children: state.livenessInstruction }),
|
|
1638
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2.5 text-lg", children: [
|
|
1639
|
+
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "CENTER" || state.livenessStage === "LEFT" || state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "1. Look Straight" }),
|
|
1640
|
+
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "2. Turn your face right" }),
|
|
1641
|
+
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1642
|
+
] })
|
|
1643
|
+
] }),
|
|
1497
1644
|
/* @__PURE__ */ jsx(
|
|
1498
|
-
"
|
|
1645
|
+
"button",
|
|
1499
1646
|
{
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
className:
|
|
1647
|
+
type: "button",
|
|
1648
|
+
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1649
|
+
onClick: handleFaceCapture,
|
|
1650
|
+
className: `py-3.5 px-4 rounded-xl text-base font-bold border-none transition-colors ${state.cameraReady && !state.loading && (state.livenessFailed || state.livenessStage === "DONE") ? "bg-[#22c55e] text-[#0b0f17] cursor-pointer hover:bg-[#16a34a]" : "bg-[#374151] text-[#e5e7eb] cursor-not-allowed"}`,
|
|
1651
|
+
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1504
1652
|
}
|
|
1505
1653
|
),
|
|
1506
1654
|
/* @__PURE__ */ jsx(
|
|
1507
|
-
"
|
|
1655
|
+
"button",
|
|
1508
1656
|
{
|
|
1509
|
-
|
|
1510
|
-
|
|
1657
|
+
type: "button",
|
|
1658
|
+
onClick: handleRetry,
|
|
1659
|
+
disabled: state.loading,
|
|
1660
|
+
className: `py-3 px-4 rounded-[10px] text-[15px] font-semibold border-none w-full transition-colors ${state.loading ? "bg-[#374151] text-[#e5e7eb] cursor-not-allowed opacity-50" : "bg-[#374151] text-[#e5e7eb] cursor-pointer hover:bg-[#4b5563]"}`,
|
|
1661
|
+
children: "Restart"
|
|
1511
1662
|
}
|
|
1512
|
-
)
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
/* @__PURE__ */ jsx("div", { className: "font-bold mb-2.5 text-[22px] text-white", children: "Liveness Check" }),
|
|
1517
|
-
/* @__PURE__ */ jsx("div", { className: "mb-2.5 text-base", children: state.livenessInstruction }),
|
|
1518
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-2.5 text-lg", children: [
|
|
1519
|
-
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "CENTER" || state.livenessStage === "LEFT" || state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "1. Look Straight" }),
|
|
1520
|
-
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "2. Turn your face right" }),
|
|
1521
|
-
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1522
|
-
] })
|
|
1523
|
-
] }),
|
|
1524
|
-
/* @__PURE__ */ jsx(
|
|
1525
|
-
"button",
|
|
1526
|
-
{
|
|
1527
|
-
type: "button",
|
|
1528
|
-
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1529
|
-
onClick: handleFaceCapture,
|
|
1530
|
-
className: `py-3.5 px-4 rounded-xl text-base font-bold border-none transition-colors ${state.cameraReady && !state.loading && (state.livenessFailed || state.livenessStage === "DONE") ? "bg-[#22c55e] text-[#0b0f17] cursor-pointer hover:bg-[#16a34a]" : "bg-[#374151] text-[#e5e7eb] cursor-not-allowed"}`,
|
|
1531
|
-
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1532
|
-
}
|
|
1533
|
-
),
|
|
1534
|
-
/* @__PURE__ */ jsx(
|
|
1535
|
-
"button",
|
|
1536
|
-
{
|
|
1537
|
-
type: "button",
|
|
1538
|
-
onClick: handleRetry,
|
|
1539
|
-
disabled: state.loading,
|
|
1540
|
-
className: `py-3 px-4 rounded-[10px] text-[15px] font-semibold border-none w-full transition-colors ${state.loading ? "bg-[#374151] text-[#e5e7eb] cursor-not-allowed opacity-50" : "bg-[#374151] text-[#e5e7eb] cursor-pointer hover:bg-[#4b5563]"}`,
|
|
1541
|
-
children: "Restart"
|
|
1542
|
-
}
|
|
1543
|
-
)
|
|
1544
|
-
] })
|
|
1545
|
-
] }) });
|
|
1663
|
+
)
|
|
1664
|
+
] })
|
|
1665
|
+
] }) })
|
|
1666
|
+
] });
|
|
1546
1667
|
}
|
|
1547
1668
|
var FaceScanModal_default = FaceScanModal;
|
|
1548
1669
|
function MobileRouteContent({ onClose, onComplete }) {
|