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.cjs.js
CHANGED
|
@@ -289,7 +289,10 @@ var KycApiService = class {
|
|
|
289
289
|
if (!response.ok) {
|
|
290
290
|
const errorData = await response.json().catch(() => ({}));
|
|
291
291
|
const message = errorData?.message || `Face upload failed with status ${response.status}`;
|
|
292
|
-
|
|
292
|
+
const error = new Error(message);
|
|
293
|
+
error.statusCode = response.status;
|
|
294
|
+
error.errorData = errorData;
|
|
295
|
+
throw error;
|
|
293
296
|
}
|
|
294
297
|
const data = await response.json();
|
|
295
298
|
return data;
|
|
@@ -951,6 +954,8 @@ var FaceMeshService = class {
|
|
|
951
954
|
}
|
|
952
955
|
this.processLiveness(faceOnCanvas, w, h);
|
|
953
956
|
} else {
|
|
957
|
+
this.livenessStateRef.current.currentYaw = null;
|
|
958
|
+
this.livenessStateRef.current.currentAbsYaw = null;
|
|
954
959
|
const vid = this.videoRef.current;
|
|
955
960
|
if (vid) {
|
|
956
961
|
const vidW = Math.max(1, vid?.videoWidth || displayW);
|
|
@@ -994,6 +999,8 @@ var FaceMeshService = class {
|
|
|
994
999
|
const midX = (leftEyeOuter.x + rightEyeOuter.x) / 2;
|
|
995
1000
|
const yaw = (nT.x - midX) / Math.max(1e-6, faceWidth);
|
|
996
1001
|
const absYaw = Math.abs(yaw);
|
|
1002
|
+
this.livenessStateRef.current.currentYaw = yaw;
|
|
1003
|
+
this.livenessStateRef.current.currentAbsYaw = absYaw;
|
|
997
1004
|
const xs = faceOnCanvas.map((p) => p.x), ys = faceOnCanvas.map((p) => p.y);
|
|
998
1005
|
const minX = Math.min(...xs) * w, maxX = Math.max(...xs) * w;
|
|
999
1006
|
const minY = Math.min(...ys) * h, maxY = Math.max(...ys) * h;
|
|
@@ -1020,11 +1027,13 @@ var FaceMeshService = class {
|
|
|
1020
1027
|
} else if (absYaw < centerThreshold) {
|
|
1021
1028
|
state.centerHold += 1;
|
|
1022
1029
|
if (state.centerHold >= holdFramesCenter) {
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
this.callbacks.onLivenessUpdate
|
|
1030
|
+
if (!state.livenessCompleted) {
|
|
1031
|
+
const newStage = "LEFT";
|
|
1032
|
+
state.stage = newStage;
|
|
1033
|
+
state.centerHold = 0;
|
|
1034
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
1035
|
+
this.callbacks.onLivenessUpdate(newStage, "Turn your face LEFT");
|
|
1036
|
+
}
|
|
1028
1037
|
}
|
|
1029
1038
|
}
|
|
1030
1039
|
} else {
|
|
@@ -1063,16 +1072,12 @@ var FaceMeshService = class {
|
|
|
1063
1072
|
state.rightHold += 1;
|
|
1064
1073
|
if (state.rightHold >= holdFramesTurn) {
|
|
1065
1074
|
state.rightHold = 0;
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
}
|
|
1073
|
-
if (this.callbacks.onCaptureTrigger) {
|
|
1074
|
-
this.callbacks.onCaptureTrigger();
|
|
1075
|
-
}
|
|
1075
|
+
state.livenessCompleted = true;
|
|
1076
|
+
const newStage = "DONE";
|
|
1077
|
+
state.stage = newStage;
|
|
1078
|
+
state.centerHold = 0;
|
|
1079
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
1080
|
+
this.callbacks.onLivenessUpdate(newStage, "Great! Now look straight at the camera");
|
|
1076
1081
|
}
|
|
1077
1082
|
}
|
|
1078
1083
|
} else {
|
|
@@ -1081,6 +1086,28 @@ var FaceMeshService = class {
|
|
|
1081
1086
|
this.callbacks.onLivenessUpdate(state.stage, yaw < -0.08 ? "You're facing left. Turn RIGHT" : "Turn a bit more RIGHT");
|
|
1082
1087
|
}
|
|
1083
1088
|
}
|
|
1089
|
+
} else if (state.stage === "DONE") {
|
|
1090
|
+
if (absYaw < centerThreshold && insideGuide) {
|
|
1091
|
+
state.centerHold += 1;
|
|
1092
|
+
if (state.centerHold >= holdFramesCenter && !state.snapTriggered) {
|
|
1093
|
+
state.snapTriggered = true;
|
|
1094
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
1095
|
+
this.callbacks.onLivenessUpdate(state.stage, "Capturing...");
|
|
1096
|
+
}
|
|
1097
|
+
if (this.callbacks.onCaptureTrigger) {
|
|
1098
|
+
this.callbacks.onCaptureTrigger();
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
} else {
|
|
1102
|
+
state.centerHold = 0;
|
|
1103
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
1104
|
+
if (!insideGuide) {
|
|
1105
|
+
this.callbacks.onLivenessUpdate(state.stage, "Center your face inside the circle");
|
|
1106
|
+
} else {
|
|
1107
|
+
this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1084
1111
|
}
|
|
1085
1112
|
}
|
|
1086
1113
|
}
|
|
@@ -1216,7 +1243,10 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1216
1243
|
snapTriggered: false,
|
|
1217
1244
|
lastResultsAt: 0,
|
|
1218
1245
|
stage: "CENTER",
|
|
1219
|
-
livenessReady: false
|
|
1246
|
+
livenessReady: false,
|
|
1247
|
+
currentYaw: null,
|
|
1248
|
+
currentAbsYaw: null,
|
|
1249
|
+
livenessCompleted: false
|
|
1220
1250
|
});
|
|
1221
1251
|
React.useEffect(() => {
|
|
1222
1252
|
livenessStateRef.current.centerHold = refs.centerHold.current;
|
|
@@ -1237,6 +1267,22 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1237
1267
|
}, []);
|
|
1238
1268
|
const handleFaceCapture = React.useCallback(async () => {
|
|
1239
1269
|
if (!videoRef.current) return;
|
|
1270
|
+
const centerThreshold = 0.05;
|
|
1271
|
+
const currentAbsYaw = livenessStateRef.current.currentAbsYaw;
|
|
1272
|
+
if (currentAbsYaw === null || currentAbsYaw === void 0) {
|
|
1273
|
+
setState((prev) => ({
|
|
1274
|
+
...prev,
|
|
1275
|
+
livenessInstruction: "Please position your face in front of the camera"
|
|
1276
|
+
}));
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1279
|
+
if (currentAbsYaw >= centerThreshold) {
|
|
1280
|
+
setState((prev) => ({
|
|
1281
|
+
...prev,
|
|
1282
|
+
livenessInstruction: "Please look straight at the camera before capturing"
|
|
1283
|
+
}));
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1240
1286
|
setState((prev) => ({ ...prev, loading: true }));
|
|
1241
1287
|
try {
|
|
1242
1288
|
const video = videoRef.current;
|
|
@@ -1390,18 +1436,82 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1390
1436
|
handleFaceCapture
|
|
1391
1437
|
};
|
|
1392
1438
|
}
|
|
1439
|
+
function Toast({ message, type = "info", duration = 5e3, onClose }) {
|
|
1440
|
+
const [isVisible, setIsVisible] = React.useState(true);
|
|
1441
|
+
React.useEffect(() => {
|
|
1442
|
+
const timer = setTimeout(() => {
|
|
1443
|
+
setIsVisible(false);
|
|
1444
|
+
setTimeout(() => {
|
|
1445
|
+
if (onClose) onClose();
|
|
1446
|
+
}, 300);
|
|
1447
|
+
}, duration);
|
|
1448
|
+
return () => clearTimeout(timer);
|
|
1449
|
+
}, [duration, onClose]);
|
|
1450
|
+
const bgColor = {
|
|
1451
|
+
success: "bg-green-600",
|
|
1452
|
+
error: "bg-red-600",
|
|
1453
|
+
info: "bg-blue-600",
|
|
1454
|
+
warning: "bg-yellow-600"
|
|
1455
|
+
}[type];
|
|
1456
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1457
|
+
"div",
|
|
1458
|
+
{
|
|
1459
|
+
className: `fixed top-4 right-4 z-[10000] transition-all duration-300 ${isVisible ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2"}`,
|
|
1460
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1461
|
+
"div",
|
|
1462
|
+
{
|
|
1463
|
+
className: `${bgColor} text-white px-6 py-4 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px] max-w-[500px]`,
|
|
1464
|
+
children: [
|
|
1465
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm font-medium", children: message }) }),
|
|
1466
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1467
|
+
"button",
|
|
1468
|
+
{
|
|
1469
|
+
onClick: () => {
|
|
1470
|
+
setIsVisible(false);
|
|
1471
|
+
setTimeout(() => {
|
|
1472
|
+
if (onClose) onClose();
|
|
1473
|
+
}, 300);
|
|
1474
|
+
},
|
|
1475
|
+
className: "text-white hover:text-gray-200 transition-colors",
|
|
1476
|
+
"aria-label": "Close",
|
|
1477
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1478
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1479
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1480
|
+
] })
|
|
1481
|
+
}
|
|
1482
|
+
)
|
|
1483
|
+
]
|
|
1484
|
+
}
|
|
1485
|
+
)
|
|
1486
|
+
}
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1393
1489
|
function FaceScanModal({ onComplete }) {
|
|
1394
1490
|
const faceCanvasRef = React.useRef(null);
|
|
1395
1491
|
const navigate = reactRouterDom.useNavigate();
|
|
1396
1492
|
const { apiService } = useKycContext();
|
|
1397
1493
|
const [sessionError, setSessionError] = React.useState(null);
|
|
1494
|
+
const [toast, setToast] = React.useState(null);
|
|
1398
1495
|
const { videoRef, cameraReady, stopCamera } = useCamera();
|
|
1399
1496
|
const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
|
|
1400
1497
|
onFaceUpload: async (blob) => {
|
|
1401
1498
|
if (!apiService) {
|
|
1402
1499
|
throw new Error("API service not initialized");
|
|
1403
1500
|
}
|
|
1404
|
-
|
|
1501
|
+
try {
|
|
1502
|
+
await apiService.uploadFaceScan(blob);
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
const errorMessage = error?.message || "";
|
|
1505
|
+
const errorData = error?.errorData || {};
|
|
1506
|
+
if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
|
|
1507
|
+
setToast({
|
|
1508
|
+
message: "Face already registered",
|
|
1509
|
+
type: "warning"
|
|
1510
|
+
});
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
throw error;
|
|
1514
|
+
}
|
|
1405
1515
|
},
|
|
1406
1516
|
onFaceCaptureComplete: (imageData) => {
|
|
1407
1517
|
if (onComplete) {
|
|
@@ -1496,61 +1606,72 @@ function FaceScanModal({ onComplete }) {
|
|
|
1496
1606
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm", children: "Redirecting to QR code page..." })
|
|
1497
1607
|
] }) }) });
|
|
1498
1608
|
}
|
|
1499
|
-
return /* @__PURE__ */ jsxRuntime.
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1609
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1610
|
+
toast && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1611
|
+
Toast,
|
|
1612
|
+
{
|
|
1613
|
+
message: toast.message,
|
|
1614
|
+
type: toast.type,
|
|
1615
|
+
onClose: () => setToast(null),
|
|
1616
|
+
duration: 6e3
|
|
1617
|
+
}
|
|
1618
|
+
),
|
|
1619
|
+
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsxs("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl mt-48", children: [
|
|
1620
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative mb-4", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-white text-center", children: "Capture Face" }) }),
|
|
1621
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4", children: [
|
|
1622
|
+
!state.modelLoading && state.modelLoaded && !state.livenessFailed && /* @__PURE__ */ jsxRuntime.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." }),
|
|
1623
|
+
state.modelLoading && !state.livenessFailed && /* @__PURE__ */ jsxRuntime.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..." }),
|
|
1624
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-square rounded-full overflow-hidden bg-black", children: [
|
|
1625
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1626
|
+
"video",
|
|
1627
|
+
{
|
|
1628
|
+
ref: videoRef,
|
|
1629
|
+
playsInline: true,
|
|
1630
|
+
muted: true,
|
|
1631
|
+
className: "w-full h-full block bg-black object-cover -scale-x-100 origin-center"
|
|
1632
|
+
}
|
|
1633
|
+
),
|
|
1634
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1635
|
+
"canvas",
|
|
1636
|
+
{
|
|
1637
|
+
ref: faceCanvasRef,
|
|
1638
|
+
className: "absolute top-0 left-0 w-full h-full pointer-events-none z-[2]"
|
|
1639
|
+
}
|
|
1640
|
+
),
|
|
1641
|
+
/* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 100 100", preserveAspectRatio: "none", className: "absolute inset-0 pointer-events-none z-[3]", children: /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "50", cy: "50", r: "44", fill: "none", stroke: "#22c55e", strokeWidth: "2", strokeDasharray: "1 3" }) })
|
|
1642
|
+
] }),
|
|
1643
|
+
!state.livenessFailed && /* @__PURE__ */ jsxRuntime.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: [
|
|
1644
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-bold mb-2.5 text-[22px] text-white", children: "Liveness Check" }),
|
|
1645
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2.5 text-base", children: state.livenessInstruction }),
|
|
1646
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2.5 text-lg", children: [
|
|
1647
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "CENTER" || state.livenessStage === "LEFT" || state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "1. Look Straight" }),
|
|
1648
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "2. Turn your face right" }),
|
|
1649
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1650
|
+
] })
|
|
1651
|
+
] }),
|
|
1505
1652
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1506
|
-
"
|
|
1653
|
+
"button",
|
|
1507
1654
|
{
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
className:
|
|
1655
|
+
type: "button",
|
|
1656
|
+
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1657
|
+
onClick: handleFaceCapture,
|
|
1658
|
+
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"}`,
|
|
1659
|
+
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1512
1660
|
}
|
|
1513
1661
|
),
|
|
1514
1662
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1515
|
-
"
|
|
1663
|
+
"button",
|
|
1516
1664
|
{
|
|
1517
|
-
|
|
1518
|
-
|
|
1665
|
+
type: "button",
|
|
1666
|
+
onClick: handleRetry,
|
|
1667
|
+
disabled: state.loading,
|
|
1668
|
+
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]"}`,
|
|
1669
|
+
children: "Restart"
|
|
1519
1670
|
}
|
|
1520
|
-
)
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-bold mb-2.5 text-[22px] text-white", children: "Liveness Check" }),
|
|
1525
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2.5 text-base", children: state.livenessInstruction }),
|
|
1526
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2.5 text-lg", children: [
|
|
1527
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "CENTER" || state.livenessStage === "LEFT" || state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "1. Look Straight" }),
|
|
1528
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "2. Turn your face right" }),
|
|
1529
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1530
|
-
] })
|
|
1531
|
-
] }),
|
|
1532
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1533
|
-
"button",
|
|
1534
|
-
{
|
|
1535
|
-
type: "button",
|
|
1536
|
-
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1537
|
-
onClick: handleFaceCapture,
|
|
1538
|
-
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"}`,
|
|
1539
|
-
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1540
|
-
}
|
|
1541
|
-
),
|
|
1542
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1543
|
-
"button",
|
|
1544
|
-
{
|
|
1545
|
-
type: "button",
|
|
1546
|
-
onClick: handleRetry,
|
|
1547
|
-
disabled: state.loading,
|
|
1548
|
-
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]"}`,
|
|
1549
|
-
children: "Restart"
|
|
1550
|
-
}
|
|
1551
|
-
)
|
|
1552
|
-
] })
|
|
1553
|
-
] }) });
|
|
1671
|
+
)
|
|
1672
|
+
] })
|
|
1673
|
+
] }) })
|
|
1674
|
+
] });
|
|
1554
1675
|
}
|
|
1555
1676
|
var FaceScanModal_default = FaceScanModal;
|
|
1556
1677
|
function MobileRouteContent({ onClose, onComplete }) {
|