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/components.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';
|
|
@@ -78,7 +78,10 @@ var KycApiService = class {
|
|
|
78
78
|
if (!response.ok) {
|
|
79
79
|
const errorData = await response.json().catch(() => ({}));
|
|
80
80
|
const message = errorData?.message || `Face upload failed with status ${response.status}`;
|
|
81
|
-
|
|
81
|
+
const error = new Error(message);
|
|
82
|
+
error.statusCode = response.status;
|
|
83
|
+
error.errorData = errorData;
|
|
84
|
+
throw error;
|
|
82
85
|
}
|
|
83
86
|
const data = await response.json();
|
|
84
87
|
return data;
|
|
@@ -740,6 +743,8 @@ var FaceMeshService = class {
|
|
|
740
743
|
}
|
|
741
744
|
this.processLiveness(faceOnCanvas, w, h);
|
|
742
745
|
} else {
|
|
746
|
+
this.livenessStateRef.current.currentYaw = null;
|
|
747
|
+
this.livenessStateRef.current.currentAbsYaw = null;
|
|
743
748
|
const vid = this.videoRef.current;
|
|
744
749
|
if (vid) {
|
|
745
750
|
const vidW = Math.max(1, vid?.videoWidth || displayW);
|
|
@@ -783,6 +788,8 @@ var FaceMeshService = class {
|
|
|
783
788
|
const midX = (leftEyeOuter.x + rightEyeOuter.x) / 2;
|
|
784
789
|
const yaw = (nT.x - midX) / Math.max(1e-6, faceWidth);
|
|
785
790
|
const absYaw = Math.abs(yaw);
|
|
791
|
+
this.livenessStateRef.current.currentYaw = yaw;
|
|
792
|
+
this.livenessStateRef.current.currentAbsYaw = absYaw;
|
|
786
793
|
const xs = faceOnCanvas.map((p) => p.x), ys = faceOnCanvas.map((p) => p.y);
|
|
787
794
|
const minX = Math.min(...xs) * w, maxX = Math.max(...xs) * w;
|
|
788
795
|
const minY = Math.min(...ys) * h, maxY = Math.max(...ys) * h;
|
|
@@ -809,11 +816,13 @@ var FaceMeshService = class {
|
|
|
809
816
|
} else if (absYaw < centerThreshold) {
|
|
810
817
|
state.centerHold += 1;
|
|
811
818
|
if (state.centerHold >= holdFramesCenter) {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
this.callbacks.onLivenessUpdate
|
|
819
|
+
if (!state.livenessCompleted) {
|
|
820
|
+
const newStage = "LEFT";
|
|
821
|
+
state.stage = newStage;
|
|
822
|
+
state.centerHold = 0;
|
|
823
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
824
|
+
this.callbacks.onLivenessUpdate(newStage, "Turn your face LEFT");
|
|
825
|
+
}
|
|
817
826
|
}
|
|
818
827
|
}
|
|
819
828
|
} else {
|
|
@@ -852,16 +861,12 @@ var FaceMeshService = class {
|
|
|
852
861
|
state.rightHold += 1;
|
|
853
862
|
if (state.rightHold >= holdFramesTurn) {
|
|
854
863
|
state.rightHold = 0;
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
}
|
|
862
|
-
if (this.callbacks.onCaptureTrigger) {
|
|
863
|
-
this.callbacks.onCaptureTrigger();
|
|
864
|
-
}
|
|
864
|
+
state.livenessCompleted = true;
|
|
865
|
+
const newStage = "DONE";
|
|
866
|
+
state.stage = newStage;
|
|
867
|
+
state.centerHold = 0;
|
|
868
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
869
|
+
this.callbacks.onLivenessUpdate(newStage, "Great! Now look straight at the camera");
|
|
865
870
|
}
|
|
866
871
|
}
|
|
867
872
|
} else {
|
|
@@ -870,6 +875,28 @@ var FaceMeshService = class {
|
|
|
870
875
|
this.callbacks.onLivenessUpdate(state.stage, yaw < -0.08 ? "You're facing left. Turn RIGHT" : "Turn a bit more RIGHT");
|
|
871
876
|
}
|
|
872
877
|
}
|
|
878
|
+
} else if (state.stage === "DONE") {
|
|
879
|
+
if (absYaw < centerThreshold && insideGuide) {
|
|
880
|
+
state.centerHold += 1;
|
|
881
|
+
if (state.centerHold >= holdFramesCenter && !state.snapTriggered) {
|
|
882
|
+
state.snapTriggered = true;
|
|
883
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
884
|
+
this.callbacks.onLivenessUpdate(state.stage, "Capturing...");
|
|
885
|
+
}
|
|
886
|
+
if (this.callbacks.onCaptureTrigger) {
|
|
887
|
+
this.callbacks.onCaptureTrigger();
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
} else {
|
|
891
|
+
state.centerHold = 0;
|
|
892
|
+
if (this.callbacks.onLivenessUpdate) {
|
|
893
|
+
if (!insideGuide) {
|
|
894
|
+
this.callbacks.onLivenessUpdate(state.stage, "Center your face inside the circle");
|
|
895
|
+
} else {
|
|
896
|
+
this.callbacks.onLivenessUpdate(state.stage, "Please look straight at the camera");
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
873
900
|
}
|
|
874
901
|
}
|
|
875
902
|
}
|
|
@@ -1005,7 +1032,10 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1005
1032
|
snapTriggered: false,
|
|
1006
1033
|
lastResultsAt: 0,
|
|
1007
1034
|
stage: "CENTER",
|
|
1008
|
-
livenessReady: false
|
|
1035
|
+
livenessReady: false,
|
|
1036
|
+
currentYaw: null,
|
|
1037
|
+
currentAbsYaw: null,
|
|
1038
|
+
livenessCompleted: false
|
|
1009
1039
|
});
|
|
1010
1040
|
useEffect(() => {
|
|
1011
1041
|
livenessStateRef.current.centerHold = refs.centerHold.current;
|
|
@@ -1026,6 +1056,22 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1026
1056
|
}, []);
|
|
1027
1057
|
const handleFaceCapture = useCallback(async () => {
|
|
1028
1058
|
if (!videoRef.current) return;
|
|
1059
|
+
const centerThreshold = 0.05;
|
|
1060
|
+
const currentAbsYaw = livenessStateRef.current.currentAbsYaw;
|
|
1061
|
+
if (currentAbsYaw === null || currentAbsYaw === void 0) {
|
|
1062
|
+
setState((prev) => ({
|
|
1063
|
+
...prev,
|
|
1064
|
+
livenessInstruction: "Please position your face in front of the camera"
|
|
1065
|
+
}));
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
if (currentAbsYaw >= centerThreshold) {
|
|
1069
|
+
setState((prev) => ({
|
|
1070
|
+
...prev,
|
|
1071
|
+
livenessInstruction: "Please look straight at the camera before capturing"
|
|
1072
|
+
}));
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1029
1075
|
setState((prev) => ({ ...prev, loading: true }));
|
|
1030
1076
|
try {
|
|
1031
1077
|
const video = videoRef.current;
|
|
@@ -1179,18 +1225,82 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
|
|
|
1179
1225
|
handleFaceCapture
|
|
1180
1226
|
};
|
|
1181
1227
|
}
|
|
1228
|
+
function Toast({ message, type = "info", duration = 5e3, onClose }) {
|
|
1229
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
1230
|
+
useEffect(() => {
|
|
1231
|
+
const timer = setTimeout(() => {
|
|
1232
|
+
setIsVisible(false);
|
|
1233
|
+
setTimeout(() => {
|
|
1234
|
+
if (onClose) onClose();
|
|
1235
|
+
}, 300);
|
|
1236
|
+
}, duration);
|
|
1237
|
+
return () => clearTimeout(timer);
|
|
1238
|
+
}, [duration, onClose]);
|
|
1239
|
+
const bgColor = {
|
|
1240
|
+
success: "bg-green-600",
|
|
1241
|
+
error: "bg-red-600",
|
|
1242
|
+
info: "bg-blue-600",
|
|
1243
|
+
warning: "bg-yellow-600"
|
|
1244
|
+
}[type];
|
|
1245
|
+
return /* @__PURE__ */ jsx(
|
|
1246
|
+
"div",
|
|
1247
|
+
{
|
|
1248
|
+
className: `fixed top-4 right-4 z-[10000] transition-all duration-300 ${isVisible ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2"}`,
|
|
1249
|
+
children: /* @__PURE__ */ jsxs(
|
|
1250
|
+
"div",
|
|
1251
|
+
{
|
|
1252
|
+
className: `${bgColor} text-white px-6 py-4 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px] max-w-[500px]`,
|
|
1253
|
+
children: [
|
|
1254
|
+
/* @__PURE__ */ jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsx("p", { className: "m-0 text-sm font-medium", children: message }) }),
|
|
1255
|
+
/* @__PURE__ */ jsx(
|
|
1256
|
+
"button",
|
|
1257
|
+
{
|
|
1258
|
+
onClick: () => {
|
|
1259
|
+
setIsVisible(false);
|
|
1260
|
+
setTimeout(() => {
|
|
1261
|
+
if (onClose) onClose();
|
|
1262
|
+
}, 300);
|
|
1263
|
+
},
|
|
1264
|
+
className: "text-white hover:text-gray-200 transition-colors",
|
|
1265
|
+
"aria-label": "Close",
|
|
1266
|
+
children: /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
|
|
1267
|
+
/* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
|
|
1268
|
+
/* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
|
|
1269
|
+
] })
|
|
1270
|
+
}
|
|
1271
|
+
)
|
|
1272
|
+
]
|
|
1273
|
+
}
|
|
1274
|
+
)
|
|
1275
|
+
}
|
|
1276
|
+
);
|
|
1277
|
+
}
|
|
1182
1278
|
function FaceScanModal({ onComplete }) {
|
|
1183
1279
|
const faceCanvasRef = useRef(null);
|
|
1184
1280
|
const navigate = useNavigate();
|
|
1185
1281
|
const { apiService } = useKycContext();
|
|
1186
1282
|
const [sessionError, setSessionError] = useState(null);
|
|
1283
|
+
const [toast, setToast] = useState(null);
|
|
1187
1284
|
const { videoRef, cameraReady, stopCamera } = useCamera();
|
|
1188
1285
|
const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
|
|
1189
1286
|
onFaceUpload: async (blob) => {
|
|
1190
1287
|
if (!apiService) {
|
|
1191
1288
|
throw new Error("API service not initialized");
|
|
1192
1289
|
}
|
|
1193
|
-
|
|
1290
|
+
try {
|
|
1291
|
+
await apiService.uploadFaceScan(blob);
|
|
1292
|
+
} catch (error) {
|
|
1293
|
+
const errorMessage = error?.message || "";
|
|
1294
|
+
const errorData = error?.errorData || {};
|
|
1295
|
+
if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
|
|
1296
|
+
setToast({
|
|
1297
|
+
message: "Face already registered",
|
|
1298
|
+
type: "warning"
|
|
1299
|
+
});
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
throw error;
|
|
1303
|
+
}
|
|
1194
1304
|
},
|
|
1195
1305
|
onFaceCaptureComplete: (imageData) => {
|
|
1196
1306
|
if (onComplete) {
|
|
@@ -1285,61 +1395,72 @@ function FaceScanModal({ onComplete }) {
|
|
|
1285
1395
|
/* @__PURE__ */ jsx("p", { className: "text-[#9ca3af] text-sm", children: "Redirecting to QR code page..." })
|
|
1286
1396
|
] }) }) });
|
|
1287
1397
|
}
|
|
1288
|
-
return /* @__PURE__ */
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1398
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1399
|
+
toast && /* @__PURE__ */ jsx(
|
|
1400
|
+
Toast,
|
|
1401
|
+
{
|
|
1402
|
+
message: toast.message,
|
|
1403
|
+
type: toast.type,
|
|
1404
|
+
onClose: () => setToast(null),
|
|
1405
|
+
duration: 6e3
|
|
1406
|
+
}
|
|
1407
|
+
),
|
|
1408
|
+
/* @__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: [
|
|
1409
|
+
/* @__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" }) }),
|
|
1410
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4", children: [
|
|
1411
|
+
!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." }),
|
|
1412
|
+
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..." }),
|
|
1413
|
+
/* @__PURE__ */ jsxs("div", { className: "relative w-full aspect-square rounded-full overflow-hidden bg-black", children: [
|
|
1414
|
+
/* @__PURE__ */ jsx(
|
|
1415
|
+
"video",
|
|
1416
|
+
{
|
|
1417
|
+
ref: videoRef,
|
|
1418
|
+
playsInline: true,
|
|
1419
|
+
muted: true,
|
|
1420
|
+
className: "w-full h-full block bg-black object-cover -scale-x-100 origin-center"
|
|
1421
|
+
}
|
|
1422
|
+
),
|
|
1423
|
+
/* @__PURE__ */ jsx(
|
|
1424
|
+
"canvas",
|
|
1425
|
+
{
|
|
1426
|
+
ref: faceCanvasRef,
|
|
1427
|
+
className: "absolute top-0 left-0 w-full h-full pointer-events-none z-[2]"
|
|
1428
|
+
}
|
|
1429
|
+
),
|
|
1430
|
+
/* @__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" }) })
|
|
1431
|
+
] }),
|
|
1432
|
+
!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: [
|
|
1433
|
+
/* @__PURE__ */ jsx("div", { className: "font-bold mb-2.5 text-[22px] text-white", children: "Liveness Check" }),
|
|
1434
|
+
/* @__PURE__ */ jsx("div", { className: "mb-2.5 text-base", children: state.livenessInstruction }),
|
|
1435
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-2.5 text-lg", children: [
|
|
1436
|
+
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "CENTER" || state.livenessStage === "LEFT" || state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "1. Look Straight" }),
|
|
1437
|
+
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "2. Turn your face right" }),
|
|
1438
|
+
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1439
|
+
] })
|
|
1440
|
+
] }),
|
|
1294
1441
|
/* @__PURE__ */ jsx(
|
|
1295
|
-
"
|
|
1442
|
+
"button",
|
|
1296
1443
|
{
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
className:
|
|
1444
|
+
type: "button",
|
|
1445
|
+
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1446
|
+
onClick: handleFaceCapture,
|
|
1447
|
+
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"}`,
|
|
1448
|
+
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1301
1449
|
}
|
|
1302
1450
|
),
|
|
1303
1451
|
/* @__PURE__ */ jsx(
|
|
1304
|
-
"
|
|
1452
|
+
"button",
|
|
1305
1453
|
{
|
|
1306
|
-
|
|
1307
|
-
|
|
1454
|
+
type: "button",
|
|
1455
|
+
onClick: handleRetry,
|
|
1456
|
+
disabled: state.loading,
|
|
1457
|
+
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]"}`,
|
|
1458
|
+
children: "Restart"
|
|
1308
1459
|
}
|
|
1309
|
-
)
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
/* @__PURE__ */ jsx("div", { className: "font-bold mb-2.5 text-[22px] text-white", children: "Liveness Check" }),
|
|
1314
|
-
/* @__PURE__ */ jsx("div", { className: "mb-2.5 text-base", children: state.livenessInstruction }),
|
|
1315
|
-
/* @__PURE__ */ jsxs("div", { className: "grid gap-2.5 text-lg", children: [
|
|
1316
|
-
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "CENTER" || state.livenessStage === "LEFT" || state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "1. Look Straight" }),
|
|
1317
|
-
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "2. Turn your face right" }),
|
|
1318
|
-
/* @__PURE__ */ jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
|
|
1319
|
-
] })
|
|
1320
|
-
] }),
|
|
1321
|
-
/* @__PURE__ */ jsx(
|
|
1322
|
-
"button",
|
|
1323
|
-
{
|
|
1324
|
-
type: "button",
|
|
1325
|
-
disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
|
|
1326
|
-
onClick: handleFaceCapture,
|
|
1327
|
-
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"}`,
|
|
1328
|
-
children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
|
|
1329
|
-
}
|
|
1330
|
-
),
|
|
1331
|
-
/* @__PURE__ */ jsx(
|
|
1332
|
-
"button",
|
|
1333
|
-
{
|
|
1334
|
-
type: "button",
|
|
1335
|
-
onClick: handleRetry,
|
|
1336
|
-
disabled: state.loading,
|
|
1337
|
-
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]"}`,
|
|
1338
|
-
children: "Restart"
|
|
1339
|
-
}
|
|
1340
|
-
)
|
|
1341
|
-
] })
|
|
1342
|
-
] }) });
|
|
1460
|
+
)
|
|
1461
|
+
] })
|
|
1462
|
+
] }) })
|
|
1463
|
+
] });
|
|
1343
1464
|
}
|
|
1344
1465
|
var FaceScanModal_default = FaceScanModal;
|
|
1345
1466
|
function MobileRouteContent({ onClose, onComplete }) {
|