mirror of
https://github.com/better-auth/better-auth.git
synced 2026-05-25 08:31:37 -05:00
docs: improve api endpoint component (#8712)
This commit is contained in:
@@ -22,7 +22,7 @@ function ApiMethodTabs({
|
|||||||
return (
|
return (
|
||||||
<provider.Provider value={{ current, setCurrent }}>
|
<provider.Provider value={{ current, setCurrent }}>
|
||||||
<div
|
<div
|
||||||
data-slot="tabs"
|
data-slot="api-method-tabs"
|
||||||
className={cn("flex flex-col gap-2", className)}
|
className={cn("flex flex-col gap-2", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -183,14 +183,9 @@ export const APIMethod = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const serverTabContent = (
|
const serverTabContent = (
|
||||||
<>
|
<div className="border shadow-sm [&_figure]:my-0 [&_figure]:border-0 [&_figure]:shadow-none [&_figure]:rounded-none [&_.fd-scroll-container]:bg-transparent">
|
||||||
{isClientOnly || isServerOnly ? null : (
|
{isClientOnly || isServerOnly ? null : (
|
||||||
<Endpoint
|
<Endpoint method={method || "GET"} path={path} className="" />
|
||||||
method={method || "GET"}
|
|
||||||
path={path}
|
|
||||||
isServerOnly={isServerOnly ?? false}
|
|
||||||
className=""
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
{serverOnlyNote || note ? (
|
{serverOnlyNote || note ? (
|
||||||
<Note>
|
<Note>
|
||||||
@@ -212,7 +207,7 @@ export const APIMethod = ({
|
|||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{!isClientOnly ? <TypeTable props={props} isServer /> : null}
|
{!isClientOnly ? <TypeTable props={props} isServer /> : null}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isExternalOnly) {
|
if (isExternalOnly) {
|
||||||
@@ -234,48 +229,53 @@ export const APIMethod = ({
|
|||||||
defaultValue={isServerOnly ? "server" : "client"}
|
defaultValue={isServerOnly ? "server" : "client"}
|
||||||
className="gap-0 w-full"
|
className="gap-0 w-full"
|
||||||
>
|
>
|
||||||
<ApiMethodTabsList className="relative flex justify-start w-full p-0 bg-background dark:bg-[#050505] hover:[&>div>a>button]:opacity-100">
|
<ApiMethodTabsList className="relative flex justify-start w-full p-0 bg-background hover:[&>div>a>button]:opacity-100">
|
||||||
<ApiMethodTabsTrigger
|
{(
|
||||||
value="client"
|
[
|
||||||
className="transition-all duration-150 ease-in-out max-w-[100px] data-[state=active]:bg-fd-secondary hover:bg-fd-secondary/70 bg-fd-secondary/50 border hover:border-primary/15 cursor-pointer data-[state=active]:border-primary/10 rounded-none dark:bg-[#050505] dark:hover:bg-[#0a0a0a] dark:data-[state=active]:bg-[#0a0a0a] dark:border-white/[0.06] dark:hover:border-white/10 dark:data-[state=active]:border-white/10"
|
{
|
||||||
>
|
value: "client",
|
||||||
<svg
|
label: "Client",
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
icon: (
|
||||||
width="1em"
|
<>
|
||||||
height="1em"
|
<rect width="20" height="14" x="2" y="3" rx="2" />
|
||||||
viewBox="0 0 24 24"
|
<path d="M8 21h8m-4-4v4" />
|
||||||
fill="none"
|
</>
|
||||||
stroke="currentColor"
|
),
|
||||||
strokeWidth="2"
|
},
|
||||||
strokeLinecap="round"
|
{
|
||||||
strokeLinejoin="round"
|
value: "server",
|
||||||
|
label: "Server",
|
||||||
|
icon: (
|
||||||
|
<>
|
||||||
|
<rect width="20" height="8" x="2" y="2" rx="2" ry="2" />
|
||||||
|
<rect width="20" height="8" x="2" y="14" rx="2" ry="2" />
|
||||||
|
<path d="M6 6h.01M6 18h.01" />
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
] as const
|
||||||
|
).map((tab) => (
|
||||||
|
<ApiMethodTabsTrigger
|
||||||
|
key={tab.value}
|
||||||
|
value={tab.value}
|
||||||
|
className="transition-all duration-150 ease-in-out max-w-[100px] data-[state=active]:bg-fd-muted/80 hover:bg-fd-secondary/70 bg-background border hover:border-primary/15 cursor-pointer data-[state=active]:border-primary/10 rounded-none dark:bg-[#050505] dark:hover:bg-[#0a0a0a] dark:data-[state=active]:bg-fd-muted/80 dark:border-white/[0.06] dark:hover:border-white/10 dark:data-[state=active]:border-white/10"
|
||||||
>
|
>
|
||||||
<rect width="20" height="14" x="2" y="3" rx="2" />
|
<svg
|
||||||
<path d="M8 21h8m-4-4v4" />
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
</svg>
|
width="1em"
|
||||||
<span>Client</span>
|
height="1em"
|
||||||
</ApiMethodTabsTrigger>
|
viewBox="0 0 24 24"
|
||||||
<ApiMethodTabsTrigger
|
fill="none"
|
||||||
value="server"
|
stroke="currentColor"
|
||||||
className="transition-all duration-150 ease-in-out max-w-[100px] data-[state=active]:bg-fd-secondary hover:bg-fd-secondary/70 bg-fd-secondary/50 border hover:border-primary/15 cursor-pointer data-[state=active]:border-primary/10 rounded-none dark:bg-[#050505] dark:hover:bg-[#0a0a0a] dark:data-[state=active]:bg-[#0a0a0a] dark:border-white/[0.06] dark:hover:border-white/10 dark:data-[state=active]:border-white/10"
|
strokeWidth="2"
|
||||||
>
|
strokeLinecap="round"
|
||||||
<svg
|
strokeLinejoin="round"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
>
|
||||||
width="1em"
|
{tab.icon}
|
||||||
height="1em"
|
</svg>
|
||||||
viewBox="0 0 24 24"
|
<span>{tab.label}</span>
|
||||||
fill="none"
|
</ApiMethodTabsTrigger>
|
||||||
stroke="currentColor"
|
))}
|
||||||
strokeWidth="2"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
>
|
|
||||||
<rect width="20" height="8" x="2" y="2" rx="2" ry="2" />
|
|
||||||
<rect width="20" height="8" x="2" y="14" rx="2" ry="2" />
|
|
||||||
<path d="M6 6h.01M6 18h.01" />
|
|
||||||
</svg>
|
|
||||||
<span>Server</span>
|
|
||||||
</ApiMethodTabsTrigger>
|
|
||||||
<div className="absolute right-0">
|
<div className="absolute right-0">
|
||||||
<a href={`#api-method${pathId}`}>
|
<a href={`#api-method${pathId}`}>
|
||||||
<Button
|
<Button
|
||||||
@@ -289,43 +289,43 @@ export const APIMethod = ({
|
|||||||
</div>
|
</div>
|
||||||
</ApiMethodTabsList>
|
</ApiMethodTabsList>
|
||||||
<ApiMethodTabsContent value="client">
|
<ApiMethodTabsContent value="client">
|
||||||
{isServerOnly ? null : (
|
<div className="border shadow-sm [&_figure]:my-0 [&_figure]:border-0 [&_figure]:shadow-none [&_figure]:rounded-none [&_.fd-scroll-container]:bg-transparent">
|
||||||
<Endpoint
|
{isServerOnly ? null : (
|
||||||
method={method || "GET"}
|
<Endpoint method={method || "GET"} path={path} />
|
||||||
path={path}
|
)}
|
||||||
isServerOnly={isServerOnly ?? false}
|
{clientOnlyNote || note ? (
|
||||||
/>
|
<Note>
|
||||||
)}
|
{note && tsxifyBackticks(note)}
|
||||||
{clientOnlyNote || note ? (
|
{clientOnlyNote ? (
|
||||||
<Note>
|
<>
|
||||||
{note && tsxifyBackticks(note)}
|
{note ? <br /> : null}
|
||||||
{clientOnlyNote ? (
|
{tsxifyBackticks(clientOnlyNote)}
|
||||||
<>
|
</>
|
||||||
{note ? <br /> : null}
|
) : null}
|
||||||
{tsxifyBackticks(clientOnlyNote)}
|
</Note>
|
||||||
</>
|
) : null}
|
||||||
|
<div className={cn("relative w-full")}>
|
||||||
|
<DynamicCodeBlock
|
||||||
|
code={`${code_prefix}${
|
||||||
|
noResult
|
||||||
|
? ""
|
||||||
|
: `const { data${
|
||||||
|
resultVariable === "data" ? "" : `: ${resultVariable}`
|
||||||
|
}, error } = `
|
||||||
|
}await authClient.${authClientMethodPath}(${clientBody});${code_suffix}`}
|
||||||
|
lang="ts"
|
||||||
|
allowCopy={!isServerOnly}
|
||||||
|
/>
|
||||||
|
{isServerOnly ? (
|
||||||
|
<div className="flex absolute inset-0 justify-center items-center w-full h-full rounded-lg border backdrop-brightness-50 backdrop-blur-xs border-border">
|
||||||
|
<span>This is a server-only endpoint</span>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</Note>
|
</div>
|
||||||
) : null}
|
{!isServerOnly ? (
|
||||||
<div className={cn("relative w-full")}>
|
<TypeTable props={props} isServer={false} />
|
||||||
<DynamicCodeBlock
|
|
||||||
code={`${code_prefix}${
|
|
||||||
noResult
|
|
||||||
? ""
|
|
||||||
: `const { data${
|
|
||||||
resultVariable === "data" ? "" : `: ${resultVariable}`
|
|
||||||
}, error } = `
|
|
||||||
}await authClient.${authClientMethodPath}(${clientBody});${code_suffix}`}
|
|
||||||
lang="ts"
|
|
||||||
allowCopy={!isServerOnly}
|
|
||||||
/>
|
|
||||||
{isServerOnly ? (
|
|
||||||
<div className="flex absolute inset-0 justify-center items-center w-full h-full rounded-lg border backdrop-brightness-50 backdrop-blur-xs border-border">
|
|
||||||
<span>This is a server-only endpoint</span>
|
|
||||||
</div>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{!isServerOnly ? <TypeTable props={props} isServer={false} /> : null}
|
|
||||||
</ApiMethodTabsContent>
|
</ApiMethodTabsContent>
|
||||||
<ApiMethodTabsContent value="server">
|
<ApiMethodTabsContent value="server">
|
||||||
{serverTabContent}
|
{serverTabContent}
|
||||||
@@ -389,8 +389,8 @@ function TypeTable({
|
|||||||
if (!filteredProps.length) return null;
|
if (!filteredProps.length) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="border border-border dark:border-white/[0.06] bg-fd-secondary/30 dark:bg-[#050505] mt-0">
|
<div className="mt-0">
|
||||||
<div className="flex items-center gap-2 px-3.5 py-2 border-b border-border dark:border-white/[0.06]">
|
<div className="flex items-center gap-2 px-3.5 py-2 border-y border-border bg-fd-muted/80">
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="12"
|
width="12"
|
||||||
@@ -408,46 +408,93 @@ function TypeTable({
|
|||||||
<path d="M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3" />
|
<path d="M12 22v-8.3a4 4 0 0 0-1.172-2.872L3 3" />
|
||||||
<path d="m15 9 6-6" />
|
<path d="m15 9 6-6" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-[11px] font-medium text-muted-foreground uppercase tracking-wider">
|
<span className="text-xs font-medium text-muted-foreground tracking-wider">
|
||||||
Parameters
|
Parameters
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="divide-y divide-border dark:divide-white/[0.04]">
|
<PropertyList props={filteredProps} />
|
||||||
{filteredProps.map((prop) => (
|
</div>
|
||||||
<div
|
);
|
||||||
key={`${prop.path.join(".")}.${prop.propName}`}
|
}
|
||||||
className="group px-3.5 py-2.5 hover:bg-fd-secondary/50 dark:hover:bg-white/[0.02] transition-colors"
|
|
||||||
style={{
|
function PropertyItem({ prop }: { prop: Property }) {
|
||||||
paddingLeft: `${0.875 + prop.path.length * 1}rem`,
|
return (
|
||||||
}}
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
>
|
<code className="text-xs font-semibold text-foreground/90">
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
{prop.propName}
|
||||||
<code className="text-[13px] font-semibold text-foreground/90">
|
</code>
|
||||||
{prop.propName}
|
<span className="text-xs font-mono text-foreground/60 font-medium">
|
||||||
</code>
|
{prop.type}
|
||||||
<span className="text-[11px] font-mono text-muted-foreground/60">
|
{prop.isNullable ? " | null" : ""}
|
||||||
{prop.type}
|
</span>
|
||||||
{prop.isNullable ? " | null" : ""}
|
{!prop.isOptional && (
|
||||||
</span>
|
<span className="text-[10px] font-mono font-medium text-amber-600 dark:text-amber-500/80">
|
||||||
{!prop.isOptional && (
|
required
|
||||||
<span className="text-[9px] font-medium uppercase tracking-widest text-amber-600 dark:text-amber-500/80">
|
</span>
|
||||||
required
|
)}
|
||||||
</span>
|
{prop.isServerOnly && (
|
||||||
)}
|
<span className="text-[10px] font-mono font-medium text-blue-600 dark:text-blue-400/80">
|
||||||
{prop.isServerOnly && (
|
server
|
||||||
<span className="text-[9px] font-medium uppercase tracking-widest text-blue-600 dark:text-blue-400/80">
|
</span>
|
||||||
server
|
)}
|
||||||
</span>
|
</div>
|
||||||
)}
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function PropertyList({
|
||||||
|
props,
|
||||||
|
nested = false,
|
||||||
|
}: {
|
||||||
|
props: Property[];
|
||||||
|
nested?: boolean;
|
||||||
|
}) {
|
||||||
|
const groups: { prop: Property; children: Property[] }[] = [];
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < props.length) {
|
||||||
|
const prop = props[i];
|
||||||
|
if (prop.type === "Object") {
|
||||||
|
const parentSegments = [...prop.path, prop.propName];
|
||||||
|
const children: Property[] = [];
|
||||||
|
i++;
|
||||||
|
while (
|
||||||
|
i < props.length &&
|
||||||
|
props[i].path.length >= parentSegments.length &&
|
||||||
|
parentSegments.every((seg, idx) => props[i].path[idx] === seg)
|
||||||
|
) {
|
||||||
|
children.push(props[i]);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
groups.push({ prop, children });
|
||||||
|
} else {
|
||||||
|
groups.push({ prop, children: [] });
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="divide-y divide-border">
|
||||||
|
{groups.map((group) => (
|
||||||
|
<div
|
||||||
|
key={`${group.prop.path.join(".")}.${group.prop.propName}`}
|
||||||
|
className={cn(
|
||||||
|
nested ? "px-3 py-3" : "px-3.5 py-3",
|
||||||
|
group.children.length > 0 && "pb-3",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<PropertyItem prop={group.prop} />
|
||||||
|
{group.prop.description && (
|
||||||
|
<p className="mt-1 mb-0 text-sm leading-relaxed max-w-xl">
|
||||||
|
{tsxifyBackticks(group.prop.description)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
{group.children.length > 0 && (
|
||||||
|
<div className="mt-3 border rounded-md overflow-hidden">
|
||||||
|
<PropertyList props={group.children} nested />
|
||||||
</div>
|
</div>
|
||||||
{prop.description && (
|
)}
|
||||||
<p className="mt-1 text-[12px] text-muted-foreground/70 leading-relaxed max-w-xl">
|
</div>
|
||||||
{tsxifyBackticks(prop.description)}
|
))}
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -885,11 +932,13 @@ function createServerBody({
|
|||||||
|
|
||||||
function Note({ children }: { children: ReactNode }) {
|
function Note({ children }: { children: ReactNode }) {
|
||||||
return (
|
return (
|
||||||
<div className="flex relative flex-col gap-2 p-3 mb-2 w-full break-words rounded-md border text-md text-wrap border-border bg-fd-secondary/50">
|
<div className="flex relative flex-col gap-2 p-3 mb-2 w-full wrap-break-word rounded-md border-b text-md text-wrap bg-fd-muted/80">
|
||||||
<span className="-mb-1 w-full text-xs select-none text-muted-foreground">
|
<span className="-mb-1 w-full text-xs select-none text-foreground/80 font-medium">
|
||||||
Notes
|
Notes
|
||||||
</span>
|
</span>
|
||||||
<p className="mt-0 mb-0 text-sm">{children as any}</p>
|
<p className="mt-0 mb-0 text-sm text-fd-muted-foreground">
|
||||||
|
{children as any}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const methodColors: Record<string, string> = {
|
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
||||||
GET: "text-emerald-700 dark:text-emerald-400",
|
|
||||||
POST: "text-blue-700 dark:text-blue-400",
|
const methodColors: Record<HttpMethod, string> = {
|
||||||
PUT: "text-amber-700 dark:text-amber-400",
|
GET: "text-green-600 dark:text-green-500",
|
||||||
DELETE: "text-red-700 dark:text-red-400",
|
POST: "text-yellow-600 dark:text-yellow-600",
|
||||||
|
PUT: "text-blue-600 dark:text-blue-400",
|
||||||
|
DELETE: "text-red-600 dark:text-red-400",
|
||||||
};
|
};
|
||||||
|
|
||||||
function Method({ method }: { method: "POST" | "GET" | "DELETE" | "PUT" }) {
|
function Method({ method }: { method: HttpMethod }) {
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-[11px] font-bold font-mono uppercase tracking-wider",
|
"text-xs font-bold font-mono uppercase",
|
||||||
methodColors[method],
|
methodColors[method],
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
@@ -24,23 +26,21 @@ function Method({ method }: { method: "POST" | "GET" | "DELETE" | "PUT" }) {
|
|||||||
export function Endpoint({
|
export function Endpoint({
|
||||||
path,
|
path,
|
||||||
method,
|
method,
|
||||||
isServerOnly,
|
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
path: string;
|
path: string;
|
||||||
method: "POST" | "GET" | "DELETE" | "PUT";
|
method: HttpMethod;
|
||||||
isServerOnly?: boolean;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"relative flex items-center w-full gap-3 px-3.5 py-2.5 border-t border-x border-border bg-fd-secondary/50 dark:border-white/[0.06] dark:bg-[#050505] group",
|
"relative flex items-center w-full gap-2 px-3.5 py-1 border-b border-border bg-fd-muted/80 group",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Method method={method} />
|
<Method method={method} />
|
||||||
<span className="font-mono text-[12px] text-muted-foreground/70">
|
<span className="font-mono text-[13px] text-foreground/80 font-medium">
|
||||||
{path}
|
{path}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user