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
8,777 changes: 6,224 additions & 2,553 deletions package-lock.json

Large diffs are not rendered by default.

13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
"format-code-blocks": "bun run ./scripts/format-code-blocks.ts",
"check-code-blocks": "./scripts/check-code-blocks.sh",
"ai": "bun run scripts/ai.ts",
"search:create-indexes": "bun run scripts/search/api-reference-index.ts && bun run scripts/search/documentation-index.ts && bun run scripts/search/sdk-reference-index.ts",
"search:update-indexes": "bun run scripts/search/api-reference-index.ts update && bun run scripts/search/documentation-index.ts update && bun run scripts/search/sdk-reference-index.ts update",
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
Expand All @@ -40,6 +42,9 @@
"@radix-ui/react-form": "^0.1.0",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/themes": "^3.1.6",
"@scalar/api-client": "^2.5.22",
"@scalar/api-client-react": "^1.3.27",
"@scalar/api-reference-react": "^0.7.38",
"@scalar/openapi-parser": "^0.10.14",
"@scalar/openapi-types": "^0.2.0",
"axios": "^1.7.9",
Expand All @@ -55,9 +60,11 @@
"react-dom": "^18.0.0",
"react-markdown": "^10.1.0",
"sass": "^1.80.6",
"search-insights": "^2.17.3",
"supertokens-website": "^20.1.5",
"tailwind-merge": "^2.5.5",
"uuid": "^11.0.3"
"uuid": "^11.0.3",
"zod": "^4.0.17"
},
"devDependencies": {
"@docusaurus/faster": "^3.8.1",
Expand All @@ -75,15 +82,13 @@
"fs-extra": "^11.2.0",
"js-yaml": "^4.1.0",
"jsdom": "^26.0.0",
"openai": "^4.73.1",
"ora": "^8.2.0",
"prettier": "3.3.3",
"tree-sitter": "^0.21.1",
"tree-sitter-typescript": "^0.23.2",
"typescript": "~5.6.2",
"vitest": "^3.0.9",
"ws": "^8.18.0",
"zod": "^3.24.2"
"ws": "^8.18.0"
},
"browserslist": {
"production": [
Expand Down
85 changes: 85 additions & 0 deletions src/components/APIRequest/APIRequest.scss
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,88 @@
}
}
}

// API Request Client
.scalar-app {
.scalar-container {
background: var(--black-a8);
}

ul[role="listbox"] {
li {
& > div {
margin: 0;
}

& > span {
margin: 0;
}

&:before {
content: none;
}
}

li[class*="group/item"] {
min-height: 30px;
}

div[class*="contents"][role="group"] {
div[class*="truncate"] {
display: none;
}
}
}
h1 {
font-size: var(--scalar-font-size-1) !important;
line-height: var(--scalar-line-height-1) !important;
}

h2 {
font-size: var(--scalar-font-size-2) !important;
line-height: var(--scalar-line-height-2) !important;
}

h3 {
font-size: var(--scalar-font-size-3) !important;
line-height: var(--scalar-line-height-3) !important;
}

h4 {
font-size: var(--scalar-font-size-4) !important;
line-height: var(--scalar-line-height-4) !important;
}

h5 {
font-size: var(--scalar-font-size-5) !important;
line-height: var(--scalar-line-height-5) !important;
}

h6 {
font-size: var(--scalar-font-size-6) !important;
line-height: var(--scalar-line-height-6) !important;
}

td {
border-color: var(--gray-6) !important;
}
tr {
border-color: var(--gray-6) !important;
}

button[aria-expanded="true"] {
border-color: var(--gray-6) !important;
}
}

:root {
--scalar-color-1: var(--gray-12);
--scalar-color-2: var(--gray-11);
--scalar-color-3: var(--gray-10);
--scalar-color-accent: var(--orange-11);
--scalar-background-1: var(--color-background);
--scalar-background-2: var(--gray-2);
--scalar-background-3: var(--gray-3);
--scalar-background-accent: var(--accent-a3);
--scalar-border-color: var(--gray-6);
}
5 changes: 4 additions & 1 deletion src/components/APIRequest/APIRequest.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Badge, Card, Text, Code, Skeleton, Flex, Heading, HoverCard, Box, Separator, Callout } from "@radix-ui/themes";
import { DocItemContext } from "@site/src/context";
import { ApiClientModalProvider, useApiClientModal } from "@scalar/api-client-react";
import InfoCircledIcon from "/img/icons/info-circled.svg";
import CopyIcon from "/img/icons/copy.svg";
import CheckIcon from "/img/icons/check.svg";
Expand All @@ -14,6 +15,7 @@ import { APIRequestParametersCard } from "./APIRequestParametersCard";
import * as Accordion from "@radix-ui/react-accordion";
import { APIRequestSchemaCard } from "./APIRequestSchemaCard";
import type { OpenAPIV3 } from "@scalar/openapi-types";
import "@scalar/api-client-react/style.css";

import "./APIRequest.scss";

Expand Down Expand Up @@ -75,7 +77,7 @@ export function APIRequestProvider({
apiName,
}}
>
{children}
<APIRequestClient.Provider>{children}</APIRequestClient.Provider>
</APIRequestContext.Provider>
);
}
Expand All @@ -97,6 +99,7 @@ export function APIRequestTitle() {
}

import Markdown from "react-markdown";
import { APIRequestClient } from "./APIRequestClient";

export function APIRequestDescription() {
const { operation } = useContext(APIRequestContext);
Expand Down
86 changes: 86 additions & 0 deletions src/components/APIRequest/APIRequestClient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { useCallback, useContext, forwardRef, ReactNode, useEffect, useMemo, useState, createContext } from "react";
import { APIRequestContext } from "./APIRequest";
import { useDocPageData, useLoadOpenApiDocument } from "@site/src/hooks";
import { ApiClientModalProvider, useApiClientModal } from "@scalar/api-client-react";

import type { OpenAPIV3 } from "@scalar/openapi-types";
import { Slot } from "@radix-ui/themes";
import { replaceVariablesForScalar, replaceVariablesInApiPath } from "@site/src/lib";

type ScalarContextType = {
path: string;
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
};

const ScalarContext = createContext<ScalarContextType>({} as ScalarContextType);

function APIRequestClientProvider({ children }: { children: React.ReactNode }) {
const { apiName, path, method } = useContext(APIRequestContext);
const apiDocument = useLoadOpenApiDocument(apiName);
const apiDomain = useDocPageData("apiDomain");
const apiBasePath = useDocPageData("apiBasePath");
const coreDomain = useDocPageData("coreDomain");
const tenantId = useDocPageData("tenantId");
const appId = useDocPageData("appId");

const parsedApiDocument = useMemo(() => {
if (!apiDocument) return null;

return replaceVariablesForScalar(apiDocument, apiName, {
apiDomain,
apiBasePath,
coreDomain,
tenantId,
appId,
});
}, [apiDocument, apiDomain, apiName, coreDomain, apiBasePath, tenantId, appId]);

const parsedPath = useMemo(() => {
return replaceVariablesInApiPath(path, { apiBasePath, tenantId, appId });
}, [path, apiBasePath, tenantId, appId]);

if (!apiDocument) return children;

return (
<ApiClientModalProvider
configuration={{
content: parsedApiDocument,
showSidebar: false,
proxyUrl: "https://proxy.scalar.com",
theme: "default",
persistAuth: false,
}}
>
<ScalarContext.Provider value={{ path: parsedPath, method }}>{children}</ScalarContext.Provider>
</ApiClientModalProvider>
);
}

const APIRequestClientTrigger = forwardRef<HTMLElement, { children: ReactNode }>(
function APIRequestClientTrigger(props, ref) {
const { children } = props;
const { path, method } = useContext(ScalarContext);
const client = useApiClientModal();

useEffect(() => {
if (!client) return;
const mountingEl = document.getElementById("docusaurus-scalar-root");
client.mount(mountingEl);
}, [client]);

const onClick = useCallback(() => {
client.open({ path, method });
}, [client, path, method]);

return (
<Slot ref={ref} onClick={onClick}>
{children}
</Slot>
);
},
);

export const APIRequestClient = {
Provider: APIRequestClientProvider,
Trigger: APIRequestClientTrigger,
};
89 changes: 75 additions & 14 deletions src/components/APIRequest/APIRequestCodeSnippet.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,30 @@
import { Text, Flex, Select, Card, Separator } from "@radix-ui/themes";
import { useDocPageData } from "@site/src/hooks";
import { DocPageState, docPageStore, generateCodeSnippetFromAPIRequest } from "@site/src/lib";
import { Text, Flex, Button, Select, Card, Separator } from "@radix-ui/themes";
import { useDocPageData, useLoadOpenApiDocument } from "@site/src/hooks";
import {
DocPageState,
docPageStore,
generateCodeSnippetFromAPIRequest,
replaceVariablesForScalar,
replaceVariablesInApiPath,
} from "@site/src/lib";
import CodeBlock from "@site/src/theme/CodeBlock";
import { useContext, useMemo } from "react";
import { useCallback, useContext, useMemo } from "react";
import { useApiClientModal } from "@scalar/api-client-react";
import { APIRequestContext } from "./APIRequest";
import * as z from "zod";
import { APIRequestClient } from "./APIRequestClient";
import { APIRequestEditParametersModal } from "./APIRequestEditParametersModal";
import { Play, Edit } from "lucide-react";

export function APIRequestCodeSnippetSegmentedControl() {
const language = useDocPageData("apiRequestExampleLanguage");
const { apiName } = useContext(APIRequestContext);
const apiDomain = useDocPageData("apiDomain");
const coreDomain = useDocPageData("coreDomain");
const apiDocument = useLoadOpenApiDocument(apiName);

const { path, method } = useContext(APIRequestContext);
const apiClient = useApiClientModal();

const title = useMemo(() => {
if (language === "shell") return "Curl Example";
Expand All @@ -15,6 +33,23 @@ export function APIRequestCodeSnippetSegmentedControl() {
if (language === "go") return "HTTP Example";
}, [language]);

const shouldConfigureServer = useMemo(() => {
const server = apiName === "fdi" ? apiDomain : coreDomain;
return !z.string().url().safeParse(server).success;
}, [apiName, apiDomain, coreDomain]);

const onEditConfigurationSuccess = useCallback(
async (data) => {
if (!apiClient) return;
await apiClient.updateConfig({
content: replaceVariablesForScalar(apiDocument, apiName, data),
});
if (!shouldConfigureServer) return;
apiClient.open({ path: replaceVariablesInApiPath(path, data), method });
},
[shouldConfigureServer, apiClient, path, method, apiDocument, apiName],
);

return (
<Flex direction="column" gap="4" p="0" asChild>
<Card className="api-request-code-snippet">
Expand Down Expand Up @@ -43,39 +78,65 @@ export function APIRequestCodeSnippetSegmentedControl() {
<CodeSnippetSection language="nodejs" />
<CodeSnippetSection language="python" />
<CodeSnippetSection language="go" />
<Flex justify="end" px="3" py="2" gap="2">
<APIRequestEditParametersModal.Root onSuccess={onEditConfigurationSuccess}>
<APIRequestEditParametersModal.Trigger>
<Button color="gray" highContrast variant="outline" size="1">
<Edit width="12px" /> Configure Server
</Button>
</APIRequestEditParametersModal.Trigger>
{shouldConfigureServer ? (
<APIRequestEditParametersModal.Trigger>
<Button variant="solid" highContrast color="gray" size="1">
<Play width="12px" /> Test Request
</Button>
</APIRequestEditParametersModal.Trigger>
) : (
<APIRequestClient.Trigger>
<Button variant="solid" highContrast color="gray" size="1">
<Play width="12px" /> Test Request
</Button>
</APIRequestClient.Trigger>
)}
</APIRequestEditParametersModal.Root>
</Flex>
</Card>
</Flex>
);
}

function CodeSnippetSection({ language }: { language: DocPageState["apiRequestExampleLanguage"] }) {
const { operation, security, method, path, apiName } = useContext(APIRequestContext);
// const tenantType = useDocPageData("tenantType");
// const hasTenantRequirement = useMemo(() => {
// return path.includes("{tenantId}");
// }, [path]);
const apiRequestExampleLanguage = useDocPageData("apiRequestExampleLanguage");
const apiDomain = useDocPageData("apiDomain");
const coreDomain = useDocPageData("coreDomain");
const apiBasePath = useDocPageData("apiBasePath");
const tenantId = useDocPageData("tenantId");
const appId = useDocPageData("appId");
const codeBlockLanguage = useMemo(() => {
if (language === "shell") return "bash";
if (language === "nodejs") return "typescript";
if (language === "python") return "python";
if (language === "go") return "go";
}, [language]);
const parsedPath = useMemo(() => {
return replaceVariablesInApiPath(path, { apiBasePath, tenantId, appId });
}, [path, apiBasePath, tenantId, appId]);

const apiDomain = useMemo(() => {
if (apiName === "fdi") return "{backendAPIDomain}";
return "{coreAPIDomain}";
}, [apiName]);
const host = useMemo(() => {
const url = apiName === "fdi" ? apiDomain : coreDomain;
return url.endsWith("/") ? url.slice(0, -1) : url;
}, [apiName, apiDomain, coreDomain]);

const snippet = useMemo(() => {
if (!operation) return null;
return generateCodeSnippetFromAPIRequest({
host: apiDomain,
host,
operation,
environment: language,
method,
security,
path,
path: parsedPath,
});
}, [operation, method, path, language]);

Expand Down
Loading