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.
- package/dist/astra-sdk.cjs.js +128 -50
- package/dist/astra-sdk.cjs.js.map +1 -1
- package/dist/astra-sdk.css +56 -0
- package/dist/astra-sdk.css.map +1 -1
- package/dist/astra-sdk.es.js +129 -51
- package/dist/astra-sdk.es.js.map +1 -1
- package/dist/components.cjs.js +128 -50
- package/dist/components.cjs.js.map +1 -1
- package/dist/components.css +56 -0
- package/dist/components.css.map +1 -1
- package/dist/components.es.js +129 -51
- package/dist/components.es.js.map +1 -1
- package/package.json +1 -1
- package/src/components/Toast.tsx +82 -0
- package/src/pages/FaceScanModal.tsx +37 -3
- 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;
|
|
@@ -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
|
-
|
|
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.
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
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
|
-
"
|
|
1610
|
+
"button",
|
|
1507
1611
|
{
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
className:
|
|
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
|
-
"
|
|
1620
|
+
"button",
|
|
1516
1621
|
{
|
|
1517
|
-
|
|
1518
|
-
|
|
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
|
-
|
|
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
|
-
] }) });
|
|
1628
|
+
)
|
|
1629
|
+
] })
|
|
1630
|
+
] }) })
|
|
1631
|
+
] });
|
|
1554
1632
|
}
|
|
1555
1633
|
var FaceScanModal_default = FaceScanModal;
|
|
1556
1634
|
function MobileRouteContent({ onClose, onComplete }) {
|