zudoku 0.32.2 → 0.32.4

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.
@@ -1,55 +1,57 @@
1
1
  import { j as e } from "./jsx-runtime-CYK1ROHF.js";
2
- import { RotateCwIcon as g, TrashIcon as f, EyeOffIcon as j, EyeIcon as v, CheckIcon as w, CopyIcon as K, FileKey2Icon as b } from "lucide-react";
3
- import { D as k, S as m, R as N } from "./SlotletProvider-CEfNOA8i.js";
2
+ import { RotateCwIcon as j, TrashIcon as v, EyeOffIcon as w, EyeIcon as K, CheckIcon as k, CopyIcon as b, FileKey2Icon as N } from "lucide-react";
3
+ import { D as I, S as x, R as S } from "./SlotletProvider-CEfNOA8i.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-D9hI1G-y.js";
6
- import { a as P } from "./index.esm--gIChbWs.js";
7
- import { a as D, L as u, O as R } from "./chunk-IR6S3I6Y-D_3UmFIn.js";
8
- import { i as y, k as q, e as O, a as z } from "./hook-CWwSAAlH.js";
5
+ import { i as h, e as g, k as A, a as C } from "./hook-CWwSAAlH.js";
6
+ import { u as d, S as E, a as P, b as D, c as q, d as R, e as p } from "./Select-D9hI1G-y.js";
7
+ import { a as O } from "./index.esm--gIChbWs.js";
8
+ import { a as z, L as u, O as F } from "./chunk-IR6S3I6Y-D_3UmFIn.js";
9
9
  import { Button as l } from "./ui/Button.js";
10
- import { Input as F } from "./ui/Input.js";
11
- import { useState as p } from "react";
12
- import { c as T } from "./cn-qaFjX9_3.js";
13
- const L = ({ service: t }) => {
14
- const s = y(), r = D(), n = P({
10
+ import { Input as T } from "./ui/Input.js";
11
+ import { useState as y } from "react";
12
+ import { c as L } from "./cn-qaFjX9_3.js";
13
+ const V = ({ service: t }) => {
14
+ const n = h(), i = g(), r = z(), a = O({
15
15
  defaultValues: {
16
16
  expiresOn: "30"
17
17
  }
18
18
  }), o = d({
19
- mutationFn: ({ description: a, expiresOn: i }) => {
19
+ mutationFn: ({ description: s, expiresOn: m }) => {
20
20
  if (!t.createKey)
21
- throw new Error("deleteKey not implemented");
22
- const h = i !== "never" ? V(Number(i)) : void 0;
21
+ throw new Error("createKey not implemented");
22
+ const f = m !== "never" ? M(Number(m)) : void 0;
23
23
  return t.createKey(
24
- { description: a, expiresOn: h },
25
- s
24
+ { description: s, expiresOn: f },
25
+ n
26
26
  );
27
27
  },
28
- onSuccess: () => r("/settings/api-keys/")
28
+ onSuccess: async () => {
29
+ await i.invalidateQueries({ queryKey: ["api-keys"] }), await r("/settings/api-keys/");
30
+ }
29
31
  });
30
32
  return t.createKey ? /* @__PURE__ */ e.jsxs("div", { className: "max-w-screen-lg pt-[--padding-content-top] pb-[--padding-content-bottom]", children: [
31
33
  /* @__PURE__ */ e.jsx("div", { className: "flex justify-between mb-4 border-b pb-1", children: /* @__PURE__ */ e.jsx("h1", { className: "font-medium text-2xl", children: "New API Key" }) }),
32
34
  /* @__PURE__ */ e.jsx(
33
35
  "form",
34
36
  {
35
- onSubmit: n.handleSubmit((a) => o.mutate(a)),
37
+ onSubmit: a.handleSubmit((s) => o.mutate(s)),
36
38
  children: /* @__PURE__ */ e.jsxs("div", { className: "flex gap-2 flex-col", children: [
37
39
  "Note",
38
- /* @__PURE__ */ e.jsx(F, { ...n.register("description") }),
40
+ /* @__PURE__ */ e.jsx(T, { ...a.register("description") }),
39
41
  "Expiration",
40
42
  /* @__PURE__ */ e.jsxs(
41
- I,
43
+ E,
42
44
  {
43
- onValueChange: (a) => n.setValue("expiresOn", a),
44
- defaultValue: n.getValues("expiresOn"),
45
+ onValueChange: (s) => a.setValue("expiresOn", s),
46
+ defaultValue: a.getValues("expiresOn"),
45
47
  children: [
46
- /* @__PURE__ */ e.jsx(S, { children: /* @__PURE__ */ e.jsx(A, {}) }),
47
- /* @__PURE__ */ e.jsx(C, { children: /* @__PURE__ */ e.jsxs(E, { children: [
48
- [7, 30, 60, 90].map((a) => /* @__PURE__ */ e.jsxs(x, { value: String(a), children: [
49
- a,
48
+ /* @__PURE__ */ e.jsx(P, { children: /* @__PURE__ */ e.jsx(D, {}) }),
49
+ /* @__PURE__ */ e.jsx(q, { children: /* @__PURE__ */ e.jsxs(R, { children: [
50
+ [7, 30, 60, 90].map((s) => /* @__PURE__ */ e.jsxs(p, { value: String(s), children: [
51
+ s,
50
52
  " days"
51
- ] }, a)),
52
- /* @__PURE__ */ e.jsx(x, { value: "never", children: "Never" })
53
+ ] }, s)),
54
+ /* @__PURE__ */ e.jsx(p, { value: "never", children: "Never" })
53
55
  ] }) })
54
56
  ]
55
57
  }
@@ -62,49 +64,49 @@ const L = ({ service: t }) => {
62
64
  }
63
65
  )
64
66
  ] }) : null;
65
- }, V = (t) => {
66
- const s = /* @__PURE__ */ new Date();
67
- return s.setDate(s.getDate() + t), s.toISOString();
68
- }, M = () => {
69
- const t = q();
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: [
67
+ }, M = (t) => {
68
+ const n = /* @__PURE__ */ new Date();
69
+ return n.setDate(n.getDate() + t), n.toISOString();
70
+ }, Q = () => {
71
+ const t = A();
72
+ return t.isAuthEnabled && t.isPending ? null : t.isAuthenticated ? /* @__PURE__ */ e.jsx(F, {}) : t.isAuthEnabled ? /* @__PURE__ */ e.jsxs("div", { className: "flex flex-col justify-center gap-2 items-center h-1/2", children: [
71
73
  "Please login first to view this page",
72
74
  /* @__PURE__ */ e.jsx(l, { onClick: () => t.login(), children: "Login" })
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
+ ] }) : /* @__PURE__ */ e.jsx("div", { className: "flex flex-col justify-center gap-2 items-center h-1/2", children: /* @__PURE__ */ e.jsxs(I, { className: "max-w-[600px]", children: [
74
76
  "Authentication needs to be enabled for API keys to work. Enable it in your Zudoku configuration under ",
75
77
  /* @__PURE__ */ e.jsx("code", { children: "authentication" }),
76
78
  "."
77
79
  ] }) });
78
80
  }, _ = ({ service: t }) => {
79
- const s = y(), r = O(), { data: n } = z({
80
- queryFn: () => t.getKeys(s),
81
+ const n = h(), i = g(), { data: r } = C({
82
+ queryFn: () => t.getKeys(n),
81
83
  queryKey: ["api-keys"],
82
84
  retry: !1
83
- }), o = d({
84
- mutationFn: (i) => {
85
+ }), a = d({
86
+ mutationFn: (s) => {
85
87
  if (!t.deleteKey)
86
88
  throw new Error("deleteKey not implemented");
87
- return t.deleteKey(i, s);
89
+ return t.deleteKey(s, n);
88
90
  },
89
91
  onSuccess: () => {
90
- r.invalidateQueries({ queryKey: ["api-keys"] });
92
+ i.invalidateQueries({ queryKey: ["api-keys"] });
91
93
  }
92
- }), a = d({
93
- mutationFn: (i) => {
94
+ }), o = d({
95
+ mutationFn: (s) => {
94
96
  if (!t.rollKey)
95
97
  throw new Error("rollKey not implemented");
96
- return t.rollKey(i, s);
98
+ return t.rollKey(s, n);
97
99
  },
98
- onSuccess: () => r.invalidateQueries({ queryKey: ["api-keys"] })
100
+ onSuccess: () => i.invalidateQueries({ queryKey: ["api-keys"] })
99
101
  });
100
102
  return /* @__PURE__ */ e.jsxs("div", { className: "max-w-screen-lg h-full pt-[--padding-content-top] pb-[--padding-content-bottom]", children: [
101
- /* @__PURE__ */ e.jsx(m, { name: "api-keys-list-page" }),
103
+ /* @__PURE__ */ e.jsx(x, { name: "api-keys-list-page" }),
102
104
  /* @__PURE__ */ e.jsxs("div", { className: "flex justify-between mb-4 border-b pb-3", children: [
103
105
  /* @__PURE__ */ e.jsx("h1", { className: "font-medium text-2xl", children: "API Keys" }),
104
106
  t.createKey && /* @__PURE__ */ e.jsx(l, { asChild: !0, children: /* @__PURE__ */ e.jsx(u, { to: "/settings/api-keys/new", children: "Create API Key" }) })
105
107
  ] }),
106
- /* @__PURE__ */ e.jsx(m, { name: "api-keys-list-page-before-keys" }),
107
- n.length === 0 ? /* @__PURE__ */ e.jsxs("div", { className: "flex flex-col justify-center gap-4 items-center p-8 border rounded bg-muted/30 text-muted-foreground", children: [
108
+ /* @__PURE__ */ e.jsx(x, { name: "api-keys-list-page-before-keys" }),
109
+ r.length === 0 ? /* @__PURE__ */ e.jsxs("div", { className: "flex flex-col justify-center gap-4 items-center p-8 border rounded bg-muted/30 text-muted-foreground", children: [
108
110
  /* @__PURE__ */ e.jsxs("p", { className: "text-center", children: [
109
111
  "No API keys created yet.",
110
112
  /* @__PURE__ */ e.jsx("br", {}),
@@ -114,29 +116,29 @@ const L = ({ service: t }) => {
114
116
  ] }) : /* @__PURE__ */ e.jsx(
115
117
  "ul",
116
118
  {
117
- className: T(
119
+ className: L(
118
120
  "grid grid-cols-1 rounded border divide-y divide-border",
119
121
  "lg:grid-cols-[minmax(250px,min-content)_1fr_min-content]"
120
122
  ),
121
- children: n.map((i) => /* @__PURE__ */ e.jsxs(
123
+ children: r.map((s) => /* @__PURE__ */ e.jsxs(
122
124
  "li",
123
125
  {
124
126
  className: "p-5 grid grid-cols-subgrid col-span-full gap-2 items-center",
125
127
  children: [
126
128
  /* @__PURE__ */ e.jsxs("div", { className: "flex flex-col gap-1 text-sm", children: [
127
- i.description ?? i.id,
129
+ s.description ?? s.id,
128
130
  /* @__PURE__ */ e.jsxs("div", { className: "text-muted-foreground text-xs", children: [
129
- i.createdOn && /* @__PURE__ */ e.jsxs("div", { children: [
131
+ s.createdOn && /* @__PURE__ */ e.jsxs("div", { children: [
130
132
  "Created on ",
131
- new Date(i.createdOn).toLocaleDateString()
133
+ new Date(s.createdOn).toLocaleDateString()
132
134
  ] }),
133
- i.expiresOn && /* @__PURE__ */ e.jsxs("div", { children: [
135
+ s.expiresOn && /* @__PURE__ */ e.jsxs("div", { children: [
134
136
  "Expires on ",
135
- new Date(i.expiresOn).toLocaleDateString()
137
+ new Date(s.expiresOn).toLocaleDateString()
136
138
  ] })
137
139
  ] })
138
140
  ] }),
139
- /* @__PURE__ */ e.jsx("div", { className: "items-center flex lg:justify-center", children: /* @__PURE__ */ e.jsx(Q, { apiKey: i.key }) }),
141
+ /* @__PURE__ */ e.jsx("div", { className: "items-center flex lg:justify-center", children: /* @__PURE__ */ e.jsx(G, { apiKey: s.key }) }),
140
142
  /* @__PURE__ */ e.jsxs("div", { className: "flex gap-2", children: [
141
143
  t.rollKey && /* @__PURE__ */ e.jsx(
142
144
  l,
@@ -145,9 +147,9 @@ const L = ({ service: t }) => {
145
147
  title: "Roll this key",
146
148
  variant: "ghost",
147
149
  onClick: () => {
148
- confirm("Do you want to roll this key?") && a.mutate(i.id);
150
+ confirm("Do you want to roll this key?") && o.mutate(s.id);
149
151
  },
150
- children: /* @__PURE__ */ e.jsx(g, { size: 16 })
152
+ children: /* @__PURE__ */ e.jsx(j, { size: 16 })
151
153
  }
152
154
  ),
153
155
  t.deleteKey && /* @__PURE__ */ e.jsx(
@@ -156,31 +158,31 @@ const L = ({ service: t }) => {
156
158
  variant: "ghost",
157
159
  size: "icon",
158
160
  onClick: () => {
159
- confirm("Do you want to delete this key?") && o.mutate(i.id);
161
+ confirm("Do you want to delete this key?") && a.mutate(s.id);
160
162
  },
161
- disabled: o.isPending,
162
- children: /* @__PURE__ */ e.jsx(f, { size: 16 })
163
+ disabled: a.isPending,
164
+ children: /* @__PURE__ */ e.jsx(v, { size: 16 })
163
165
  }
164
166
  )
165
167
  ] })
166
168
  ]
167
169
  },
168
- i.id
170
+ s.id
169
171
  ))
170
172
  }
171
173
  )
172
174
  ] });
173
- }, Q = ({ apiKey: t }) => {
174
- const [s, r] = p(!1), [n, o] = p(!1);
175
+ }, G = ({ apiKey: t }) => {
176
+ const [n, i] = y(!1), [r, a] = y(!1);
175
177
  return /* @__PURE__ */ e.jsxs("div", { className: "flex gap-2 items-center text-sm", children: [
176
- /* @__PURE__ */ e.jsx("div", { className: "border rounded bg-gray-100 dark:bg-gray-950 p-1 font-mono truncate h-9 items-center flex px-2", children: s ? t : "•".repeat(t.length) }),
178
+ /* @__PURE__ */ e.jsx("div", { className: "border rounded bg-gray-100 dark:bg-gray-950 p-1 font-mono truncate h-9 items-center flex px-2", children: n ? t : "•".repeat(t.length) }),
177
179
  /* @__PURE__ */ e.jsx(
178
180
  l,
179
181
  {
180
182
  variant: "outline",
181
- onClick: () => r((a) => !a),
183
+ onClick: () => i((o) => !o),
182
184
  size: "icon",
183
- children: s ? /* @__PURE__ */ e.jsx(j, { size: 16 }) : /* @__PURE__ */ e.jsx(v, { size: 16 })
185
+ children: n ? /* @__PURE__ */ e.jsx(w, { size: 16 }) : /* @__PURE__ */ e.jsx(K, { size: 16 })
184
186
  }
185
187
  ),
186
188
  /* @__PURE__ */ e.jsx(
@@ -189,68 +191,68 @@ const L = ({ service: t }) => {
189
191
  variant: "outline",
190
192
  onClick: () => {
191
193
  navigator.clipboard.writeText(t).then(() => {
192
- o(!0), setTimeout(() => o(!1), 2e3);
194
+ a(!0), setTimeout(() => a(!1), 2e3);
193
195
  });
194
196
  },
195
197
  size: "icon",
196
- children: n ? /* @__PURE__ */ e.jsx(w, { size: 16 }) : /* @__PURE__ */ e.jsx(K, { size: 16 })
198
+ children: r ? /* @__PURE__ */ e.jsx(k, { size: 16 }) : /* @__PURE__ */ e.jsx(b, { size: 16 })
197
199
  }
198
200
  )
199
201
  ] });
200
- }, G = "https://zudoku-rewiringamerica-main-ef9c9c0.d2.zuplo.dev", $ = (t) => ({
201
- deleteKey: async (s, r) => {
202
- const n = new Request(t + `/v1/developer/api-keys/${s}`, {
202
+ }, $ = "https://zudoku-rewiringamerica-main-ef9c9c0.d2.zuplo.dev", B = (t) => ({
203
+ deleteKey: async (n, i) => {
204
+ const r = new Request(t + `/v1/developer/api-keys/${n}`, {
203
205
  method: "DELETE"
204
206
  });
205
- await r.signRequest(n);
206
- const o = await fetch(n);
207
- c(o.ok, "Failed to delete API key");
207
+ await i.signRequest(r);
208
+ const a = await fetch(r);
209
+ c(a.ok, "Failed to delete API key");
208
210
  },
209
- rollKey: async (s, r) => {
210
- const n = await fetch(
211
- await r.signRequest(
212
- new Request(t + `/v1/developer/api-keys/${s}/key`, {
211
+ rollKey: async (n, i) => {
212
+ const r = await fetch(
213
+ await i.signRequest(
214
+ new Request(t + `/v1/developer/api-keys/${n}/key`, {
213
215
  method: "DELETE"
214
216
  })
215
217
  )
216
218
  );
217
- c(n.ok, "Failed to delete API key");
219
+ c(r.ok, "Failed to delete API key");
218
220
  },
219
- createKey: async (s, r) => {
220
- const n = new Request(t + "/v1/developer/api-keys", {
221
+ createKey: async (n, i) => {
222
+ const r = new Request(t + "/v1/developer/api-keys", {
221
223
  method: "POST",
222
224
  headers: {
223
225
  "Content-Type": "application/json"
224
226
  },
225
- body: JSON.stringify(s)
227
+ body: JSON.stringify(n)
226
228
  });
227
- await r.signRequest(n);
228
- const o = await fetch(n);
229
- c(o.ok, "Failed to create API key");
229
+ await i.signRequest(r);
230
+ const a = await fetch(r);
231
+ c(a.ok, "Failed to create API key");
230
232
  },
231
- getKeys: async (s) => {
232
- const r = new Request(t + "/v1/developer/api-keys");
233
- await s.signRequest(r);
234
- const n = await fetch(r);
235
- return c(n.ok, "Failed to fetch API keys"), await n.json();
233
+ getKeys: async (n) => {
234
+ const i = new Request(t + "/v1/developer/api-keys");
235
+ await n.signRequest(i);
236
+ const r = await fetch(i);
237
+ return c(r.ok, "Failed to fetch API keys"), await r.json();
236
238
  }
237
- }), ie = (t) => t, re = (t) => {
238
- const s = "endpoint" in t ? t.endpoint : G, r = "getKeys" in t ? t : $(s);
239
+ }), re = (t) => t, ae = (t) => {
240
+ const n = "endpoint" in t ? t.endpoint : $, i = "getKeys" in t ? t : B(n);
239
241
  return {
240
242
  getProfileMenuItems: () => [
241
243
  {
242
244
  label: "API Keys",
243
245
  path: "/settings/api-keys",
244
246
  category: "middle",
245
- icon: b
247
+ icon: N
246
248
  }
247
249
  ],
248
- getIdentities: async (n) => {
250
+ getIdentities: async (r) => {
249
251
  try {
250
- return (await r.getKeys(n)).map((a) => ({
251
- authorizeRequest: (i) => (i.headers.set("Authorization", `Bearer ${a.key}`), i),
252
- id: a.id,
253
- label: a.description ?? a.id
252
+ return (await i.getKeys(r)).map((o) => ({
253
+ authorizeRequest: (s) => (s.headers.set("Authorization", `Bearer ${o.key}`), s),
254
+ id: o.id,
255
+ label: o.description ?? o.id
254
256
  }));
255
257
  } catch {
256
258
  return [];
@@ -258,16 +260,16 @@ const L = ({ service: t }) => {
258
260
  },
259
261
  getRoutes: () => [
260
262
  {
261
- element: /* @__PURE__ */ e.jsx(M, {}),
262
- errorElement: /* @__PURE__ */ e.jsx(N, {}),
263
+ element: /* @__PURE__ */ e.jsx(Q, {}),
264
+ errorElement: /* @__PURE__ */ e.jsx(S, {}),
263
265
  children: [
264
266
  {
265
267
  path: "/settings/api-keys",
266
- element: /* @__PURE__ */ e.jsx(_, { service: r })
268
+ element: /* @__PURE__ */ e.jsx(_, { service: i })
267
269
  },
268
270
  {
269
271
  path: "/settings/api-keys/new",
270
- element: /* @__PURE__ */ e.jsx(L, { service: r })
272
+ element: /* @__PURE__ */ e.jsx(V, { service: i })
271
273
  }
272
274
  ]
273
275
  }
@@ -275,7 +277,7 @@ const L = ({ service: t }) => {
275
277
  };
276
278
  };
277
279
  export {
278
- re as apiKeyPlugin,
279
- ie as createApiKeyService
280
+ ae as apiKeyPlugin,
281
+ re as createApiKeyService
280
282
  };
281
283
  //# 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 createApiKeyService = <T extends ApiKeyService>(service: T): T =>\n service;\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","createApiKeyService","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, useQueryClient } 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 queryClient = useQueryClient();\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(\"createKey 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: async () => {\n await queryClient.invalidateQueries({ queryKey: [\"api-keys\"] });\n await navigate(\"/settings/api-keys/\");\n },\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 createApiKeyService = <T extends ApiKeyService>(service: T): T =>\n service;\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","queryClient","useQueryClient","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","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","createApiKeyService","apiKeyPlugin","options","FileKey2Icon","RouterError"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zudoku",
3
- "version": "0.32.2",
3
+ "version": "0.32.4",
4
4
  "type": "module",
5
5
  "homepage": "https://zudoku.dev",
6
6
  "repository": {
@@ -14,6 +14,14 @@ import { getRoutesByConfig } from "./main.js";
14
14
  const routes = getRoutesByConfig(config);
15
15
  const root = document.getElementById("root")!;
16
16
 
17
+ declare global {
18
+ interface Window {
19
+ ZUDOKU_VERSION: string;
20
+ }
21
+ }
22
+
23
+ window.ZUDOKU_VERSION = process.env.ZUDOKU_VERSION ?? "unknown";
24
+
17
25
  if (process.env.SENTRY_DSN) {
18
26
  void import("./sentry.js").then((mod) => {
19
27
  mod.initSentry({ dsn: process.env.SENTRY_DSN as string });
@@ -66,7 +66,7 @@ export const Header = memo(function HeaderInner() {
66
66
  <header className="sticky lg:top-0 z-10 bg-background/80 backdrop-blur w-full">
67
67
  <Banner />
68
68
  <div className="border-b">
69
- <div className="max-w-screen-2xl border-l border-r mx-auto grid grid-cols-[1fr_auto] lg:grid-cols-[calc(var(--side-nav-width))_1fr] lg:gap-12 items-center px-4 lg:px-8 h-[--top-header-height]">
69
+ <div className="max-w-screen-2xl 2xl:border-x mx-auto grid grid-cols-[1fr_auto] lg:grid-cols-[calc(var(--side-nav-width))_1fr] lg:gap-12 items-center px-4 lg:px-8 h-[--top-header-height]">
70
70
  <div className="flex">
71
71
  <Link to="/">
72
72
  <div className="flex items-center gap-3.5">
@@ -177,8 +177,8 @@ export const Header = memo(function HeaderInner() {
177
177
  </div>
178
178
  </div>
179
179
  </div>
180
- <div className="border-b">
181
- <div className="max-w-screen-2xl mx-auto border-l border-r">
180
+ <div className="border-b hidden lg:block">
181
+ <div className="max-w-screen-2xl mx-auto 2xl:border-x">
182
182
  <Slotlet name="top-navigation-before" />
183
183
  <TopNavigation />
184
184
  <Slotlet name="top-navigation-after" />
@@ -66,7 +66,7 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
66
66
  <Header />
67
67
  <Slotlet name="layout-after-head" />
68
68
 
69
- <div className="w-full max-w-screen-2xl mx-auto px-4 lg:px-8 border-l border-r">
69
+ <div className="w-full min-h-[calc(100vh-var(--header-height))] max-w-screen-2xl mx-auto px-4 lg:px-8 2xl:border-x">
70
70
  {showSpinner ? (
71
71
  <LoadingFallback />
72
72
  ) : (
@@ -79,7 +79,7 @@ export const Layout = ({ children }: { children?: ReactNode }) => {
79
79
  <Sidebar onRequestClose={() => setDrawerOpen(false)} />
80
80
  <div
81
81
  className={cn(
82
- "lg:hidden -mx-10 px-10 py-2 sticky bg-background/80 backdrop-blur z-10 top-0 left-0 right-0 border-b",
82
+ "lg:hidden -mx-4 px-4 py-2 sticky bg-background/80 backdrop-blur z-10 top-0 left-0 right-0 border-b",
83
83
  "peer-data-[navigation=false]:hidden",
84
84
  )}
85
85
  >
@@ -1,4 +1,4 @@
1
- import { useMutation } from "@tanstack/react-query";
1
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
2
  import { useForm } from "react-hook-form";
3
3
  import { Link, useNavigate } from "react-router";
4
4
  import {
@@ -18,6 +18,7 @@ type CreateApiKey = { description: string; expiresOn?: string };
18
18
 
19
19
  export const CreateApiKey = ({ service }: { service: ApiKeyService }) => {
20
20
  const context = useZudoku();
21
+ const queryClient = useQueryClient();
21
22
  const navigate = useNavigate();
22
23
  const form = useForm<CreateApiKey>({
23
24
  defaultValues: {
@@ -27,7 +28,7 @@ export const CreateApiKey = ({ service }: { service: ApiKeyService }) => {
27
28
  const createKeyMutation = useMutation({
28
29
  mutationFn: ({ description, expiresOn }: CreateApiKey) => {
29
30
  if (!service.createKey) {
30
- throw new Error("deleteKey not implemented");
31
+ throw new Error("createKey not implemented");
31
32
  }
32
33
 
33
34
  const expiresOnDate =
@@ -38,7 +39,10 @@ export const CreateApiKey = ({ service }: { service: ApiKeyService }) => {
38
39
  context,
39
40
  );
40
41
  },
41
- onSuccess: () => navigate("/settings/api-keys/"),
42
+ onSuccess: async () => {
43
+ await queryClient.invalidateQueries({ queryKey: ["api-keys"] });
44
+ await navigate("/settings/api-keys/");
45
+ },
42
46
  });
43
47
 
44
48
  if (!service.createKey) {