@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 +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +98 -83
- package/dist/index.mjs +98 -83
- package/package.json +1 -1
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
|
-
|
|
493
|
-
|
|
494
|
-
|
|
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
|
|
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
|
|
543
|
+
const handleArrayChange = (index, value, isPasscode) => {
|
|
559
544
|
const cleanValue = value.replace(/[^0-9]/g, "");
|
|
560
545
|
if (!cleanValue && value !== "") return;
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
|
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
|
|
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++)
|
|
574
|
-
|
|
575
|
-
if (length < 6)
|
|
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
|
-
|
|
578
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
612
|
-
|
|
613
|
-
|
|
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
|
-
|
|
629
|
-
|
|
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
|
-
|
|
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
|
-
|
|
651
|
-
|
|
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 === "
|
|
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:
|
|
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
|
-
|
|
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) =>
|
|
751
|
+
onChange: (e) => handleArrayChange(index, e.target.value, false),
|
|
737
752
|
onKeyDown: (e) => {
|
|
738
|
-
if (e.key === "Backspace" && !otp[index] && index > 0)
|
|
753
|
+
if (e.key === "Backspace" && !otp[index] && index > 0) otpRefs.current[index - 1]?.focus();
|
|
739
754
|
},
|
|
740
|
-
className: "w-7 h-
|
|
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")),
|
|
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:
|
|
746
|
-
disabled: isSubmitting,
|
|
747
|
-
className: "w-full
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
-
|
|
414
|
-
|
|
415
|
-
|
|
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
|
|
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
|
|
464
|
+
const handleArrayChange = (index, value, isPasscode) => {
|
|
480
465
|
const cleanValue = value.replace(/[^0-9]/g, "");
|
|
481
466
|
if (!cleanValue && value !== "") return;
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
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
|
|
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
|
|
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++)
|
|
495
|
-
|
|
496
|
-
if (length < 6)
|
|
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
|
-
|
|
499
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
550
|
-
|
|
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
|
-
|
|
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
|
-
|
|
572
|
-
|
|
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 === "
|
|
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:
|
|
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
|
-
|
|
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) =>
|
|
672
|
+
onChange: (e) => handleArrayChange(index, e.target.value, false),
|
|
658
673
|
onKeyDown: (e) => {
|
|
659
|
-
if (e.key === "Backspace" && !otp[index] && index > 0)
|
|
674
|
+
if (e.key === "Backspace" && !otp[index] && index > 0) otpRefs.current[index - 1]?.focus();
|
|
660
675
|
},
|
|
661
|
-
className: "w-7 h-
|
|
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")),
|
|
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:
|
|
667
|
-
disabled: isSubmitting,
|
|
668
|
-
className: "w-full
|
|
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
|
-
|
|
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=
|
|
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=
|
|
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=
|
|
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
|
};
|