Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@
}

/* kill the vertical guide on every block rendered by Code Hike */
/* pre[data-ch="true"] .flex {
pre[data-ch="true"] .flex {
border-left: none !important;
} */
}
}
77 changes: 50 additions & 27 deletions components/openapi/api-playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,53 @@ export function APIPlayground({
}: APIPlaygroundProps) {
const [loading, setLoading] = useState(false);
const [response, setResponse] = useState<APIResponse | null>(null);
const [formData, setFormData] = useState<Record<string, any>>({});

const getInitialFormData = () => {
const initialData: Record<string, any> = {};

// Add example values for parameters
if (operation.parameters) {
for (const param of operation.parameters) {
if (param.example !== undefined) {
initialData[param.name] = param.example.toString();
}
}
}

// Add example values for request body
if (operation.requestBody) {
const bodySchema =
operation.requestBody.content?.["application/json"]?.schema;
if (bodySchema?.type === "object" && bodySchema.properties) {
const exampleBody =
operation.requestBody.content?.["application/json"]?.example;
if (exampleBody && typeof exampleBody === "object") {
for (const [key, value] of Object.entries(exampleBody)) {
initialData[`body.${key}`] =
typeof value === "string" ? value : JSON.stringify(value);
}
}
}
}

return initialData;
};

const [formData, setFormData] =
useState<Record<string, any>>(getInitialFormData());
const [openSections, setOpenSections] = useState<string[]>([]);
const formRef = useRef<HTMLFormElement>(null);

// Group parameters by type
const pathParams = operation.parameters?.filter((p) => p.in === "path") || [];
const queryParams =
operation.parameters?.filter((p) => p.in === "query") || [];
const headerParams =
operation.parameters?.filter((p) => p.in === "header") || [];
const hasRequestBody = !!operation.requestBody;

// Build display URL (without base URL)
const buildDisplayUrl = () => {
let path = operation.path;
// Replace path parameters

if (operation.parameters) {
for (const param of operation.parameters) {
if (param.in === "path" && formData[param.name]) {
Expand All @@ -71,9 +102,9 @@ export function APIPlayground({
}
}

// Add query parameters
const queryParams = operation.parameters?.filter(
(p) => p.in === "query" && formData[p.name]
(p) =>
p.in === "query" && formData[p.name] && formData[p.name].trim() !== "",
);
const queryString =
queryParams && queryParams.length > 0
Expand All @@ -85,7 +116,6 @@ export function APIPlayground({
return `${path}${queryString}`;
};

// Build full URL (with base URL for requests)
const buildUrl = () => {
const displayUrl = buildDisplayUrl();
return `${baseUrl || "https://api.hiro.so"}${displayUrl}`;
Expand All @@ -99,34 +129,30 @@ export function APIPlayground({
headerParams.length > 0 ||
hasRequestBody;

// Check if all required fields are filled
const isFormValid = () => {
// Check required path parameters
if (pathParams.some((p) => p.required && !formData[p.name])) {
return false;
}

// Check required query parameters
if (queryParams.some((p) => p.required && !formData[p.name])) {
return false;
}

// Check required header parameters
if (headerParams.some((p) => p.required && !formData[p.name])) {
return false;
}

// Check required body fields
if (operation.requestBody?.required) {
const bodySchema =
operation.requestBody.content?.["application/json"]?.schema;
if (bodySchema?.type === "object" && bodySchema.properties) {
const hasRequiredBodyFields = Object.entries(
bodySchema.properties
bodySchema.properties,
).some(
([propName, propSchema]: [string, any]) =>
bodySchema.required?.includes(propName) &&
!formData[`body.${propName}`]
!formData[`body.${propName}`],
);
if (hasRequiredBodyFields) {
return false;
Expand All @@ -146,17 +172,16 @@ export function APIPlayground({
if (headerParams.some((p) => p.required && !formData[p.name]))
requiredSections.push("header");

// Check for required body fields
if (operation.requestBody?.required) {
const bodySchema =
operation.requestBody.content?.["application/json"]?.schema;
if (bodySchema?.type === "object" && bodySchema.properties) {
const hasRequiredBodyFields = Object.entries(
bodySchema.properties
bodySchema.properties,
).some(
([propName, propSchema]: [string, any]) =>
bodySchema.required?.includes(propName) &&
!formData[`body.${propName}`]
!formData[`body.${propName}`],
);
if (hasRequiredBodyFields) {
requiredSections.push("body");
Expand All @@ -169,7 +194,6 @@ export function APIPlayground({
return;
}

// Build request body from individual fields if needed
let finalFormData = { ...formData };
if (operation.requestBody) {
const bodySchema =
Expand All @@ -178,7 +202,7 @@ export function APIPlayground({
const bodyObject: Record<string, any> = {};

for (const [propName, propSchema] of Object.entries(
bodySchema.properties
bodySchema.properties,
) as [string, any][]) {
const fieldValue = formData[`body.${propName}`];
if (fieldValue !== undefined && fieldValue !== "") {
Expand Down Expand Up @@ -265,7 +289,6 @@ export function APIPlayground({
}
}

// Submit the request
handleSubmit(finalFormData);
};

Expand All @@ -280,7 +303,7 @@ export function APIPlayground({
{
proxyUrl: playgroundOptions?.proxyUrl,
auth: playgroundOptions?.defaultAuth,
}
},
);
setResponse(result);
} catch (error) {
Expand All @@ -298,7 +321,7 @@ export function APIPlayground({
<div
className={cn(
"px-3 py-2 bg-card border-b border-border",
!hasParameters && "border-b-0"
!hasParameters && "border-b-0",
)}
>
<div className="flex items-center gap-2">
Expand Down Expand Up @@ -338,7 +361,7 @@ export function APIPlayground({
open={openSections.includes("path")}
onOpenChange={(open) => {
setOpenSections((prev) =>
open ? [...prev, "path"] : prev.filter((s) => s !== "path")
open ? [...prev, "path"] : prev.filter((s) => s !== "path"),
);
}}
>
Expand Down Expand Up @@ -370,7 +393,7 @@ export function APIPlayground({
open={openSections.includes("query")}
onOpenChange={(open) => {
setOpenSections((prev) =>
open ? [...prev, "query"] : prev.filter((s) => s !== "query")
open ? [...prev, "query"] : prev.filter((s) => s !== "query"),
);
}}
>
Expand Down Expand Up @@ -404,7 +427,7 @@ export function APIPlayground({
setOpenSections((prev) =>
open
? [...prev, "header"]
: prev.filter((s) => s !== "header")
: prev.filter((s) => s !== "header"),
);
}}
>
Expand Down Expand Up @@ -434,7 +457,7 @@ export function APIPlayground({
open={openSections.includes("body")}
onOpenChange={(open) => {
setOpenSections((prev) =>
open ? [...prev, "body"] : prev.filter((s) => s !== "body")
open ? [...prev, "body"] : prev.filter((s) => s !== "body"),
);
}}
>
Expand Down Expand Up @@ -469,7 +492,7 @@ export function APIPlayground({
"inline-flex items-center rounded border transition-colors font-fono text-xs px-1.5 py-0 h-5",
response.status >= 200 && response.status < 300
? "bg-[#e7f7e7] text-[#4B714D] border-[#c2ebc4] dark:bg-background dark:text-[#c2ebc4] dark:border-[#c2ebc4]"
: "bg-[#ffe7e7] text-[#8A4B4B] border-[#ffc2c2] dark:bg-background dark:text-[#ffc2c2] dark:border-[#ffc2c2]"
: "bg-[#ffe7e7] text-[#8A4B4B] border-[#ffc2c2] dark:bg-background dark:text-[#ffc2c2] dark:border-[#ffc2c2]",
)}
>
{response.status} {response.statusText || ""}
Expand Down Expand Up @@ -576,7 +599,7 @@ function MethodBadge({ method }: { method: string }) {
<span
className={cn(
"inline-flex items-center justify-center rounded px-2 py-1 text-xs font-semibold border",
getMethodClasses()
getMethodClasses(),
)}
>
{upperMethod}
Expand Down
2 changes: 2 additions & 0 deletions components/openapi/api-playground/request-builder.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,8 @@ export const RequestBuilder = forwardRef<HTMLFormElement, RequestBuilderProps>(
hasError && "border-red-500",
"font-fono"
)}
disabled={false}
type="text"
/>

{hasError && (
Expand Down
2 changes: 1 addition & 1 deletion components/openapi/api-playground/request-executor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function executeRequest(
operation.parameters?.filter((p) => p.in === "query") || [];
for (const param of queryParameters) {
const value = formData[param.name];
if (!value) continue;
if (!value || value.trim() === "") continue;

if (clarityConversion && param.schema?.["x-clarity-type"]) {
try {
Expand Down
3 changes: 2 additions & 1 deletion content/docs/apis/ordinals-api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Ordinals API
sidebarTitle: Overview
description: Fast, reliable data for Bitcoin ordinals and BRC-20 tokens via REST.
llm: false
---

## Overview
Expand All @@ -26,4 +27,4 @@ For more usage examples, click [here](/apis/ordinals-api/usage).
type: help
### Need help building with the Ordinals API?
Reach out to us on the <span className="font-bold">#ordinals</span> channel on [Discord](https://stacks.chat/) under the Hiro Developer Tools section. There's also a [weekly office hours](https://www.addevent.com/event/kI22007085) call every Wednesday at 1pm ET.
:::
:::
3 changes: 2 additions & 1 deletion content/docs/apis/platform-api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Platform API
sidebarTitle: Overview
description: Programmatic access to devnet and chainhook management via REST.
llm: false
---

## Overview
Expand All @@ -26,4 +27,4 @@ For more usage examples, click [here](/apis/platform-api/usage).
type: help
### Need help building with the Platform API?
Reach out to us on the <span className="font-bold">#hiro-platform</span> channel on [Discord](https://stacks.chat/) under the Hiro Developer Tools section. There's also a [weekly office hours](https://www.addevent.com/event/kI22007085) call every Wednesday at 1pm ET.
:::
:::
3 changes: 2 additions & 1 deletion content/docs/apis/runes-api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Runes API
sidebarTitle: Overview
description: Fast, reliable data for Bitcoin Runes via REST.
llm: false
---

## Overview
Expand All @@ -26,4 +27,4 @@ For more usage examples, click [here](/apis/runes-api/usage).
type: help
### Need help building with the Runes API?
Reach out to us on the <span className="font-bold">#ordinals</span> channel on [Discord](https://stacks.chat/) under the Hiro Developer Tools section. There's also a [weekly office hours](https://www.addevent.com/event/kI22007085) call every Wednesday at 1pm ET.
:::
:::
3 changes: 2 additions & 1 deletion content/docs/apis/signer-metrics-api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Signer Metrics API
sidebarTitle: Overview
description: Monitor and analyze signer behavior on the Stacks network via REST.
llm: false
---

## Overview
Expand All @@ -26,4 +27,4 @@ For more usage examples, click [here](/apis/signer-metrics-api/usage).
type: help
### Need help building with the Signer Metrics API?
Reach out to us on the <span className="font-bold">#api</span> channel on [Discord](https://stacks.chat/) under the Hiro Developer Tools section. There's also a [weekly office hours](https://www.addevent.com/event/kI22007085) call every Wednesday at 1pm ET.
:::
:::
1 change: 1 addition & 0 deletions content/docs/apis/stacks-blockchain-api/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
title: Stacks Blockchain API
sidebarTitle: Overview
description: Fast, reliable blockchain data for the Stacks ecosystem via REST.
llm: false
---

## Overview
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: Block by height (v3)
sidebarTitle: Block by height (v3)
description: Retrieves a block by its height using the v3 endpoint.
full: true
isRpc: true
---

<APIPage
document="./openapi/stacks-node-rpc-api.json"
operations={[{ path: '/v3/blocks/height/{block_height}', method: 'get' }]}
hasHead={false}
/>
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
"recent-blocks",
"block-by-hash",
"block-by-height",
"block-by-height-v3",
"block-by-burn-block-hash",
"block-by-burn-block-height",
"block-proposal"
"block-proposal",
"upload-block"
],
"defaultOpen": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: Upload block
sidebarTitle: Upload block
description: Upload a block to the node.
full: true
isRpc: true
---

<APIPage
document="./openapi/stacks-node-rpc-api.json"
operations={[{ path: '/v3/blocks/upload', method: 'post' }]}
hasHead={false}
/>
13 changes: 13 additions & 0 deletions content/docs/apis/stacks-blockchain-api/reference/info/health.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
title: Health check
sidebarTitle: Health
description: Get health information about the node's synchronization status.
full: true
isRpc: true
---

<APIPage
document="./openapi/stacks-node-rpc-api.json"
operations={[{ path: '/v3/health', method: 'get' }]}
hasHead={false}
/>
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"pages": [
"status",
"core-api",
"health",
"network-block-time",
"network-given-block-time",
"total-and-unlocked-stx-supply",
Expand Down
Loading