/** * Component: API Docs Response Viewer * Documentation: documentation/backend/services/api-tokens.md * * Displays API response with syntax highlighting, status badge, and copy functionality. */ 'use client'; import { useState, useMemo } from 'react'; interface ResponseViewerProps { status: number | null; data: string | null; error: string | null; loading: boolean; } function statusColor(status: number): string { if (status >= 200 && status < 300) return 'bg-emerald-100 dark:bg-emerald-900/30 text-emerald-700 dark:text-emerald-300'; if (status >= 400 && status < 500) return 'bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-300'; return 'bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-300'; } /** Tokenize JSON string into typed segments for React rendering */ type JsonToken = { type: 'string' | 'number' | 'boolean' | 'null' | 'plain'; value: string }; function tokenizeJson(json: string): JsonToken[] { const tokens: JsonToken[] = []; const regex = /("(?:[^"\\]|\\.)*")|(\b\d+\.?\d*\b)|(\btrue\b|\bfalse\b)|(\bnull\b)/g; let lastIndex = 0; let match: RegExpExecArray | null; while ((match = regex.exec(json)) !== null) { if (match.index > lastIndex) { tokens.push({ type: 'plain', value: json.slice(lastIndex, match.index) }); } if (match[1] !== undefined) tokens.push({ type: 'string', value: match[1] }); else if (match[2] !== undefined) tokens.push({ type: 'number', value: match[2] }); else if (match[3] !== undefined) tokens.push({ type: 'boolean', value: match[3] }); else if (match[4] !== undefined) tokens.push({ type: 'null', value: match[4] }); lastIndex = regex.lastIndex; } if (lastIndex < json.length) { tokens.push({ type: 'plain', value: json.slice(lastIndex) }); } return tokens; } const TOKEN_COLORS: Record = { string: 'text-emerald-400', number: 'text-blue-400', boolean: 'text-purple-400', null: 'text-purple-400', plain: 'text-gray-300', }; export function ResponseViewer({ status, data, error, loading }: ResponseViewerProps) { const [copied, setCopied] = useState(false); const tokens = useMemo(() => { if (!data) return []; try { const formatted = JSON.stringify(JSON.parse(data), null, 2); return tokenizeJson(formatted); } catch { return [{ type: 'plain' as const, value: data }]; } }, [data]); const handleCopy = async () => { if (!data) return; try { const formatted = JSON.stringify(JSON.parse(data), null, 2); await navigator.clipboard.writeText(formatted); } catch { await navigator.clipboard.writeText(data); } setCopied(true); setTimeout(() => setCopied(false), 2000); }; if (loading) { return (
Sending request...
); } if (error) { return (
{error}
); } if (!data || status === null) return null; return (
{/* Header bar */}
Response {status}
{/* JSON body */}
        {tokens.map((t, i) => (
          {t.value}
        ))}
      
); }