zudoku 0.66.2 → 0.66.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.
Files changed (206) 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/components/SignIn.js +4 -2
  9. package/dist/lib/authentication/components/SignIn.js.map +1 -1
  10. package/dist/lib/authentication/components/SignUp.js +4 -2
  11. package/dist/lib/authentication/components/SignUp.js.map +1 -1
  12. package/dist/lib/authentication/hook.d.ts +2 -0
  13. package/dist/lib/authentication/hook.js +10 -0
  14. package/dist/lib/authentication/hook.js.map +1 -1
  15. package/dist/lib/authentication/providers/firebase.js +67 -9
  16. package/dist/lib/authentication/providers/firebase.js.map +1 -1
  17. package/dist/lib/authentication/ui/EmailVerificationUi.d.ts +4 -0
  18. package/dist/lib/authentication/ui/EmailVerificationUi.js +34 -0
  19. package/dist/lib/authentication/ui/EmailVerificationUi.js.map +1 -0
  20. package/dist/lib/authentication/ui/ZudokuAuthUi.d.ts +7 -2
  21. package/dist/lib/authentication/ui/ZudokuAuthUi.js +43 -11
  22. package/dist/lib/authentication/ui/ZudokuAuthUi.js.map +1 -1
  23. package/dist/lib/authentication/utils/relativeRedirectUrl.d.ts +1 -0
  24. package/dist/lib/authentication/utils/relativeRedirectUrl.js +8 -0
  25. package/dist/lib/authentication/utils/relativeRedirectUrl.js.map +1 -0
  26. package/dist/lib/components/index.d.ts +2 -0
  27. package/dist/lib/errors/ErrorMessage.d.ts +3 -0
  28. package/dist/lib/errors/ErrorMessage.js +16 -0
  29. package/dist/lib/errors/ErrorMessage.js.map +1 -0
  30. package/dist/lib/hooks/index.d.ts +2 -0
  31. package/dist/lib/oas/graphql/index.js +7 -3
  32. package/dist/lib/oas/graphql/index.js.map +1 -1
  33. package/dist/lib/plugins/api-keys/SettingsApiKeys.js +9 -172
  34. package/dist/lib/plugins/api-keys/SettingsApiKeys.js.map +1 -1
  35. package/dist/lib/plugins/api-keys/index.d.ts +4 -1
  36. package/dist/lib/plugins/api-keys/index.js +21 -17
  37. package/dist/lib/plugins/api-keys/index.js.map +1 -1
  38. package/dist/lib/plugins/api-keys/settings/ApiKeyItem.d.ts +12 -0
  39. package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js +133 -0
  40. package/dist/lib/plugins/api-keys/settings/ApiKeyItem.js.map +1 -0
  41. package/dist/lib/plugins/api-keys/settings/ApiKeyList.d.ts +4 -0
  42. package/dist/lib/plugins/api-keys/settings/ApiKeyList.js +30 -0
  43. package/dist/lib/plugins/api-keys/settings/ApiKeyList.js.map +1 -0
  44. package/dist/lib/plugins/api-keys/settings/RevealApiKey.d.ts +6 -0
  45. package/dist/lib/plugins/api-keys/settings/RevealApiKey.js +39 -0
  46. package/dist/lib/plugins/api-keys/settings/RevealApiKey.js.map +1 -0
  47. package/dist/lib/plugins/openapi/ParamInfos.js +1 -0
  48. package/dist/lib/plugins/openapi/ParamInfos.js.map +1 -1
  49. package/dist/lib/plugins/openapi/Sidecar.js +3 -2
  50. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  51. package/dist/lib/plugins/openapi/schema/SchemaView.js +1 -1
  52. package/dist/lib/plugins/openapi/schema/SchemaView.js.map +1 -1
  53. package/dist/lib/plugins/openapi/util/createHttpSnippet.js +24 -1
  54. package/dist/lib/plugins/openapi/util/createHttpSnippet.js.map +1 -1
  55. package/dist/lib/ui/Button.js +1 -1
  56. package/dist/lib/ui/Button.js.map +1 -1
  57. package/dist/vite/config.js +7 -0
  58. package/dist/vite/config.js.map +1 -1
  59. package/dist/vite/plugin-api-keys.js +4 -1
  60. package/dist/vite/plugin-api-keys.js.map +1 -1
  61. package/dist/vite/plugin-markdown-export.js +1 -1
  62. package/dist/vite/plugin-markdown-export.js.map +1 -1
  63. package/dist/vite/prerender/worker.js +3 -0
  64. package/dist/vite/prerender/worker.js.map +1 -1
  65. package/dist/vite/zuplo.d.ts +13 -0
  66. package/dist/vite/zuplo.js +15 -0
  67. package/dist/vite/zuplo.js.map +1 -0
  68. package/lib/{ActionButton-DUgvSylL.js → ActionButton-BSM2oNHF.js} +2 -2
  69. package/lib/{ActionButton-DUgvSylL.js.map → ActionButton-BSM2oNHF.js.map} +1 -1
  70. package/lib/{Button-CynVW1JV.js → Button-IOAeVaIH.js} +7 -6
  71. package/lib/{Button-CynVW1JV.js.map → Button-IOAeVaIH.js.map} +1 -1
  72. package/lib/{ClaudeLogo-CGRfGTk2.js → ClaudeLogo-DgjivS8A.js} +3 -3
  73. package/lib/{ClaudeLogo-CGRfGTk2.js.map → ClaudeLogo-DgjivS8A.js.map} +1 -1
  74. package/lib/{Drawer-Ci7XwhqT.js → Drawer-BRMcpfuF.js} +6 -6
  75. package/lib/{Drawer-Ci7XwhqT.js.map → Drawer-BRMcpfuF.js.map} +1 -1
  76. package/lib/Frame-DxlznfVd.js +205 -0
  77. package/lib/Frame-DxlznfVd.js.map +1 -0
  78. package/lib/{IndexingDialog-B5zCiUKr.js → IndexingDialog-DZWj_3cU.js} +2 -2
  79. package/lib/{IndexingDialog-B5zCiUKr.js.map → IndexingDialog-DZWj_3cU.js.map} +1 -1
  80. package/lib/{useMutation-C6RqWmTS.js → Input-D-kqEQ5M.js} +41 -23
  81. package/lib/Input-D-kqEQ5M.js.map +1 -0
  82. package/lib/{MdxPage-Bjf72BP3.js → MdxPage-BL-HbZrv.js} +9 -9
  83. package/lib/{MdxPage-Bjf72BP3.js.map → MdxPage-BL-HbZrv.js.map} +1 -1
  84. package/lib/{Mermaid-D_VSX7_Q.js → Mermaid-BjSczjLW.js} +3 -3
  85. package/lib/{Mermaid-D_VSX7_Q.js.map → Mermaid-BjSczjLW.js.map} +1 -1
  86. package/lib/{OAuthErrorPage-1Ekji0PK.js → OAuthErrorPage-DQtg28Go.js} +20 -21
  87. package/lib/{OAuthErrorPage-1Ekji0PK.js.map → OAuthErrorPage-DQtg28Go.js.map} +1 -1
  88. package/lib/{OasProvider-BZxmTyMM.js → OasProvider--qcZwrKS.js} +4 -4
  89. package/lib/{OasProvider-BZxmTyMM.js.map → OasProvider--qcZwrKS.js.map} +1 -1
  90. package/lib/{OperationList-B7nPIFB8.js → OperationList-CSJYzxQY.js} +1101 -1087
  91. package/lib/{OperationList-B7nPIFB8.js.map → OperationList-CSJYzxQY.js.map} +1 -1
  92. package/lib/{RouteGuard-9wjejsKm.js → RouteGuard-D0f743SM.js} +5 -5
  93. package/lib/{RouteGuard-9wjejsKm.js.map → RouteGuard-D0f743SM.js.map} +1 -1
  94. package/lib/{SchemaList-16_obkku.js → SchemaList-DtyuDrQA.js} +8 -8
  95. package/lib/{SchemaList-16_obkku.js.map → SchemaList-DtyuDrQA.js.map} +1 -1
  96. package/lib/SchemaView-G-SVXxAG.js +435 -0
  97. package/lib/SchemaView-G-SVXxAG.js.map +1 -0
  98. package/lib/{Select-CkxXP5I7.js → Secret-BxGpIhDP.js} +121 -121
  99. package/lib/Secret-BxGpIhDP.js.map +1 -0
  100. package/lib/SignUp-CDl7bQj3.js +50 -0
  101. package/lib/SignUp-CDl7bQj3.js.map +1 -0
  102. package/lib/{SyntaxHighlight-j_HRSPCU.js → SyntaxHighlight-Dgd0AaaX.js} +2 -2
  103. package/lib/{SyntaxHighlight-j_HRSPCU.js.map → SyntaxHighlight-Dgd0AaaX.js.map} +1 -1
  104. package/lib/{Toc-z05x698-.js → Toc-D_Rj4jVx.js} +2 -2
  105. package/lib/{Toc-z05x698-.js.map → Toc-D_Rj4jVx.js.map} +1 -1
  106. package/lib/{ZudokuContext-BXldanA8.js → ZudokuContext-DNHMZfcP.js} +33 -33
  107. package/lib/{ZudokuContext-BXldanA8.js.map → ZudokuContext-DNHMZfcP.js.map} +1 -1
  108. package/lib/{chunk-PVWAREVJ-dLIqswPy.js → chunk-PVWAREVJ-ClM0m2aJ.js} +19 -19
  109. package/lib/{chunk-PVWAREVJ-dLIqswPy.js.map → chunk-PVWAREVJ-ClM0m2aJ.js.map} +1 -1
  110. package/lib/{circular-D5sYCIWL.js → circular-BxODTa7z.js} +2 -2
  111. package/lib/{circular-D5sYCIWL.js.map → circular-BxODTa7z.js.map} +1 -1
  112. package/lib/{createServer-BlwU7lIr.js → createServer-BpreIXp6.js} +10 -10
  113. package/lib/{createServer-BlwU7lIr.js.map → createServer-BpreIXp6.js.map} +1 -1
  114. package/lib/createVariantComponent-CQVt-H3r.js +18 -0
  115. package/lib/createVariantComponent-CQVt-H3r.js.map +1 -0
  116. package/lib/{errors-BtC4Kn2j.js → errors-DliW1dED.js} +2 -2
  117. package/lib/{errors-BtC4Kn2j.js.map → errors-DliW1dED.js.map} +1 -1
  118. package/lib/{firebase-Ibm_tv3G.js → firebase-D4tbaCYB.js} +1588 -1342
  119. package/lib/firebase-D4tbaCYB.js.map +1 -0
  120. package/lib/hook-CHw_R_xu.js +52 -0
  121. package/lib/hook-CHw_R_xu.js.map +1 -0
  122. package/lib/{index-eKVhlB94.js → index-1TbL0HXQ.js} +2 -2
  123. package/lib/{index-eKVhlB94.js.map → index-1TbL0HXQ.js.map} +1 -1
  124. package/lib/{index-CeVTNcfF.js → index-9MxNUgg4.js} +99 -100
  125. package/lib/{index-CeVTNcfF.js.map → index-9MxNUgg4.js.map} +1 -1
  126. package/lib/{ErrorAlert-BUlG32M9.js → index-CboxZOVW.js} +5373 -4335
  127. package/lib/index-CboxZOVW.js.map +1 -0
  128. package/lib/index-CrcNWbel.js.map +1 -1
  129. package/lib/{index-Css56y3F.js → index-DXXZDuSJ.js} +4 -4
  130. package/lib/{index-Css56y3F.js.map → index-DXXZDuSJ.js.map} +1 -1
  131. package/lib/index.esm-BYObtETB.js.map +1 -1
  132. package/lib/index.esm-DtzT_KoE.js.map +1 -1
  133. package/lib/{index.esm-BoKBnRoT.js → index.esm-ti5zvZS_.js} +16 -14
  134. package/lib/index.esm-ti5zvZS_.js.map +1 -0
  135. package/lib/jsx-runtime-BzflLqGi.js.map +1 -1
  136. package/lib/{mutation-BoVlx8yA.js → mutation-DMHWqmFp.js} +2 -2
  137. package/lib/{mutation-BoVlx8yA.js.map → mutation-DMHWqmFp.js.map} +1 -1
  138. package/lib/ui/ActionButton.js +1 -1
  139. package/lib/ui/Button.js +6 -5
  140. package/lib/ui/Button.js.map +1 -1
  141. package/lib/ui/Carousel.js.map +1 -1
  142. package/lib/ui/Drawer.js +2 -2
  143. package/lib/ui/SyntaxHighlight.js +2 -2
  144. package/lib/zudoku.__internal.js +507 -479
  145. package/lib/zudoku.__internal.js.map +1 -1
  146. package/lib/zudoku.auth-auth0.js +1 -1
  147. package/lib/zudoku.auth-azureb2c.js +4 -4
  148. package/lib/zudoku.auth-clerk.js +2 -2
  149. package/lib/zudoku.auth-firebase.js +6 -5
  150. package/lib/zudoku.auth-firebase.js.map +1 -1
  151. package/lib/zudoku.auth-openid.js +4 -4
  152. package/lib/zudoku.auth-supabase.js +5 -5
  153. package/lib/zudoku.components.js +20 -21
  154. package/lib/zudoku.components.js.map +1 -1
  155. package/lib/zudoku.hooks.js +3 -3
  156. package/lib/zudoku.mermaid.js +3 -3
  157. package/lib/zudoku.plugin-api-catalog.js +8 -9
  158. package/lib/zudoku.plugin-api-catalog.js.map +1 -1
  159. package/lib/zudoku.plugin-api-keys.js +579 -544
  160. package/lib/zudoku.plugin-api-keys.js.map +1 -1
  161. package/lib/zudoku.plugin-custom-pages.js +1 -1
  162. package/lib/zudoku.plugin-markdown.js +1 -1
  163. package/lib/zudoku.plugin-openapi.js +3 -3
  164. package/lib/zudoku.plugin-redirect.js +1 -1
  165. package/lib/zudoku.plugin-search-pagefind.js +5 -5
  166. package/lib/zudoku.router.js +2 -2
  167. package/lib/zudoku.router.js.map +1 -1
  168. package/package.json +7 -5
  169. package/src/lib/auth/issuer.ts +1 -1
  170. package/src/lib/authentication/authentication.ts +8 -2
  171. package/src/lib/authentication/components/SignIn.tsx +5 -2
  172. package/src/lib/authentication/components/SignUp.tsx +5 -2
  173. package/src/lib/authentication/hook.ts +16 -0
  174. package/src/lib/authentication/providers/firebase.tsx +98 -6
  175. package/src/lib/authentication/ui/EmailVerificationUi.tsx +129 -0
  176. package/src/lib/authentication/ui/ZudokuAuthUi.tsx +170 -38
  177. package/src/lib/authentication/utils/relativeRedirectUrl.ts +12 -0
  178. package/src/lib/errors/ErrorMessage.tsx +38 -0
  179. package/src/lib/oas/graphql/index.ts +7 -3
  180. package/src/lib/plugins/api-keys/SettingsApiKeys.tsx +36 -476
  181. package/src/lib/plugins/api-keys/index.tsx +35 -21
  182. package/src/lib/plugins/api-keys/settings/ApiKeyItem.tsx +342 -0
  183. package/src/lib/plugins/api-keys/settings/ApiKeyList.tsx +64 -0
  184. package/src/lib/plugins/api-keys/settings/RevealApiKey.tsx +124 -0
  185. package/src/lib/plugins/openapi/ParamInfos.tsx +1 -0
  186. package/src/lib/plugins/openapi/Sidecar.tsx +3 -2
  187. package/src/lib/plugins/openapi/schema/SchemaView.tsx +6 -4
  188. package/src/lib/plugins/openapi/util/createHttpSnippet.ts +29 -1
  189. package/src/lib/ui/Button.tsx +1 -0
  190. package/lib/ErrorAlert-BUlG32M9.js.map +0 -1
  191. package/lib/RouterError-DfTZblpv.js +0 -42
  192. package/lib/RouterError-DfTZblpv.js.map +0 -1
  193. package/lib/SchemaView-eyvR4bRt.js +0 -597
  194. package/lib/SchemaView-eyvR4bRt.js.map +0 -1
  195. package/lib/Select-CkxXP5I7.js.map +0 -1
  196. package/lib/SignUp-D54_QWFy.js +0 -50
  197. package/lib/SignUp-D54_QWFy.js.map +0 -1
  198. package/lib/createVariantComponent-B9_dVBvu.js +0 -35
  199. package/lib/createVariantComponent-B9_dVBvu.js.map +0 -1
  200. package/lib/firebase-Ibm_tv3G.js.map +0 -1
  201. package/lib/hook-BNxidGQq.js +0 -40
  202. package/lib/hook-BNxidGQq.js.map +0 -1
  203. package/lib/index-DSOi7zVM.js +0 -1059
  204. package/lib/index-DSOi7zVM.js.map +0 -1
  205. package/lib/index.esm-BoKBnRoT.js.map +0 -1
  206. 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";
@@ -93,8 +92,7 @@ const createDefaultHandler = (
93
92
  method: "DELETE",
94
93
  },
95
94
  );
96
- await context.signRequest(request);
97
- const response = await fetch(request);
95
+ const response = await fetch(await context.signRequest(request));
98
96
  await throwIfProblemJson(response);
99
97
  invariant(response.ok, "Failed to delete API key");
100
98
  },
@@ -136,7 +134,7 @@ const createDefaultHandler = (
136
134
  ),
137
135
  );
138
136
  await throwIfProblemJson(response);
139
- invariant(response.ok, "Failed to delete API key");
137
+ invariant(response.ok, "Failed to roll API key");
140
138
  },
141
139
  getConsumers: async (context) => {
142
140
  const request = new Request(
@@ -154,6 +152,9 @@ const createDefaultHandler = (
154
152
  id: string;
155
153
  label?: string;
156
154
  subject?: string;
155
+ createdOn?: string;
156
+ updatedOn?: string;
157
+ expiresOn?: string;
157
158
  apiKeys: {
158
159
  data: ApiKey[];
159
160
  };
@@ -163,6 +164,9 @@ const createDefaultHandler = (
163
164
 
164
165
  return data.data.map((consumer) => ({
165
166
  id: consumer.id,
167
+ createdOn: consumer.createdOn,
168
+ updatedOn: consumer.updatedOn,
169
+ expiresOn: consumer.expiresOn,
166
170
  label: consumer.label || consumer.subject || "API Key",
167
171
  apiKeys: consumer.apiKeys.data,
168
172
  key: consumer.apiKeys.data.at(0),
@@ -175,13 +179,24 @@ const createDefaultHandler = (
175
179
  export const createApiKeyService = <T extends ApiKeyService>(service: T): T =>
176
180
  service;
177
181
 
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;
182
+ export const apiKeyPlugin = ({
183
+ deploymentName,
184
+ ...options
185
+ }: Omit<ApiKeysOptions, "enabled"> & {
186
+ deploymentName?: string;
187
+ }): ZudokuPlugin & ApiIdentityPlugin & ProfileMenuPlugin => {
188
+ const service = deploymentName
189
+ ? createDefaultHandler(deploymentName, { deploymentName, ...options })
190
+ : options;
191
+
192
+ if (!service.getConsumers) {
193
+ throw new Error("getConsumers is required when using the apiKeyPlugin");
194
+ }
195
+
196
+ const verifiedService: ApiKeyService = {
197
+ ...service,
198
+ getConsumers: service.getConsumers,
199
+ };
185
200
 
186
201
  return {
187
202
  getProfileMenuItems: () => [
@@ -195,7 +210,7 @@ export const apiKeyPlugin = (
195
210
 
196
211
  getIdentities: async (context) => {
197
212
  try {
198
- const consumers = await service.getConsumers(context);
213
+ const consumers = await verifiedService.getConsumers(context);
199
214
 
200
215
  return consumers.map((consumer) => ({
201
216
  authorizeRequest: (request) => {
@@ -212,19 +227,18 @@ export const apiKeyPlugin = (
212
227
  return [];
213
228
  }
214
229
  },
230
+
215
231
  getRoutes: (): RouteObject[] => {
216
232
  return [
217
233
  {
218
- element: <ProtectedRoute />,
219
- errorElement: <RouterError />,
220
- children: [
221
- {
222
- path: "/settings/api-keys",
223
- element: <SettingsApiKeys service={service} />,
224
- },
225
- ],
234
+ path: "/settings/api-keys",
235
+ element: <SettingsApiKeys service={verifiedService} />,
226
236
  },
227
237
  ];
228
238
  },
239
+
240
+ getProtectedRoutes: () => {
241
+ return ["/settings/api-keys"];
242
+ },
229
243
  };
230
244
  };