@retinalabsllc/zairusjs 11.0.1 → 11.0.2
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 +6 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +53 -86
- package/dist/index.mjs +51 -84
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -71,10 +71,15 @@ interface PipleAuthProps {
|
|
|
71
71
|
}) => Promise<{
|
|
72
72
|
success: boolean;
|
|
73
73
|
error?: string;
|
|
74
|
+
has2FA?: boolean;
|
|
75
|
+
hasPasskey?: boolean;
|
|
76
|
+
passkeyOptions?: any;
|
|
74
77
|
}>;
|
|
75
78
|
onVerifyOtp: (payload: {
|
|
76
79
|
email: string;
|
|
77
|
-
code
|
|
80
|
+
code?: string;
|
|
81
|
+
isPasskey?: boolean;
|
|
82
|
+
passkeyCredential?: any;
|
|
78
83
|
}) => Promise<{
|
|
79
84
|
success: boolean;
|
|
80
85
|
error?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -71,10 +71,15 @@ interface PipleAuthProps {
|
|
|
71
71
|
}) => Promise<{
|
|
72
72
|
success: boolean;
|
|
73
73
|
error?: string;
|
|
74
|
+
has2FA?: boolean;
|
|
75
|
+
hasPasskey?: boolean;
|
|
76
|
+
passkeyOptions?: any;
|
|
74
77
|
}>;
|
|
75
78
|
onVerifyOtp: (payload: {
|
|
76
79
|
email: string;
|
|
77
|
-
code
|
|
80
|
+
code?: string;
|
|
81
|
+
isPasskey?: boolean;
|
|
82
|
+
passkeyCredential?: any;
|
|
78
83
|
}) => Promise<{
|
|
79
84
|
success: boolean;
|
|
80
85
|
error?: string;
|
package/dist/index.js
CHANGED
|
@@ -369,6 +369,7 @@ var import_react6 = __toESM(require("react"));
|
|
|
369
369
|
var import_navigation2 = require("next/navigation");
|
|
370
370
|
var import_react_hot_toast = __toESM(require("react-hot-toast"));
|
|
371
371
|
var import_react_google_recaptcha_v3 = require("react-google-recaptcha-v3");
|
|
372
|
+
var import_browser = require("@simplewebauthn/browser");
|
|
372
373
|
var InputSpinner = () => /* @__PURE__ */ import_react6.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_react6.default.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), /* @__PURE__ */ import_react6.default.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" }));
|
|
373
374
|
function AuthFormInner({
|
|
374
375
|
companyName,
|
|
@@ -400,6 +401,9 @@ function AuthFormInner({
|
|
|
400
401
|
const [lastName, setLastName] = (0, import_react6.useState)("");
|
|
401
402
|
const [orgName, setOrgName] = (0, import_react6.useState)("");
|
|
402
403
|
const [agreedToTerms, setAgreedToTerms] = (0, import_react6.useState)(false);
|
|
404
|
+
const [userHas2FA, setUserHas2FA] = (0, import_react6.useState)(false);
|
|
405
|
+
const [userHasPasskey, setUserHasPasskey] = (0, import_react6.useState)(false);
|
|
406
|
+
const [authOptions, setAuthOptions] = (0, import_react6.useState)(null);
|
|
403
407
|
const [otp, setOtp] = (0, import_react6.useState)(["", "", "", "", "", ""]);
|
|
404
408
|
const inputRefs = (0, import_react6.useRef)([]);
|
|
405
409
|
const cleanAlpha = (val) => val.replace(/[^a-zA-Z\s-]/g, "");
|
|
@@ -417,9 +421,7 @@ function AuthFormInner({
|
|
|
417
421
|
const newOtp = [...otp];
|
|
418
422
|
newOtp[index] = cleanValue.slice(-1);
|
|
419
423
|
setOtp(newOtp);
|
|
420
|
-
if (cleanValue && index < 5)
|
|
421
|
-
inputRefs.current[index + 1]?.focus();
|
|
422
|
-
}
|
|
424
|
+
if (cleanValue && index < 5) inputRefs.current[index + 1]?.focus();
|
|
423
425
|
};
|
|
424
426
|
const handlePaste = (e) => {
|
|
425
427
|
e.preventDefault();
|
|
@@ -428,13 +430,10 @@ function AuthFormInner({
|
|
|
428
430
|
if (!numbersOnly) return;
|
|
429
431
|
const newOtp = [...otp];
|
|
430
432
|
const length = Math.min(numbersOnly.length, 6);
|
|
431
|
-
for (let i = 0; i < length; i++)
|
|
432
|
-
newOtp[i] = numbersOnly[i];
|
|
433
|
-
}
|
|
433
|
+
for (let i = 0; i < length; i++) newOtp[i] = numbersOnly[i];
|
|
434
434
|
setOtp(newOtp);
|
|
435
|
-
if (length < 6)
|
|
436
|
-
|
|
437
|
-
} else {
|
|
435
|
+
if (length < 6) inputRefs.current[length]?.focus();
|
|
436
|
+
else {
|
|
438
437
|
inputRefs.current[5]?.focus();
|
|
439
438
|
verifyOtpCode(newOtp.join(""));
|
|
440
439
|
}
|
|
@@ -461,7 +460,10 @@ function AuthFormInner({
|
|
|
461
460
|
recaptchaToken
|
|
462
461
|
});
|
|
463
462
|
if (res.success) {
|
|
464
|
-
|
|
463
|
+
setUserHas2FA(!!res.has2FA);
|
|
464
|
+
setUserHasPasskey(!!res.hasPasskey);
|
|
465
|
+
if (res.passkeyOptions) setAuthOptions(res.passkeyOptions);
|
|
466
|
+
import_react_hot_toast.default.success(res.has2FA ? "Security check required" : "Verification code sent");
|
|
465
467
|
setStep("OTP");
|
|
466
468
|
setCountdown(60);
|
|
467
469
|
} else {
|
|
@@ -491,6 +493,23 @@ function AuthFormInner({
|
|
|
491
493
|
setIsSubmitting(false);
|
|
492
494
|
}
|
|
493
495
|
};
|
|
496
|
+
const triggerPasskeyLogin = async () => {
|
|
497
|
+
if (!authOptions || isSubmitting) return;
|
|
498
|
+
setIsSubmitting(true);
|
|
499
|
+
try {
|
|
500
|
+
const credentialResponse = await (0, import_browser.startAuthentication)({ optionsJSON: authOptions });
|
|
501
|
+
const res = await onVerifyOtp({ email: emailId, isPasskey: true, passkeyCredential: credentialResponse });
|
|
502
|
+
if (res.success) {
|
|
503
|
+
window.location.href = res.redirect || redirectUrl;
|
|
504
|
+
} else {
|
|
505
|
+
import_react_hot_toast.default.error(res.error || "Device verification failed.");
|
|
506
|
+
}
|
|
507
|
+
} catch (err) {
|
|
508
|
+
if (err.name !== "NotAllowedError") import_react_hot_toast.default.error("Passkey verification interrupted.");
|
|
509
|
+
} finally {
|
|
510
|
+
setIsSubmitting(false);
|
|
511
|
+
}
|
|
512
|
+
};
|
|
494
513
|
const handleNextSignupStep = () => {
|
|
495
514
|
if (signupStep === "NAME") {
|
|
496
515
|
if (requireOrganization) setSignupStep("ORGANIZATION");
|
|
@@ -509,7 +528,7 @@ function AuthFormInner({
|
|
|
509
528
|
}
|
|
510
529
|
return false;
|
|
511
530
|
};
|
|
512
|
-
return /* @__PURE__ */ import_react6.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_react6.default.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "p-8 md:p-12" }, step === "INPUT" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "mb-12 text-center mt-2" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: "
|
|
531
|
+
return /* @__PURE__ */ import_react6.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_react6.default.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "p-8 md:p-12" }, step === "INPUT" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "mb-12 text-center mt-2" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, "Don't have an account? ", /* @__PURE__ */ import_react6.default.createElement("button", { type: "button", onClick: () => {
|
|
513
532
|
setMode("SIGNUP");
|
|
514
533
|
setSignupStep(getInitialSignupStep());
|
|
515
534
|
}, className: "text-black transition-colors ml-1" }, "Sign up")) : /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, "Already have an account? ", /* @__PURE__ */ import_react6.default.createElement("button", { type: "button", onClick: () => setMode("LOGIN"), className: "text-black transition-colors ml-1" }, "Log in")))), /* @__PURE__ */ import_react6.default.createElement("form", { className: "space-y-6", autoComplete: "off", onSubmit: (e) => {
|
|
@@ -517,69 +536,7 @@ function AuthFormInner({
|
|
|
517
536
|
if (mode === "SIGNUP" && signupStep === "NAME") handleNextSignupStep();
|
|
518
537
|
else if (mode === "SIGNUP" && signupStep === "ORGANIZATION") handleNextSignupStep();
|
|
519
538
|
else handleAuthRequestSubmit();
|
|
520
|
-
} }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ import_react6.default.createElement(
|
|
521
|
-
"input",
|
|
522
|
-
{
|
|
523
|
-
type: "text",
|
|
524
|
-
value: firstName,
|
|
525
|
-
onChange: (e) => setFirstName(cleanAlpha(e.target.value)),
|
|
526
|
-
required: true,
|
|
527
|
-
autoFocus: true,
|
|
528
|
-
className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
|
|
529
|
-
placeholder: "First name"
|
|
530
|
-
}
|
|
531
|
-
)), /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ import_react6.default.createElement(
|
|
532
|
-
"input",
|
|
533
|
-
{
|
|
534
|
-
type: "text",
|
|
535
|
-
value: lastName,
|
|
536
|
-
onChange: (e) => setLastName(cleanAlpha(e.target.value)),
|
|
537
|
-
required: true,
|
|
538
|
-
className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
|
|
539
|
-
placeholder: "Last name"
|
|
540
|
-
}
|
|
541
|
-
))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ import_react6.default.createElement(
|
|
542
|
-
"input",
|
|
543
|
-
{
|
|
544
|
-
type: "text",
|
|
545
|
-
value: orgName,
|
|
546
|
-
onChange: (e) => setOrgName(cleanOrgName(e.target.value)),
|
|
547
|
-
required: true,
|
|
548
|
-
autoFocus: true,
|
|
549
|
-
className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
|
|
550
|
-
placeholder: "Acme Corporation"
|
|
551
|
-
}
|
|
552
|
-
))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "EMAIL_ID") && /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Email ID"), /* @__PURE__ */ import_react6.default.createElement(
|
|
553
|
-
"input",
|
|
554
|
-
{
|
|
555
|
-
type: "email",
|
|
556
|
-
value: emailId,
|
|
557
|
-
onChange: (e) => setEmailId(cleanEmailId(e.target.value)),
|
|
558
|
-
required: true,
|
|
559
|
-
autoFocus: true,
|
|
560
|
-
className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
|
|
561
|
-
placeholder: "name@company.com"
|
|
562
|
-
}
|
|
563
|
-
)), mode === "SIGNUP" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-start gap-3 mt-4" }, /* @__PURE__ */ import_react6.default.createElement(
|
|
564
|
-
"input",
|
|
565
|
-
{
|
|
566
|
-
type: "checkbox",
|
|
567
|
-
id: "zairus-terms",
|
|
568
|
-
checked: agreedToTerms,
|
|
569
|
-
onChange: (e) => setAgreedToTerms(e.target.checked),
|
|
570
|
-
className: "mt-0.5 w-4 h-4 bg-white border-neutral-300 rounded text-black focus:ring-black cursor-pointer",
|
|
571
|
-
required: true
|
|
572
|
-
}
|
|
573
|
-
), /* @__PURE__ */ import_react6.default.createElement("label", { htmlFor: "zairus-terms", className: "text-[11px] text-neutral-500 cursor-pointer leading-snug" }, "I agree to ", companyName, "'s ", /* @__PURE__ */ import_react6.default.createElement("a", { href: termsUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Terms of Service"), " and ", /* @__PURE__ */ import_react6.default.createElement("a", { href: privacyUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Privacy Policy"), "."))), /* @__PURE__ */ import_react6.default.createElement(
|
|
574
|
-
ThreeDActionButton,
|
|
575
|
-
{
|
|
576
|
-
type: "submit",
|
|
577
|
-
disabled: isContinueDisabled(),
|
|
578
|
-
isLoading: isSubmitting,
|
|
579
|
-
className: "w-full mt-10"
|
|
580
|
-
},
|
|
581
|
-
"Continue"
|
|
582
|
-
))), step === "OTP" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-center mb-10 mt-2" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: " text-xl text-black mb-2 tracking-tight " }, "Security Check"), /* @__PURE__ */ import_react6.default.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the code sent to ", /* @__PURE__ */ import_react6.default.createElement("br", null), /* @__PURE__ */ import_react6.default.createElement("span", { className: "text-black " }, emailId))), /* @__PURE__ */ import_react6.default.createElement("form", { className: "space-y-10", autoComplete: "off", onSubmit: (e) => {
|
|
539
|
+
} }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ import_react6.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-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "First name" })), /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ import_react6.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-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Last name" }))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ import_react6.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-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Acme Corporation" }))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "EMAIL_ID") && /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ import_react6.default.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Email ID"), /* @__PURE__ */ import_react6.default.createElement("input", { type: "email", value: emailId, onChange: (e) => setEmailId(cleanEmailId(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "name@company.com" })), mode === "SIGNUP" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex items-start gap-3 mt-4" }, /* @__PURE__ */ import_react6.default.createElement("input", { type: "checkbox", id: "zairus-terms", checked: agreedToTerms, onChange: (e) => setAgreedToTerms(e.target.checked), className: "mt-0.5 w-4 h-4 bg-white border-neutral-300 rounded text-black focus:ring-black cursor-pointer", required: true }), /* @__PURE__ */ import_react6.default.createElement("label", { htmlFor: "zairus-terms", className: "text-[11px] text-neutral-500 cursor-pointer leading-snug" }, "I agree to ", companyName, "'s ", /* @__PURE__ */ import_react6.default.createElement("a", { href: termsUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Terms of Service"), " and ", /* @__PURE__ */ import_react6.default.createElement("a", { href: privacyUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Privacy Policy"), "."))), /* @__PURE__ */ import_react6.default.createElement(ThreeDActionButton, { type: "submit", disabled: isContinueDisabled(), isLoading: isSubmitting, className: "w-full mt-10" }, "Continue"))), step === "OTP" && /* @__PURE__ */ import_react6.default.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "text-center mb-10 mt-2" }, /* @__PURE__ */ import_react6.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ import_react6.default.createElement("p", { className: "text-[13px] text-neutral-500" }, userHas2FA ? "Enter the code from your authenticator app." : `Enter the code sent to ${emailId}`)), /* @__PURE__ */ import_react6.default.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
|
|
583
540
|
e.preventDefault();
|
|
584
541
|
verifyOtpCode(otp.join(""));
|
|
585
542
|
} }, /* @__PURE__ */ import_react6.default.createElement("div", { className: "flex justify-between gap-2", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ import_react6.default.createElement(
|
|
@@ -594,17 +551,19 @@ function AuthFormInner({
|
|
|
594
551
|
maxLength: 1,
|
|
595
552
|
value: digit,
|
|
596
553
|
onChange: (e) => handleOtpChange(index, e.target.value),
|
|
597
|
-
|
|
554
|
+
onKeyDown: (e) => {
|
|
555
|
+
if (e.key === "Backspace" && !otp[index] && index > 0) inputRefs.current[index - 1]?.focus();
|
|
556
|
+
},
|
|
557
|
+
className: "w-10 h-12 text-center text-lg bg-transparent border-b-2 border-neutral-100 text-black outline-none focus:border-black transition-all duration-300"
|
|
598
558
|
}
|
|
599
|
-
))), /* @__PURE__ */ import_react6.default.createElement(
|
|
600
|
-
|
|
559
|
+
))), /* @__PURE__ */ import_react6.default.createElement(ThreeDActionButton, { type: "submit", disabled: otp.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Verify Code")), userHasPasskey && /* @__PURE__ */ import_react6.default.createElement("div", { className: "mt-8 pt-6 " }, /* @__PURE__ */ import_react6.default.createElement(
|
|
560
|
+
"button",
|
|
601
561
|
{
|
|
602
|
-
|
|
603
|
-
disabled:
|
|
604
|
-
|
|
605
|
-
className: "w-full"
|
|
562
|
+
onClick: triggerPasskeyLogin,
|
|
563
|
+
disabled: isSubmitting,
|
|
564
|
+
className: "w-full py-3 rounded-full text-black bg-neutral-100 hover:bg-neutral-200 text-[13px] font-medium tracking-wide transition-colors outline-none disabled:opacity-50"
|
|
606
565
|
},
|
|
607
|
-
"
|
|
566
|
+
"Use Passkey / Biometrics"
|
|
608
567
|
))))));
|
|
609
568
|
}
|
|
610
569
|
var PipleAuth = (props) => {
|
|
@@ -2665,7 +2624,7 @@ var import_react45 = __toESM(require("react"));
|
|
|
2665
2624
|
var import_react_hot_toast8 = __toESM(require("react-hot-toast"));
|
|
2666
2625
|
var import_react46 = require("@hugeicons/react");
|
|
2667
2626
|
var import_core_free_icons16 = require("@hugeicons/core-free-icons");
|
|
2668
|
-
var
|
|
2627
|
+
var import_browser2 = require("@simplewebauthn/browser");
|
|
2669
2628
|
var UniversalSecurityPage = ({
|
|
2670
2629
|
has2FA,
|
|
2671
2630
|
hasPasskey,
|
|
@@ -2810,7 +2769,7 @@ var UniversalSecurityPage = ({
|
|
|
2810
2769
|
const options = await onRequestPasskeySetup();
|
|
2811
2770
|
let credentialResponse;
|
|
2812
2771
|
try {
|
|
2813
|
-
credentialResponse = await (0,
|
|
2772
|
+
credentialResponse = await (0, import_browser2.startRegistration)({ optionsJSON: options });
|
|
2814
2773
|
} catch (err) {
|
|
2815
2774
|
if (err.name === "NotAllowedError") {
|
|
2816
2775
|
throw new Error("Passkey setup was canceled or is not supported on this device.");
|
|
@@ -2861,7 +2820,15 @@ var UniversalSecurityPage = ({
|
|
|
2861
2820
|
toggleLoading: isPasskeyLoading,
|
|
2862
2821
|
isLast: true
|
|
2863
2822
|
}
|
|
2864
|
-
))), show2FASetupModal && /* @__PURE__ */ import_react45.default.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShow2FASetupModal(false) }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200 max-h-[90vh]" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center justify-between p-5 shrink-0 " }, /* @__PURE__ */ import_react45.default.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable 2FA"), /* @__PURE__ */ import_react45.default.createElement("button", { onClick: () => !isSubmitting && setShow2FASetupModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.CancelCircleIcon, size: 18 }))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 pt-2" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "w-full flex justify-center mb-6 mix-blend-multiply" }, twoFaQr ? /* @__PURE__ */ import_react45.default.createElement("img", { src: twoFaQr, alt: "2FA QR Code", className: "w-40 h-40 object-contain rounded-xl border border-neutral-100 p-2" }) : /* @__PURE__ */ import_react45.default.createElement("div", { className: "w-40 h-40 bg-neutral-100 rounded-xl flex items-center justify-center animate-pulse" })), /* @__PURE__ */ import_react45.default.createElement("div", { className: "
|
|
2823
|
+
))), show2FASetupModal && /* @__PURE__ */ import_react45.default.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShow2FASetupModal(false) }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200 max-h-[90vh]" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center justify-between p-5 shrink-0 " }, /* @__PURE__ */ import_react45.default.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable 2FA"), /* @__PURE__ */ import_react45.default.createElement("button", { onClick: () => !isSubmitting && setShow2FASetupModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.CancelCircleIcon, size: 18 }))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 pt-2" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "w-full flex justify-center mb-6 mix-blend-multiply" }, twoFaQr ? /* @__PURE__ */ import_react45.default.createElement("img", { src: twoFaQr, alt: "2FA QR Code", className: "w-40 h-40 object-contain rounded-xl border border-neutral-100 p-2" }) : /* @__PURE__ */ import_react45.default.createElement("div", { className: "w-40 h-40 bg-neutral-100 rounded-xl flex items-center justify-center animate-pulse" })), /* @__PURE__ */ import_react45.default.createElement("div", { className: "grid grid-cols-1 gap-y-6 gap-x-4 mb-8" }, /* @__PURE__ */ import_react45.default.createElement("div", null, /* @__PURE__ */ import_react45.default.createElement("span", { className: "text-[11px] tracking-[0.2em] text-neutral-400 block mb-2 uppercase" }, "Setup Key"), /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center gap-2 min-w-0 bg-neutral-50 px-3 py-2.5 rounded-xl " }, /* @__PURE__ */ import_react45.default.createElement("span", { className: "text-[13px] font-mono tracking-widest text-black truncate min-w-0 block flex-1" }, twoFaSecret), /* @__PURE__ */ import_react45.default.createElement(
|
|
2824
|
+
"button",
|
|
2825
|
+
{
|
|
2826
|
+
type: "button",
|
|
2827
|
+
onClick: () => handleCopySecret(twoFaSecret),
|
|
2828
|
+
className: "text-neutral-400 hover:text-black transition-colors outline-none shrink-0 ml-2"
|
|
2829
|
+
},
|
|
2830
|
+
/* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.Copy01Icon, size: 16 })
|
|
2831
|
+
)))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "text-center mb-8 mt-2" }, /* @__PURE__ */ import_react45.default.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ import_react45.default.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the 6-digit code from your app")), /* @__PURE__ */ import_react45.default.createElement("form", { className: "space-y-4", autoComplete: "off", onSubmit: (e) => {
|
|
2865
2832
|
e.preventDefault();
|
|
2866
2833
|
submitEnable2FA(otp.join(""));
|
|
2867
2834
|
} }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex justify-between gap-2 pb-6", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ import_react45.default.createElement(
|
|
@@ -2932,7 +2899,7 @@ var UniversalSecurityPage = ({
|
|
|
2932
2899
|
className: "w-full py-3.5"
|
|
2933
2900
|
},
|
|
2934
2901
|
"Verify & Disable"
|
|
2935
|
-
))))), showPasskeyModal && /* @__PURE__ */ import_react45.default.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShowPasskeyModal(false) }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center justify-between p-5 shrink-0" }, /* @__PURE__ */ import_react45.default.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable Passkey"), /* @__PURE__ */ import_react45.default.createElement("button", { onClick: () => !isSubmitting && setShowPasskeyModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.CancelCircleIcon, size: 18 }))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "p-6 text-center" }, /* @__PURE__ */ import_react45.default.createElement("p", { className: "text-[13px] text-neutral-500 mb-8 leading-relaxed" }, "Use your device's biometrics (Face ID, Touch ID, or PIN) to securely sign into your account without needing a password."), /* @__PURE__ */ import_react45.default.createElement(
|
|
2902
|
+
))))), showPasskeyModal && /* @__PURE__ */ import_react45.default.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShowPasskeyModal(false) }), /* @__PURE__ */ import_react45.default.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200" }, /* @__PURE__ */ import_react45.default.createElement("div", { className: "flex items-center justify-between p-5 shrink-0" }, /* @__PURE__ */ import_react45.default.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable Passkey"), /* @__PURE__ */ import_react45.default.createElement("button", { onClick: () => !isSubmitting && setShowPasskeyModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ import_react45.default.createElement(import_react46.HugeiconsIcon, { icon: import_core_free_icons16.CancelCircleIcon, size: 18 }))), /* @__PURE__ */ import_react45.default.createElement("div", { className: "p-6 text-center" }, /* @__PURE__ */ import_react45.default.createElement("h2", { className: "text-lg text-black mb-2 tracking-tight" }, "Simplify your Sign In"), /* @__PURE__ */ import_react45.default.createElement("p", { className: "text-[13px] text-neutral-500 mb-8 leading-relaxed" }, "Use your device's biometrics (Face ID, Touch ID, or PIN) to securely sign into your account without needing a password."), /* @__PURE__ */ import_react45.default.createElement(
|
|
2936
2903
|
ThreeDActionButton,
|
|
2937
2904
|
{
|
|
2938
2905
|
onClick: submitEnablePasskey,
|
package/dist/index.mjs
CHANGED
|
@@ -297,6 +297,7 @@ import React5, { useState as useState3, useRef as useRef2, useEffect as useEffec
|
|
|
297
297
|
import { useSearchParams } from "next/navigation";
|
|
298
298
|
import toast from "react-hot-toast";
|
|
299
299
|
import { useGoogleReCaptcha, GoogleReCaptchaProvider } from "react-google-recaptcha-v3";
|
|
300
|
+
import { startAuthentication } from "@simplewebauthn/browser";
|
|
300
301
|
var InputSpinner = () => /* @__PURE__ */ React5.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__ */ React5.createElement("circle", { className: "opacity-25", cx: "12", cy: "12", r: "10", stroke: "currentColor", strokeWidth: "4" }), /* @__PURE__ */ React5.createElement("path", { className: "opacity-75", fill: "currentColor", d: "M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" }));
|
|
301
302
|
function AuthFormInner({
|
|
302
303
|
companyName,
|
|
@@ -328,6 +329,9 @@ function AuthFormInner({
|
|
|
328
329
|
const [lastName, setLastName] = useState3("");
|
|
329
330
|
const [orgName, setOrgName] = useState3("");
|
|
330
331
|
const [agreedToTerms, setAgreedToTerms] = useState3(false);
|
|
332
|
+
const [userHas2FA, setUserHas2FA] = useState3(false);
|
|
333
|
+
const [userHasPasskey, setUserHasPasskey] = useState3(false);
|
|
334
|
+
const [authOptions, setAuthOptions] = useState3(null);
|
|
331
335
|
const [otp, setOtp] = useState3(["", "", "", "", "", ""]);
|
|
332
336
|
const inputRefs = useRef2([]);
|
|
333
337
|
const cleanAlpha = (val) => val.replace(/[^a-zA-Z\s-]/g, "");
|
|
@@ -345,9 +349,7 @@ function AuthFormInner({
|
|
|
345
349
|
const newOtp = [...otp];
|
|
346
350
|
newOtp[index] = cleanValue.slice(-1);
|
|
347
351
|
setOtp(newOtp);
|
|
348
|
-
if (cleanValue && index < 5)
|
|
349
|
-
inputRefs.current[index + 1]?.focus();
|
|
350
|
-
}
|
|
352
|
+
if (cleanValue && index < 5) inputRefs.current[index + 1]?.focus();
|
|
351
353
|
};
|
|
352
354
|
const handlePaste = (e) => {
|
|
353
355
|
e.preventDefault();
|
|
@@ -356,13 +358,10 @@ function AuthFormInner({
|
|
|
356
358
|
if (!numbersOnly) return;
|
|
357
359
|
const newOtp = [...otp];
|
|
358
360
|
const length = Math.min(numbersOnly.length, 6);
|
|
359
|
-
for (let i = 0; i < length; i++)
|
|
360
|
-
newOtp[i] = numbersOnly[i];
|
|
361
|
-
}
|
|
361
|
+
for (let i = 0; i < length; i++) newOtp[i] = numbersOnly[i];
|
|
362
362
|
setOtp(newOtp);
|
|
363
|
-
if (length < 6)
|
|
364
|
-
|
|
365
|
-
} else {
|
|
363
|
+
if (length < 6) inputRefs.current[length]?.focus();
|
|
364
|
+
else {
|
|
366
365
|
inputRefs.current[5]?.focus();
|
|
367
366
|
verifyOtpCode(newOtp.join(""));
|
|
368
367
|
}
|
|
@@ -389,7 +388,10 @@ function AuthFormInner({
|
|
|
389
388
|
recaptchaToken
|
|
390
389
|
});
|
|
391
390
|
if (res.success) {
|
|
392
|
-
|
|
391
|
+
setUserHas2FA(!!res.has2FA);
|
|
392
|
+
setUserHasPasskey(!!res.hasPasskey);
|
|
393
|
+
if (res.passkeyOptions) setAuthOptions(res.passkeyOptions);
|
|
394
|
+
toast.success(res.has2FA ? "Security check required" : "Verification code sent");
|
|
393
395
|
setStep("OTP");
|
|
394
396
|
setCountdown(60);
|
|
395
397
|
} else {
|
|
@@ -419,6 +421,23 @@ function AuthFormInner({
|
|
|
419
421
|
setIsSubmitting(false);
|
|
420
422
|
}
|
|
421
423
|
};
|
|
424
|
+
const triggerPasskeyLogin = async () => {
|
|
425
|
+
if (!authOptions || isSubmitting) return;
|
|
426
|
+
setIsSubmitting(true);
|
|
427
|
+
try {
|
|
428
|
+
const credentialResponse = await startAuthentication({ optionsJSON: authOptions });
|
|
429
|
+
const res = await onVerifyOtp({ email: emailId, isPasskey: true, passkeyCredential: credentialResponse });
|
|
430
|
+
if (res.success) {
|
|
431
|
+
window.location.href = res.redirect || redirectUrl;
|
|
432
|
+
} else {
|
|
433
|
+
toast.error(res.error || "Device verification failed.");
|
|
434
|
+
}
|
|
435
|
+
} catch (err) {
|
|
436
|
+
if (err.name !== "NotAllowedError") toast.error("Passkey verification interrupted.");
|
|
437
|
+
} finally {
|
|
438
|
+
setIsSubmitting(false);
|
|
439
|
+
}
|
|
440
|
+
};
|
|
422
441
|
const handleNextSignupStep = () => {
|
|
423
442
|
if (signupStep === "NAME") {
|
|
424
443
|
if (requireOrganization) setSignupStep("ORGANIZATION");
|
|
@@ -437,7 +456,7 @@ function AuthFormInner({
|
|
|
437
456
|
}
|
|
438
457
|
return false;
|
|
439
458
|
};
|
|
440
|
-
return /* @__PURE__ */ React5.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__ */ React5.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ React5.createElement("div", { className: "p-8 md:p-12" }, step === "INPUT" && /* @__PURE__ */ React5.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "mb-12 text-center mt-2" }, /* @__PURE__ */ React5.createElement("h2", { className: "
|
|
459
|
+
return /* @__PURE__ */ React5.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__ */ React5.createElement("div", { className: "w-full bg-white rounded-2xl overflow-hidden" }, /* @__PURE__ */ React5.createElement("div", { className: "p-8 md:p-12" }, step === "INPUT" && /* @__PURE__ */ React5.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "mb-12 text-center mt-2" }, /* @__PURE__ */ React5.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, mode === "LOGIN" ? `${companyName} ${workspaceLabel}` : "Create Account"), /* @__PURE__ */ React5.createElement("div", { className: "text-[13px] text-neutral-500" }, mode === "LOGIN" ? /* @__PURE__ */ React5.createElement(React5.Fragment, null, "Don't have an account? ", /* @__PURE__ */ React5.createElement("button", { type: "button", onClick: () => {
|
|
441
460
|
setMode("SIGNUP");
|
|
442
461
|
setSignupStep(getInitialSignupStep());
|
|
443
462
|
}, className: "text-black transition-colors ml-1" }, "Sign up")) : /* @__PURE__ */ React5.createElement(React5.Fragment, null, "Already have an account? ", /* @__PURE__ */ React5.createElement("button", { type: "button", onClick: () => setMode("LOGIN"), className: "text-black transition-colors ml-1" }, "Log in")))), /* @__PURE__ */ React5.createElement("form", { className: "space-y-6", autoComplete: "off", onSubmit: (e) => {
|
|
@@ -445,69 +464,7 @@ function AuthFormInner({
|
|
|
445
464
|
if (mode === "SIGNUP" && signupStep === "NAME") handleNextSignupStep();
|
|
446
465
|
else if (mode === "SIGNUP" && signupStep === "ORGANIZATION") handleNextSignupStep();
|
|
447
466
|
else handleAuthRequestSubmit();
|
|
448
|
-
} }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ React5.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ React5.createElement(
|
|
449
|
-
"input",
|
|
450
|
-
{
|
|
451
|
-
type: "text",
|
|
452
|
-
value: firstName,
|
|
453
|
-
onChange: (e) => setFirstName(cleanAlpha(e.target.value)),
|
|
454
|
-
required: true,
|
|
455
|
-
autoFocus: true,
|
|
456
|
-
className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
|
|
457
|
-
placeholder: "First name"
|
|
458
|
-
}
|
|
459
|
-
)), /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ React5.createElement(
|
|
460
|
-
"input",
|
|
461
|
-
{
|
|
462
|
-
type: "text",
|
|
463
|
-
value: lastName,
|
|
464
|
-
onChange: (e) => setLastName(cleanAlpha(e.target.value)),
|
|
465
|
-
required: true,
|
|
466
|
-
className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
|
|
467
|
-
placeholder: "Last name"
|
|
468
|
-
}
|
|
469
|
-
))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ React5.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ React5.createElement(
|
|
470
|
-
"input",
|
|
471
|
-
{
|
|
472
|
-
type: "text",
|
|
473
|
-
value: orgName,
|
|
474
|
-
onChange: (e) => setOrgName(cleanOrgName(e.target.value)),
|
|
475
|
-
required: true,
|
|
476
|
-
autoFocus: true,
|
|
477
|
-
className: "w-full px-2 py-3 text-sm bg-transparent border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
|
|
478
|
-
placeholder: "Acme Corporation"
|
|
479
|
-
}
|
|
480
|
-
))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "EMAIL_ID") && /* @__PURE__ */ React5.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Email ID"), /* @__PURE__ */ React5.createElement(
|
|
481
|
-
"input",
|
|
482
|
-
{
|
|
483
|
-
type: "email",
|
|
484
|
-
value: emailId,
|
|
485
|
-
onChange: (e) => setEmailId(cleanEmailId(e.target.value)),
|
|
486
|
-
required: true,
|
|
487
|
-
autoFocus: true,
|
|
488
|
-
className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300",
|
|
489
|
-
placeholder: "name@company.com"
|
|
490
|
-
}
|
|
491
|
-
)), mode === "SIGNUP" && /* @__PURE__ */ React5.createElement("div", { className: "flex items-start gap-3 mt-4" }, /* @__PURE__ */ React5.createElement(
|
|
492
|
-
"input",
|
|
493
|
-
{
|
|
494
|
-
type: "checkbox",
|
|
495
|
-
id: "zairus-terms",
|
|
496
|
-
checked: agreedToTerms,
|
|
497
|
-
onChange: (e) => setAgreedToTerms(e.target.checked),
|
|
498
|
-
className: "mt-0.5 w-4 h-4 bg-white border-neutral-300 rounded text-black focus:ring-black cursor-pointer",
|
|
499
|
-
required: true
|
|
500
|
-
}
|
|
501
|
-
), /* @__PURE__ */ React5.createElement("label", { htmlFor: "zairus-terms", className: "text-[11px] text-neutral-500 cursor-pointer leading-snug" }, "I agree to ", companyName, "'s ", /* @__PURE__ */ React5.createElement("a", { href: termsUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Terms of Service"), " and ", /* @__PURE__ */ React5.createElement("a", { href: privacyUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Privacy Policy"), "."))), /* @__PURE__ */ React5.createElement(
|
|
502
|
-
ThreeDActionButton,
|
|
503
|
-
{
|
|
504
|
-
type: "submit",
|
|
505
|
-
disabled: isContinueDisabled(),
|
|
506
|
-
isLoading: isSubmitting,
|
|
507
|
-
className: "w-full mt-10"
|
|
508
|
-
},
|
|
509
|
-
"Continue"
|
|
510
|
-
))), step === "OTP" && /* @__PURE__ */ React5.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "text-center mb-10 mt-2" }, /* @__PURE__ */ React5.createElement("h2", { className: " text-xl text-black mb-2 tracking-tight " }, "Security Check"), /* @__PURE__ */ React5.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the code sent to ", /* @__PURE__ */ React5.createElement("br", null), /* @__PURE__ */ React5.createElement("span", { className: "text-black " }, emailId))), /* @__PURE__ */ React5.createElement("form", { className: "space-y-10", autoComplete: "off", onSubmit: (e) => {
|
|
467
|
+
} }, mode === "SIGNUP" && signupStep === "NAME" && requireNames && /* @__PURE__ */ React5.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "First Name"), /* @__PURE__ */ React5.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-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "First name" })), /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Last Name"), /* @__PURE__ */ React5.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-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Last name" }))), mode === "SIGNUP" && signupStep === "ORGANIZATION" && requireOrganization && /* @__PURE__ */ React5.createElement("div", { className: "flex flex-col gap-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5 relative" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Organization Name"), /* @__PURE__ */ React5.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-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "Acme Corporation" }))), (mode === "LOGIN" || mode === "SIGNUP" && signupStep === "EMAIL_ID") && /* @__PURE__ */ React5.createElement("div", { className: "space-y-6" }, /* @__PURE__ */ React5.createElement("div", { className: "space-y-1.5" }, /* @__PURE__ */ React5.createElement("label", { className: "text-[11px] text-neutral-400 tracking-[0.2em] block " }, "Email ID"), /* @__PURE__ */ React5.createElement("input", { type: "email", value: emailId, onChange: (e) => setEmailId(cleanEmailId(e.target.value)), required: true, autoFocus: true, className: "w-full px-2 py-3 bg-transparent text-sm border-b border-neutral-100 text-black outline-none focus:border-black transition-all duration-300", placeholder: "name@company.com" })), mode === "SIGNUP" && /* @__PURE__ */ React5.createElement("div", { className: "flex items-start gap-3 mt-4" }, /* @__PURE__ */ React5.createElement("input", { type: "checkbox", id: "zairus-terms", checked: agreedToTerms, onChange: (e) => setAgreedToTerms(e.target.checked), className: "mt-0.5 w-4 h-4 bg-white border-neutral-300 rounded text-black focus:ring-black cursor-pointer", required: true }), /* @__PURE__ */ React5.createElement("label", { htmlFor: "zairus-terms", className: "text-[11px] text-neutral-500 cursor-pointer leading-snug" }, "I agree to ", companyName, "'s ", /* @__PURE__ */ React5.createElement("a", { href: termsUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Terms of Service"), " and ", /* @__PURE__ */ React5.createElement("a", { href: privacyUrl, target: "_blank", rel: "noreferrer", className: "text-black underline " }, "Privacy Policy"), "."))), /* @__PURE__ */ React5.createElement(ThreeDActionButton, { type: "submit", disabled: isContinueDisabled(), isLoading: isSubmitting, className: "w-full mt-10" }, "Continue"))), step === "OTP" && /* @__PURE__ */ React5.createElement("div", { className: "animate-in fade-in duration-300" }, /* @__PURE__ */ React5.createElement("div", { className: "text-center mb-10 mt-2" }, /* @__PURE__ */ React5.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ React5.createElement("p", { className: "text-[13px] text-neutral-500" }, userHas2FA ? "Enter the code from your authenticator app." : `Enter the code sent to ${emailId}`)), /* @__PURE__ */ React5.createElement("form", { className: "space-y-8", autoComplete: "off", onSubmit: (e) => {
|
|
511
468
|
e.preventDefault();
|
|
512
469
|
verifyOtpCode(otp.join(""));
|
|
513
470
|
} }, /* @__PURE__ */ React5.createElement("div", { className: "flex justify-between gap-2", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ React5.createElement(
|
|
@@ -522,17 +479,19 @@ function AuthFormInner({
|
|
|
522
479
|
maxLength: 1,
|
|
523
480
|
value: digit,
|
|
524
481
|
onChange: (e) => handleOtpChange(index, e.target.value),
|
|
525
|
-
|
|
482
|
+
onKeyDown: (e) => {
|
|
483
|
+
if (e.key === "Backspace" && !otp[index] && index > 0) inputRefs.current[index - 1]?.focus();
|
|
484
|
+
},
|
|
485
|
+
className: "w-10 h-12 text-center text-lg bg-transparent border-b-2 border-neutral-100 text-black outline-none focus:border-black transition-all duration-300"
|
|
526
486
|
}
|
|
527
|
-
))), /* @__PURE__ */ React5.createElement(
|
|
528
|
-
|
|
487
|
+
))), /* @__PURE__ */ React5.createElement(ThreeDActionButton, { type: "submit", disabled: otp.join("").length < 6, isLoading: isSubmitting, className: "w-full" }, "Verify Code")), userHasPasskey && /* @__PURE__ */ React5.createElement("div", { className: "mt-8 pt-6 " }, /* @__PURE__ */ React5.createElement(
|
|
488
|
+
"button",
|
|
529
489
|
{
|
|
530
|
-
|
|
531
|
-
disabled:
|
|
532
|
-
|
|
533
|
-
className: "w-full"
|
|
490
|
+
onClick: triggerPasskeyLogin,
|
|
491
|
+
disabled: isSubmitting,
|
|
492
|
+
className: "w-full py-3 rounded-full text-black bg-neutral-100 hover:bg-neutral-200 text-[13px] font-medium tracking-wide transition-colors outline-none disabled:opacity-50"
|
|
534
493
|
},
|
|
535
|
-
"
|
|
494
|
+
"Use Passkey / Biometrics"
|
|
536
495
|
))))));
|
|
537
496
|
}
|
|
538
497
|
var PipleAuth = (props) => {
|
|
@@ -2849,7 +2808,15 @@ var UniversalSecurityPage = ({
|
|
|
2849
2808
|
toggleLoading: isPasskeyLoading,
|
|
2850
2809
|
isLast: true
|
|
2851
2810
|
}
|
|
2852
|
-
))), show2FASetupModal && /* @__PURE__ */ React27.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ React27.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShow2FASetupModal(false) }), /* @__PURE__ */ React27.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200 max-h-[90vh]" }, /* @__PURE__ */ React27.createElement("div", { className: "flex items-center justify-between p-5 shrink-0 " }, /* @__PURE__ */ React27.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable 2FA"), /* @__PURE__ */ React27.createElement("button", { onClick: () => !isSubmitting && setShow2FASetupModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: CancelCircleIcon6, size: 18 }))), /* @__PURE__ */ React27.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 pt-2" }, /* @__PURE__ */ React27.createElement("div", { className: "w-full flex justify-center mb-6 mix-blend-multiply" }, twoFaQr ? /* @__PURE__ */ React27.createElement("img", { src: twoFaQr, alt: "2FA QR Code", className: "w-40 h-40 object-contain rounded-xl border border-neutral-100 p-2" }) : /* @__PURE__ */ React27.createElement("div", { className: "w-40 h-40 bg-neutral-100 rounded-xl flex items-center justify-center animate-pulse" })), /* @__PURE__ */ React27.createElement("div", { className: "
|
|
2811
|
+
))), show2FASetupModal && /* @__PURE__ */ React27.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ React27.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShow2FASetupModal(false) }), /* @__PURE__ */ React27.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200 max-h-[90vh]" }, /* @__PURE__ */ React27.createElement("div", { className: "flex items-center justify-between p-5 shrink-0 " }, /* @__PURE__ */ React27.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable 2FA"), /* @__PURE__ */ React27.createElement("button", { onClick: () => !isSubmitting && setShow2FASetupModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: CancelCircleIcon6, size: 18 }))), /* @__PURE__ */ React27.createElement("div", { className: "flex-1 overflow-y-auto custom-scrollbar p-6 pt-2" }, /* @__PURE__ */ React27.createElement("div", { className: "w-full flex justify-center mb-6 mix-blend-multiply" }, twoFaQr ? /* @__PURE__ */ React27.createElement("img", { src: twoFaQr, alt: "2FA QR Code", className: "w-40 h-40 object-contain rounded-xl border border-neutral-100 p-2" }) : /* @__PURE__ */ React27.createElement("div", { className: "w-40 h-40 bg-neutral-100 rounded-xl flex items-center justify-center animate-pulse" })), /* @__PURE__ */ React27.createElement("div", { className: "grid grid-cols-1 gap-y-6 gap-x-4 mb-8" }, /* @__PURE__ */ React27.createElement("div", null, /* @__PURE__ */ React27.createElement("span", { className: "text-[11px] tracking-[0.2em] text-neutral-400 block mb-2 uppercase" }, "Setup Key"), /* @__PURE__ */ React27.createElement("div", { className: "flex items-center gap-2 min-w-0 bg-neutral-50 px-3 py-2.5 rounded-xl " }, /* @__PURE__ */ React27.createElement("span", { className: "text-[13px] font-mono tracking-widest text-black truncate min-w-0 block flex-1" }, twoFaSecret), /* @__PURE__ */ React27.createElement(
|
|
2812
|
+
"button",
|
|
2813
|
+
{
|
|
2814
|
+
type: "button",
|
|
2815
|
+
onClick: () => handleCopySecret(twoFaSecret),
|
|
2816
|
+
className: "text-neutral-400 hover:text-black transition-colors outline-none shrink-0 ml-2"
|
|
2817
|
+
},
|
|
2818
|
+
/* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: Copy01Icon3, size: 16 })
|
|
2819
|
+
)))), /* @__PURE__ */ React27.createElement("div", { className: "text-center mb-8 mt-2" }, /* @__PURE__ */ React27.createElement("h2", { className: "text-xl text-black mb-2 tracking-tight" }, "Security Check"), /* @__PURE__ */ React27.createElement("p", { className: "text-[13px] text-neutral-500" }, "Enter the 6-digit code from your app")), /* @__PURE__ */ React27.createElement("form", { className: "space-y-4", autoComplete: "off", onSubmit: (e) => {
|
|
2853
2820
|
e.preventDefault();
|
|
2854
2821
|
submitEnable2FA(otp.join(""));
|
|
2855
2822
|
} }, /* @__PURE__ */ React27.createElement("div", { className: "flex justify-between gap-2 pb-6", onPaste: handlePaste }, otp.map((digit, index) => /* @__PURE__ */ React27.createElement(
|
|
@@ -2920,7 +2887,7 @@ var UniversalSecurityPage = ({
|
|
|
2920
2887
|
className: "w-full py-3.5"
|
|
2921
2888
|
},
|
|
2922
2889
|
"Verify & Disable"
|
|
2923
|
-
))))), showPasskeyModal && /* @__PURE__ */ React27.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ React27.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShowPasskeyModal(false) }), /* @__PURE__ */ React27.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200" }, /* @__PURE__ */ React27.createElement("div", { className: "flex items-center justify-between p-5 shrink-0" }, /* @__PURE__ */ React27.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable Passkey"), /* @__PURE__ */ React27.createElement("button", { onClick: () => !isSubmitting && setShowPasskeyModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: CancelCircleIcon6, size: 18 }))), /* @__PURE__ */ React27.createElement("div", { className: "p-6 text-center" }, /* @__PURE__ */ React27.createElement("p", { className: "text-[13px] text-neutral-500 mb-8 leading-relaxed" }, "Use your device's biometrics (Face ID, Touch ID, or PIN) to securely sign into your account without needing a password."), /* @__PURE__ */ React27.createElement(
|
|
2890
|
+
))))), showPasskeyModal && /* @__PURE__ */ React27.createElement("div", { className: "fixed inset-0 z-120 flex items-center justify-center p-4 pointer-events-auto" }, /* @__PURE__ */ React27.createElement("div", { className: "absolute inset-0 bg-black/40 ", onClick: () => !isSubmitting && setShowPasskeyModal(false) }), /* @__PURE__ */ React27.createElement("div", { className: "relative w-full max-w-sm bg-white rounded-2xl flex flex-col overflow-hidden shadow-2xl animate-in zoom-in-95 duration-200" }, /* @__PURE__ */ React27.createElement("div", { className: "flex items-center justify-between p-5 shrink-0" }, /* @__PURE__ */ React27.createElement("h3", { className: "text-[15px] text-black tracking-tight" }, "Enable Passkey"), /* @__PURE__ */ React27.createElement("button", { onClick: () => !isSubmitting && setShowPasskeyModal(false), className: "text-neutral-400 hover:text-black transition-colors outline-none" }, /* @__PURE__ */ React27.createElement(HugeiconsIcon19, { icon: CancelCircleIcon6, size: 18 }))), /* @__PURE__ */ React27.createElement("div", { className: "p-6 text-center" }, /* @__PURE__ */ React27.createElement("h2", { className: "text-lg text-black mb-2 tracking-tight" }, "Simplify your Sign In"), /* @__PURE__ */ React27.createElement("p", { className: "text-[13px] text-neutral-500 mb-8 leading-relaxed" }, "Use your device's biometrics (Face ID, Touch ID, or PIN) to securely sign into your account without needing a password."), /* @__PURE__ */ React27.createElement(
|
|
2924
2891
|
ThreeDActionButton,
|
|
2925
2892
|
{
|
|
2926
2893
|
onClick: submitEnablePasskey,
|