UNPKG

@frank-auth/react

Version:

Flexible and customizable React UI components for Frank Authentication

426 lines (425 loc) 12.3 kB
'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