ui integration and rest api includes

This commit is contained in:
2026-05-20 15:34:41 +05:30
parent f4dc2b6a5a
commit 349d152b76
18 changed files with 3631 additions and 589 deletions

View File

@@ -1,12 +1,15 @@
import { useEffect, useRef, useState } from 'react'
import EndpointCard from './EndpointCard'
import { getTopicIcon } from '../lib/icons'
import { BASE_URL } from '../data/topics'
// Strip the documented BASE_URL so fetches are same-origin and get
// proxied by Vite (dev) or Express (prod), which injects the secret header.
function toProxyPath(fullUrl) {
if (fullUrl.startsWith(BASE_URL)) return fullUrl.slice(BASE_URL.length)
import { LEGACY_BASE_URL } from '../data/topics'
function toProxyPath(fullUrl, baseUrl) {
// Only proxy the legacy Hasura API (to inject the admin secret via server)
if (baseUrl === LEGACY_BASE_URL && fullUrl.startsWith(baseUrl)) {
return fullUrl.slice(baseUrl.length)
}
// Let the browser hit the new REST API directly (it supports CORS)
return fullUrl
}
@@ -21,8 +24,6 @@ export default function TopicView({ topic, searchQuery }) {
)
: topic.endpoints
// Only one endpoint's response is visible at a time. Sending on a new
// endpoint replaces the previous one (and cancels its in-flight fetch).
const [active, setActive] = useState(null)
const abortRef = useRef(null)
@@ -41,12 +42,28 @@ export default function TopicView({ topic, searchQuery }) {
setActive({ name: endpoint.name, result: null, loading: true })
const start = Date.now()
try {
const res = await fetch(toProxyPath(composedUrl), {
const fetchOptions = {
method: endpoint.method,
headers: { 'Content-Type': 'application/json' },
signal: controller.signal
})
}
if (['POST', 'PUT', 'PATCH'].includes(endpoint.method) && endpoint.body) {
if (typeof endpoint.body === 'string') {
try {
// Re-stringify in case it's a badly formatted JSON string, but mostly it's just raw string
fetchOptions.body = JSON.stringify(JSON.parse(endpoint.body))
} catch {
fetchOptions.body = endpoint.body
}
} else {
fetchOptions.body = JSON.stringify(endpoint.body)
}
}
const res = await fetch(toProxyPath(composedUrl, topic.baseUrl), fetchOptions)
const ms = Date.now() - start
const text = await res.text()
let body
@@ -71,23 +88,26 @@ export default function TopicView({ topic, searchQuery }) {
}
return (
<div className="max-w-4xl">
<div className="mb-8 flex items-start gap-4">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-brand-500 to-indigo-600 flex items-center justify-center shadow-glow flex-shrink-0">
<Icon className="text-white w-6 h-6" />
</div>
<div>
<h1 className="text-3xl font-bold text-slate-900 tracking-tight">{topic.name}</h1>
<p className="text-slate-600 mt-2 leading-relaxed">{topic.description}</p>
<div className="mt-3 text-xs text-slate-400 uppercase tracking-wide">
{filtered.length} endpoint{filtered.length === 1 ? '' : 's'}
</div>
<div className="pb-24">
<div className="mb-12">
<h1 className="text-4xl lg:text-5xl font-bold text-slate-900 mb-4 tracking-tight">{topic.name}</h1>
<p className="text-lg text-slate-600 max-w-3xl leading-relaxed">{topic.description}</p>
<div className="mt-4 flex items-center gap-3">
<span className={`inline-flex items-center gap-1.5 text-[11px] font-bold uppercase tracking-widest px-3 py-1 rounded-full border ${
topic.type === 'legacy'
? 'bg-brand-50 text-brand-600 border-brand-100'
: 'bg-indigo-50 text-indigo-600 border-indigo-100'
}`}>
<span className="w-1.5 h-1.5 rounded-full bg-current"></span>
{topic.type === 'legacy' ? 'Hasura API' : 'REST API'}
</span>
<span className="text-sm text-slate-400">{filtered.length} endpoint{filtered.length !== 1 ? 's' : ''}</span>
</div>
</div>
<div className="space-y-4">
{filtered.length === 0 ? (
<div className="text-center py-16 text-slate-400 text-sm">
<div className="text-center py-20 text-slate-400 text-sm bg-white rounded-2xl border border-slate-200/60">
No endpoints match "{searchQuery}".
</div>
) : (
@@ -95,8 +115,9 @@ export default function TopicView({ topic, searchQuery }) {
const isActive = active?.name === e.name
return (
<EndpointCard
key={`${topic.id}/${e.name}`}
key={`${topic.uniqueId}/${e.name}`}
endpoint={e}
baseUrl={topic.baseUrl}
onSend={handleSend}
result={isActive ? active.result : null}
loading={isActive ? active.loading : false}