zudoku 0.66.2 → 0.66.3

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 (161) hide show
  1. package/dist/config/validators/validate.d.ts +32 -3
  2. package/dist/config/validators/validate.js +1 -1
  3. package/dist/config/validators/validate.js.map +1 -1
  4. package/dist/flat-config.d.ts +1 -1
  5. package/dist/lib/auth/issuer.js +1 -1
  6. package/dist/lib/auth/issuer.js.map +1 -1
  7. package/dist/lib/authentication/authentication.d.ts +3 -2
  8. package/dist/lib/authentication/hook.d.ts +2 -0
  9. package/dist/lib/authentication/hook.js +10 -0
  10. package/dist/lib/authentication/hook.js.map +1 -1
  11. package/dist/lib/authentication/providers/firebase.js +67 -9
  12. package/dist/lib/authentication/providers/firebase.js.map +1 -1
  13. package/dist/lib/authentication/ui/EmailVerificationUi.d.ts +4 -0
  14. package/dist/lib/authentication/ui/EmailVerificationUi.js +34 -0
  15. package/dist/lib/authentication/ui/EmailVerificationUi.js.map +1 -0
  16. package/dist/lib/authentication/ui/ZudokuAuthUi.d.ts +7 -2
  17. package/dist/lib/authentication/ui/ZudokuAuthUi.js +43 -11
  18. package/dist/lib/authentication/ui/ZudokuAuthUi.js.map +1 -1
  19. package/dist/lib/authentication/utils/relativeRedirectUrl.d.ts +1 -0
  20. package/dist/lib/authentication/utils/relativeRedirectUrl.js +8 -0
  21. package/dist/lib/authentication/utils/relativeRedirectUrl.js.map +1 -0
  22. package/dist/lib/components/index.d.ts +2 -0
  23. package/dist/lib/errors/ErrorMessage.d.ts +3 -0
  24. package/dist/lib/errors/ErrorMessage.js +16 -0
  25. package/dist/lib/errors/ErrorMessage.js.map +1 -0
  26. package/dist/lib/hooks/index.d.ts +2 -0
  27. package/dist/lib/plugins/api-keys/SettingsApiKeys.js +9 -172
  28. package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
  29. package/dist/lib/plugins/api-keys/index.d.ts +4 -1
  30. package/dist/lib/plugins/api-keys/index.js +19 -14
  31. package/dist/lib/plugins/api-keys/index.js.map +1 -1
  32. package/dist/lib/plugins/api-keys/settings/ApiKeyItem.d.ts +12 -0
  33. package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js +133 -0
  34. package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js.map +1 -0
  35. package/dist/lib/plugins/api-keys/settings/ApiKeyList.d.ts +4 -0
  36. package/dist/lib/plugins/api-keys/settings/ApiKeyList.js +30 -0
  37. package/dist/lib/plugins/api-keys/settings/ApiKeyList.js.map +1 -0
  38. package/dist/lib/plugins/api-keys/settings/RevealApiKey.d.ts +6 -0
  39. package/dist/lib/plugins/api-keys/settings/RevealApiKey.js +39 -0
  40. package/dist/lib/plugins/api-keys/settings/RevealApiKey.js.map +1 -0
  41. package/dist/vite/config.js +7 -0
  42. package/dist/vite/config.js.map +1 -1
  43. package/dist/vite/plugin-api-keys.js +4 -1
  44. package/dist/vite/plugin-api-keys.js.map +1 -1
  45. package/dist/vite/zuplo.d.ts +13 -0
  46. package/dist/vite/zuplo.js +15 -0
  47. package/dist/vite/zuplo.js.map +1 -0
  48. package/lib/{ClaudeLogo-CGRfGTk2.js → ClaudeLogo-BZslN9XF.js} +3 -3
  49. package/lib/{ClaudeLogo-CGRfGTk2.js.map → ClaudeLogo-BZslN9XF.js.map} +1 -1
  50. package/lib/{Drawer-Ci7XwhqT.js → Drawer-BRMcpfuF.js} +6 -6
  51. package/lib/{Drawer-Ci7XwhqT.js.map → Drawer-BRMcpfuF.js.map} +1 -1
  52. package/lib/Frame-DxlznfVd.js +205 -0
  53. package/lib/Frame-DxlznfVd.js.map +1 -0
  54. package/lib/{useMutation-C6RqWmTS.js → Input-D-kqEQ5M.js} +41 -23
  55. package/lib/Input-D-kqEQ5M.js.map +1 -0
  56. package/lib/{MdxPage-Bjf72BP3.js → MdxPage-B4zZq5aR.js} +8 -8
  57. package/lib/{MdxPage-Bjf72BP3.js.map → MdxPage-B4zZq5aR.js.map} +1 -1
  58. package/lib/{Mermaid-D_VSX7_Q.js → Mermaid-BjSczjLW.js} +3 -3
  59. package/lib/{Mermaid-D_VSX7_Q.js.map → Mermaid-BjSczjLW.js.map} +1 -1
  60. package/lib/{OAuthErrorPage-1Ekji0PK.js → OAuthErrorPage-DRY2hlga.js} +20 -21
  61. package/lib/{OAuthErrorPage-1Ekji0PK.js.map → OAuthErrorPage-DRY2hlga.js.map} +1 -1
  62. package/lib/{OasProvider-BZxmTyMM.js → OasProvider-lMwTD76Y.js} +4 -4
  63. package/lib/{OasProvider-BZxmTyMM.js.map → OasProvider-lMwTD76Y.js.map} +1 -1
  64. package/lib/{OperationList-B7nPIFB8.js → OperationList-Bm76b4vl.js} +23 -24
  65. package/lib/{OperationList-B7nPIFB8.js.map → OperationList-Bm76b4vl.js.map} +1 -1
  66. package/lib/{RouteGuard-9wjejsKm.js → RouteGuard-DGc32XJV.js} +4 -4
  67. package/lib/{RouteGuard-9wjejsKm.js.map → RouteGuard-DGc32XJV.js.map} +1 -1
  68. package/lib/{SchemaList-16_obkku.js → SchemaList-DX4FPogg.js} +7 -7
  69. package/lib/{SchemaList-16_obkku.js.map → SchemaList-DX4FPogg.js.map} +1 -1
  70. package/lib/SchemaView-CjXvSRxy.js +434 -0
  71. package/lib/SchemaView-CjXvSRxy.js.map +1 -0
  72. package/lib/{Select-CkxXP5I7.js → Secret-BxGpIhDP.js} +121 -121
  73. package/lib/Secret-BxGpIhDP.js.map +1 -0
  74. package/lib/{SignUp-D54_QWFy.js → SignUp-CntxjFGS.js} +4 -4
  75. package/lib/{SignUp-D54_QWFy.js.map → SignUp-CntxjFGS.js.map} +1 -1
  76. package/lib/{SyntaxHighlight-j_HRSPCU.js → SyntaxHighlight-Dgd0AaaX.js} +2 -2
  77. package/lib/{SyntaxHighlight-j_HRSPCU.js.map → SyntaxHighlight-Dgd0AaaX.js.map} +1 -1
  78. package/lib/{Toc-z05x698-.js → Toc-L1vGGHA1.js} +2 -2
  79. package/lib/{Toc-z05x698-.js.map → Toc-L1vGGHA1.js.map} +1 -1
  80. package/lib/{ZudokuContext-BXldanA8.js → ZudokuContext-DNHMZfcP.js} +33 -33
  81. package/lib/{ZudokuContext-BXldanA8.js.map → ZudokuContext-DNHMZfcP.js.map} +1 -1
  82. package/lib/{chunk-PVWAREVJ-dLIqswPy.js → chunk-PVWAREVJ-ClM0m2aJ.js} +19 -19
  83. package/lib/{chunk-PVWAREVJ-dLIqswPy.js.map → chunk-PVWAREVJ-ClM0m2aJ.js.map} +1 -1
  84. package/lib/{circular-D5sYCIWL.js → circular-BIN_WQ0c.js} +2 -2
  85. package/lib/{circular-D5sYCIWL.js.map → circular-BIN_WQ0c.js.map} +1 -1
  86. package/lib/{createServer-BlwU7lIr.js → createServer-BEl12QFw.js} +4 -4
  87. package/lib/{createServer-BlwU7lIr.js.map → createServer-BEl12QFw.js.map} +1 -1
  88. package/lib/createVariantComponent-CQVt-H3r.js +18 -0
  89. package/lib/createVariantComponent-CQVt-H3r.js.map +1 -0
  90. package/lib/{errors-BtC4Kn2j.js → errors-CtBbD47A.js} +2 -2
  91. package/lib/{errors-BtC4Kn2j.js.map → errors-CtBbD47A.js.map} +1 -1
  92. package/lib/{firebase-Ibm_tv3G.js → firebase-CT38ugg4.js} +1588 -1342
  93. package/lib/firebase-CT38ugg4.js.map +1 -0
  94. package/lib/hook-CHw_R_xu.js +52 -0
  95. package/lib/hook-CHw_R_xu.js.map +1 -0
  96. package/lib/{ErrorAlert-BUlG32M9.js → index-CG2v-T7-.js} +5373 -4335
  97. package/lib/index-CG2v-T7-.js.map +1 -0
  98. package/lib/{index-eKVhlB94.js → index-CISwSpdT.js} +2 -2
  99. package/lib/{index-eKVhlB94.js.map → index-CISwSpdT.js.map} +1 -1
  100. package/lib/{index-Css56y3F.js → index-DXXZDuSJ.js} +4 -4
  101. package/lib/{index-Css56y3F.js.map → index-DXXZDuSJ.js.map} +1 -1
  102. package/lib/{index-CeVTNcfF.js → index-jI2Fjpy-.js} +98 -99
  103. package/lib/{index-CeVTNcfF.js.map → index-jI2Fjpy-.js.map} +1 -1
  104. package/lib/{index.esm-BoKBnRoT.js → index.esm-Bu35TNgg.js} +16 -14
  105. package/lib/index.esm-Bu35TNgg.js.map +1 -0
  106. package/lib/{mutation-BoVlx8yA.js → mutation-DMHWqmFp.js} +2 -2
  107. package/lib/{mutation-BoVlx8yA.js.map → mutation-DMHWqmFp.js.map} +1 -1
  108. package/lib/ui/Drawer.js +2 -2
  109. package/lib/ui/SyntaxHighlight.js +2 -2
  110. package/lib/zudoku.__internal.js +507 -479
  111. package/lib/zudoku.__internal.js.map +1 -1
  112. package/lib/zudoku.auth-auth0.js +1 -1
  113. package/lib/zudoku.auth-azureb2c.js +4 -4
  114. package/lib/zudoku.auth-clerk.js +2 -2
  115. package/lib/zudoku.auth-firebase.js +6 -5
  116. package/lib/zudoku.auth-firebase.js.map +1 -1
  117. package/lib/zudoku.auth-openid.js +4 -4
  118. package/lib/zudoku.auth-supabase.js +5 -5
  119. package/lib/zudoku.components.js +20 -21
  120. package/lib/zudoku.components.js.map +1 -1
  121. package/lib/zudoku.hooks.js +3 -3
  122. package/lib/zudoku.mermaid.js +3 -3
  123. package/lib/zudoku.plugin-api-catalog.js +8 -9
  124. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  125. package/lib/zudoku.plugin-api-keys.js +580 -543
  126. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  127. package/lib/zudoku.plugin-custom-pages.js +1 -1
  128. package/lib/zudoku.plugin-markdown.js +1 -1
  129. package/lib/zudoku.plugin-openapi.js +3 -3
  130. package/lib/zudoku.plugin-redirect.js +1 -1
  131. package/lib/zudoku.plugin-search-pagefind.js +3 -3
  132. package/lib/zudoku.router.js +2 -2
  133. package/package.json +4 -2
  134. package/src/lib/auth/issuer.ts +1 -1
  135. package/src/lib/authentication/authentication.ts +8 -2
  136. package/src/lib/authentication/hook.ts +16 -0
  137. package/src/lib/authentication/providers/firebase.tsx +98 -6
  138. package/src/lib/authentication/ui/EmailVerificationUi.tsx +129 -0
  139. package/src/lib/authentication/ui/ZudokuAuthUi.tsx +170 -38
  140. package/src/lib/authentication/utils/relativeRedirectUrl.ts +12 -0
  141. package/src/lib/errors/ErrorMessage.tsx +38 -0
  142. package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +36 -476
  143. package/src/lib/plugins/api-keys/index.tsx +33 -18
  144. package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -0
  145. package/src/lib/plugins/api-keys/settings/ApiKeyList.tsx +64 -0
  146. package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +124 -0
  147. package/lib/ErrorAlert-BUlG32M9.js.map +0 -1
  148. package/lib/RouterError-DfTZblpv.js +0 -42
  149. package/lib/RouterError-DfTZblpv.js.map +0 -1
  150. package/lib/SchemaView-eyvR4bRt.js +0 -597
  151. package/lib/SchemaView-eyvR4bRt.js.map +0 -1
  152. package/lib/Select-CkxXP5I7.js.map +0 -1
  153. package/lib/createVariantComponent-B9_dVBvu.js +0 -35
  154. package/lib/createVariantComponent-B9_dVBvu.js.map +0 -1
  155. package/lib/firebase-Ibm_tv3G.js.map +0 -1
  156. package/lib/hook-BNxidGQq.js +0 -40
  157. package/lib/hook-BNxidGQq.js.map +0 -1
  158. package/lib/index-DSOi7zVM.js +0 -1059
  159. package/lib/index-DSOi7zVM.js.map +0 -1
  160. package/lib/index.esm-BoKBnRoT.js.map +0 -1
  161. package/lib/useMutation-C6RqWmTS.js.map +0 -1
@@ -1,181 +1,26 @@
1
+ import { useState } from "react";
2
+ import { ErrorBoundary } from "react-error-boundary";
3
+ import { Button } from "zudoku/components";
4
+ import { useAuth } from "zudoku/hooks";
1
5
  import {
2
- useMutation,
3
- useQueryClient,
4
- useSuspenseQuery,
5
- } from "@tanstack/react-query";
6
- import {
7
- CheckIcon,
8
- CircleSlashIcon,
9
- PencilLineIcon,
10
- RefreshCwIcon,
11
- TrashIcon,
12
- XIcon,
13
- } from "lucide-react";
14
- import { AnimatePresence } from "motion/react";
15
- import React, { useState } from "react";
16
- import { Alert, AlertTitle } from "zudoku/ui/Alert.js";
17
- import { Card, CardHeader } from "zudoku/ui/Card.js";
18
- import {
19
- Dialog,
20
- DialogClose,
21
- DialogContent,
22
- DialogDescription,
23
- DialogFooter,
24
- DialogHeader,
25
- DialogTitle,
26
- DialogTrigger,
27
- } from "zudoku/ui/Dialog.js";
28
- import { Secret } from "zudoku/ui/Secret.js";
29
- import { useZudoku } from "../../components/context/ZudokuContext.js";
6
+ Item,
7
+ ItemActions,
8
+ ItemContent,
9
+ ItemDescription,
10
+ ItemTitle,
11
+ } from "zudoku/ui/Item.js";
30
12
  import { Slot } from "../../components/Slot.js";
31
- import { Button } from "../../ui/Button.js";
32
- import { Input } from "../../ui/Input.js";
33
- import { cn } from "../../util/cn.js";
13
+ import { ErrorMessage } from "../../errors/ErrorMessage.js";
34
14
  import { CreateApiKeyDialog } from "./CreateApiKeyDialog.js";
35
- import type { ApiConsumer, ApiKey, ApiKeyService } from "./index.js";
15
+ import type { ApiKeyService } from "./index.js";
16
+ import { ApiKeyList } from "./settings/ApiKeyList.js";
36
17
 
37
18
  export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
38
- const context = useZudoku();
39
- const queryClient = useQueryClient();
40
- const [editingConsumerId, setEditingConsumerId] = useState<string | null>(
41
- null,
42
- );
43
- const [editingLabel, setEditingLabel] = useState<string>("");
44
- const { data, isFetching } = useSuspenseQuery({
45
- queryFn: () => service.getConsumers(context),
46
- queryKey: ["api-keys"],
47
- retry: false,
48
- });
49
-
50
19
  const [isCreateApiKeyOpen, setIsCreateApiKeyOpen] = useState(false);
51
-
52
- const deleteKeyMutation = useMutation({
53
- mutationFn: ({
54
- consumerId,
55
- keyId,
56
- }: {
57
- consumerId: string;
58
- keyId: string;
59
- }) => {
60
- if (!service.deleteKey) {
61
- throw new Error("deleteKey not implemented");
62
- }
63
-
64
- return service.deleteKey(consumerId, keyId, context);
65
- },
66
- onMutate: async ({ consumerId, keyId }) => {
67
- await queryClient.cancelQueries({ queryKey: ["api-keys"] });
68
- const previousData = queryClient.getQueryData<ApiConsumer[]>([
69
- "api-keys",
70
- ]);
71
- queryClient.setQueryData<ApiConsumer[]>(["api-keys"], (old) => {
72
- if (!old) {
73
- return old;
74
- }
75
-
76
- return old.map((consumer) => {
77
- if (consumer.id === consumerId) {
78
- return {
79
- ...consumer,
80
- apiKeys: consumer.apiKeys.filter((key) => key.id !== keyId),
81
- };
82
- }
83
- return consumer;
84
- });
85
- });
86
-
87
- return { previousData };
88
- },
89
- onError: (_err, _variables, context) => {
90
- if (context?.previousData) {
91
- queryClient.setQueryData(["api-keys"], context.previousData);
92
- }
93
- },
94
- onSuccess: () => {
95
- void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
96
- },
97
- });
98
-
99
- const updateConsumerMutation = useMutation({
100
- mutationFn: ({
101
- consumerId,
102
- label,
103
- }: {
104
- consumerId: string;
105
- label: string;
106
- }) => {
107
- if (!service.updateConsumer) {
108
- throw new Error("updateConsumer not implemented");
109
- }
110
-
111
- return service.updateConsumer({ id: consumerId, label }, context);
112
- },
113
- onMutate: async ({ consumerId, label }) => {
114
- await queryClient.cancelQueries({ queryKey: ["api-keys"] });
115
-
116
- const previousData = queryClient.getQueryData(["api-keys"]);
117
- queryClient.setQueryData<ApiConsumer[]>(["api-keys"], (old) => {
118
- if (!old) {
119
- return old;
120
- }
121
-
122
- return old.map((consumer) => {
123
- if (consumer.id === consumerId) {
124
- return {
125
- ...consumer,
126
- label,
127
- };
128
- }
129
- return consumer;
130
- });
131
- });
132
-
133
- return { previousData };
134
- },
135
- onError: (_err, _variables, context) => {
136
- if (context?.previousData) {
137
- queryClient.setQueryData(["api-keys"], context.previousData);
138
- }
139
- },
140
- onSuccess: () => {
141
- void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
142
- },
143
- });
144
-
145
- const rollKeyMutation = useMutation({
146
- mutationFn: (id: string) => {
147
- if (!service.rollKey) {
148
- throw new Error("rollKey not implemented");
149
- }
150
-
151
- return service.rollKey(id, context);
152
- },
153
- onSuccess: () => queryClient.invalidateQueries({ queryKey: ["api-keys"] }),
154
- });
155
-
156
- const handleStartEdit = (consumerId: string, currentLabel: string) => {
157
- setEditingConsumerId(consumerId);
158
- setEditingLabel(currentLabel);
159
- };
160
-
161
- const handleSaveEdit = (consumerId: string) => {
162
- if (editingLabel.trim()) {
163
- updateConsumerMutation.mutate({
164
- consumerId,
165
- label: editingLabel.trim(),
166
- });
167
- }
168
- setEditingConsumerId(null);
169
- setEditingLabel("");
170
- };
171
-
172
- const handleCancelEdit = () => {
173
- setEditingConsumerId(null);
174
- setEditingLabel("");
175
- };
20
+ const auth = useAuth();
176
21
 
177
22
  return (
178
- <div className="max-w-screen-md h-full pt-(--padding-content-top) pb-(--padding-content-bottom)">
23
+ <div className="max-w-3xl h-full pt-(--padding-content-top) pb-(--padding-content-bottom)">
179
24
  <Slot.Target name="api-keys-list-page" />
180
25
 
181
26
  <div className="flex justify-between pb-3">
@@ -192,313 +37,28 @@ export const SettingsApiKeys = ({ service }: { service: ApiKeyService }) => {
192
37
  <p>Create, manage, and monitor your API keys</p>
193
38
 
194
39
  <Slot.Target name="api-keys-list-page-before-keys" />
195
- <div className="h-8"></div>
196
- {rollKeyMutation.isError && (
197
- <Alert variant="destructive" className="mb-4">
198
- <CircleSlashIcon size={16} />
199
- <AlertTitle>{rollKeyMutation.error.message}</AlertTitle>
200
- </Alert>
201
- )}
202
- {updateConsumerMutation.isError && (
203
- <Alert variant="destructive" className="mb-4">
204
- <CircleSlashIcon size={16} />
205
- <AlertTitle>{updateConsumerMutation.error.message}</AlertTitle>
206
- </Alert>
40
+ {auth.profile?.emailVerified === false ? (
41
+ <Item variant="outline">
42
+ <ItemContent>
43
+ <ItemTitle>Verified email required</ItemTitle>
44
+ <ItemDescription>
45
+ You need to verify your email to access API keys.
46
+ </ItemDescription>
47
+ </ItemContent>
48
+
49
+ <ItemActions>
50
+ <Button onClick={() => auth.requestEmailVerification()}>
51
+ Verify email
52
+ </Button>
53
+ </ItemActions>
54
+ </Item>
55
+ ) : (
56
+ <ErrorBoundary
57
+ fallbackRender={({ error }) => <ErrorMessage error={error} />}
58
+ >
59
+ <ApiKeyList service={service} />
60
+ </ErrorBoundary>
207
61
  )}
208
- {deleteKeyMutation.isError && (
209
- <Alert variant="destructive" className="mb-4">
210
- <CircleSlashIcon size={16} />
211
- <AlertTitle>{deleteKeyMutation.error.message}</AlertTitle>
212
- </Alert>
213
- )}
214
- <div className="">
215
- {data.length === 0 ? (
216
- <div className="flex col-span-full flex-col justify-center gap-4 items-center p-8 border rounded-sm bg-muted/30 text-muted-foreground">
217
- <p className="text-center">
218
- You have no API keys yet.
219
- <br />
220
- {service.createKey && "Get started and create your first key."}
221
- </p>
222
- {service.createKey && (
223
- <CreateApiKeyDialog
224
- service={service}
225
- isOpen={isCreateApiKeyOpen}
226
- onOpenChange={setIsCreateApiKeyOpen}
227
- />
228
- )}
229
- </div>
230
- ) : (
231
- <ul
232
- className={cn(
233
- "grid grid-cols-[1fr_min-content] divide-y divide-border col-span-6",
234
- )}
235
- >
236
- {data.map((consumers) => (
237
- <Card
238
- className="grid grid-cols-subgrid col-span-full items-center mb-4 group"
239
- key={consumers.id}
240
- >
241
- <CardHeader className="border-b col-span-full grid-cols-subgrid grid">
242
- <div className="h-10 flex flex-col text-sm justify-center">
243
- <div className="font-medium text-lg flex items-center gap-2">
244
- {editingConsumerId === consumers.id ? (
245
- <div className="flex items-center gap-2 w-full">
246
- <Input
247
- maxLength={32}
248
- value={editingLabel}
249
- onChange={(e) => setEditingLabel(e.target.value)}
250
- onKeyDown={(e) => {
251
- if (e.key === "Enter") {
252
- handleSaveEdit(consumers.id);
253
- } else if (e.key === "Escape") {
254
- handleCancelEdit();
255
- }
256
- }}
257
- className="text-lg font-medium"
258
- autoFocus
259
- />
260
- <div className="flex items-center">
261
- <Button
262
- size="icon"
263
- variant="ghost"
264
- onClick={() => handleSaveEdit(consumers.id)}
265
- disabled={!editingLabel.trim()}
266
- >
267
- <CheckIcon size={16} />
268
- </Button>
269
- <Button
270
- size="icon"
271
- variant="ghost"
272
- onClick={handleCancelEdit}
273
- >
274
- <XIcon size={16} />
275
- </Button>
276
- </div>
277
- </div>
278
- ) : (
279
- consumers.label
280
- )}
281
- <div className="text-muted-foreground text-xs">
282
- {consumers.createdOn}
283
- </div>
284
- </div>
285
- <div className="text-muted-foreground text-xs">
286
- {consumers.createdOn && (
287
- <div>
288
- Created on{" "}
289
- {new Date(consumers.createdOn).toLocaleDateString()}
290
- </div>
291
- )}
292
- {consumers.expiresOn && (
293
- <div>
294
- Expires on{" "}
295
- {new Date(consumers.expiresOn).toLocaleDateString()}
296
- </div>
297
- )}
298
- </div>
299
- </div>
300
-
301
- <div className="flex justify-end">
302
- {service.updateConsumer && (
303
- <Button
304
- variant="ghost"
305
- onClick={() =>
306
- handleStartEdit(consumers.id, consumers.label)
307
- }
308
- className={cn(
309
- "flex gap-2",
310
- editingConsumerId === consumers.id &&
311
- "opacity-0! pointer-events-none",
312
- )}
313
- disabled={editingConsumerId === consumers.id}
314
- >
315
- <PencilLineIcon size={16} />
316
- <span className="hidden md:block">Edit label</span>
317
- </Button>
318
- )}
319
- {service.rollKey && (
320
- <Dialog>
321
- <DialogTrigger asChild>
322
- <Button
323
- title="Roll this key"
324
- variant="ghost"
325
- disabled={rollKeyMutation.isPending}
326
- className="flex items-center gap-2"
327
- >
328
- <RefreshCwIcon
329
- size={16}
330
- className={
331
- rollKeyMutation.isPending
332
- ? "animate-spin"
333
- : undefined
334
- }
335
- />
336
- <span className="hidden md:block">Roll key</span>
337
- </Button>
338
- </DialogTrigger>
339
- <DialogContent>
340
- <DialogHeader>
341
- <DialogTitle>Roll API Key</DialogTitle>
342
- <DialogDescription>
343
- Are you sure you want to roll this API key?
344
- </DialogDescription>
345
- </DialogHeader>
346
- <DialogFooter>
347
- <DialogClose asChild>
348
- <Button variant="outline">Cancel</Button>
349
- </DialogClose>
350
- <DialogClose asChild>
351
- <Button
352
- onClick={() => {
353
- rollKeyMutation.mutate(consumers.id);
354
- }}
355
- >
356
- Roll Key
357
- </Button>
358
- </DialogClose>
359
- </DialogFooter>
360
- </DialogContent>
361
- </Dialog>
362
- )}
363
- </div>
364
- </CardHeader>
365
- <div className="col-span-full grid-cols-subgrid grid">
366
- <AnimatePresence>
367
- {consumers.apiKeys.map((apiKey) => (
368
- <React.Fragment key={apiKey.id}>
369
- <RevealApiKey
370
- apiKey={apiKey}
371
- onDeleteKey={() => {
372
- deleteKeyMutation.mutate({
373
- consumerId: consumers.id,
374
- keyId: apiKey.id,
375
- });
376
- }}
377
- className={
378
- deleteKeyMutation.variables?.keyId === apiKey.id &&
379
- (deleteKeyMutation.isPending || isFetching)
380
- ? "opacity-10!"
381
- : undefined
382
- }
383
- />
384
- <div className="col-span-full h-px bg-border"></div>
385
- </React.Fragment>
386
- ))}
387
- </AnimatePresence>
388
- </div>
389
- </Card>
390
- ))}
391
- </ul>
392
- )}
393
- </div>
394
- </div>
395
- );
396
- };
397
-
398
- const getTimeAgo = (date: string) => {
399
- const now = new Date();
400
- const created = new Date(date);
401
- const diffInSeconds = Math.floor((now.getTime() - created.getTime()) / 1000);
402
-
403
- const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
404
-
405
- if (diffInSeconds < 60) return rtf.format(-diffInSeconds, "second");
406
- if (diffInSeconds < 3600)
407
- return rtf.format(-Math.floor(diffInSeconds / 60), "minute");
408
- if (diffInSeconds < 86400)
409
- return rtf.format(-Math.floor(diffInSeconds / 3600), "hour");
410
- if (diffInSeconds < 2592000)
411
- return rtf.format(-Math.floor(diffInSeconds / 86400), "day");
412
- if (diffInSeconds < 31536000)
413
- return rtf.format(-Math.floor(diffInSeconds / 2592000), "month");
414
- return rtf.format(-Math.floor(diffInSeconds / 31536000), "year");
415
- };
416
-
417
- const RevealApiKey = ({
418
- apiKey,
419
- onDeleteKey,
420
- className,
421
- }: {
422
- apiKey: ApiKey;
423
- onDeleteKey: () => void;
424
- className?: string;
425
- }) => {
426
- const [revealed, setRevealed] = useState(false);
427
-
428
- const { key, createdOn, expiresOn } = apiKey;
429
- const isExpired = expiresOn && new Date(expiresOn) < new Date();
430
- const daysUntilExpiry = expiresOn
431
- ? Math.ceil(
432
- (new Date(expiresOn).getTime() - Date.now()) / (1000 * 60 * 60 * 24),
433
- )
434
- : Infinity;
435
- const expiresSoon = daysUntilExpiry <= 7 && !isExpired;
436
-
437
- return (
438
- <div className={cn("grid col-span-full grid-cols-subgrid p-6", className)}>
439
- <div className="flex flex-col gap-1">
440
- <Secret
441
- className="max-w-fit w-full"
442
- secret={key}
443
- status={isExpired ? "expired" : expiresSoon ? "expiring" : "active"}
444
- revealed={revealed}
445
- onReveal={setRevealed}
446
- />
447
- <div className="flex gap-1 mt-0.5 text-nowrap">
448
- {createdOn && (
449
- <span className="text-xs text-muted-foreground">
450
- Created {getTimeAgo(createdOn)}.
451
- </span>
452
- )}{" "}
453
- {expiresOn && expiresSoon && (
454
- <span className="text-xs text-primary">
455
- Expires in {daysUntilExpiry}{" "}
456
- {daysUntilExpiry === 1 ? "day" : "days"}.
457
- </span>
458
- )}
459
- {expiresOn && isExpired && (
460
- <span className="text-xs text-primary">
461
- Expired{" "}
462
- {daysUntilExpiry === 0
463
- ? "today."
464
- : `${daysUntilExpiry * -1} days ago.`}
465
- </span>
466
- )}
467
- </div>
468
- </div>
469
- <div className="flex justify-end">
470
- {expiresOn && onDeleteKey && (
471
- <Dialog>
472
- <DialogTrigger asChild>
473
- <Button variant="ghost" size="icon">
474
- <TrashIcon size={16} />
475
- </Button>
476
- </DialogTrigger>
477
- <DialogContent>
478
- <DialogHeader>
479
- <DialogTitle>Delete API Key</DialogTitle>
480
- <DialogDescription>
481
- Are you sure you want to delete this API key?
482
- </DialogDescription>
483
- </DialogHeader>
484
- <DialogFooter>
485
- <DialogClose asChild>
486
- <Button variant="outline">Cancel</Button>
487
- </DialogClose>
488
- <DialogClose asChild>
489
- <Button
490
- onClick={() => {
491
- onDeleteKey();
492
- }}
493
- >
494
- Delete
495
- </Button>
496
- </DialogClose>
497
- </DialogFooter>
498
- </DialogContent>
499
- </Dialog>
500
- )}
501
- </div>
502
62
  </div>
503
63
  );
504
64
  };
@@ -1,5 +1,6 @@
1
1
  import { KeyRoundIcon } from "lucide-react";
2
2
  import type { RouteObject } from "react-router";
3
+ import type { ApiKeysOptions } from "../../../config/validators/validate.js";
3
4
  import type { UseAuthReturn } from "../../authentication/hook.js";
4
5
  import type {
5
6
  ApiIdentityPlugin,
@@ -7,9 +8,7 @@ import type {
7
8
  ZudokuPlugin,
8
9
  } from "../../core/plugins.js";
9
10
  import type { ZudokuContext } from "../../core/ZudokuContext.js";
10
- import { RouterError } from "../../errors/RouterError.js";
11
11
  import invariant from "../../util/invariant.js";
12
- import { ProtectedRoute } from "./ProtectedRoute.js";
13
12
  import { SettingsApiKeys } from "./SettingsApiKeys.js";
14
13
 
15
14
  const DEFAULT_API_KEY_ENDPOINT = "https://api.zuploedge.com/v2/client";
@@ -154,6 +153,9 @@ const createDefaultHandler = (
154
153
  id: string;
155
154
  label?: string;
156
155
  subject?: string;
156
+ createdOn?: string;
157
+ updatedOn?: string;
158
+ expiresOn?: string;
157
159
  apiKeys: {
158
160
  data: ApiKey[];
159
161
  };
@@ -163,6 +165,9 @@ const createDefaultHandler = (
163
165
 
164
166
  return data.data.map((consumer) => ({
165
167
  id: consumer.id,
168
+ createdOn: consumer.createdOn,
169
+ updatedOn: consumer.updatedOn,
170
+ expiresOn: consumer.expiresOn,
166
171
  label: consumer.label || consumer.subject || "API Key",
167
172
  apiKeys: consumer.apiKeys.data,
168
173
  key: consumer.apiKeys.data.at(0),
@@ -175,13 +180,24 @@ const createDefaultHandler = (
175
180
  export const createApiKeyService = <T extends ApiKeyService>(service: T): T =>
176
181
  service;
177
182
 
178
- export const apiKeyPlugin = (
179
- options: ApiKeyPluginOptions,
180
- ): ZudokuPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {
181
- const service: ApiKeyService =
182
- "deploymentName" in options
183
- ? createDefaultHandler(options.deploymentName, options)
184
- : options;
183
+ export const apiKeyPlugin = ({
184
+ deploymentName,
185
+ ...options
186
+ }: Omit<ApiKeysOptions, "enabled"> & {
187
+ deploymentName?: string;
188
+ }): ZudokuPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {
189
+ const service = deploymentName
190
+ ? createDefaultHandler(deploymentName, { deploymentName, ...options })
191
+ : options;
192
+
193
+ if (!service.getConsumers) {
194
+ throw new Error("getConsumers is required when using the apiKeyPlugin");
195
+ }
196
+
197
+ const verifiedService: ApiKeyService = {
198
+ ...service,
199
+ getConsumers: service.getConsumers,
200
+ };
185
201
 
186
202
  return {
187
203
  getProfileMenuItems: () => [
@@ -195,7 +211,7 @@ export const apiKeyPlugin = (
195
211
 
196
212
  getIdentities: async (context) => {
197
213
  try {
198
- const consumers = await service.getConsumers(context);
214
+ const consumers = await verifiedService.getConsumers(context);
199
215
 
200
216
  return consumers.map((consumer) => ({
201
217
  authorizeRequest: (request) => {
@@ -212,19 +228,18 @@ export const apiKeyPlugin = (
212
228
  return [];
213
229
  }
214
230
  },
231
+
215
232
  getRoutes: (): RouteObject[] => {
216
233
  return [
217
234
  {
218
- element: <ProtectedRoute />,
219
- errorElement: <RouterError />,
220
- children: [
221
- {
222
- path: "/settings/api-keys",
223
- element: <SettingsApiKeys service={service} />,
224
- },
225
- ],
235
+ path: "/settings/api-keys",
236
+ element: <SettingsApiKeys service={verifiedService} />,
226
237
  },
227
238
  ];
228
239
  },
240
+
241
+ getProtectedRoutes: () => {
242
+ return ["/settings/api-keys"];
243
+ },
229
244
  };
230
245
  };