zudoku 0.1.1-dev.31 → 0.1.1-dev.33

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 (137) hide show
  1. package/dist/app/App.js +8 -1
  2. package/dist/app/App.js.map +1 -1
  3. package/dist/config/config.d.ts +7 -0
  4. package/dist/lib/components/Header.js +1 -1
  5. package/dist/lib/components/Header.js.map +1 -1
  6. package/dist/lib/components/Layout.js +1 -2
  7. package/dist/lib/components/Layout.js.map +1 -1
  8. package/dist/lib/components/SyntaxHighlight.d.ts +1 -0
  9. package/dist/lib/components/SyntaxHighlight.js +1 -1
  10. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  11. package/dist/lib/components/context/DevPortalProvider.js +1 -1
  12. package/dist/lib/components/context/DevPortalProvider.js.map +1 -1
  13. package/dist/lib/components/navigation/SideNavigation.js +1 -1
  14. package/dist/lib/components/navigation/SideNavigation.js.map +1 -1
  15. package/dist/lib/components/navigation/SideNavigationCategory.js +1 -1
  16. package/dist/lib/components/navigation/SideNavigationCategory.js.map +1 -1
  17. package/dist/lib/components/navigation/SideNavigationItem.d.ts +2 -1
  18. package/dist/lib/components/navigation/SideNavigationItem.js +11 -4
  19. package/dist/lib/components/navigation/SideNavigationItem.js.map +1 -1
  20. package/dist/lib/components/navigation/SideNavigationWrapper.d.ts +3 -0
  21. package/dist/lib/components/navigation/SideNavigationWrapper.js +2 -3
  22. package/dist/lib/components/navigation/SideNavigationWrapper.js.map +1 -1
  23. package/dist/lib/core/DevPortalContext.d.ts +1 -0
  24. package/dist/lib/core/DevPortalContext.js +2 -2
  25. package/dist/lib/core/DevPortalContext.js.map +1 -1
  26. package/dist/lib/core/plugins.d.ts +1 -1
  27. package/dist/lib/core/plugins.js +1 -1
  28. package/dist/lib/core/plugins.js.map +1 -1
  29. package/dist/lib/plugins/api-key/CreateApiKeys.d.ts +5 -0
  30. package/dist/lib/plugins/api-key/CreateApiKeys.js +37 -0
  31. package/dist/lib/plugins/api-key/CreateApiKeys.js.map +1 -0
  32. package/dist/lib/plugins/api-key/SettingsApiKeys.d.ts +3 -2
  33. package/dist/lib/plugins/api-key/SettingsApiKeys.js +35 -4
  34. package/dist/lib/plugins/api-key/SettingsApiKeys.js.map +1 -1
  35. package/dist/lib/plugins/api-key/index.d.ts +19 -21
  36. package/dist/lib/plugins/api-key/index.js +57 -36
  37. package/dist/lib/plugins/api-key/index.js.map +1 -1
  38. package/dist/lib/plugins/index.js +0 -1
  39. package/dist/lib/plugins/index.js.map +1 -1
  40. package/dist/lib/plugins/openapi/MakeRequest.js +27 -12
  41. package/dist/lib/plugins/openapi/MakeRequest.js.map +1 -1
  42. package/dist/lib/plugins/openapi/OperationListItem.js +24 -1
  43. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  44. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +1 -1
  45. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  46. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +2 -2
  47. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
  48. package/dist/lib/plugins/openapi/Sidecar.js +13 -13
  49. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  50. package/dist/lib/plugins/openapi/graphql/gql.d.ts +2 -2
  51. package/dist/lib/plugins/openapi/graphql/gql.js +1 -1
  52. package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
  53. package/dist/lib/plugins/openapi/graphql/graphql.d.ts +1 -0
  54. package/dist/lib/plugins/openapi/graphql/graphql.js +4 -0
  55. package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
  56. package/dist/lib/plugins/openapi/index.js +2 -0
  57. package/dist/lib/plugins/openapi/index.js.map +1 -1
  58. package/dist/lib/plugins/openapi/playground/Headers.js +5 -16
  59. package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
  60. package/dist/lib/plugins/openapi/playground/InlineInput.d.ts +1 -0
  61. package/dist/lib/plugins/openapi/playground/InlineInput.js +1 -1
  62. package/dist/lib/plugins/openapi/playground/{UrlParts.d.ts → PathParams.d.ts} +2 -2
  63. package/dist/lib/plugins/openapi/playground/{UrlParts.js → PathParams.js} +4 -4
  64. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -0
  65. package/dist/lib/plugins/openapi/playground/Playground.d.ts +8 -4
  66. package/dist/lib/plugins/openapi/playground/Playground.js +49 -46
  67. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  68. package/dist/lib/plugins/openapi/playground/QueryParams.d.ts +4 -6
  69. package/dist/lib/plugins/openapi/playground/QueryParams.js +27 -22
  70. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  71. package/dist/lib/plugins/openapi/playground/UrlDisplay.d.ts +4 -0
  72. package/dist/lib/plugins/openapi/playground/UrlDisplay.js +22 -0
  73. package/dist/lib/plugins/openapi/playground/UrlDisplay.js.map +1 -0
  74. package/dist/lib/plugins/openapi/playground/createUrl.d.ts +2 -0
  75. package/dist/lib/plugins/openapi/playground/createUrl.js +15 -0
  76. package/dist/lib/plugins/openapi/playground/createUrl.js.map +1 -0
  77. package/dist/lib/plugins/openapi/util/generateSchemaExample.d.ts +1 -1
  78. package/dist/lib/plugins/openapi/util/generateSchemaExample.js +19 -9
  79. package/dist/lib/plugins/openapi/util/generateSchemaExample.js.map +1 -1
  80. package/dist/lib/plugins/redirect/index.d.ts +1 -2
  81. package/dist/lib/util/MdxComponents.js +1 -1
  82. package/dist/lib/util/MdxComponents.js.map +1 -1
  83. package/dist/lib/util/createVariantComponent.d.ts +4 -3
  84. package/dist/lib/util/createVariantComponent.js +9 -5
  85. package/dist/lib/util/createVariantComponent.js.map +1 -1
  86. package/dist/vite/plugin-api-keys.d.ts +4 -0
  87. package/dist/vite/plugin-api-keys.js +33 -0
  88. package/dist/vite/plugin-api-keys.js.map +1 -0
  89. package/dist/vite/plugin-mdx.js +1 -1
  90. package/dist/vite/plugin-mdx.js.map +1 -1
  91. package/dist/vite/plugin-redirect.d.ts +4 -0
  92. package/dist/vite/plugin-redirect.js +32 -0
  93. package/dist/vite/plugin-redirect.js.map +1 -0
  94. package/dist/vite/plugin.js +4 -0
  95. package/dist/vite/plugin.js.map +1 -1
  96. package/lib/{urql-B7mLfVog.js → urql-DMlBWUKL.js} +301 -321
  97. package/lib/{DevPortal-Dh66z5c3.js → util-BJVAslZ-.js} +3666 -4913
  98. package/lib/zudoku.components.js +1310 -4
  99. package/lib/zudoku.openapi-worker.js +1 -1
  100. package/lib/zudoku.plugins.js +12876 -12545
  101. package/package.json +1 -1
  102. package/src/app/App.tsx +8 -1
  103. package/src/lib/components/Header.tsx +2 -2
  104. package/src/lib/components/Layout.tsx +10 -6
  105. package/src/lib/components/SyntaxHighlight.tsx +6 -3
  106. package/src/lib/components/context/DevPortalProvider.ts +1 -1
  107. package/src/lib/components/navigation/SideNavigation.tsx +4 -1
  108. package/src/lib/components/navigation/SideNavigationCategory.tsx +3 -1
  109. package/src/lib/components/navigation/SideNavigationItem.tsx +15 -5
  110. package/src/lib/components/navigation/SideNavigationWrapper.tsx +14 -11
  111. package/src/lib/core/DevPortalContext.ts +3 -2
  112. package/src/lib/core/plugins.ts +1 -1
  113. package/src/lib/plugins/api-key/CreateApiKeys.tsx +84 -0
  114. package/src/lib/plugins/api-key/SettingsApiKeys.tsx +111 -11
  115. package/src/lib/plugins/api-key/index.tsx +80 -77
  116. package/src/lib/plugins/index.ts +0 -1
  117. package/src/lib/plugins/openapi/MakeRequest.tsx +38 -29
  118. package/src/lib/plugins/openapi/OperationListItem.tsx +137 -1
  119. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +1 -1
  120. package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +1 -2
  121. package/src/lib/plugins/openapi/Sidecar.tsx +20 -20
  122. package/src/lib/plugins/openapi/graphql/gql.ts +3 -3
  123. package/src/lib/plugins/openapi/graphql/graphql.ts +5 -0
  124. package/src/lib/plugins/openapi/index.tsx +2 -0
  125. package/src/lib/plugins/openapi/playground/Headers.tsx +14 -20
  126. package/src/lib/plugins/openapi/playground/InlineInput.tsx +1 -1
  127. package/src/lib/plugins/openapi/playground/{UrlParts.tsx → PathParams.tsx} +22 -26
  128. package/src/lib/plugins/openapi/playground/Playground.tsx +205 -155
  129. package/src/lib/plugins/openapi/playground/QueryParams.tsx +89 -57
  130. package/src/lib/plugins/openapi/playground/UrlDisplay.tsx +32 -0
  131. package/src/lib/plugins/openapi/playground/createUrl.ts +22 -0
  132. package/src/lib/plugins/openapi/util/generateSchemaExample.ts +24 -9
  133. package/src/lib/plugins/redirect/index.tsx +1 -1
  134. package/src/lib/util/MdxComponents.tsx +1 -0
  135. package/src/lib/util/createVariantComponent.tsx +11 -8
  136. package/dist/lib/plugins/openapi/playground/UrlParts.js.map +0 -1
  137. package/src/lib/plugins/openapi/queries.graphql +0 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.1.1-dev.31",
3
+ "version": "0.1.1-dev.33",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
package/src/app/App.tsx CHANGED
@@ -7,6 +7,8 @@ import config from "virtual:zudoku-config";
7
7
  import { configuredApiPlugins } from "virtual:zudoku-api-plugins";
8
8
  import { configuredAuthProvider } from "virtual:zudoku-auth";
9
9
  import { configuredDocsPlugins } from "virtual:zudoku-docs-plugins";
10
+ import { configuredApiKeysPlugin } from "virtual:zudoku-api-keys-plugin";
11
+ import { configuredRedirectPlugin } from "virtual:zudoku-redirect-plugin";
10
12
 
11
13
  // Base React Component
12
14
  import { DevPortal } from "zudoku/components";
@@ -27,7 +29,12 @@ export default function App() {
27
29
  }}
28
30
  navigation={config.navigation ?? []}
29
31
  authentication={configuredAuthProvider}
30
- plugins={[...configuredDocsPlugins, ...configuredApiPlugins]}
32
+ plugins={[
33
+ ...configuredDocsPlugins,
34
+ ...configuredApiPlugins,
35
+ configuredApiKeysPlugin,
36
+ configuredRedirectPlugin,
37
+ ]}
31
38
  />
32
39
  );
33
40
  }
@@ -37,9 +37,9 @@ export const Header = memo(() => {
37
37
  </button>
38
38
  </div>
39
39
  <div className="items-center justify-self-end text-sm hidden lg:flex">
40
- {!isLoggedIn ? (
40
+ {isLoggedIn ? (
41
41
  <button
42
- className="cursor-pointer hover:bg-secondary p-1 px-2 mx-2 rounded"
42
+ className="cursor-pointer hover:bg-secondary p-1 px-2 mx-2 rounded text-nowrap"
43
43
  onClick={logout}
44
44
  >
45
45
  Logout {email ? `(${email})` : null}
@@ -41,15 +41,19 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
41
41
  {meta?.favicon && <link rel="icon" href={meta.favicon} />}
42
42
  </Helmet>
43
43
  <Header />
44
+
44
45
  <div className="max-w-screen-2xl mx-auto pt-[--header-height] px-10 lg:px-12">
45
- <Suspense
46
- fallback={<SideNavigationWrapper>Loading...</SideNavigationWrapper>}
47
- >
46
+ <Suspense fallback={<div className="top-48">Warte kurz...</div>}>
48
47
  <SideNavigation />
48
+ <main
49
+ className="dark:border-white/10 translate-x-0
50
+ lg:overflow-visible
51
+ lg:peer-data-[navigation=true]:w-[calc(100%-var(--side-nav-width))]
52
+ lg:peer-data-[navigation=true]:translate-x-[--side-nav-width] peer-data-[navigation=true]:pl-12"
53
+ >
54
+ {children ?? <Outlet />}
55
+ </main>
49
56
  </Suspense>
50
- <main className="dark:border-white/10 lg:overflow-visible translate-x-0 lg:w-[calc(100%-var(--side-nav-width))] lg:translate-x-[--side-nav-width] pl-12">
51
- {children ?? <Outlet />}
52
- </main>
53
57
  </div>
54
58
  </>
55
59
  );
@@ -32,6 +32,7 @@ type SyntaxHighlightProps = {
32
32
  noBackground?: boolean;
33
33
  wrapLines?: boolean;
34
34
  copyable?: boolean;
35
+ showLanguageIndicator?: boolean;
35
36
  } & Omit<HighlightProps, "children">;
36
37
 
37
38
  export const SyntaxHighlight = ({
@@ -81,9 +82,11 @@ export const SyntaxHighlight = ({
81
82
  )}
82
83
  </button>
83
84
  )}
84
- <span className="absolute top-1.5 right-3 text-[11px] font-mono text-muted-foreground transition group-hover:opacity-0">
85
- {props.language}
86
- </span>
85
+ {props.showLanguageIndicator && (
86
+ <span className="absolute top-1.5 right-3 text-[11px] font-mono text-muted-foreground transition group-hover:opacity-0">
87
+ {props.language}
88
+ </span>
89
+ )}
87
90
  {tokens.map((line, i) => (
88
91
  <div key={i} {...getLineProps({ line })}>
89
92
  {line.map((token, key) => (
@@ -23,7 +23,7 @@ export const useApiIdentities = () => {
23
23
  const { getApiIdentities } = useDevPortal();
24
24
  return useSuspenseQuery({
25
25
  queryFn: getApiIdentities,
26
- queryKey: ["api-keys"],
26
+ queryKey: ["api-identities"],
27
27
  });
28
28
  };
29
29
 
@@ -9,7 +9,10 @@ export const SideNavigation = () => {
9
9
  const navigation = useNavigation();
10
10
 
11
11
  return (
12
- <SideNavigationWrapper ref={navRef}>
12
+ <SideNavigationWrapper
13
+ ref={navRef}
14
+ data={navigation.data.items.length > 0 ? "true" : "false"}
15
+ >
13
16
  {navigation.data.items.map((category) => (
14
17
  <SideNavigationCategory key={category.label} category={category} />
15
18
  ))}
@@ -36,7 +36,7 @@ export const SideNavigationCategory = ({
36
36
  open={isOpen}
37
37
  onOpenChange={() => setIsOpen((prev) => !prev)}
38
38
  >
39
- {category.label.length > 0 && (
39
+ {category.label.length > 0 ? (
40
40
  <Collapsible.Trigger asChild={isCollapsible} disabled={!isCollapsible}>
41
41
  <h5
42
42
  className={cn(
@@ -53,6 +53,8 @@ export const SideNavigationCategory = ({
53
53
  )}
54
54
  </h5>
55
55
  </Collapsible.Trigger>
56
+ ) : (
57
+ "Endpoints"
56
58
  )}
57
59
  <Collapsible.Content className="CollapsibleContent -mx-[--padding-nav-item]">
58
60
  {/* margin on Collapsible.Content will lead jumpiness when animating because it's not added to the calculated height */}
@@ -15,7 +15,7 @@ import { DynamicIcon, isValidIcon } from "../DynamicIcon.js";
15
15
  import { useNavigationCollapsibleState } from "./useNavigationCollapsibleState.js";
16
16
  import { checkHasActiveItem, isLinkItem, isPathItem } from "./util.js";
17
17
 
18
- export const linkItem = cva(
18
+ export const navigationListItem = cva(
19
19
  "flex px-[--padding-nav-item] py-1.5 rounded-lg hover:bg-accent transition-colors duration-300",
20
20
  {
21
21
  variants: {
@@ -23,6 +23,10 @@ export const linkItem = cva(
23
23
  true: "text-primary font-medium",
24
24
  false: "text-foreground/80",
25
25
  },
26
+ isMuted: {
27
+ true: "text-foreground/30",
28
+ false: "",
29
+ },
26
30
  },
27
31
  },
28
32
  );
@@ -54,7 +58,9 @@ export const SideNavigationItem = ({
54
58
  if (isLinkItem(item)) {
55
59
  const classes = cn(
56
60
  "flex items-center gap-2",
57
- linkItem({ isActive: item.href === location.pathname }),
61
+ navigationListItem({
62
+ isActive: item.href === location.pathname,
63
+ }),
58
64
  );
59
65
  return item.href.startsWith("http") ? (
60
66
  <a
@@ -101,7 +107,10 @@ export const SideNavigationItem = ({
101
107
  className="flex flex-col"
102
108
  >
103
109
  <Collapsible.Trigger
104
- className={cn("group text-start", linkItem({ isActive: false }))}
110
+ className={cn(
111
+ "group text-start",
112
+ navigationListItem({ isActive: false }),
113
+ )}
105
114
  >
106
115
  {linkContent}
107
116
  </Collapsible.Trigger>
@@ -124,15 +133,16 @@ export const SideNavigationItem = ({
124
133
  <AnchorLink
125
134
  to={item.path}
126
135
  {...{ [DATA_ANCHOR_ATTR]: item.path }}
127
- className={linkItem({
136
+ className={navigationListItem({
128
137
  isActive: item.path.slice(1) === activeAnchor,
138
+ isMuted: item.muted,
129
139
  })}
130
140
  >
131
141
  {linkContent}
132
142
  </AnchorLink>
133
143
  ) : (
134
144
  <NavLink
135
- className={({ isActive }) => linkItem({ isActive })}
145
+ className={({ isActive }) => navigationListItem({ isActive })}
136
146
  to={currentPath}
137
147
  >
138
148
  {linkContent}
@@ -1,15 +1,18 @@
1
1
  import { forwardRef, type PropsWithChildren } from "react";
2
+ import { cn } from "../../util/cn.js";
2
3
 
3
4
  export const SideNavigationWrapper = forwardRef<
4
5
  HTMLDivElement,
5
- PropsWithChildren
6
- >(({ children }, ref) => {
7
- return (
8
- <nav
9
- className="hidden lg:flex flex-col fixed text-sm overflow-y-auto shrink-0 p-[--padding-nav-item] -mx-[--padding-nav-item] pb-20 pt-[--padding-content-top] w-[--side-nav-width] h-[calc(100%-var(--header-height))] scroll-pt-2 gap-2"
10
- ref={ref}
11
- >
12
- {children}
13
- </nav>
14
- );
15
- });
6
+ PropsWithChildren<{ data?: string; className?: string }>
7
+ >(({ children, className, data }, ref) => (
8
+ <nav
9
+ data-navigation={data}
10
+ className={cn(
11
+ "peer hidden lg:flex flex-col fixed text-sm overflow-y-auto shrink-0 p-[--padding-nav-item] -mx-[--padding-nav-item] pb-20 pt-[--padding-content-top] w-[--side-nav-width] h-[calc(100%-var(--header-height))] scroll-pt-2 gap-2",
12
+ className,
13
+ )}
14
+ ref={ref}
15
+ >
16
+ {children}
17
+ </nav>
18
+ ));
@@ -7,7 +7,7 @@ import {
7
7
  type DevPortalProps,
8
8
  } from "../components/DevPortal.js";
9
9
  import {
10
- isApiKeyPlugin,
10
+ isApiIdentityPlugin,
11
11
  isNavigationPlugin,
12
12
  needsInitialization,
13
13
  type DevPortalPlugin,
@@ -26,6 +26,7 @@ export interface ApiIdentity {
26
26
  type BaseNavigationCategoryItem = {
27
27
  label: ReactNode;
28
28
  title?: string;
29
+ muted?: boolean;
29
30
  icon?: ReactNode | IconName;
30
31
  };
31
32
 
@@ -117,7 +118,7 @@ export class DevPortalContext {
117
118
  getApiIdentities = async () => {
118
119
  const keys = await Promise.all(
119
120
  this.plugins
120
- .filter(isApiKeyPlugin)
121
+ .filter(isApiIdentityPlugin)
121
122
  .map((plugin) => plugin.getIdentities(this)),
122
123
  );
123
124
 
@@ -37,7 +37,7 @@ export const needsInitialization = (
37
37
  ): obj is InitializationPlugin =>
38
38
  "initialize" in obj && typeof obj.initialize === "function";
39
39
 
40
- export const isApiKeyPlugin = (
40
+ export const isApiIdentityPlugin = (
41
41
  obj: DevPortalPlugin,
42
42
  ): obj is ApiIdentityPlugin =>
43
43
  "getIdentities" in obj && typeof obj.getIdentities === "function";
@@ -0,0 +1,84 @@
1
+ import { useDevPortal } from "../../components/context/DevPortalProvider.js";
2
+ import { type ApiKeyPluginOptions, ApiKeyService } from "./index.js";
3
+ import { useMutation } from "@tanstack/react-query";
4
+ import { Button } from "../../ui/Button.js";
5
+ import { Input } from "../../components/Input.js";
6
+ import { cn } from "../../util/cn.js";
7
+ import { Link, useNavigate } from "react-router-dom";
8
+ import { useForm } from "react-hook-form";
9
+
10
+ type CreateApiKeys = { description: string; expiresAt?: string };
11
+
12
+ export const CreateApiKey = ({
13
+ options,
14
+ service,
15
+ }: {
16
+ options: ApiKeyPluginOptions;
17
+ service: ApiKeyService;
18
+ }) => {
19
+ const context = useDevPortal();
20
+ const navigate = useNavigate();
21
+ const form = useForm<CreateApiKeys>();
22
+ const createKeyMutation = useMutation({
23
+ mutationFn: ({ description, expiresAt }: CreateApiKeys) => {
24
+ if (!service.createKey) {
25
+ throw new Error("deleteKey not implemented");
26
+ }
27
+
28
+ return service.createKey({ description }, context);
29
+ },
30
+ onSuccess: () => navigate("/settings/api-keys/"),
31
+ });
32
+
33
+ if (!service.createKey) {
34
+ return null;
35
+ }
36
+
37
+ return (
38
+ <div className="max-w-screen-lg pt-[--padding-content-top] pb-[--padding-content-bottom]">
39
+ <div className="flex justify-between mb-4 border-b pb-1">
40
+ <h1 className="font-medium text-2xl">New API Keys</h1>
41
+ </div>
42
+ <form
43
+ onSubmit={form.handleSubmit((data) => {
44
+ if (data.expiresAt === "never") {
45
+ delete data.expiresAt;
46
+ }
47
+ createKeyMutation.mutate(data);
48
+ })}
49
+ >
50
+ <div className="flex gap-2 flex-col">
51
+ Note
52
+ <Input {...form.register("description")} />
53
+ Expiration
54
+ <select
55
+ className={cn(
56
+ "row-start-1 col-start-1 border border-input text-foreground px-2 py-1 pe-6",
57
+ "rounded-md appearance-none bg-zinc-50 hover:bg-white dark:bg-zinc-800 hover:dark:bg-zinc-800/75",
58
+ )}
59
+ {...form.register("expiresAt")}
60
+ >
61
+ <option value="never">Never</option>
62
+ {[7, 30, 60, 90].map((option) => (
63
+ <option value={addDaysToDate(option)} key={option}>
64
+ {option} Days
65
+ </option>
66
+ ))}
67
+ </select>
68
+ <div className="flex gap-2">
69
+ <Button>Generate Key</Button>
70
+ <Button variant="outline" asChild>
71
+ <Link to="/settings/api-keys/">Cancel</Link>
72
+ </Button>
73
+ </div>
74
+ </div>
75
+ </form>
76
+ </div>
77
+ );
78
+ };
79
+
80
+ const addDaysToDate = (days: number): string => {
81
+ const date = new Date();
82
+ date.setDate(date.getDate() + days);
83
+ return date.toISOString().split("T")[0];
84
+ };
@@ -1,22 +1,122 @@
1
- import { useApiIdentities } from "../../components/context/DevPortalProvider.js";
2
- import { type ApiKeyPluginOptions } from "./index.js";
1
+ import { useDevPortal } from "../../components/context/DevPortalProvider.js";
2
+ import { type ApiKeyPluginOptions, ApiKeyService } from "./index.js";
3
+ import {
4
+ useMutation,
5
+ useQueryClient,
6
+ useSuspenseQuery,
7
+ } from "@tanstack/react-query";
8
+ import { Button } from "../../ui/Button.js";
9
+ import { Card } from "../../ui/Card.js";
10
+ import { EyeIcon, EyeOffIcon, RotateCwIcon, TrashIcon } from "lucide-react";
11
+ import { Link } from "react-router-dom";
12
+ import { useState } from "react";
3
13
 
4
14
  export const SettingsApiKeys = ({
5
15
  options,
16
+ service,
6
17
  }: {
7
18
  options: ApiKeyPluginOptions;
19
+ service: ApiKeyService;
8
20
  }) => {
9
- const keys = useApiIdentities();
21
+ const context = useDevPortal();
22
+ const queryClient = useQueryClient();
23
+ const { data } = useSuspenseQuery({
24
+ queryFn: () => service.getKeys(context),
25
+ queryKey: ["api-keys"],
26
+ });
27
+
28
+ const deleteKeyMutation = useMutation({
29
+ mutationFn: (id: string) => {
30
+ if (!service.deleteKey) {
31
+ throw new Error("deleteKey not implemented");
32
+ }
33
+
34
+ return service.deleteKey(id, context);
35
+ },
36
+ onSuccess: () => {
37
+ void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
38
+ },
39
+ });
40
+
41
+ return (
42
+ <div className="max-w-screen-lg pt-[--padding-content-top] pb-[--padding-content-bottom]">
43
+ <div className="flex justify-between mb-4 border-b border-border pb-1">
44
+ <h1 className="font-medium text-2xl">API Keys</h1>
45
+ {service.createKey && (
46
+ <Button asChild>
47
+ <Link to="/settings/api-keys/new">Create API Key</Link>
48
+ </Button>
49
+ )}
50
+ </div>
51
+
52
+ <Card>
53
+ <ul className="grid grid-cols-[min-content_1fr_min-content] ">
54
+ {data?.map((key) => (
55
+ <li
56
+ className="border-b border-border p-5 grid grid-cols-subgrid col-span-full gap-2 items-center"
57
+ key={key.id}
58
+ >
59
+ <div className="flex flex-col gap-1 text-sm">
60
+ {key.description ?? key.id}
61
+ {key.expiresOn ? (
62
+ <div>Expires on {key.expiresOn}</div>
63
+ ) : (
64
+ key.createdOn && (
65
+ <div className="text-gray-500 whitespace-pre">
66
+ Created on {new Date(key.createdOn).toLocaleDateString()}
67
+ </div>
68
+ )
69
+ )}
70
+ </div>
71
+ <div className="items-center flex justify-center">
72
+ <RevealApiKey apiKey={key.key} />
73
+ </div>
74
+ <div className="flex gap-2">
75
+ {service.rollKey && (
76
+ <Button size="icon">
77
+ <RotateCwIcon size={16} />
78
+ </Button>
79
+ )}
80
+ {service.deleteKey && (
81
+ <Button
82
+ variant="ghost"
83
+ size="icon"
84
+ onClick={() => {
85
+ if (!confirm("Do you want to delete this key?")) {
86
+ return;
87
+ }
88
+
89
+ deleteKeyMutation.mutate(key.id);
90
+ }}
91
+ disabled={deleteKeyMutation.isPending}
92
+ >
93
+ <TrashIcon size={16} />
94
+ </Button>
95
+ )}
96
+ </div>
97
+ </li>
98
+ ))}
99
+ </ul>
100
+ </Card>
101
+ </div>
102
+ );
103
+ };
104
+
105
+ const RevealApiKey = ({ apiKey }: { apiKey: string }) => {
106
+ const [revealed, setRevealed] = useState(false);
10
107
 
11
108
  return (
12
- <div className="prose dark:prose-invert">
13
- <h1>Who's who</h1>
14
- <button className="border">Create API Key</button>
15
- <ul>
16
- {keys.data.map((key) => (
17
- <li>{key.name}</li>
18
- ))}
19
- </ul>
109
+ <div className="flex gap-2 items-center text-sm">
110
+ <code className="border border-border rounded bg-gray-100 dark:bg-gray-950 p-1 font-mono">
111
+ {revealed ? apiKey : "".repeat(apiKey.length)}
112
+ </code>
113
+ <Button
114
+ variant="outline"
115
+ onClick={() => setRevealed((prev) => !prev)}
116
+ size="icon"
117
+ >
118
+ {revealed ? <EyeOffIcon size={16} /> : <EyeIcon size={16} />}
119
+ </Button>
20
120
  </div>
21
121
  );
22
122
  };