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.
- package/dist/astra-sdk.cjs.js +131 -53
- package/dist/astra-sdk.cjs.js.map +1 -1
- package/dist/astra-sdk.css +64 -0
- package/dist/astra-sdk.css.map +1 -1
- package/dist/astra-sdk.es.js +132 -54
- package/dist/astra-sdk.es.js.map +1 -1
- package/dist/components.cjs.js +131 -53
- package/dist/components.cjs.js.map +1 -1
- package/dist/components.css +64 -0
- package/dist/components.css.map +1 -1
- package/dist/components.es.js +132 -54
- 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/pages/QRCodePage.tsx +3 -3
- 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;
|
|
@@ -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
|
-
|
|
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.
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
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
|
-
"
|
|
1405
|
+
"button",
|
|
1302
1406
|
{
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
className:
|
|
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
|
-
"
|
|
1415
|
+
"button",
|
|
1311
1416
|
{
|
|
1312
|
-
|
|
1313
|
-
|
|
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
|
-
|
|
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
|
-
] }) });
|
|
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-
|
|
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: "
|
|
1474
|
-
fgColor: "#
|
|
1551
|
+
bgColor: "#000000",
|
|
1552
|
+
fgColor: "#FFFFFF"
|
|
1475
1553
|
}
|
|
1476
1554
|
) }),
|
|
1477
1555
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full text-left mt-auto", children: [
|