UNPKG

@frank-auth/react

Version:

Flexible and customizable React UI components for Frank Authentication

496 lines (495 loc) 22.1 kB
'use client'; import { jsx as e, jsxs as s, Fragment as L } from "react/jsx-runtime"; import N from "react"; import { useDisclosure as V, Card as A, CardHeader as S, Chip as D, Button as h, Divider as z, CardBody as H, Alert as j, Modal as Y, ModalContent as R, ModalHeader as $, ModalBody as U, Input as W, Dropdown as K, DropdownTrigger as q, DropdownMenu as G, DropdownItem as B } from "@heroui/react"; import { usePasskeys as E } from "../../../hooks/use-passkeys.js"; import { useConfig as J } from "../../../hooks/use-config.js"; function Q({ onSuccess: t, onError: c, onClose: u, isOpen: f }) { const { registerPasskey: p, isSupported: y, isAvailable: o } = E(), [d, i] = N.useState("intro"), [n, g] = N.useState(""), [k, l] = N.useState(!1); N.useEffect(() => { if (!f) i("intro"), g(""); else { const r = w(); g(`${r.browser} on ${r.os}`); } }, [f]); const w = () => { const r = navigator.userAgent; let a = "Unknown Browser", x = "Unknown OS"; return r.includes("Chrome") ? a = "Chrome" : r.includes("Firefox") ? a = "Firefox" : r.includes("Safari") ? a = "Safari" : r.includes("Edge") && (a = "Edge"), r.includes("Windows") ? x = "Windows" : r.includes("Mac") ? x = "macOS" : r.includes("Linux") ? x = "Linux" : r.includes("Android") ? x = "Android" : r.includes("iOS") && (x = "iOS"), { browser: a, os: x }; }, C = async () => { if (!n.trim()) { c("Please enter a name for your passkey"); return; } try { l(!0), i("registering"), await p(n.trim()), t("Passkey registered successfully"), u(); } catch (r) { const a = r instanceof Error ? r.message : "Failed to register passkey"; c(a), i("name"); } finally { l(!1); } }; return y ? o ? /* @__PURE__ */ s("div", { className: "space-y-4", children: [ d === "intro" && /* @__PURE__ */ s("div", { className: "text-center space-y-4", children: [ /* @__PURE__ */ e("div", { className: "flex items-center justify-center w-16 h-16 bg-primary/10 rounded-full mx-auto", children: /* @__PURE__ */ e("svg", { className: "w-8 h-8 text-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" } ) }) }), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-lg font-semibold", children: "Create a Passkey" }), /* @__PURE__ */ e("p", { className: "text-sm text-default-500 mt-2", children: "Passkeys are a secure and convenient way to sign in without a password." }) ] }), /* @__PURE__ */ s("div", { className: "text-left space-y-3", children: [ /* @__PURE__ */ s("div", { className: "flex items-start gap-3", children: [ /* @__PURE__ */ e( "div", { className: "flex items-center justify-center w-6 h-6 bg-success text-white text-xs rounded-full flex-shrink-0 mt-0.5", children: "✓" } ), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("p", { className: "text-sm font-medium", children: "More secure than passwords" }), /* @__PURE__ */ e("p", { className: "text-xs text-default-500", children: "Protected by your device's security" }) ] }) ] }), /* @__PURE__ */ s("div", { className: "flex items-start gap-3", children: [ /* @__PURE__ */ e( "div", { className: "flex items-center justify-center w-6 h-6 bg-success text-white text-xs rounded-full flex-shrink-0 mt-0.5", children: "✓" } ), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("p", { className: "text-sm font-medium", children: "Fast and convenient" }), /* @__PURE__ */ e("p", { className: "text-xs text-default-500", children: "Sign in with just your fingerprint or face" }) ] }) ] }), /* @__PURE__ */ s("div", { className: "flex items-start gap-3", children: [ /* @__PURE__ */ e( "div", { className: "flex items-center justify-center w-6 h-6 bg-success text-white text-xs rounded-full flex-shrink-0 mt-0.5", children: "✓" } ), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("p", { className: "text-sm font-medium", children: "Phishing resistant" }), /* @__PURE__ */ e("p", { className: "text-xs text-default-500", children: "Can't be stolen or used on fake sites" }) ] }) ] }) ] }), /* @__PURE__ */ e( h, { color: "primary", onPress: () => i("name"), className: "w-full", children: "Continue" } ) ] }), d === "name" && /* @__PURE__ */ s("div", { className: "space-y-4", children: [ /* @__PURE__ */ s("div", { className: "text-center", children: [ /* @__PURE__ */ e("h3", { className: "text-lg font-semibold", children: "Name Your Passkey" }), /* @__PURE__ */ e("p", { className: "text-sm text-default-500 mt-2", children: "Give your passkey a name so you can identify it later." }) ] }), /* @__PURE__ */ e( W, { label: "Passkey Name", placeholder: "Enter a name for this passkey", value: n, onValueChange: g, description: "For example: 'iPhone Touch ID' or 'YubiKey'", isInvalid: !n.trim(), errorMessage: n.trim() ? "" : "Name is required" } ), /* @__PURE__ */ s("div", { className: "flex gap-2", children: [ /* @__PURE__ */ e( h, { variant: "light", onPress: () => i("intro"), className: "flex-1", children: "Back" } ), /* @__PURE__ */ e( h, { color: "primary", onPress: C, isDisabled: !n.trim(), className: "flex-1", children: "Create Passkey" } ) ] }) ] }), d === "registering" && /* @__PURE__ */ s("div", { className: "text-center space-y-4", children: [ /* @__PURE__ */ e("div", { className: "flex items-center justify-center w-16 h-16 bg-primary/10 rounded-full mx-auto", children: /* @__PURE__ */ e( "svg", { className: "w-8 h-8 text-primary animate-pulse", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" } ) } ) }), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-lg font-semibold", children: "Creating Passkey..." }), /* @__PURE__ */ e("p", { className: "text-sm text-default-500 mt-2", children: "Follow your browser's prompts to complete the setup." }) ] }), /* @__PURE__ */ e(j, { color: "primary", variant: "flat", children: /* @__PURE__ */ s("div", { className: "space-y-1", children: [ /* @__PURE__ */ e("p", { className: "text-sm font-medium", children: "What to expect:" }), /* @__PURE__ */ s("ul", { className: "text-xs space-y-1 ml-4", children: [ /* @__PURE__ */ e("li", { children: "• You may be asked to use your fingerprint, face, or PIN" }), /* @__PURE__ */ e("li", { children: "• Or insert and tap your security key" }), /* @__PURE__ */ e("li", { children: "• Allow the browser to create a passkey" }) ] }) ] }) }) ] }) ] }) : /* @__PURE__ */ s("div", { className: "text-center space-y-4", children: [ /* @__PURE__ */ e("div", { className: "flex items-center justify-center w-16 h-16 bg-warning/10 rounded-full mx-auto", children: /* @__PURE__ */ e("svg", { className: "w-8 h-8 text-warning", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 15.5c-.77.833.192 2.5 1.732 2.5z" } ) }) }), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-lg font-semibold", children: "No Authenticator Available" }), /* @__PURE__ */ e("p", { className: "text-sm text-default-500 mt-2", children: "No biometric or security key authenticator is available on this device." }) ] }), /* @__PURE__ */ e(h, { variant: "light", onPress: u, children: "Close" }) ] }) : /* @__PURE__ */ s("div", { className: "text-center space-y-4", children: [ /* @__PURE__ */ e("div", { className: "flex items-center justify-center w-16 h-16 bg-danger/10 rounded-full mx-auto", children: /* @__PURE__ */ e("svg", { className: "w-8 h-8 text-danger", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 15.5c-.77.833.192 2.5 1.732 2.5z" } ) }) }), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-lg font-semibold", children: "Passkeys Not Supported" }), /* @__PURE__ */ e("p", { className: "text-sm text-default-500 mt-2", children: "Your browser doesn't support passkeys. Please use a modern browser like Chrome, Firefox, Safari, or Edge." }) ] }), /* @__PURE__ */ e(h, { variant: "light", onPress: u, children: "Close" }) ] }); } function X({ passkey: t, onRename: c, onDelete: u, isDisabled: f }) { const [p, y] = N.useState(!1), [o, d] = N.useState(t.name), i = () => { o.trim() && o !== t.name && c(t.id, o.trim()), y(!1); }, n = () => { d(t.name), y(!1); }, g = () => (t.authenticatorType?.toLowerCase() || "").includes("platform") ? /* @__PURE__ */ e("svg", { className: "w-5 h-5 text-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" } ) }) : /* @__PURE__ */ e("svg", { className: "w-5 h-5 text-secondary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" } ) }), k = (l) => new Date(l).toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" }); return /* @__PURE__ */ s("div", { className: "flex items-center justify-between p-4 border border-default-200 rounded-lg", children: [ /* @__PURE__ */ s("div", { className: "flex items-center gap-3", children: [ /* @__PURE__ */ e("div", { className: "flex items-center justify-center w-10 h-10 bg-default-100 rounded-lg", children: g() }), /* @__PURE__ */ e("div", { className: "flex flex-col", children: p ? /* @__PURE__ */ s("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ e( W, { value: o, onValueChange: d, size: "sm", className: "w-48", onKeyDown: (l) => { l.key === "Enter" && i(), l.key === "Escape" && n(); }, autoFocus: !0 } ), /* @__PURE__ */ e(h, { size: "sm", color: "primary", onPress: i, children: "Save" }), /* @__PURE__ */ e(h, { size: "sm", variant: "light", onPress: n, children: "Cancel" }) ] }) : /* @__PURE__ */ s(L, { children: [ /* @__PURE__ */ s("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ e("span", { className: "text-sm font-medium", children: t.name }), t.isPrimary && /* @__PURE__ */ e(D, { size: "sm", color: "primary", variant: "flat", children: "Primary" }) ] }), /* @__PURE__ */ s("div", { className: "flex items-center gap-2 text-xs text-default-500", children: [ /* @__PURE__ */ s("span", { children: [ "Created ", k(t.createdAt) ] }), t.lastUsedAt && /* @__PURE__ */ s(L, { children: [ /* @__PURE__ */ e("span", { children: "•" }), /* @__PURE__ */ s("span", { children: [ "Last used ", k(t.lastUsedAt) ] }) ] }) ] }) ] }) }) ] }), !p && /* @__PURE__ */ s(K, { children: [ /* @__PURE__ */ e(q, { children: /* @__PURE__ */ e( h, { isIconOnly: !0, variant: "light", size: "sm", isDisabled: f, children: /* @__PURE__ */ e("svg", { className: "w-4 h-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 5v.01M12 12v.01M12 19v.01M12 6a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2zm0 7a1 1 0 110-2 1 1 0 010 2z" } ) }) } ) }), /* @__PURE__ */ s(G, { children: [ /* @__PURE__ */ e( B, { onPress: () => y(!0), children: "Rename" }, "rename" ), /* @__PURE__ */ e( B, { color: "danger", onPress: () => u(t.id), children: "Delete" }, "delete" ) ] }) ] }) ] }); } function re({ onSuccess: t, onError: c, className: u = "", isDisabled: f = !1, variant: p = "bordered", size: y = "md", showRegistration: o = !0, showManagement: d = !0, maxPasskeys: i = 10, hideSections: n = [], customTypes: g = [] }) { const { passkeys: k, isSupported: l, isAvailable: w, deletePasskey: C, renamePasskey: r, passkeyCount: a, isLoading: x } = E(), { components: I } = J(), b = V(), M = I.PasskeySetup; if (M) return /* @__PURE__ */ e(M, { onSuccess: t, onError: c, className: u, isDisabled: f, variant: p, size: y, showRegistration: o, showManagement: d, maxPasskeys: i, hideSections: n, customTypes: g }); const O = async (m) => { try { await C(m), t?.("Passkey deleted successfully"); } catch (v) { const P = v instanceof Error ? v.message : "Failed to delete passkey"; c?.(P); } }, F = async (m, v) => { try { await r(m, v), t?.("Passkey renamed successfully"); } catch (P) { const T = P instanceof Error ? P.message : "Failed to rename passkey"; c?.(T); } }; return /* @__PURE__ */ s("div", { className: `space-y-6 ${u}`, children: [ /* @__PURE__ */ s(A, { variant: p, children: [ /* @__PURE__ */ e(S, { children: /* @__PURE__ */ s("div", { className: "flex items-center justify-between w-full", children: [ /* @__PURE__ */ s("div", { className: "flex items-center gap-3", children: [ /* @__PURE__ */ e("div", { className: `flex items-center justify-center w-10 h-10 rounded-lg ${a > 0 ? "bg-success/10" : "bg-default/10"}`, children: /* @__PURE__ */ e( "svg", { className: `w-5 h-5 ${a > 0 ? "text-success" : "text-default-400"}`, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" } ) } ) }), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("h4", { className: "text-md font-semibold", children: "Passkeys" }), /* @__PURE__ */ s("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ s("span", { className: "text-sm text-default-500", children: [ a, " of ", i, " passkeys configured" ] }), a > 0 && /* @__PURE__ */ e(D, { size: "sm", color: "success", variant: "flat", children: "Active" }) ] }) ] }) ] }), o && l && w && a < i && /* @__PURE__ */ e( h, { color: "primary", size: "sm", onPress: b.onOpen, isDisabled: f || x, children: "Add Passkey" } ) ] }) }), a === 0 && /* @__PURE__ */ s(L, { children: [ /* @__PURE__ */ e(z, {}), /* @__PURE__ */ e(H, { children: l ? w ? /* @__PURE__ */ s("div", { className: "space-y-3", children: [ /* @__PURE__ */ e("p", { className: "text-sm text-default-600", children: "Passkeys provide a secure and convenient way to sign in without passwords. They use your device's built-in security features like fingerprint, face recognition, or security keys." }), /* @__PURE__ */ s("div", { className: "flex items-center gap-2", children: [ /* @__PURE__ */ e( "svg", { className: "w-4 h-4 text-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" } ) } ), /* @__PURE__ */ e("p", { className: "text-xs text-default-500", children: "Passkeys are more secure than passwords and can't be stolen or phished." }) ] }) ] }) : /* @__PURE__ */ e(j, { color: "warning", variant: "flat", children: /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("p", { className: "text-sm font-medium", children: "No Authenticator Available" }), /* @__PURE__ */ e("p", { className: "text-xs mt-1", children: "No biometric or security key authenticator is available on this device." }) ] }) }) : /* @__PURE__ */ e(j, { color: "warning", variant: "flat", children: /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("p", { className: "text-sm font-medium", children: "Passkeys Not Supported" }), /* @__PURE__ */ e("p", { className: "text-xs mt-1", children: "Your browser doesn't support passkeys. Please use a modern browser." }) ] }) }) }) ] }) ] }), d && a > 0 && !n.includes("management") && /* @__PURE__ */ s(A, { variant: p, children: [ /* @__PURE__ */ e(S, { children: /* @__PURE__ */ s("div", { className: "flex items-center gap-3", children: [ /* @__PURE__ */ e("div", { className: "flex items-center justify-center w-10 h-10 bg-primary/10 rounded-lg", children: /* @__PURE__ */ e( "svg", { className: "w-5 h-5 text-primary", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e( "path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10" } ) } ) }), /* @__PURE__ */ s("div", { children: [ /* @__PURE__ */ e("h4", { className: "text-md font-semibold", children: "Your Passkeys" }), /* @__PURE__ */ e("p", { className: "text-sm text-default-500", children: "Manage your registered passkeys" }) ] }) ] }) }), /* @__PURE__ */ e(z, {}), /* @__PURE__ */ e(H, { children: /* @__PURE__ */ e("div", { className: "space-y-3", children: k.map((m) => /* @__PURE__ */ e( X, { passkey: m, onRename: F, onDelete: O, isDisabled: f }, m.id )) }) }) ] }), /* @__PURE__ */ e( Y, { isOpen: b.isOpen, onOpenChange: b.onOpenChange, size: "md", placement: "center", hideCloseButton: !0, children: /* @__PURE__ */ e(R, { children: (m) => /* @__PURE__ */ s(L, { children: [ /* @__PURE__ */ e($, {}), /* @__PURE__ */ e(U, { children: /* @__PURE__ */ e( Q, { onSuccess: (v) => { t?.(v), m(); }, onError: (v) => c?.(v), onClose: m, isOpen: b.isOpen } ) }) ] }) }) } ) ] }); } export { re as PasskeySetup }; //# sourceMappingURL=passkey-setup.js.map