257 lines
No EOL
9.6 KiB
TypeScript
257 lines
No EOL
9.6 KiB
TypeScript
import { cn } from '@/lib/utils'
|
|
import { colors } from '@/lib/theme'
|
|
import type { APIEndpoint } from '@/lib/docs/types'
|
|
import CodeBlock from './CodeBlock'
|
|
import { LuLock } from 'react-icons/lu'
|
|
|
|
interface APIEndpointDocProps {
|
|
endpoint: APIEndpoint
|
|
className?: string
|
|
}
|
|
|
|
const methodStyles = {
|
|
GET: {
|
|
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
|
color: colors.accents.success,
|
|
borderColor: 'rgba(16, 185, 129, 0.3)',
|
|
},
|
|
POST: {
|
|
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
color: colors.accents.info,
|
|
borderColor: 'rgba(59, 130, 246, 0.3)',
|
|
},
|
|
PUT: {
|
|
backgroundColor: colors.accents.warningBg,
|
|
color: colors.accents.warning,
|
|
borderColor: 'rgba(245, 158, 11, 0.3)',
|
|
},
|
|
DELETE: {
|
|
backgroundColor: 'rgba(239, 68, 68, 0.1)',
|
|
color: colors.accents.error,
|
|
borderColor: 'rgba(239, 68, 68, 0.3)',
|
|
},
|
|
PATCH: {
|
|
backgroundColor: 'rgba(168, 85, 247, 0.1)',
|
|
color: '#a855f7',
|
|
borderColor: 'rgba(168, 85, 247, 0.3)',
|
|
},
|
|
} as const
|
|
|
|
export default function APIEndpointDoc({
|
|
endpoint,
|
|
className,
|
|
}: APIEndpointDocProps) {
|
|
return (
|
|
<div id={endpoint.id} className={cn('scroll-mt-20', className)}>
|
|
<div className="space-y-6">
|
|
{/* Header */}
|
|
<div className="space-y-3">
|
|
<div className="flex items-center gap-3">
|
|
<span
|
|
className="rounded-md border px-3 py-1 text-sm font-bold"
|
|
style={methodStyles[endpoint.method]}
|
|
>
|
|
{endpoint.method}
|
|
</span>
|
|
<code className="text-lg font-mono" style={{ color: colors.text.secondary }}>
|
|
{endpoint.path}
|
|
</code>
|
|
</div>
|
|
<p className="leading-relaxed" style={{ color: colors.text.body }}>{endpoint.description}</p>
|
|
{endpoint.auth?.required && (
|
|
<div
|
|
className="flex items-center gap-2 rounded-lg border px-4 py-2 text-sm"
|
|
style={{
|
|
borderColor: 'rgba(245, 158, 11, 0.3)',
|
|
backgroundColor: colors.accents.warningBg,
|
|
color: colors.accents.warning,
|
|
}}
|
|
>
|
|
<LuLock className="h-4 w-4" />
|
|
<span>
|
|
Authentication required
|
|
{endpoint.auth.type && `: ${endpoint.auth.type}`}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Query Parameters */}
|
|
{endpoint.parameters?.query && endpoint.parameters.query.length > 0 && (
|
|
<div className="space-y-2">
|
|
<h4 className="text-sm font-semibold" style={{ color: colors.text.body }}>
|
|
Query Parameters
|
|
</h4>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="border-b" style={{ borderColor: colors.borders.default }}>
|
|
<th className="px-4 py-2 text-left font-medium" style={{ color: colors.text.muted }}>
|
|
Name
|
|
</th>
|
|
<th className="px-4 py-2 text-left font-medium" style={{ color: colors.text.muted }}>
|
|
Type
|
|
</th>
|
|
<th className="px-4 py-2 text-left font-medium" style={{ color: colors.text.muted }}>
|
|
Description
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{endpoint.parameters.query.map((param, index) => (
|
|
<tr
|
|
key={index}
|
|
className="border-b last:border-0"
|
|
style={{ borderColor: colors.borders.subtle }}
|
|
>
|
|
<td className="px-4 py-3 font-mono" style={{ color: colors.text.secondary }}>
|
|
{param.name}
|
|
{!param.optional && (
|
|
<span className="ml-1" style={{ color: colors.accents.error }}>*</span>
|
|
)}
|
|
</td>
|
|
<td className="px-4 py-3 font-mono" style={{ color: colors.text.muted }}>
|
|
{param.type}
|
|
</td>
|
|
<td className="px-4 py-3" style={{ color: colors.text.body }}>
|
|
{param.description}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Request Body */}
|
|
{endpoint.parameters?.body && endpoint.parameters.body.length > 0 && (
|
|
<div className="space-y-2">
|
|
<h4 className="text-sm font-semibold" style={{ color: colors.text.body }}>Request Body</h4>
|
|
<div className="overflow-x-auto">
|
|
<table className="w-full text-sm">
|
|
<thead>
|
|
<tr className="border-b" style={{ borderColor: colors.borders.default }}>
|
|
<th className="px-4 py-2 text-left font-medium" style={{ color: colors.text.muted }}>
|
|
Field
|
|
</th>
|
|
<th className="px-4 py-2 text-left font-medium" style={{ color: colors.text.muted }}>
|
|
Type
|
|
</th>
|
|
<th className="px-4 py-2 text-left font-medium" style={{ color: colors.text.muted }}>
|
|
Description
|
|
</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{endpoint.parameters.body.map((param, index) => (
|
|
<tr
|
|
key={index}
|
|
className="border-b last:border-0"
|
|
style={{ borderColor: colors.borders.subtle }}
|
|
>
|
|
<td className="px-4 py-3 font-mono" style={{ color: colors.text.secondary }}>
|
|
{param.name}
|
|
{!param.optional && (
|
|
<span className="ml-1" style={{ color: colors.accents.error }}>*</span>
|
|
)}
|
|
</td>
|
|
<td className="px-4 py-3 font-mono" style={{ color: colors.text.muted }}>
|
|
{param.type}
|
|
</td>
|
|
<td className="px-4 py-3" style={{ color: colors.text.body }}>
|
|
{param.description}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Responses */}
|
|
<div className="space-y-3">
|
|
<h4 className="text-sm font-semibold" style={{ color: colors.text.body }}>Responses</h4>
|
|
{endpoint.responses.map((response, index) => {
|
|
const isSuccess = response.status >= 200 && response.status < 300
|
|
const isError = response.status >= 400
|
|
const statusStyle = isSuccess
|
|
? { backgroundColor: 'rgba(16, 185, 129, 0.1)', color: colors.accents.success }
|
|
: isError
|
|
? { backgroundColor: 'rgba(239, 68, 68, 0.1)', color: colors.accents.error }
|
|
: { backgroundColor: 'rgba(59, 130, 246, 0.1)', color: colors.accents.info }
|
|
|
|
return (
|
|
<div
|
|
key={index}
|
|
className="space-y-2 rounded-lg border p-4"
|
|
style={{
|
|
borderColor: colors.borders.default,
|
|
backgroundColor: colors.backgrounds.card,
|
|
}}
|
|
>
|
|
<div className="flex items-center gap-3">
|
|
<span
|
|
className="rounded px-2 py-1 text-sm font-mono font-semibold"
|
|
style={statusStyle}
|
|
>
|
|
{response.status}
|
|
</span>
|
|
<span className="text-sm" style={{ color: colors.text.body }}>
|
|
{response.description}
|
|
</span>
|
|
</div>
|
|
{response.example && (
|
|
<CodeBlock
|
|
code={JSON.stringify(response.example, null, 2)}
|
|
language="json"
|
|
title="Example Response"
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Examples */}
|
|
{endpoint.examples && endpoint.examples.length > 0 && (
|
|
<div className="space-y-3">
|
|
<h4 className="text-sm font-semibold" style={{ color: colors.text.body }}>
|
|
Request Examples
|
|
</h4>
|
|
{endpoint.examples.map((example, index) => (
|
|
<div key={index} className="space-y-3">
|
|
{example.title && (
|
|
<h5 className="text-sm font-medium" style={{ color: colors.text.muted }}>
|
|
{example.title}
|
|
</h5>
|
|
)}
|
|
<div className="grid gap-3 lg:grid-cols-2">
|
|
<CodeBlock
|
|
code={
|
|
typeof example.request === 'string'
|
|
? example.request
|
|
: JSON.stringify(example.request, null, 2)
|
|
}
|
|
language="bash"
|
|
title="Request"
|
|
/>
|
|
<CodeBlock
|
|
code={
|
|
typeof example.response === 'string'
|
|
? example.response
|
|
: JSON.stringify(example.response, null, 2)
|
|
}
|
|
language="json"
|
|
title="Response"
|
|
/>
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
)
|
|
} |