UNPKG

@frank-auth/react

Version:

Flexible and customizable React UI components for Frank Authentication

628 lines (625 loc) 17.6 kB
'use client'; import { jsx as t, jsxs as f, Fragment as D } from "react/jsx-runtime"; import n from "@emotion/styled"; import { Button as R } from "../ui/button/button.js"; import { Input as we } from "../ui/input/input.js"; import { Chip as xe } from "../ui/chip/chip.js"; import { Progress as J } from "../ui/progress/progress.js"; import { useConfig as $e } from "../../hooks/use-config.js"; import { useTheme as F } from "../../theme/context.js"; import { EyeSlashIcon as Se, EyeIcon as ke, ExclamationTriangleIcon as Ce, CheckIcon as qe } from "@heroicons/react/24/outline"; import { AnimatePresence as C, motion as K } from "framer-motion"; import h from "react"; import { FieldError as ve } from "./field-error.js"; import { useFormField as Pe } from "./form-wrapper.js"; import { generatePasswordSuggestions as Ie, getPasswordStrength as Le } from "../auth/sign-up/index.js"; const ze = n.div` display: flex; flex-direction: column; gap: ${(e) => e.theme.spacing[2]}; `, Re = n(R)` color: ${(e) => e.theme.colors.text.quaternary}; &:hover { color: ${(e) => e.theme.colors.text.tertiary}; } `, X = n.svg` width: ${(e) => e.theme.spacing[4]}; height: ${(e) => e.theme.spacing[4]}; color: ${(e) => e.theme.colors.text.quaternary}; `, Q = n(K.div)` display: flex; flex-direction: column; gap: ${(e) => e.theme.spacing[2]}; `, Y = n.div` display: flex; align-items: center; justify-content: space-between; font-size: ${(e) => e.theme.fontSizes.sm}; `, Fe = n.span` color: ${(e) => e.theme.colors.text.secondary}; `, Me = n.span` font-weight: ${(e) => e.theme.fontWeights.medium}; color: ${(e) => { switch (e.strengthColor) { case "danger": return e.theme.colors.danger[500]; case "warning": return e.theme.colors.warning[500]; case "primary": return e.theme.colors.primary[500]; case "success": return e.theme.colors.success[500]; default: return e.theme.colors.text.primary; } }}; `, Ae = n.div` font-size: ${(e) => e.theme.fontSizes.xs}; color: ${(e) => e.theme.colors.text.tertiary}; `, Oe = n(K.div)` display: flex; flex-direction: column; gap: ${(e) => e.theme.spacing[2]}; `, Te = n.div` font-size: ${(e) => e.theme.fontSizes.sm}; font-weight: ${(e) => e.theme.fontWeights.medium}; color: ${(e) => e.theme.colors.text.primary}; `, Ee = n.div` display: flex; flex-direction: column; gap: ${(e) => e.theme.spacing[1]}; `, Ve = n.div` display: flex; align-items: center; gap: ${(e) => e.theme.spacing[2]}; font-size: ${(e) => e.theme.fontSizes.xs}; `, Ne = n.div` width: ${(e) => e.theme.spacing[4]}; height: ${(e) => e.theme.spacing[4]}; border-radius: ${(e) => e.theme.borderRadius.full}; display: flex; align-items: center; justify-content: center; background-color: ${(e) => e.met ? e.theme.colors.success[100] : e.theme.colors.background.tertiary}; color: ${(e) => e.met ? e.theme.colors.success[600] : e.theme.colors.text.quaternary}; `, Ue = n.svg` width: ${(e) => e.theme.spacing[3]}; height: ${(e) => e.theme.spacing[3]}; `, We = n.div` width: 6px; height: 6px; border-radius: ${(e) => e.theme.borderRadius.full}; background-color: currentColor; `, Ge = n.span` color: ${(e) => e.met ? e.theme.colors.success[600] : e.theme.colors.text.tertiary}; `, _e = n.div` display: flex; flex-direction: column; gap: ${(e) => e.theme.spacing[2]}; `, je = n.div` font-size: ${(e) => e.theme.fontSizes.xs}; color: ${(e) => e.theme.colors.text.tertiary}; `, Be = n.div` display: flex; flex-direction: column; gap: ${(e) => e.theme.spacing[1]}; `, Ze = n.button` font-size: ${(e) => e.theme.fontSizes.xs}; font-family: monospace; color: ${(e) => e.theme.colors.primary[600]}; background: none; border: none; cursor: pointer; text-align: left; padding: 0; &:hover { color: ${(e) => e.theme.colors.primary[800]}; text-decoration: underline; } `, He = n.div` font-size: ${(e) => e.theme.fontSizes.xs}; display: flex; flex-direction: column; gap: ${(e) => e.theme.spacing[1]}; `, De = n.div` display: flex; align-items: center; gap: ${(e) => e.theme.spacing[1]}; color: ${(e) => e.theme.colors.text.tertiary}; `, Xe = n.svg` width: ${(e) => e.theme.spacing[3]}; height: ${(e) => e.theme.spacing[3]}; `, Je = n.div` font-size: ${(e) => e.theme.fontSizes.xs}; display: flex; flex-direction: column; gap: ${(e) => e.theme.spacing[1]}; `, Ke = n.div` display: flex; align-items: center; gap: ${(e) => e.theme.spacing[1]}; color: ${(e) => e.met ? e.theme.colors.success[600] : e.theme.colors.text.quaternary}; `, Qe = n.svg` width: ${(e) => e.theme.spacing[3]}; height: ${(e) => e.theme.spacing[3]}; opacity: ${(e) => e.met ? 1 : 0.3}; `, L = { minLength: 8, maxLength: 128, requireUppercase: !0, requireLowercase: !0, requireNumbers: !0, requireSpecialChars: !0, preventCommon: !0, preventUserInfo: !1 }, Ye = [ "password", "123456", "123456789", "qwerty", "abc123", "password123", "admin", "letmein", "welcome", "monkey", "1234567890", "iloveyou" ], z = /[!@#$%^&*()_+=\[\]{}|;':",./<>?~`-]/; function et(e, s, m) { const a = []; let o = 0; const u = []; if (s.minLength) { const i = e.length >= s.minLength; a.push({ id: "length", label: `At least ${s.minLength} characters`, met: i, required: !0 }), i ? o += 1 : u.push( `Password must be at least ${s.minLength} characters long` ); } if (s.requireUppercase) { const i = /[A-Z]/.test(e); a.push({ id: "uppercase", label: "One uppercase letter", met: i, required: !0 }), i ? o += 1 : u.push("Password must contain at least one uppercase letter"); } if (s.requireLowercase) { const i = /[a-z]/.test(e); a.push({ id: "lowercase", label: "One lowercase letter", met: i, required: !0 }), i ? o += 1 : u.push("Password must contain at least one lowercase letter"); } if (s.requireNumbers) { const i = /\d/.test(e); a.push({ id: "number", label: "One number", met: i, required: !0 }), i ? o += 1 : u.push("Password must contain at least one number"); } if (s.requireSpecialChars) { const i = z.test(e); a.push({ id: "special", label: "One special character", met: i, required: !0 }), i ? o += 1 : u.push( "Password must contain at least one special character (!@#$%^&*()_+-=[]{}|;':\",./<>?~`)" ); } if (s.preventCommon) { const i = Ye.includes(e.toLowerCase()); a.push({ id: "common", label: "Not a common password", met: !i, required: !1 }), i && (o = Math.max(0, o - 2), u.push("This is a commonly used password")); } if (e.length >= 12 && (o += 1), e.length >= 16 && (o += 1), /[A-Z].*[A-Z]/.test(e) && (o += 0.5), /\d.*\d/.test(e) && (o += 0.5), z.test(e)) { const i = e.match( new RegExp(z.source, "g") ); i && i.length >= 2 && (o += 0.5); } o = Math.min(4, Math.floor(o * 0.8)); let l, d, p; switch (o) { case 0: case 1: l = "Very Weak", d = "danger", p = 20; break; case 2: l = "Weak", d = "warning", p = 40; break; case 3: l = "Good", d = "primary", p = 70; break; case 4: l = "Strong", d = "success", p = 100; break; default: l = "Very Weak", d = "danger", p = 0; } return { score: o, label: l, color: d, percentage: p, feedback: u, requirements: a }; } function tt({ name: e = "password", label: s = "Password", placeholder: m = "Enter your password", value: a = "", onChange: o, onBlur: u, onFocus: l, required: d = !1, disabled: p = !1, showStrength: i = !0, showRequirements: M = !1, showSuggestions: te = !1, enableGenerate: re = !1, allowToggle: A = !0, rules: w = L, size: O = "md", radius: se = "md", variant: T = "bordered", className: E = "", autoFocus: V = !1, autoComplete: N = "current-password", isConfirmation: S = !1, originalPassword: k, error: x, description: U, startContent: ne, advanceRequirements: W }) { const { theme: c } = F(), { components: q, organizationSettings: G } = $e(), g = Pe(e), oe = q.Input ?? we; q.Button ?? R; const _ = q.PasswordField; if (_) return /* @__PURE__ */ t( _, { name: e, label: s, placeholder: m, value: a, onChange: o, onBlur: u, onFocus: l, required: d, disabled: p, showStrength: i, showRequirements: M, allowToggle: A, rules: w, size: O, variant: T, className: E, autoFocus: V, autoComplete: N, isConfirmation: S, originalPassword: k, error: x, description: U } ); const [ie, ce] = h.useState(a), [v, ae] = h.useState(!1), [j, B] = h.useState(!1), [Z, le] = h.useState( [] ), y = o ? a : ie, H = h.useMemo(() => { const r = G?.passwordPolicy; return r ? { ...L, ...w, minLength: r.minLength || w.minLength, requireUppercase: r.requireUppercase ?? w.requireUppercase, requireLowercase: r.requireLowercase ?? w.requireLowercase, requireNumbers: r.requireNumbers ?? w.requireNumbers, requireSpecialChars: r.requireSpecialChars ?? w.requireSpecialChars } : { ...L, ...w }; }, [w, G]), b = h.useMemo(() => y ? et(y, H) : null, [y, H]), P = h.useMemo(() => !S || !k || !y ? null : k !== y ? "Passwords do not match" : null, [S, k, y]), I = h.useMemo(() => { const r = []; return x && (Array.isArray(x) ? r.push(...x) : r.push(x)), g.error && (Array.isArray(g.error) ? r.push(...g.error) : r.push(g.error)), P && r.push(P), r.length > 0 ? r : null; }, [x, g.error, P]), he = h.useCallback( (r) => { o ? o(r) : ce(r), g.clearError && g.clearError(); }, [o, g] ), me = h.useCallback(() => { B(!1), g.setTouched && g.setTouched(!0), u?.(); }, [g, u]), ue = h.useCallback(() => { B(!0), l?.(); }, [l]), de = h.useCallback(() => { ae((r) => !r); }, []), ge = M && (j || y), fe = te && (j || y), pe = i && y && !S, ye = h.useCallback(() => { const r = Ie(); le(r); }, []); return /* @__PURE__ */ f(ze, { theme: c, className: E, children: [ /* @__PURE__ */ t( oe, { name: e, label: s, placeholder: m, value: y, onBlur: me, onFocus: ue, type: v ? "text" : "password", isRequired: d, isDisabled: p, onChange: (r) => he(typeof r == "string" ? r : r.target.value), required: d, disabled: p, size: O, radius: se, variant: T, autoFocus: V, autoComplete: N, description: U, isInvalid: !!I, errorMessage: "", startContent: ne, endContent: A && /* @__PURE__ */ t( Re, { theme: c, isIconOnly: !0, variant: "ghost", size: "sm", type: "button", color: "secondary", onClick: de, "aria-label": v ? "Hide password" : "Show password", children: v ? /* @__PURE__ */ t(X, { theme: c, as: Se }) : /* @__PURE__ */ t(X, { theme: c, as: ke }) } ) } ), I && /* @__PURE__ */ t(ve, { error: I, fieldName: e }), /* @__PURE__ */ t(C, { children: pe && b && /* @__PURE__ */ f( Q, { theme: c, initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, children: [ /* @__PURE__ */ f(Y, { theme: c, children: [ /* @__PURE__ */ t(Fe, { theme: c, children: "Password strength:" }), /* @__PURE__ */ t( Me, { theme: c, strengthColor: b.color, children: b.label } ) ] }), /* @__PURE__ */ t( J, { value: b.percentage, color: b.color, size: "xs" } ), b.feedback.length > 0 && /* @__PURE__ */ t(Ae, { theme: c, children: b.feedback[0] }) ] } ) }), /* @__PURE__ */ t(C, { children: ge && b && /* @__PURE__ */ t( Oe, { theme: c, initial: { opacity: 0, height: 0 }, animate: { opacity: 1, height: "auto" }, exit: { opacity: 0, height: 0 }, children: W ? /* @__PURE__ */ t( rt, { password: a, requirements: W } ) : /* @__PURE__ */ f(D, { children: [ /* @__PURE__ */ t(Te, { theme: c, children: "Password Requirements:" }), /* @__PURE__ */ t(Ee, { theme: c, children: b.requirements.map((r) => /* @__PURE__ */ f(Ve, { theme: c, children: [ /* @__PURE__ */ t(Ne, { theme: c, met: r.met, children: r.met ? /* @__PURE__ */ t( Ue, { theme: c, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ t( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" } ) } ) : /* @__PURE__ */ t(We, { theme: c }) }), /* @__PURE__ */ t(Ge, { theme: c, met: r.met, children: r.label }) ] }, r.id)) }) ] }) } ) }), /* @__PURE__ */ t(C, { children: fe && b && /* @__PURE__ */ t(_e, { theme: c, children: Z.length > 0 && /* @__PURE__ */ f(D, { children: [ /* @__PURE__ */ t(je, { theme: c, children: "Suggested passwords:" }), /* @__PURE__ */ t(Be, { theme: c, children: Z.map((r, be) => /* @__PURE__ */ t( Ze, { theme: c, type: "button", children: r }, be )) }) ] }) }) }), /* @__PURE__ */ t(C, { children: re && /* @__PURE__ */ t( R, { type: "button", variant: "light", size: "sm", onPress: ye, children: "Generate secure password" } ) }) ] }); } const ee = h.memo(tt); function yt({ originalPassword: e, ...s }) { return /* @__PURE__ */ t( ee, { ...s, name: s.name || "passwordConfirmation", label: s.label || "Confirm Password", placeholder: s.placeholder || "Confirm your password", autoComplete: "new-password", isConfirmation: !0, originalPassword: e, showStrength: !1, showRequirements: !1 } ); } function rt({ password: e, requirements: s }) { const { theme: m } = F(), a = Le(e), o = (l) => { switch (l) { case "weak": return "danger"; case "fair": return "warning"; case "good": return "primary"; case "strong": return "success"; default: return "default"; } }, u = (l) => { switch (l) { case "weak": return "Weak"; case "fair": return "Fair"; case "good": return "Good"; case "strong": return "Strong"; default: return ""; } }; return e ? /* @__PURE__ */ f(Q, { theme: m, children: [ /* @__PURE__ */ f(Y, { theme: m, children: [ /* @__PURE__ */ t( J, { value: a.score / 6 * 100, color: o(a.strength), size: "xs", style: { flex: 1 } } ), /* @__PURE__ */ t( xe, { size: "sm", color: o(a.strength), variant: "flat", children: u(a.strength) } ) ] }), a.feedback.length > 0 && /* @__PURE__ */ t(He, { theme: m, children: a.feedback.map((l, d) => /* @__PURE__ */ f(De, { theme: m, children: [ /* @__PURE__ */ t(Xe, { theme: m, as: Ce }), /* @__PURE__ */ t("span", { children: l }) ] }, d)) }), s && /* @__PURE__ */ f(Je, { theme: m, children: [ s.minLength && /* @__PURE__ */ t( $, { met: e.length >= s.minLength, text: `At least ${s.minLength} characters` } ), s.requireUppercase && /* @__PURE__ */ t( $, { met: /[A-Z]/.test(e), text: "One uppercase letter" } ), s.requireLowercase && /* @__PURE__ */ t( $, { met: /[a-z]/.test(e), text: "One lowercase letter" } ), s.requireNumbers && /* @__PURE__ */ t( $, { met: /\d/.test(e), text: "One number" } ), s.requireSymbols && /* @__PURE__ */ t( $, { met: /[!@#$%^&*(),.?":{}|<>]/.test(e), text: "One symbol" } ) ] }) ] }) : null; } function $({ met: e, text: s }) { const { theme: m } = F(); return /* @__PURE__ */ f(Ke, { theme: m, met: e, children: [ /* @__PURE__ */ t(Qe, { theme: m, met: e, as: qe }), /* @__PURE__ */ t("span", { children: s }) ] }); } var bt = ee; export { yt as PasswordConfirmationField, ee as PasswordField, tt as PasswordFieldComponent, X as StyledSvg, bt as default }; //# sourceMappingURL=password-field.js.map