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