astra-sdk-web 1.1.9 → 1.1.10

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.
@@ -137,6 +137,36 @@ var KycApiService = class {
137
137
  throw new Error(`Document upload failed: ${message}`);
138
138
  }
139
139
  }
140
+ /**
141
+ * Retry session - resets the session to allow face registration again
142
+ */
143
+ async retrySession() {
144
+ const deviceType = this.config.deviceType || this.detectDeviceType();
145
+ try {
146
+ const response = await fetch(
147
+ `${this.config.apiBaseUrl}/api/v2/dashboard/merchant/onsite/session/${this.config.sessionId}/retry`,
148
+ {
149
+ method: "POST",
150
+ headers: {
151
+ "x-server-key": this.config.serverKey,
152
+ "device-type": deviceType,
153
+ "Content-Type": "application/json"
154
+ },
155
+ credentials: "include"
156
+ }
157
+ );
158
+ if (!response.ok) {
159
+ const errorData = await response.json().catch(() => ({}));
160
+ const message = errorData?.message || `Retry failed with status ${response.status}`;
161
+ throw new Error(message);
162
+ }
163
+ const data = await response.json();
164
+ return data;
165
+ } catch (error) {
166
+ const message = error?.message || "Retry failed";
167
+ throw new Error(`Retry failed: ${message}`);
168
+ }
169
+ }
140
170
  /**
141
171
  * Check if session is active, throw error if not
142
172
  */
@@ -411,6 +441,7 @@ function DocumentUploadModal({ onComplete }) {
411
441
  const navigate = reactRouterDom.useNavigate();
412
442
  const { apiService } = useKycContext();
413
443
  const [sessionError, setSessionError] = React.useState(null);
444
+ const [kycCompleted, setKycCompleted] = React.useState(false);
414
445
  const {
415
446
  state,
416
447
  setState,
@@ -426,6 +457,15 @@ function DocumentUploadModal({ onComplete }) {
426
457
  throw new Error("API service not initialized");
427
458
  }
428
459
  await apiService.uploadDocument(blob, docType);
460
+ try {
461
+ const statusResponse = await apiService.getSessionStatus();
462
+ const { completed_steps, status } = statusResponse.data;
463
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
464
+ setKycCompleted(true);
465
+ }
466
+ } catch (error) {
467
+ console.error("Error checking completion status:", error);
468
+ }
429
469
  },
430
470
  onUpload: (file, docType) => {
431
471
  if (onComplete) {
@@ -444,11 +484,18 @@ function DocumentUploadModal({ onComplete }) {
444
484
  try {
445
485
  const statusResponse = await apiService.getSessionStatus();
446
486
  const { completed_steps, next_step, status } = statusResponse.data;
487
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
488
+ setKycCompleted(true);
489
+ return;
490
+ }
447
491
  if (status !== "ACTIVE") {
448
492
  throw new Error("Session expired or inactive");
449
493
  }
450
494
  if (completed_steps.includes(COMPLETED_STEPS.DOCS)) {
451
- console.log("Document already uploaded");
495
+ if (completed_steps.includes(COMPLETED_STEPS.FACE) && completed_steps.includes(COMPLETED_STEPS.DOCS)) {
496
+ setKycCompleted(true);
497
+ return;
498
+ }
452
499
  }
453
500
  if (next_step === COMPLETED_STEPS.FACE && !completed_steps.includes(COMPLETED_STEPS.FACE)) {
454
501
  console.warn("Face scan not completed, but in document upload modal");
@@ -464,6 +511,14 @@ function DocumentUploadModal({ onComplete }) {
464
511
  };
465
512
  checkSession();
466
513
  }, [apiService, navigate]);
514
+ if (kycCompleted) {
515
+ 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.jsx("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
516
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
517
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
518
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
519
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
520
+ ] }) }) });
521
+ }
467
522
  if (sessionError) {
468
523
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black p-4 z-[1000] flex items-center justify-center font-sans overflow-y-auto custom__scrollbar", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
469
524
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-red-500", children: "Session Expired" }),
@@ -1300,6 +1355,9 @@ function FaceScanModal({ onComplete }) {
1300
1355
  const { apiService } = useKycContext();
1301
1356
  const [sessionError, setSessionError] = React.useState(null);
1302
1357
  const [toast, setToast] = React.useState(null);
1358
+ const [showRetryButton, setShowRetryButton] = React.useState(false);
1359
+ const [isRetrying, setIsRetrying] = React.useState(false);
1360
+ const [kycCompleted, setKycCompleted] = React.useState(false);
1303
1361
  const { videoRef, cameraReady, stopCamera } = useCamera();
1304
1362
  const { state, setState, refs, handleFaceCapture } = useFaceScan(videoRef, faceCanvasRef, {
1305
1363
  onFaceUpload: async (blob) => {
@@ -1312,10 +1370,12 @@ function FaceScanModal({ onComplete }) {
1312
1370
  const errorMessage = error?.message || "";
1313
1371
  const errorData = error?.errorData || {};
1314
1372
  if (errorMessage.includes("Face already registered") || errorMessage.includes("already registered") || errorData?.message?.includes("Face already registered") || error?.statusCode === 500 && errorMessage.includes("Face")) {
1373
+ setShowRetryButton(true);
1315
1374
  setToast({
1316
- message: "Face already registered",
1375
+ message: "Face already registered. Click Retry to register again.",
1317
1376
  type: "warning"
1318
1377
  });
1378
+ setState((prev) => ({ ...prev, loading: false }));
1319
1379
  return;
1320
1380
  }
1321
1381
  throw error;
@@ -1336,6 +1396,10 @@ function FaceScanModal({ onComplete }) {
1336
1396
  if (status !== "ACTIVE") {
1337
1397
  throw new Error("Session expired or inactive");
1338
1398
  }
1399
+ if (status === "COMPLETED" || completed_steps.includes(COMPLETED_STEPS.COMPLETED)) {
1400
+ setKycCompleted(true);
1401
+ return;
1402
+ }
1339
1403
  if (completed_steps.includes(COMPLETED_STEPS.FACE)) {
1340
1404
  setState((prev) => ({ ...prev, showDocumentUpload: true }));
1341
1405
  return;
@@ -1383,7 +1447,47 @@ function FaceScanModal({ onComplete }) {
1383
1447
  }
1384
1448
  }
1385
1449
  }, [cameraReady, apiService]);
1386
- const handleRetry = () => {
1450
+ const handleRetry = async () => {
1451
+ if (!apiService || isRetrying) return;
1452
+ setIsRetrying(true);
1453
+ setShowRetryButton(false);
1454
+ setToast(null);
1455
+ try {
1456
+ await apiService.retrySession();
1457
+ stopCamera();
1458
+ setState({
1459
+ cameraReady: false,
1460
+ livenessStage: "CENTER",
1461
+ livenessReady: false,
1462
+ livenessFailed: false,
1463
+ modelLoading: true,
1464
+ modelLoaded: false,
1465
+ livenessInstruction: "Look straight at the camera",
1466
+ loading: false,
1467
+ allStepsCompleted: false,
1468
+ capturedImage: null,
1469
+ showDocumentUpload: false
1470
+ });
1471
+ refs.centerHold.current = 0;
1472
+ refs.leftHold.current = 0;
1473
+ refs.rightHold.current = 0;
1474
+ refs.snapTriggered.current = false;
1475
+ refs.lastResultsAt.current = 0;
1476
+ refs.modelLoaded.current = false;
1477
+ refs.livenessFailed.current = false;
1478
+ setTimeout(() => {
1479
+ window.location.reload();
1480
+ }, 500);
1481
+ } catch (error) {
1482
+ setIsRetrying(false);
1483
+ setShowRetryButton(true);
1484
+ setToast({
1485
+ message: error?.message || "Retry failed. Please try again.",
1486
+ type: "error"
1487
+ });
1488
+ }
1489
+ };
1490
+ const handleRestart = () => {
1387
1491
  stopCamera();
1388
1492
  setState({
1389
1493
  cameraReady: false,
@@ -1409,6 +1513,14 @@ function FaceScanModal({ onComplete }) {
1409
1513
  window.location.reload();
1410
1514
  }, 100);
1411
1515
  };
1516
+ if (kycCompleted) {
1517
+ 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.jsx("div", { className: "max-w-[400px] w-full mx-auto bg-[#0b0f17] rounded-2xl p-6 shadow-xl", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
1518
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-4 text-6xl", children: "\u2705" }),
1519
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "m-0 mb-4 text-[26px] font-bold text-green-500", children: "KYC Completed" }),
1520
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#e5e7eb] mb-4 text-lg", children: "All steps have been completed successfully." }),
1521
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[#9ca3af] text-sm mb-6", children: "Please return to your desktop to continue." })
1522
+ ] }) }) });
1523
+ }
1412
1524
  if (state.showDocumentUpload) {
1413
1525
  return /* @__PURE__ */ jsxRuntime.jsx(
1414
1526
  DocumentUploadModal_default,
@@ -1471,13 +1583,23 @@ function FaceScanModal({ onComplete }) {
1471
1583
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: state.livenessStage === "DONE" ? "opacity-100" : "opacity-30", children: "3. Turn your face left" })
1472
1584
  ] })
1473
1585
  ] }),
1586
+ showRetryButton && /* @__PURE__ */ jsxRuntime.jsx(
1587
+ "button",
1588
+ {
1589
+ type: "button",
1590
+ onClick: handleRetry,
1591
+ disabled: isRetrying || state.loading,
1592
+ className: "py-3.5 px-4 rounded-xl text-base font-bold border-none transition-colors bg-[#f59e0b] text-[#0b0f17] cursor-pointer hover:bg-[#d97706] disabled:opacity-50 disabled:cursor-not-allowed",
1593
+ children: isRetrying ? "Retrying..." : "Retry Face Registration"
1594
+ }
1595
+ ),
1474
1596
  /* @__PURE__ */ jsxRuntime.jsx(
1475
1597
  "button",
1476
1598
  {
1477
1599
  type: "button",
1478
- disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE",
1600
+ disabled: !state.cameraReady || state.loading || !state.livenessFailed && state.livenessStage !== "DONE" || showRetryButton,
1479
1601
  onClick: handleFaceCapture,
1480
- 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"}`,
1602
+ 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") && !showRetryButton ? "bg-[#22c55e] text-[#0b0f17] cursor-pointer hover:bg-[#16a34a]" : "bg-[#374151] text-[#e5e7eb] cursor-not-allowed"}`,
1481
1603
  children: state.loading ? "Capturing..." : state.livenessFailed || state.livenessStage === "DONE" ? "Capture & Continue" : "Complete steps to continue"
1482
1604
  }
1483
1605
  ),
@@ -1485,9 +1607,9 @@ function FaceScanModal({ onComplete }) {
1485
1607
  "button",
1486
1608
  {
1487
1609
  type: "button",
1488
- onClick: handleRetry,
1489
- disabled: state.loading,
1490
- 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]"}`,
1610
+ onClick: handleRestart,
1611
+ disabled: state.loading || isRetrying,
1612
+ className: `py-3 px-4 rounded-[10px] text-[15px] font-semibold border-none w-full transition-colors ${state.loading || isRetrying ? "bg-[#374151] text-[#e5e7eb] cursor-not-allowed opacity-50" : "bg-[#374151] text-[#e5e7eb] cursor-pointer hover:bg-[#4b5563]"}`,
1491
1613
  children: "Restart"
1492
1614
  }
1493
1615
  )