zudoku 0.1.1-dev.31 → 0.1.1-dev.32

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 (124) hide show
  1. package/dist/app/App.js +6 -1
  2. package/dist/app/App.js.map +1 -1
  3. package/dist/config/config.d.ts +5 -0
  4. package/dist/lib/components/Header.js +1 -1
  5. package/dist/lib/components/Header.js.map +1 -1
  6. package/dist/lib/components/Layout.js +1 -1
  7. package/dist/lib/components/Layout.js.map +1 -1
  8. package/dist/lib/components/SyntaxHighlight.d.ts +1 -0
  9. package/dist/lib/components/SyntaxHighlight.js +1 -1
  10. package/dist/lib/components/SyntaxHighlight.js.map +1 -1
  11. package/dist/lib/components/context/DevPortalProvider.js +1 -1
  12. package/dist/lib/components/context/DevPortalProvider.js.map +1 -1
  13. package/dist/lib/components/navigation/SideNavigation.js +1 -1
  14. package/dist/lib/components/navigation/SideNavigation.js.map +1 -1
  15. package/dist/lib/components/navigation/SideNavigationCategory.js +1 -1
  16. package/dist/lib/components/navigation/SideNavigationCategory.js.map +1 -1
  17. package/dist/lib/components/navigation/SideNavigationItem.d.ts +1 -0
  18. package/dist/lib/components/navigation/SideNavigationItem.js +8 -1
  19. package/dist/lib/components/navigation/SideNavigationItem.js.map +1 -1
  20. package/dist/lib/components/navigation/SideNavigationWrapper.d.ts +3 -0
  21. package/dist/lib/components/navigation/SideNavigationWrapper.js +2 -3
  22. package/dist/lib/components/navigation/SideNavigationWrapper.js.map +1 -1
  23. package/dist/lib/core/DevPortalContext.d.ts +1 -0
  24. package/dist/lib/core/DevPortalContext.js +2 -2
  25. package/dist/lib/core/DevPortalContext.js.map +1 -1
  26. package/dist/lib/core/plugins.d.ts +1 -1
  27. package/dist/lib/core/plugins.js +1 -1
  28. package/dist/lib/core/plugins.js.map +1 -1
  29. package/dist/lib/plugins/api-key/CreateApiKeys.d.ts +5 -0
  30. package/dist/lib/plugins/api-key/CreateApiKeys.js +37 -0
  31. package/dist/lib/plugins/api-key/CreateApiKeys.js.map +1 -0
  32. package/dist/lib/plugins/api-key/SettingsApiKeys.d.ts +3 -2
  33. package/dist/lib/plugins/api-key/SettingsApiKeys.js +30 -4
  34. package/dist/lib/plugins/api-key/SettingsApiKeys.js.map +1 -1
  35. package/dist/lib/plugins/api-key/index.d.ts +18 -21
  36. package/dist/lib/plugins/api-key/index.js +49 -24
  37. package/dist/lib/plugins/api-key/index.js.map +1 -1
  38. package/dist/lib/plugins/openapi/MakeRequest.js +22 -12
  39. package/dist/lib/plugins/openapi/MakeRequest.js.map +1 -1
  40. package/dist/lib/plugins/openapi/OperationListItem.js +24 -1
  41. package/dist/lib/plugins/openapi/OperationListItem.js.map +1 -1
  42. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js +1 -1
  43. package/dist/lib/plugins/openapi/RequestBodySidecarBox.js.map +1 -1
  44. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js +2 -2
  45. package/dist/lib/plugins/openapi/ResponsesSidecarBox.js.map +1 -1
  46. package/dist/lib/plugins/openapi/Sidecar.js +13 -13
  47. package/dist/lib/plugins/openapi/Sidecar.js.map +1 -1
  48. package/dist/lib/plugins/openapi/graphql/gql.d.ts +2 -2
  49. package/dist/lib/plugins/openapi/graphql/gql.js +1 -1
  50. package/dist/lib/plugins/openapi/graphql/gql.js.map +1 -1
  51. package/dist/lib/plugins/openapi/graphql/graphql.d.ts +1 -0
  52. package/dist/lib/plugins/openapi/graphql/graphql.js +4 -0
  53. package/dist/lib/plugins/openapi/graphql/graphql.js.map +1 -1
  54. package/dist/lib/plugins/openapi/index.js +2 -0
  55. package/dist/lib/plugins/openapi/index.js.map +1 -1
  56. package/dist/lib/plugins/openapi/playground/Headers.js +5 -16
  57. package/dist/lib/plugins/openapi/playground/Headers.js.map +1 -1
  58. package/dist/lib/plugins/openapi/playground/InlineInput.js +1 -1
  59. package/dist/lib/plugins/openapi/playground/{UrlParts.d.ts → PathParams.d.ts} +2 -2
  60. package/dist/lib/plugins/openapi/playground/{UrlParts.js → PathParams.js} +4 -4
  61. package/dist/lib/plugins/openapi/playground/PathParams.js.map +1 -0
  62. package/dist/lib/plugins/openapi/playground/Playground.d.ts +7 -4
  63. package/dist/lib/plugins/openapi/playground/Playground.js +48 -46
  64. package/dist/lib/plugins/openapi/playground/Playground.js.map +1 -1
  65. package/dist/lib/plugins/openapi/playground/QueryParams.d.ts +4 -6
  66. package/dist/lib/plugins/openapi/playground/QueryParams.js +27 -22
  67. package/dist/lib/plugins/openapi/playground/QueryParams.js.map +1 -1
  68. package/dist/lib/plugins/openapi/playground/UrlDisplay.d.ts +4 -0
  69. package/dist/lib/plugins/openapi/playground/UrlDisplay.js +22 -0
  70. package/dist/lib/plugins/openapi/playground/UrlDisplay.js.map +1 -0
  71. package/dist/lib/plugins/openapi/playground/createUrl.d.ts +2 -0
  72. package/dist/lib/plugins/openapi/playground/createUrl.js +15 -0
  73. package/dist/lib/plugins/openapi/playground/createUrl.js.map +1 -0
  74. package/dist/lib/plugins/openapi/util/generateSchemaExample.d.ts +1 -1
  75. package/dist/lib/plugins/openapi/util/generateSchemaExample.js +19 -9
  76. package/dist/lib/plugins/openapi/util/generateSchemaExample.js.map +1 -1
  77. package/dist/lib/util/MdxComponents.js +1 -1
  78. package/dist/lib/util/MdxComponents.js.map +1 -1
  79. package/dist/vite/plugin-api-keys.d.ts +4 -0
  80. package/dist/vite/plugin-api-keys.js +33 -0
  81. package/dist/vite/plugin-api-keys.js.map +1 -0
  82. package/dist/vite/plugin-mdx.js +1 -1
  83. package/dist/vite/plugin-mdx.js.map +1 -1
  84. package/dist/vite/plugin.js +2 -0
  85. package/dist/vite/plugin.js.map +1 -1
  86. package/lib/{urql-B7mLfVog.js → urql-DMlBWUKL.js} +301 -321
  87. package/lib/{DevPortal-Dh66z5c3.js → util-CJko6Ria.js} +3661 -4915
  88. package/lib/zudoku.components.js +1313 -4
  89. package/lib/zudoku.openapi-worker.js +1 -1
  90. package/lib/zudoku.plugins.js +16256 -15935
  91. package/package.json +1 -1
  92. package/src/app/App.tsx +6 -1
  93. package/src/lib/components/Header.tsx +2 -2
  94. package/src/lib/components/Layout.tsx +6 -1
  95. package/src/lib/components/SyntaxHighlight.tsx +6 -3
  96. package/src/lib/components/context/DevPortalProvider.ts +1 -1
  97. package/src/lib/components/navigation/SideNavigation.tsx +4 -1
  98. package/src/lib/components/navigation/SideNavigationCategory.tsx +3 -1
  99. package/src/lib/components/navigation/SideNavigationItem.tsx +8 -1
  100. package/src/lib/components/navigation/SideNavigationWrapper.tsx +14 -11
  101. package/src/lib/core/DevPortalContext.ts +3 -2
  102. package/src/lib/core/plugins.ts +1 -1
  103. package/src/lib/plugins/api-key/CreateApiKeys.tsx +84 -0
  104. package/src/lib/plugins/api-key/SettingsApiKeys.tsx +105 -11
  105. package/src/lib/plugins/api-key/index.tsx +71 -66
  106. package/src/lib/plugins/openapi/MakeRequest.tsx +25 -23
  107. package/src/lib/plugins/openapi/OperationListItem.tsx +137 -1
  108. package/src/lib/plugins/openapi/RequestBodySidecarBox.tsx +1 -1
  109. package/src/lib/plugins/openapi/ResponsesSidecarBox.tsx +1 -2
  110. package/src/lib/plugins/openapi/Sidecar.tsx +20 -20
  111. package/src/lib/plugins/openapi/graphql/gql.ts +3 -3
  112. package/src/lib/plugins/openapi/graphql/graphql.ts +5 -0
  113. package/src/lib/plugins/openapi/index.tsx +2 -0
  114. package/src/lib/plugins/openapi/playground/Headers.tsx +14 -20
  115. package/src/lib/plugins/openapi/playground/InlineInput.tsx +1 -1
  116. package/src/lib/plugins/openapi/playground/{UrlParts.tsx → PathParams.tsx} +22 -26
  117. package/src/lib/plugins/openapi/playground/Playground.tsx +205 -155
  118. package/src/lib/plugins/openapi/playground/QueryParams.tsx +84 -57
  119. package/src/lib/plugins/openapi/playground/UrlDisplay.tsx +32 -0
  120. package/src/lib/plugins/openapi/playground/createUrl.ts +22 -0
  121. package/src/lib/plugins/openapi/util/generateSchemaExample.ts +24 -9
  122. package/src/lib/util/MdxComponents.tsx +1 -0
  123. package/dist/lib/plugins/openapi/playground/UrlParts.js.map +0 -1
  124. package/src/lib/plugins/openapi/queries.graphql +0 -6
@@ -9,15 +9,17 @@ import {
9
9
  import { Card, CardContent } from "../../../ui/Card.js";
10
10
  import { Button } from "../../../ui/Button.js";
11
11
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "../../../ui/Tabs.js";
12
- import { Fragment } from "react";
12
+ import { Fragment, useMemo } from "react";
13
13
  import { SyntaxHighlight } from "../../../components/SyntaxHighlight.js";
14
14
  import { ColorizedParam } from "../ColorizedParam.js";
15
15
  import { useMutation } from "@tanstack/react-query";
16
- import { useForm } from "react-hook-form";
16
+ import { FormProvider, useForm } from "react-hook-form";
17
17
  import { Headers } from "./Headers.js";
18
18
  import { QueryParams } from "./QueryParams.js";
19
- import { UrlParts } from "./UrlParts.js";
20
- import { LoaderCircle } from "lucide-react";
19
+ import { PathParams } from "./PathParams.js";
20
+ import { CirclePlayIcon, LoaderCircle, MonitorCheckIcon } from "lucide-react";
21
+ import { createUrl } from "./createUrl.js";
22
+ import { UrlDisplay } from "./UrlDisplay.js";
21
23
 
22
24
  function mimeTypeToLanguage(mimeType: string) {
23
25
  const mimeTypeMapping = {
@@ -36,6 +38,19 @@ function mimeTypeToLanguage(mimeType: string) {
36
38
  )?.[0][1];
37
39
  }
38
40
 
41
+ const statusCodeMap: Record<number, string> = {
42
+ 200: "OK",
43
+ 201: "Created",
44
+ 202: "Accepted",
45
+ 204: "No Content",
46
+ 400: "Bad Request",
47
+ 401: "Unauthorized",
48
+ 403: "Forbidden",
49
+ 404: "Not Found",
50
+ 405: "Method Not Allowed",
51
+ 500: "Internal Server Error",
52
+ };
53
+
39
54
  export type Header = {
40
55
  name: string;
41
56
  value: string;
@@ -43,8 +58,9 @@ export type Header = {
43
58
  export type QueryParam = {
44
59
  name: string;
45
60
  value: string;
61
+ active?: boolean;
46
62
  };
47
- export type UrlPart = {
63
+ export type PathParam = {
48
64
  name: string;
49
65
  value: string;
50
66
  };
@@ -52,7 +68,7 @@ export type UrlPart = {
52
68
  export type PlaygroundForm = {
53
69
  body: string;
54
70
  queryParams: QueryParam[];
55
- urlParts: UrlPart[];
71
+ pathParams: PathParam[];
56
72
  headers: Header[];
57
73
  };
58
74
 
@@ -60,63 +76,58 @@ const Playground = ({
60
76
  url,
61
77
  host,
62
78
  method,
63
- defaultHeaders,
79
+ headers = [{ name: "", value: "" }],
80
+ queryParams = [],
81
+ pathParams = [],
64
82
  }: {
65
83
  host: string;
66
84
  url: string;
67
85
  method: string;
68
- defaultHeaders?: Header[];
86
+ headers?: Header[];
87
+ queryParams?: QueryParam[];
88
+ pathParams?: PathParam[];
69
89
  }) => {
70
- const { register, control, handleSubmit, watch } = useForm<PlaygroundForm>({
71
- defaultValues: {
72
- body: "",
73
- queryParams: [{ name: "", value: "" }],
74
- headers: defaultHeaders ?? [{ name: "", value: "" }],
75
- urlParts: url
76
- .split("/")
77
- .filter((part) => part.startsWith("{") && part.endsWith("}"))
78
- .map((part) => ({ name: part.slice(1, -1), value: "" })),
79
- },
80
- });
81
-
90
+ const { register, control, handleSubmit, watch, ...form } =
91
+ useForm<PlaygroundForm>({
92
+ defaultValues: {
93
+ body: "",
94
+ queryParams,
95
+ headers,
96
+ pathParams,
97
+ },
98
+ });
82
99
  const formState = watch();
83
100
 
84
- const x = useMutation({
101
+ const queryMutation = useMutation({
85
102
  mutationFn: async (data: PlaygroundForm) => {
86
- const fullUrl =
87
- host +
88
- url
89
- .split("/")
90
- .map((v) =>
91
- v.startsWith("{") && v.endsWith("}")
92
- ? data.urlParts.find((part) => part.name === v.slice(1, -1))
93
- ?.value ?? v
94
- : v,
95
- )
96
- .join("/");
97
-
98
- const response = await fetch(fullUrl, {
103
+ const requestUrl = createUrl(host, url, data);
104
+ const start = performance.now();
105
+ const response = await fetch(requestUrl, {
99
106
  method: method.toUpperCase(),
100
107
  headers: Object.fromEntries(
101
- data.urlParts
108
+ data.pathParams
102
109
  .filter((h) => h.name)
103
110
  .map((header) => [header.name, header.value]),
104
111
  ),
105
112
  });
106
113
 
114
+ const body = await response.text();
115
+
107
116
  return {
108
117
  status: response.status,
109
118
  headers: response.headers,
110
- body: await response.text(),
119
+ size: body.length,
120
+ body,
121
+ time: performance.now() - start,
111
122
  };
112
123
  },
113
124
  });
114
125
 
115
- const path = url.split("/").map((part) => (
126
+ const path = url.split("/").map((part, i, arr) => (
116
127
  <Fragment key={part}>
117
128
  {part.startsWith("{") && part.endsWith("}") ? (
118
129
  <ColorizedParam
119
- name={part.slice(1, -1)}
130
+ name={part}
120
131
  backgroundOpacity="0"
121
132
  onClick={() => {
122
133
  console.log("asd");
@@ -124,148 +135,187 @@ const Playground = ({
124
135
  slug={part.slice(1, -1)}
125
136
  >
126
137
  {
127
- formState.urlParts.find((p) => {
128
- console.log(
129
- p.name,
130
- part.slice(1, -1),
131
- p.name === part.slice(1, -1),
132
- p.value,
133
- );
134
- return p.name === part.slice(1, -1);
135
- })?.value
138
+ formState.pathParams.find((p) => p.name === part.slice(1, -1))
139
+ ?.value
136
140
  }
137
141
  </ColorizedParam>
138
142
  ) : (
139
143
  part
140
144
  )}
141
- {"/"}
145
+ {i < arr.length - 1 && "/"}
142
146
  <wbr />
143
147
  </Fragment>
144
148
  ));
145
149
 
146
- const lang = mimeTypeToLanguage(x.data?.headers.get("Content-Type") ?? "");
150
+ const lang = mimeTypeToLanguage(
151
+ queryMutation.data?.headers.get("Content-Type") ?? "",
152
+ );
153
+
154
+ const headerEntries = Array.from(queryMutation.data?.headers.entries() ?? []);
155
+
156
+ console.log(
157
+ "QP lol",
158
+ formState.queryParams,
159
+ formState.queryParams.filter((p) => p.active),
160
+ );
161
+
162
+ const urlQueryParams = formState.queryParams
163
+ .filter((p) => p.active)
164
+ .map((p) => (
165
+ <Fragment key={p.name}>
166
+ {p.name}={p.value}
167
+ <wbr />
168
+ </Fragment>
169
+ ));
147
170
 
148
171
  return (
149
172
  <Dialog>
150
- <DialogTrigger>Open</DialogTrigger>
173
+ <DialogTrigger asChild>
174
+ <CirclePlayIcon
175
+ className="cursor-pointer text-primary hover:text-primary/80"
176
+ size={16}
177
+ />
178
+ </DialogTrigger>
151
179
 
152
- <DialogContent className="max-w-screen-xl w-full h-5/6">
153
- <form
154
- onSubmit={handleSubmit((data) => {
155
- x.mutateAsync(data);
156
- })}
157
- >
158
- <DialogHeader>
159
- <DialogTitle className="mb-4">API Playground</DialogTitle>
160
- <DialogDescription>
161
- <div className="grid grid-cols-2 gap-2">
162
- <Card>
163
- <CardContent className="border-b border-border pt-4">
164
- <div className="font-bold mb-1">URL</div>
165
- <div className="flex gap-2 items-center">
166
- <div className="border rounded border-border flex justify-stretch w-full">
167
- <div className="border-r border-border p-2 bg-muted font-mono">
168
- {method.toUpperCase()}
180
+ <DialogContent className="max-w-screen-xl w-full h-5/6 overflow-auto p-0">
181
+ <FormProvider {...{ register, control, handleSubmit, watch, ...form }}>
182
+ <form
183
+ onSubmit={handleSubmit((data) => {
184
+ queryMutation.mutateAsync(data);
185
+ })}
186
+ >
187
+ <div className="grid grid-cols-2 text-sm h-full">
188
+ <div className="flex flex-col gap-4 p-8 bg-muted/50 after:bg-muted-foreground/20 relative after:absolute after:w-px after:inset-0 after:left-auto">
189
+ <div className="flex gap-2 items-stretch">
190
+ <div className="flex flex-1 items-center w-full border rounded-md border-border">
191
+ <div className="border-r border-border p-2 bg-muted rounded-l-md self-stretch font-semibold font-mono">
192
+ {method.toUpperCase()}
193
+ </div>
194
+ <div className="p-2 font-mono text-xs">
195
+ {path}
196
+ {urlQueryParams.length > 0 ? "?" : ""}
197
+ {urlQueryParams}
198
+ </div>
199
+ </div>
200
+ <Button type="submit" className="h-auto flex gap-1">
201
+ Send
202
+ </Button>
203
+ </div>
204
+ <Tabs defaultValue="parameters">
205
+ <TabsList>
206
+ <TabsTrigger value="parameters">Parameters</TabsTrigger>
207
+ <TabsTrigger value="headers">
208
+ Headers{" "}
209
+ {formState.headers.length > 0 &&
210
+ `(${formState.headers.length})`}
211
+ </TabsTrigger>
212
+ <TabsTrigger
213
+ value="body"
214
+ disabled={["POST", "PUT", "PATCH", "DELETE"].includes(
215
+ method.toUpperCase(),
216
+ )}
217
+ >
218
+ Body
219
+ </TabsTrigger>
220
+ </TabsList>
221
+ <TabsContent value="headers">
222
+ <Headers
223
+ control={control}
224
+ register={register}
225
+ headers={formState.headers}
226
+ />
227
+ </TabsContent>
228
+ <TabsContent value="parameters">
229
+ <div className="grid grid-cols-[min-content_1fr_1fr_auto]">
230
+ {pathParams.length > 0 && (
231
+ <div className="font-semibold my-3 col-span-3">
232
+ Path Parameters
169
233
  </div>
170
- <div className="p-2 whitespace-nowrap overflow-scroll flex-1">
171
- {path}
234
+ )}
235
+ <PathParams control={control} register={register} />
236
+ {queryParams.length > 0 && (
237
+ <span className="font-semibold my-3 col-span-3">
238
+ Query Parameters
239
+ </span>
240
+ )}
241
+ <QueryParams control={control} />
242
+ </div>
243
+ </TabsContent>
244
+ <TabsContent value="body">
245
+ <textarea
246
+ {...register("body")}
247
+ className="border border-border w-full rounded p-2 bg-muted h-40"
248
+ />
249
+ </TabsContent>
250
+ </Tabs>
251
+ </div>
252
+ <div className="flex flex-col gap-4 p-8 bg-muted/70">
253
+ {queryMutation.error ? (
254
+ <div>{queryMutation.error.message}</div>
255
+ ) : queryMutation.data ? (
256
+ <div className="flex flex-col gap-2">
257
+ <div className="flex gap-2">
258
+ <div className="flex text-xs gap-6">
259
+ <div>
260
+ Status: {queryMutation.data.status}{" "}
261
+ {statusCodeMap[queryMutation.data.status] ?? ""}
172
262
  </div>
263
+ <div>Time: {queryMutation.data.time.toFixed(0)}ms</div>
264
+ <div>Size: {queryMutation.data.size} B</div>
173
265
  </div>
174
- <Button type="submit">Send</Button>
175
266
  </div>
176
- </CardContent>
177
- <Tabs defaultValue="parameters">
178
- <CardContent className="border-b border-border py-4">
267
+ {/*<UrlDisplay host={host} path={url} />*/}
268
+ <Tabs defaultValue="response">
179
269
  <TabsList>
180
- <TabsTrigger value="parameters">Parameters</TabsTrigger>
270
+ <TabsTrigger value="response">Response</TabsTrigger>
181
271
  <TabsTrigger value="headers">
182
- Headers ({formState.headers.length - 1})
183
- </TabsTrigger>
184
- <TabsTrigger
185
- value="body"
186
- disabled={["POST", "PUT", "PATCH", "DELETE"].includes(
187
- method.toUpperCase(),
188
- )}
189
- >
190
- Body
272
+ {headerEntries.length
273
+ ? `Headers (${headerEntries.length})`
274
+ : "No headers"}
191
275
  </TabsTrigger>
192
276
  </TabsList>
193
- </CardContent>
194
- <CardContent className="overflow-auto h-full">
195
- <TabsContent value="headers">
196
- <Headers
197
- control={control}
198
- register={register}
199
- headers={formState.headers}
200
- />
201
- </TabsContent>
202
- <TabsContent value="parameters">
203
- <UrlParts control={control} register={register} />
204
- Query Parameters
205
- <QueryParams
206
- control={control}
207
- queryParams={formState.queryParams}
208
- register={register}
209
- />
210
- </TabsContent>
211
- <TabsContent value="auth">
212
- <CardContent>
213
- {url
214
- .split("/")
215
- .map((part, i) =>
216
- part.startsWith("{") && part.endsWith("}")
217
- ? formState.urlParts[i]
218
- : part,
219
- )
220
- .join("/")}
221
- </CardContent>
222
- Change your password here.
277
+
278
+ <TabsContent value="response">
279
+ <Card className="shadow-none p-4">
280
+ <SyntaxHighlight
281
+ language={lang ?? "json"}
282
+ noBackground
283
+ className="overflow-x-auto "
284
+ code={
285
+ queryMutation.data?.body ?? JSON.stringify("")
286
+ }
287
+ />
288
+ </Card>
223
289
  </TabsContent>
224
- <TabsContent value="body">
225
- <textarea
226
- {...register("body")}
227
- className="border border-border w-full rounded p-2 bg-muted h-40"
228
- />
290
+ <TabsContent value="headers">
291
+ <Card className="grid grid-cols-2 w-full gap-2.5 font-mono text-xs shadow-none p-4">
292
+ <div className="font-semibold">Key</div>
293
+ <div className="font-semibold">Value</div>
294
+ {headerEntries.map(([key, value]) => (
295
+ <Fragment key={key}>
296
+ <div>{key}</div>
297
+ <div>{value}</div>
298
+ </Fragment>
299
+ ))}
300
+ </Card>
229
301
  </TabsContent>
230
- </CardContent>
231
- </Tabs>
232
- </Card>
233
- <Card>
234
- <CardContent>
235
- <div className="mt-2 font-mono gap-2 flex">
236
- {method.toUpperCase()} {x.data?.status}{" "}
237
- {url
238
- .split("/")
239
- .map((v) =>
240
- v.startsWith("{") && v.endsWith("}")
241
- ? formState.urlParts.find((part) => part.name === v)
242
- ?.value ?? v
243
- : v,
244
- )
245
- .join("/")}
246
- </div>
247
-
248
- <div className="w-full overflow-auto max-h-80 rounded-xl border border-border p-4 dark:!bg-foreground/10 dark:border-transparent">
249
- {x.isPending && (
250
- <>
251
- <LoaderCircle size={18} className="animate-spins" /> a
252
- request to see the response."
253
- </>
302
+ </Tabs>
303
+ </div>
304
+ ) : (
305
+ <div className="grid place-items-center h-full">
306
+ <span className="text-[16px] font-semibold text-muted-foreground">
307
+ {queryMutation.isPending ? (
308
+ <LoaderCircle size={18} className="animate-spin" />
309
+ ) : (
310
+ "Send a request first to see the response here"
254
311
  )}
255
- <SyntaxHighlight
256
- language={lang ?? "json"}
257
- noBackground
258
- copyable={false}
259
- className="overflow-x-auto "
260
- code={x.data?.body ?? JSON.stringify("")}
261
- />
262
- </div>
263
- </CardContent>
264
- </Card>
312
+ </span>
313
+ </div>
314
+ )}
265
315
  </div>
266
- </DialogDescription>
267
- </DialogHeader>
268
- </form>
316
+ </div>
317
+ </form>
318
+ </FormProvider>
269
319
  </DialogContent>
270
320
  </Dialog>
271
321
  );
@@ -2,78 +2,105 @@ import {
2
2
  Control,
3
3
  Controller,
4
4
  useFieldArray,
5
- UseFormRegister,
5
+ useFormContext,
6
+ useWatch,
6
7
  } from "react-hook-form";
7
- import { TrashIcon } from "lucide-react";
8
+ import { XIcon } from "lucide-react";
8
9
  import { InlineInput } from "./InlineInput.js";
9
- import { PlaygroundForm, QueryParam } from "./Playground.js";
10
+ import { PlaygroundForm } from "./Playground.js";
10
11
  import { useEffect } from "react";
11
12
 
12
- export const QueryParams = ({
13
+ const QueryParamActive = ({
14
+ i,
13
15
  control,
14
- register,
15
- queryParams,
16
16
  }: {
17
- register: UseFormRegister<PlaygroundForm>;
17
+ i: number;
18
18
  control: Control<PlaygroundForm>;
19
- queryParams: QueryParam[];
20
19
  }) => {
21
- const { fields, append, remove } = useFieldArray<PlaygroundForm>({
22
- control,
23
- name: "queryParams",
24
- });
20
+ const value = useWatch({ control, name: `queryParams.${i}.value` });
21
+ const active = useWatch({ control, name: `queryParams.${i}.active` });
22
+ const form = useFormContext<PlaygroundForm>();
23
+ const dirty = form.formState.dirtyFields;
25
24
 
26
- const queryCount = queryParams?.length;
27
- const queryLastName = queryParams.at(-1)?.name;
28
- const queryLastValue = queryParams.at(-1)?.value;
25
+ console.log("dirty fields", dirty);
29
26
 
30
27
  useEffect(() => {
31
- // if (queryCount === 0) {
32
- // append({ name: "", value: "" });
33
- // }
34
- console.log(queryLastName, queryLastValue, queryCount);
35
- if (queryLastName !== "" || queryLastValue !== "") {
36
- console.log("appending");
37
- append({ name: "", value: "" });
28
+ if (value) {
29
+ console.log(`queryParams.${i}.active`, active);
30
+ form.setValue(`queryParams.${i}.active`, true);
38
31
  }
39
- }, [append, queryLastValue, queryLastName, queryCount]);
32
+ }, [value]);
40
33
 
41
34
  return (
42
- <div className="grid grid-cols-[1fr_1fr_auto]">
43
- {fields.map((field, i) => (
44
- <div
45
- key={field.id}
46
- className="grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-muted rounded overflow-hidden group"
47
- >
48
- {field.id}
49
- <Controller
50
- control={control}
51
- render={({ field }) => {
52
- return (
53
- <InlineInput {...field} placeholder="Name" className="peer" />
54
- );
55
- }}
56
- name={`queryParams.${i}.name`}
57
- />
58
- <Controller
59
- control={control}
60
- render={({ field }) => {
61
- return (
62
- <InlineInput {...field} placeholder="Value" className="peer" />
63
- );
64
- }}
65
- name={`queryParams.${i}.value`}
35
+ <Controller
36
+ name={`queryParams.${i}.active`}
37
+ render={({ field }) => {
38
+ return (
39
+ <input
40
+ type="checkbox"
41
+ id={`queryParams.${i}.active`}
42
+ {...field}
43
+ checked={field.value}
66
44
  />
45
+ );
46
+ }}
47
+ />
48
+ );
49
+ };
67
50
 
68
- <button
69
- className="hover:bg-black/5 p-1 rounded mr-2 text-muted-foreground invisible group-hover:visible peer-focus:visible"
70
- onClick={() => remove(i)}
71
- >
72
- <TrashIcon size={16} />
73
- </button>
74
- <div className="col-span-full border-b border-border"></div>
75
- </div>
76
- ))}
51
+ export const QueryParams = ({
52
+ control,
53
+ }: {
54
+ control: Control<PlaygroundForm>;
55
+ }) => {
56
+ const { fields } = useFieldArray<PlaygroundForm>({
57
+ control,
58
+ name: "queryParams",
59
+ });
60
+
61
+ return fields.map((field, i) => (
62
+ <div
63
+ key={field.id}
64
+ className="px-2 grid-cols-subgrid col-span-full grid items-center gap-x-2 has-[:focus]:bg-muted hover:bg-accent rounded overflow-hidden group"
65
+ >
66
+ <QueryParamActive i={i} control={control} />
67
+
68
+ <Controller
69
+ control={control}
70
+ render={({ field }) => {
71
+ return (
72
+ <div className="flex items-center px-2 bg-transparent h-6 font-mono text-xs m-2">
73
+ {field.value}
74
+ </div>
75
+ );
76
+ }}
77
+ name={`queryParams.${i}.name`}
78
+ />
79
+ <Controller
80
+ control={control}
81
+ render={({ field }) => {
82
+ return (
83
+ <InlineInput {...field} placeholder="Value" className="peer" />
84
+ );
85
+ }}
86
+ name={`queryParams.${i}.value`}
87
+ />
88
+ <Controller
89
+ control={control}
90
+ render={({ field }) => {
91
+ return (
92
+ <button
93
+ className="hover:bg-black/5 p-1 rounded mr-2 text-muted-foreground invisible group-hover:visible peer-focus:visible"
94
+ onClick={() => field.onChange("")}
95
+ >
96
+ <XIcon size={16} />
97
+ </button>
98
+ );
99
+ }}
100
+ name={`queryParams.${i}.value`}
101
+ />
102
+
103
+ <div className="-mx-2 col-span-full border-b border-border"></div>
77
104
  </div>
78
- );
105
+ ));
79
106
  };
@@ -0,0 +1,32 @@
1
+ import { useFormContext } from "react-hook-form";
2
+ import type { PlaygroundForm } from "./Playground.js";
3
+
4
+ export const UrlDisplay = ({ host, path }: { host: string; path: string }) => {
5
+ const { watch } = useFormContext<PlaygroundForm>();
6
+ const data = watch();
7
+ const url = new URL(
8
+ host +
9
+ path
10
+ .split("/")
11
+ .map((v) =>
12
+ v.startsWith("{") && v.endsWith("}")
13
+ ? data.pathParams.find((part) => part.name === v.slice(1, -1))
14
+ ?.value ?? v
15
+ : v,
16
+ )
17
+ .join("/"),
18
+ );
19
+
20
+ data.queryParams.forEach((param) => {
21
+ if (!param.value) {
22
+ return;
23
+ }
24
+ url.searchParams.set(param.name, param.value);
25
+ });
26
+
27
+ return (
28
+ <div className="overflow-auto font-mono whitespace-nowrap">
29
+ {url.toString()}
30
+ </div>
31
+ );
32
+ };
@@ -0,0 +1,22 @@
1
+ import { PlaygroundForm } from "./Playground.js";
2
+
3
+ export const createUrl = (host: string, path: string, data: PlaygroundForm) => {
4
+ const url = new URL(
5
+ host +
6
+ path
7
+ .split("/")
8
+ .map((v) =>
9
+ v.startsWith("{") && v.endsWith("}")
10
+ ? data.pathParams.find((part) => part.name === v.slice(1, -1))
11
+ ?.value ?? v
12
+ : v,
13
+ )
14
+ .join("/"),
15
+ );
16
+
17
+ data.queryParams.forEach((param) => {
18
+ url.searchParams.set(param.name, param.value);
19
+ });
20
+
21
+ return url;
22
+ };