astra-sdk-web 1.1.5 → 1.1.7

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.
@@ -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
- throw new Error(message);
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;
@@ -1185,18 +1188,82 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
1185
1188
  handleFaceCapture
1186
1189
  };
1187
1190
  }
1191
+ function Toast({ message, type = "info", duration = 5e3, onClose }) {
1192
+ const [isVisible, setIsVisible] = React.useState(true);
1193
+ React.useEffect(() => {
1194
+ const timer = setTimeout(() => {
1195
+ setIsVisible(false);
1196
+ setTimeout(() => {
1197
+ if (onClose) onClose();
1198
+ }, 300);
1199
+ }, duration);
1200
+ return () => clearTimeout(timer);
1201
+ }, [duration, onClose]);
1202
+ const bgColor = {
1203
+ success: "bg-green-600",
1204
+ error: "bg-red-600",
1205
+ info: "bg-blue-600",
1206
+ warning: "bg-yellow-600"
1207
+ }[type];
1208
+ return /* @__PURE__ */ jsxRuntime.jsx(
1209
+ "div",
1210
+ {
1211
+ className: `fixed top-4 right-4 z-[10000] transition-all duration-300 ${isVisible ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2"}`,
1212
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1213
+ "div",
1214
+ {
1215
+ className: `${bgColor} text-white px-6 py-4 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px] max-w-[500px]`,
1216
+ children: [
1217
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm font-medium", children: message }) }),
1218
+ /* @__PURE__ */ jsxRuntime.jsx(
1219
+ "button",
1220
+ {
1221
+ onClick: () => {
1222
+ setIsVisible(false);
1223
+ setTimeout(() => {
1224
+ if (onClose) onClose();
1225
+ }, 300);
1226
+ },
1227
+ className: "text-white hover:text-gray-200 transition-colors",
1228
+ "aria-label": "Close",
1229
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1230
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1231
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1232
+ ] })
1233
+ }
1234
+ )
1235
+ ]
1236
+ }
1237
+ )
1238
+ }
1239
+ );
1240
+ }
1188
1241
  function FaceScanModal({ onComplete }) {
1189
1242
  const faceCanvasRef = React.useRef(null);
1190
1243
  const navigate = reactRouterDom.useNavigate();
1191
1244
  const { apiService } = useKycContext();
1192
1245
  const [sessionError, setSessionError] = React.useState(null);
1246
+ const [toast, setToast] = React.useState(null);
1193
1247
  const { videoRef, cameraReady, stopCamera } = useCamera();
1194
1248
  const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
1195
1249
  onFaceUpload: async (blob) => {
1196
1250
  if (!apiService) {
1197
1251
  throw new Error("API service not initialized");
1198
1252
  }
1199
- await apiService.uploadFaceScan(blob);
1253
+ try {
1254
+ await apiService.uploadFaceScan(blob);
1255
+ } catch (error) {
1256
+ const errorMessage = error?.message || "";
1257
+ const errorData = error?.errorData || {};
1258
+ if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
1259
+ setToast({
1260
+ message: "Face has already been registered for this session. Proceeding to document upload.",
1261
+ type: "warning"
1262
+ });
1263
+ return;
1264
+ }
1265
+ throw error;
1266
+ }
1200
1267
  },
1201
1268
  onFaceCaptureComplete: (imageData) => {
1202
1269
  if (onComplete) {
@@ -1291,61 +1358,72 @@ function FaceScanModal({ onComplete }) {
1291
1358
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm", children: "Redirecting to QR code page..." })
1292
1359
  ] }) }) });
1293
1360
  }
1294
- return /* @__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: [
1295
- /* @__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" }) }),
1296
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4", children: [
1297
- !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." }),
1298
- 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..." }),
1299
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-square rounded-full overflow-hidden bg-black", children: [
1361
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1362
+ toast && /* @__PURE__ */ jsxRuntime.jsx(
1363
+ Toast,
1364
+ {
1365
+ message: toast.message,
1366
+ type: toast.type,
1367
+ onClose: () => setToast(null),
1368
+ duration: 6e3
1369
+ }
1370
+ ),
1371
+ /* @__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: [
1372
+ /* @__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" }) }),
1373
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4", children: [
1374
+ !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." }),
1375
+ 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..." }),
1376
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-square rounded-full overflow-hidden bg-black", children: [
1377
+ /* @__PURE__ */ jsxRuntime.jsx(
1378
+ "video",
1379
+ {
1380
+ ref: videoRef,
1381
+ playsInline: true,
1382
+ muted: true,
1383
+ className: "w-full h-full block bg-black object-cover -scale-x-100 origin-center"
1384
+ }
1385
+ ),
1386
+ /* @__PURE__ */ jsxRuntime.jsx(
1387
+ "canvas",
1388
+ {
1389
+ ref: faceCanvasRef,
1390
+ className: "absolute top-0 left-0 w-full h-full pointer-events-none z-[2]"
1391
+ }
1392
+ ),
1393
+ /* @__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" }) })
1394
+ ] }),
1395
+ !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: [
1396
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-bold mb-2.5 text-[22px] text-white", children: "Liveness Check" }),
1397
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2.5 text-base", children: state.livenessInstruction }),
1398
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2.5 text-lg", children: [
1399
+ /* @__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" }),
1400
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "2. Turn your face right" }),
1401
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
1402
+ ] })
1403
+ ] }),
1300
1404
  /* @__PURE__ */ jsxRuntime.jsx(
1301
- "video",
1405
+ "button",
1302
1406
  {
1303
- ref: videoRef,
1304
- playsInline: true,
1305
- muted: true,
1306
- className: "w-full h-full block bg-black object-cover -scale-x-100 origin-center"
1407
+ type: "button",
1408
+ disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
1409
+ onClick: handleFaceCapture,
1410
+ 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"}`,
1411
+ children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
1307
1412
  }
1308
1413
  ),
1309
1414
  /* @__PURE__ */ jsxRuntime.jsx(
1310
- "canvas",
1415
+ "button",
1311
1416
  {
1312
- ref: faceCanvasRef,
1313
- className: "absolute top-0 left-0 w-full h-full pointer-events-none z-[2]"
1417
+ type: "button",
1418
+ onClick: handleRetry,
1419
+ disabled: state.loading,
1420
+ 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]"}`,
1421
+ children: "Restart"
1314
1422
  }
1315
- ),
1316
- /* @__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" }) })
1317
- ] }),
1318
- !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: [
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
- ] }) });
1423
+ )
1424
+ ] })
1425
+ ] }) })
1426
+ ] });
1349
1427
  }
1350
1428
  var FaceScanModal_default = FaceScanModal;
1351
1429
  function MobileRouteContent({ onClose, onComplete }) {
@@ -1463,15 +1541,15 @@ function QRCodePage({ onClose, onNavigate, mobileBaseUrl = "https://astra-sdk-re
1463
1541
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-3 sm:gap-4 flex-1 overflow-y-auto custom__scrollbar", children: [
1464
1542
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "m-0 text-white text-xl sm:text-2xl font-semibold leading-tight", children: "Continue on Mobile" }),
1465
1543
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-white text-sm sm:text-base opacity-90 leading-relaxed", children: "Scan this QR on your phone to capture your face and document" }),
1466
- qrUrl && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center items-center p-3 sm:p-4 bg-white rounded-xl border-2 border-white", children: /* @__PURE__ */ jsxRuntime.jsx(
1544
+ qrUrl && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center items-center p-3 sm:p-4 bg-black rounded-xl border-2 border-white shadow-lg", children: /* @__PURE__ */ jsxRuntime.jsx(
1467
1545
  qrcode_react.QRCodeSVG,
1468
1546
  {
1469
1547
  value: qrUrl,
1470
1548
  size: 180,
1471
1549
  level: "H",
1472
1550
  includeMargin: true,
1473
- bgColor: "transparent",
1474
- fgColor: "#000000"
1551
+ bgColor: "#000000",
1552
+ fgColor: "#FFFFFF"
1475
1553
  }
1476
1554
  ) }),
1477
1555
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full text-left mt-auto", children: [