zudoku 0.30.1 → 0.31.0

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 (69) hide show
  1. package/dist/config/loader.js +4 -1
  2. package/dist/config/loader.js.map +1 -1
  3. package/dist/config/validators/common.d.ts +97 -12
  4. package/dist/config/validators/common.js +16 -1
  5. package/dist/config/validators/common.js.map +1 -1
  6. package/dist/config/validators/validate.d.ts +33 -6
  7. package/dist/lib/components/ThemeSwitch.js +8 -4
  8. package/dist/lib/components/ThemeSwitch.js.map +1 -1
  9. package/dist/lib/components/context/ZudokuContext.js +8 -2
  10. package/dist/lib/components/context/ZudokuContext.js.map +1 -1
  11. package/dist/lib/core/RouteGuard.js +6 -2
  12. package/dist/lib/core/RouteGuard.js.map +1 -1
  13. package/dist/lib/oas/parser/upgrade/index.js +2 -1
  14. package/dist/lib/oas/parser/upgrade/index.js.map +1 -1
  15. package/dist/lib/util/useLatest.d.ts +1 -0
  16. package/dist/lib/util/useLatest.js +15 -0
  17. package/dist/lib/util/useLatest.js.map +1 -0
  18. package/dist/vite/config.js +25 -12
  19. package/dist/vite/config.js.map +1 -1
  20. package/dist/vite/plugin-api.js +26 -10
  21. package/dist/vite/plugin-api.js.map +1 -1
  22. package/dist/vite/plugin-mdx.js +31 -26
  23. package/dist/vite/plugin-mdx.js.map +1 -1
  24. package/dist/vite/prerender/worker.js +2 -1
  25. package/dist/vite/prerender/worker.js.map +1 -1
  26. package/lib/{AuthenticationPlugin-DeEA69mE.js → AuthenticationPlugin-DjnQ5hs7.js} +2 -2
  27. package/lib/{AuthenticationPlugin-DeEA69mE.js.map → AuthenticationPlugin-DjnQ5hs7.js.map} +1 -1
  28. package/lib/{MdxPage-BalfwlsD.js → MdxPage-ZX2oquWw.js} +3 -3
  29. package/lib/{MdxPage-BalfwlsD.js.map → MdxPage-ZX2oquWw.js.map} +1 -1
  30. package/lib/{OpenApiRoute-ULLXjfro.js → OpenApiRoute-BP9kzG5k.js} +2 -2
  31. package/lib/{OpenApiRoute-ULLXjfro.js.map → OpenApiRoute-BP9kzG5k.js.map} +1 -1
  32. package/lib/{OperationList-CnYOgwBj.js → OperationList-c72qPMtm.js} +6 -7
  33. package/lib/{OperationList-CnYOgwBj.js.map → OperationList-c72qPMtm.js.map} +1 -1
  34. package/lib/{Select-BcAbBUmk.js → Select-NSz0gku6.js} +3 -3
  35. package/lib/{Select-BcAbBUmk.js.map → Select-NSz0gku6.js.map} +1 -1
  36. package/lib/{createServer-E3cXjB0P.js → createServer-ZDNGmPfQ.js} +2 -2
  37. package/lib/{createServer-E3cXjB0P.js.map → createServer-ZDNGmPfQ.js.map} +1 -1
  38. package/lib/hook-Dnj3SwPC.js +1455 -0
  39. package/lib/hook-Dnj3SwPC.js.map +1 -0
  40. package/lib/{index-CnKeVT2S.js → index-CZjcfK-H.js} +28 -30
  41. package/lib/{index-CnKeVT2S.js.map → index-CZjcfK-H.js.map} +1 -1
  42. package/lib/{hook-NIpDSpau.js → mutation-ByGtmi0-.js} +46 -65
  43. package/lib/mutation-ByGtmi0-.js.map +1 -0
  44. package/lib/{useScrollToAnchor-BVCQSeKB.js → useScrollToAnchor-DkVfWsxe.js} +2 -2
  45. package/lib/{useScrollToAnchor-BVCQSeKB.js.map → useScrollToAnchor-DkVfWsxe.js.map} +1 -1
  46. package/lib/zudoku.auth-auth0.js +1 -1
  47. package/lib/zudoku.auth-clerk.js +2 -2
  48. package/lib/zudoku.auth-openid.js +4 -5
  49. package/lib/zudoku.auth-openid.js.map +1 -1
  50. package/lib/zudoku.components.js +519 -489
  51. package/lib/zudoku.components.js.map +1 -1
  52. package/lib/zudoku.plugin-api-catalog.js +15 -16
  53. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  54. package/lib/zudoku.plugin-api-keys.js +12 -13
  55. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  56. package/lib/zudoku.plugin-markdown.js +1 -1
  57. package/lib/zudoku.plugin-openapi.js +2 -2
  58. package/package.json +3 -1
  59. package/src/app/main.css +2 -1
  60. package/src/lib/components/ThemeSwitch.tsx +28 -7
  61. package/src/lib/components/context/ZudokuContext.ts +13 -2
  62. package/src/lib/core/RouteGuard.tsx +6 -2
  63. package/src/lib/oas/parser/upgrade/index.ts +2 -1
  64. package/src/lib/util/useLatest.ts +18 -0
  65. package/lib/ZudokuContext-dUyBGMap.js +0 -1229
  66. package/lib/ZudokuContext-dUyBGMap.js.map +0 -1
  67. package/lib/hook-NIpDSpau.js.map +0 -1
  68. package/lib/state-bfQxaDxU.js +0 -211
  69. package/lib/state-bfQxaDxU.js.map +0 -1
@@ -1,25 +1,24 @@
1
1
  import { j as e } from "./jsx-runtime-Bdg6XQ1m.js";
2
2
  import { s as j } from "./index-CjJS0l4l.js";
3
- import { u as b } from "./ZudokuContext-dUyBGMap.js";
4
- import { b as y } from "./chunk-SYFQ2XB5-QijJrSf0.js";
5
- import { Head as v, Link as N } from "./zudoku.components.js";
6
- import { u as w } from "./state-bfQxaDxU.js";
3
+ import { u as b, a as y } from "./hook-Dnj3SwPC.js";
4
+ import { b as v } from "./chunk-SYFQ2XB5-QijJrSf0.js";
5
+ import { Head as N, Link as w } from "./zudoku.components.js";
7
6
  import { M as C } from "./Markdown-8mv9nhGd.js";
8
7
  import { c as h } from "./cn-qaFjX9_3.js";
9
8
  const f = (r, n) => j(`${r}-${n}`), k = ({
10
9
  items: r,
11
- filterCatalogItems: n = (l) => l,
12
- categories: o,
10
+ filterCatalogItems: n = (o) => o,
11
+ categories: l,
13
12
  label: c = "API Library"
14
13
  }) => {
15
- const [l, p] = y(), a = l.get("category"), x = w(), g = b({
14
+ const [o, p] = v(), a = o.get("category"), x = b(), g = y({
16
15
  queryFn: () => n(r, { auth: x }),
17
16
  queryKey: ["catalogItems", x]
18
17
  });
19
18
  return /* @__PURE__ */ e.jsxs("section", { className: "pt-[--padding-content-top] pb-[--padding-content-bottom]", children: [
20
- /* @__PURE__ */ e.jsx(v, { children: /* @__PURE__ */ e.jsx("title", { children: c }) }),
19
+ /* @__PURE__ */ e.jsx(N, { children: /* @__PURE__ */ e.jsx("title", { children: c }) }),
21
20
  /* @__PURE__ */ e.jsxs("div", { className: "grid grid-cols-12 gap-12", children: [
22
- /* @__PURE__ */ e.jsx("div", { className: "flex flex-col gap-4 col-span-3 not-prose sticky top-48", children: /* @__PURE__ */ e.jsx("div", { className: "max-w-[--side-nav-width] flex flex-col gap-4 justify-between", children: o == null ? void 0 : o.map((s, d) => /* @__PURE__ */ e.jsxs("div", { children: [
21
+ /* @__PURE__ */ e.jsx("div", { className: "flex flex-col gap-4 col-span-3 not-prose sticky top-48", children: /* @__PURE__ */ e.jsx("div", { className: "max-w-[--side-nav-width] flex flex-col gap-4 justify-between", children: l == null ? void 0 : l.map((s, d) => /* @__PURE__ */ e.jsxs("div", { children: [
23
22
  /* @__PURE__ */ e.jsxs("div", { className: "flex justify-between mb-2.5", children: [
24
23
  /* @__PURE__ */ e.jsx("span", { className: "font-medium text-sm", children: s.label }),
25
24
  d === 0 && a && /* @__PURE__ */ e.jsx(
@@ -72,7 +71,7 @@ const f = (r, n) => j(`${r}-${n}`), k = ({
72
71
  (d) => d.tags.find((t) => f(d.label, t) === a)
73
72
  )
74
73
  ).map((s) => /* @__PURE__ */ e.jsx(
75
- N,
74
+ w,
76
75
  {
77
76
  to: {
78
77
  pathname: `/${s.path}`,
@@ -95,12 +94,12 @@ const f = (r, n) => j(`${r}-${n}`), k = ({
95
94
  ] })
96
95
  ] })
97
96
  ] });
98
- }, M = ({
97
+ }, L = ({
99
98
  navigationId: r,
100
99
  items: n,
101
- label: o,
100
+ label: l,
102
101
  categories: c,
103
- filterCatalogItems: l
102
+ filterCatalogItems: o
104
103
  }) => ({
105
104
  getRoutes: () => [
106
105
  {
@@ -108,9 +107,9 @@ const f = (r, n) => j(`${r}-${n}`), k = ({
108
107
  element: /* @__PURE__ */ e.jsx(
109
108
  k,
110
109
  {
111
- label: o,
110
+ label: l,
112
111
  items: n,
113
- filterCatalogItems: l,
112
+ filterCatalogItems: o,
114
113
  categories: c ?? []
115
114
  }
116
115
  )
@@ -118,6 +117,6 @@ const f = (r, n) => j(`${r}-${n}`), k = ({
118
117
  ]
119
118
  });
120
119
  export {
121
- M as apiCatalogPlugin
120
+ L as apiCatalogPlugin
122
121
  };
123
122
  //# sourceMappingURL=zudoku.plugin-api-catalog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugin-api-catalog.js","sources":["../src/lib/plugins/api-catalog/Catalog.tsx","../src/lib/plugins/api-catalog/index.tsx"],"sourcesContent":["import slugify from \"@sindresorhus/slugify\";\nimport { useSuspenseQuery } from \"@tanstack/react-query\";\nimport { useSearchParams } from \"react-router\";\nimport { Head, Link } from \"zudoku/components\";\nimport { useAuthState } from \"../../authentication/state.js\";\nimport { Markdown } from \"../../components/Markdown.js\";\nimport { cn } from \"../../util/cn.js\";\nimport type { ApiCatalogPluginOptions } from \"./index.js\";\n\nconst getKey = (category: string, tag: string) => slugify(`${category}-${tag}`);\n\nexport const Catalog = ({\n items,\n filterCatalogItems = (items) => items,\n categories,\n label = \"API Library\",\n}: Omit<ApiCatalogPluginOptions, \"navigationId\">) => {\n const [searchParams, setSearchParams] = useSearchParams();\n const activeCategory = searchParams.get(\"category\");\n const auth = useAuthState();\n\n const catalogItems = useSuspenseQuery({\n queryFn: () => filterCatalogItems(items, { auth }),\n queryKey: [\"catalogItems\", auth],\n });\n\n return (\n <section className=\"pt-[--padding-content-top] pb-[--padding-content-bottom]\">\n <Head>\n <title>{label}</title>\n </Head>\n <div className=\"grid grid-cols-12 gap-12\">\n <div className=\"flex flex-col gap-4 col-span-3 not-prose sticky top-48\">\n <div className=\"max-w-[--side-nav-width] flex flex-col gap-4 justify-between\">\n {categories?.map((category, idx) => (\n <div key={category.label}>\n <div className=\"flex justify-between mb-2.5\">\n <span className=\"font-medium text-sm\">{category.label}</span>\n {idx === 0 && activeCategory && (\n <button\n type=\"button\"\n className=\"text-end text-sm text-foreground/60 hover:text-foreground\"\n onClick={() => setSearchParams({})}\n >\n Clear\n </button>\n )}\n </div>\n <ul className=\"space-y-1 [&>li]:py-2\">\n {category.tags\n .map((tag) => ({\n tag,\n count: items.filter((api) =>\n api.categories.find((c) => c.tags.includes(tag)),\n ).length,\n }))\n .map(({ tag, count }) => {\n const slug = getKey(category.label, tag);\n const isActive = slug === activeCategory;\n\n return (\n <li\n key={slug}\n className={cn(\n \"flex rounded-lg justify-between text-sm cursor-pointer hover:text-primary transition px-[--padding-nav-item] -mx-[--padding-nav-item]\",\n isActive && \"bg-border/30 rounded\",\n )}\n onClick={() => setSearchParams({ category: slug })}\n >\n <span>{tag}</span>\n <span\n className={cn(\n \"flex items-center justify-center border rounded-md w-8 text-xs font-semibold\",\n isActive &&\n \"bg-primary border-primary text-primary-foreground\",\n )}\n >\n {count}\n </span>\n </li>\n );\n })}\n </ul>\n </div>\n ))}\n </div>\n </div>\n <div className=\"col-span-9\">\n <h3 className=\"mt-0 text-2xl font-bold mb-4\">{label}</h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n {catalogItems.data\n .filter(\n (api) =>\n !activeCategory ||\n api.categories.find((c) =>\n c.tags.find((t) => getKey(c.label, t) === activeCategory),\n ),\n )\n .map((api) => (\n <Link\n to={{\n pathname: `/${api.path}`,\n search: activeCategory ? `category=${activeCategory}` : \"\",\n }}\n className=\"no-underline hover:!text-foreground\"\n key={api.path}\n >\n <div className=\"border h-full rounded p-4 flex flex-col gap-2 cursor-pointer hover:bg-border/20 font-normal\">\n <span className=\"font-semibold\">{api.label}</span>\n <Markdown\n className=\"text-sm whitespace-pre-wrap mb-6 line-clamp-2\"\n content={api.description}\n />\n </div>\n </Link>\n ))}\n </div>\n </div>\n </div>\n </section>\n );\n};\n","import type { AuthState } from \"../../authentication/state.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { Catalog } from \"./Catalog.js\";\n\nexport type ApiCatalogItem = {\n path: string;\n label: string;\n description: string;\n categories: CatalogCategory[];\n};\n\nexport type CatalogCategory = {\n label: string;\n tags: string[];\n};\n\nexport type ApiCatalogPluginOptions = {\n navigationId: string;\n label: string;\n categories?: CatalogCategory[];\n items: ApiCatalogItem[];\n filterCatalogItems?: filterCatalogItems;\n};\n\nexport type CatalogContext<ProviderData = unknown> = {\n auth: AuthState<ProviderData>;\n};\n\nexport type filterCatalogItems<ProviderData = unknown> = (\n items: ApiCatalogItem[],\n { auth }: CatalogContext<ProviderData>,\n) => ApiCatalogItem[];\n\nexport const apiCatalogPlugin = ({\n navigationId,\n items,\n label,\n categories,\n filterCatalogItems,\n}: {\n navigationId: string;\n label: string;\n categories?: CatalogCategory[];\n items: ApiCatalogItem[];\n filterCatalogItems?: filterCatalogItems;\n}): ZudokuPlugin => {\n return {\n getRoutes: () => {\n return [\n {\n path: navigationId,\n element: (\n <Catalog\n label={label}\n items={items}\n filterCatalogItems={filterCatalogItems}\n categories={categories ?? []}\n />\n ),\n },\n ];\n },\n };\n};\n"],"names":["getKey","category","tag","slugify","Catalog","items","filterCatalogItems","categories","label","searchParams","setSearchParams","useSearchParams","activeCategory","auth","useAuthState","catalogItems","useSuspenseQuery","jsxs","jsx","Head","idx","api","c","count","slug","isActive","cn","Link","Markdown","apiCatalogPlugin","navigationId"],"mappings":"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"}
1
+ {"version":3,"file":"zudoku.plugin-api-catalog.js","sources":["../src/lib/plugins/api-catalog/Catalog.tsx","../src/lib/plugins/api-catalog/index.tsx"],"sourcesContent":["import slugify from \"@sindresorhus/slugify\";\nimport { useSuspenseQuery } from \"@tanstack/react-query\";\nimport { useSearchParams } from \"react-router\";\nimport { Head, Link } from \"zudoku/components\";\nimport { useAuthState } from \"../../authentication/state.js\";\nimport { Markdown } from \"../../components/Markdown.js\";\nimport { cn } from \"../../util/cn.js\";\nimport type { ApiCatalogPluginOptions } from \"./index.js\";\n\nconst getKey = (category: string, tag: string) => slugify(`${category}-${tag}`);\n\nexport const Catalog = ({\n items,\n filterCatalogItems = (items) => items,\n categories,\n label = \"API Library\",\n}: Omit<ApiCatalogPluginOptions, \"navigationId\">) => {\n const [searchParams, setSearchParams] = useSearchParams();\n const activeCategory = searchParams.get(\"category\");\n const auth = useAuthState();\n\n const catalogItems = useSuspenseQuery({\n queryFn: () => filterCatalogItems(items, { auth }),\n queryKey: [\"catalogItems\", auth],\n });\n\n return (\n <section className=\"pt-[--padding-content-top] pb-[--padding-content-bottom]\">\n <Head>\n <title>{label}</title>\n </Head>\n <div className=\"grid grid-cols-12 gap-12\">\n <div className=\"flex flex-col gap-4 col-span-3 not-prose sticky top-48\">\n <div className=\"max-w-[--side-nav-width] flex flex-col gap-4 justify-between\">\n {categories?.map((category, idx) => (\n <div key={category.label}>\n <div className=\"flex justify-between mb-2.5\">\n <span className=\"font-medium text-sm\">{category.label}</span>\n {idx === 0 && activeCategory && (\n <button\n type=\"button\"\n className=\"text-end text-sm text-foreground/60 hover:text-foreground\"\n onClick={() => setSearchParams({})}\n >\n Clear\n </button>\n )}\n </div>\n <ul className=\"space-y-1 [&>li]:py-2\">\n {category.tags\n .map((tag) => ({\n tag,\n count: items.filter((api) =>\n api.categories.find((c) => c.tags.includes(tag)),\n ).length,\n }))\n .map(({ tag, count }) => {\n const slug = getKey(category.label, tag);\n const isActive = slug === activeCategory;\n\n return (\n <li\n key={slug}\n className={cn(\n \"flex rounded-lg justify-between text-sm cursor-pointer hover:text-primary transition px-[--padding-nav-item] -mx-[--padding-nav-item]\",\n isActive && \"bg-border/30 rounded\",\n )}\n onClick={() => setSearchParams({ category: slug })}\n >\n <span>{tag}</span>\n <span\n className={cn(\n \"flex items-center justify-center border rounded-md w-8 text-xs font-semibold\",\n isActive &&\n \"bg-primary border-primary text-primary-foreground\",\n )}\n >\n {count}\n </span>\n </li>\n );\n })}\n </ul>\n </div>\n ))}\n </div>\n </div>\n <div className=\"col-span-9\">\n <h3 className=\"mt-0 text-2xl font-bold mb-4\">{label}</h3>\n\n <div className=\"grid grid-cols-2 gap-4\">\n {catalogItems.data\n .filter(\n (api) =>\n !activeCategory ||\n api.categories.find((c) =>\n c.tags.find((t) => getKey(c.label, t) === activeCategory),\n ),\n )\n .map((api) => (\n <Link\n to={{\n pathname: `/${api.path}`,\n search: activeCategory ? `category=${activeCategory}` : \"\",\n }}\n className=\"no-underline hover:!text-foreground\"\n key={api.path}\n >\n <div className=\"border h-full rounded p-4 flex flex-col gap-2 cursor-pointer hover:bg-border/20 font-normal\">\n <span className=\"font-semibold\">{api.label}</span>\n <Markdown\n className=\"text-sm whitespace-pre-wrap mb-6 line-clamp-2\"\n content={api.description}\n />\n </div>\n </Link>\n ))}\n </div>\n </div>\n </div>\n </section>\n );\n};\n","import type { AuthState } from \"../../authentication/state.js\";\nimport type { ZudokuPlugin } from \"../../core/plugins.js\";\nimport { Catalog } from \"./Catalog.js\";\n\nexport type ApiCatalogItem = {\n path: string;\n label: string;\n description: string;\n categories: CatalogCategory[];\n};\n\nexport type CatalogCategory = {\n label: string;\n tags: string[];\n};\n\nexport type ApiCatalogPluginOptions = {\n navigationId: string;\n label: string;\n categories?: CatalogCategory[];\n items: ApiCatalogItem[];\n filterCatalogItems?: filterCatalogItems;\n};\n\nexport type CatalogContext<ProviderData = unknown> = {\n auth: AuthState<ProviderData>;\n};\n\nexport type filterCatalogItems<ProviderData = unknown> = (\n items: ApiCatalogItem[],\n { auth }: CatalogContext<ProviderData>,\n) => ApiCatalogItem[];\n\nexport const apiCatalogPlugin = ({\n navigationId,\n items,\n label,\n categories,\n filterCatalogItems,\n}: {\n navigationId: string;\n label: string;\n categories?: CatalogCategory[];\n items: ApiCatalogItem[];\n filterCatalogItems?: filterCatalogItems;\n}): ZudokuPlugin => {\n return {\n getRoutes: () => {\n return [\n {\n path: navigationId,\n element: (\n <Catalog\n label={label}\n items={items}\n filterCatalogItems={filterCatalogItems}\n categories={categories ?? []}\n />\n ),\n },\n ];\n },\n };\n};\n"],"names":["getKey","category","tag","slugify","Catalog","items","filterCatalogItems","categories","label","searchParams","setSearchParams","useSearchParams","activeCategory","auth","useAuthState","catalogItems","useSuspenseQuery","jsxs","jsx","Head","idx","api","c","count","slug","isActive","cn","Link","Markdown","apiCatalogPlugin","navigationId"],"mappings":"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"}
@@ -1,14 +1,13 @@
1
1
  import { j as e } from "./jsx-runtime-Bdg6XQ1m.js";
2
- import { RotateCwIcon as g, TrashIcon as f, EyeOffIcon as j, EyeIcon as v, CheckIcon as w, CopyIcon as b, FileKey2Icon as K } from "lucide-react";
3
- import { D as k, S as m, R as N } from "./SlotletProvider-D0mFmGJu.js";
2
+ import { RotateCwIcon as g, TrashIcon as f, EyeOffIcon as j, EyeIcon as v, CheckIcon as w, CopyIcon as b, FileKey2Icon as k } from "lucide-react";
3
+ import { D as K, S as m, R as N } from "./SlotletProvider-D0mFmGJu.js";
4
4
  import { i as c } from "./invariant-Caa8-XvF.js";
5
- import { u as d, S as I, a as S, b as A, c as C, d as E, e as x } from "./Select-BcAbBUmk.js";
5
+ import { u as d, S as I, a as S, b as A, c as C, d as E, e as x } from "./Select-NSz0gku6.js";
6
6
  import { a as P } from "./index.esm-CrSoEshU.js";
7
7
  import { a as D, L as u, O as R } from "./chunk-SYFQ2XB5-QijJrSf0.js";
8
- import { a as y, e as q, u as O } from "./ZudokuContext-dUyBGMap.js";
8
+ import { i as y, k as q, e as O, a as z } from "./hook-Dnj3SwPC.js";
9
9
  import { Button as l } from "./ui/Button.js";
10
- import { Input as z } from "./ui/Input.js";
11
- import { u as F } from "./hook-NIpDSpau.js";
10
+ import { Input as F } from "./ui/Input.js";
12
11
  import { useState as p } from "react";
13
12
  import { c as T } from "./cn-qaFjX9_3.js";
14
13
  const L = ({ service: t }) => {
@@ -36,7 +35,7 @@ const L = ({ service: t }) => {
36
35
  onSubmit: n.handleSubmit((a) => o.mutate(a)),
37
36
  children: /* @__PURE__ */ e.jsxs("div", { className: "flex gap-2 flex-col", children: [
38
37
  "Note",
39
- /* @__PURE__ */ e.jsx(z, { ...n.register("description") }),
38
+ /* @__PURE__ */ e.jsx(F, { ...n.register("description") }),
40
39
  "Expiration",
41
40
  /* @__PURE__ */ e.jsxs(
42
41
  I,
@@ -67,17 +66,17 @@ const L = ({ service: t }) => {
67
66
  const s = /* @__PURE__ */ new Date();
68
67
  return s.setDate(s.getDate() + t), s.toISOString();
69
68
  }, M = () => {
70
- const t = F();
69
+ const t = q();
71
70
  return t.isAuthEnabled && t.isPending ? null : t.isAuthenticated ? /* @__PURE__ */ e.jsx(R, {}) : t.isAuthEnabled ? /* @__PURE__ */ e.jsxs("div", { className: "flex flex-col justify-center gap-2 items-center h-1/2", children: [
72
71
  "Please login first to view this page",
73
72
  /* @__PURE__ */ e.jsx(l, { onClick: () => t.login(), children: "Login" })
74
- ] }) : /* @__PURE__ */ e.jsx("div", { className: "flex flex-col justify-center gap-2 items-center h-1/2", children: /* @__PURE__ */ e.jsxs(k, { className: "max-w-[600px]", children: [
73
+ ] }) : /* @__PURE__ */ e.jsx("div", { className: "flex flex-col justify-center gap-2 items-center h-1/2", children: /* @__PURE__ */ e.jsxs(K, { className: "max-w-[600px]", children: [
75
74
  "Authentication needs to be enabled for API keys to work. Enable it in your Zudoku configuration under ",
76
75
  /* @__PURE__ */ e.jsx("code", { children: "authentication" }),
77
76
  "."
78
77
  ] }) });
79
78
  }, _ = ({ service: t }) => {
80
- const s = y(), r = q(), { data: n } = O({
79
+ const s = y(), r = O(), { data: n } = z({
81
80
  queryFn: () => t.getKeys(s),
82
81
  queryKey: ["api-keys"],
83
82
  retry: !1
@@ -235,7 +234,7 @@ const L = ({ service: t }) => {
235
234
  const n = await fetch(r);
236
235
  return c(n.ok, "Failed to fetch API keys"), await n.json();
237
236
  }
238
- }), re = (t) => {
237
+ }), ie = (t) => {
239
238
  const s = "endpoint" in t ? t.endpoint : G, r = "getKeys" in t ? t : $(s);
240
239
  return {
241
240
  getProfileMenuItems: () => [
@@ -243,7 +242,7 @@ const L = ({ service: t }) => {
243
242
  label: "API Keys",
244
243
  path: "/settings/api-keys",
245
244
  category: "middle",
246
- icon: K
245
+ icon: k
247
246
  }
248
247
  ],
249
248
  getIdentities: async (n) => {
@@ -276,6 +275,6 @@ const L = ({ service: t }) => {
276
275
  };
277
276
  };
278
277
  export {
279
- re as apiKeyPlugin
278
+ ie as apiKeyPlugin
280
279
  };
281
280
  //# sourceMappingURL=zudoku.plugin-api-keys.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"zudoku.plugin-api-keys.js","sources":["../src/lib/plugins/api-keys/CreateApiKey.tsx","../src/lib/plugins/api-keys/ProtectedRoute.tsx","../src/lib/plugins/api-keys/SettingsApiKeys.tsx","../src/lib/plugins/api-keys/index.tsx"],"sourcesContent":["import { useMutation } from \"@tanstack/react-query\";\nimport { useForm } from \"react-hook-form\";\nimport { Link, useNavigate } from \"react-router\";\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"zudoku/ui/Select.js\";\nimport { useZudoku } from \"../../components/context/ZudokuContext.js\";\nimport { Button } from \"../../ui/Button.js\";\nimport { Input } from \"../../ui/Input.js\";\nimport { ApiKeyService } from \"./index.js\";\n\ntype CreateApiKey = { description: string; expiresOn?: string };\n\nexport const CreateApiKey = ({ service }: { service: ApiKeyService }) => {\n const context = useZudoku();\n const navigate = useNavigate();\n const form = useForm<CreateApiKey>({\n defaultValues: {\n expiresOn: \"30\",\n },\n });\n const createKeyMutation = useMutation({\n mutationFn: ({ description, expiresOn }: CreateApiKey) => {\n if (!service.createKey) {\n throw new Error(\"deleteKey not implemented\");\n }\n\n const expiresOnDate =\n expiresOn !== \"never\" ? addDaysToDate(Number(expiresOn)) : undefined;\n\n return service.createKey(\n { description: description, expiresOn: expiresOnDate },\n context,\n );\n },\n onSuccess: () => navigate(\"/settings/api-keys/\"),\n });\n\n if (!service.createKey) {\n return null;\n }\n\n return (\n <div className=\"max-w-screen-lg pt-[--padding-content-top] pb-[--padding-content-bottom]\">\n <div className=\"flex justify-between mb-4 border-b pb-1\">\n <h1 className=\"font-medium text-2xl\">New API Key</h1>\n </div>\n <form\n onSubmit={form.handleSubmit((data) => createKeyMutation.mutate(data))}\n >\n <div className=\"flex gap-2 flex-col\">\n Note\n <Input {...form.register(\"description\")} />\n Expiration\n <Select\n onValueChange={(value) => form.setValue(\"expiresOn\", value)}\n defaultValue={form.getValues(\"expiresOn\")}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n {[7, 30, 60, 90].map((option) => (\n <SelectItem value={String(option)} key={option}>\n {option} days\n </SelectItem>\n ))}\n <SelectItem value=\"never\">Never</SelectItem>\n </SelectGroup>\n </SelectContent>\n </Select>\n <div className=\"flex gap-2\">\n <Button>Generate Key</Button>\n <Button variant=\"outline\" asChild>\n <Link to=\"/settings/api-keys/\">Cancel</Link>\n </Button>\n </div>\n </div>\n </form>\n </div>\n );\n};\n\nconst addDaysToDate = (days: number): string => {\n const date = new Date();\n date.setDate(date.getDate() + days);\n return date.toISOString();\n};\n","import { Outlet } from \"react-router\";\nimport { useAuth } from \"../../authentication/hook.js\";\nimport { DeveloperHint } from \"../../components/DeveloperHint.js\";\nimport { Button } from \"../../ui/Button.js\";\n\nexport const ProtectedRoute = () => {\n const auth = useAuth();\n\n // TODO: should we suspend here somehow?\n if (auth.isAuthEnabled && auth.isPending) {\n return null;\n }\n\n return auth.isAuthenticated ? (\n <Outlet />\n ) : !auth.isAuthEnabled ? (\n <div className=\"flex flex-col justify-center gap-2 items-center h-1/2\">\n <DeveloperHint className=\"max-w-[600px]\">\n Authentication needs to be enabled for API keys to work. Enable it in\n your Zudoku configuration under <code>authentication</code>.\n </DeveloperHint>\n </div>\n ) : (\n <div className=\"flex flex-col justify-center gap-2 items-center h-1/2\">\n Please login first to view this page\n <Button onClick={() => auth.login()}>Login</Button>\n </div>\n );\n};\n","import {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from \"@tanstack/react-query\";\nimport {\n CheckIcon,\n CopyIcon,\n EyeIcon,\n EyeOffIcon,\n RotateCwIcon,\n TrashIcon,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { useZudoku } from \"../../components/context/ZudokuContext.js\";\nimport { Slotlet } from \"../../components/SlotletProvider.js\";\nimport { Button } from \"../../ui/Button.js\";\nimport { cn } from \"../../util/cn.js\";\nimport { ApiKeyService } from \"./index.js\";\n\nexport const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {\n const context = useZudoku();\n const queryClient = useQueryClient();\n const { data } = useSuspenseQuery({\n queryFn: () => service.getKeys(context),\n queryKey: [\"api-keys\"],\n retry: false,\n });\n\n const deleteKeyMutation = useMutation({\n mutationFn: (id: string) => {\n if (!service.deleteKey) {\n throw new Error(\"deleteKey not implemented\");\n }\n\n return service.deleteKey(id, context);\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: [\"api-keys\"] });\n },\n });\n\n const rollKeyMutation = useMutation({\n mutationFn: (id: string) => {\n if (!service.rollKey) {\n throw new Error(\"rollKey not implemented\");\n }\n\n return service.rollKey(id, context);\n },\n onSuccess: () => queryClient.invalidateQueries({ queryKey: [\"api-keys\"] }),\n });\n\n return (\n <div className=\"max-w-screen-lg h-full pt-[--padding-content-top] pb-[--padding-content-bottom]\">\n <Slotlet name=\"api-keys-list-page\" />\n\n <div className=\"flex justify-between mb-4 border-b pb-3\">\n <h1 className=\"font-medium text-2xl\">API Keys</h1>\n {service.createKey && (\n <Button asChild>\n <Link to=\"/settings/api-keys/new\">Create API Key</Link>\n </Button>\n )}\n </div>\n\n <Slotlet name=\"api-keys-list-page-before-keys\" />\n\n {data.length === 0 ? (\n <div className=\"flex flex-col justify-center gap-4 items-center p-8 border rounded bg-muted/30 text-muted-foreground\">\n <p className=\"text-center\">\n No API keys created yet.\n <br />\n Get started and create your first key.\n </p>\n {service.createKey && (\n <Button asChild variant=\"outline\">\n <Link to=\"/settings/api-keys/new\">Create API Key</Link>\n </Button>\n )}\n </div>\n ) : (\n <ul\n className={cn(\n \"grid grid-cols-1 rounded border divide-y divide-border\",\n \"lg:grid-cols-[minmax(250px,min-content)_1fr_min-content]\",\n )}\n >\n {data.map((key) => (\n <li\n className=\"p-5 grid grid-cols-subgrid col-span-full gap-2 items-center\"\n key={key.id}\n >\n <div className=\"flex flex-col gap-1 text-sm\">\n {key.description ?? key.id}\n <div className=\"text-muted-foreground text-xs\">\n {key.createdOn && (\n <div>\n Created on {new Date(key.createdOn).toLocaleDateString()}\n </div>\n )}\n {key.expiresOn && (\n <div>\n Expires on {new Date(key.expiresOn).toLocaleDateString()}\n </div>\n )}\n </div>\n </div>\n <div className=\"items-center flex lg:justify-center\">\n <RevealApiKey apiKey={key.key} />\n </div>\n <div className=\"flex gap-2\">\n {service.rollKey && (\n <Button\n size=\"icon\"\n title=\"Roll this key\"\n variant=\"ghost\"\n onClick={() => {\n if (!confirm(\"Do you want to roll this key?\")) {\n return;\n }\n\n rollKeyMutation.mutate(key.id);\n }}\n >\n <RotateCwIcon size={16} />\n </Button>\n )}\n {service.deleteKey && (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => {\n if (!confirm(\"Do you want to delete this key?\")) {\n return;\n }\n\n deleteKeyMutation.mutate(key.id);\n }}\n disabled={deleteKeyMutation.isPending}\n >\n <TrashIcon size={16} />\n </Button>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n};\n\nconst RevealApiKey = ({ apiKey }: { apiKey: string }) => {\n const [revealed, setRevealed] = useState(false);\n const [copied, setCopied] = useState(false);\n\n return (\n <div className=\"flex gap-2 items-center text-sm\">\n <div className=\"border rounded bg-gray-100 dark:bg-gray-950 p-1 font-mono truncate h-9 items-center flex px-2\">\n {revealed ? apiKey : \"•\".repeat(apiKey.length)}\n </div>\n <Button\n variant=\"outline\"\n onClick={() => setRevealed((prev) => !prev)}\n size=\"icon\"\n >\n {revealed ? <EyeOffIcon size={16} /> : <EyeIcon size={16} />}\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => {\n void navigator.clipboard.writeText(apiKey).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n });\n }}\n size=\"icon\"\n >\n {copied ? <CheckIcon size={16} /> : <CopyIcon size={16} />}\n </Button>\n </div>\n );\n};\n","import { FileKey2Icon } from \"lucide-react\";\nimport { type RouteObject } from \"react-router\";\nimport { ZudokuContext } from \"../../core/ZudokuContext.js\";\nimport {\n type ApiIdentityPlugin,\n type ZudokuPlugin,\n ProfileMenuPlugin,\n} from \"../../core/plugins.js\";\nimport { RouterError } from \"../../errors/RouterError.js\";\nimport invariant from \"../../util/invariant.js\";\nimport { CreateApiKey } from \"./CreateApiKey.js\";\nimport { ProtectedRoute } from \"./ProtectedRoute.js\";\nimport { SettingsApiKeys } from \"./SettingsApiKeys.js\";\n\nconst DEFAULT_API_KEY_ENDPOINT =\n \"https://zudoku-rewiringamerica-main-ef9c9c0.d2.zuplo.dev\";\n\nexport type ApiKeyService = {\n getKeys: (context: ZudokuContext) => Promise<ApiKey[]>;\n rollKey?: (id: string, context: ZudokuContext) => Promise<void>;\n deleteKey?: (id: string, context: ZudokuContext) => Promise<void>;\n updateKeyDescription?: (\n apiKey: { id: string; description: string },\n context: ZudokuContext,\n ) => Promise<void>;\n getUsage?: (apiKeys: string[], context: ZudokuContext) => Promise<void>;\n createKey?: (\n apiKey: { description: string; expiresOn?: string },\n context: ZudokuContext,\n ) => Promise<void>;\n};\n\nexport type GetApiKeysOptions = ApiKeyService | { endpoint: string } | object;\n\nexport type ApiKeyPluginOptions = object & GetApiKeysOptions;\n\nexport interface ApiKey {\n id: string;\n description?: string;\n createdOn?: string;\n updatedOn?: string;\n expiresOn?: string;\n key: string;\n}\n\nconst createDefaultHandler = (endpoint: string): ApiKeyService => {\n return {\n deleteKey: async (id, context) => {\n const request = new Request(endpoint + `/v1/developer/api-keys/${id}`, {\n method: \"DELETE\",\n });\n\n await context.signRequest(request);\n\n const response = await fetch(request);\n invariant(response.ok, \"Failed to delete API key\");\n },\n rollKey: async (id, context) => {\n const response = await fetch(\n await context.signRequest(\n new Request(endpoint + `/v1/developer/api-keys/${id}/key`, {\n method: \"DELETE\",\n }),\n ),\n );\n invariant(response.ok, \"Failed to delete API key\");\n },\n createKey: async (apiKey, context) => {\n const request = new Request(endpoint + `/v1/developer/api-keys`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(apiKey),\n });\n\n await context.signRequest(request);\n\n const response = await fetch(request);\n invariant(response.ok, \"Failed to create API key\");\n },\n getKeys: async (context) => {\n const request = new Request(endpoint + `/v1/developer/api-keys`);\n\n await context.signRequest(request);\n\n const keys = await fetch(request);\n invariant(keys.ok, \"Failed to fetch API keys\");\n\n return await keys.json();\n },\n };\n};\n\nexport const apiKeyPlugin = (\n options: ApiKeyPluginOptions,\n): ZudokuPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {\n const endpoint =\n \"endpoint\" in options ? options.endpoint : DEFAULT_API_KEY_ENDPOINT;\n\n const service =\n \"getKeys\" in options ? options : createDefaultHandler(endpoint);\n\n return {\n getProfileMenuItems: () => [\n {\n label: \"API Keys\",\n path: \"/settings/api-keys\",\n category: \"middle\",\n icon: FileKey2Icon,\n },\n ],\n getIdentities: async (context) => {\n try {\n const keys = await service.getKeys(context);\n\n return keys.map((key) => ({\n authorizeRequest: (request) => {\n request.headers.set(\"Authorization\", `Bearer ${key.key}`);\n return request;\n },\n id: key.id,\n label: key.description ?? key.id,\n }));\n } catch {\n return [];\n }\n },\n getRoutes: (): RouteObject[] => {\n // TODO: Make lazy\n return [\n {\n element: <ProtectedRoute />,\n errorElement: <RouterError />,\n children: [\n {\n path: \"/settings/api-keys\",\n element: <SettingsApiKeys service={service} />,\n },\n {\n path: \"/settings/api-keys/new\",\n element: <CreateApiKey service={service} />,\n },\n ],\n },\n ];\n },\n };\n};\n"],"names":["CreateApiKey","service","context","useZudoku","navigate","useNavigate","form","useForm","createKeyMutation","useMutation","description","expiresOn","expiresOnDate","addDaysToDate","jsxs","jsx","data","Input","Select","value","SelectTrigger","SelectValue","SelectContent","SelectGroup","option","SelectItem","Button","Link","days","date","ProtectedRoute","auth","useAuth","Outlet","DeveloperHint","SettingsApiKeys","queryClient","useQueryClient","useSuspenseQuery","deleteKeyMutation","id","rollKeyMutation","Slotlet","cn","key","RevealApiKey","RotateCwIcon","TrashIcon","apiKey","revealed","setRevealed","useState","copied","setCopied","prev","EyeOffIcon","EyeIcon","CheckIcon","CopyIcon","DEFAULT_API_KEY_ENDPOINT","createDefaultHandler","endpoint","request","response","invariant","keys","apiKeyPlugin","options","FileKey2Icon","RouterError"],"mappings":"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"}
1
+ {"version":3,"file":"zudoku.plugin-api-keys.js","sources":["../src/lib/plugins/api-keys/CreateApiKey.tsx","../src/lib/plugins/api-keys/ProtectedRoute.tsx","../src/lib/plugins/api-keys/SettingsApiKeys.tsx","../src/lib/plugins/api-keys/index.tsx"],"sourcesContent":["import { useMutation } from \"@tanstack/react-query\";\nimport { useForm } from \"react-hook-form\";\nimport { Link, useNavigate } from \"react-router\";\nimport {\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"zudoku/ui/Select.js\";\nimport { useZudoku } from \"../../components/context/ZudokuContext.js\";\nimport { Button } from \"../../ui/Button.js\";\nimport { Input } from \"../../ui/Input.js\";\nimport { ApiKeyService } from \"./index.js\";\n\ntype CreateApiKey = { description: string; expiresOn?: string };\n\nexport const CreateApiKey = ({ service }: { service: ApiKeyService }) => {\n const context = useZudoku();\n const navigate = useNavigate();\n const form = useForm<CreateApiKey>({\n defaultValues: {\n expiresOn: \"30\",\n },\n });\n const createKeyMutation = useMutation({\n mutationFn: ({ description, expiresOn }: CreateApiKey) => {\n if (!service.createKey) {\n throw new Error(\"deleteKey not implemented\");\n }\n\n const expiresOnDate =\n expiresOn !== \"never\" ? addDaysToDate(Number(expiresOn)) : undefined;\n\n return service.createKey(\n { description: description, expiresOn: expiresOnDate },\n context,\n );\n },\n onSuccess: () => navigate(\"/settings/api-keys/\"),\n });\n\n if (!service.createKey) {\n return null;\n }\n\n return (\n <div className=\"max-w-screen-lg pt-[--padding-content-top] pb-[--padding-content-bottom]\">\n <div className=\"flex justify-between mb-4 border-b pb-1\">\n <h1 className=\"font-medium text-2xl\">New API Key</h1>\n </div>\n <form\n onSubmit={form.handleSubmit((data) => createKeyMutation.mutate(data))}\n >\n <div className=\"flex gap-2 flex-col\">\n Note\n <Input {...form.register(\"description\")} />\n Expiration\n <Select\n onValueChange={(value) => form.setValue(\"expiresOn\", value)}\n defaultValue={form.getValues(\"expiresOn\")}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectGroup>\n {[7, 30, 60, 90].map((option) => (\n <SelectItem value={String(option)} key={option}>\n {option} days\n </SelectItem>\n ))}\n <SelectItem value=\"never\">Never</SelectItem>\n </SelectGroup>\n </SelectContent>\n </Select>\n <div className=\"flex gap-2\">\n <Button>Generate Key</Button>\n <Button variant=\"outline\" asChild>\n <Link to=\"/settings/api-keys/\">Cancel</Link>\n </Button>\n </div>\n </div>\n </form>\n </div>\n );\n};\n\nconst addDaysToDate = (days: number): string => {\n const date = new Date();\n date.setDate(date.getDate() + days);\n return date.toISOString();\n};\n","import { Outlet } from \"react-router\";\nimport { useAuth } from \"../../authentication/hook.js\";\nimport { DeveloperHint } from \"../../components/DeveloperHint.js\";\nimport { Button } from \"../../ui/Button.js\";\n\nexport const ProtectedRoute = () => {\n const auth = useAuth();\n\n // TODO: should we suspend here somehow?\n if (auth.isAuthEnabled && auth.isPending) {\n return null;\n }\n\n return auth.isAuthenticated ? (\n <Outlet />\n ) : !auth.isAuthEnabled ? (\n <div className=\"flex flex-col justify-center gap-2 items-center h-1/2\">\n <DeveloperHint className=\"max-w-[600px]\">\n Authentication needs to be enabled for API keys to work. Enable it in\n your Zudoku configuration under <code>authentication</code>.\n </DeveloperHint>\n </div>\n ) : (\n <div className=\"flex flex-col justify-center gap-2 items-center h-1/2\">\n Please login first to view this page\n <Button onClick={() => auth.login()}>Login</Button>\n </div>\n );\n};\n","import {\n useMutation,\n useQueryClient,\n useSuspenseQuery,\n} from \"@tanstack/react-query\";\nimport {\n CheckIcon,\n CopyIcon,\n EyeIcon,\n EyeOffIcon,\n RotateCwIcon,\n TrashIcon,\n} from \"lucide-react\";\nimport { useState } from \"react\";\nimport { Link } from \"react-router\";\nimport { useZudoku } from \"../../components/context/ZudokuContext.js\";\nimport { Slotlet } from \"../../components/SlotletProvider.js\";\nimport { Button } from \"../../ui/Button.js\";\nimport { cn } from \"../../util/cn.js\";\nimport { ApiKeyService } from \"./index.js\";\n\nexport const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {\n const context = useZudoku();\n const queryClient = useQueryClient();\n const { data } = useSuspenseQuery({\n queryFn: () => service.getKeys(context),\n queryKey: [\"api-keys\"],\n retry: false,\n });\n\n const deleteKeyMutation = useMutation({\n mutationFn: (id: string) => {\n if (!service.deleteKey) {\n throw new Error(\"deleteKey not implemented\");\n }\n\n return service.deleteKey(id, context);\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: [\"api-keys\"] });\n },\n });\n\n const rollKeyMutation = useMutation({\n mutationFn: (id: string) => {\n if (!service.rollKey) {\n throw new Error(\"rollKey not implemented\");\n }\n\n return service.rollKey(id, context);\n },\n onSuccess: () => queryClient.invalidateQueries({ queryKey: [\"api-keys\"] }),\n });\n\n return (\n <div className=\"max-w-screen-lg h-full pt-[--padding-content-top] pb-[--padding-content-bottom]\">\n <Slotlet name=\"api-keys-list-page\" />\n\n <div className=\"flex justify-between mb-4 border-b pb-3\">\n <h1 className=\"font-medium text-2xl\">API Keys</h1>\n {service.createKey && (\n <Button asChild>\n <Link to=\"/settings/api-keys/new\">Create API Key</Link>\n </Button>\n )}\n </div>\n\n <Slotlet name=\"api-keys-list-page-before-keys\" />\n\n {data.length === 0 ? (\n <div className=\"flex flex-col justify-center gap-4 items-center p-8 border rounded bg-muted/30 text-muted-foreground\">\n <p className=\"text-center\">\n No API keys created yet.\n <br />\n Get started and create your first key.\n </p>\n {service.createKey && (\n <Button asChild variant=\"outline\">\n <Link to=\"/settings/api-keys/new\">Create API Key</Link>\n </Button>\n )}\n </div>\n ) : (\n <ul\n className={cn(\n \"grid grid-cols-1 rounded border divide-y divide-border\",\n \"lg:grid-cols-[minmax(250px,min-content)_1fr_min-content]\",\n )}\n >\n {data.map((key) => (\n <li\n className=\"p-5 grid grid-cols-subgrid col-span-full gap-2 items-center\"\n key={key.id}\n >\n <div className=\"flex flex-col gap-1 text-sm\">\n {key.description ?? key.id}\n <div className=\"text-muted-foreground text-xs\">\n {key.createdOn && (\n <div>\n Created on {new Date(key.createdOn).toLocaleDateString()}\n </div>\n )}\n {key.expiresOn && (\n <div>\n Expires on {new Date(key.expiresOn).toLocaleDateString()}\n </div>\n )}\n </div>\n </div>\n <div className=\"items-center flex lg:justify-center\">\n <RevealApiKey apiKey={key.key} />\n </div>\n <div className=\"flex gap-2\">\n {service.rollKey && (\n <Button\n size=\"icon\"\n title=\"Roll this key\"\n variant=\"ghost\"\n onClick={() => {\n if (!confirm(\"Do you want to roll this key?\")) {\n return;\n }\n\n rollKeyMutation.mutate(key.id);\n }}\n >\n <RotateCwIcon size={16} />\n </Button>\n )}\n {service.deleteKey && (\n <Button\n variant=\"ghost\"\n size=\"icon\"\n onClick={() => {\n if (!confirm(\"Do you want to delete this key?\")) {\n return;\n }\n\n deleteKeyMutation.mutate(key.id);\n }}\n disabled={deleteKeyMutation.isPending}\n >\n <TrashIcon size={16} />\n </Button>\n )}\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n};\n\nconst RevealApiKey = ({ apiKey }: { apiKey: string }) => {\n const [revealed, setRevealed] = useState(false);\n const [copied, setCopied] = useState(false);\n\n return (\n <div className=\"flex gap-2 items-center text-sm\">\n <div className=\"border rounded bg-gray-100 dark:bg-gray-950 p-1 font-mono truncate h-9 items-center flex px-2\">\n {revealed ? apiKey : \"•\".repeat(apiKey.length)}\n </div>\n <Button\n variant=\"outline\"\n onClick={() => setRevealed((prev) => !prev)}\n size=\"icon\"\n >\n {revealed ? <EyeOffIcon size={16} /> : <EyeIcon size={16} />}\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => {\n void navigator.clipboard.writeText(apiKey).then(() => {\n setCopied(true);\n setTimeout(() => setCopied(false), 2000);\n });\n }}\n size=\"icon\"\n >\n {copied ? <CheckIcon size={16} /> : <CopyIcon size={16} />}\n </Button>\n </div>\n );\n};\n","import { FileKey2Icon } from \"lucide-react\";\nimport { type RouteObject } from \"react-router\";\nimport { ZudokuContext } from \"../../core/ZudokuContext.js\";\nimport {\n type ApiIdentityPlugin,\n type ZudokuPlugin,\n ProfileMenuPlugin,\n} from \"../../core/plugins.js\";\nimport { RouterError } from \"../../errors/RouterError.js\";\nimport invariant from \"../../util/invariant.js\";\nimport { CreateApiKey } from \"./CreateApiKey.js\";\nimport { ProtectedRoute } from \"./ProtectedRoute.js\";\nimport { SettingsApiKeys } from \"./SettingsApiKeys.js\";\n\nconst DEFAULT_API_KEY_ENDPOINT =\n \"https://zudoku-rewiringamerica-main-ef9c9c0.d2.zuplo.dev\";\n\nexport type ApiKeyService = {\n getKeys: (context: ZudokuContext) => Promise<ApiKey[]>;\n rollKey?: (id: string, context: ZudokuContext) => Promise<void>;\n deleteKey?: (id: string, context: ZudokuContext) => Promise<void>;\n updateKeyDescription?: (\n apiKey: { id: string; description: string },\n context: ZudokuContext,\n ) => Promise<void>;\n getUsage?: (apiKeys: string[], context: ZudokuContext) => Promise<void>;\n createKey?: (\n apiKey: { description: string; expiresOn?: string },\n context: ZudokuContext,\n ) => Promise<void>;\n};\n\nexport type GetApiKeysOptions = ApiKeyService | { endpoint: string } | object;\n\nexport type ApiKeyPluginOptions = object & GetApiKeysOptions;\n\nexport interface ApiKey {\n id: string;\n description?: string;\n createdOn?: string;\n updatedOn?: string;\n expiresOn?: string;\n key: string;\n}\n\nconst createDefaultHandler = (endpoint: string): ApiKeyService => {\n return {\n deleteKey: async (id, context) => {\n const request = new Request(endpoint + `/v1/developer/api-keys/${id}`, {\n method: \"DELETE\",\n });\n\n await context.signRequest(request);\n\n const response = await fetch(request);\n invariant(response.ok, \"Failed to delete API key\");\n },\n rollKey: async (id, context) => {\n const response = await fetch(\n await context.signRequest(\n new Request(endpoint + `/v1/developer/api-keys/${id}/key`, {\n method: \"DELETE\",\n }),\n ),\n );\n invariant(response.ok, \"Failed to delete API key\");\n },\n createKey: async (apiKey, context) => {\n const request = new Request(endpoint + `/v1/developer/api-keys`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(apiKey),\n });\n\n await context.signRequest(request);\n\n const response = await fetch(request);\n invariant(response.ok, \"Failed to create API key\");\n },\n getKeys: async (context) => {\n const request = new Request(endpoint + `/v1/developer/api-keys`);\n\n await context.signRequest(request);\n\n const keys = await fetch(request);\n invariant(keys.ok, \"Failed to fetch API keys\");\n\n return await keys.json();\n },\n };\n};\n\nexport const apiKeyPlugin = (\n options: ApiKeyPluginOptions,\n): ZudokuPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {\n const endpoint =\n \"endpoint\" in options ? options.endpoint : DEFAULT_API_KEY_ENDPOINT;\n\n const service =\n \"getKeys\" in options ? options : createDefaultHandler(endpoint);\n\n return {\n getProfileMenuItems: () => [\n {\n label: \"API Keys\",\n path: \"/settings/api-keys\",\n category: \"middle\",\n icon: FileKey2Icon,\n },\n ],\n getIdentities: async (context) => {\n try {\n const keys = await service.getKeys(context);\n\n return keys.map((key) => ({\n authorizeRequest: (request) => {\n request.headers.set(\"Authorization\", `Bearer ${key.key}`);\n return request;\n },\n id: key.id,\n label: key.description ?? key.id,\n }));\n } catch {\n return [];\n }\n },\n getRoutes: (): RouteObject[] => {\n // TODO: Make lazy\n return [\n {\n element: <ProtectedRoute />,\n errorElement: <RouterError />,\n children: [\n {\n path: \"/settings/api-keys\",\n element: <SettingsApiKeys service={service} />,\n },\n {\n path: \"/settings/api-keys/new\",\n element: <CreateApiKey service={service} />,\n },\n ],\n },\n ];\n },\n };\n};\n"],"names":["CreateApiKey","service","context","useZudoku","navigate","useNavigate","form","useForm","createKeyMutation","useMutation","description","expiresOn","expiresOnDate","addDaysToDate","jsxs","jsx","data","Input","Select","value","SelectTrigger","SelectValue","SelectContent","SelectGroup","option","SelectItem","Button","Link","days","date","ProtectedRoute","auth","useAuth","Outlet","DeveloperHint","SettingsApiKeys","queryClient","useQueryClient","useSuspenseQuery","deleteKeyMutation","id","rollKeyMutation","Slotlet","cn","key","RevealApiKey","RotateCwIcon","TrashIcon","apiKey","revealed","setRevealed","useState","copied","setCopied","prev","EyeOffIcon","EyeIcon","CheckIcon","CopyIcon","DEFAULT_API_KEY_ENDPOINT","createDefaultHandler","endpoint","request","response","invariant","keys","apiKeyPlugin","options","FileKey2Icon","RouterError"],"mappings":"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"}
@@ -53,7 +53,7 @@ const P = (e) => ({
53
53
  const u = {
54
54
  path: r,
55
55
  lazy: async () => {
56
- const { MdxPage: p } = await import("./MdxPage-BalfwlsD.js"), { default: f, ...l } = await i();
56
+ const { MdxPage: p } = await import("./MdxPage-ZX2oquWw.js"), { default: f, ...l } = await i();
57
57
  return {
58
58
  element: /* @__PURE__ */ d.jsx(
59
59
  p,
@@ -2,10 +2,10 @@ import "./jsx-runtime-Bdg6XQ1m.js";
2
2
  import "./index-CjJS0l4l.js";
3
3
  import "lucide-react";
4
4
  import "./chunk-SYFQ2XB5-QijJrSf0.js";
5
- import "./hook-NIpDSpau.js";
5
+ import "./hook-Dnj3SwPC.js";
6
6
  import "./ui/Button.js";
7
7
  import "./joinUrl-nLx9pD-Z.js";
8
- import { o as f } from "./index-CnKeVT2S.js";
8
+ import { o as f } from "./index-CZjcfK-H.js";
9
9
  export {
10
10
  f as openApiPlugin
11
11
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.30.1",
3
+ "version": "0.31.0",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -111,6 +111,7 @@
111
111
  }
112
112
  },
113
113
  "dependencies": {
114
+ "@apidevtools/json-schema-ref-parser": "11.9.0",
114
115
  "@envelop/core": "5.0.3",
115
116
  "@graphql-typed-document-node/core": "3.2.0",
116
117
  "@hookform/resolvers": "3.10.0",
@@ -140,6 +141,7 @@
140
141
  "@radix-ui/react-toggle-group": "1.1.1",
141
142
  "@radix-ui/react-tooltip": "1.1.6",
142
143
  "@radix-ui/react-visually-hidden": "1.1.1",
144
+ "@scalar/openapi-parser": "0.10.5",
143
145
  "@sentry/node": "8.50.0",
144
146
  "@sindresorhus/slugify": "2.2.1",
145
147
  "@stefanprobst/rehype-extract-toc": "2.2.1",
package/src/app/main.css CHANGED
@@ -221,7 +221,8 @@
221
221
 
222
222
  --R: calc(var(--s) * sqrt(var(--m) * var(--m) + 1) + var(--b) / 2);
223
223
  height: calc(2 * var(--R));
224
- --_g: #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
224
+ --_g:
225
+ #0000 calc(99% - var(--b)), #000 calc(101% - var(--b)) 99%, #0000 101%;
225
226
  mask:
226
227
  radial-gradient(
227
228
  var(--R) at left 50% bottom calc(-1 * var(--m) * var(--s)),
@@ -1,6 +1,7 @@
1
- import { MoonStarIcon, SunIcon } from "lucide-react";
1
+ import { MoonIcon, MoonStarIcon, SunIcon } from "lucide-react";
2
2
  import { useTheme } from "next-themes";
3
3
  import { Button } from "zudoku/ui/Button.js";
4
+ import { cn } from "../util/cn.js";
4
5
  import { ClientOnly } from "./ClientOnly.js";
5
6
 
6
7
  export const ThemeSwitch = () => {
@@ -9,18 +10,38 @@ export const ThemeSwitch = () => {
9
10
 
10
11
  return (
11
12
  <ClientOnly fallback={<Button variant="ghost" size="icon" />}>
12
- <Button
13
- variant="ghost"
14
- size="icon"
13
+ <button
14
+ type="button"
15
+ className="flex rounded-full border p-0.5 gap-0.5 group"
16
+ onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
15
17
  aria-label={
16
18
  resolvedTheme === "dark"
17
19
  ? "Switch to light mode"
18
20
  : "Switch to dark mode"
19
21
  }
20
- onClick={() => setTheme(resolvedTheme === "dark" ? "light" : "dark")}
21
22
  >
22
- <ThemeIcon size={18} />
23
- </Button>
23
+ <div
24
+ className={cn(
25
+ "rounded-full p-0.5 [&>svg>circle]:transition-colors [&>svg>path]:transition-transform transition-all [&>svg>path]:duration-200 [&>svg>circle]:duration-500 [&>svg>circle]:fill-transparent",
26
+ resolvedTheme === "light" && "border bg-muted/50",
27
+ resolvedTheme === "dark" &&
28
+ "group-hover:[&>svg>path]:scale-110 group-hover:[&>svg>path]:-translate-x-[1px] group-hover:[&>svg>path]:-translate-y-[1px] group-hover:rotate-[15deg] ",
29
+ )}
30
+ >
31
+ <SunIcon size={16} />
32
+ </div>
33
+ <div
34
+ className={cn(
35
+ "rounded-full p-0.5 transition-transform transform-gpu duration-500",
36
+ resolvedTheme === "dark" &&
37
+ "border bg-muted [&>svg>path]:fill-white [&>svg>path]:stroke-transparent",
38
+ resolvedTheme === "light" &&
39
+ "group-hover:rotate-[-10deg] [&>svg>path]:stroke-currentColor",
40
+ )}
41
+ >
42
+ <MoonIcon size={16} />
43
+ </div>
44
+ </button>
24
45
  </ClientOnly>
25
46
  );
26
47
  };
@@ -1,6 +1,7 @@
1
1
  import { useQuery, useSuspenseQuery } from "@tanstack/react-query";
2
2
  import { createContext, useContext } from "react";
3
3
  import { matchPath, useLocation } from "react-router";
4
+ import { useAuth } from "../../authentication/hook.js";
4
5
  import { ZudokuContext } from "../../core/ZudokuContext.js";
5
6
  import { joinPath } from "../../util/joinPath.js";
6
7
  import { CACHE_KEYS } from "../cache.js";
@@ -30,8 +31,13 @@ export const useApiIdentities = () => {
30
31
  };
31
32
 
32
33
  export const useCurrentNavigation = () => {
33
- const { getPluginSidebar, sidebars, topNavigation } = useZudoku();
34
+ const { getPluginSidebar, sidebars, topNavigation, options } = useZudoku();
34
35
  const location = useLocation();
36
+ const auth = useAuth();
37
+
38
+ const isProtectedRoute = options.protectedRoutes?.some((route) =>
39
+ matchPath(route, location.pathname),
40
+ );
35
41
 
36
42
  const currentSidebarItem = Object.entries(sidebars).find(([, sidebar]) => {
37
43
  return traverseSidebar(sidebar, (item) => {
@@ -56,8 +62,13 @@ export const useCurrentNavigation = () => {
56
62
  queryKey: ["plugin-sidebar", location.pathname],
57
63
  });
58
64
 
65
+ const hideSidebar =
66
+ auth.isAuthEnabled && !auth.isAuthenticated && isProtectedRoute;
67
+
59
68
  return {
60
- sidebar: [...(currentSidebarItem ? currentSidebarItem[1] : []), ...data],
69
+ sidebar: hideSidebar
70
+ ? []
71
+ : [...(currentSidebarItem ? currentSidebarItem[1] : []), ...data],
61
72
  topNavItem: currentTopNavItem,
62
73
  };
63
74
  };
@@ -3,11 +3,13 @@ import { matchPath, Outlet, useLocation } from "react-router";
3
3
  import { useAuth } from "../authentication/hook.js";
4
4
  import { useZudoku } from "../components/context/ZudokuContext.js";
5
5
  import { ZudokuError } from "../util/invariant.js";
6
+ import { useLatest } from "../util/useLatest.js";
6
7
 
7
8
  export const RouteGuard = () => {
8
9
  const auth = useAuth();
9
10
  const zudoku = useZudoku();
10
11
  const location = useLocation();
12
+ const latestPath = useLatest(location.pathname);
11
13
 
12
14
  const isProtected = zudoku.options.protectedRoutes?.some((path) =>
13
15
  matchPath({ path, end: true }, location.pathname),
@@ -15,9 +17,11 @@ export const RouteGuard = () => {
15
17
 
16
18
  useEffect(() => {
17
19
  if (isProtected && !auth.isAuthenticated) {
18
- void zudoku.authentication?.signIn();
20
+ void zudoku.authentication?.signIn({
21
+ redirectTo: latestPath.current,
22
+ });
19
23
  }
20
- }, [isProtected, auth.isAuthenticated, zudoku.authentication]);
24
+ }, [isProtected, auth.isAuthenticated, zudoku.authentication, latestPath]);
21
25
 
22
26
  if (isProtected && !auth.isAuthenticated) {
23
27
  return null;
@@ -41,7 +41,8 @@ export const upgradeSchema = (schema: RecordAny): OpenAPIDocument => {
41
41
  });
42
42
 
43
43
  schema = traverse(schema, (sub) => {
44
- if (sub.example !== undefined) {
44
+ // may be null or undefined
45
+ if (sub.example) {
45
46
  const isExampleObject =
46
47
  typeof sub.example === "object" &&
47
48
  (sub.example.summary !== undefined ||
@@ -0,0 +1,18 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ // This hook is useful when you need to store a reference to a variable that changes over time,
4
+ // but you don't want to re-run any callbacks that depend on that variable every time it changes.
5
+ // By using a reference to the latest value, you can ensure that any callbacks only run when necessary,
6
+ // without any unnecessary re-renders.
7
+
8
+ // Note: It's safe to put the return value of this hook in a dependency array, because it won't change!
9
+ export const useLatest = <T>(current: T) => {
10
+ const valueRef = useRef(current);
11
+ valueRef.current = current;
12
+
13
+ useEffect(() => {
14
+ valueRef.current = current;
15
+ }, [current]);
16
+
17
+ return valueRef;
18
+ };