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
@@ -0,0 +1,342 @@
1
+ import { useMutation, useQueryClient } from "@tanstack/react-query";
2
+ import {
3
+ CheckIcon,
4
+ CircleSlashIcon,
5
+ PencilLineIcon,
6
+ RefreshCwIcon,
7
+ XIcon,
8
+ } from "lucide-react";
9
+ import { AnimatePresence } from "motion/react";
10
+ import { useState } from "react";
11
+ import { Alert, AlertTitle } from "zudoku/ui/Alert.js";
12
+ import { Button } from "zudoku/ui/Button.js";
13
+ import {
14
+ Dialog,
15
+ DialogClose,
16
+ DialogContent,
17
+ DialogDescription,
18
+ DialogFooter,
19
+ DialogHeader,
20
+ DialogTitle,
21
+ DialogTrigger,
22
+ } from "zudoku/ui/Dialog.js";
23
+ import {
24
+ Frame,
25
+ FrameDescription,
26
+ FrameHeader,
27
+ FramePanel,
28
+ FrameTitle,
29
+ } from "zudoku/ui/Frame.js";
30
+ import { Input } from "zudoku/ui/Input.js";
31
+ import { useZudoku } from "../../../components/context/ZudokuContext.js";
32
+ import type { ZudokuContext } from "../../../core/ZudokuContext.js";
33
+ import { cn } from "../../../util/cn.js";
34
+ import type { ApiConsumer } from "../index.js";
35
+ import { RevealApiKey } from "./RevealApiKey.js";
36
+
37
+ const ApiKeyItem = ({
38
+ consumer,
39
+ onUpdate,
40
+ onRollKey,
41
+ onDeleteKey,
42
+ }: {
43
+ consumer: ApiConsumer;
44
+ onUpdate?: (
45
+ data: { label: string; id: string },
46
+ context: ZudokuContext,
47
+ ) => Promise<void>;
48
+ onRollKey?: (consumerId: string, context: ZudokuContext) => Promise<void>;
49
+ onDeleteKey?: (
50
+ consumerId: string,
51
+ keyId: string,
52
+ context: ZudokuContext,
53
+ ) => Promise<void>;
54
+ }) => {
55
+ const [isEditing, setIsEditing] = useState(false);
56
+ const [editingLabel, setEditingLabel] = useState(consumer.label);
57
+ const queryClient = useQueryClient();
58
+ const context = useZudoku();
59
+
60
+ const rollKeyMutation = useMutation({
61
+ mutationFn: async (id: string) => {
62
+ if (!onRollKey) {
63
+ throw new Error("rollKey not implemented");
64
+ }
65
+
66
+ return await onRollKey?.(id, context);
67
+ },
68
+ onSuccess: () =>
69
+ void queryClient.invalidateQueries({ queryKey: ["api-keys"] }),
70
+ });
71
+
72
+ const deleteKeyMutation = useMutation({
73
+ mutationFn: ({
74
+ consumerId,
75
+ keyId,
76
+ }: {
77
+ consumerId: string;
78
+ keyId: string;
79
+ }) => {
80
+ if (!onDeleteKey) {
81
+ throw new Error("deleteKey not implemented");
82
+ }
83
+
84
+ return onDeleteKey(consumerId, keyId, context);
85
+ },
86
+ onMutate: async ({ consumerId, keyId }) => {
87
+ await queryClient.cancelQueries({ queryKey: ["api-keys"] });
88
+ const previousData = queryClient.getQueryData<ApiConsumer[]>([
89
+ "api-keys",
90
+ ]);
91
+ queryClient.setQueryData<ApiConsumer[]>(["api-keys"], (old) => {
92
+ if (!old) {
93
+ return old;
94
+ }
95
+
96
+ return old.map((consumer) => {
97
+ if (consumer.id === consumerId) {
98
+ return {
99
+ ...consumer,
100
+ apiKeys: consumer.apiKeys.filter((key) => key.id !== keyId),
101
+ };
102
+ }
103
+ return consumer;
104
+ });
105
+ });
106
+
107
+ return { previousData };
108
+ },
109
+ onError: (_err, _variables, context) => {
110
+ if (context?.previousData) {
111
+ queryClient.setQueryData(["api-keys"], context.previousData);
112
+ }
113
+ },
114
+ onSuccess: () => {
115
+ void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
116
+ },
117
+ });
118
+
119
+ const updateConsumerMutation = useMutation({
120
+ mutationFn: ({
121
+ consumerId,
122
+ label,
123
+ }: {
124
+ consumerId: string;
125
+ label: string;
126
+ }) => {
127
+ if (!onUpdate) {
128
+ throw new Error("updateConsumer not implemented");
129
+ }
130
+
131
+ return onUpdate({ id: consumerId, label }, context);
132
+ },
133
+ onMutate: async ({ consumerId, label }) => {
134
+ await queryClient.cancelQueries({ queryKey: ["api-keys"] });
135
+
136
+ const previousData = queryClient.getQueryData(["api-keys"]);
137
+ queryClient.setQueryData<ApiConsumer[]>(["api-keys"], (old) => {
138
+ if (!old) {
139
+ return old;
140
+ }
141
+
142
+ return old.map((consumer) => {
143
+ if (consumer.id === consumerId) {
144
+ return {
145
+ ...consumer,
146
+ label,
147
+ };
148
+ }
149
+ return consumer;
150
+ });
151
+ });
152
+
153
+ return { previousData };
154
+ },
155
+ onError: (_err, _variables, context) => {
156
+ if (context?.previousData) {
157
+ queryClient.setQueryData(["api-keys"], context.previousData);
158
+ }
159
+ },
160
+ onSuccess: () => {
161
+ void queryClient.invalidateQueries({ queryKey: ["api-keys"] });
162
+ },
163
+ });
164
+
165
+ const handleStartEdit = () => {
166
+ setIsEditing(true);
167
+ setEditingLabel(consumer.label);
168
+ };
169
+
170
+ const handleSaveEdit = () => {
171
+ if (editingLabel.trim()) {
172
+ updateConsumerMutation.mutate({
173
+ label: editingLabel.trim(),
174
+ consumerId: consumer.id,
175
+ });
176
+ }
177
+ setIsEditing(false);
178
+ };
179
+
180
+ return (
181
+ <>
182
+ {rollKeyMutation.isError && (
183
+ <Alert variant="destructive" className="mb-4">
184
+ <CircleSlashIcon size={16} />
185
+ <AlertTitle>{rollKeyMutation.error.message}</AlertTitle>
186
+ </Alert>
187
+ )}
188
+ {updateConsumerMutation.isError && (
189
+ <Alert variant="destructive" className="mb-4">
190
+ <CircleSlashIcon size={16} />
191
+ <AlertTitle>{updateConsumerMutation.error.message}</AlertTitle>
192
+ </Alert>
193
+ )}
194
+ {deleteKeyMutation.isError && (
195
+ <Alert variant="destructive" className="mb-4">
196
+ <CircleSlashIcon size={16} />
197
+ <AlertTitle>{deleteKeyMutation.error.message}</AlertTitle>
198
+ </Alert>
199
+ )}
200
+ <Frame
201
+ className="grid grid-cols-subgrid col-span-full items-center mb-4 group"
202
+ key={consumer.id}
203
+ >
204
+ <FrameHeader className="col-span-full flex-row items-start justify-between gap-4">
205
+ <div className="flex flex-col gap-1">
206
+ {isEditing ? (
207
+ <div className="flex items-center gap-2">
208
+ <Input
209
+ maxLength={32}
210
+ value={editingLabel}
211
+ onChange={(e) => setEditingLabel(e.target.value)}
212
+ onKeyDown={(e) => {
213
+ if (e.key === "Enter") {
214
+ handleSaveEdit();
215
+ } else if (e.key === "Escape") {
216
+ setIsEditing(false);
217
+ }
218
+ }}
219
+ autoFocus
220
+ />
221
+ <div className="flex items-center">
222
+ <Button
223
+ size="icon"
224
+ variant="ghost"
225
+ onClick={handleSaveEdit}
226
+ disabled={!editingLabel.trim()}
227
+ >
228
+ <CheckIcon size={16} />
229
+ </Button>
230
+ <Button
231
+ size="icon"
232
+ variant="ghost"
233
+ onClick={() => setIsEditing(false)}
234
+ >
235
+ <XIcon size={16} />
236
+ </Button>
237
+ </div>
238
+ </div>
239
+ ) : (
240
+ <FrameTitle>{consumer.label}</FrameTitle>
241
+ )}
242
+ <FrameDescription>
243
+ {consumer.createdOn && (
244
+ <div>
245
+ Created on {new Date(consumer.createdOn).toLocaleDateString()}
246
+ </div>
247
+ )}
248
+ {consumer.expiresOn && (
249
+ <div>
250
+ Expires on {new Date(consumer.expiresOn).toLocaleDateString()}
251
+ </div>
252
+ )}
253
+ </FrameDescription>
254
+ </div>
255
+
256
+ <div className="flex gap-1">
257
+ {onUpdate && (
258
+ <Button
259
+ variant="ghost"
260
+ onClick={handleStartEdit}
261
+ className={cn(
262
+ "flex gap-2",
263
+ isEditing && "opacity-0! pointer-events-none",
264
+ )}
265
+ disabled={isEditing}
266
+ >
267
+ <PencilLineIcon size={16} />
268
+ <span className="hidden md:block">Edit label</span>
269
+ </Button>
270
+ )}
271
+ {onRollKey && (
272
+ <Dialog>
273
+ <DialogTrigger asChild>
274
+ <Button
275
+ title="Roll this key"
276
+ variant="ghost"
277
+ disabled={rollKeyMutation.isPending}
278
+ className="flex items-center gap-2"
279
+ >
280
+ <RefreshCwIcon
281
+ size={16}
282
+ className={
283
+ rollKeyMutation.isPending ? "animate-spin" : undefined
284
+ }
285
+ />
286
+ <span className="hidden md:block">Roll key</span>
287
+ </Button>
288
+ </DialogTrigger>
289
+ <DialogContent>
290
+ <DialogHeader>
291
+ <DialogTitle>Roll API Key</DialogTitle>
292
+ <DialogDescription>
293
+ Are you sure you want to roll this API key?
294
+ </DialogDescription>
295
+ </DialogHeader>
296
+ <DialogFooter>
297
+ <DialogClose asChild>
298
+ <Button variant="outline">Cancel</Button>
299
+ </DialogClose>
300
+ <DialogClose asChild>
301
+ <Button
302
+ onClick={() => {
303
+ rollKeyMutation.mutate(consumer.id);
304
+ }}
305
+ >
306
+ Roll Key
307
+ </Button>
308
+ </DialogClose>
309
+ </DialogFooter>
310
+ </DialogContent>
311
+ </Dialog>
312
+ )}
313
+ </div>
314
+ </FrameHeader>
315
+ <FramePanel className="p-0 grid grid-cols-subgrid col-span-full divide-y divide-border">
316
+ <AnimatePresence>
317
+ {consumer.apiKeys.map((apiKey) => (
318
+ <RevealApiKey
319
+ key={apiKey.id}
320
+ apiKey={apiKey}
321
+ onDeleteKey={() => {
322
+ deleteKeyMutation.mutate({
323
+ consumerId: consumer.id,
324
+ keyId: apiKey.id,
325
+ });
326
+ }}
327
+ className={
328
+ deleteKeyMutation.variables?.keyId === apiKey.id &&
329
+ deleteKeyMutation.isPending
330
+ ? "opacity-10!"
331
+ : undefined
332
+ }
333
+ />
334
+ ))}
335
+ </AnimatePresence>
336
+ </FramePanel>
337
+ </Frame>
338
+ </>
339
+ );
340
+ };
341
+
342
+ export default ApiKeyItem;
@@ -0,0 +1,64 @@
1
+ import { useSuspenseQuery } from "@tanstack/react-query";
2
+ import { useState } from "react";
3
+ import { useZudoku } from "../../../components/context/ZudokuContext.js";
4
+ import { cn } from "../../../util/cn.js";
5
+ import { ZudokuError } from "../../../util/invariant.js";
6
+ import { CreateApiKeyDialog } from "../CreateApiKeyDialog.js";
7
+ import type { ApiKeyService } from "../index.js";
8
+ import ApiKeyItem from "./ApiKeyItem.js";
9
+
10
+ export const ApiKeyList = ({ service }: { service: ApiKeyService }) => {
11
+ const context = useZudoku();
12
+
13
+ const { data } = useSuspenseQuery({
14
+ queryFn: async () => {
15
+ try {
16
+ return await service.getConsumers(context);
17
+ } catch (error) {
18
+ throw new ZudokuError("Cannot get API keys", {
19
+ cause: error,
20
+ title: "Error getting API keys",
21
+ developerHint:
22
+ "Check the response of the API request for more information.",
23
+ });
24
+ }
25
+ },
26
+ queryKey: ["api-keys"],
27
+ retry: false,
28
+ });
29
+
30
+ const [isCreateApiKeyOpen, setIsCreateApiKeyOpen] = useState(false);
31
+
32
+ return (
33
+ <div className="mt-8">
34
+ {data.length === 0 ? (
35
+ <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">
36
+ <p className="text-center">
37
+ You have no API keys yet.
38
+ <br />
39
+ {service.createKey && "Get started and create your first key."}
40
+ </p>
41
+ {service.createKey && (
42
+ <CreateApiKeyDialog
43
+ service={service}
44
+ isOpen={isCreateApiKeyOpen}
45
+ onOpenChange={setIsCreateApiKeyOpen}
46
+ />
47
+ )}
48
+ </div>
49
+ ) : (
50
+ <ul className={cn("grid grid-cols-[1fr_min-content] col-span-6")}>
51
+ {data.map((consumer) => (
52
+ <ApiKeyItem
53
+ key={consumer.id}
54
+ consumer={consumer}
55
+ onUpdate={service.updateConsumer}
56
+ onRollKey={service.rollKey}
57
+ onDeleteKey={service.deleteKey}
58
+ />
59
+ ))}
60
+ </ul>
61
+ )}
62
+ </div>
63
+ );
64
+ };
@@ -0,0 +1,124 @@
1
+ import { TrashIcon } from "lucide-react";
2
+ import { useState } from "react";
3
+ import { Button } from "zudoku/ui/Button.js";
4
+ import {
5
+ Dialog,
6
+ DialogClose,
7
+ DialogContent,
8
+ DialogDescription,
9
+ DialogFooter,
10
+ DialogHeader,
11
+ DialogTitle,
12
+ DialogTrigger,
13
+ } from "zudoku/ui/Dialog.js";
14
+ import { Secret } from "zudoku/ui/Secret.js";
15
+ import { cn } from "../../../util/cn.js";
16
+ import type { ApiKey } from "../index.js";
17
+
18
+ export const RevealApiKey = ({
19
+ apiKey,
20
+ onDeleteKey,
21
+ className,
22
+ }: {
23
+ apiKey: ApiKey;
24
+ onDeleteKey: () => void;
25
+ className?: string;
26
+ }) => {
27
+ const [revealed, setRevealed] = useState(false);
28
+
29
+ const { key, createdOn, expiresOn } = apiKey;
30
+ const isExpired = expiresOn && new Date(expiresOn) < new Date();
31
+ const daysUntilExpiry = expiresOn
32
+ ? Math.ceil(
33
+ (new Date(expiresOn).getTime() - Date.now()) / (1000 * 60 * 60 * 24),
34
+ )
35
+ : Infinity;
36
+ const expiresSoon = daysUntilExpiry <= 7 && !isExpired;
37
+
38
+ return (
39
+ <div className={cn("grid col-span-full grid-cols-subgrid p-6", className)}>
40
+ <div className="flex flex-col gap-1">
41
+ <Secret
42
+ className="max-w-fit w-full"
43
+ secret={key}
44
+ status={isExpired ? "expired" : expiresSoon ? "expiring" : "active"}
45
+ revealed={revealed}
46
+ onReveal={setRevealed}
47
+ />
48
+ <div className="flex gap-1 mt-0.5 text-nowrap">
49
+ {createdOn && (
50
+ <span className="text-xs text-muted-foreground">
51
+ Created {getTimeAgo(createdOn)}.
52
+ </span>
53
+ )}{" "}
54
+ {expiresOn && expiresSoon && (
55
+ <span className="text-xs text-primary">
56
+ Expires in {daysUntilExpiry}{" "}
57
+ {daysUntilExpiry === 1 ? "day" : "days"}.
58
+ </span>
59
+ )}
60
+ {expiresOn && isExpired && (
61
+ <span className="text-xs text-primary">
62
+ Expired{" "}
63
+ {daysUntilExpiry === 0
64
+ ? "today."
65
+ : `${daysUntilExpiry * -1} days ago.`}
66
+ </span>
67
+ )}
68
+ </div>
69
+ </div>
70
+ <div className="flex justify-end">
71
+ {expiresOn && onDeleteKey && (
72
+ <Dialog>
73
+ <DialogTrigger asChild>
74
+ <Button variant="ghost" size="icon">
75
+ <TrashIcon size={16} />
76
+ </Button>
77
+ </DialogTrigger>
78
+ <DialogContent>
79
+ <DialogHeader>
80
+ <DialogTitle>Delete API Key</DialogTitle>
81
+ <DialogDescription>
82
+ Are you sure you want to delete this API key?
83
+ </DialogDescription>
84
+ </DialogHeader>
85
+ <DialogFooter>
86
+ <DialogClose asChild>
87
+ <Button variant="outline">Cancel</Button>
88
+ </DialogClose>
89
+ <DialogClose asChild>
90
+ <Button
91
+ onClick={() => {
92
+ onDeleteKey();
93
+ }}
94
+ >
95
+ Delete
96
+ </Button>
97
+ </DialogClose>
98
+ </DialogFooter>
99
+ </DialogContent>
100
+ </Dialog>
101
+ )}
102
+ </div>
103
+ </div>
104
+ );
105
+ };
106
+
107
+ const getTimeAgo = (date: string) => {
108
+ const now = new Date();
109
+ const created = new Date(date);
110
+ const diffInSeconds = Math.floor((now.getTime() - created.getTime()) / 1000);
111
+
112
+ const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
113
+
114
+ if (diffInSeconds < 60) return rtf.format(-diffInSeconds, "second");
115
+ if (diffInSeconds < 3600)
116
+ return rtf.format(-Math.floor(diffInSeconds / 60), "minute");
117
+ if (diffInSeconds < 86400)
118
+ return rtf.format(-Math.floor(diffInSeconds / 3600), "hour");
119
+ if (diffInSeconds < 2592000)
120
+ return rtf.format(-Math.floor(diffInSeconds / 86400), "day");
121
+ if (diffInSeconds < 31536000)
122
+ return rtf.format(-Math.floor(diffInSeconds / 2592000), "month");
123
+ return rtf.format(-Math.floor(diffInSeconds / 31536000), "year");
124
+ };
@@ -40,6 +40,7 @@ const getSchemaInfos = (schema?: SchemaObject) => {
40
40
  schema.enum && "enum",
41
41
  schema.const && "const",
42
42
  schema.format,
43
+ schema.type === "array" && schema.items?.contentMediaType,
43
44
  schema.minimum !== undefined && `min: ${schema.minimum}`,
44
45
  schema.maximum !== undefined && `max: ${schema.maximum}`,
45
46
  schema.minLength !== undefined && `minLength: ${schema.minLength}`,
@@ -149,10 +149,10 @@ export const Sidecar = ({
149
149
  selectedServer,
150
150
  exampleBody: currentExampleCode
151
151
  ? {
152
- mimeType: "application/json",
152
+ mimeType: selectedContent?.mediaType ?? "application/json",
153
153
  text: JSON.stringify(currentExampleCode, null, 2),
154
154
  }
155
- : { mimeType: "application/json" },
155
+ : { mimeType: selectedContent?.mediaType ?? "application/json" },
156
156
  });
157
157
 
158
158
  return getConverted(snippet, selectedLang);
@@ -161,6 +161,7 @@ export const Sidecar = ({
161
161
  operation,
162
162
  selectedServer,
163
163
  selectedLang,
164
+ selectedContent,
164
165
  options,
165
166
  auth,
166
167
  context,
@@ -154,10 +154,12 @@ export const SchemaView = ({
154
154
  <FrameDescription>{schema.description}</FrameDescription>
155
155
  </FrameHeader>
156
156
  )}
157
- <FramePanel className="p-0!">
158
- {itemsList}
159
- {additionalObjectProperties}
160
- </FramePanel>
157
+ {(itemsList.length > 0 || additionalObjectProperties) && (
158
+ <FramePanel className="p-0!">
159
+ {itemsList}
160
+ {additionalObjectProperties}
161
+ </FramePanel>
162
+ )}
161
163
  {schema.additionalProperties === true && (
162
164
  <FrameFooter>
163
165
  <a
@@ -1,6 +1,23 @@
1
1
  import { HTTPSnippet } from "@zudoku/httpsnippet";
2
2
  import type { OperationsFragmentFragment } from "../graphql/graphql.js";
3
3
 
4
+ const toFormDataParams = (text?: string) => {
5
+ const stringify = (v: unknown) =>
6
+ typeof v === "string" ? v : JSON.stringify(v);
7
+
8
+ try {
9
+ const obj = text && JSON.parse(text);
10
+ if (typeof obj !== "object" || !obj) return [];
11
+
12
+ return Object.entries(obj).flatMap(([name, value]) => {
13
+ const values = Array.isArray(value) ? value : [value];
14
+ return values.map((v) => ({ name, value: stringify(v) }));
15
+ });
16
+ } catch {
17
+ return [];
18
+ }
19
+ };
20
+
4
21
  export const createHttpSnippet = ({
5
22
  operation,
6
23
  selectedServer,
@@ -13,11 +30,22 @@ export const createHttpSnippet = ({
13
30
  text?: string;
14
31
  };
15
32
  }) => {
33
+ const isMultipart =
34
+ exampleBody.mimeType === "multipart/form-data" ||
35
+ exampleBody.mimeType === "application/x-www-form-urlencoded";
36
+
37
+ const postData = isMultipart
38
+ ? {
39
+ mimeType: exampleBody.mimeType,
40
+ params: toFormDataParams(exampleBody.text),
41
+ }
42
+ : exampleBody;
43
+
16
44
  return new HTTPSnippet({
17
45
  method: operation.method.toUpperCase(),
18
46
  url:
19
47
  selectedServer + operation.path.replaceAll("{", ":").replaceAll("}", ""),
20
- postData: exampleBody,
48
+ postData,
21
49
  headers: [
22
50
  ...(exampleBody.text
23
51
  ? [{ name: "Content-Type", value: exampleBody.mimeType }]
@@ -51,6 +51,7 @@ export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
51
51
  const Comp = asChild ? Slot : "button";
52
52
  return (
53
53
  <Comp
54
+ type={asChild ? undefined : "button"}
54
55
  className={cn(buttonVariants({ variant, size, className }))}
55
56
  ref={ref}
56
57
  {...props}