@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
453 lines (452 loc) • 15.3 kB
JavaScript
'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