@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
378 lines (377 loc) • 10.6 kB
JavaScript
'use client';
import { jsx as t, jsxs as S } from "react/jsx-runtime";
import c from "@emotion/styled";
import { Button as X } from "../ui/button/button.js";
import { Input as ne } from "../ui/input/input.js";
import { Chip as re } from "../ui/chip/chip.js";
import { Popover as F } from "../ui/popover/popover.js";
import { Listbox as le, ListboxItem as me } from "../ui/listbox/listbox.js";
import { useTheme as ae } from "../../theme/context.js";
import { AnimatePresence as ce, motion as he } from "framer-motion";
import n from "react";
import { useConfig as ue } from "../../hooks/use-config.js";
import { FieldError as de } from "./field-error.js";
import { useFormField as fe } from "./form-wrapper.js";
const pe = c.div`
display: flex;
flex-direction: column;
gap: ${(e) => e.theme.spacing[1]};
`, ge = c.div`
padding: ${(e) => e.theme.spacing[1]};
`, ve = c.div`
font-size: ${(e) => e.theme.fontSizes.xs};
color: ${(e) => e.theme.colors.text.tertiary};
`, ye = c.span`
font-weight: ${(e) => e.theme.fontWeights.medium};
`, $e = c(he.div)`
display: flex;
flex-direction: column;
gap: ${(e) => e.theme.spacing[1]};
`, ke = c.div`
font-size: ${(e) => e.theme.fontSizes.xs};
color: ${(e) => e.theme.colors.text.secondary};
font-weight: ${(e) => e.theme.fontWeights.medium};
`, xe = c.div`
display: flex;
flex-wrap: wrap;
gap: ${(e) => e.theme.spacing[1]};
`, Ce = c.svg`
width: ${(e) => e.theme.spacing[4]};
height: ${(e) => e.theme.spacing[4]};
color: ${(e) => e.theme.colors.text.quaternary};
`, Ie = c.svg`
width: ${(e) => e.theme.spacing[3]};
height: ${(e) => e.theme.spacing[3]};
`, Le = c.svg`
width: ${(e) => e.theme.spacing[4]};
height: ${(e) => e.theme.spacing[4]};
color: ${(e) => e.theme.colors.primary[500]};
`, we = c(X)`
font-size: ${(e) => e.theme.fontSizes.xs};
height: ${(e) => e.theme.spacing[6]};
`, ze = [
"gmail.com",
"yahoo.com",
"hotmail.com",
"outlook.com",
"icloud.com",
"aol.com",
"live.com",
"msn.com",
"me.com",
"mail.com",
"protonmail.com",
"yandex.com",
"zoho.com",
"fastmail.com"
], U = {
"gmial.com": "gmail.com",
"gmai.com": "gmail.com",
"gmil.com": "gmail.com",
"yahho.com": "yahoo.com",
"yaho.com": "yahoo.com",
"hotmial.com": "hotmail.com",
"hotmil.com": "hotmail.com",
"outlok.com": "outlook.com",
"outloo.com": "outlook.com"
};
function De(e, x = {}) {
const {
validateFormat: C = !0,
validateDomain: y = !1,
allowedDomains: r = [],
blockedDomains: $ = []
} = x, h = [], d = [];
let i = null, l = null;
C && (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e) || h.push("Please enter a valid email address"));
const f = e.split("@");
return f.length === 2 && (l = f[0], i = f[1].toLowerCase(), i && U[i] && d.push(
`Did you mean ${l}@${U[i]}?`
), y && i && (r.length > 0 && !r.includes(i) && h.push(
`Email must be from one of these domains: ${r.join(", ")}`
), $.includes(i) && h.push("This email domain is not allowed"), /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(i) || h.push("Invalid email domain")), l && i && i.length > 0 && !i.includes(".") && ze.filter(
(p) => p.toLowerCase().startsWith(i.toLowerCase())
).slice(0, 3).forEach((p) => {
d.push(`${l}@${p}`);
})), {
isValid: h.length === 0,
errors: h,
suggestions: d,
domain: i,
username: l
};
}
function Se({
name: e = "email",
label: x = "Email",
placeholder: C = "Enter your email address",
value: y = "",
onChange: r,
onBlur: $,
onFocus: h,
required: d = !1,
disabled: i = !1,
showSuggestions: l = !0,
validateFormat: f = !0,
validateDomain: k = !1,
allowedDomains: p = [],
blockedDomains: I = [],
size: j = "md",
radius: Y = "md",
variant: q = "bordered",
className: T = "",
autoFocus: W = !1,
autoComplete: R = "email",
error: g,
description: V,
showVerificationStatus: A = !1,
isVerified: M = !1,
onRequestVerification: L,
startContent: O,
endContent: w
}) {
const { theme: m } = ae(), { components: b, organizationSettings: z } = ue(), a = fe(e), ee = n.useMemo(
() => b.Input ?? ne,
[b.Input]
), Z = b.EmailField;
if (Z)
return /* @__PURE__ */ t(
Z,
{
name: e,
label: x,
placeholder: C,
value: y,
onChange: r,
onBlur: $,
onFocus: h,
required: d,
disabled: i,
showSuggestions: l,
validateFormat: f,
validateDomain: k,
allowedDomains: p,
blockedDomains: I,
size: j,
variant: q,
className: T,
autoFocus: W,
autoComplete: R,
error: g,
description: V,
showVerificationStatus: A,
isVerified: M,
onRequestVerification: L,
startContent: O,
endContent: w
}
);
const [oe, _] = n.useState(y), [E, G] = n.useState(!1), [H, v] = n.useState(!1), u = r ? y : oe, D = n.useMemo(() => {
const o = z?.emailRestrictions?.allowedDomains;
return o && o.length > 0 ? o : p;
}, [p, z]), J = n.useMemo(() => {
const o = z?.emailRestrictions?.blockedDomains;
return o && o.length > 0 ? [...I, ...o] : I;
}, [I, z]), s = n.useMemo(() => u ? De(u, {
validateFormat: f,
validateDomain: k,
allowedDomains: D,
blockedDomains: J
}) : null, [
u,
f,
k,
D,
J
]), P = n.useMemo(() => {
const o = [];
return g && (Array.isArray(g) ? o.push(...g) : o.push(g)), a.error && (Array.isArray(a.error) ? o.push(...a.error) : o.push(a.error)), s && !s.isValid && o.push(...s.errors), o.length > 0 ? o : null;
}, [g, a.error, s]), B = n.useCallback(
(o) => {
r ? r(o) : _(o), a.clearError && a.clearError(), s?.suggestions.length && E && l ? v(!0) : v(!1);
},
[r, a, s, E, l]
), te = n.useCallback(() => {
if (G(!1), v(!1), u) {
const o = u.trim().toLowerCase();
o !== u && (r ? r(o) : _(o));
}
a.setTouched && a.setTouched(!0), $?.();
}, [a, $, u, r]), ie = n.useCallback(() => {
G(!0), h?.(), l && s?.suggestions.length && v(!0);
}, [h, l, s]), K = n.useCallback(
(o) => {
B(o), v(!1);
},
[B]
), se = n.useMemo(() => !A || w ? null : M ? /* @__PURE__ */ t(
re,
{
size: "sm",
color: "success",
variant: "flat",
startContent: /* @__PURE__ */ t(
Ie,
{
theme: m,
fill: "none",
stroke: "currentColor",
viewBox: "0 0 24 24",
children: /* @__PURE__ */ t(
"path",
{
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M5 13l4 4L19 7"
}
)
}
),
children: "Verified"
}
) : u && s?.isValid && L ? /* @__PURE__ */ t(
X,
{
size: "sm",
variant: "ghost",
color: "primary",
onPress: L,
children: "Verify"
}
) : null, [
A,
w,
M,
u,
s,
L,
m
]), N = /* @__PURE__ */ t(
ee,
{
name: e,
label: x,
placeholder: C,
value: u,
onChange: (o) => B(o.target.value),
onBlur: te,
onFocus: ie,
type: "email",
isRequired: d,
required: d,
isDisabled: i,
disabled: i,
size: j,
radius: Y,
variant: "bordered",
autoFocus: W,
autoComplete: R,
description: V,
isInvalid: !!P,
errorMessage: "",
startContent: O || /* @__PURE__ */ t(
Ce,
{
theme: m,
fill: "none",
stroke: "currentColor",
viewBox: "0 0 24 24",
children: /* @__PURE__ */ t(
"path",
{
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"
}
)
}
),
endContent: w || se
}
);
return /* @__PURE__ */ S(pe, { theme: m, className: T, children: [
l && s?.suggestions.length ? /* @__PURE__ */ S(
F,
{
isOpen: H,
onOpenChange: v,
placement: "bottom-start",
showArrow: !0,
children: [
/* @__PURE__ */ t(F.Trigger, { children: N }),
/* @__PURE__ */ t(F.Content, { children: /* @__PURE__ */ t(ge, { theme: m, children: /* @__PURE__ */ t(
le,
{
"aria-label": "Email suggestions",
onAction: (o) => K(o),
children: s.suggestions.map((o, Q) => /* @__PURE__ */ t(
me,
{
value: o,
startContent: /* @__PURE__ */ t(
Le,
{
theme: m,
fill: "none",
stroke: "currentColor",
viewBox: "0 0 24 24",
children: /* @__PURE__ */ t(
"path",
{
strokeLinecap: "round",
strokeLinejoin: "round",
strokeWidth: 2,
d: "M13 10V3L4 14h7v7l9-11h-7z"
}
)
}
),
children: o
},
o
))
}
) }) })
]
}
) : N,
P && /* @__PURE__ */ t(de, { error: P, fieldName: e }),
D.length > 0 && /* @__PURE__ */ S(ve, { theme: m, children: [
/* @__PURE__ */ t(ye, { theme: m, children: "Allowed domains:" }),
" ",
D.join(", ")
] }),
/* @__PURE__ */ t(ce, { children: !H && s?.suggestions.length && E && /* @__PURE__ */ S(
$e,
{
theme: m,
initial: { opacity: 0, y: -10 },
animate: { opacity: 1, y: 0 },
exit: { opacity: 0, y: -10 },
children: [
/* @__PURE__ */ t(ke, { theme: m, children: "Did you mean:" }),
/* @__PURE__ */ t(xe, { theme: m, children: s.suggestions.slice(0, 3).map((o, Q) => /* @__PURE__ */ t(
we,
{
theme: m,
size: "sm",
variant: "ghost",
color: "primary",
onClick: () => K(o),
children: o
},
Q
)) })
]
}
) })
] });
}
var _e = Se;
export {
Se as EmailField,
_e as default
};
//# sourceMappingURL=email-field.js.map