UNPKG

@frank-auth/react

Version:

Flexible and customizable React UI components for Frank Authentication

453 lines (452 loc) 15.3 kB
'use client'; import { jsx as e, jsxs as s, Fragment as G } from "react/jsx-runtime"; import "@emotion/styled"; import { Button as X } from "../../ui/button/button.js"; import { Spinner as ae } from "../../ui/spinner/spinner.js"; import { Link as q } from "../../ui/link/link.js"; import { Divider as V } from "../../ui/divider/divider.js"; import { EnvelopeIcon as H, ArrowLeftIcon as U, ExclamationTriangleIcon as ne, KeyIcon as J, CheckCircleIcon as ie } from "@heroicons/react/24/outline"; import { motion as K } from "framer-motion"; import le, { useState as g, useCallback as D, useMemo as B, useEffect as oe } from "react"; import { useAuth as Z } from "../../../hooks/use-auth.js"; import { useConfig as z } from "../../../hooks/use-config.js"; import { useMagicLink as ee } from "../../../hooks/use-magic-link.js"; import ce from "../../forms/email-field.js"; import A from "../../forms/form-wrapper.js"; import Q from "../../forms/password-field.js"; const te = le.memo( ({ password: n, requirements: i = { minLength: 8, requireUppercase: !0, requireLowercase: !0, requireNumbers: !0, requireSymbols: !1 } }) => { const d = B(() => { const t = []; return i.minLength && t.push({ label: `At least ${i.minLength} characters`, passed: n.length >= i.minLength }), i.requireUppercase && t.push({ label: "One uppercase letter", passed: /[A-Z]/.test(n) }), i.requireLowercase && t.push({ label: "One lowercase letter", passed: /[a-z]/.test(n) }), i.requireNumbers && t.push({ label: "One number", passed: /\d/.test(n) }), i.requireSymbols && t.push({ label: "One special character", passed: /[!@#$%^&*(),.?":{}|<>]/.test(n) }), t; }, [n, i]), v = d.filter((t) => t.passed).length, o = Math.round(v / d.length * 100), w = () => o < 40 ? "bg-danger-500" : o < 70 ? "bg-warning-500" : "bg-success-500", N = () => o < 40 ? "Weak" : o < 70 ? "Medium" : "Strong"; return /* @__PURE__ */ s("div", { className: "space-y-3", children: [ /* @__PURE__ */ s("div", { className: "space-y-2", children: [ /* @__PURE__ */ s("div", { className: "flex justify-between text-sm", children: [ /* @__PURE__ */ e("span", { className: "text-default-600", children: "Password strength" }), /* @__PURE__ */ e( "span", { className: `font-medium ${o < 40 ? "text-danger-600" : o < 70 ? "text-warning-600" : "text-success-600"}`, children: N() } ) ] }), /* @__PURE__ */ e("div", { className: "w-full bg-default-200 rounded-full h-2", children: /* @__PURE__ */ e( K.div, { className: `h-2 rounded-full ${w()}`, initial: { width: 0 }, animate: { width: `${o}%` }, transition: { duration: 0.3 } } ) }) ] }), /* @__PURE__ */ e("div", { className: "space-y-1", children: d.map((t, x) => /* @__PURE__ */ s("div", { className: "flex items-center gap-2 text-sm", children: [ /* @__PURE__ */ e( ie, { className: `w-4 h-4 ${t.passed ? "text-success-600" : "text-default-300"}` } ), /* @__PURE__ */ e( "span", { className: t.passed ? "text-success-700 dark:text-success-400" : "text-default-500", children: t.label } ) ] }, x)) }) ] }); } ); te.displayName = "PasswordStrengthIndicator"; function de({ email: n = "", onSuccess: i, onError: d, title: v = "Forgot Password", subtitle: o = "Enter your email address and we'll send you a link to reset your password.", redirectUrl: w, variant: N = "default", size: t = "md", radius: x = "md", className: C = "", showBackLink: L = !0, organizationId: f }) { const { isValidEmail: _ } = ee(), { requestPasswordReset: I, isLoading: k } = Z(), { components: E, linksPath: F } = z(), [m, r] = g(n), [$, y] = g(!1), [M, u] = g(null), R = E.Button ?? X, T = D( async (b) => { if (b.preventDefault(), u(null), !m) { u("Please enter your email address"); return; } if (!_(m)) { u("Please enter a valid email address"); return; } try { const P = w || (typeof window < "u" ? `${window.location.origin}/auth/reset-password` : "/auth/reset-password"), S = await I({ email: m, redirectUrl: P // organizationId, }); S.success ? (y(!0), i?.(m)) : (u(S.error || "Failed to send reset link"), d?.(new Error(S.error || "Failed to send reset link"))); } catch (p) { const P = p instanceof Error ? p.message : "Failed to send reset link"; u(P), d?.(p instanceof Error ? p : new Error(P)); } }, [ m, _, I, w, f, i, d ] ), c = D(async () => { y(!1), u(null); }, []), h = B( () => ({ size: t, variant: "flat", className: `space-y-6 ${C}`, title: v, subtitle: $ ? void 0 : o, showCard: N === "card" }), [t, C, v, o, N, $] ); return $ ? /* @__PURE__ */ e(A, { ...h, children: /* @__PURE__ */ s("div", { className: "text-center space-y-4", children: [ /* @__PURE__ */ e( K.div, { initial: { scale: 0 }, animate: { scale: 1 }, className: "mx-auto w-16 h-16 rounded-full bg-success-100 dark:bg-success-900/30 flex items-center justify-center", children: /* @__PURE__ */ e(H, { className: "w-8 h-8 text-success-600" }) } ), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-xl font-semibold text-foreground mb-2", children: "Check Your Email" }), /* @__PURE__ */ s("p", { className: "text-default-500 text-sm", children: [ "We've sent a password reset link to ", /* @__PURE__ */ e("strong", { children: m }) ] }) ] }), /* @__PURE__ */ s("div", { className: "space-y-3", children: [ /* @__PURE__ */ e("p", { className: "text-sm text-default-400", children: "Didn't receive the email? Check your spam folder." }), /* @__PURE__ */ e(R, { variant: "light", size: "sm", onPress: c, children: "Send Another Email" }) ] }), L && /* @__PURE__ */ s(G, { children: [ /* @__PURE__ */ e(V, { className: "my-4" }), /* @__PURE__ */ s( q, { href: F?.signIn || "/auth/sign-in", className: "flex items-center justify-center gap-2 text-sm", children: [ /* @__PURE__ */ e(U, { className: "w-4 h-4" }), "Back to Sign In" ] } ) ] }) ] }) }) : /* @__PURE__ */ e(A, { ...h, onSubmit: T, children: /* @__PURE__ */ s("div", { className: "space-y-4", children: [ /* @__PURE__ */ e( ce, { label: "Email Address", name: "email", placeholder: "Enter your email address", value: m, onChange: r, startContent: /* @__PURE__ */ e(H, { className: "w-4 h-4 text-default-400" }), size: t, radius: x, required: !0, disabled: k, variant: "bordered", autoFocus: !0 } ), M && /* @__PURE__ */ e("div", { className: "text-danger-600 text-sm bg-danger-50 dark:bg-danger-900/20 rounded-lg p-3", children: M }), /* @__PURE__ */ e( R, { type: "submit", color: "primary", size: t, radius: x, className: "w-full", isLoading: k, isDisabled: !m || k, children: k ? "Sending..." : "Send Reset Link" } ), L && /* @__PURE__ */ s(G, { children: [ /* @__PURE__ */ e(V, { className: "my-4" }), /* @__PURE__ */ e("div", { className: "text-center", children: /* @__PURE__ */ s( q, { href: F?.signIn || "/auth/sign-in", className: "flex items-center justify-center gap-2 text-sm", children: [ /* @__PURE__ */ e(U, { className: "w-4 h-4" }), "Back to Sign In" ] } ) }) ] }) ] }) }); } function ue({ token: n, onSuccess: i, onError: d, title: v = "Reset Password", subtitle: o = "Enter your new password below.", redirectUrl: w = "/auth/sign-in", variant: N = "default", size: t = "md", radius: x = "md", className: C = "", autoVerify: L = !0, passwordRequirements: f = { minLength: 8, requireUppercase: !0, requireLowercase: !0, requireNumbers: !0, requireSymbols: !1 } }) { const { signIn: _, validateToken: I } = Z(), { components: k, linksPath: E } = z(), { extractTokenFromUrl: F } = ee(), { resetPassword: m } = Z(), [r, $] = g(""), [y, M] = g(""), [u, R] = g(!1), [T, c] = g(null), [h, b] = g("idle"), [p, P] = g(null), S = k.Button ?? X, j = B(() => n || F(), [n, F]); oe(() => { L && h === "idle" && se(); }, [L, j, h]); const se = D(async () => { if (!j) { b("invalid"), c("No reset token found in URL"); return; } try { b("verifying"), c(null); const a = await I({ token: j, type: "password" }); a.success ? (b("valid"), P(j)) : (b("invalid"), c(a.error || "Invalid or expired reset link")); } catch (a) { b("invalid"); const l = a instanceof Error ? a.message : "Failed to verify reset token"; c(l), d?.(a instanceof Error ? a : new Error(l)); } }, [j, I, d]), O = B(() => { const a = []; return f.minLength && a.push(r.length >= f.minLength), f.requireUppercase && a.push(/[A-Z]/.test(r)), f.requireLowercase && a.push(/[a-z]/.test(r)), f.requireNumbers && a.push(/\d/.test(r)), f.requireSymbols && a.push(/[!@#$%^&*(),.?":{}|<>]/.test(r)), a.every((l) => l); }, [r, f]), re = D( async (a) => { if (a.preventDefault(), c(null), !r || !y) { c("Please fill in all fields"); return; } if (r !== y) { c("Passwords do not match"); return; } if (!O) { c("Password does not meet requirements"); return; } if (!p) { c("Invalid reset token"); return; } try { R(!0); const l = await m({ token: p, newPassword: r }); l.status === "complete" && l.user ? (i?.(l), setTimeout(() => { typeof window < "u" && (window.location.href = w); }, 2e3)) : c(l.error?.message || "Failed to reset password"); } catch (l) { const Y = l instanceof Error ? l.message : "Failed to reset password"; c(Y), d?.(l instanceof Error ? l : new Error(Y)); } finally { R(!1); } }, [ r, y, O, p, m, i, d, w ] ), W = B( () => ({ size: t, variant: "flat", className: `space-y-6 ${C}`, title: v, subtitle: h === "valid" ? o : void 0, showCard: N === "card" }), [t, C, v, o, N, h] ); return h === "verifying" ? /* @__PURE__ */ e(A, { ...W, children: /* @__PURE__ */ s("div", { className: "text-center space-y-4", children: [ /* @__PURE__ */ e(ae, { size: "lg" }), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-xl font-semibold text-foreground mb-2", children: "Verifying Reset Link" }), /* @__PURE__ */ e("p", { className: "text-default-500 text-sm", children: "Please wait while we verify your reset link..." }) ] }) ] }) }) : h === "invalid" ? /* @__PURE__ */ e(A, { ...W, children: /* @__PURE__ */ s("div", { className: "text-center space-y-4", children: [ /* @__PURE__ */ e( K.div, { initial: { scale: 0 }, animate: { scale: 1 }, className: "mx-auto w-16 h-16 rounded-full bg-danger-100 dark:bg-danger-900/30 flex items-center justify-center", children: /* @__PURE__ */ e(ne, { className: "w-8 h-8 text-danger-600" }) } ), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-xl font-semibold text-foreground mb-2", children: "Invalid Reset Link" }), /* @__PURE__ */ e("p", { className: "text-default-500 text-sm", children: T || "This password reset link is invalid or has expired." }) ] }), /* @__PURE__ */ s("div", { className: "space-y-3", children: [ /* @__PURE__ */ e( S, { as: q, href: E?.forgotPassword || "/auth/forgot-password", color: "primary", children: "Request New Reset Link" } ), /* @__PURE__ */ e("div", { className: "text-center", children: /* @__PURE__ */ s( q, { href: E?.signIn || "/auth/sign-in", className: "text-sm flex items-center justify-center gap-2", children: [ /* @__PURE__ */ e(U, { className: "w-4 h-4" }), "Back to Sign In" ] } ) }) ] }) ] }) }) : /* @__PURE__ */ e(A, { ...W, onSubmit: re, children: /* @__PURE__ */ s("div", { className: "space-y-4", children: [ /* @__PURE__ */ e( Q, { label: "New Password", name: "password", placeholder: "Enter your new password", value: r, onChange: $, startContent: /* @__PURE__ */ e(J, { className: "w-4 h-4 text-default-400" }), size: t, radius: x, required: !0, disabled: u, variant: "bordered", autoFocus: !0 } ), r && /* @__PURE__ */ e( te, { password: r, requirements: f } ), /* @__PURE__ */ e( Q, { label: "Confirm Password", name: "confirmPassword", placeholder: "Confirm your new password", value: y, onChange: M, startContent: /* @__PURE__ */ e(J, { className: "w-4 h-4 text-default-400" }), size: t, radius: x, required: !0, disabled: u, variant: "bordered" } ), T && /* @__PURE__ */ e("div", { className: "text-danger-600 text-sm bg-danger-50 dark:bg-danger-900/20 rounded-lg p-3", children: T }), /* @__PURE__ */ e( S, { type: "submit", color: "primary", size: t, radius: x, className: "w-full", isLoading: u, isDisabled: !r || !y || !O || u, children: u ? "Resetting..." : "Reset Password" } ), /* @__PURE__ */ e(V, { className: "my-4" }), /* @__PURE__ */ e("div", { className: "text-center", children: /* @__PURE__ */ s( q, { href: E?.signIn || "/auth/sign-in", className: "text-sm flex items-center justify-center gap-2", children: [ /* @__PURE__ */ e(U, { className: "w-4 h-4" }), "Back to Sign In" ] } ) }) ] }) }); } function Le(n) { return /* @__PURE__ */ e(de, { ...n, variant: "card" }); } function Ie(n) { return /* @__PURE__ */ e(ue, { ...n, variant: "card" }); } export { de as ForgotPassword, Le as ForgotPasswordCard, ue as ResetPassword, Ie as ResetPasswordCard, de as default }; //# sourceMappingURL=forgot-password.js.map