freshyo/apps/fallback-ui/src/routes/demo.tsx
2026-03-24 20:50:14 +05:30

259 lines
9.7 KiB
TypeScript

import { useState } from 'react'
import { getAuthToken } from '@/services/auth'
const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:4000'
export function DemoRoute() {
const [method, setMethod] = useState<string>('GET')
const [endpoint, setEndpoint] = useState<string>('/api/test')
const [headers, setHeaders] = useState<string>('{}')
const [body, setBody] = useState<string>('{}')
const [response, setResponse] = useState<any>(null)
const [error, setError] = useState<string>('')
const [loading, setLoading] = useState(false)
const [history, setHistory] = useState<Array<{ method: string; endpoint: string; timestamp: string }>>([])
const handleSubmit = async () => {
setLoading(true)
setError('')
setResponse(null)
try {
const token = await getAuthToken()
const url = `${API_BASE_URL}${endpoint}`
let parsedHeaders: Record<string, string> = {}
try {
parsedHeaders = JSON.parse(headers)
} catch {
throw new Error('Invalid headers JSON')
}
const fetchOptions: RequestInit = {
method,
headers: {
'Content-Type': 'application/json',
...(token && { Authorization: `Bearer ${token}` }),
...parsedHeaders,
},
}
if (method !== 'GET' && method !== 'HEAD') {
try {
const parsedBody = JSON.parse(body)
fetchOptions.body = JSON.stringify(parsedBody)
} catch {
throw new Error('Invalid body JSON')
}
}
const startTime = performance.now()
const res = await fetch(url, fetchOptions)
const endTime = performance.now()
const duration = Math.round(endTime - startTime)
let data
const contentType = res.headers.get('content-type')
if (contentType && contentType.includes('application/json')) {
data = await res.json()
} else {
data = await res.text()
}
setResponse({
status: res.status,
statusText: res.statusText,
duration: `${duration}ms`,
headers: Object.fromEntries(res.headers.entries()),
data,
})
// Add to history
setHistory(prev => [
{ method, endpoint, timestamp: new Date().toLocaleTimeString() },
...prev.slice(0, 9), // Keep last 10
])
} catch (err: any) {
setError(err.message || 'An error occurred')
} finally {
setLoading(false)
}
}
const loadFromHistory = (item: { method: string; endpoint: string }) => {
setMethod(item.method)
setEndpoint(item.endpoint)
}
const getStatusColor = (status: number) => {
if (status >= 200 && status < 300) return 'bg-green-500'
if (status >= 300 && status < 400) return 'bg-yellow-500'
if (status >= 400) return 'bg-red-500'
return 'bg-gray-500'
}
return (
<div className="max-w-6xl mx-auto p-6 space-y-6">
<div className="flex items-center justify-between mb-6">
<h1 className="text-3xl font-bold text-gray-900">API Demo & Testing</h1>
<span className="px-3 py-1 bg-gray-200 rounded-full text-sm text-gray-700">
{API_BASE_URL}
</span>
</div>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
{/* Request Panel */}
<div className="lg:col-span-2 bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">Request</h2>
<div className="space-y-4">
<div className="flex gap-2">
<select
value={method}
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => setMethod(e.target.value)}
className="px-4 py-2 border rounded-md bg-white w-32"
>
<option value="GET">GET</option>
<option value="POST">POST</option>
<option value="PUT">PUT</option>
<option value="PATCH">PATCH</option>
<option value="DELETE">DELETE</option>
</select>
<input
type="text"
placeholder="/api/endpoint"
value={endpoint}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setEndpoint(e.target.value)}
className="flex-1 px-4 py-2 border rounded-md"
/>
<button
onClick={handleSubmit}
disabled={loading}
className="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:bg-blue-400 transition-colors"
>
{loading ? 'Sending...' : 'Send'}
</button>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">Headers (JSON)</label>
<textarea
value={headers}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setHeaders(e.target.value)}
placeholder='{"Custom-Header": "value"}'
className="w-full px-4 py-2 border rounded-md font-mono text-sm min-h-[80px]"
/>
</div>
{method !== 'GET' && method !== 'HEAD' && (
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">Body (JSON)</label>
<textarea
value={body}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setBody(e.target.value)}
placeholder='{"key": "value"}'
className="w-full px-4 py-2 border rounded-md font-mono text-sm min-h-[120px]"
/>
</div>
)}
</div>
</div>
{/* History Panel */}
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">History</h2>
<div className="space-y-2 max-h-[400px] overflow-y-auto">
{history.length === 0 ? (
<p className="text-gray-500 text-sm">No requests yet</p>
) : (
history.map((item, index) => (
<button
key={index}
onClick={() => loadFromHistory(item)}
className="w-full text-left p-3 rounded hover:bg-gray-100 transition-colors text-sm border"
>
<div className="flex items-center gap-2">
<span className={`px-2 py-0.5 rounded text-xs font-medium ${
item.method === 'GET' ? 'bg-blue-100 text-blue-700' : 'bg-gray-100 text-gray-700'
}`}>
{item.method}
</span>
<span className="truncate flex-1 text-gray-900">{item.endpoint}</span>
</div>
<div className="text-xs text-gray-500 mt-1">
{item.timestamp}
</div>
</button>
))
)}
</div>
</div>
{/* Response Panel */}
<div className="lg:col-span-3 bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">Response</h2>
{error ? (
<div className="p-4 bg-red-50 border border-red-200 rounded-md text-red-700">
<p className="font-medium">Error</p>
<p className="text-sm">{error}</p>
</div>
) : response ? (
<div className="space-y-4">
<div className="flex items-center gap-4">
<span className={`px-3 py-1 rounded-full text-white text-sm font-medium ${getStatusColor(response.status)}`}>
{response.status} {response.statusText}
</span>
<span className="px-3 py-1 border rounded-full text-sm">
{response.duration}
</span>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">Response Headers</label>
<pre className="bg-gray-100 p-3 rounded-md overflow-x-auto text-xs">
{JSON.stringify(response.headers, null, 2)}
</pre>
</div>
<div className="space-y-2">
<label className="block text-sm font-medium text-gray-700">Response Body</label>
<pre className="bg-gray-100 p-3 rounded-md overflow-x-auto text-xs">
{typeof response.data === 'string'
? response.data
: JSON.stringify(response.data, null, 2)}
</pre>
</div>
</div>
) : (
<div className="text-center py-12 text-gray-500">
<p>Send a request to see the response</p>
</div>
)}
</div>
</div>
{/* Quick Links */}
<div className="bg-white rounded-lg shadow-md p-6">
<h2 className="text-xl font-semibold mb-4">Quick Test Endpoints</h2>
<div className="flex flex-wrap gap-2">
{[
{ method: 'GET', endpoint: '/trpc/user.banner.getActiveBanners' },
{ method: 'GET', endpoint: '/trpc/user.product.getAllProducts' },
{ method: 'GET', endpoint: '/trpc/user.user.getCurrentUser' },
{ method: 'POST', endpoint: '/trpc/user.auth.login' },
].map((item, index) => (
<button
key={index}
onClick={() => loadFromHistory(item)}
className="px-4 py-2 border rounded-md hover:bg-gray-50 transition-colors text-sm"
>
<span className="mr-2 px-2 py-0.5 bg-gray-200 rounded text-xs">
{item.method}
</span>
{item.endpoint}
</button>
))}
</div>
</div>
</div>
)
}