import { useState, useMemo } from 'react'; import { Copy, Download, ChevronRight, ChevronDown, Check } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { ScrollArea } from '@/components/ui/scroll-area'; import { Input } from '@/components/ui/input'; import { toast } from 'sonner'; interface RawDataViewerProps { data: any; title?: string; } export function RawDataViewer({ data, title = 'Raw Data' }: RawDataViewerProps) { const [searchQuery, setSearchQuery] = useState(''); const [expandedPaths, setExpandedPaths] = useState>(new Set(['root'])); const [copiedPath, setCopiedPath] = useState(null); const jsonString = useMemo(() => JSON.stringify(data, null, 2), [data]); const handleCopy = async () => { try { await navigator.clipboard.writeText(jsonString); toast.success('Copied to clipboard'); } catch (error) { toast.error('Failed to copy'); } }; const handleDownload = () => { const blob = new Blob([jsonString], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${title.toLowerCase().replace(/\s+/g, '-')}-${Date.now()}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); toast.success('Download started'); }; const togglePath = (path: string) => { const newExpanded = new Set(expandedPaths); if (newExpanded.has(path)) { newExpanded.delete(path); } else { newExpanded.add(path); } setExpandedPaths(newExpanded); }; const handleCopyValue = async (value: any, path: string) => { try { const valueString = typeof value === 'string' ? value : JSON.stringify(value, null, 2); await navigator.clipboard.writeText(valueString); setCopiedPath(path); setTimeout(() => setCopiedPath(null), 2000); toast.success('Value copied'); } catch (error) { toast.error('Failed to copy'); } }; const renderValue = (value: any, key: string, path: string, depth: number = 0): JSX.Element => { const isExpanded = expandedPaths.has(path); const indent = depth * 20; // Filter by search query if (searchQuery && !JSON.stringify({ [key]: value }).toLowerCase().includes(searchQuery.toLowerCase())) { return <>; } if (value === null) { return (
{key}: null
); } if (typeof value === 'boolean') { return (
{key}: {value.toString()}
); } if (typeof value === 'number') { return (
{key}: {value}
); } if (typeof value === 'string') { const isUrl = value.startsWith('http://') || value.startsWith('https://'); const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); const isDate = !isNaN(Date.parse(value)) && value.includes('-'); return (
{key}: {isUrl ? ( "{value}" ) : ( "{value}" )}
); } if (Array.isArray(value)) { return (
togglePath(path)} > {isExpanded ? : } {key}: Array[{value.length}]
{isExpanded && (
{value.map((item, index) => renderValue(item, `[${index}]`, `${path}.${index}`, depth + 1))}
)}
); } if (typeof value === 'object') { const keys = Object.keys(value); return (
togglePath(path)} > {isExpanded ? : } {key}: Object ({keys.length} keys)
{isExpanded && (
{keys.map((k) => renderValue(value[k], k, `${path}.${k}`, depth + 1))}
)}
); } return <>; }; return (
{/* Header */}

{title}

{/* Search */} setSearchQuery(e.target.value)} className="max-w-sm" /> {/* JSON Tree */}
{Object.keys(data).map((key) => renderValue(data[key], key, `root.${key}`, 0))}
{/* Stats */}
Keys: {Object.keys(data).length} Size: {(jsonString.length / 1024).toFixed(2)} KB Lines: {jsonString.split('\n').length}
); }