@better-auth-ui/heroui 1.6.6 → 1.6.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/components/auth/api-key/api-key-skeleton.d.ts +1 -0
  2. package/dist/components/auth/api-key/api-key-skeleton.js +21 -0
  3. package/dist/components/auth/api-key/api-key.d.ts +5 -0
  4. package/dist/components/auth/api-key/api-key.js +55 -0
  5. package/dist/components/auth/api-key/api-keys-empty.d.ts +4 -0
  6. package/dist/components/auth/api-key/api-keys-empty.js +35 -0
  7. package/dist/components/auth/api-key/api-keys.d.ts +6 -0
  8. package/dist/components/auth/api-key/api-keys.js +41 -0
  9. package/dist/components/auth/api-key/create-api-key-dialog.d.ts +5 -0
  10. package/dist/components/auth/api-key/create-api-key-dialog.js +74 -0
  11. package/dist/components/auth/api-key/delete-api-key-dialog.d.ts +7 -0
  12. package/dist/components/auth/api-key/delete-api-key-dialog.js +51 -0
  13. package/dist/components/auth/api-key/new-api-key-dialog.d.ts +7 -0
  14. package/dist/components/auth/api-key/new-api-key-dialog.js +60 -0
  15. package/dist/components/auth/multi-session/switch-account-submenu-content.js +14 -15
  16. package/dist/components/auth/passkey/add-passkey-dialog.d.ts +0 -0
  17. package/dist/components/auth/passkey/delete-passkey-dialog.d.ts +0 -0
  18. package/dist/components/auth/passkey/passkey-skeleton.d.ts +0 -0
  19. package/dist/components/auth/passkey/passkeys-empty.d.ts +0 -0
  20. package/dist/components/auth/settings/security/linked-account.js +1 -1
  21. package/dist/lib/auth/api-key-plugin.d.ts +23 -0
  22. package/dist/lib/auth/api-key-plugin.js +10 -0
  23. package/dist/plugins.d.ts +2 -0
  24. package/dist/plugins.js +22 -20
  25. package/package.json +3 -3
  26. package/src/components/auth/api-key/api-key-skeleton.tsx +17 -0
  27. package/src/components/auth/api-key/api-key.tsx +64 -0
  28. package/src/components/auth/api-key/api-keys-empty.tsx +33 -0
  29. package/src/components/auth/api-key/api-keys.tsx +71 -0
  30. package/src/components/auth/api-key/create-api-key-dialog.tsx +134 -0
  31. package/src/components/auth/api-key/delete-api-key-dialog.tsx +92 -0
  32. package/src/components/auth/api-key/new-api-key-dialog.tsx +94 -0
  33. package/src/components/auth/multi-session/switch-account-submenu-content.tsx +1 -3
  34. package/src/components/auth/passkey/add-passkey-dialog.tsx +0 -0
  35. package/src/components/auth/passkey/delete-passkey-dialog.tsx +0 -0
  36. package/src/components/auth/passkey/passkey-skeleton.tsx +0 -0
  37. package/src/components/auth/passkey/passkeys-empty.tsx +0 -0
  38. package/src/components/auth/settings/security/linked-account.tsx +2 -4
  39. package/src/lib/auth/api-key-plugin.ts +15 -0
  40. package/src/plugins.ts +2 -0
@@ -0,0 +1 @@
1
+ export declare function ApiKeySkeleton(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,21 @@
1
+ import { Skeleton as e } from "@heroui/react";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
+ //#region src/components/auth/api-key/api-key-skeleton.tsx
4
+ function r() {
5
+ return /* @__PURE__ */ t("div", {
6
+ className: "flex items-center justify-between",
7
+ children: /* @__PURE__ */ n("div", {
8
+ className: "flex items-center gap-3",
9
+ children: [/* @__PURE__ */ t(e, { className: "size-10 rounded-xl" }), /* @__PURE__ */ n("div", {
10
+ className: "flex flex-col gap-1",
11
+ children: [
12
+ /* @__PURE__ */ t(e, { className: "h-4 w-28 rounded-lg" }),
13
+ /* @__PURE__ */ t(e, { className: "h-3 w-36 rounded-lg" }),
14
+ /* @__PURE__ */ t(e, { className: "h-3 w-32 rounded-lg" })
15
+ ]
16
+ })]
17
+ })
18
+ });
19
+ }
20
+ //#endregion
21
+ export { r as ApiKeySkeleton };
@@ -0,0 +1,5 @@
1
+ import { ListedApiKey } from '@better-auth-ui/react';
2
+ export type ApiKeyProps = {
3
+ apiKey: ListedApiKey;
4
+ };
5
+ export declare function ApiKey({ apiKey }: ApiKeyProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,55 @@
1
+ import { apiKeyPlugin as e } from "../../../lib/auth/api-key-plugin.js";
2
+ import { DeleteApiKeyDialog as t } from "./delete-api-key-dialog.js";
3
+ import { useAuth as n, useAuthPlugin as r } from "@better-auth-ui/react";
4
+ import { Key as i, Xmark as a } from "@gravity-ui/icons";
5
+ import { Button as o } from "@heroui/react";
6
+ import { useState as s } from "react";
7
+ import { jsx as c, jsxs as l } from "react/jsx-runtime";
8
+ //#region src/components/auth/api-key/api-key.tsx
9
+ function u({ apiKey: u }) {
10
+ let { localization: d } = n(), { localization: f } = r(e), [p, m] = s(!1), h = `${u.start}${"*".repeat(16)}`;
11
+ return /* @__PURE__ */ l("div", {
12
+ className: "flex items-center gap-3",
13
+ children: [
14
+ /* @__PURE__ */ c("div", {
15
+ className: "flex size-10 shrink-0 items-center justify-center rounded-xl bg-surface-secondary",
16
+ children: /* @__PURE__ */ c(i, { className: "size-4.5" })
17
+ }),
18
+ /* @__PURE__ */ l("div", {
19
+ className: "flex flex-col min-w-0",
20
+ children: [
21
+ /* @__PURE__ */ c("span", {
22
+ className: "text-sm font-medium leading-tight truncate",
23
+ children: u.name || f.apiKey
24
+ }),
25
+ /* @__PURE__ */ c("span", {
26
+ className: "text-xs text-muted font-mono truncate",
27
+ children: h
28
+ }),
29
+ /* @__PURE__ */ c("span", {
30
+ className: "text-xs text-muted",
31
+ children: new Date(u.createdAt).toLocaleString(void 0, {
32
+ dateStyle: "medium",
33
+ timeStyle: "short"
34
+ })
35
+ })
36
+ ]
37
+ }),
38
+ /* @__PURE__ */ l(o, {
39
+ className: "ml-auto shrink-0",
40
+ variant: "outline",
41
+ size: "sm",
42
+ onPress: () => m(!0),
43
+ "aria-label": f.deleteApiKey,
44
+ children: [/* @__PURE__ */ c(a, {}), d.settings.delete]
45
+ }),
46
+ /* @__PURE__ */ c(t, {
47
+ isOpen: p,
48
+ onOpenChange: m,
49
+ apiKey: u
50
+ })
51
+ ]
52
+ });
53
+ }
54
+ //#endregion
55
+ export { u as ApiKey };
@@ -0,0 +1,4 @@
1
+ export type ApiKeysEmptyProps = {
2
+ onCreatePress: () => void;
3
+ };
4
+ export declare function ApiKeysEmpty({ onCreatePress }: ApiKeysEmptyProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,35 @@
1
+ import { apiKeyPlugin as e } from "../../../lib/auth/api-key-plugin.js";
2
+ import { useAuthPlugin as t } from "@better-auth-ui/react";
3
+ import { Key as n } from "@gravity-ui/icons";
4
+ import { Button as r } from "@heroui/react";
5
+ import { jsx as i, jsxs as a } from "react/jsx-runtime";
6
+ //#region src/components/auth/api-key/api-keys-empty.tsx
7
+ function o({ onCreatePress: o }) {
8
+ let { localization: s } = t(e);
9
+ return /* @__PURE__ */ a("div", {
10
+ className: "flex flex-col items-center justify-center gap-4",
11
+ children: [
12
+ /* @__PURE__ */ i("div", {
13
+ className: "flex size-10 items-center justify-center rounded-xl bg-surface-secondary",
14
+ children: /* @__PURE__ */ i(n, { className: "size-4.5" })
15
+ }),
16
+ /* @__PURE__ */ a("div", {
17
+ className: "flex flex-col items-center justify-center gap-1 text-center",
18
+ children: [/* @__PURE__ */ i("p", {
19
+ className: "text-sm font-semibold",
20
+ children: s.noApiKeys
21
+ }), /* @__PURE__ */ i("p", {
22
+ className: "text-muted text-xs",
23
+ children: s.apiKeysDescription
24
+ })]
25
+ }),
26
+ /* @__PURE__ */ i(r, {
27
+ size: "sm",
28
+ onPress: o,
29
+ children: s.createApiKey
30
+ })
31
+ ]
32
+ });
33
+ }
34
+ //#endregion
35
+ export { o as ApiKeysEmpty };
@@ -0,0 +1,6 @@
1
+ import { CardProps } from '@heroui/react';
2
+ export type ApiKeysProps = {
3
+ className?: string;
4
+ variant?: CardProps["variant"];
5
+ };
6
+ export declare function ApiKeys({ className, variant }: ApiKeysProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,41 @@
1
+ import { apiKeyPlugin as e } from "../../../lib/auth/api-key-plugin.js";
2
+ import { ApiKey as t } from "./api-key.js";
3
+ import { ApiKeySkeleton as n } from "./api-key-skeleton.js";
4
+ import { ApiKeysEmpty as r } from "./api-keys-empty.js";
5
+ import { CreateApiKeyDialog as i } from "./create-api-key-dialog.js";
6
+ import { useAuth as a, useAuthPlugin as o, useListApiKeys as s } from "@better-auth-ui/react";
7
+ import { Button as c, Card as l, cn as u } from "@heroui/react";
8
+ import { useState as d } from "react";
9
+ import { jsx as f, jsxs as p } from "react/jsx-runtime";
10
+ //#region src/components/auth/api-key/api-keys.tsx
11
+ function m({ className: m, variant: h }) {
12
+ let { authClient: g } = a(), { localization: _ } = o(e), { data: v, isPending: y } = s(g), [b, x] = d(!1);
13
+ return /* @__PURE__ */ p("div", {
14
+ className: u("flex flex-col gap-3", m),
15
+ children: [
16
+ /* @__PURE__ */ p("div", {
17
+ className: "flex items-end justify-between gap-3",
18
+ children: [/* @__PURE__ */ f("h2", {
19
+ className: "text-sm font-semibold truncate",
20
+ children: _.apiKeys
21
+ }), /* @__PURE__ */ f(c, {
22
+ className: "shrink-0",
23
+ size: "sm",
24
+ isDisabled: y,
25
+ onPress: () => x(!0),
26
+ children: _.createApiKey
27
+ })]
28
+ }),
29
+ /* @__PURE__ */ f(l, {
30
+ variant: h,
31
+ children: /* @__PURE__ */ f(l.Content, { children: y ? /* @__PURE__ */ f(n, {}) : v?.apiKeys.length ? v?.apiKeys.map((e, n) => /* @__PURE__ */ p("div", { children: [n > 0 && /* @__PURE__ */ f("div", { className: "border-b border-dashed -mx-4 my-4" }), /* @__PURE__ */ f(t, { apiKey: e })] }, e.id)) : /* @__PURE__ */ f(r, { onCreatePress: () => x(!0) }) })
32
+ }),
33
+ /* @__PURE__ */ f(i, {
34
+ isOpen: b,
35
+ onOpenChange: x
36
+ })
37
+ ]
38
+ });
39
+ }
40
+ //#endregion
41
+ export { m as ApiKeys };
@@ -0,0 +1,5 @@
1
+ export type CreateApiKeyDialogProps = {
2
+ isOpen: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ };
5
+ export declare function CreateApiKeyDialog({ isOpen, onOpenChange }: CreateApiKeyDialogProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,74 @@
1
+ import { apiKeyPlugin as e } from "../../../lib/auth/api-key-plugin.js";
2
+ import { NewApiKeyDialog as t } from "./new-api-key-dialog.js";
3
+ import { useAuth as n, useAuthPlugin as r, useCreateApiKey as i } from "@better-auth-ui/react";
4
+ import { Key as a } from "@gravity-ui/icons";
5
+ import { AlertDialog as o, Button as s, FieldError as c, Form as l, Input as u, Label as d, Spinner as f, TextField as p } from "@heroui/react";
6
+ import { useState as m } from "react";
7
+ import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
8
+ //#region src/components/auth/api-key/create-api-key-dialog.tsx
9
+ function v({ isOpen: v, onOpenChange: y }) {
10
+ let { authClient: b, localization: x } = n(), { localization: S } = r(e), { mutate: C, isPending: w } = i(b), [T, E] = m(!1), [D, O] = m(null), [k, A] = m(null), j = (e) => {
11
+ e || (O(null), A(null)), y(e);
12
+ };
13
+ return /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(o.Backdrop, {
14
+ isOpen: v,
15
+ onOpenChange: j,
16
+ children: /* @__PURE__ */ g(o.Container, { children: /* @__PURE__ */ g(o.Dialog, { children: /* @__PURE__ */ _(l, {
17
+ onSubmit: (e) => {
18
+ e.preventDefault();
19
+ let t = new FormData(e.target).get("name");
20
+ C(t ? { name: t.trim() } : void 0, { onSuccess: (e) => {
21
+ j(!1), O(t), A(e.key), E(!0);
22
+ } });
23
+ },
24
+ children: [
25
+ /* @__PURE__ */ g(o.CloseTrigger, {}),
26
+ /* @__PURE__ */ _(o.Header, { children: [/* @__PURE__ */ g(o.Icon, {
27
+ status: "default",
28
+ children: /* @__PURE__ */ g(a, {})
29
+ }), /* @__PURE__ */ g(o.Heading, { children: S.createApiKey })] }),
30
+ /* @__PURE__ */ _(o.Body, {
31
+ className: "overflow-visible",
32
+ children: [/* @__PURE__ */ g("p", {
33
+ className: "text-muted text-sm",
34
+ children: S.apiKeysDescription
35
+ }), /* @__PURE__ */ _(p, {
36
+ className: "mt-4",
37
+ id: "name",
38
+ name: "name",
39
+ isDisabled: w,
40
+ children: [
41
+ /* @__PURE__ */ g(d, { children: S.name }),
42
+ /* @__PURE__ */ g(u, {
43
+ autoFocus: !0,
44
+ placeholder: x.settings.optional,
45
+ variant: "secondary"
46
+ }),
47
+ /* @__PURE__ */ g(c, {})
48
+ ]
49
+ })]
50
+ }),
51
+ /* @__PURE__ */ _(o.Footer, { children: [/* @__PURE__ */ g(s, {
52
+ slot: "close",
53
+ variant: "tertiary",
54
+ isDisabled: w,
55
+ children: x.settings.cancel
56
+ }), /* @__PURE__ */ _(s, {
57
+ type: "submit",
58
+ isPending: w,
59
+ children: [w && /* @__PURE__ */ g(f, {
60
+ color: "current",
61
+ size: "sm"
62
+ }), S.createApiKey]
63
+ })] })
64
+ ]
65
+ }) }) })
66
+ }), /* @__PURE__ */ g(t, {
67
+ isOpen: T,
68
+ onOpenChange: E,
69
+ secretKey: k,
70
+ name: D
71
+ })] });
72
+ }
73
+ //#endregion
74
+ export { v as CreateApiKeyDialog };
@@ -0,0 +1,7 @@
1
+ import { ListedApiKey } from '@better-auth-ui/react';
2
+ export type DeleteApiKeyDialogProps = {
3
+ isOpen: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ apiKey: ListedApiKey;
6
+ };
7
+ export declare function DeleteApiKeyDialog({ isOpen, onOpenChange, apiKey }: DeleteApiKeyDialogProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,51 @@
1
+ import { apiKeyPlugin as e } from "../../../lib/auth/api-key-plugin.js";
2
+ import { useAuth as t, useAuthPlugin as n, useDeleteApiKey as r } from "@better-auth-ui/react";
3
+ import { Key as i } from "@gravity-ui/icons";
4
+ import { AlertDialog as a, Button as o, Input as s, Label as c, Spinner as l, TextField as u } from "@heroui/react";
5
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
6
+ //#region src/components/auth/api-key/delete-api-key-dialog.tsx
7
+ function p({ isOpen: p, onOpenChange: m, apiKey: h }) {
8
+ let { authClient: g, localization: _ } = t(), { localization: v } = n(e), y = `${h.start}${"*".repeat(16)}`, { mutate: b, isPending: x } = r(g, { onSuccess: () => m(!1) });
9
+ return /* @__PURE__ */ d(a.Backdrop, {
10
+ isOpen: p,
11
+ onOpenChange: m,
12
+ children: /* @__PURE__ */ d(a.Container, { children: /* @__PURE__ */ f(a.Dialog, { children: [
13
+ /* @__PURE__ */ d(a.CloseTrigger, {}),
14
+ /* @__PURE__ */ f(a.Header, { children: [/* @__PURE__ */ d(a.Icon, {
15
+ status: "danger",
16
+ children: /* @__PURE__ */ d(i, {})
17
+ }), /* @__PURE__ */ d(a.Heading, { children: v.deleteApiKey })] }),
18
+ /* @__PURE__ */ f(a.Body, {
19
+ className: "flex flex-col gap-4 overflow-visible",
20
+ children: [/* @__PURE__ */ d("p", {
21
+ className: "text-muted text-sm",
22
+ children: v.deleteApiKeyWarning
23
+ }), /* @__PURE__ */ f(u, {
24
+ value: y,
25
+ className: "font-mono text-xs",
26
+ variant: "secondary",
27
+ children: [/* @__PURE__ */ d(c, { children: h.name || v.apiKey }), /* @__PURE__ */ d(s, {
28
+ readOnly: !0,
29
+ className: "font-mono text-xs"
30
+ })]
31
+ })]
32
+ }),
33
+ /* @__PURE__ */ f(a.Footer, { children: [/* @__PURE__ */ d(o, {
34
+ slot: "close",
35
+ variant: "tertiary",
36
+ isDisabled: x,
37
+ children: _.settings.cancel
38
+ }), /* @__PURE__ */ f(o, {
39
+ variant: "danger",
40
+ onPress: () => b({ keyId: h.id }),
41
+ isPending: x,
42
+ children: [x && /* @__PURE__ */ d(l, {
43
+ color: "current",
44
+ size: "sm"
45
+ }), v.deleteApiKey]
46
+ })] })
47
+ ] }) })
48
+ });
49
+ }
50
+ //#endregion
51
+ export { p as DeleteApiKeyDialog };
@@ -0,0 +1,7 @@
1
+ export type NewApiKeyDialogProps = {
2
+ isOpen: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ name: string | null;
5
+ secretKey: string | null;
6
+ };
7
+ export declare function NewApiKeyDialog({ isOpen, onOpenChange, name, secretKey }: NewApiKeyDialogProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,60 @@
1
+ import { apiKeyPlugin as e } from "../../../lib/auth/api-key-plugin.js";
2
+ import { useAuth as t, useAuthPlugin as n } from "@better-auth-ui/react";
3
+ import { Check as r, Copy as i, Key as a } from "@gravity-ui/icons";
4
+ import { AlertDialog as o, Button as s, InputGroup as c, Label as l, TextField as u, toast as d } from "@heroui/react";
5
+ import { useState as f } from "react";
6
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
7
+ //#region src/components/auth/api-key/new-api-key-dialog.tsx
8
+ function h({ isOpen: h, onOpenChange: g, name: _, secretKey: v }) {
9
+ let { localization: y } = t(), { localization: b } = n(e), [x, S] = f(!1);
10
+ return /* @__PURE__ */ p(o.Backdrop, {
11
+ isOpen: h,
12
+ onOpenChange: g,
13
+ children: /* @__PURE__ */ p(o.Container, { children: /* @__PURE__ */ m(o.Dialog, { children: [
14
+ /* @__PURE__ */ p(o.CloseTrigger, {}),
15
+ /* @__PURE__ */ m(o.Header, { children: [/* @__PURE__ */ p(o.Icon, {
16
+ status: "warning",
17
+ children: /* @__PURE__ */ p(a, {})
18
+ }), /* @__PURE__ */ p(o.Heading, { children: b.newApiKey })] }),
19
+ /* @__PURE__ */ m(o.Body, {
20
+ className: "flex flex-col gap-4 overflow-visible",
21
+ children: [/* @__PURE__ */ p("p", {
22
+ className: "text-muted text-sm",
23
+ children: b.newApiKeyWarning
24
+ }), /* @__PURE__ */ m(u, {
25
+ value: v ?? "",
26
+ className: "font-mono text-xs",
27
+ children: [/* @__PURE__ */ p(l, { children: _ || b.apiKey }), /* @__PURE__ */ m(c, {
28
+ variant: "secondary",
29
+ children: [/* @__PURE__ */ p(c.Input, {
30
+ readOnly: !0,
31
+ className: "font-mono text-xs"
32
+ }), /* @__PURE__ */ p(c.Suffix, {
33
+ className: "px-0",
34
+ children: /* @__PURE__ */ p(s, {
35
+ isIconOnly: !0,
36
+ "aria-label": y.settings.copyToClipboard,
37
+ size: "sm",
38
+ variant: "ghost",
39
+ onPress: async () => {
40
+ if (v) try {
41
+ await navigator.clipboard.writeText(v), S(!0), setTimeout(() => S(!1), 1500);
42
+ } catch (e) {
43
+ d.danger(e instanceof Error ? e.message : String(e));
44
+ }
45
+ },
46
+ children: p(x ? r : i, {})
47
+ })
48
+ })]
49
+ })]
50
+ })]
51
+ }),
52
+ /* @__PURE__ */ p(o.Footer, { children: /* @__PURE__ */ p(s, {
53
+ slot: "close",
54
+ children: b.dismissNewKey
55
+ }) })
56
+ ] }) })
57
+ });
58
+ }
59
+ //#endregion
60
+ export { h as NewApiKeyDialog };
@@ -3,27 +3,26 @@ import { SwitchAccountSubmenuItem as t } from "./switch-account-submenu-item.js"
3
3
  import { multiSessionPlugin as n } from "../../../lib/auth/multi-session-plugin.js";
4
4
  import { useAuth as r, useAuthPlugin as i, useListDeviceSessions as a, useSession as o } from "@better-auth-ui/react";
5
5
  import { Check as s, CirclePlus as c } from "@gravity-ui/icons";
6
- import { Dropdown as l, Label as u, Separator as d } from "@heroui/react";
7
- import { jsx as f, jsxs as p } from "react/jsx-runtime";
6
+ import { Dropdown as l, Label as u } from "@heroui/react";
7
+ import { jsx as d, jsxs as f } from "react/jsx-runtime";
8
8
  //#region src/components/auth/multi-session/switch-account-submenu-content.tsx
9
- function m() {
10
- let { authClient: m, basePaths: h, viewPaths: g } = r(), { localization: _ } = i(n), { data: v } = o(m), { data: y, isPending: b } = a(m);
11
- return /* @__PURE__ */ f(l.Popover, {
9
+ function p() {
10
+ let { authClient: p, basePaths: m, viewPaths: h } = r(), { localization: g } = i(n), { data: _ } = o(p), { data: v, isPending: y } = a(p);
11
+ return /* @__PURE__ */ d(l.Popover, {
12
12
  className: "min-w-40 md:min-w-56 max-w-[48svw]",
13
- children: /* @__PURE__ */ p(l.Menu, { children: [
14
- /* @__PURE__ */ p(l.Item, {
13
+ children: /* @__PURE__ */ f(l.Menu, { children: [
14
+ /* @__PURE__ */ f(l.Item, {
15
15
  className: "px-2",
16
- children: [/* @__PURE__ */ f(e, { isPending: b }), !b && /* @__PURE__ */ f(s, { className: "ml-auto" })]
16
+ children: [/* @__PURE__ */ d(e, { isPending: y }), !y && /* @__PURE__ */ d(s, { className: "ml-auto" })]
17
17
  }),
18
- y?.filter((e) => e.session.id !== v?.session?.id).map((e) => /* @__PURE__ */ f(t, { deviceSession: e }, e.session.id)),
19
- /* @__PURE__ */ f(d, {}),
20
- /* @__PURE__ */ p(l.Item, {
21
- textValue: _.addAccount,
22
- href: `${h.auth}/${g.auth.signIn}`,
23
- children: [/* @__PURE__ */ f(c, { className: "text-muted" }), /* @__PURE__ */ f(u, { children: _.addAccount })]
18
+ v?.filter((e) => e.session.id !== _?.session?.id).map((e) => /* @__PURE__ */ d(t, { deviceSession: e }, e.session.id)),
19
+ /* @__PURE__ */ f(l.Item, {
20
+ textValue: g.addAccount,
21
+ href: `${m.auth}/${h.auth.signIn}`,
22
+ children: [/* @__PURE__ */ d(c, { className: "text-muted" }), /* @__PURE__ */ d(u, { children: g.addAccount })]
24
23
  })
25
24
  ] })
26
25
  });
27
26
  }
28
27
  //#endregion
29
- export { m as SwitchAccountSubmenuContent };
28
+ export { p as SwitchAccountSubmenuContent };
@@ -11,7 +11,7 @@ function g({ account: g, provider: _ }) {
11
11
  children: [
12
12
  /* @__PURE__ */ m("div", {
13
13
  className: f("flex size-10 shrink-0 items-center justify-center rounded-xl bg-surface-secondary"),
14
- children: m(D || c, { className: f("size-4.5", !g && "opacity-50") })
14
+ children: m(D || c, { className: "size-4.5" })
15
15
  }),
16
16
  /* @__PURE__ */ h("div", {
17
17
  className: "flex flex-col min-w-0",
@@ -0,0 +1,23 @@
1
+ import { ApiKeyPluginOptions } from '@better-auth-ui/core/plugins';
2
+ import { ApiKeys } from '../../components/auth/api-key/api-keys';
3
+ export declare const apiKeyPlugin: ((options?: ApiKeyPluginOptions | undefined) => Omit<{
4
+ securityCards: (typeof ApiKeys)[];
5
+ localization: {
6
+ apiKey: string;
7
+ apiKeys: string;
8
+ apiKeysDescription: string;
9
+ createApiKey: string;
10
+ noApiKeys: string;
11
+ name: string;
12
+ newApiKey: string;
13
+ newApiKeyWarning: string;
14
+ deleteApiKey: string;
15
+ deleteApiKeyWarning: string;
16
+ dismissNewKey: string;
17
+ };
18
+ id: "apiKey";
19
+ }, "id"> & {
20
+ id: "apiKey";
21
+ }) & {
22
+ id: "apiKey";
23
+ };
@@ -0,0 +1,10 @@
1
+ import { ApiKeys as e } from "../../components/auth/api-key/api-keys.js";
2
+ import { createAuthPlugin as t } from "@better-auth-ui/core";
3
+ import { apiKeyPlugin as n } from "@better-auth-ui/core/plugins";
4
+ //#region src/lib/auth/api-key-plugin.ts
5
+ var r = t(n.id, (t = {}) => ({
6
+ ...n(t),
7
+ securityCards: [e]
8
+ }));
9
+ //#endregion
10
+ export { r as apiKeyPlugin };
package/dist/plugins.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './components/auth/api-key/api-keys';
1
2
  export * from './components/auth/delete-user/danger-zone';
2
3
  export * from './components/auth/delete-user/delete-user';
3
4
  export * from './components/auth/magic-link/magic-link';
@@ -11,6 +12,7 @@ export * from './components/auth/theme/appearance';
11
12
  export * from './components/auth/theme/theme-toggle-item';
12
13
  export * from './components/auth/username/sign-in-username';
13
14
  export * from './components/auth/username/username-field';
15
+ export * from './lib/auth/api-key-plugin';
14
16
  export * from './lib/auth/auth-plugin';
15
17
  export * from './lib/auth/delete-user-plugin';
16
18
  export * from './lib/auth/magic-link-plugin';
package/dist/plugins.js CHANGED
@@ -1,21 +1,23 @@
1
1
  "use client";
2
- import { deleteUserPlugin as e } from "./lib/auth/delete-user-plugin.js";
3
- import { DeleteUser as t } from "./components/auth/delete-user/delete-user.js";
4
- import { DangerZone as n } from "./components/auth/delete-user/danger-zone.js";
5
- import { magicLinkPlugin as r } from "./lib/auth/magic-link-plugin.js";
6
- import { MagicLink as i } from "./components/auth/magic-link/magic-link.js";
7
- import { ManageAccounts as a } from "./components/auth/multi-session/manage-accounts.js";
8
- import { SwitchAccountSubmenuItem as o } from "./components/auth/multi-session/switch-account-submenu-item.js";
9
- import { SwitchAccountSubmenuContent as s } from "./components/auth/multi-session/switch-account-submenu-content.js";
10
- import { SwitchAccountSubmenu as c } from "./components/auth/multi-session/switch-account-submenu.js";
11
- import { multiSessionPlugin as l } from "./lib/auth/multi-session-plugin.js";
12
- import { ManageAccount as u } from "./components/auth/multi-session/manage-account.js";
13
- import { passkeyPlugin as d } from "./lib/auth/passkey-plugin.js";
14
- import { Passkeys as f } from "./components/auth/passkey/passkeys.js";
15
- import { ThemeToggleItem as p } from "./components/auth/theme/theme-toggle-item.js";
16
- import { themePlugin as m } from "./lib/auth/theme-plugin.js";
17
- import { Appearance as h } from "./components/auth/theme/appearance.js";
18
- import { UsernameField as g } from "./components/auth/username/username-field.js";
19
- import { usernamePlugin as _ } from "./lib/auth/username-plugin.js";
20
- import { SignInUsername as v } from "./components/auth/username/sign-in-username.js";
21
- export { h as Appearance, n as DangerZone, t as DeleteUser, i as MagicLink, u as ManageAccount, a as ManageAccounts, f as Passkeys, v as SignInUsername, c as SwitchAccountSubmenu, s as SwitchAccountSubmenuContent, o as SwitchAccountSubmenuItem, p as ThemeToggleItem, g as UsernameField, e as deleteUserPlugin, r as magicLinkPlugin, l as multiSessionPlugin, d as passkeyPlugin, m as themePlugin, _ as usernamePlugin };
2
+ import { apiKeyPlugin as e } from "./lib/auth/api-key-plugin.js";
3
+ import { ApiKeys as t } from "./components/auth/api-key/api-keys.js";
4
+ import { deleteUserPlugin as n } from "./lib/auth/delete-user-plugin.js";
5
+ import { DeleteUser as r } from "./components/auth/delete-user/delete-user.js";
6
+ import { DangerZone as i } from "./components/auth/delete-user/danger-zone.js";
7
+ import { magicLinkPlugin as a } from "./lib/auth/magic-link-plugin.js";
8
+ import { MagicLink as o } from "./components/auth/magic-link/magic-link.js";
9
+ import { ManageAccounts as s } from "./components/auth/multi-session/manage-accounts.js";
10
+ import { SwitchAccountSubmenuItem as c } from "./components/auth/multi-session/switch-account-submenu-item.js";
11
+ import { SwitchAccountSubmenuContent as l } from "./components/auth/multi-session/switch-account-submenu-content.js";
12
+ import { SwitchAccountSubmenu as u } from "./components/auth/multi-session/switch-account-submenu.js";
13
+ import { multiSessionPlugin as d } from "./lib/auth/multi-session-plugin.js";
14
+ import { ManageAccount as f } from "./components/auth/multi-session/manage-account.js";
15
+ import { passkeyPlugin as p } from "./lib/auth/passkey-plugin.js";
16
+ import { Passkeys as m } from "./components/auth/passkey/passkeys.js";
17
+ import { ThemeToggleItem as h } from "./components/auth/theme/theme-toggle-item.js";
18
+ import { themePlugin as g } from "./lib/auth/theme-plugin.js";
19
+ import { Appearance as _ } from "./components/auth/theme/appearance.js";
20
+ import { UsernameField as v } from "./components/auth/username/username-field.js";
21
+ import { usernamePlugin as y } from "./lib/auth/username-plugin.js";
22
+ import { SignInUsername as b } from "./components/auth/username/sign-in-username.js";
23
+ export { t as ApiKeys, _ as Appearance, i as DangerZone, r as DeleteUser, o as MagicLink, f as ManageAccount, s as ManageAccounts, m as Passkeys, b as SignInUsername, u as SwitchAccountSubmenu, l as SwitchAccountSubmenuContent, c as SwitchAccountSubmenuItem, h as ThemeToggleItem, v as UsernameField, e as apiKeyPlugin, n as deleteUserPlugin, a as magicLinkPlugin, d as multiSessionPlugin, p as passkeyPlugin, g as themePlugin, y as usernamePlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-auth-ui/heroui",
3
- "version": "1.6.6",
3
+ "version": "1.6.7",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "build": "vite build",
@@ -38,7 +38,7 @@
38
38
  "@types/react": "^19.2.14",
39
39
  "@vitejs/plugin-react": "^6.0.1",
40
40
  "@vitest/browser-playwright": "^4.1.5",
41
- "better-auth": "^1.6.9",
41
+ "better-auth": "^1.6.10",
42
42
  "bowser": "^2.11.0",
43
43
  "playwright": "^1.59.1",
44
44
  "react": "^19.2.6",
@@ -54,7 +54,7 @@
54
54
  "@internationalized/date": ">=3.12.1",
55
55
  "@tanstack/react-pacer": ">=0.22.0",
56
56
  "@tanstack/react-query": ">=5.100.9",
57
- "better-auth": ">=1.6.9",
57
+ "better-auth": ">=1.6.10",
58
58
  "react": ">=19.2.6",
59
59
  "react-dom": ">=19.2.6",
60
60
  "bowser": ">=2.11.0"
@@ -0,0 +1,17 @@
1
+ import { Skeleton } from "@heroui/react"
2
+
3
+ export function ApiKeySkeleton() {
4
+ return (
5
+ <div className="flex items-center justify-between">
6
+ <div className="flex items-center gap-3">
7
+ <Skeleton className="size-10 rounded-xl" />
8
+
9
+ <div className="flex flex-col gap-1">
10
+ <Skeleton className="h-4 w-28 rounded-lg" />
11
+ <Skeleton className="h-3 w-36 rounded-lg" />
12
+ <Skeleton className="h-3 w-32 rounded-lg" />
13
+ </div>
14
+ </div>
15
+ </div>
16
+ )
17
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ type ListedApiKey,
3
+ useAuth,
4
+ useAuthPlugin
5
+ } from "@better-auth-ui/react"
6
+ import { Key, Xmark } from "@gravity-ui/icons"
7
+ import { Button } from "@heroui/react"
8
+ import { useState } from "react"
9
+
10
+ import { apiKeyPlugin } from "../../../lib/auth/api-key-plugin"
11
+ import { DeleteApiKeyDialog } from "./delete-api-key-dialog"
12
+
13
+ export type ApiKeyProps = {
14
+ apiKey: ListedApiKey
15
+ }
16
+
17
+ export function ApiKey({ apiKey }: ApiKeyProps) {
18
+ const { localization } = useAuth()
19
+ const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)
20
+ const [deleteOpen, setDeleteOpen] = useState(false)
21
+
22
+ const preview = `${apiKey.start}${"*".repeat(16)}`
23
+
24
+ return (
25
+ <div className="flex items-center gap-3">
26
+ <div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-surface-secondary">
27
+ <Key className="size-4.5" />
28
+ </div>
29
+
30
+ <div className="flex flex-col min-w-0">
31
+ <span className="text-sm font-medium leading-tight truncate">
32
+ {apiKey.name || apiKeyLocalization.apiKey}
33
+ </span>
34
+
35
+ <span className="text-xs text-muted font-mono truncate">{preview}</span>
36
+
37
+ <span className="text-xs text-muted">
38
+ {new Date(apiKey.createdAt).toLocaleString(undefined, {
39
+ dateStyle: "medium",
40
+ timeStyle: "short"
41
+ })}
42
+ </span>
43
+ </div>
44
+
45
+ <Button
46
+ className="ml-auto shrink-0"
47
+ variant="outline"
48
+ size="sm"
49
+ onPress={() => setDeleteOpen(true)}
50
+ aria-label={apiKeyLocalization.deleteApiKey}
51
+ >
52
+ <Xmark />
53
+
54
+ {localization.settings.delete}
55
+ </Button>
56
+
57
+ <DeleteApiKeyDialog
58
+ isOpen={deleteOpen}
59
+ onOpenChange={setDeleteOpen}
60
+ apiKey={apiKey}
61
+ />
62
+ </div>
63
+ )
64
+ }
@@ -0,0 +1,33 @@
1
+ import { useAuthPlugin } from "@better-auth-ui/react"
2
+ import { Key } from "@gravity-ui/icons"
3
+ import { Button } from "@heroui/react"
4
+
5
+ import { apiKeyPlugin } from "../../../lib/auth/api-key-plugin"
6
+
7
+ export type ApiKeysEmptyProps = {
8
+ onCreatePress: () => void
9
+ }
10
+
11
+ export function ApiKeysEmpty({ onCreatePress }: ApiKeysEmptyProps) {
12
+ const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)
13
+
14
+ return (
15
+ <div className="flex flex-col items-center justify-center gap-4">
16
+ <div className="flex size-10 items-center justify-center rounded-xl bg-surface-secondary">
17
+ <Key className="size-4.5" />
18
+ </div>
19
+
20
+ <div className="flex flex-col items-center justify-center gap-1 text-center">
21
+ <p className="text-sm font-semibold">{apiKeyLocalization.noApiKeys}</p>
22
+
23
+ <p className="text-muted text-xs">
24
+ {apiKeyLocalization.apiKeysDescription}
25
+ </p>
26
+ </div>
27
+
28
+ <Button size="sm" onPress={onCreatePress}>
29
+ {apiKeyLocalization.createApiKey}
30
+ </Button>
31
+ </div>
32
+ )
33
+ }
@@ -0,0 +1,71 @@
1
+ import {
2
+ type ApiKeyAuthClient,
3
+ useAuth,
4
+ useAuthPlugin,
5
+ useListApiKeys
6
+ } from "@better-auth-ui/react"
7
+ import { Button, Card, type CardProps, cn } from "@heroui/react"
8
+ import { useState } from "react"
9
+
10
+ import { apiKeyPlugin } from "../../../lib/auth/api-key-plugin"
11
+ import { ApiKey } from "./api-key"
12
+ import { ApiKeySkeleton } from "./api-key-skeleton"
13
+ import { ApiKeysEmpty } from "./api-keys-empty"
14
+ import { CreateApiKeyDialog } from "./create-api-key-dialog"
15
+
16
+ export type ApiKeysProps = {
17
+ className?: string
18
+ variant?: CardProps["variant"]
19
+ }
20
+
21
+ export function ApiKeys({ className, variant }: ApiKeysProps) {
22
+ const { authClient } = useAuth()
23
+ const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)
24
+
25
+ const { data: listData, isPending } = useListApiKeys(
26
+ authClient as ApiKeyAuthClient
27
+ )
28
+
29
+ const [createOpen, setCreateOpen] = useState(false)
30
+
31
+ return (
32
+ <div className={cn("flex flex-col gap-3", className)}>
33
+ <div className="flex items-end justify-between gap-3">
34
+ <h2 className="text-sm font-semibold truncate">
35
+ {apiKeyLocalization.apiKeys}
36
+ </h2>
37
+
38
+ <Button
39
+ className="shrink-0"
40
+ size="sm"
41
+ isDisabled={isPending}
42
+ onPress={() => setCreateOpen(true)}
43
+ >
44
+ {apiKeyLocalization.createApiKey}
45
+ </Button>
46
+ </div>
47
+
48
+ <Card variant={variant}>
49
+ <Card.Content>
50
+ {isPending ? (
51
+ <ApiKeySkeleton />
52
+ ) : !listData?.apiKeys.length ? (
53
+ <ApiKeysEmpty onCreatePress={() => setCreateOpen(true)} />
54
+ ) : (
55
+ listData?.apiKeys.map((key, index) => (
56
+ <div key={key.id}>
57
+ {index > 0 && (
58
+ <div className="border-b border-dashed -mx-4 my-4" />
59
+ )}
60
+
61
+ <ApiKey apiKey={key} />
62
+ </div>
63
+ ))
64
+ )}
65
+ </Card.Content>
66
+ </Card>
67
+
68
+ <CreateApiKeyDialog isOpen={createOpen} onOpenChange={setCreateOpen} />
69
+ </div>
70
+ )
71
+ }
@@ -0,0 +1,134 @@
1
+ import {
2
+ type ApiKeyAuthClient,
3
+ useAuth,
4
+ useAuthPlugin,
5
+ useCreateApiKey
6
+ } from "@better-auth-ui/react"
7
+ import { Key } from "@gravity-ui/icons"
8
+ import {
9
+ AlertDialog,
10
+ Button,
11
+ FieldError,
12
+ Form,
13
+ Input,
14
+ Label,
15
+ Spinner,
16
+ TextField
17
+ } from "@heroui/react"
18
+ import { type SyntheticEvent, useState } from "react"
19
+
20
+ import { apiKeyPlugin } from "../../../lib/auth/api-key-plugin"
21
+
22
+ import { NewApiKeyDialog } from "./new-api-key-dialog"
23
+
24
+ export type CreateApiKeyDialogProps = {
25
+ isOpen: boolean
26
+ onOpenChange: (open: boolean) => void
27
+ }
28
+
29
+ export function CreateApiKeyDialog({
30
+ isOpen,
31
+ onOpenChange
32
+ }: CreateApiKeyDialogProps) {
33
+ const { authClient, localization } = useAuth()
34
+ const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)
35
+
36
+ const { mutate: createApiKey, isPending: isCreating } = useCreateApiKey(
37
+ authClient as ApiKeyAuthClient
38
+ )
39
+
40
+ const [isNewKeyDialogOpen, setIsNewKeyDialogOpen] = useState(false)
41
+ const [keyName, setKeyName] = useState<string | null>(null)
42
+ const [secretKey, setSecretKey] = useState<string | null>(null)
43
+
44
+ const handleOpenChange = (open: boolean) => {
45
+ if (!open) {
46
+ setKeyName(null)
47
+ setSecretKey(null)
48
+ }
49
+
50
+ onOpenChange(open)
51
+ }
52
+
53
+ const handleSubmit = (e: SyntheticEvent<HTMLFormElement>) => {
54
+ e.preventDefault()
55
+
56
+ const formData = new FormData(e.target as HTMLFormElement)
57
+ const name = formData.get("name") as string
58
+
59
+ createApiKey(name ? { name: name.trim() } : undefined, {
60
+ onSuccess: (result) => {
61
+ handleOpenChange(false)
62
+ setKeyName(name)
63
+ setSecretKey(result.key)
64
+ setIsNewKeyDialogOpen(true)
65
+ }
66
+ })
67
+ }
68
+
69
+ return (
70
+ <>
71
+ <AlertDialog.Backdrop isOpen={isOpen} onOpenChange={handleOpenChange}>
72
+ <AlertDialog.Container>
73
+ <AlertDialog.Dialog>
74
+ <Form onSubmit={handleSubmit}>
75
+ <AlertDialog.CloseTrigger />
76
+
77
+ <AlertDialog.Header>
78
+ <AlertDialog.Icon status="default">
79
+ <Key />
80
+ </AlertDialog.Icon>
81
+
82
+ <AlertDialog.Heading>
83
+ {apiKeyLocalization.createApiKey}
84
+ </AlertDialog.Heading>
85
+ </AlertDialog.Header>
86
+
87
+ <AlertDialog.Body className="overflow-visible">
88
+ <p className="text-muted text-sm">
89
+ {apiKeyLocalization.apiKeysDescription}
90
+ </p>
91
+
92
+ <TextField
93
+ className="mt-4"
94
+ id="name"
95
+ name="name"
96
+ isDisabled={isCreating}
97
+ >
98
+ <Label>{apiKeyLocalization.name}</Label>
99
+
100
+ <Input
101
+ autoFocus
102
+ placeholder={localization.settings.optional}
103
+ variant="secondary"
104
+ />
105
+
106
+ <FieldError />
107
+ </TextField>
108
+ </AlertDialog.Body>
109
+
110
+ <AlertDialog.Footer>
111
+ <Button slot="close" variant="tertiary" isDisabled={isCreating}>
112
+ {localization.settings.cancel}
113
+ </Button>
114
+
115
+ <Button type="submit" isPending={isCreating}>
116
+ {isCreating && <Spinner color="current" size="sm" />}
117
+
118
+ {apiKeyLocalization.createApiKey}
119
+ </Button>
120
+ </AlertDialog.Footer>
121
+ </Form>
122
+ </AlertDialog.Dialog>
123
+ </AlertDialog.Container>
124
+ </AlertDialog.Backdrop>
125
+
126
+ <NewApiKeyDialog
127
+ isOpen={isNewKeyDialogOpen}
128
+ onOpenChange={setIsNewKeyDialogOpen}
129
+ secretKey={secretKey}
130
+ name={keyName}
131
+ />
132
+ </>
133
+ )
134
+ }
@@ -0,0 +1,92 @@
1
+ import {
2
+ type ApiKeyAuthClient,
3
+ type ListedApiKey,
4
+ useAuth,
5
+ useAuthPlugin,
6
+ useDeleteApiKey
7
+ } from "@better-auth-ui/react"
8
+ import { Key } from "@gravity-ui/icons"
9
+ import {
10
+ AlertDialog,
11
+ Button,
12
+ Input,
13
+ Label,
14
+ Spinner,
15
+ TextField
16
+ } from "@heroui/react"
17
+
18
+ import { apiKeyPlugin } from "../../../lib/auth/api-key-plugin"
19
+
20
+ export type DeleteApiKeyDialogProps = {
21
+ isOpen: boolean
22
+ onOpenChange: (open: boolean) => void
23
+ apiKey: ListedApiKey
24
+ }
25
+
26
+ export function DeleteApiKeyDialog({
27
+ isOpen,
28
+ onOpenChange,
29
+ apiKey
30
+ }: DeleteApiKeyDialogProps) {
31
+ const { authClient, localization } = useAuth()
32
+ const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)
33
+ const preview = `${apiKey.start}${"*".repeat(16)}`
34
+ const { mutate: deleteApiKey, isPending: isDeleting } = useDeleteApiKey(
35
+ authClient as ApiKeyAuthClient,
36
+ {
37
+ onSuccess: () => onOpenChange(false)
38
+ }
39
+ )
40
+
41
+ return (
42
+ <AlertDialog.Backdrop isOpen={isOpen} onOpenChange={onOpenChange}>
43
+ <AlertDialog.Container>
44
+ <AlertDialog.Dialog>
45
+ <AlertDialog.CloseTrigger />
46
+
47
+ <AlertDialog.Header>
48
+ <AlertDialog.Icon status="danger">
49
+ <Key />
50
+ </AlertDialog.Icon>
51
+
52
+ <AlertDialog.Heading>
53
+ {apiKeyLocalization.deleteApiKey}
54
+ </AlertDialog.Heading>
55
+ </AlertDialog.Header>
56
+
57
+ <AlertDialog.Body className="flex flex-col gap-4 overflow-visible">
58
+ <p className="text-muted text-sm">
59
+ {apiKeyLocalization.deleteApiKeyWarning}
60
+ </p>
61
+
62
+ <TextField
63
+ value={preview}
64
+ className="font-mono text-xs"
65
+ variant="secondary"
66
+ >
67
+ <Label>{apiKey.name || apiKeyLocalization.apiKey}</Label>
68
+
69
+ <Input readOnly className="font-mono text-xs" />
70
+ </TextField>
71
+ </AlertDialog.Body>
72
+
73
+ <AlertDialog.Footer>
74
+ <Button slot="close" variant="tertiary" isDisabled={isDeleting}>
75
+ {localization.settings.cancel}
76
+ </Button>
77
+
78
+ <Button
79
+ variant="danger"
80
+ onPress={() => deleteApiKey({ keyId: apiKey.id })}
81
+ isPending={isDeleting}
82
+ >
83
+ {isDeleting && <Spinner color="current" size="sm" />}
84
+
85
+ {apiKeyLocalization.deleteApiKey}
86
+ </Button>
87
+ </AlertDialog.Footer>
88
+ </AlertDialog.Dialog>
89
+ </AlertDialog.Container>
90
+ </AlertDialog.Backdrop>
91
+ )
92
+ }
@@ -0,0 +1,94 @@
1
+ import { useAuth, useAuthPlugin } from "@better-auth-ui/react"
2
+ import { Check, Copy, Key } from "@gravity-ui/icons"
3
+ import {
4
+ AlertDialog,
5
+ Button,
6
+ InputGroup,
7
+ Label,
8
+ TextField,
9
+ toast
10
+ } from "@heroui/react"
11
+ import { useState } from "react"
12
+
13
+ import { apiKeyPlugin } from "../../../lib/auth/api-key-plugin"
14
+
15
+ export type NewApiKeyDialogProps = {
16
+ isOpen: boolean
17
+ onOpenChange: (open: boolean) => void
18
+ name: string | null
19
+ secretKey: string | null
20
+ }
21
+
22
+ export function NewApiKeyDialog({
23
+ isOpen,
24
+ onOpenChange,
25
+ name,
26
+ secretKey
27
+ }: NewApiKeyDialogProps) {
28
+ const { localization } = useAuth()
29
+ const { localization: apiKeyLocalization } = useAuthPlugin(apiKeyPlugin)
30
+
31
+ const [copied, setCopied] = useState(false)
32
+
33
+ const copySecretKey = async () => {
34
+ if (!secretKey) return
35
+
36
+ try {
37
+ await navigator.clipboard.writeText(secretKey)
38
+ setCopied(true)
39
+ setTimeout(() => setCopied(false), 1500)
40
+ } catch (error) {
41
+ toast.danger(error instanceof Error ? error.message : String(error))
42
+ }
43
+ }
44
+
45
+ return (
46
+ <AlertDialog.Backdrop isOpen={isOpen} onOpenChange={onOpenChange}>
47
+ <AlertDialog.Container>
48
+ <AlertDialog.Dialog>
49
+ <AlertDialog.CloseTrigger />
50
+
51
+ <AlertDialog.Header>
52
+ <AlertDialog.Icon status="warning">
53
+ <Key />
54
+ </AlertDialog.Icon>
55
+
56
+ <AlertDialog.Heading>
57
+ {apiKeyLocalization.newApiKey}
58
+ </AlertDialog.Heading>
59
+ </AlertDialog.Header>
60
+
61
+ <AlertDialog.Body className="flex flex-col gap-4 overflow-visible">
62
+ <p className="text-muted text-sm">
63
+ {apiKeyLocalization.newApiKeyWarning}
64
+ </p>
65
+
66
+ <TextField value={secretKey ?? ""} className="font-mono text-xs">
67
+ <Label>{name || apiKeyLocalization.apiKey}</Label>
68
+
69
+ <InputGroup variant="secondary">
70
+ <InputGroup.Input readOnly className="font-mono text-xs" />
71
+
72
+ <InputGroup.Suffix className="px-0">
73
+ <Button
74
+ isIconOnly
75
+ aria-label={localization.settings.copyToClipboard}
76
+ size="sm"
77
+ variant="ghost"
78
+ onPress={copySecretKey}
79
+ >
80
+ {copied ? <Check /> : <Copy />}
81
+ </Button>
82
+ </InputGroup.Suffix>
83
+ </InputGroup>
84
+ </TextField>
85
+ </AlertDialog.Body>
86
+
87
+ <AlertDialog.Footer>
88
+ <Button slot="close">{apiKeyLocalization.dismissNewKey}</Button>
89
+ </AlertDialog.Footer>
90
+ </AlertDialog.Dialog>
91
+ </AlertDialog.Container>
92
+ </AlertDialog.Backdrop>
93
+ )
94
+ }
@@ -6,7 +6,7 @@ import {
6
6
  useSession
7
7
  } from "@better-auth-ui/react"
8
8
  import { Check, CirclePlus } from "@gravity-ui/icons"
9
- import { Dropdown, Label, Separator } from "@heroui/react"
9
+ import { Dropdown, Label } from "@heroui/react"
10
10
 
11
11
  import { multiSessionPlugin } from "../../../lib/auth/multi-session-plugin"
12
12
  import { UserView } from "../user/user-view"
@@ -50,8 +50,6 @@ export function SwitchAccountSubmenuContent() {
50
50
  />
51
51
  ))}
52
52
 
53
- <Separator />
54
-
55
53
  <Dropdown.Item
56
54
  textValue={multiSessionLocalization.addAccount}
57
55
  href={`${basePaths.auth}/${viewPaths.auth.signIn}`}
File without changes
@@ -60,11 +60,9 @@ export function LinkedAccount({ account, provider }: LinkedAccountProps) {
60
60
  )}
61
61
  >
62
62
  {ProviderIcon ? (
63
- <ProviderIcon className={cn("size-4.5", !account && "opacity-50")} />
63
+ <ProviderIcon className="size-4.5" />
64
64
  ) : (
65
- <PlugConnection
66
- className={cn("size-4.5", !account && "opacity-50")}
67
- />
65
+ <PlugConnection className="size-4.5" />
68
66
  )}
69
67
  </div>
70
68
 
@@ -0,0 +1,15 @@
1
+ import { createAuthPlugin } from "@better-auth-ui/core"
2
+ import {
3
+ type ApiKeyPluginOptions,
4
+ apiKeyPlugin as coreApiKeyPlugin
5
+ } from "@better-auth-ui/core/plugins"
6
+
7
+ import { ApiKeys } from "../../components/auth/api-key/api-keys"
8
+
9
+ export const apiKeyPlugin = createAuthPlugin(
10
+ coreApiKeyPlugin.id,
11
+ (options: ApiKeyPluginOptions = {}) => ({
12
+ ...coreApiKeyPlugin(options),
13
+ securityCards: [ApiKeys]
14
+ })
15
+ )
package/src/plugins.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  "use client"
2
2
 
3
+ export * from "./components/auth/api-key/api-keys"
3
4
  export * from "./components/auth/delete-user/danger-zone"
4
5
  export * from "./components/auth/delete-user/delete-user"
5
6
  // Plugin-contributed components that may be used standalone
@@ -14,6 +15,7 @@ export * from "./components/auth/theme/appearance"
14
15
  export * from "./components/auth/theme/theme-toggle-item"
15
16
  export * from "./components/auth/username/sign-in-username"
16
17
  export * from "./components/auth/username/username-field"
18
+ export * from "./lib/auth/api-key-plugin"
17
19
  export * from "./lib/auth/auth-plugin"
18
20
  export * from "./lib/auth/delete-user-plugin"
19
21
  export * from "./lib/auth/magic-link-plugin"