@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
426 lines (425 loc) • 12.3 kB
JavaScript
'use client';
import { jsx as e, jsxs as h } from "react/jsx-runtime";
import "@emotion/styled";
import { Button as $ } from "../../../ui/button/button.js";
import { Card as G, CardBody as J } from "../../../ui/card/card.js";
import { Chip as K } from "../../../ui/chip/chip.js";
import { Modal as O, ModalHeader as Q, ModalBody as U } from "../../../ui/modal/modal.js";
import { CheckCircleIcon as W, XCircleIcon as A, ArrowPathIcon as Y, DevicePhoneMobileIcon as T } from "@heroicons/react/24/outline";
import { useState as S, useEffect as z, useCallback as I } from "react";
import { useAuth as Z } from "../../../../hooks/use-auth.js";
import { useConfig as _ } from "../../../../hooks/use-config.js";
import { withErrorBoundary as C } from "../../common/error-boundary.js";
import { VerificationError as L, VerificationInput as ee } from "../../common/verification.js";
function re({
phoneNumber: i,
userId: r,
organizationId: s,
autoSubmit: t = !0,
codeLength: o = 6,
resendDelay: a = 30,
maxResendAttempts: f = 3,
expirationTime: m = 300,
// 5 minutes
onVerificationSuccess: y,
onVerificationError: g,
onCodeSent: k,
onCodeResent: E
}) {
const { client: x } = Z(), [u, R] = S(""), [p, v] = S("idle"), [B, l] = S(null), [c, P] = S(0), [w, M] = S(0), [N, b] = S(null);
z(() => {
if (w > 0) {
const n = setTimeout(
() => M((d) => d - 1),
1e3
);
return () => clearTimeout(n);
}
}, [w]), z(() => {
if (N) {
const d = setInterval(() => {
/* @__PURE__ */ new Date() > N && (v("expired"), l("Verification code has expired"));
}, 1e3);
return () => clearInterval(d);
}
}, [N]), z(() => {
t && u.length === o && p === "sent" && D();
}, [u, o, t, p]);
const V = I(async () => {
if (!i || !r || !s) {
l("Missing required parameters for phone verification");
return;
}
try {
v("sending"), l(null);
const n = await x.mfa.sendSMSCode({
orgId: s,
userId: r
});
if (n.success)
v("sent"), M(a), b(new Date(Date.now() + m * 1e3)), k?.();
else
throw new Error(
n.message || "Failed to send SMS verification code"
);
} catch (n) {
const d = n instanceof Error ? n : new Error("Failed to send SMS verification code");
v("error"), l(d.message), g?.(d);
}
}, [
i,
r,
s,
x,
a,
m,
k,
g
]), j = I(async () => {
if (c >= f) {
l("Maximum resend attempts reached");
return;
}
try {
P((n) => n + 1), await V(), E?.(c + 1);
} catch (n) {
const d = n instanceof Error ? n : new Error("Failed to resend SMS code");
g?.(d);
}
}, [
c,
f,
V,
E,
g
]), D = I(async () => {
if (!u || u.length !== o) {
l("Please enter a valid verification code");
return;
}
if (!i) {
l("Phone number is required for verification");
return;
}
try {
if (v("verifying"), l(null), (await x.authentication.verifyPhone({
verificationRequest: {
phoneNumber: i,
code: u
}
})).verified)
v("verified"), y?.({
phoneNumber: i || "",
verified: !0
});
else
throw new Error("Invalid verification code");
} catch (n) {
const d = n instanceof Error ? n : new Error("Phone verification failed");
v("error"), l(d.message), g?.(d);
}
}, [
u,
o,
i,
x,
y,
g
]), q = I(() => {
R(""), v("idle"), l(null), P(0), M(0), b(null);
}, []);
return {
code: u,
setCode: R,
status: p,
error: B,
resendAttempts: c,
timeRemaining: w,
expiresAt: N,
canResend: w === 0 && c < f,
sendCode: V,
resendCode: j,
handleVerify: D,
reset: q
};
}
function ne(i) {
const r = i.replace(/\D/g, "");
return r.length === 10 ? `(${r.slice(0, 3)}) ${r.slice(3, 6)}-${r.slice(6)}` : r.length === 11 && r[0] === "1" ? `+1 (${r.slice(1, 4)}) ${r.slice(4, 7)}-${r.slice(7)}` : i;
}
function X(i) {
return ne(i).replace(/(\d{3})-(\d{4})/, "***-$2");
}
const H = C(function({
phoneNumber: r,
userId: s,
organizationId: t,
autoSubmit: o = !0,
codeLength: a = 6,
resendDelay: f = 30,
maxResendAttempts: m = 3,
expirationTime: y = 300,
onVerificationSuccess: g,
onVerificationError: k,
onCodeSent: E,
onCodeResent: x,
className: u,
style: R,
disabled: p = !1
}) {
const { config: v } = _(), {
code: B,
setCode: l,
status: c,
error: P,
resendAttempts: w,
timeRemaining: M,
canResend: N,
sendCode: b,
resendCode: V,
handleVerify: j
} = re({
phoneNumber: r,
userId: s,
organizationId: t,
autoSubmit: o,
codeLength: a,
resendDelay: f,
maxResendAttempts: m,
expirationTime: y,
onVerificationSuccess: g,
onVerificationError: k,
onCodeSent: E,
onCodeResent: x
}), D = (F) => {
F.length <= a && l(F);
}, q = (F) => {
F.preventDefault(), j();
}, n = r ? X(r) : "";
return /* @__PURE__ */ e("div", { className: u, style: R, children: c === "verified" ? /* @__PURE__ */ h("div", { className: "text-center py-8", children: [
/* @__PURE__ */ e(W, { className: "h-16 w-16 text-success mx-auto mb-4" }),
/* @__PURE__ */ e("h3", { className: "text-lg font-semibold mb-2", children: "Phone Verified!" }),
/* @__PURE__ */ e("p", { className: "text-default-500", children: "Your phone number has been successfully verified." })
] }) : c === "expired" ? /* @__PURE__ */ h("div", { className: "text-center py-8", children: [
/* @__PURE__ */ e(A, { className: "h-16 w-16 text-warning mx-auto mb-4" }),
/* @__PURE__ */ e("h3", { className: "text-lg font-semibold mb-2", children: "Code Expired" }),
/* @__PURE__ */ e("p", { className: "text-default-500 mb-4", children: "The SMS verification code has expired. Please request a new one." }),
/* @__PURE__ */ e(
$,
{
color: "primary",
onClick: b,
disabled: p,
startContent: /* @__PURE__ */ e(Y, { className: "h-4 w-4" }),
children: "Send New Code"
}
)
] }) : /* @__PURE__ */ h("form", { onSubmit: q, className: "space-y-4", children: [
/* @__PURE__ */ h("div", { className: "text-center", children: [
/* @__PURE__ */ e(T, { className: "h-12 w-12 text-primary mx-auto mb-4" }),
/* @__PURE__ */ e("h3", { className: "text-lg font-semibold mb-2", children: "Verify Your Phone" }),
/* @__PURE__ */ h("p", { className: "text-default-500 mb-4", children: [
"We've sent a verification code to ",
n
] })
] }),
P && /* @__PURE__ */ e(L, { error: P }),
/* @__PURE__ */ h("div", { className: "space-y-4", children: [
/* @__PURE__ */ e(
ee,
{
value: B,
onChange: D,
length: a,
disabled: p || c === "verifying",
placeholder: "Enter SMS code",
type: "tel"
}
),
/* @__PURE__ */ e(
$,
{
type: "submit",
color: "primary",
className: "w-full",
isLoading: c === "verifying",
disabled: p || B.length !== a || c === "verifying",
children: c === "verifying" ? "Verifying..." : "Verify Phone"
}
),
/* @__PURE__ */ h("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ e("span", { className: "text-sm text-default-500", children: "Didn't receive the code?" }),
/* @__PURE__ */ e(
te,
{
onResend: V,
disabled: p || !N,
remainingTime: M,
attempt: w,
maxAttempts: m
}
)
] })
] }),
c === "idle" && /* @__PURE__ */ e(
$,
{
color: "primary",
variant: "flat",
className: "w-full",
onClick: b,
disabled: p,
isLoading: c === "sending",
children: c === "sending" ? "Sending..." : "Send SMS Code"
}
)
] }) });
}), ve = C(
function({
showHeader: r = !0,
showInstructions: s = !0,
...t
}) {
return /* @__PURE__ */ h("div", { className: "space-y-6", children: [
r && /* @__PURE__ */ h("div", { className: "text-center", children: [
/* @__PURE__ */ e("h2", { className: "text-2xl font-bold", children: "Phone Verification" }),
s && /* @__PURE__ */ e("p", { className: "text-default-500 mt-2", children: "Please verify your phone number to continue" })
] }),
/* @__PURE__ */ e(H, { ...t })
] });
}
), ge = C(
function({
isOpen: r,
onClose: s,
isDismissable: t = !0,
...o
}) {
return /* @__PURE__ */ h(
O,
{
isOpen: r,
onClose: s,
isDismissable: t,
size: "md",
classNames: {
backdrop: "bg-gradient-to-t from-zinc-900 to-zinc-900/10 backdrop-opacity-20"
},
children: [
/* @__PURE__ */ e(Q, { className: "flex flex-col gap-1", children: "Phone Verification" }),
/* @__PURE__ */ e(U, { children: /* @__PURE__ */ e(H, { ...o }) })
]
}
);
}
), ye = C(
function({
variant: r = "shadow",
radius: s = "lg",
...t
}) {
return /* @__PURE__ */ e(
G,
{
className: `max-w-md mx-auto ${t.className || ""}`,
variant: r,
radius: s,
children: /* @__PURE__ */ e(J, { className: "p-6", children: /* @__PURE__ */ e(H, { ...t }) })
}
);
}
), xe = C(
function({
status: r,
phoneNumber: s,
onRetry: t,
className: o
}) {
const f = (() => {
const m = X(s);
switch (r) {
case "sent":
return {
color: "primary",
icon: /* @__PURE__ */ e(T, { className: "h-4 w-4" }),
message: `SMS code sent to ${m}`
};
case "verified":
return {
color: "success",
icon: /* @__PURE__ */ e(W, { className: "h-4 w-4" }),
message: `Phone ${m} verified successfully`
};
case "error":
return {
color: "danger",
icon: /* @__PURE__ */ e(A, { className: "h-4 w-4" }),
message: "SMS verification failed"
};
case "expired":
return {
color: "warning",
icon: /* @__PURE__ */ e(A, { className: "h-4 w-4" }),
message: "SMS code expired"
};
default:
return {
color: "default",
icon: /* @__PURE__ */ e(T, { className: "h-4 w-4" }),
message: "Phone verification"
};
}
})();
return /* @__PURE__ */ h(
"div",
{
className: `flex items-center justify-between p-3 rounded-lg ${o || ""}`,
children: [
/* @__PURE__ */ e("div", { className: "flex items-center gap-3", children: /* @__PURE__ */ e(K, { color: f.color, variant: "flat", startContent: f.icon, children: f.message }) }),
(r === "error" || r === "expired") && t && /* @__PURE__ */ e(
$,
{
size: "sm",
color: "primary",
variant: "flat",
onClick: t,
startContent: /* @__PURE__ */ e(Y, { className: "h-3 w-3" }),
children: "Retry"
}
)
]
}
);
}
), te = C(function({
onResend: r,
disabled: s = !1,
remainingTime: t = 0,
attempt: o = 0,
maxAttempts: a = 3,
className: f
}) {
const m = t === 0 && o < a, y = a - o;
return /* @__PURE__ */ e(
$,
{
size: "sm",
color: "primary",
variant: "light",
onClick: r,
disabled: s || !m,
className: f,
startContent: m ? /* @__PURE__ */ e(Y, { className: "h-3 w-3" }) : void 0,
children: t > 0 ? `Resend in ${t}s` : o >= a ? "Max attempts reached" : `Resend SMS${o > 0 ? ` (${y} left)` : ""}`
}
);
});
export {
H as PhoneVerification,
ye as PhoneVerificationCard,
ve as PhoneVerificationForm,
ge as PhoneVerificationModal,
xe as PhoneVerificationStatus,
te as ResendSMSButton
};
//# sourceMappingURL=phone-verification.js.map