blodemd 0.0.4 → 0.0.6
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.
- package/README.md +12 -1
- package/dev-server/app/[[...slug]]/page.tsx +139 -0
- package/dev-server/app/blodemd-dev/invalidate/route.ts +12 -0
- package/dev-server/app/blodemd-dev/static/[...path]/route.ts +32 -0
- package/dev-server/app/blodemd-dev/version/route.ts +14 -0
- package/dev-server/app/blodemd-internal/proxy/route.ts +86 -0
- package/dev-server/app/error.tsx +24 -0
- package/dev-server/app/globals.css +4 -0
- package/dev-server/app/layout.tsx +38 -0
- package/dev-server/app/not-found.tsx +18 -0
- package/dev-server/app/search/route.ts +17 -0
- package/dev-server/components/dev-reload-script.tsx +86 -0
- package/dev-server/components/providers.tsx +15 -0
- package/dev-server/lib/dev-state.ts +8 -0
- package/dev-server/lib/local-content-source.ts +103 -0
- package/dev-server/lib/local-runtime.tsx +558 -0
- package/dev-server/next.config.js +46 -0
- package/dev-server/package.json +57 -0
- package/dev-server/postcss.config.mjs +7 -0
- package/dev-server/public/glide-variable.woff2 +0 -0
- package/dev-server/tsconfig.json +49 -0
- package/dist/cli.mjs +299 -26
- package/dist/cli.mjs.map +1 -1
- package/docs/app/globals.css +455 -0
- package/docs/components/api/api-playground.tsx +295 -0
- package/docs/components/api/api-reference.tsx +121 -0
- package/docs/components/content/collection-index.tsx +114 -0
- package/docs/components/docs/contextual-menu.tsx +406 -0
- package/docs/components/docs/copy-page-menu.tsx +255 -0
- package/docs/components/docs/doc-header.tsx +192 -0
- package/docs/components/docs/doc-shell.tsx +289 -0
- package/docs/components/docs/doc-sidebar.tsx +206 -0
- package/docs/components/docs/doc-toc.tsx +45 -0
- package/docs/components/docs/mobile-nav.tsx +207 -0
- package/docs/components/mdx/accordion.tsx +83 -0
- package/docs/components/mdx/badge.tsx +79 -0
- package/docs/components/mdx/callout.tsx +88 -0
- package/docs/components/mdx/card.tsx +104 -0
- package/docs/components/mdx/code-block.tsx +75 -0
- package/docs/components/mdx/code-group.tsx +94 -0
- package/docs/components/mdx/color.tsx +87 -0
- package/docs/components/mdx/columns.tsx +25 -0
- package/docs/components/mdx/expandable.tsx +45 -0
- package/docs/components/mdx/field-layout.tsx +77 -0
- package/docs/components/mdx/frame.tsx +23 -0
- package/docs/components/mdx/get-text-content.ts +18 -0
- package/docs/components/mdx/icon.tsx +56 -0
- package/docs/components/mdx/index.tsx +96 -0
- package/docs/components/mdx/installer.tsx +20 -0
- package/docs/components/mdx/panel.tsx +11 -0
- package/docs/components/mdx/param-field.tsx +56 -0
- package/docs/components/mdx/preview.tsx +36 -0
- package/docs/components/mdx/prompt.tsx +63 -0
- package/docs/components/mdx/request-example.tsx +27 -0
- package/docs/components/mdx/response-field.tsx +42 -0
- package/docs/components/mdx/steps.tsx +92 -0
- package/docs/components/mdx/tabs.tsx +88 -0
- package/docs/components/mdx/tile.tsx +43 -0
- package/docs/components/mdx/tooltip.tsx +71 -0
- package/docs/components/mdx/tree.tsx +120 -0
- package/docs/components/mdx/type-table.tsx +71 -0
- package/docs/components/mdx/update.tsx +44 -0
- package/docs/components/mdx/video.tsx +12 -0
- package/docs/components/mdx/view.tsx +66 -0
- package/docs/components/providers.tsx +15 -0
- package/docs/components/ui/breadcrumb.tsx +92 -0
- package/docs/components/ui/button.tsx +90 -0
- package/docs/components/ui/card.tsx +92 -0
- package/docs/components/ui/command.tsx +139 -0
- package/docs/components/ui/dialog.tsx +97 -0
- package/docs/components/ui/field.tsx +237 -0
- package/docs/components/ui/input.tsx +105 -0
- package/docs/components/ui/label.tsx +22 -0
- package/docs/components/ui/popover.tsx +72 -0
- package/docs/components/ui/search.tsx +380 -0
- package/docs/components/ui/separator.tsx +26 -0
- package/docs/components/ui/sheet.tsx +104 -0
- package/docs/components/ui/sidebar.tsx +433 -0
- package/docs/components/ui/theme-toggle.tsx +62 -0
- package/docs/components/ui/tooltip.tsx +53 -0
- package/docs/lib/contextual-options.ts +193 -0
- package/docs/lib/docs-collection.ts +22 -0
- package/docs/lib/mdx.ts +90 -0
- package/docs/lib/navigation.ts +288 -0
- package/docs/lib/openapi.ts +158 -0
- package/docs/lib/routes.ts +10 -0
- package/docs/lib/server-cache.ts +83 -0
- package/docs/lib/shiki.ts +35 -0
- package/docs/lib/theme.ts +29 -0
- package/docs/lib/toc.ts +2 -0
- package/docs/lib/utils.ts +5 -0
- package/package.json +34 -3
- package/packages/@repo/common/dist/index.d.ts +9 -0
- package/packages/@repo/common/dist/index.d.ts.map +1 -0
- package/packages/@repo/common/dist/index.js +42 -0
- package/packages/@repo/common/package.json +34 -0
- package/packages/@repo/common/src/common.unit.test.ts +55 -0
- package/packages/@repo/common/src/index.ts +51 -0
- package/packages/@repo/contracts/dist/api-key.d.ts +30 -0
- package/packages/@repo/contracts/dist/api-key.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/api-key.js +20 -0
- package/packages/@repo/contracts/dist/dates.d.ts +4 -0
- package/packages/@repo/contracts/dist/dates.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/dates.js +2 -0
- package/packages/@repo/contracts/dist/deployment.d.ts +71 -0
- package/packages/@repo/contracts/dist/deployment.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/deployment.js +46 -0
- package/packages/@repo/contracts/dist/domain.d.ts +94 -0
- package/packages/@repo/contracts/dist/domain.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/domain.js +36 -0
- package/packages/@repo/contracts/dist/ids.d.ts +14 -0
- package/packages/@repo/contracts/dist/ids.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/ids.js +10 -0
- package/packages/@repo/contracts/dist/index.d.ts +10 -0
- package/packages/@repo/contracts/dist/index.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/index.js +11 -0
- package/packages/@repo/contracts/dist/pagination.d.ts +23 -0
- package/packages/@repo/contracts/dist/pagination.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/pagination.js +15 -0
- package/packages/@repo/contracts/dist/project.d.ts +25 -0
- package/packages/@repo/contracts/dist/project.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/project.js +23 -0
- package/packages/@repo/contracts/dist/tenant.d.ts +99 -0
- package/packages/@repo/contracts/dist/tenant.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/tenant.js +36 -0
- package/packages/@repo/contracts/dist/user.d.ts +9 -0
- package/packages/@repo/contracts/dist/user.d.ts.map +1 -0
- package/packages/@repo/contracts/dist/user.js +9 -0
- package/packages/@repo/contracts/package.json +37 -0
- package/packages/@repo/contracts/src/api-key.ts +27 -0
- package/packages/@repo/contracts/src/dates.ts +4 -0
- package/packages/@repo/contracts/src/deployment.ts +73 -0
- package/packages/@repo/contracts/src/domain.ts +51 -0
- package/packages/@repo/contracts/src/ids.ts +22 -0
- package/packages/@repo/contracts/src/index.ts +11 -0
- package/packages/@repo/contracts/src/pagination.ts +21 -0
- package/packages/@repo/contracts/src/project.ts +30 -0
- package/packages/@repo/contracts/src/tenant.ts +54 -0
- package/packages/@repo/contracts/src/user.ts +12 -0
- package/packages/@repo/models/dist/docs-config.d.ts +985 -0
- package/packages/@repo/models/dist/docs-config.d.ts.map +1 -0
- package/packages/@repo/models/dist/docs-config.js +548 -0
- package/packages/@repo/models/dist/index.d.ts +3 -0
- package/packages/@repo/models/dist/index.d.ts.map +1 -0
- package/packages/@repo/models/dist/index.js +3 -0
- package/packages/@repo/models/dist/tenant.d.ts +25 -0
- package/packages/@repo/models/dist/tenant.d.ts.map +1 -0
- package/packages/@repo/models/dist/tenant.js +1 -0
- package/packages/@repo/models/package.json +37 -0
- package/packages/@repo/models/src/docs-config.ts +648 -0
- package/packages/@repo/models/src/index.ts +3 -0
- package/packages/@repo/models/src/tenant.ts +29 -0
- package/packages/@repo/prebuild/dist/index.d.ts +2 -0
- package/packages/@repo/prebuild/dist/index.d.ts.map +1 -0
- package/packages/@repo/prebuild/dist/index.js +2 -0
- package/packages/@repo/prebuild/dist/openapi.d.ts +43 -0
- package/packages/@repo/prebuild/dist/openapi.d.ts.map +1 -0
- package/packages/@repo/prebuild/dist/openapi.js +58 -0
- package/packages/@repo/prebuild/package.json +39 -0
- package/packages/@repo/prebuild/src/index.ts +2 -0
- package/packages/@repo/prebuild/src/openapi.ts +116 -0
- package/packages/@repo/previewing/dist/blob-source.d.ts +16 -0
- package/packages/@repo/previewing/dist/blob-source.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/blob-source.js +110 -0
- package/packages/@repo/previewing/dist/content-source.d.ts +12 -0
- package/packages/@repo/previewing/dist/content-source.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/content-source.js +1 -0
- package/packages/@repo/previewing/dist/fs-source.d.ts +11 -0
- package/packages/@repo/previewing/dist/fs-source.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/fs-source.js +79 -0
- package/packages/@repo/previewing/dist/index.d.ts +120 -0
- package/packages/@repo/previewing/dist/index.d.ts.map +1 -0
- package/packages/@repo/previewing/dist/index.js +984 -0
- package/packages/@repo/previewing/package.json +41 -0
- package/packages/@repo/previewing/src/blob-source.ts +167 -0
- package/packages/@repo/previewing/src/content-source.ts +12 -0
- package/packages/@repo/previewing/src/fs-source.ts +111 -0
- package/packages/@repo/previewing/src/index.ts +1490 -0
- package/packages/@repo/previewing/src/index.unit.test.ts +290 -0
- package/packages/@repo/validation/dist/index.d.ts +12 -0
- package/packages/@repo/validation/dist/index.d.ts.map +1 -0
- package/packages/@repo/validation/dist/index.js +30 -0
- package/packages/@repo/validation/package.json +37 -0
- package/packages/@repo/validation/src/index.ts +59 -0
- package/packages/@repo/validation/src/mintlify-docs-schema.json +5016 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useMemo, useState } from "react";
|
|
4
|
+
import type { ChangeEvent } from "react";
|
|
5
|
+
|
|
6
|
+
import { Button } from "@/components/ui/button";
|
|
7
|
+
import { Field, FieldLabel } from "@/components/ui/field";
|
|
8
|
+
import { Input } from "@/components/ui/input";
|
|
9
|
+
import type { OpenApiEntry } from "@/lib/openapi";
|
|
10
|
+
|
|
11
|
+
const extractParams = (entry: OpenApiEntry, location: "path" | "query") =>
|
|
12
|
+
(entry.operation.parameters ?? []).filter(
|
|
13
|
+
(param) => (param as { in?: string }).in === location
|
|
14
|
+
) as { name?: string; required?: boolean; description?: string }[];
|
|
15
|
+
|
|
16
|
+
export const ApiPlayground = ({
|
|
17
|
+
entry,
|
|
18
|
+
proxyEnabled,
|
|
19
|
+
}: {
|
|
20
|
+
entry: OpenApiEntry;
|
|
21
|
+
proxyEnabled: boolean;
|
|
22
|
+
}) => {
|
|
23
|
+
const servers = entry.spec.servers ?? [];
|
|
24
|
+
const [serverIndex, setServerIndex] = useState(0);
|
|
25
|
+
const [response, setResponse] = useState<string | null>(null);
|
|
26
|
+
const [status, setStatus] = useState<number | null>(null);
|
|
27
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
28
|
+
const [body, setBody] = useState("{}");
|
|
29
|
+
const [authToken, setAuthToken] = useState("");
|
|
30
|
+
const [useProxy, setUseProxy] = useState(proxyEnabled);
|
|
31
|
+
|
|
32
|
+
const pathParams = useMemo(() => extractParams(entry, "path"), [entry]);
|
|
33
|
+
const queryParams = useMemo(() => extractParams(entry, "query"), [entry]);
|
|
34
|
+
const [pathValues, setPathValues] = useState<Record<string, string>>({});
|
|
35
|
+
const [queryValues, setQueryValues] = useState<Record<string, string>>({});
|
|
36
|
+
|
|
37
|
+
const baseUrl = servers[serverIndex]?.url ?? "";
|
|
38
|
+
const canSend = Boolean(baseUrl);
|
|
39
|
+
|
|
40
|
+
const buildUrl = useCallback(() => {
|
|
41
|
+
let { path } = entry.operation;
|
|
42
|
+
for (const param of pathParams) {
|
|
43
|
+
const key = param.name ?? "";
|
|
44
|
+
const value = pathValues[key] ?? "";
|
|
45
|
+
path = path.replace(`{${key}}`, encodeURIComponent(value));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const url = new URL(path, baseUrl || "http://localhost");
|
|
49
|
+
for (const param of queryParams) {
|
|
50
|
+
const key = param.name ?? "";
|
|
51
|
+
const value = queryValues[key];
|
|
52
|
+
if (value) {
|
|
53
|
+
url.searchParams.set(key, value);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return url.toString();
|
|
58
|
+
}, [
|
|
59
|
+
baseUrl,
|
|
60
|
+
entry.operation,
|
|
61
|
+
pathParams,
|
|
62
|
+
pathValues,
|
|
63
|
+
queryParams,
|
|
64
|
+
queryValues,
|
|
65
|
+
]);
|
|
66
|
+
|
|
67
|
+
const handleUseProxyChange = useCallback(
|
|
68
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
69
|
+
setUseProxy(event.target.checked);
|
|
70
|
+
},
|
|
71
|
+
[]
|
|
72
|
+
);
|
|
73
|
+
const handleServerChange = useCallback(
|
|
74
|
+
(event: ChangeEvent<HTMLSelectElement>) => {
|
|
75
|
+
setServerIndex(Number(event.target.value));
|
|
76
|
+
},
|
|
77
|
+
[]
|
|
78
|
+
);
|
|
79
|
+
const handlePathValueChange = useCallback(
|
|
80
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
81
|
+
const { name, value } = event.target;
|
|
82
|
+
setPathValues((prev) => ({
|
|
83
|
+
...prev,
|
|
84
|
+
[name]: value,
|
|
85
|
+
}));
|
|
86
|
+
},
|
|
87
|
+
[]
|
|
88
|
+
);
|
|
89
|
+
const handleQueryValueChange = useCallback(
|
|
90
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
91
|
+
const { name, value } = event.target;
|
|
92
|
+
setQueryValues((prev) => ({
|
|
93
|
+
...prev,
|
|
94
|
+
[name]: value,
|
|
95
|
+
}));
|
|
96
|
+
},
|
|
97
|
+
[]
|
|
98
|
+
);
|
|
99
|
+
const handleAuthTokenChange = useCallback(
|
|
100
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
101
|
+
setAuthToken(event.target.value);
|
|
102
|
+
},
|
|
103
|
+
[]
|
|
104
|
+
);
|
|
105
|
+
const handleBodyChange = useCallback(
|
|
106
|
+
(event: ChangeEvent<HTMLTextAreaElement>) => {
|
|
107
|
+
setBody(event.target.value);
|
|
108
|
+
},
|
|
109
|
+
[]
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const handleSend = useCallback(async () => {
|
|
113
|
+
const url = buildUrl();
|
|
114
|
+
setIsLoading(true);
|
|
115
|
+
setResponse(null);
|
|
116
|
+
setStatus(null);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
const { method } = entry.operation;
|
|
120
|
+
const requestHeaders = {
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const payload = {
|
|
126
|
+
body,
|
|
127
|
+
headers: requestHeaders,
|
|
128
|
+
method,
|
|
129
|
+
url,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const requestUrl = useProxy ? "/blodemd-internal/proxy" : url;
|
|
133
|
+
const requestMethod = useProxy ? "POST" : method;
|
|
134
|
+
const requestHeadersToSend = useProxy
|
|
135
|
+
? {
|
|
136
|
+
"Content-Type": "application/json",
|
|
137
|
+
}
|
|
138
|
+
: requestHeaders;
|
|
139
|
+
|
|
140
|
+
let requestBody: string | undefined;
|
|
141
|
+
if (useProxy) {
|
|
142
|
+
requestBody = JSON.stringify(payload);
|
|
143
|
+
} else if (method === "GET") {
|
|
144
|
+
requestBody = undefined;
|
|
145
|
+
} else {
|
|
146
|
+
requestBody = body;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const res = await fetch(requestUrl, {
|
|
150
|
+
body: requestBody,
|
|
151
|
+
headers: requestHeadersToSend,
|
|
152
|
+
method: requestMethod,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const text = await res.text();
|
|
156
|
+
setStatus(res.status);
|
|
157
|
+
let formatted = text;
|
|
158
|
+
try {
|
|
159
|
+
formatted = JSON.stringify(JSON.parse(text), null, 2);
|
|
160
|
+
} catch {
|
|
161
|
+
formatted = text;
|
|
162
|
+
}
|
|
163
|
+
setResponse(formatted || "(empty response)");
|
|
164
|
+
} catch (error) {
|
|
165
|
+
setStatus(0);
|
|
166
|
+
setResponse(error instanceof Error ? error.message : "Request failed.");
|
|
167
|
+
} finally {
|
|
168
|
+
setIsLoading(false);
|
|
169
|
+
}
|
|
170
|
+
}, [authToken, body, buildUrl, entry.operation, useProxy]);
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<section className="mt-7 grid gap-3">
|
|
174
|
+
<div className="grid gap-3 rounded-xl border border-border bg-surface p-4">
|
|
175
|
+
<div className="flex items-center justify-between">
|
|
176
|
+
<h2>Try it out</h2>
|
|
177
|
+
{proxyEnabled ? (
|
|
178
|
+
<label className="flex items-center gap-2 text-sm">
|
|
179
|
+
<input
|
|
180
|
+
checked={useProxy}
|
|
181
|
+
className="accent-primary"
|
|
182
|
+
onChange={handleUseProxyChange}
|
|
183
|
+
type="checkbox"
|
|
184
|
+
/>
|
|
185
|
+
Use docs proxy
|
|
186
|
+
</label>
|
|
187
|
+
) : null}
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
{servers.length ? (
|
|
191
|
+
<Field>
|
|
192
|
+
<FieldLabel htmlFor="api-server">Server</FieldLabel>
|
|
193
|
+
<select
|
|
194
|
+
className="flex h-[var(--field-height)] w-full rounded-[var(--field-radius)] border border-input bg-card px-[var(--field-padding-x)] py-[var(--field-padding-y)] font-sans text-base text-foreground shadow-input transition-colors hover:border-input-hover focus:border-ring focus:outline-hidden focus:ring-2 focus:ring-ring/15 focus:ring-offset-1 focus:ring-offset-background"
|
|
195
|
+
id="api-server"
|
|
196
|
+
onChange={handleServerChange}
|
|
197
|
+
value={serverIndex}
|
|
198
|
+
>
|
|
199
|
+
{servers.map((server, index) => (
|
|
200
|
+
<option key={server.url} value={index}>
|
|
201
|
+
{server.url}
|
|
202
|
+
</option>
|
|
203
|
+
))}
|
|
204
|
+
</select>
|
|
205
|
+
</Field>
|
|
206
|
+
) : null}
|
|
207
|
+
|
|
208
|
+
{pathParams.length ? (
|
|
209
|
+
<div className="grid gap-2.5 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]">
|
|
210
|
+
{pathParams.map((param) => (
|
|
211
|
+
<Field key={param.name}>
|
|
212
|
+
<FieldLabel htmlFor={`path-${param.name}`}>
|
|
213
|
+
{param.name}
|
|
214
|
+
</FieldLabel>
|
|
215
|
+
<Input
|
|
216
|
+
id={`path-${param.name}`}
|
|
217
|
+
name={param.name ?? ""}
|
|
218
|
+
onChange={handlePathValueChange}
|
|
219
|
+
placeholder={param.required ? "Required" : "Optional"}
|
|
220
|
+
type="text"
|
|
221
|
+
value={pathValues[param.name ?? ""] ?? ""}
|
|
222
|
+
/>
|
|
223
|
+
</Field>
|
|
224
|
+
))}
|
|
225
|
+
</div>
|
|
226
|
+
) : null}
|
|
227
|
+
|
|
228
|
+
{queryParams.length ? (
|
|
229
|
+
<div className="grid gap-2.5 grid-cols-[repeat(auto-fit,minmax(180px,1fr))]">
|
|
230
|
+
{queryParams.map((param) => (
|
|
231
|
+
<Field key={param.name}>
|
|
232
|
+
<FieldLabel htmlFor={`query-${param.name}`}>
|
|
233
|
+
{param.name}
|
|
234
|
+
</FieldLabel>
|
|
235
|
+
<Input
|
|
236
|
+
id={`query-${param.name}`}
|
|
237
|
+
name={param.name ?? ""}
|
|
238
|
+
onChange={handleQueryValueChange}
|
|
239
|
+
placeholder={param.required ? "Required" : "Optional"}
|
|
240
|
+
type="text"
|
|
241
|
+
value={queryValues[param.name ?? ""] ?? ""}
|
|
242
|
+
/>
|
|
243
|
+
</Field>
|
|
244
|
+
))}
|
|
245
|
+
</div>
|
|
246
|
+
) : null}
|
|
247
|
+
|
|
248
|
+
<Field>
|
|
249
|
+
<FieldLabel htmlFor="auth-token">Auth token</FieldLabel>
|
|
250
|
+
<Input
|
|
251
|
+
id="auth-token"
|
|
252
|
+
onChange={handleAuthTokenChange}
|
|
253
|
+
placeholder="Bearer token"
|
|
254
|
+
type="password"
|
|
255
|
+
value={authToken}
|
|
256
|
+
/>
|
|
257
|
+
</Field>
|
|
258
|
+
|
|
259
|
+
{entry.operation.method === "GET" ? null : (
|
|
260
|
+
<Field>
|
|
261
|
+
<FieldLabel htmlFor="request-body">Request body</FieldLabel>
|
|
262
|
+
<textarea
|
|
263
|
+
className="flex w-full rounded-[var(--field-radius)] border border-input bg-card px-[var(--field-padding-x)] py-[var(--field-padding-y)] font-sans text-base text-foreground shadow-input transition-colors placeholder:text-placeholder-foreground hover:border-input-hover focus:border-ring focus:outline-hidden focus:ring-2 focus:ring-ring/15 focus:ring-offset-1 focus:ring-offset-background"
|
|
264
|
+
id="request-body"
|
|
265
|
+
onChange={handleBodyChange}
|
|
266
|
+
rows={6}
|
|
267
|
+
value={body}
|
|
268
|
+
/>
|
|
269
|
+
</Field>
|
|
270
|
+
)}
|
|
271
|
+
|
|
272
|
+
<Button
|
|
273
|
+
className="w-full"
|
|
274
|
+
disabled={isLoading || !canSend}
|
|
275
|
+
onClick={handleSend}
|
|
276
|
+
type="button"
|
|
277
|
+
>
|
|
278
|
+
{isLoading ? "Sending..." : "Send request"}
|
|
279
|
+
</Button>
|
|
280
|
+
{canSend ? null : (
|
|
281
|
+
<p className="text-sm text-muted-foreground">
|
|
282
|
+
Add a server URL in your OpenAPI spec to enable requests.
|
|
283
|
+
</p>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
{response === null ? null : (
|
|
287
|
+
<div className="rounded-xl border border-border bg-primary/[0.08] p-3">
|
|
288
|
+
<div className="font-semibold">Status: {status}</div>
|
|
289
|
+
<pre className="mt-2 overflow-x-auto">{response}</pre>
|
|
290
|
+
</div>
|
|
291
|
+
)}
|
|
292
|
+
</div>
|
|
293
|
+
</section>
|
|
294
|
+
);
|
|
295
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import dynamic from "next/dynamic";
|
|
2
|
+
|
|
3
|
+
import type { OpenApiEntry } from "@/lib/openapi";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
const ApiPlayground = dynamic(async () => {
|
|
7
|
+
const apiModule = await import("@/components/api/api-playground");
|
|
8
|
+
return { default: apiModule.ApiPlayground };
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const methodColors: Record<string, string> = {
|
|
12
|
+
delete: "bg-red-500",
|
|
13
|
+
get: "bg-blue-500",
|
|
14
|
+
patch: "bg-amber-500",
|
|
15
|
+
post: "bg-green-500",
|
|
16
|
+
put: "bg-orange-500",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const ApiReference = ({
|
|
20
|
+
entry,
|
|
21
|
+
proxyEnabled,
|
|
22
|
+
}: {
|
|
23
|
+
entry: OpenApiEntry;
|
|
24
|
+
proxyEnabled: boolean;
|
|
25
|
+
}) => {
|
|
26
|
+
const { operation } = entry;
|
|
27
|
+
const parameters = operation.parameters ?? [];
|
|
28
|
+
const requestBody = operation.requestBody as
|
|
29
|
+
| { content?: Record<string, { schema?: unknown }> }
|
|
30
|
+
| undefined;
|
|
31
|
+
const responses = operation.responses ?? {};
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div>
|
|
35
|
+
<header className="mb-4 flex items-center gap-4">
|
|
36
|
+
<div
|
|
37
|
+
className={cn(
|
|
38
|
+
"rounded-full px-2.5 py-1.5 text-xs font-semibold uppercase text-white",
|
|
39
|
+
methodColors[operation.method.toLowerCase()] ?? "bg-primary"
|
|
40
|
+
)}
|
|
41
|
+
>
|
|
42
|
+
{operation.method}
|
|
43
|
+
</div>
|
|
44
|
+
<div className="font-mono">{operation.path}</div>
|
|
45
|
+
</header>
|
|
46
|
+
{operation.summary ? (
|
|
47
|
+
<p className="font-semibold">{operation.summary}</p>
|
|
48
|
+
) : null}
|
|
49
|
+
{operation.description ? (
|
|
50
|
+
<p className="text-muted-foreground">{operation.description}</p>
|
|
51
|
+
) : null}
|
|
52
|
+
|
|
53
|
+
{parameters.length ? (
|
|
54
|
+
<section className="mt-7 grid gap-3">
|
|
55
|
+
<h2>Parameters</h2>
|
|
56
|
+
<div className="grid gap-2">
|
|
57
|
+
<div className="grid grid-cols-[120px_80px_80px_1fr] gap-3 rounded-lg border border-border bg-background/60 p-2.5 font-bold">
|
|
58
|
+
<span>Name</span>
|
|
59
|
+
<span>In</span>
|
|
60
|
+
<span>Required</span>
|
|
61
|
+
<span>Description</span>
|
|
62
|
+
</div>
|
|
63
|
+
{parameters.map((param) => {
|
|
64
|
+
const name = (param as { name?: string }).name ?? "";
|
|
65
|
+
const location = (param as { in?: string }).in ?? "";
|
|
66
|
+
const required = (param as { required?: boolean }).required
|
|
67
|
+
? "Yes"
|
|
68
|
+
: "No";
|
|
69
|
+
const description =
|
|
70
|
+
(param as { description?: string }).description ?? "";
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<div
|
|
74
|
+
className="grid grid-cols-[120px_80px_80px_1fr] gap-3 rounded-lg border border-border bg-background/60 p-2.5"
|
|
75
|
+
key={name}
|
|
76
|
+
>
|
|
77
|
+
<span>{name}</span>
|
|
78
|
+
<span>{location}</span>
|
|
79
|
+
<span>{required}</span>
|
|
80
|
+
<span>{description}</span>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
})}
|
|
84
|
+
</div>
|
|
85
|
+
</section>
|
|
86
|
+
) : null}
|
|
87
|
+
|
|
88
|
+
{requestBody?.content ? (
|
|
89
|
+
<section className="mt-7 grid gap-3">
|
|
90
|
+
<h2>Request Body</h2>
|
|
91
|
+
<pre className="overflow-x-auto rounded-lg bg-code p-3 text-code-foreground">
|
|
92
|
+
{JSON.stringify(requestBody.content, null, 2)}
|
|
93
|
+
</pre>
|
|
94
|
+
</section>
|
|
95
|
+
) : null}
|
|
96
|
+
|
|
97
|
+
{Object.keys(responses).length ? (
|
|
98
|
+
<section className="mt-7 grid gap-3">
|
|
99
|
+
<h2>Responses</h2>
|
|
100
|
+
<div className="grid gap-2">
|
|
101
|
+
{Object.entries(responses).map(([status, response]) => {
|
|
102
|
+
const description =
|
|
103
|
+
(response as { description?: string }).description ?? "";
|
|
104
|
+
return (
|
|
105
|
+
<div
|
|
106
|
+
className="flex justify-between rounded-lg border border-border bg-background/60 p-2.5"
|
|
107
|
+
key={status}
|
|
108
|
+
>
|
|
109
|
+
<span className="font-semibold">{status}</span>
|
|
110
|
+
<span className="text-muted-foreground">{description}</span>
|
|
111
|
+
</div>
|
|
112
|
+
);
|
|
113
|
+
})}
|
|
114
|
+
</div>
|
|
115
|
+
</section>
|
|
116
|
+
) : null}
|
|
117
|
+
|
|
118
|
+
<ApiPlayground entry={entry} proxyEnabled={proxyEnabled} />
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ContentEntry } from "@repo/previewing";
|
|
2
|
+
import Link from "next/link";
|
|
3
|
+
|
|
4
|
+
import { toDocHref } from "@/lib/routes";
|
|
5
|
+
|
|
6
|
+
type CollectionEntry = Extract<ContentEntry, { kind: "entry" }>;
|
|
7
|
+
|
|
8
|
+
const formatDate = (value?: string) => {
|
|
9
|
+
if (!value) {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
const date = new Date(value);
|
|
13
|
+
if (Number.isNaN(date.getTime())) {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
return date.toLocaleDateString("en-US", {
|
|
17
|
+
day: "numeric",
|
|
18
|
+
month: "short",
|
|
19
|
+
year: "numeric",
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const formatPrice = (price?: number, currency?: string) => {
|
|
24
|
+
if (price === undefined || !currency) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return new Intl.NumberFormat("en-US", {
|
|
29
|
+
currency,
|
|
30
|
+
style: "currency",
|
|
31
|
+
}).format(price);
|
|
32
|
+
} catch {
|
|
33
|
+
return `${price} ${currency}`;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const buildMeta = (entry: CollectionEntry) => {
|
|
38
|
+
const frontmatter = entry.frontmatter as Record<string, unknown>;
|
|
39
|
+
switch (entry.type) {
|
|
40
|
+
case "blog": {
|
|
41
|
+
const date = formatDate(frontmatter.date as string | undefined);
|
|
42
|
+
const tags = Array.isArray(frontmatter.tags)
|
|
43
|
+
? frontmatter.tags.join(", ")
|
|
44
|
+
: null;
|
|
45
|
+
return [date, tags ? `Tags: ${tags}` : null].filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
case "courses": {
|
|
48
|
+
const order = frontmatter.order as number | undefined;
|
|
49
|
+
return order === undefined ? [] : [`Lesson ${order}`];
|
|
50
|
+
}
|
|
51
|
+
case "products": {
|
|
52
|
+
const sku = frontmatter.sku as string | undefined;
|
|
53
|
+
const price = formatPrice(
|
|
54
|
+
frontmatter.price as number | undefined,
|
|
55
|
+
frontmatter.currency as string | undefined
|
|
56
|
+
);
|
|
57
|
+
return [sku ? `SKU ${sku}` : null, price].filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
case "notes":
|
|
60
|
+
case "todos": {
|
|
61
|
+
const date = formatDate(frontmatter.date as string | undefined);
|
|
62
|
+
return date ? [date] : [];
|
|
63
|
+
}
|
|
64
|
+
case "forms": {
|
|
65
|
+
const fields = Array.isArray(frontmatter.fields)
|
|
66
|
+
? frontmatter.fields.length
|
|
67
|
+
: null;
|
|
68
|
+
return fields ? [`${fields} fields`] : [];
|
|
69
|
+
}
|
|
70
|
+
case "sheets": {
|
|
71
|
+
const columns = Array.isArray(frontmatter.columns)
|
|
72
|
+
? frontmatter.columns.length
|
|
73
|
+
: null;
|
|
74
|
+
return columns ? [`${columns} columns`] : [];
|
|
75
|
+
}
|
|
76
|
+
default: {
|
|
77
|
+
return [];
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const CollectionIndex = ({
|
|
83
|
+
entries,
|
|
84
|
+
basePath,
|
|
85
|
+
}: {
|
|
86
|
+
entries: CollectionEntry[];
|
|
87
|
+
basePath: string;
|
|
88
|
+
}) => (
|
|
89
|
+
<div className="grid gap-4">
|
|
90
|
+
{entries.map((entry) => {
|
|
91
|
+
const meta = buildMeta(entry);
|
|
92
|
+
return (
|
|
93
|
+
<Link
|
|
94
|
+
className="block rounded-xl border border-border bg-surface p-4.5 transition-[border-color,box-shadow] hover:border-primary hover:shadow-md"
|
|
95
|
+
href={toDocHref(entry.slug, basePath)}
|
|
96
|
+
key={`${entry.collectionId}-${entry.slug}`}
|
|
97
|
+
>
|
|
98
|
+
<div className="text-lg font-semibold">{entry.title}</div>
|
|
99
|
+
{entry.description ? (
|
|
100
|
+
<p className="mt-1.5 text-muted-foreground">{entry.description}</p>
|
|
101
|
+
) : null}
|
|
102
|
+
{meta.length ? (
|
|
103
|
+
<div className="mt-2 text-sm text-muted-foreground">
|
|
104
|
+
{meta.join(" | ")}
|
|
105
|
+
</div>
|
|
106
|
+
) : null}
|
|
107
|
+
{entry.type === "slides" ? (
|
|
108
|
+
<div className="mt-2 text-sm text-muted-foreground">Slide deck</div>
|
|
109
|
+
) : null}
|
|
110
|
+
</Link>
|
|
111
|
+
);
|
|
112
|
+
})}
|
|
113
|
+
</div>
|
|
114
|
+
);
|