vesant-sdk 1.7.0-dev.e0ee6d5 → 1.7.0-next.a744d2e

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/react.d.mts CHANGED
@@ -529,10 +529,12 @@ declare function useReuseKYCSubmission(client: KycClient): {
529
529
  verifyFace: (session: CreateReuseKycSessionResponse, opts?: {
530
530
  defaultDevice?: "ask" | "this" | "mobile";
531
531
  renderQR?: (payload: string) => React.ReactNode;
532
+ onCancel?: () => void;
532
533
  }) => Promise<ReuseKycCallback | null>;
533
534
  runFlow: (request: CreateReuseKycSessionRequest, opts?: {
534
535
  defaultDevice?: "ask" | "this" | "mobile";
535
536
  renderQR?: (payload: string) => React.ReactNode;
537
+ onCancel?: () => void;
536
538
  }) => Promise<{
537
539
  kind: "not_required";
538
540
  session: CreateReuseKycSessionResponse;
package/dist/react.d.ts CHANGED
@@ -529,10 +529,12 @@ declare function useReuseKYCSubmission(client: KycClient): {
529
529
  verifyFace: (session: CreateReuseKycSessionResponse, opts?: {
530
530
  defaultDevice?: "ask" | "this" | "mobile";
531
531
  renderQR?: (payload: string) => React.ReactNode;
532
+ onCancel?: () => void;
532
533
  }) => Promise<ReuseKycCallback | null>;
533
534
  runFlow: (request: CreateReuseKycSessionRequest, opts?: {
534
535
  defaultDevice?: "ask" | "this" | "mobile";
535
536
  renderQR?: (payload: string) => React.ReactNode;
537
+ onCancel?: () => void;
536
538
  }) => Promise<{
537
539
  kind: "not_required";
538
540
  session: CreateReuseKycSessionResponse;
package/dist/react.js CHANGED
@@ -888,7 +888,6 @@ function useCustomerProfile(client, customerId, options = {}) {
888
888
  var Camera = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjAvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvMjAwMS9SRUMtU1ZHLTIwMDEwOTA0L0RURC9zdmcxMC5kdGQiPg0KPCEtLSBVcGxvYWRlZCB0bzogU1ZHIFJlcG8sIHd3dy5zdmdyZXBvLmNvbSwgR2VuZXJhdG9yOiBTVkcgUmVwbyBNaXhlciBUb29scyAtLT4NCjxzdmcgdmVyc2lvbj0iMS4wIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgDQoJIHdpZHRoPSI4MDBweCIgaGVpZ2h0PSI4MDBweCIgdmlld0JveD0iMCAwIDY0IDY0IiBlbmFibGUtYmFja2dyb3VuZD0ibmV3IDAgMCA2NCA2NCIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBmaWxsPSIjMjMxRjIwIiBkPSJNNjAsMTBINDkuNjU2bC02LjgyOC02LjgyOEM0Mi4wNzgsMi40MjIsNDEuMDYyLDIsNDAsMkgyNGMtMS4wNjIsMC0yLjA3OCwwLjQyMi0yLjgyOCwxLjE3MkwxNC4zNDQsMTBINA0KCQljLTIuMjExLDAtNCwxLjc4OS00LDR2NDRjMCwyLjIxMSwxLjc4OSw0LDQsNGg1NmMyLjIxMSwwLDQtMS43ODksNC00VjE0QzY0LDExLjc4OSw2Mi4yMTEsMTAsNjAsMTB6IE0zMiw1MA0KCQljLTguODM2LDAtMTYtNy4xNjQtMTYtMTZzNy4xNjQtMTYsMTYtMTZzMTYsNy4xNjQsMTYsMTZTNDAuODM2LDUwLDMyLDUweiIvPg0KCTxjaXJjbGUgZmlsbD0iIzIzMUYyMCIgY3g9IjMyIiBjeT0iMzQiIHI9IjgiLz4NCjwvZz4NCjwvc3ZnPg==";
889
889
  var Done = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik04LjUgMTIuNUwxMC41IDE0LjVMMTUuNSA5LjUiIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPg0KPHBhdGggZD0iTTcgMy4zMzc4MkM4LjQ3MDg3IDIuNDg2OTcgMTAuMTc4NiAyIDEyIDJDMTcuNTIyOCAyIDIyIDYuNDc3MTUgMjIgMTJDMjIgMTcuNTIyOCAxNy41MjI4IDIyIDEyIDIyQzYuNDc3MTUgMjIgMiAxNy41MjI4IDIgMTJDMiAxMC4xNzg2IDIuNDg2OTcgOC40NzA4NyAzLjMzNzgyIDciIHN0cm9rZT0iIzFDMjc0QyIgc3Ryb2tlLXdpZHRoPSIxLjUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPg0KPC9zdmc+";
890
890
  var Close = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNC41IDkuNTAwMDJMOS41IDE0LjVNOS40OTk5OCA5LjVMMTQuNSAxNC41IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjxwYXRoIGQ9Ik03IDMuMzM3ODJDOC40NzA4NyAyLjQ4Njk3IDEwLjE3ODYgMiAxMiAyQzE3LjUyMjggMiAyMiA2LjQ3NzE1IDIyIDEyQzIyIDE3LjUyMjggMTcuNTIyOCAyMiAxMiAyMkM2LjQ3NzE1IDIyIDIgMTcuNTIyOCAyIDEyQzIgMTAuMTc4NiAyLjQ4Njk3IDguNDcwODcgMy4zMzc4MiA3IiBzdHJva2U9IiMxQzI3NEMiIHN0cm9rZS13aWR0aD0iMS41IiBzdHJva2UtbGluZWNhcD0icm91bmQiLz4NCjwvc3ZnPg==";
891
- var Upload = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xNyAxN0gxNy4wMU0xNS42IDE0SDE4QzE4LjkzMTkgMTQgMTkuMzk3OCAxNCAxOS43NjU0IDE0LjE1MjJDMjAuMjU1NCAxNC4zNTUyIDIwLjY0NDggMTQuNzQ0NiAyMC44NDc4IDE1LjIzNDZDMjEgMTUuNjAyMiAyMSAxNi4wNjgxIDIxIDE3QzIxIDE3LjkzMTkgMjEgMTguMzk3OCAyMC44NDc4IDE4Ljc2NTRDMjAuNjQ0OCAxOS4yNTU0IDIwLjI1NTQgMTkuNjQ0OCAxOS43NjU0IDE5Ljg0NzhDMTkuMzk3OCAyMCAxOC45MzE5IDIwIDE4IDIwSDZDNS4wNjgxMiAyMCA0LjYwMjE4IDIwIDQuMjM0NjMgMTkuODQ3OEMzLjc0NDU4IDE5LjY0NDggMy4zNTUyMyAxOS4yNTU0IDMuMTUyMjQgMTguNzY1NEMzIDE4LjM5NzggMyAxNy45MzE5IDMgMTdDMyAxNi4wNjgxIDMgMTUuNjAyMiAzLjE1MjI0IDE1LjIzNDZDMy4zNTUyMyAxNC43NDQ2IDMuNzQ0NTggMTQuMzU1MiA0LjIzNDYzIDE0LjE1MjJDNC42MDIxOCAxNCA1LjA2ODEyIDE0IDYgMTRIOC40TTEyIDE1VjRNMTIgNEwxNSA3TTEyIDRMOSA3IiBzdHJva2U9IiMwMDAwMDAiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+DQo8L3N2Zz4=";
892
891
 
893
892
  // src/kyc/FaceCaptureModal.tsx
894
893
  var MOBILE_UA = /Mobi|Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i;
@@ -905,9 +904,9 @@ function headerSubtitle(stageKind) {
905
904
  case "accepted":
906
905
  return "All set";
907
906
  case "declined":
908
- return "Verification didn't match, try once more";
907
+ return "Verification Declined";
909
908
  case "max_attempts":
910
- return "We couldn't verify you";
909
+ return "We couldn't verify your identity";
911
910
  default:
912
911
  return "Please capture a clear photo of your face";
913
912
  }
@@ -920,16 +919,18 @@ function FaceCaptureModal({
920
919
  client,
921
920
  session,
922
921
  onComplete,
922
+ onCancel,
923
923
  defaultDevice,
924
924
  renderQR
925
925
  }) {
926
926
  const isMobile = typeof navigator !== "undefined" && MOBILE_UA.test(navigator.userAgent);
927
+ const initialChoice = defaultDevice ?? (isMobile ? "this" : "ask");
927
928
  const initialStage = (() => {
928
- const choice = defaultDevice ?? (isMobile ? "this" : "ask");
929
- if (choice === "this") return { kind: "capture" };
930
- if (choice === "mobile") return { kind: "qr", mobileConnected: false };
929
+ if (initialChoice === "this") return { kind: "capture" };
930
+ if (initialChoice === "mobile") return { kind: "qr", mobileConnected: false };
931
931
  return { kind: "choose" };
932
932
  })();
933
+ const hasMethodChoice = initialChoice === "ask";
933
934
  const [stage, setStage] = React.useState(initialStage);
934
935
  const [attempts, setAttempts] = React.useState(session.attempts);
935
936
  const maxAttempts = session.max_attempts || 1;
@@ -1110,6 +1111,9 @@ function FaceCaptureModal({
1110
1111
  }, [captureMode]);
1111
1112
  const close = (result) => {
1112
1113
  cancelledRef.current = true;
1114
+ if (result === null && onCancel) {
1115
+ onCancel();
1116
+ }
1113
1117
  onComplete(result);
1114
1118
  };
1115
1119
  const renderChoose = () => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: circleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })))), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "How would you like to verify?"), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement(
@@ -1118,7 +1122,7 @@ function FaceCaptureModal({
1118
1122
  style: primaryButtonStyle,
1119
1123
  onClick: () => setStage({ kind: "capture" })
1120
1124
  },
1121
- /* @__PURE__ */ React__default.default.createElement("img", { src: isMobile ? Camera : Upload, alt: "", width: 20, height: 20 }),
1125
+ /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }),
1122
1126
  /* @__PURE__ */ React__default.default.createElement("span", null, "Continue on this device")
1123
1127
  ), /* @__PURE__ */ React__default.default.createElement(
1124
1128
  "button",
@@ -1135,6 +1139,13 @@ function FaceCaptureModal({
1135
1139
  onClick: () => setStage({ kind: "capture" })
1136
1140
  },
1137
1141
  "Use this device instead"
1142
+ ), hasMethodChoice && /* @__PURE__ */ React__default.default.createElement(
1143
+ "button",
1144
+ {
1145
+ style: secondaryButtonStyle,
1146
+ onClick: () => setStage({ kind: "choose" })
1147
+ },
1148
+ "Change verification method"
1138
1149
  ), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")));
1139
1150
  const renderCapture = (declinedReason) => {
1140
1151
  if (captureMode === "live") {
@@ -1150,13 +1161,23 @@ function FaceCaptureModal({
1150
1161
  if (captureMode === "preview" && capturedPreview) {
1151
1162
  return /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, declinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Looks good? Submit this selfie or retake it."), /* @__PURE__ */ React__default.default.createElement("div", { style: previewBoxStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: capturedPreview, alt: "Captured selfie preview", style: previewImgStyle })), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleConfirmSubmit }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Submit")), /* @__PURE__ */ React__default.default.createElement("button", { style: secondaryButtonStyle, onClick: handleRetake }, "Retake"), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
1152
1163
  }
1153
- return /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: circleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React__default.default.createElement("div", { style: badgeStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React__default.default.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React__default.default.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React__default.default.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, declinedReason ? "Try again" : "Open Camera")), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
1164
+ return /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: visualGuideStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: circleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React__default.default.createElement("div", { style: badgeStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 16, height: 16 })))), declinedReason && /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, declinedReason)), /* @__PURE__ */ React__default.default.createElement("div", { style: instructionsBoxStyle }, /* @__PURE__ */ React__default.default.createElement("h3", { style: instructionsTitleStyle }, "Tips for best results:"), /* @__PURE__ */ React__default.default.createElement("ul", { style: instructionsListStyle }, /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Ensure good lighting on your face")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Remove glasses or accessories if possible")), /* @__PURE__ */ React__default.default.createElement("li", { style: instructionItemStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: bulletStyle }, "\u2022"), /* @__PURE__ */ React__default.default.createElement("span", null, "Look directly at the camera")))), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleOpenCamera }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, declinedReason ? "Try again" : "Open Camera")), hasMethodChoice && /* @__PURE__ */ React__default.default.createElement(
1165
+ "button",
1166
+ {
1167
+ style: secondaryButtonStyle,
1168
+ onClick: () => setStage({ kind: "choose" })
1169
+ },
1170
+ "Change verification method"
1171
+ ), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", attempts + 1, " of ", maxAttempts));
1154
1172
  };
1155
1173
  const renderSubmitting = () => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: { ...spinnerStyle, margin: "24px auto", width: 32, height: 32 } }), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Verifying your photo\u2026"), /* @__PURE__ */ React__default.default.createElement("p", { style: { ...attemptsTextStyle, marginTop: 8 } }, "This usually takes a few seconds."));
1156
1174
  const renderAccepted = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: successCircleStyle }, /* @__PURE__ */ React__default.default.createElement("img", { src: Done, alt: "", width: 48, height: 48 })), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Verified"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, "Your identity has been confirmed. You can continue."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Continue")));
1157
- const renderDeclined = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("strong", null, "Verification declined."), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, result.declined_reason ?? "We couldn't match your selfie. Please take a new one.")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleRetakeAfterDecline }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Retake")), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(result) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
1158
- const renderMaxAttempts = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Maximum attempts reached"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, result.declined_reason ?? "We couldn't verify your identity after several attempts."), result.data?.freeze_account && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "For your security, your account has been temporarily restricted", result.data.freeze_duration_minutes ? ` for ${result.data.freeze_duration_minutes} minutes` : "", ". Please contact support if you need immediate help."), result.data?.enforce_logout && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "You will be signed out of your session."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
1159
- const renderError = (message) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Something went wrong"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, message), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try again"), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, "Cancel")));
1175
+ const renderDeclined = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: contentStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: alertBoxStyle }, /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, result.declined_reason ?? "We couldn't match your selfie. Please take a new one.")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: handleRetakeAfterDecline }, /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }), /* @__PURE__ */ React__default.default.createElement("span", null, "Retake")), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(result) }, "Cancel")), /* @__PURE__ */ React__default.default.createElement("p", { style: attemptsTextStyle }, "Attempt ", Math.min(attempts + 1, maxAttempts), " of ", maxAttempts));
1176
+ const renderMaxAttempts = (result) => /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Maximum Attempts Reached"), /* @__PURE__ */ React__default.default.createElement("p", { style: subtitleStyle }, result.declined_reason ?? "We couldn't verify your identity after several attempts."), result.data?.freeze_account && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "For your security, your account has been temporarily restricted", result.data.freeze_duration_minutes ? ` for ${result.data.freeze_duration_minutes} minutes` : "", ". Please contact support if you need immediate help."), result.data?.enforce_logout && /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, "You will be signed out of your session."), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => close(result) }, "Close")));
1177
+ const renderError = (message) => {
1178
+ const sessionExpired = /session\s+expired/i.test(message);
1179
+ return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center" } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, sessionExpired ? "Session expired" : "Something went wrong"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, sessionExpired ? "Your verification session is no longer valid. Please start a new verification from the beginning." : message), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, !sessionExpired && /* @__PURE__ */ React__default.default.createElement("button", { style: primaryButtonStyle, onClick: () => setStage({ kind: "capture" }) }, "Try again"), /* @__PURE__ */ React__default.default.createElement("button", { style: cancelButtonStyle, onClick: () => close(null) }, sessionExpired ? "Close" : "Cancel")));
1180
+ };
1160
1181
  const body = (() => {
1161
1182
  switch (stage.kind) {
1162
1183
  case "choose":
@@ -1364,11 +1385,63 @@ var attemptsTextStyle = {
1364
1385
  };
1365
1386
  var footerStyle = { padding: "12px 24px 16px", borderTop: "1px solid #f3f4f6" };
1366
1387
  var footerTextStyle = { margin: 0, fontSize: 12, color: "#9ca3af", textAlign: "center" };
1388
+ var FACE_MESSAGES = {
1389
+ idle: "Initializing camera...",
1390
+ loading: "Loading face detection...",
1391
+ scanning: "Position your face in the circle",
1392
+ too_far: "Face too far. Please move closer to the camera",
1393
+ too_close: "Too close. Please move back a little",
1394
+ off_center: "Center your face in the circle",
1395
+ ok: "Looking great! Tap the button to capture"
1396
+ };
1397
+ var FACE_COLORS = {
1398
+ idle: "#9ca3af",
1399
+ loading: "#3b82f6",
1400
+ scanning: "#3b82f6",
1401
+ too_far: "#ef4444",
1402
+ too_close: "#f59e0b",
1403
+ off_center: "#f59e0b",
1404
+ ok: "#10b981"
1405
+ };
1406
+ var MEDIAPIPE_BASE = "https://cdn.jsdelivr.net/npm/@mediapipe/face_detection";
1407
+ var mediapipeLoaderPromise = null;
1408
+ function loadMediaPipeFaceDetection() {
1409
+ if (typeof window === "undefined" || typeof document === "undefined") {
1410
+ return Promise.reject(new Error("MediaPipe requires a browser environment"));
1411
+ }
1412
+ const w = window;
1413
+ if (w.FaceDetection) {
1414
+ return Promise.resolve(w.FaceDetection);
1415
+ }
1416
+ if (mediapipeLoaderPromise) return mediapipeLoaderPromise;
1417
+ mediapipeLoaderPromise = new Promise((resolve, reject) => {
1418
+ const script = document.createElement("script");
1419
+ script.src = `${MEDIAPIPE_BASE}/face_detection.js`;
1420
+ script.async = true;
1421
+ script.crossOrigin = "anonymous";
1422
+ script.onload = () => {
1423
+ if (w.FaceDetection) {
1424
+ resolve(w.FaceDetection);
1425
+ } else {
1426
+ mediapipeLoaderPromise = null;
1427
+ reject(new Error("MediaPipe loaded but FaceDetection global is missing"));
1428
+ }
1429
+ };
1430
+ script.onerror = () => {
1431
+ mediapipeLoaderPromise = null;
1432
+ reject(new Error("Failed to load MediaPipe face_detection script"));
1433
+ };
1434
+ document.head.appendChild(script);
1435
+ });
1436
+ return mediapipeLoaderPromise;
1437
+ }
1367
1438
  function LiveCamera({ onCapture, onCancel, message }) {
1368
1439
  const videoRef = React.useRef(null);
1369
1440
  const streamRef = React.useRef(null);
1370
1441
  const [ready, setReady] = React.useState(false);
1371
1442
  const [error, setError] = React.useState(null);
1443
+ const [faceStatus, setFaceStatus] = React.useState("idle");
1444
+ const [faceDetectionFailed, setFaceDetectionFailed] = React.useState(false);
1372
1445
  React.useEffect(() => {
1373
1446
  let cancelled = false;
1374
1447
  async function init() {
@@ -1409,6 +1482,77 @@ function LiveCamera({ onCapture, onCancel, message }) {
1409
1482
  streamRef.current = null;
1410
1483
  };
1411
1484
  }, []);
1485
+ React.useEffect(() => {
1486
+ if (!ready) {
1487
+ setFaceStatus("idle");
1488
+ return;
1489
+ }
1490
+ let cancelled = false;
1491
+ let rafId = null;
1492
+ let detector = null;
1493
+ setFaceStatus("loading");
1494
+ setFaceDetectionFailed(false);
1495
+ (async () => {
1496
+ try {
1497
+ const FaceDetection = await loadMediaPipeFaceDetection();
1498
+ if (cancelled) return;
1499
+ const d = new FaceDetection({
1500
+ locateFile: (f) => `${MEDIAPIPE_BASE}/${f}`
1501
+ });
1502
+ d.setOptions({ model: "short", minDetectionConfidence: 0.5 });
1503
+ d.onResults((results) => {
1504
+ if (cancelled) return;
1505
+ const detections = results.detections ?? [];
1506
+ let next;
1507
+ if (detections.length === 0) {
1508
+ next = "scanning";
1509
+ } else {
1510
+ const bb = detections[0].boundingBox;
1511
+ if (bb.width < 0.18) next = "too_far";
1512
+ else if (bb.width > 0.72) next = "too_close";
1513
+ else if (bb.xCenter < 0.28 || bb.xCenter > 0.72 || bb.yCenter < 0.28 || bb.yCenter > 0.72) {
1514
+ next = "off_center";
1515
+ } else {
1516
+ next = "ok";
1517
+ }
1518
+ }
1519
+ setFaceStatus(next);
1520
+ });
1521
+ await d.initialize();
1522
+ if (cancelled) return;
1523
+ detector = d;
1524
+ setFaceStatus("scanning");
1525
+ const tick = async () => {
1526
+ if (cancelled) return;
1527
+ const v = videoRef.current;
1528
+ if (v?.readyState === 4 && detector) {
1529
+ try {
1530
+ await detector.send({ image: v });
1531
+ } catch {
1532
+ }
1533
+ }
1534
+ if (!cancelled) {
1535
+ rafId = requestAnimationFrame(tick);
1536
+ }
1537
+ };
1538
+ tick();
1539
+ } catch (err) {
1540
+ console.error("Face detection failed to initialize:", err);
1541
+ if (!cancelled) {
1542
+ setFaceDetectionFailed(true);
1543
+ setFaceStatus("scanning");
1544
+ }
1545
+ }
1546
+ })();
1547
+ return () => {
1548
+ cancelled = true;
1549
+ if (rafId !== null) cancelAnimationFrame(rafId);
1550
+ try {
1551
+ detector?.close?.();
1552
+ } catch {
1553
+ }
1554
+ };
1555
+ }, [ready]);
1412
1556
  const capture = () => {
1413
1557
  const video = videoRef.current;
1414
1558
  if (!video || !video.videoWidth) return;
@@ -1430,7 +1574,17 @@ function LiveCamera({ onCapture, onCancel, message }) {
1430
1574
  if (error) {
1431
1575
  return /* @__PURE__ */ React__default.default.createElement("div", { style: { ...contentStyle, alignItems: "center", textAlign: "center", padding: 0 } }, /* @__PURE__ */ React__default.default.createElement("div", { style: errorCircleStyle }, "!"), /* @__PURE__ */ React__default.default.createElement("h3", { style: titleStyle }, "Camera unavailable"), /* @__PURE__ */ React__default.default.createElement("p", { style: alertTextStyle }, error), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Back")));
1432
1576
  }
1433
- return /* @__PURE__ */ React__default.default.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 12 } }, /* @__PURE__ */ React__default.default.createElement("div", { style: videoFrameStyle }, /* @__PURE__ */ React__default.default.createElement(
1577
+ const detectionActive = ready && !faceDetectionFailed;
1578
+ const liveMessage = detectionActive ? FACE_MESSAGES[faceStatus] : message;
1579
+ const liveFaceColor = detectionActive ? FACE_COLORS[faceStatus] : void 0;
1580
+ const captureDisabled = !ready || detectionActive && faceStatus !== "ok";
1581
+ const ringPulse = detectionActive && (faceStatus === "too_far" || faceStatus === "too_close") ? "pulse 1.5s ease-in-out infinite" : void 0;
1582
+ const dynamicCircleStyle = {
1583
+ ...faceCircleStyle,
1584
+ ...liveFaceColor ? { borderColor: liveFaceColor } : null,
1585
+ ...ringPulse ? { animation: ringPulse } : null
1586
+ };
1587
+ return /* @__PURE__ */ React__default.default.createElement("div", { style: videoFrameStyle }, /* @__PURE__ */ React__default.default.createElement(
1434
1588
  "video",
1435
1589
  {
1436
1590
  ref: videoRef,
@@ -1439,24 +1593,87 @@ function LiveCamera({ onCapture, onCancel, message }) {
1439
1593
  autoPlay: true,
1440
1594
  style: videoStyle
1441
1595
  }
1442
- ), /* @__PURE__ */ React__default.default.createElement("div", { style: faceOverlayContainerStyle, "aria-hidden": "true" }, /* @__PURE__ */ React__default.default.createElement("div", { style: faceRingWrapStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: faceDashedRingStyle }), /* @__PURE__ */ React__default.default.createElement("div", { style: faceCircleStyle }))), message && /* @__PURE__ */ React__default.default.createElement("div", { style: liveMessagePillContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: liveMessagePillStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: liveMessagePillTextStyle }, message))), !ready && /* @__PURE__ */ React__default.default.createElement("div", { style: videoLoadingStyle }, "Starting camera\u2026")), /* @__PURE__ */ React__default.default.createElement("div", { style: buttonsContainerStyle }, /* @__PURE__ */ React__default.default.createElement(
1596
+ ), /* @__PURE__ */ React__default.default.createElement("div", { style: faceOverlayContainerStyle, "aria-hidden": "true" }, /* @__PURE__ */ React__default.default.createElement("div", { style: faceRingWrapStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: faceDashedRingStyle }), /* @__PURE__ */ React__default.default.createElement("div", { style: dynamicCircleStyle }))), liveMessage && /* @__PURE__ */ React__default.default.createElement("div", { style: liveMessagePillContainerStyle }, /* @__PURE__ */ React__default.default.createElement("div", { style: liveMessagePillStyle }, /* @__PURE__ */ React__default.default.createElement("span", { style: liveMessagePillTextStyle }, liveMessage))), /* @__PURE__ */ React__default.default.createElement(
1443
1597
  "button",
1444
1598
  {
1445
- style: ready ? primaryButtonStyle : { ...primaryButtonStyle, opacity: 0.5, cursor: "not-allowed" },
1599
+ type: "button",
1600
+ onClick: onCancel,
1601
+ "aria-label": "Close camera",
1602
+ style: overlayCloseButtonStyle
1603
+ },
1604
+ /* @__PURE__ */ React__default.default.createElement("img", { src: Close, alt: "", width: 16, height: 16 })
1605
+ ), !ready && /* @__PURE__ */ React__default.default.createElement("div", { style: videoLoadingStyle }, "Starting camera\u2026"), /* @__PURE__ */ React__default.default.createElement("div", { style: bottomGradientStyle }, /* @__PURE__ */ React__default.default.createElement(
1606
+ "button",
1607
+ {
1608
+ type: "button",
1446
1609
  onClick: capture,
1447
- disabled: !ready
1610
+ disabled: captureDisabled,
1611
+ "aria-label": "Capture",
1612
+ style: captureDisabled ? { ...shutterButtonStyle, opacity: 0.5, cursor: "not-allowed" } : shutterButtonStyle
1448
1613
  },
1449
- /* @__PURE__ */ React__default.default.createElement("img", { src: Camera, alt: "", width: 20, height: 20 }),
1450
- /* @__PURE__ */ React__default.default.createElement("span", null, "Capture")
1451
- ), /* @__PURE__ */ React__default.default.createElement("button", { style: secondaryButtonStyle, onClick: onCancel }, "Cancel")));
1614
+ /* @__PURE__ */ React__default.default.createElement("span", { style: shutterInnerStyle })
1615
+ )));
1452
1616
  }
1453
1617
  var videoFrameStyle = {
1454
1618
  position: "relative",
1455
1619
  width: "100%",
1456
- aspectRatio: "1 / 1",
1457
- borderRadius: 12,
1620
+ aspectRatio: "3 / 4",
1621
+ borderRadius: 24,
1458
1622
  overflow: "hidden",
1459
- background: "#0f172a"
1623
+ background: "#000000",
1624
+ border: "4px solid rgba(255, 255, 255, 0.08)",
1625
+ boxShadow: "0 25px 50px -12px rgba(0, 0, 0, 0.5)"
1626
+ };
1627
+ var overlayCloseButtonStyle = {
1628
+ position: "absolute",
1629
+ top: 12,
1630
+ right: 12,
1631
+ width: 32,
1632
+ height: 32,
1633
+ display: "inline-flex",
1634
+ alignItems: "center",
1635
+ justifyContent: "center",
1636
+ padding: 0,
1637
+ border: "none",
1638
+ borderRadius: "50%",
1639
+ background: "rgba(0, 0, 0, 0.4)",
1640
+ backdropFilter: "blur(6px)",
1641
+ color: "#ffffff",
1642
+ cursor: "pointer",
1643
+ zIndex: 10
1644
+ };
1645
+ var bottomGradientStyle = {
1646
+ position: "absolute",
1647
+ bottom: 0,
1648
+ left: 0,
1649
+ right: 0,
1650
+ padding: "16px",
1651
+ background: "linear-gradient(to top, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 100%)",
1652
+ display: "flex",
1653
+ alignItems: "center",
1654
+ justifyContent: "center",
1655
+ zIndex: 10
1656
+ };
1657
+ var shutterButtonStyle = {
1658
+ width: 64,
1659
+ height: 64,
1660
+ borderRadius: "50%",
1661
+ background: "rgba(255, 255, 255, 0.15)",
1662
+ backdropFilter: "blur(6px)",
1663
+ border: "2px solid rgba(255, 255, 255, 0.7)",
1664
+ display: "inline-flex",
1665
+ alignItems: "center",
1666
+ justifyContent: "center",
1667
+ cursor: "pointer",
1668
+ padding: 0,
1669
+ transition: "transform 120ms ease-out, background 120ms"
1670
+ };
1671
+ var shutterInnerStyle = {
1672
+ display: "block",
1673
+ width: 44,
1674
+ height: 44,
1675
+ borderRadius: "50%",
1676
+ background: "rgba(255, 255, 255, 0.85)"
1460
1677
  };
1461
1678
  var videoStyle = {
1462
1679
  width: "100%",
@@ -1500,7 +1717,9 @@ var videoLoadingStyle = {
1500
1717
  justifyContent: "center",
1501
1718
  color: "#e5e7eb",
1502
1719
  fontSize: 14,
1503
- fontWeight: 500
1720
+ fontWeight: 500,
1721
+ background: "rgba(0, 0, 0, 0.4)",
1722
+ zIndex: 5
1504
1723
  };
1505
1724
  var liveMessagePillContainerStyle = {
1506
1725
  position: "absolute",
@@ -1884,6 +2103,7 @@ function useReuseKYCSubmission(client$1) {
1884
2103
  session,
1885
2104
  defaultDevice: opts.defaultDevice,
1886
2105
  renderQR: opts.renderQR,
2106
+ onCancel: opts.onCancel,
1887
2107
  onComplete: (result) => {
1888
2108
  cleanup();
1889
2109
  resolve(result);