astra-sdk-web 1.1.6 → 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.
@@ -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
- throw new Error(message);
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;
@@ -1390,18 +1393,82 @@ function useFaceScan(videoRef, canvasRef, callbacks) {
1390
1393
  handleFaceCapture
1391
1394
  };
1392
1395
  }
1396
+ function Toast({ message, type = "info", duration = 5e3, onClose }) {
1397
+ const [isVisible, setIsVisible] = React.useState(true);
1398
+ React.useEffect(() => {
1399
+ const timer = setTimeout(() => {
1400
+ setIsVisible(false);
1401
+ setTimeout(() => {
1402
+ if (onClose) onClose();
1403
+ }, 300);
1404
+ }, duration);
1405
+ return () => clearTimeout(timer);
1406
+ }, [duration, onClose]);
1407
+ const bgColor = {
1408
+ success: "bg-green-600",
1409
+ error: "bg-red-600",
1410
+ info: "bg-blue-600",
1411
+ warning: "bg-yellow-600"
1412
+ }[type];
1413
+ return /* @__PURE__ */ jsxRuntime.jsx(
1414
+ "div",
1415
+ {
1416
+ className: `fixed top-4 right-4 z-[10000] transition-all duration-300 ${isVisible ? "opacity-100 translate-y-0" : "opacity-0 -translate-y-2"}`,
1417
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1418
+ "div",
1419
+ {
1420
+ className: `${bgColor} text-white px-6 py-4 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px] max-w-[500px]`,
1421
+ children: [
1422
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "m-0 text-sm font-medium", children: message }) }),
1423
+ /* @__PURE__ */ jsxRuntime.jsx(
1424
+ "button",
1425
+ {
1426
+ onClick: () => {
1427
+ setIsVisible(false);
1428
+ setTimeout(() => {
1429
+ if (onClose) onClose();
1430
+ }, 300);
1431
+ },
1432
+ className: "text-white hover:text-gray-200 transition-colors",
1433
+ "aria-label": "Close",
1434
+ children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", children: [
1435
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }),
1436
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })
1437
+ ] })
1438
+ }
1439
+ )
1440
+ ]
1441
+ }
1442
+ )
1443
+ }
1444
+ );
1445
+ }
1393
1446
  function FaceScanModal({ onComplete }) {
1394
1447
  const faceCanvasRef = React.useRef(null);
1395
1448
  const navigate = reactRouterDom.useNavigate();
1396
1449
  const { apiService } = useKycContext();
1397
1450
  const [sessionError, setSessionError] = React.useState(null);
1451
+ const [toast, setToast] = React.useState(null);
1398
1452
  const { videoRef, cameraReady, stopCamera } = useCamera();
1399
1453
  const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
1400
1454
  onFaceUpload: async (blob) => {
1401
1455
  if (!apiService) {
1402
1456
  throw new Error("API service not initialized");
1403
1457
  }
1404
- await apiService.uploadFaceScan(blob);
1458
+ try {
1459
+ await apiService.uploadFaceScan(blob);
1460
+ } catch (error) {
1461
+ const errorMessage = error?.message || "";
1462
+ const errorData = error?.errorData || {};
1463
+ if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
1464
+ setToast({
1465
+ message: "Face has already been registered for this session. Proceeding to document upload.",
1466
+ type: "warning"
1467
+ });
1468
+ return;
1469
+ }
1470
+ throw error;
1471
+ }
1405
1472
  },
1406
1473
  onFaceCaptureComplete: (imageData) => {
1407
1474
  if (onComplete) {
@@ -1496,61 +1563,72 @@ function FaceScanModal({ onComplete }) {
1496
1563
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm", children: "Redirecting to QR code page..." })
1497
1564
  ] }) }) });
1498
1565
  }
1499
- 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: [
1500
- /* @__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" }) }),
1501
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4", children: [
1502
- !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." }),
1503
- 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..." }),
1504
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-square rounded-full overflow-hidden bg-black", children: [
1566
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1567
+ toast && /* @__PURE__ */ jsxRuntime.jsx(
1568
+ Toast,
1569
+ {
1570
+ message: toast.message,
1571
+ type: toast.type,
1572
+ onClose: () => setToast(null),
1573
+ duration: 6e3
1574
+ }
1575
+ ),
1576
+ /* @__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: [
1577
+ /* @__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" }) }),
1578
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-4", children: [
1579
+ !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." }),
1580
+ 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..." }),
1581
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full aspect-square rounded-full overflow-hidden bg-black", children: [
1582
+ /* @__PURE__ */ jsxRuntime.jsx(
1583
+ "video",
1584
+ {
1585
+ ref: videoRef,
1586
+ playsInline: true,
1587
+ muted: true,
1588
+ className: "w-full h-full block bg-black object-cover -scale-x-100 origin-center"
1589
+ }
1590
+ ),
1591
+ /* @__PURE__ */ jsxRuntime.jsx(
1592
+ "canvas",
1593
+ {
1594
+ ref: faceCanvasRef,
1595
+ className: "absolute top-0 left-0 w-full h-full pointer-events-none z-[2]"
1596
+ }
1597
+ ),
1598
+ /* @__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" }) })
1599
+ ] }),
1600
+ !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: [
1601
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-bold mb-2.5 text-[22px] text-white", children: "Liveness Check" }),
1602
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-2.5 text-base", children: state.livenessInstruction }),
1603
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid gap-2.5 text-lg", children: [
1604
+ /* @__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" }),
1605
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "RIGHT" || state.livenessStage === "DONE" ? "opacity-100" : "opacity-40", children: "2. Turn your face right" }),
1606
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
1607
+ ] })
1608
+ ] }),
1505
1609
  /* @__PURE__ */ jsxRuntime.jsx(
1506
- "video",
1610
+ "button",
1507
1611
  {
1508
- ref: videoRef,
1509
- playsInline: true,
1510
- muted: true,
1511
- className: "w-full h-full block bg-black object-cover -scale-x-100 origin-center"
1612
+ type: "button",
1613
+ disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
1614
+ onClick: handleFaceCapture,
1615
+ 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"}`,
1616
+ children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
1512
1617
  }
1513
1618
  ),
1514
1619
  /* @__PURE__ */ jsxRuntime.jsx(
1515
- "canvas",
1620
+ "button",
1516
1621
  {
1517
- ref: faceCanvasRef,
1518
- className: "absolute top-0 left-0 w-full h-full pointer-events-none z-[2]"
1622
+ type: "button",
1623
+ onClick: handleRetry,
1624
+ disabled: state.loading,
1625
+ 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]"}`,
1626
+ children: "Restart"
1519
1627
  }
1520
- ),
1521
- /* @__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" }) })
1522
- ] }),
1523
- !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: [
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
- ] }) });
1628
+ )
1629
+ ] })
1630
+ ] }) })
1631
+ ] });
1554
1632
  }
1555
1633
  var FaceScanModal_default = FaceScanModal;
1556
1634
  function MobileRouteContent({ onClose, onComplete }) {