@retinalabsllc/zairusjs 15.1.45 → 15.1.55

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/index.d.mts CHANGED
@@ -71,16 +71,19 @@ interface PipleAuthProps {
71
71
  r_c?: string;
72
72
  mode: AuthMode;
73
73
  recaptchaToken?: string;
74
+ forceOtp?: boolean;
74
75
  }) => Promise<{
75
76
  success: boolean;
76
77
  error?: string;
77
78
  hasPasskey?: boolean;
78
79
  needsPukSetup?: boolean;
80
+ hasPuk?: boolean;
79
81
  passkeyOptions?: any;
80
82
  }>;
81
83
  onVerifyOtp: (payload: {
82
84
  phoneNumber: string;
83
85
  code?: string;
86
+ passcode?: string;
84
87
  isPasskey?: boolean;
85
88
  passkeyCredential?: any;
86
89
  }) => Promise<{
package/dist/index.d.ts CHANGED
@@ -71,16 +71,19 @@ interface PipleAuthProps {
71
71
  r_c?: string;
72
72
  mode: AuthMode;
73
73
  recaptchaToken?: string;
74
+ forceOtp?: boolean;
74
75
  }) => Promise<{
75
76
  success: boolean;
76
77
  error?: string;
77
78
  hasPasskey?: boolean;
78
79
  needsPukSetup?: boolean;
80
+ hasPuk?: boolean;
79
81
  passkeyOptions?: any;
80
82
  }>;
81
83
  onVerifyOtp: (payload: {
82
84
  phoneNumber: string;
83
85
  code?: string;
86
+ passcode?: string;
84
87
  isPasskey?: boolean;
85
88
  passkeyCredential?: any;
86
89
  }) => Promise<{
package/dist/index.js CHANGED
@@ -488,27 +488,10 @@ var SUPPORTED_COUNTRIES = [
488
488
  var InputSpinner2 = () => /* @__PURE__ */ import_react7.default.createElement("svg", { className: "animate-spin h-4 w-4 text-neutral-400", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24" }, /* @__PURE__ */ import_react7.default.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), /* @__PURE__ */ import_react7.default.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" }));
489
489
  var CountrySelectorDialog = ({ isOpen, onClose, onSelect }) => {
490
490
  if (!isOpen) return null;
491
- return /* @__PURE__ */ import_react7.default.createElement("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "absolute inset-0", onClick: onClose }), /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-full max-w-sm bg-white rounded-2xl shadow-2xl overflow-hidden relative z-10 animate-in fade-in zoom-in-95 duration-200" }, /* @__PURE__ */ import_react7.default.createElement(
492
- "button",
493
- {
494
- onClick: onClose,
495
- className: "absolute top-4 right-4 p-2 text-neutral-700 hover:text-neutral-400 transition-colors outline-none"
496
- },
497
- /* @__PURE__ */ import_react7.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react7.default.createElement("path", { d: "M18 6 6 18" }), /* @__PURE__ */ import_react7.default.createElement("path", { d: "m6 6 12 12" }))
498
- ), /* @__PURE__ */ import_react7.default.createElement("div", { className: "p-6 sm:p-8" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "mb-6 mt-2" }, /* @__PURE__ */ import_react7.default.createElement("h2", { className: "text-xl text-black tracking-tight mb-2" }, "Select Country"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-[13px] text-neutral-500 leading-relaxed" }, "Choose your region to continue.")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex flex-col gap-2" }, SUPPORTED_COUNTRIES.map((c) => /* @__PURE__ */ import_react7.default.createElement(
499
- "button",
500
- {
501
- key: c.code,
502
- onClick: () => {
503
- onSelect(c);
504
- onClose();
505
- },
506
- className: "flex items-center gap-4 p-3 rounded-xl hover:bg-neutral-50 transition-colors text-left outline-none"
507
- },
508
- /* @__PURE__ */ import_react7.default.createElement(FlagImage, { countryCode: c.code }),
509
- /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-sm text-black flex-1 " }, c.name),
510
- /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-sm text-neutral-400 " }, c.dialCode)
511
- ))))));
491
+ return /* @__PURE__ */ import_react7.default.createElement("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "absolute inset-0", onClick: onClose }), /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-full max-w-sm bg-white rounded-2xl shadow-2xl overflow-hidden relative z-10 animate-in fade-in zoom-in-95 duration-200" }, /* @__PURE__ */ import_react7.default.createElement("button", { onClick: onClose, className: "absolute top-4 right-4 p-2 text-neutral-700 hover:text-neutral-400 transition-colors outline-none" }, /* @__PURE__ */ import_react7.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ import_react7.default.createElement("path", { d: "M18 6 6 18" }), /* @__PURE__ */ import_react7.default.createElement("path", { d: "m6 6 12 12" }))), /* @__PURE__ */ import_react7.default.createElement("div", { className: "p-6 sm:p-8" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "mb-6 mt-2" }, /* @__PURE__ */ import_react7.default.createElement("h2", { className: "text-xl text-black tracking-tight mb-2" }, "Select Country"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-[13px] text-neutral-500 leading-relaxed" }, "Choose your region to continue.")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex flex-col gap-2" }, SUPPORTED_COUNTRIES.map((c) => /* @__PURE__ */ import_react7.default.createElement("button", { key: c.code, onClick: () => {
492
+ onSelect(c);
493
+ onClose();
494
+ }, className: "flex items-center gap-4 p-3 rounded-xl hover:bg-neutral-50 transition-colors text-left outline-none" }, /* @__PURE__ */ import_react7.default.createElement(FlagImage, { countryCode: c.code }), /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-sm text-black flex-1 " }, c.name), /* @__PURE__ */ import_react7.default.createElement("span", { className: "text-sm text-neutral-400 " }, c.dialCode)))))));
512
495
  };
513
496
  function AuthFormInner({
514
497
  companyName,
@@ -546,7 +529,9 @@ function AuthFormInner({
546
529
  const [userHasPasskey, setUserHasPasskey] = (0, import_react7.useState)(false);
547
530
  const [authOptions, setAuthOptions] = (0, import_react7.useState)(null);
548
531
  const [otp, setOtp] = (0, import_react7.useState)(["", "", "", "", "", ""]);
549
- const inputRefs = (0, import_react7.useRef)([]);
532
+ const [passcode, setPasscode] = (0, import_react7.useState)(["", "", "", "", "", ""]);
533
+ const otpRefs = (0, import_react7.useRef)([]);
534
+ const passRefs = (0, import_react7.useRef)([]);
550
535
  const cleanAlpha = (val) => val.replace(/[^a-zA-Z]/g, "").substring(0, 50);
551
536
  const cleanOrgName = (val) => val.replace(/[^a-zA-Z0-9\s]/g, "").substring(0, 50);
552
537
  (0, import_react7.useEffect)(() => {
@@ -555,40 +540,44 @@ function AuthFormInner({
555
540
  return () => clearTimeout(timer);
556
541
  }
557
542
  }, [countdown]);
558
- const handleOtpChange = (index, value) => {
543
+ const handleArrayChange = (index, value, isPasscode) => {
559
544
  const cleanValue = value.replace(/[^0-9]/g, "");
560
545
  if (!cleanValue && value !== "") return;
561
- const newOtp = [...otp];
562
- newOtp[index] = cleanValue.slice(-1);
563
- setOtp(newOtp);
564
- if (cleanValue && index < 5) inputRefs.current[index + 1]?.focus();
546
+ const arr = isPasscode ? [...passcode] : [...otp];
547
+ const setArr = isPasscode ? setPasscode : setOtp;
548
+ const refs = isPasscode ? passRefs : otpRefs;
549
+ arr[index] = cleanValue.slice(-1);
550
+ setArr(arr);
551
+ if (cleanValue && index < 5) refs.current[index + 1]?.focus();
565
552
  };
566
- const handlePaste = (e) => {
553
+ const handleArrayPaste = (e, isPasscode) => {
567
554
  e.preventDefault();
568
555
  const pastedData = e.clipboardData.getData("text/plain");
569
556
  const numbersOnly = pastedData.replace(/[^0-9]/g, "");
570
557
  if (!numbersOnly) return;
571
- const newOtp = [...otp];
558
+ const arr = isPasscode ? [...passcode] : [...otp];
559
+ const setArr = isPasscode ? setPasscode : setOtp;
560
+ const refs = isPasscode ? passRefs : otpRefs;
572
561
  const length = Math.min(numbersOnly.length, 6);
573
- for (let i = 0; i < length; i++) newOtp[i] = numbersOnly[i];
574
- setOtp(newOtp);
575
- if (length < 6) inputRefs.current[length]?.focus();
562
+ for (let i = 0; i < length; i++) arr[i] = numbersOnly[i];
563
+ setArr(arr);
564
+ if (length < 6) refs.current[length]?.focus();
576
565
  else {
577
- inputRefs.current[5]?.focus();
578
- verifyOtpCode(newOtp.join(""));
566
+ refs.current[5]?.focus();
567
+ if (isPasscode) verifyPasscodeSubmit(arr.join(""));
568
+ else verifyOtpCode(arr.join(""));
579
569
  }
580
570
  };
581
571
  const getFormattedPhoneNumber = () => {
582
572
  const cleanNumber = phoneNumber.replace(/^0+/, "");
583
573
  return `${selectedCountry.dialCode}${cleanNumber}`;
584
574
  };
585
- const handleAuthRequestSubmit = async (e) => {
586
- if (e) e.preventDefault();
575
+ const handleAuthRequestSubmit = async (forceOtp = false) => {
587
576
  if (mode === "SIGNUP" && !agreedToTerms) {
588
577
  import_react_hot_toast.default.error("You must agree to the Terms and Privacy Policy.");
589
578
  return;
590
579
  }
591
- if (isSubmitting || countdown > 0) return;
580
+ if (isSubmitting) return;
592
581
  setIsSubmitting(true);
593
582
  try {
594
583
  let recaptchaToken = void 0;
@@ -603,14 +592,21 @@ function AuthFormInner({
603
592
  organizationName: mode === "SIGNUP" && requireOrganization ? orgName : void 0,
604
593
  r_c: mode === "SIGNUP" && invitationCode.trim() ? invitationCode.trim().toUpperCase() : void 0,
605
594
  mode,
606
- recaptchaToken
595
+ recaptchaToken,
596
+ forceOtp
607
597
  });
608
598
  if (res.success) {
609
599
  setUserHasPasskey(!!res.hasPasskey);
610
600
  if (res.passkeyOptions) setAuthOptions(res.passkeyOptions);
611
- import_react_hot_toast.default.success(res.needsPukSetup ? "Verification code sent" : "Security check required");
612
- setStep("OTP");
613
- setCountdown(60);
601
+ if (res.hasPuk && !forceOtp) {
602
+ setStep("PASSCODE");
603
+ setTimeout(() => passRefs.current[0]?.focus(), 100);
604
+ } else {
605
+ setStep("OTP");
606
+ setCountdown(60);
607
+ import_react_hot_toast.default.success("Verification code sent");
608
+ setTimeout(() => otpRefs.current[0]?.focus(), 100);
609
+ }
614
610
  } else {
615
611
  import_react_hot_toast.default.error(res.error || "Authentication failed.");
616
612
  }
@@ -624,16 +620,29 @@ function AuthFormInner({
624
620
  if (codeToVerify.length !== 6 || isSubmitting) return;
625
621
  setIsSubmitting(true);
626
622
  try {
627
- const res = await onVerifyOtp({
628
- phoneNumber: getFormattedPhoneNumber(),
629
- code: codeToVerify
630
- });
631
- if (res.success) {
632
- window.location.href = res.redirect || redirectUrl;
633
- } else {
623
+ const res = await onVerifyOtp({ phoneNumber: getFormattedPhoneNumber(), code: codeToVerify });
624
+ if (res.success) window.location.href = res.redirect || redirectUrl;
625
+ else {
634
626
  import_react_hot_toast.default.error(res.error || "Invalid code.");
635
627
  setOtp(["", "", "", "", "", ""]);
636
- inputRefs.current[0]?.focus();
628
+ otpRefs.current[0]?.focus();
629
+ }
630
+ } catch {
631
+ import_react_hot_toast.default.error("Verification failed.");
632
+ } finally {
633
+ setIsSubmitting(false);
634
+ }
635
+ };
636
+ const verifyPasscodeSubmit = async (passToVerify) => {
637
+ if (passToVerify.length !== 6 || isSubmitting) return;
638
+ setIsSubmitting(true);
639
+ try {
640
+ const res = await onVerifyOtp({ phoneNumber: getFormattedPhoneNumber(), passcode: passToVerify });
641
+ if (res.success) window.location.href = res.redirect || redirectUrl;
642
+ else {
643
+ import_react_hot_toast.default.error(res.error || "Invalid passcode.");
644
+ setPasscode(["", "", "", "", "", ""]);
645
+ passRefs.current[0]?.focus();
637
646
  }
638
647
  } catch {
639
648
  import_react_hot_toast.default.error("Verification failed.");
@@ -646,16 +655,9 @@ function AuthFormInner({
646
655
  setIsSubmitting(true);
647
656
  try {
648
657
  const credentialResponse = await (0, import_browser.startAuthentication)({ optionsJSON: authOptions });
649
- const res = await onVerifyOtp({
650
- phoneNumber: getFormattedPhoneNumber(),
651
- isPasskey: true,
652
- passkeyCredential: credentialResponse
653
- });
654
- if (res.success) {
655
- window.location.href = res.redirect || redirectUrl;
656
- } else {
657
- import_react_hot_toast.default.error(res.error || "Device verification failed.");
658
- }
658
+ const res = await onVerifyOtp({ phoneNumber: getFormattedPhoneNumber(), isPasskey: true, passkeyCredential: credentialResponse });
659
+ if (res.success) window.location.href = res.redirect || redirectUrl;
660
+ else import_react_hot_toast.default.error(res.error || "Device verification failed.");
659
661
  } catch (err) {
660
662
  if (err.name !== "NotAllowedError") import_react_hot_toast.default.error("Passkey verification interrupted.");
661
663
  } finally {
@@ -683,14 +685,7 @@ function AuthFormInner({
683
685
  }
684
686
  return false;
685
687
  };
686
- return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(
687
- CountrySelectorDialog,
688
- {
689
- isOpen: isCountryDialogOpen,
690
- onClose: () => setIsCountryDialogOpen(false),
691
- onSelect: setSelectedCountry
692
- }
693
- ), /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-full max-w-md mx-auto flex flex-col items-center gap-4 relative z-10 animate-in fade-in duration-300" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "p-6" }, step === "INPUT" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "mb-12 mt-2 text-left" }, /* @__PURE__ */ import_react7.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, "Don't have an account? ", /* @__PURE__ */ import_react7.default.createElement("button", { type: "button", onClick: () => {
688
+ return /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, /* @__PURE__ */ import_react7.default.createElement(CountrySelectorDialog, { isOpen: isCountryDialogOpen, onClose: () => setIsCountryDialogOpen(false), onSelect: setSelectedCountry }), /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-full max-w-md mx-auto flex flex-col items-center gap-4 relative z-10 animate-in fade-in duration-300" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "p-6" }, step === "INPUT" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "mb-12 mt-2 text-left" }, /* @__PURE__ */ import_react7.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, "Don't have an account? ", /* @__PURE__ */ import_react7.default.createElement("button", { type: "button", onClick: () => {
694
689
  setMode("SIGNUP");
695
690
  setSignupStep(getInitialSignupStep());
696
691
  }, className: "text-black transition-colors ml-1 " }, "Sign up")) : /* @__PURE__ */ import_react7.default.createElement(import_react7.default.Fragment, null, "Already have an account? ", /* @__PURE__ */ import_react7.default.createElement("button", { type: "button", onClick: () => setMode("LOGIN"), className: "text-black transition-colors ml-1 " }, "Log in")))), /* @__PURE__ */ import_react7.default.createElement("form", { className: "space-y-6", autoComplete: "off", onSubmit: (e) => {
@@ -698,7 +693,7 @@ function AuthFormInner({
698
693
  if (mode === "SIGNUP" && signupStep === "NAME") handleNextSignupStep();
699
694
  else if (mode === "SIGNUP" && signupStep === "ORGANIZATION") handleNextSignupStep();
700
695
  else if (mode === "SIGNUP" && signupStep === "PHONE_ID") handleNextSignupStep();
701
- else handleAuthRequestSubmit();
696
+ else handleAuthRequestSubmit(false);
702
697
  } }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex flex-col gap-6 animate-in fade-in slide-in-from-right-4 duration-300" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react7.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ import_react7.default.createElement("input", { type: "text", value: firstName, onChange: (e) => setFirstName(cleanAlpha(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300", placeholder: "First name" })), /* @__PURE__ */ import_react7.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react7.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ import_react7.default.createElement("input", { type: "text", value: lastName, onChange: (e) => setLastName(cleanAlpha(e.target.value)), required: true, className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Last name" }))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex flex-col gap-6 animate-in fade-in slide-in-from-right-4 duration-300" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react7.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ import_react7.default.createElement("input", { type: "text", value: orgName, onChange: (e) => setOrgName(cleanOrgName(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Acme Corporation" }))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "PHONE_ID") && /* @__PURE__ */ import_react7.default.createElement("div", { className: "space-y-6 animate-in fade-in slide-in-from-right-4 duration-300" }, /* @__PURE__ */ import_react7.default.createElement(
703
698
  PhoneInput,
704
699
  {
@@ -719,35 +714,55 @@ function AuthFormInner({
719
714
  className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300 tracking-widest",
720
715
  placeholder: "Referral Code (Optional)"
721
716
  }
722
- ))), /* @__PURE__ */ import_react7.default.createElement(ThreeDActionButton, { type: "submit", disabled: isContinueDisabled(), isLoading: isSubmitting, className: "w-full mt-10" }, mode === "SIGNUP" && signupStep === "INVITATION" ? "Complete Setup" : "Continue"))), step === "OTP" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-left mb-10 mt-2" }, /* @__PURE__ */ import_react7.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter your 6-digit verification code.")), /* @__PURE__ */ import_react7.default.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
717
+ ))), /* @__PURE__ */ import_react7.default.createElement(ThreeDActionButton, { type: "submit", disabled: isContinueDisabled(), isLoading: isSubmitting, className: "w-full mt-10" }, mode === "SIGNUP" && signupStep === "INVITATION" ? "Complete Setup" : "Continue"))), step === "PASSCODE" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-left mb-10 mt-2" }, /* @__PURE__ */ import_react7.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Enter Passcode"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter your secure 6-digit transaction passcode.")), /* @__PURE__ */ import_react7.default.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
718
+ e.preventDefault();
719
+ verifyPasscodeSubmit(passcode.join(""));
720
+ } }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex justify-between gap-2", onPaste: (e) => handleArrayPaste(e, true) }, passcode.map((digit, index) => /* @__PURE__ */ import_react7.default.createElement(
721
+ "input",
722
+ {
723
+ key: index,
724
+ ref: (el) => {
725
+ passRefs.current[index] = el;
726
+ },
727
+ type: "password",
728
+ inputMode: "numeric",
729
+ maxLength: 1,
730
+ value: digit,
731
+ onChange: (e) => handleArrayChange(index, e.target.value, true),
732
+ onKeyDown: (e) => {
733
+ if (e.key === "Backspace" && !passcode[index] && index > 0) passRefs.current[index - 1]?.focus();
734
+ },
735
+ className: "w-7 h-10 text-center text-lg bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300"
736
+ }
737
+ ))), /* @__PURE__ */ import_react7.default.createElement(ThreeDActionButton, { type: "submit", disabled: passcode.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Login")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "pt-6 flex flex-col gap-3" }, userHasPasskey && /* @__PURE__ */ import_react7.default.createElement("button", { onClick: triggerPasskeyLogin, disabled: isSubmitting, className: "w-full py-2.5 rounded-full text-black border border-neutral-200 hover:bg-neutral-50 text-[11px] tracking-wide transition-colors outline-none disabled:opacity-50" }, "Use Passkey / Biometrics"), /* @__PURE__ */ import_react7.default.createElement("button", { onClick: () => handleAuthRequestSubmit(true), disabled: isSubmitting, className: "w-full text-center text-[12px] text-neutral-500 hover:text-black transition-colors outline-none mt-2" }, "Use OTP instead"))), step === "OTP" && /* @__PURE__ */ import_react7.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "text-left mb-10 mt-2" }, /* @__PURE__ */ import_react7.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ import_react7.default.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter your 6-digit verification code.")), /* @__PURE__ */ import_react7.default.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
723
738
  e.preventDefault();
724
739
  verifyOtpCode(otp.join(""));
725
- } }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex justify-between gap-2", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ import_react7.default.createElement(
740
+ } }, /* @__PURE__ */ import_react7.default.createElement("div", { className: "flex justify-between gap-2", onPaste: (e) => handleArrayPaste(e, false) }, otp.map((digit, index) => /* @__PURE__ */ import_react7.default.createElement(
726
741
  "input",
727
742
  {
728
743
  key: index,
729
744
  ref: (el) => {
730
- inputRefs.current[index] = el;
745
+ otpRefs.current[index] = el;
731
746
  },
732
747
  type: "text",
733
748
  inputMode: "numeric",
734
749
  maxLength: 1,
735
750
  value: digit,
736
- onChange: (e) => handleOtpChange(index, e.target.value),
751
+ onChange: (e) => handleArrayChange(index, e.target.value, false),
737
752
  onKeyDown: (e) => {
738
- if (e.key === "Backspace" && !otp[index] && index > 0) inputRefs.current[index - 1]?.focus();
753
+ if (e.key === "Backspace" && !otp[index] && index > 0) otpRefs.current[index - 1]?.focus();
739
754
  },
740
- className: "w-7 h-12 text-center text-sm bg-transparent border-b-2 border-neutral-200 text-black outline-none focus:border-black transition-all duration-300"
755
+ className: "w-7 h-10 text-center text-sm bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300"
741
756
  }
742
- ))), /* @__PURE__ */ import_react7.default.createElement(ThreeDActionButton, { type: "submit", disabled: otp.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Verify Code")), userHasPasskey && /* @__PURE__ */ import_react7.default.createElement("div", { className: "pt-6" }, /* @__PURE__ */ import_react7.default.createElement(
757
+ ))), /* @__PURE__ */ import_react7.default.createElement(ThreeDActionButton, { type: "submit", disabled: otp.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Verify Code")), /* @__PURE__ */ import_react7.default.createElement("div", { className: "pt-6 flex flex-col gap-3" }, /* @__PURE__ */ import_react7.default.createElement(
743
758
  "button",
744
759
  {
745
- onClick: triggerPasskeyLogin,
746
- disabled: isSubmitting,
747
- className: "w-full py-2.5 rounded-full text-black border border-neutral-200 hover:bg-neutral-50 text-xs tracking-wide transition-colors outline-none disabled:opacity-50"
760
+ onClick: () => handleAuthRequestSubmit(true),
761
+ disabled: countdown > 0 || isSubmitting,
762
+ className: "w-full text-center text-[12px] text-neutral-500 hover:text-black transition-colors outline-none disabled:opacity-50"
748
763
  },
749
- "Use Passkey / Biometrics"
750
- )))))));
764
+ countdown > 0 ? `Resend code in ${countdown}s` : "Resend OTP"
765
+ ), userHasPasskey && /* @__PURE__ */ import_react7.default.createElement("button", { onClick: triggerPasskeyLogin, disabled: isSubmitting, className: "w-full py-2.5 rounded-full text-black border border-neutral-200 hover:bg-neutral-50 text-[11px] tracking-wide transition-colors outline-none disabled:opacity-50 mt-4" }, "Use Passkey / Biometrics")))))));
751
766
  }
752
767
  var PipleAuth = (props) => {
753
768
  if (props.useRecaptcha && props.recaptchaSiteKey) {
@@ -1633,7 +1648,7 @@ var UniversalHomeView = ({
1633
1648
  const [isAvatarLoaded, setIsAvatarLoaded] = (0, import_react25.useState)(false);
1634
1649
  const [showNotifDialog, setShowNotifDialog] = (0, import_react25.useState)(false);
1635
1650
  const [selectedNotif, setSelectedNotif] = (0, import_react25.useState)(null);
1636
- const fallbackAvatarUrl = `/api/avatar/?name=${encodeURIComponent(fullName || " ")}&background=ffffff&color=000000&size=200&font-size=0.33`;
1651
+ const fallbackAvatarUrl = `/api/avatar/?name=${encodeURIComponent(fullName || " ")}&background=ffffff&color=000000&size=100&font-size=0.33`;
1637
1652
  const finalAvatarSrc = avatarSrc || fallbackAvatarUrl;
1638
1653
  const hasUnreadNotifs = notifications.some((n) => !n.isRead);
1639
1654
  return /* @__PURE__ */ import_react25.default.createElement("div", { className: "w-full max-w-3xl mx-auto flex flex-col animate-in fade-in duration-300 relative" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "flex items-center justify-between w-full pb-4 " }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "w-10 h-10 rounded-full bg-white p-0.75 flex items-center justify-center cursor-pointer", onClick: () => window.location.href = "/app/account/me" }, /* @__PURE__ */ import_react25.default.createElement("div", { className: "relative w-full h-full rounded-full overflow-hidden bg-neutral-100 flex items-center justify-center" }, !isAvatarLoaded && /* @__PURE__ */ import_react25.default.createElement("div", { className: "absolute inset-0 flex items-center justify-center z-10 bg-neutral-100" }, /* @__PURE__ */ import_react25.default.createElement(import_react26.HugeiconsIcon, { icon: import_core_free_icons8.Loading03Icon, size: 16, className: "animate-spin text-black" })), /* @__PURE__ */ import_react25.default.createElement(
@@ -3855,7 +3870,7 @@ var formatWeb3Name = (name) => {
3855
3870
  var PageSpinner4 = () => /* @__PURE__ */ import_react67.default.createElement("div", { className: "flex justify-center items-center py-12 w-full" }, /* @__PURE__ */ import_react67.default.createElement(import_react68.HugeiconsIcon, { icon: import_core_free_icons22.Loading03Icon, size: 32, className: "animate-spin mb-4 text-black" }));
3856
3871
  var AvatarLogo = ({ src, alt, name, sizeClass = "w-12 h-12" }) => {
3857
3872
  const [isLoaded, setIsLoaded] = (0, import_react67.useState)(false);
3858
- const fallbackUrl = `/api/avatar/?name=${encodeURIComponent(name || " ")}&background=ffffff&color=000000&size=200&font-size=0.33`;
3873
+ const fallbackUrl = `/api/avatar/?name=${encodeURIComponent(name || " ")}&background=ffffff&color=000000&size=100&font-size=0.33`;
3859
3874
  const finalSrc = src || fallbackUrl;
3860
3875
  return /* @__PURE__ */ import_react67.default.createElement("div", { className: `relative shrink-0 rounded-full bg-neutral-100 flex items-center justify-center overflow-hidden border-[3px] border-white ${sizeClass}` }, !isLoaded && /* @__PURE__ */ import_react67.default.createElement(import_react68.HugeiconsIcon, { icon: import_core_free_icons22.Loading03Icon, size: 16, className: "animate-spin text-neutral-400 absolute" }), /* @__PURE__ */ import_react67.default.createElement("img", { src: finalSrc, alt, onLoad: () => setIsLoaded(true), className: `w-full h-full object-cover transition-opacity duration-300 ${isLoaded ? "opacity-100" : "opacity-0"}` }));
3861
3876
  };
@@ -3989,7 +4004,7 @@ var UniversalReferralPage = ({
3989
4004
  };
3990
4005
  const AvatarLogo2 = ({ src, alt, name, sizeClass = "w-10 h-10" }) => {
3991
4006
  const [isLoaded, setIsLoaded] = (0, import_react69.useState)(false);
3992
- const fallbackUrl = `/api/avatar/?name=${encodeURIComponent(name || " ")}&background=e0e0e0&color=000000&size=200&font-size=0.33`;
4007
+ const fallbackUrl = `/api/avatar/?name=${encodeURIComponent(name || " ")}&background=e0e0e0&color=000000&size=100&font-size=0.33`;
3993
4008
  const finalSrc = src || fallbackUrl;
3994
4009
  return /* @__PURE__ */ import_react69.default.createElement("div", { className: `relative shrink-0 rounded-full bg-neutral-100 flex items-center justify-center overflow-hidden border-2 border-white ${sizeClass}` }, !isLoaded && /* @__PURE__ */ import_react69.default.createElement(import_react70.HugeiconsIcon, { icon: import_core_free_icons23.Loading03Icon, size: 16, className: "animate-spin text-neutral-400 absolute" }), /* @__PURE__ */ import_react69.default.createElement("img", { src: finalSrc, alt, onLoad: () => setIsLoaded(true), className: `w-full h-full object-cover transition-opacity duration-300 ${isLoaded ? "opacity-100" : "opacity-0"}` }));
3995
4010
  };
package/dist/index.mjs CHANGED
@@ -409,27 +409,10 @@ var SUPPORTED_COUNTRIES = [
409
409
  var InputSpinner2 = () => /* @__PURE__ */ React6.createElement("svg", { className: "animate-spin h-4 w-4 text-neutral-400", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24" }, /* @__PURE__ */ React6.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), /* @__PURE__ */ React6.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" }));
410
410
  var CountrySelectorDialog = ({ isOpen, onClose, onSelect }) => {
411
411
  if (!isOpen) return null;
412
- return /* @__PURE__ */ React6.createElement("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40" }, /* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0", onClick: onClose }), /* @__PURE__ */ React6.createElement("div", { className: "w-full max-w-sm bg-white rounded-2xl shadow-2xl overflow-hidden relative z-10 animate-in fade-in zoom-in-95 duration-200" }, /* @__PURE__ */ React6.createElement(
413
- "button",
414
- {
415
- onClick: onClose,
416
- className: "absolute top-4 right-4 p-2 text-neutral-700 hover:text-neutral-400 transition-colors outline-none"
417
- },
418
- /* @__PURE__ */ React6.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React6.createElement("path", { d: "M18 6 6 18" }), /* @__PURE__ */ React6.createElement("path", { d: "m6 6 12 12" }))
419
- ), /* @__PURE__ */ React6.createElement("div", { className: "p-6 sm:p-8" }, /* @__PURE__ */ React6.createElement("div", { className: "mb-6 mt-2" }, /* @__PURE__ */ React6.createElement("h2", { className: "text-xl text-black tracking-tight mb-2" }, "Select Country"), /* @__PURE__ */ React6.createElement("p", { className: "text-[13px] text-neutral-500 leading-relaxed" }, "Choose your region to continue.")), /* @__PURE__ */ React6.createElement("div", { className: "flex flex-col gap-2" }, SUPPORTED_COUNTRIES.map((c) => /* @__PURE__ */ React6.createElement(
420
- "button",
421
- {
422
- key: c.code,
423
- onClick: () => {
424
- onSelect(c);
425
- onClose();
426
- },
427
- className: "flex items-center gap-4 p-3 rounded-xl hover:bg-neutral-50 transition-colors text-left outline-none"
428
- },
429
- /* @__PURE__ */ React6.createElement(FlagImage, { countryCode: c.code }),
430
- /* @__PURE__ */ React6.createElement("span", { className: "text-sm text-black flex-1 " }, c.name),
431
- /* @__PURE__ */ React6.createElement("span", { className: "text-sm text-neutral-400 " }, c.dialCode)
432
- ))))));
412
+ return /* @__PURE__ */ React6.createElement("div", { className: "fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/40" }, /* @__PURE__ */ React6.createElement("div", { className: "absolute inset-0", onClick: onClose }), /* @__PURE__ */ React6.createElement("div", { className: "w-full max-w-sm bg-white rounded-2xl shadow-2xl overflow-hidden relative z-10 animate-in fade-in zoom-in-95 duration-200" }, /* @__PURE__ */ React6.createElement("button", { onClick: onClose, className: "absolute top-4 right-4 p-2 text-neutral-700 hover:text-neutral-400 transition-colors outline-none" }, /* @__PURE__ */ React6.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React6.createElement("path", { d: "M18 6 6 18" }), /* @__PURE__ */ React6.createElement("path", { d: "m6 6 12 12" }))), /* @__PURE__ */ React6.createElement("div", { className: "p-6 sm:p-8" }, /* @__PURE__ */ React6.createElement("div", { className: "mb-6 mt-2" }, /* @__PURE__ */ React6.createElement("h2", { className: "text-xl text-black tracking-tight mb-2" }, "Select Country"), /* @__PURE__ */ React6.createElement("p", { className: "text-[13px] text-neutral-500 leading-relaxed" }, "Choose your region to continue.")), /* @__PURE__ */ React6.createElement("div", { className: "flex flex-col gap-2" }, SUPPORTED_COUNTRIES.map((c) => /* @__PURE__ */ React6.createElement("button", { key: c.code, onClick: () => {
413
+ onSelect(c);
414
+ onClose();
415
+ }, className: "flex items-center gap-4 p-3 rounded-xl hover:bg-neutral-50 transition-colors text-left outline-none" }, /* @__PURE__ */ React6.createElement(FlagImage, { countryCode: c.code }), /* @__PURE__ */ React6.createElement("span", { className: "text-sm text-black flex-1 " }, c.name), /* @__PURE__ */ React6.createElement("span", { className: "text-sm text-neutral-400 " }, c.dialCode)))))));
433
416
  };
434
417
  function AuthFormInner({
435
418
  companyName,
@@ -467,7 +450,9 @@ function AuthFormInner({
467
450
  const [userHasPasskey, setUserHasPasskey] = useState4(false);
468
451
  const [authOptions, setAuthOptions] = useState4(null);
469
452
  const [otp, setOtp] = useState4(["", "", "", "", "", ""]);
470
- const inputRefs = useRef2([]);
453
+ const [passcode, setPasscode] = useState4(["", "", "", "", "", ""]);
454
+ const otpRefs = useRef2([]);
455
+ const passRefs = useRef2([]);
471
456
  const cleanAlpha = (val) => val.replace(/[^a-zA-Z]/g, "").substring(0, 50);
472
457
  const cleanOrgName = (val) => val.replace(/[^a-zA-Z0-9\s]/g, "").substring(0, 50);
473
458
  useEffect3(() => {
@@ -476,40 +461,44 @@ function AuthFormInner({
476
461
  return () => clearTimeout(timer);
477
462
  }
478
463
  }, [countdown]);
479
- const handleOtpChange = (index, value) => {
464
+ const handleArrayChange = (index, value, isPasscode) => {
480
465
  const cleanValue = value.replace(/[^0-9]/g, "");
481
466
  if (!cleanValue && value !== "") return;
482
- const newOtp = [...otp];
483
- newOtp[index] = cleanValue.slice(-1);
484
- setOtp(newOtp);
485
- if (cleanValue && index < 5) inputRefs.current[index + 1]?.focus();
467
+ const arr = isPasscode ? [...passcode] : [...otp];
468
+ const setArr = isPasscode ? setPasscode : setOtp;
469
+ const refs = isPasscode ? passRefs : otpRefs;
470
+ arr[index] = cleanValue.slice(-1);
471
+ setArr(arr);
472
+ if (cleanValue && index < 5) refs.current[index + 1]?.focus();
486
473
  };
487
- const handlePaste = (e) => {
474
+ const handleArrayPaste = (e, isPasscode) => {
488
475
  e.preventDefault();
489
476
  const pastedData = e.clipboardData.getData("text/plain");
490
477
  const numbersOnly = pastedData.replace(/[^0-9]/g, "");
491
478
  if (!numbersOnly) return;
492
- const newOtp = [...otp];
479
+ const arr = isPasscode ? [...passcode] : [...otp];
480
+ const setArr = isPasscode ? setPasscode : setOtp;
481
+ const refs = isPasscode ? passRefs : otpRefs;
493
482
  const length = Math.min(numbersOnly.length, 6);
494
- for (let i = 0; i < length; i++) newOtp[i] = numbersOnly[i];
495
- setOtp(newOtp);
496
- if (length < 6) inputRefs.current[length]?.focus();
483
+ for (let i = 0; i < length; i++) arr[i] = numbersOnly[i];
484
+ setArr(arr);
485
+ if (length < 6) refs.current[length]?.focus();
497
486
  else {
498
- inputRefs.current[5]?.focus();
499
- verifyOtpCode(newOtp.join(""));
487
+ refs.current[5]?.focus();
488
+ if (isPasscode) verifyPasscodeSubmit(arr.join(""));
489
+ else verifyOtpCode(arr.join(""));
500
490
  }
501
491
  };
502
492
  const getFormattedPhoneNumber = () => {
503
493
  const cleanNumber = phoneNumber.replace(/^0+/, "");
504
494
  return `${selectedCountry.dialCode}${cleanNumber}`;
505
495
  };
506
- const handleAuthRequestSubmit = async (e) => {
507
- if (e) e.preventDefault();
496
+ const handleAuthRequestSubmit = async (forceOtp = false) => {
508
497
  if (mode === "SIGNUP" && !agreedToTerms) {
509
498
  toast.error("You must agree to the Terms and Privacy Policy.");
510
499
  return;
511
500
  }
512
- if (isSubmitting || countdown > 0) return;
501
+ if (isSubmitting) return;
513
502
  setIsSubmitting(true);
514
503
  try {
515
504
  let recaptchaToken = void 0;
@@ -524,14 +513,21 @@ function AuthFormInner({
524
513
  organizationName: mode === "SIGNUP" && requireOrganization ? orgName : void 0,
525
514
  r_c: mode === "SIGNUP" && invitationCode.trim() ? invitationCode.trim().toUpperCase() : void 0,
526
515
  mode,
527
- recaptchaToken
516
+ recaptchaToken,
517
+ forceOtp
528
518
  });
529
519
  if (res.success) {
530
520
  setUserHasPasskey(!!res.hasPasskey);
531
521
  if (res.passkeyOptions) setAuthOptions(res.passkeyOptions);
532
- toast.success(res.needsPukSetup ? "Verification code sent" : "Security check required");
533
- setStep("OTP");
534
- setCountdown(60);
522
+ if (res.hasPuk && !forceOtp) {
523
+ setStep("PASSCODE");
524
+ setTimeout(() => passRefs.current[0]?.focus(), 100);
525
+ } else {
526
+ setStep("OTP");
527
+ setCountdown(60);
528
+ toast.success("Verification code sent");
529
+ setTimeout(() => otpRefs.current[0]?.focus(), 100);
530
+ }
535
531
  } else {
536
532
  toast.error(res.error || "Authentication failed.");
537
533
  }
@@ -545,16 +541,29 @@ function AuthFormInner({
545
541
  if (codeToVerify.length !== 6 || isSubmitting) return;
546
542
  setIsSubmitting(true);
547
543
  try {
548
- const res = await onVerifyOtp({
549
- phoneNumber: getFormattedPhoneNumber(),
550
- code: codeToVerify
551
- });
552
- if (res.success) {
553
- window.location.href = res.redirect || redirectUrl;
554
- } else {
544
+ const res = await onVerifyOtp({ phoneNumber: getFormattedPhoneNumber(), code: codeToVerify });
545
+ if (res.success) window.location.href = res.redirect || redirectUrl;
546
+ else {
555
547
  toast.error(res.error || "Invalid code.");
556
548
  setOtp(["", "", "", "", "", ""]);
557
- inputRefs.current[0]?.focus();
549
+ otpRefs.current[0]?.focus();
550
+ }
551
+ } catch {
552
+ toast.error("Verification failed.");
553
+ } finally {
554
+ setIsSubmitting(false);
555
+ }
556
+ };
557
+ const verifyPasscodeSubmit = async (passToVerify) => {
558
+ if (passToVerify.length !== 6 || isSubmitting) return;
559
+ setIsSubmitting(true);
560
+ try {
561
+ const res = await onVerifyOtp({ phoneNumber: getFormattedPhoneNumber(), passcode: passToVerify });
562
+ if (res.success) window.location.href = res.redirect || redirectUrl;
563
+ else {
564
+ toast.error(res.error || "Invalid passcode.");
565
+ setPasscode(["", "", "", "", "", ""]);
566
+ passRefs.current[0]?.focus();
558
567
  }
559
568
  } catch {
560
569
  toast.error("Verification failed.");
@@ -567,16 +576,9 @@ function AuthFormInner({
567
576
  setIsSubmitting(true);
568
577
  try {
569
578
  const credentialResponse = await startAuthentication({ optionsJSON: authOptions });
570
- const res = await onVerifyOtp({
571
- phoneNumber: getFormattedPhoneNumber(),
572
- isPasskey: true,
573
- passkeyCredential: credentialResponse
574
- });
575
- if (res.success) {
576
- window.location.href = res.redirect || redirectUrl;
577
- } else {
578
- toast.error(res.error || "Device verification failed.");
579
- }
579
+ const res = await onVerifyOtp({ phoneNumber: getFormattedPhoneNumber(), isPasskey: true, passkeyCredential: credentialResponse });
580
+ if (res.success) window.location.href = res.redirect || redirectUrl;
581
+ else toast.error(res.error || "Device verification failed.");
580
582
  } catch (err) {
581
583
  if (err.name !== "NotAllowedError") toast.error("Passkey verification interrupted.");
582
584
  } finally {
@@ -604,14 +606,7 @@ function AuthFormInner({
604
606
  }
605
607
  return false;
606
608
  };
607
- return /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(
608
- CountrySelectorDialog,
609
- {
610
- isOpen: isCountryDialogOpen,
611
- onClose: () => setIsCountryDialogOpen(false),
612
- onSelect: setSelectedCountry
613
- }
614
- ), /* @__PURE__ */ React6.createElement("div", { className: "w-full max-w-md mx-auto flex flex-col items-center gap-4 relative z-10 animate-in fade-in duration-300" }, /* @__PURE__ */ React6.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ React6.createElement("div", { className: "p-6" }, step === "INPUT" && /* @__PURE__ */ React6.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React6.createElement("div", { className: "mb-12 mt-2 text-left" }, /* @__PURE__ */ React6.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ React6.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, "Don't have an account? ", /* @__PURE__ */ React6.createElement("button", { type: "button", onClick: () => {
609
+ return /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(CountrySelectorDialog, { isOpen: isCountryDialogOpen, onClose: () => setIsCountryDialogOpen(false), onSelect: setSelectedCountry }), /* @__PURE__ */ React6.createElement("div", { className: "w-full max-w-md mx-auto flex flex-col items-center gap-4 relative z-10 animate-in fade-in duration-300" }, /* @__PURE__ */ React6.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ React6.createElement("div", { className: "p-6" }, step === "INPUT" && /* @__PURE__ */ React6.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React6.createElement("div", { className: "mb-12 mt-2 text-left" }, /* @__PURE__ */ React6.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ React6.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ React6.createElement(React6.Fragment, null, "Don't have an account? ", /* @__PURE__ */ React6.createElement("button", { type: "button", onClick: () => {
615
610
  setMode("SIGNUP");
616
611
  setSignupStep(getInitialSignupStep());
617
612
  }, className: "text-black transition-colors ml-1 " }, "Sign up")) : /* @__PURE__ */ React6.createElement(React6.Fragment, null, "Already have an account? ", /* @__PURE__ */ React6.createElement("button", { type: "button", onClick: () => setMode("LOGIN"), className: "text-black transition-colors ml-1 " }, "Log in")))), /* @__PURE__ */ React6.createElement("form", { className: "space-y-6", autoComplete: "off", onSubmit: (e) => {
@@ -619,7 +614,7 @@ function AuthFormInner({
619
614
  if (mode === "SIGNUP" && signupStep === "NAME") handleNextSignupStep();
620
615
  else if (mode === "SIGNUP" && signupStep === "ORGANIZATION") handleNextSignupStep();
621
616
  else if (mode === "SIGNUP" && signupStep === "PHONE_ID") handleNextSignupStep();
622
- else handleAuthRequestSubmit();
617
+ else handleAuthRequestSubmit(false);
623
618
  } }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ React6.createElement("div", { className: "flex flex-col gap-6 animate-in fade-in slide-in-from-right-4 duration-300" }, /* @__PURE__ */ React6.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React6.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ React6.createElement("input", { type: "text", value: firstName, onChange: (e) => setFirstName(cleanAlpha(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300", placeholder: "First name" })), /* @__PURE__ */ React6.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React6.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ React6.createElement("input", { type: "text", value: lastName, onChange: (e) => setLastName(cleanAlpha(e.target.value)), required: true, className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Last name" }))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ React6.createElement("div", { className: "flex flex-col gap-6 animate-in fade-in slide-in-from-right-4 duration-300" }, /* @__PURE__ */ React6.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React6.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ React6.createElement("input", { type: "text", value: orgName, onChange: (e) => setOrgName(cleanOrgName(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Acme Corporation" }))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "PHONE_ID") && /* @__PURE__ */ React6.createElement("div", { className: "space-y-6 animate-in fade-in slide-in-from-right-4 duration-300" }, /* @__PURE__ */ React6.createElement(
624
619
  PhoneInput,
625
620
  {
@@ -640,35 +635,55 @@ function AuthFormInner({
640
635
  className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300 tracking-widest",
641
636
  placeholder: "Referral Code (Optional)"
642
637
  }
643
- ))), /* @__PURE__ */ React6.createElement(ThreeDActionButton, { type: "submit", disabled: isContinueDisabled(), isLoading: isSubmitting, className: "w-full mt-10" }, mode === "SIGNUP" && signupStep === "INVITATION" ? "Complete Setup" : "Continue"))), step === "OTP" && /* @__PURE__ */ React6.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React6.createElement("div", { className: "text-left mb-10 mt-2" }, /* @__PURE__ */ React6.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ React6.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter your 6-digit verification code.")), /* @__PURE__ */ React6.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
638
+ ))), /* @__PURE__ */ React6.createElement(ThreeDActionButton, { type: "submit", disabled: isContinueDisabled(), isLoading: isSubmitting, className: "w-full mt-10" }, mode === "SIGNUP" && signupStep === "INVITATION" ? "Complete Setup" : "Continue"))), step === "PASSCODE" && /* @__PURE__ */ React6.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React6.createElement("div", { className: "text-left mb-10 mt-2" }, /* @__PURE__ */ React6.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Enter Passcode"), /* @__PURE__ */ React6.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter your secure 6-digit transaction passcode.")), /* @__PURE__ */ React6.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
639
+ e.preventDefault();
640
+ verifyPasscodeSubmit(passcode.join(""));
641
+ } }, /* @__PURE__ */ React6.createElement("div", { className: "flex justify-between gap-2", onPaste: (e) => handleArrayPaste(e, true) }, passcode.map((digit, index) => /* @__PURE__ */ React6.createElement(
642
+ "input",
643
+ {
644
+ key: index,
645
+ ref: (el) => {
646
+ passRefs.current[index] = el;
647
+ },
648
+ type: "password",
649
+ inputMode: "numeric",
650
+ maxLength: 1,
651
+ value: digit,
652
+ onChange: (e) => handleArrayChange(index, e.target.value, true),
653
+ onKeyDown: (e) => {
654
+ if (e.key === "Backspace" && !passcode[index] && index > 0) passRefs.current[index - 1]?.focus();
655
+ },
656
+ className: "w-7 h-10 text-center text-lg bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300"
657
+ }
658
+ ))), /* @__PURE__ */ React6.createElement(ThreeDActionButton, { type: "submit", disabled: passcode.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Login")), /* @__PURE__ */ React6.createElement("div", { className: "pt-6 flex flex-col gap-3" }, userHasPasskey && /* @__PURE__ */ React6.createElement("button", { onClick: triggerPasskeyLogin, disabled: isSubmitting, className: "w-full py-2.5 rounded-full text-black border border-neutral-200 hover:bg-neutral-50 text-[11px] tracking-wide transition-colors outline-none disabled:opacity-50" }, "Use Passkey / Biometrics"), /* @__PURE__ */ React6.createElement("button", { onClick: () => handleAuthRequestSubmit(true), disabled: isSubmitting, className: "w-full text-center text-[12px] text-neutral-500 hover:text-black transition-colors outline-none mt-2" }, "Use OTP instead"))), step === "OTP" && /* @__PURE__ */ React6.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React6.createElement("div", { className: "text-left mb-10 mt-2" }, /* @__PURE__ */ React6.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ React6.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter your 6-digit verification code.")), /* @__PURE__ */ React6.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
644
659
  e.preventDefault();
645
660
  verifyOtpCode(otp.join(""));
646
- } }, /* @__PURE__ */ React6.createElement("div", { className: "flex justify-between gap-2", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ React6.createElement(
661
+ } }, /* @__PURE__ */ React6.createElement("div", { className: "flex justify-between gap-2", onPaste: (e) => handleArrayPaste(e, false) }, otp.map((digit, index) => /* @__PURE__ */ React6.createElement(
647
662
  "input",
648
663
  {
649
664
  key: index,
650
665
  ref: (el) => {
651
- inputRefs.current[index] = el;
666
+ otpRefs.current[index] = el;
652
667
  },
653
668
  type: "text",
654
669
  inputMode: "numeric",
655
670
  maxLength: 1,
656
671
  value: digit,
657
- onChange: (e) => handleOtpChange(index, e.target.value),
672
+ onChange: (e) => handleArrayChange(index, e.target.value, false),
658
673
  onKeyDown: (e) => {
659
- if (e.key === "Backspace" && !otp[index] && index > 0) inputRefs.current[index - 1]?.focus();
674
+ if (e.key === "Backspace" && !otp[index] && index > 0) otpRefs.current[index - 1]?.focus();
660
675
  },
661
- className: "w-7 h-12 text-center text-sm bg-transparent border-b-2 border-neutral-200 text-black outline-none focus:border-black transition-all duration-300"
676
+ className: "w-7 h-10 text-center text-sm bg-transparent border-b border-neutral-200 text-black outline-none focus:border-black transition-all duration-300"
662
677
  }
663
- ))), /* @__PURE__ */ React6.createElement(ThreeDActionButton, { type: "submit", disabled: otp.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Verify Code")), userHasPasskey && /* @__PURE__ */ React6.createElement("div", { className: "pt-6" }, /* @__PURE__ */ React6.createElement(
678
+ ))), /* @__PURE__ */ React6.createElement(ThreeDActionButton, { type: "submit", disabled: otp.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Verify Code")), /* @__PURE__ */ React6.createElement("div", { className: "pt-6 flex flex-col gap-3" }, /* @__PURE__ */ React6.createElement(
664
679
  "button",
665
680
  {
666
- onClick: triggerPasskeyLogin,
667
- disabled: isSubmitting,
668
- className: "w-full py-2.5 rounded-full text-black border border-neutral-200 hover:bg-neutral-50 text-xs tracking-wide transition-colors outline-none disabled:opacity-50"
681
+ onClick: () => handleAuthRequestSubmit(true),
682
+ disabled: countdown > 0 || isSubmitting,
683
+ className: "w-full text-center text-[12px] text-neutral-500 hover:text-black transition-colors outline-none disabled:opacity-50"
669
684
  },
670
- "Use Passkey / Biometrics"
671
- )))))));
685
+ countdown > 0 ? `Resend code in ${countdown}s` : "Resend OTP"
686
+ ), userHasPasskey && /* @__PURE__ */ React6.createElement("button", { onClick: triggerPasskeyLogin, disabled: isSubmitting, className: "w-full py-2.5 rounded-full text-black border border-neutral-200 hover:bg-neutral-50 text-[11px] tracking-wide transition-colors outline-none disabled:opacity-50 mt-4" }, "Use Passkey / Biometrics")))))));
672
687
  }
673
688
  var PipleAuth = (props) => {
674
689
  if (props.useRecaptcha && props.recaptchaSiteKey) {
@@ -1586,7 +1601,7 @@ var UniversalHomeView = ({
1586
1601
  const [isAvatarLoaded, setIsAvatarLoaded] = useState10(false);
1587
1602
  const [showNotifDialog, setShowNotifDialog] = useState10(false);
1588
1603
  const [selectedNotif, setSelectedNotif] = useState10(null);
1589
- const fallbackAvatarUrl = `/api/avatar/?name=${encodeURIComponent(fullName || " ")}&background=ffffff&color=000000&size=200&font-size=0.33`;
1604
+ const fallbackAvatarUrl = `/api/avatar/?name=${encodeURIComponent(fullName || " ")}&background=ffffff&color=000000&size=100&font-size=0.33`;
1590
1605
  const finalAvatarSrc = avatarSrc || fallbackAvatarUrl;
1591
1606
  const hasUnreadNotifs = notifications.some((n) => !n.isRead);
1592
1607
  return /* @__PURE__ */ React16.createElement("div", { className: "w-full max-w-3xl mx-auto flex flex-col animate-in fade-in duration-300 relative" }, /* @__PURE__ */ React16.createElement("div", { className: "flex items-center justify-between w-full pb-4 " }, /* @__PURE__ */ React16.createElement("div", { className: "w-10 h-10 rounded-full bg-white p-0.75 flex items-center justify-center cursor-pointer", onClick: () => window.location.href = "/app/account/me" }, /* @__PURE__ */ React16.createElement("div", { className: "relative w-full h-full rounded-full overflow-hidden bg-neutral-100 flex items-center justify-center" }, !isAvatarLoaded && /* @__PURE__ */ React16.createElement("div", { className: "absolute inset-0 flex items-center justify-center z-10 bg-neutral-100" }, /* @__PURE__ */ React16.createElement(HugeiconsIcon10, { icon: Loading03Icon5, size: 16, className: "animate-spin text-black" })), /* @__PURE__ */ React16.createElement(
@@ -3860,7 +3875,7 @@ var formatWeb3Name = (name) => {
3860
3875
  var PageSpinner4 = () => /* @__PURE__ */ React41.createElement("div", { className: "flex justify-center items-center py-12 w-full" }, /* @__PURE__ */ React41.createElement(HugeiconsIcon27, { icon: Loading03Icon16, size: 32, className: "animate-spin mb-4 text-black" }));
3861
3876
  var AvatarLogo = ({ src, alt, name, sizeClass = "w-12 h-12" }) => {
3862
3877
  const [isLoaded, setIsLoaded] = useState27(false);
3863
- const fallbackUrl = `/api/avatar/?name=${encodeURIComponent(name || " ")}&background=ffffff&color=000000&size=200&font-size=0.33`;
3878
+ const fallbackUrl = `/api/avatar/?name=${encodeURIComponent(name || " ")}&background=ffffff&color=000000&size=100&font-size=0.33`;
3864
3879
  const finalSrc = src || fallbackUrl;
3865
3880
  return /* @__PURE__ */ React41.createElement("div", { className: `relative shrink-0 rounded-full bg-neutral-100 flex items-center justify-center overflow-hidden border-[3px] border-white ${sizeClass}` }, !isLoaded && /* @__PURE__ */ React41.createElement(HugeiconsIcon27, { icon: Loading03Icon16, size: 16, className: "animate-spin text-neutral-400 absolute" }), /* @__PURE__ */ React41.createElement("img", { src: finalSrc, alt, onLoad: () => setIsLoaded(true), className: `w-full h-full object-cover transition-opacity duration-300 ${isLoaded ? "opacity-100" : "opacity-0"}` }));
3866
3881
  };
@@ -3999,7 +4014,7 @@ var UniversalReferralPage = ({
3999
4014
  };
4000
4015
  const AvatarLogo2 = ({ src, alt, name, sizeClass = "w-10 h-10" }) => {
4001
4016
  const [isLoaded, setIsLoaded] = useState28(false);
4002
- const fallbackUrl = `/api/avatar/?name=${encodeURIComponent(name || " ")}&background=e0e0e0&color=000000&size=200&font-size=0.33`;
4017
+ const fallbackUrl = `/api/avatar/?name=${encodeURIComponent(name || " ")}&background=e0e0e0&color=000000&size=100&font-size=0.33`;
4003
4018
  const finalSrc = src || fallbackUrl;
4004
4019
  return /* @__PURE__ */ React42.createElement("div", { className: `relative shrink-0 rounded-full bg-neutral-100 flex items-center justify-center overflow-hidden border-2 border-white ${sizeClass}` }, !isLoaded && /* @__PURE__ */ React42.createElement(HugeiconsIcon28, { icon: Loading03Icon17, size: 16, className: "animate-spin text-neutral-400 absolute" }), /* @__PURE__ */ React42.createElement("img", { src: finalSrc, alt, onLoad: () => setIsLoaded(true), className: `w-full h-full object-cover transition-opacity duration-300 ${isLoaded ? "opacity-100" : "opacity-0"}` }));
4005
4020
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retinalabsllc/zairusjs",
3
- "version": "15.1.45",
3
+ "version": "15.1.55",
4
4
  "description": "A perceptive, Ai data driven Next.js UI component library.",
5
5
  "author": "Retina Labs Company",
6
6
  "license": "MIT",