"use client";
import { MockRequestBaseURL } from "@/components/fumastudio/base-url";
import { CodeBlock } from "@/components/fumastudio/code-block";
import { colors } from "@/components/fumastudio/colors";
import { CheckCircleFilled } from "@/components/fumastudio/icons";
import { AuthSchemes } from "@/components/fumastudio/scheme";
import { ServerDialog } from "@/components/fumastudio/server-dialog";
import { ResponseSnippets } from "@/components/fumastudio/snippet";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Button } from "@/components/ui/button";
import { ButtonGroup } from "@/components/ui/button-group";
import {
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
} from "@/components/ui/command";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/ui/popover";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
} from "@/components/ui/select";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { useClipboard } from "@/hooks/use-clipboard";
import { useDebounce } from "@/hooks/use-debounce";
import { useDynamicSegment } from "@/hooks/use-dynamic-segment";
import { httpCodes } from "@/lib/http-codes";
import { cn } from "@/lib/utils";
import { ApiKeyHeaderScheme, ApiKeyQueryScheme } from "@trythis/js-schema";
import { Snippet } from "@trythis/nextjs";
import { PageEntry, Server } from "@trythis/nextjs/utils";
import {
AlertTriangle,
AlertTriangleIcon,
CheckIcon,
ChevronDownIcon,
CircleCheck,
CircleX,
Copy,
Info,
Loader,
ShareIcon,
} from "lucide-react";
import parseJson from "parse-json";
import React, {
ChangeEvent,
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
const NO_AUTH = {
type: "null",
description: "No auth configured",
} as const;
type MockRequestProps = {
children: React.ReactNode;
open: boolean;
url: string;
onOpenChange: (state: boolean) => void;
};
const MockRequest = (props: MockRequestProps) => {
const [isOpen, setIsOpen] = useState(false);
const {
isLoading,
responseStatus,
requestBody,
responseBody,
responseHeaders,
contentType,
responseSnippets,
onSend,
setRequestBody,
} = useMockRequest();
return (
<Dialog open={props.open} onOpenChange={props.onOpenChange}>
<DialogTrigger asChild>{props.children}</DialogTrigger>
<DialogContent
className="p-0 max-w-4xl xl:max-w-4xl h-[99dvh] max-h-[calc(100dvh-2rem)] sm:max-h-[calc(100dvh-3rem)] md:max-h-[calc(100dvh-6rem)]"
hiddenCloseButton>
<DialogHeader className="sr-only">
<DialogTitle>Try it</DialogTitle>
<DialogDescription>
Test requests as fast as possible
</DialogDescription>
</DialogHeader>
<div className="relative flex flex-col gap-y-2.5 p-2 w-[calc(100%-2rem)]x bg-background dark:bg-background-dark rounded-3xl border-standard">
<div className="sticky top-0 z-50 bg-background-light dark:bg-background-dark flex gap-x-2 h-10">
<RequestDropdown className="max-w-60 w-60" />
<MockRequestBaseURL
url={props.url}
className="w-full scrollbar-hide"
/>
<ButtonGroup>
<Button
variant="outline"
disabled={isLoading}
onClick={() => onSend()}>
{isLoading ? (
<>
<Loader className="w-4 animate-spin duration-300x" />{" "}
Wait
</>
) : (
"Send"
)}
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="pl-2!"
disabled={
isLoading
}>
<ChevronDownIcon />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="[--radius:1rem]">
<DropdownMenuGroup>
<DropdownMenuItem
onClick={() =>
setIsOpen(
!isOpen,
)
}>
<AlertTriangleIcon />
List Servers
</DropdownMenuItem>
<DropdownMenuItem>
<ShareIcon />
Save options
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</ButtonGroup>
</div>
<ServerDialog
open={isOpen}
onOpenChange={setIsOpen}
/>
<div className="grid grid-cols-2 gap-x-1.5 h-[calc(100%-2.5rem)]">
<Accordion
className="w-full px-2"
collapsible
defaultValue="Authorization"
type="single">
<AccordionItem value="Authorization">
<AccordionTrigger className="py-1.5 text-[15px] leading-6 hover:no-underline text-sm">
Authorization
</AccordionTrigger>
<AccordionContent className="h-full py-2.5">
<AuthSchemeDropdown />
</AccordionContent>
</AccordionItem>
<AccordionItem value="Body">
<AccordionTrigger className="py-1.5 text-[15px] leading-6 hover:no-underline text-sm">
Body
</AccordionTrigger>
<AccordionContent className="h-full py-2.5">
<TextEditor />
</AccordionContent>
</AccordionItem>
</Accordion>
<div className="flex flex-col overflow-y-scroll gap-y-2.5 scrollbar-hide">
{responseBody &&
responseStatus &&
contentType && (
<div className="flex flex-col bg-accent border-input border gap-0 px-1.5 pb-1.5 rounded-xl">
<ResponseBody
status={
responseStatus
}
body={
responseBody
}
headers={
responseHeaders
}
contentType={
contentType
}
/>
</div>
)}
{responseSnippets &&
responseSnippets.length > 0 && (
<div className="flex flex-col bg-accent border-input border gap-0 px-1.5 pb-1.5 rounded-xl">
<ResponseSnippets
snippets={
responseSnippets
}
/>
</div>
)}
</div>
</div>
</div>
</DialogContent>
</Dialog>
);
};
const RequestDropdown = (props: { className?: string }) => {
const { requests, selectedRequest, selectRequest } = useMockRequest();
const [open, setOpen] = useState<boolean>(false);
const [value, setValue] = useState<string>(selectedRequest?.slug || "");
const onSelect = (currentValue: string) => {
const slug = currentValue === value ? "" : currentValue;
selectRequest(slug);
setValue(slug);
setOpen(false);
};
return (
<div className={cn("*:not-first:mt-2", props.className)}>
<Popover onOpenChange={setOpen} open={open}>
<PopoverTrigger asChild>
<Button
aria-expanded={open}
className="w-full justify-between border-input bg-background px-3 font-normal outline-none outline-offset-0 hover:bg-background focus-visible:outline-[3px]"
role="combobox"
variant="outline">
<span
className={cn(
"truncate",
!value &&
"text-muted-foreground",
)}>
{value
? requests.find(
(request) =>
request.slug ===
value,
)?.name
: "Select request"}
</span>
<ChevronDownIcon
aria-hidden="true"
className="shrink-0 text-muted-foreground/80"
size={16}
/>
</Button>
</PopoverTrigger>
<PopoverContent
align="start"
className="w-full min-w-(--radix-popper-anchor-width) border-input p-0"
noPortal>
<Command>
<CommandInput placeholder="Search requests..." />
<CommandList>
<CommandEmpty>
No request found.
</CommandEmpty>
<CommandGroup>
{requests.map((request) => (
<CommandItem
key={request.slug}
onSelect={onSelect}
value={request.slug}
className="flex items-center gap-2">
<span
className={`shrink-0 w-14 text-center font-geist-mono rounded-sm font-semibold py-0.5 text-xs leading-5 ${colors(request.method, { tryit: true })}`}>
{
request.method
}
</span>
<span className="truncate flex-1">
{request.name}
</span>
{value ===
request.slug && (
<CheckIcon
className="ml-auto shrink-0"
size={
16
}
/>
)}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
</div>
);
};
const AuthSchemeDropdown = () => {
const {
authSchemes,
selectedScheme,
setScheme,
authValues,
setAuthValues,
} = useMockRequest();
const apiKeyQuery = authSchemes.find(
(item) => item.type === "apiKeyQuery",
)!;
const apiKeyHeader = authSchemes.find(
(item) => item.type === "apiKeyHeader",
)!;
const onChange =
(key: keyof AuthValues) => (e: ChangeEvent<HTMLInputElement>) => {
setAuthValues(key, e.target.value);
};
const currentAuthScheme =
authSchemes.find((scheme) => scheme.type === selectedScheme) ||
NO_AUTH;
return (
<div className="h-100 flex-1">
<Select
value={selectedScheme}
onValueChange={(v: SecuritySchemes) => setScheme(v)}>
<SelectTrigger className="h-14 flex gap-0.5 focus-visible:ring-0 focus-visible:ring-offset-0 focus-visible:outline-none ring-0 bg-accent">
<div className="[&_*[role=option]>span]:start-auto [&_*[role=option]>span]:end-2 [&_*[role=option]]:ps-2 [&_*[role=option]]:pe-8">
<div className="flex flex-col w-full text-start">
<span>{selectedScheme}</span>
{currentAuthScheme?.description && (
<span className="mt-1 block text-muted-foreground text-xs">
{
currentAuthScheme.description
}
</span>
)}
</div>
</div>
</SelectTrigger>
<SelectContent
side="top"
align="start"
sideOffset={-60}
className="[&_*[role=option]>span]:start-auto [&_*[role=option]>span]:end-2 [&_*[role=option]]:ps-2 [&_*[role=option]]:pe-8">
{authSchemes.map((scheme) => (
<SelectItem
value={scheme.type}
key={scheme.type}>
{scheme.type}
{scheme.description && (
<span className="mt-1 block text-muted-foreground text-xs">
{scheme.description}
</span>
)}
</SelectItem>
))}
</SelectContent>
</Select>
<div className="mt-3.5">
{selectedScheme === "bearerAuth" && (
<>
<div className="flex text-xs mb-1.5 items-center font-mono">
<Label className="font-medium text-xs">
Authorization (header) *
</Label>
<span className="ml-auto text-muted-foreground">
string
</span>
</div>
<div className="border border-input bg-muted flex items-center h-9 rounded-sm px-1.5 text-muted-foreground text-center">
<span>Bearer</span>
<Input
className="focus-visible:ring-0 ml-1 focus-visible:ring-offset-0 focus-visible:outline-none border-0 ring-0 p-0"
value={authValues?.token}
onChange={onChange(
"token",
)}></Input>
</div>
</>
)}
{selectedScheme === "basicAuth" && (
<div className="grid grid-cols-2 gap-x-1.5">
<div>
<div className="flex text-xs mb-1.5 items-center font-mono">
<Label className="font-medium text-xs">
username *
</Label>
<span className="ml-auto text-muted-foreground">
string
</span>
</div>
<div className="border border-input bg-muted flex items-center h-9 rounded-sm px-1.5 text-muted-foreground text-center">
<Input
className="focus-visible:ring-0 ml-1 focus-visible:ring-offset-0 focus-visible:outline-none border-0 ring-0 p-0"
placeholder="Enter value"
value={
authValues?.username
}
onChange={onChange(
"username",
)}></Input>
</div>
</div>
<div>
<div className="flex text-xs mb-1.5 items-center font-mono">
<Label className="font-medium text-xs">
password *
</Label>
<span className="ml-auto text-muted-foreground">
string
</span>
</div>
<div className="border border-input bg-muted flex items-center h-9 rounded-sm px-1.5 text-muted-foreground text-center">
<Input
className="focus-visible:ring-0 ml-1 focus-visible:ring-offset-0 focus-visible:outline-none border-0 ring-0 p-0"
placeholder="Enter value"
value={
authValues?.password
}
onChange={onChange(
"password",
)}></Input>
</div>
</div>
</div>
)}
{selectedScheme === "apiKeyQuery" && (
<>
<div className="flex text-xs mb-1.5 items-center font-mono">
<Label className="font-medium text-xs">
{apiKeyQuery.name} (
{apiKeyQuery.in}) *
</Label>
<span className="ml-auto text-muted-foreground">
string
</span>
</div>
<div className="border border-input bg-muted flex items-center h-9 rounded-sm px-1.5 text-muted-foreground text-center">
<Input
className="focus-visible:ring-0 ml-1 focus-visible:ring-offset-0 focus-visible:outline-none border-0 ring-0 p-0"
placeholder="Enter value"
value={authValues?.apiKeyQuery}
onChange={onChange(
"apiKeyQuery",
)}></Input>
</div>
</>
)}
{selectedScheme === "apiKeyHeader" && (
<>
<div className="flex text-xs mb-1.5 items-center font-mono">
<Label className="font-medium text-xs">
{apiKeyHeader.name} (
{apiKeyHeader.in}) *
</Label>
<span className="ml-auto text-muted-foreground">
string
</span>
</div>
<div className="border border-input bg-muted flex items-center h-9 rounded-sm px-1.5 text-muted-foreground text-center">
<Input
className="focus-visible:ring-0 ml-1 focus-visible:ring-offset-0 focus-visible:outline-none border-0 ring-0 p-0"
placeholder="Enter value"
value={authValues?.apiKeyHeader}
onChange={onChange(
"apiKeyHeader",
)}></Input>
</div>
</>
)}
</div>
</div>
);
};
interface TextEditorProps {
readOnly?: boolean;
}
function TextEditor({ readOnly = false }: TextEditorProps) {
const {
isLoading,
requestBody: value,
setRequestBody: onChange,
} = useMockRequest();
const { error } = useError(value);
return (
<div className="relative">
<div className="flex flex-col bg-mutedx bg-gray-800/5 border-gray-400/30 border rounded-md h-full">
<div className="overflow-auto h-full">
<textarea
value={value}
onChange={(e) => onChange(e.target.value)}
readOnly={readOnly || isLoading}
className={`w-full scrollbar-hide h-77.5 p-2 font-geist-mono resize-none focus:outline-none ${
readOnly ? "cursor-default" : ""
}`}
spellCheck={false}
/>
</div>
<div
className={`font-geist-mono p-2 text-xs text-red-400 border-gray-400/30 h-28 max-h-28 overflow-scroll scrollbar-hide ${error ? "border-t" : ""}`}>
{error && <pre>{error}</pre>}
</div>
</div>
</div>
);
}
type ResponseBodyProps = {
body: string;
status: number;
contentType: string;
headers: any;
};
const ResponseBody = (props: ResponseBodyProps) => {
const { body, status } = props;
const [view, setView] = useState<Type>("body");
const { isCopied, copyValue } = useClipboard();
const httpStatus = httpCodes.find((item) => item.status === status);
const { icon: Icon, className } = getStatusIcon(httpStatus?.status);
if (!body) {
return (
<div className="flex items-center justify-center text-sm h-9 rounded-xl text-muted-foreground">
No defined response.
</div>
);
}
return (
<div className="w-full">
<Tabs defaultValue="status" className="gap-0">
<TabsList className="text-foreground w-full rounded-none bg-transparent px-1.5 h-10 flex items-center justify-between">
<div className="flex flex-wrap items-center gap-x-1.5">
<TabsTrigger
value={"status"}
className="gap-x-1.5 px-1.5 py-1 text-xs hover:text-foreground relative after:bottom-0 after:-mb-1 after:h-0.5 data-[state=active]:bg-transparent data-[state=active]:shadow-none">
<div className={className}>
<Icon className="h-4 w-4" />
</div>
<span className="max-w-35 truncate">
{httpStatus?.status} –{" "}
{httpStatus?.statusText}
</span>
</TabsTrigger>
</div>
<div className="ml-auto flex items-center gap-x-2.5">
<Select
value={view}
onValueChange={(v: Type) =>
setView(v)
}>
<SelectTrigger className="focus-visible:ring-0 h-8 hover:bg-muted-foreground/15 hover:border text-sm font-geist focus-visible:ring-offset-0 focus-visible:outline-none ring-0 pl-1.5 pr-1 py-1.25 rounded-lg">
<span>{view}</span>
</SelectTrigger>
<SelectContent className="">
<SelectItem
value="body"
className="text-sm font-geist">
Body
</SelectItem>
<SelectItem
value="headers"
className="text-sm font-geist">
Headers
</SelectItem>
</SelectContent>
</Select>
<button
className="flex items-center transition-colors text-muted-foreground hover:text-foreground"
onClick={() => {
if (view === "body") {
copyValue(props.body);
}
if (view === "headers") {
copyValue(props.headers);
}
}}
title="Copy snippet">
{!isCopied && (
<Copy className="size-4" />
)}
{isCopied && (
<CheckCircleFilled className="size-5 cursor-pointer" />
)}
</button>
</div>
</TabsList>
<TabsContent
value="status"
className="relative w-full overflow-scroll border h-44 max-h-48 scrollbar-hide border-input rounded-xl">
<div className="text-sm">
{view === "body" && (
<CodeBlock
code={props.body}
language="json"
/>
)}
{view === "headers" && (
<CodeBlock
code={props.headers}
language="json"
/>
)}
</div>
</TabsContent>
</Tabs>
</div>
);
};
const useError = (value: string) => {
const debouncedValue = useDebounce(value);
const [error, setError] = useState<string>("");
useEffect(() => {
if (!debouncedValue) {
setError("");
return;
}
try {
parseJson(debouncedValue);
setError("");
} catch (err: any) {
setError(err?.message ?? "Invalid JSON");
}
}, [debouncedValue]);
return { error };
};
type Type = "body" | "headers";
type StatusIcon = {
icon: React.ElementType;
className: string;
};
function getStatusIcon(status?: number): StatusIcon {
const statusIcons: Record<number, StatusIcon> = {
1: { icon: Info, className: "text-blue-600 dark:text-blue-500" },
2: {
icon: CircleCheck,
className: "text-green-600 dark:text-green-500",
},
3: {
icon: AlertTriangle,
className: "text-yellow-600 dark:text-yellow-500",
},
4: { icon: CircleX, className: "text-red-600 dark:text-red-500" },
5: { icon: CircleX, className: "text-red-600 dark:text-red-500" },
};
const statusGroup = status ? Math.floor(status / 100) : 4;
return statusIcons[statusGroup] ?? statusIcons[4];
}
export type Schemes = typeof NO_AUTH | AuthSchemes;
export type SecuritySchemes = Schemes[][number]["type"];
export type AuthValues = {
token: string;
username: string;
password: string;
apiKeyQuery: string;
apiKeyHeader: string;
};
interface MockRequestState {
baseURL: string;
isLoading: boolean;
responseBody: string | null;
contentType: string | null;
responseStatus: number | null;
requestBody: string;
requests: PageEntry[];
selectedRequest: Omit<PageEntry, "data"> | null;
authValues: AuthValues;
responseHeaders: string | null;
selectedScheme: SecuritySchemes;
responseSnippets?: Snippet[];
servers: Server[];
authSchemes: Schemes[];
responseSlug: string;
onSend: () => Promise<void>;
setBaseURL: (url: string) => void;
setAuthValues: (key: keyof AuthValues, value: string) => void;
setScheme: (scheme: SecuritySchemes) => void;
setRequestBody: (val: string) => void;
selectRequest: (slug: string) => void;
setResponseSlug: (slug: string) => void;
}
type MockRequestProviderProps = {
children: ReactNode;
proxyPath?: string;
intialRequests?: PageEntry[];
currentRequest?: Omit<PageEntry, "data">;
responseSnippets?: Snippet[];
defaultRequestBody?: Record<string, any>;
authSchemes?: Schemes[];
servers: Server[];
baseURL: string;
};
const MockRequestContext = createContext<MockRequestState | null>(null);
const MockRequestProvider = (props: MockRequestProviderProps) => {
const apiPath = props.proxyPath || "/api/proxy";
const [_responseSlug, _setResponseSlug] = useState("");
const [_isLoading, _setIsLoading] = useState(false);
const [_baseUrl, _setBaseUrl] = useState("");
const { navigate } = useDynamicSegment();
const [requests, setRequests] = useState<PageEntry[]>(
props?.intialRequests || [],
);
const [_servers, _setServers] = useState<Server[]>(props?.servers || []);
const [_authSchemes, _setAuthSchemes] = useState<Schemes[]>(() => {
const item = [
{
type: "null",
description: "No auth configured",
} as typeof NO_AUTH,
];
if (props.authSchemes?.length === 0) {
return item;
}
return props.authSchemes || item;
});
const [_responseSnippets, _setResponseSnippets] = useState<Snippet[]>([]);
const [_requestBody, _setRequestBody] = useState("");
const [_selectedRequest, _setSelectedRequest] = useState<Omit<
PageEntry,
"data"
> | null>(props?.currentRequest || null);
const [selectedScheme, setSelectedScheme] = useState<SecuritySchemes>(
_authSchemes[0]?.type || "null",
);
const [_authValues, _setAuthValues] = useState<AuthValues>({
token: "",
username: "",
password: "",
apiKeyQuery: "",
apiKeyHeader: "",
});
const [_responseStatus, _setResponseStatus] = useState<number | null>(
null,
);
const [_responseBody, _setResponseBody] = useState<string | null>(null);
const [_responseHeaders, _setResponseHeaders] = useState<string | null>(
null,
);
const [_contentType, _setContentType] = useState<string | null>(null);
const onSend = async (): Promise<void> => {
if (!_selectedRequest) {
console.log("No request was selected.");
return;
}
if (!_baseUrl) {
console.log("Baseurl was not found.");
return;
}
const schemes = _authSchemes;
try {
console.log("OnSend: sending");
_setIsLoading(true);
const targetUrl = "x-proxy-target-url";
let headers: Record<string, string> = {
// TODO Trim double forward slashes
[targetUrl]: `${_baseUrl}${props.baseURL}`,
};
const apiKeyQueryScheme = schemes.find(
(s: any) => s.type === "apiKeyQuery",
) as ApiKeyQueryScheme;
const apiKeyHeaderScheme = schemes.find(
(s: any) => s.type === "apiKeyHeader",
) as ApiKeyHeaderScheme;
if (selectedScheme === "bearerAuth") {
headers.Authorization = `Bearer ${_authValues.token}`;
}
if (selectedScheme === "basicAuth") {
const encoded = Buffer.from(
`${_authValues.username}:${_authValues.password}`,
"base64",
).toString("base64");
headers.Authorization = `Basic ${encoded}`;
}
if (selectedScheme === "apiKeyHeader" && apiKeyHeaderScheme) {
headers[apiKeyHeaderScheme.name] =
_authValues.apiKeyHeader;
}
if (selectedScheme === "apiKeyQuery" && apiKeyQueryScheme) {
const query = new URLSearchParams({
[apiKeyQueryScheme.name]: _authValues.apiKeyQuery,
});
headers[targetUrl] += `?${query.toString()}`;
}
console.log("_selectedRequest: ", _selectedRequest);
const req = await window.fetch(apiPath, {
method: _selectedRequest.method,
headers,
body:
_selectedRequest.method !== "GET" &&
_selectedRequest.method !== "HEAD"
? _requestBody
: undefined,
});
const contentType =
req.headers.get("content-type")?.toLowerCase() || null;
const res = await req.text();
_setContentType(contentType);
_setResponseBody(res);
_setResponseStatus(req.status);
_setResponseHeaders(
JSON.stringify(
Object.fromEntries(req.headers.entries()),
null,
2,
),
);
_setIsLoading(false);
} catch (error) {
console.log("OnSend error: ", error);
_setIsLoading(false);
} finally {
_setIsLoading(false);
}
};
const setAuthValues = (key: keyof AuthValues, value: string) => {
// TODO debounce this action
_setAuthValues((p: AuthValues) => ({
...p,
[key]: value,
}));
};
const setScheme = (scheme: SecuritySchemes) => {
setSelectedScheme(scheme);
};
const setRequestBody = (val: string) => {
_setRequestBody(val);
};
const selectRequest = (slug: string) => {
if (!requests || requests.length === 0) {
console.warn(
"Requests are empty, there is nothing to select.",
);
return;
}
const req = requests.find((item) => item.slug === slug)!;
if (!req) {
return;
}
navigate(req.slug);
};
const setBaseURL = (value: string) => {
_setBaseUrl(value);
};
const setResponseSlug = (slug: string) => {
_setResponseSlug(slug);
};
useEffect(() => {
if (
_responseSlug &&
props.defaultRequestBody &&
typeof props.defaultRequestBody === "object"
) {
_setRequestBody(
JSON.stringify(
props.defaultRequestBody?.[_responseSlug] || {},
null,
2,
),
);
}
}, [_responseSlug, props.defaultRequestBody]);
useEffect(() => {
if (props.responseSnippets) {
_setResponseSnippets(props.responseSnippets);
}
}, [props.responseSnippets]);
return (
<MockRequestContext.Provider
value={{
baseURL: _baseUrl,
isLoading: _isLoading,
responseStatus: _responseStatus,
requestBody: _requestBody,
requests,
selectedRequest: _selectedRequest,
authValues: _authValues,
selectedScheme: selectedScheme,
responseBody: _responseBody,
contentType: _contentType,
responseHeaders: _responseHeaders,
responseSnippets: _responseSnippets,
servers: _servers,
authSchemes: _authSchemes,
responseSlug: _responseSlug,
onSend,
setAuthValues,
setScheme,
setRequestBody,
selectRequest,
setBaseURL,
setResponseSlug,
}}>
{props.children}
</MockRequestContext.Provider>
);
};
const useMockRequest = () => {
const ctx = useContext(MockRequestContext);
if (!ctx)
throw new Error(
"useMockRequest must be used inside MockRequestProvider",
);
return ctx;
};
export { MockRequest, MockRequestProvider, useMockRequest };