|
|
@ -6,60 +6,94 @@ interface DebugLog { |
|
|
id: string; |
|
|
id: string; |
|
|
timestamp: string; |
|
|
timestamp: string; |
|
|
url: string; |
|
|
url: string; |
|
|
|
|
|
fullUrl?: string; |
|
|
method: string; |
|
|
method: string; |
|
|
status: number; |
|
|
status: number; |
|
|
headers: Record<string, string>; |
|
|
|
|
|
response?: any; |
|
|
|
|
|
|
|
|
statusText?: string; |
|
|
|
|
|
requestHeaders: Record<string, string>; |
|
|
|
|
|
requestBody?: any; |
|
|
|
|
|
responseData?: any; |
|
|
|
|
|
error?: string; |
|
|
|
|
|
duration?: number; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export default function DebugToast() { |
|
|
export default function DebugToast() { |
|
|
const [logs, setLogs] = useState<DebugLog[]>([]); |
|
|
const [logs, setLogs] = useState<DebugLog[]>([]); |
|
|
const [isOpen, setIsOpen] = useState(false); |
|
|
|
|
|
|
|
|
const [isOpen, setIsOpen] = useState(true); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
const handleDebugLog = (event: CustomEvent<DebugLog>) => { |
|
|
|
|
|
setLogs((prev) => [event.detail, ...prev].slice(0, 10)); |
|
|
|
|
|
setIsOpen(true); |
|
|
|
|
|
|
|
|
const handleDebugLog = (event: any) => { |
|
|
|
|
|
setLogs((prev) => [event.detail, ...prev].slice(0, 20)); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
window.addEventListener("debug-log" as any, handleDebugLog); |
|
|
|
|
|
return () => window.removeEventListener("debug-log" as any, handleDebugLog); |
|
|
|
|
|
|
|
|
window.addEventListener("debug-log", handleDebugLog); |
|
|
|
|
|
return () => window.removeEventListener("debug-log", handleDebugLog); |
|
|
}, []); |
|
|
}, []); |
|
|
|
|
|
|
|
|
if (!isOpen || logs.length === 0) return null; |
|
|
|
|
|
|
|
|
const getStatusColor = (status: number) => { |
|
|
|
|
|
if (status >= 200 && status < 300) return "bg-green-900 border-green-500"; |
|
|
|
|
|
if (status >= 400) return "bg-red-900 border-red-500"; |
|
|
|
|
|
return "bg-yellow-900 border-yellow-500"; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<div className="fixed top-4 right-4 z-[9999] max-w-md max-h-[80vh] overflow-auto bg-red-900 text-white p-4 rounded-lg shadow-2xl border-2 border-red-500"> |
|
|
|
|
|
<div className="flex justify-between items-center mb-2"> |
|
|
|
|
|
<h3 className="font-bold text-sm">🐛 Debug Log</h3> |
|
|
|
|
|
|
|
|
<div className="fixed top-2 left-2 z-[9999] max-w-[95vw] max-h-[90vh] overflow-auto bg-gray-900 text-white p-3 rounded-lg shadow-2xl border-2 border-gray-600 text-[11px]"> |
|
|
|
|
|
<div className="flex justify-between items-center mb-2 sticky top-0 bg-gray-900 pb-2"> |
|
|
|
|
|
<h3 className="font-bold">🔍 API Debug ({logs.length})</h3> |
|
|
|
|
|
<div className="flex gap-2"> |
|
|
|
|
|
<button |
|
|
|
|
|
onClick={() => setLogs([])} |
|
|
|
|
|
className="text-white hover:text-red-300 px-2" |
|
|
|
|
|
> |
|
|
|
|
|
پاک |
|
|
|
|
|
</button> |
|
|
<button |
|
|
<button |
|
|
onClick={() => setIsOpen(false)} |
|
|
|
|
|
className="text-white hover:text-red-200" |
|
|
|
|
|
|
|
|
onClick={() => setIsOpen(!isOpen)} |
|
|
|
|
|
className="text-white hover:text-blue-300 px-2" |
|
|
> |
|
|
> |
|
|
✕ |
|
|
|
|
|
|
|
|
{isOpen ? "−" : "+"} |
|
|
</button> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
{isOpen && logs.map((log) => ( |
|
|
|
|
|
<div key={log.id} className={`mb-3 p-2 rounded border ${getStatusColor(log.status)}`}> |
|
|
|
|
|
<div className="font-bold mb-1 flex justify-between"> |
|
|
|
|
|
<span>{log.method} {log.status} {log.statusText}</span> |
|
|
|
|
|
{log.duration && <span className="text-blue-300">{log.duration}ms</span>} |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
{logs.map((log) => ( |
|
|
|
|
|
<div key={log.id} className="mb-4 p-2 bg-red-950 rounded text-xs"> |
|
|
|
|
|
<div className="font-bold mb-1"> |
|
|
|
|
|
{log.method} {log.status} |
|
|
|
|
|
|
|
|
<div className="mb-1 text-gray-300 break-all text-[10px]"> |
|
|
|
|
|
{log.fullUrl || log.url} |
|
|
</div> |
|
|
</div> |
|
|
<div className="mb-1 text-red-300 break-all">{log.url}</div> |
|
|
|
|
|
<div className="mb-1 text-yellow-300">{log.timestamp}</div> |
|
|
|
|
|
|
|
|
|
|
|
<details className="mt-2"> |
|
|
|
|
|
<summary className="cursor-pointer text-blue-300">Headers</summary> |
|
|
|
|
|
<pre className="mt-1 text-[10px] overflow-auto"> |
|
|
|
|
|
{JSON.stringify(log.headers, null, 2)} |
|
|
|
|
|
|
|
|
<div className="text-yellow-300 text-[10px] mb-2">{log.timestamp}</div> |
|
|
|
|
|
|
|
|
|
|
|
{log.error && ( |
|
|
|
|
|
<div className="mb-2 text-red-300 text-[10px]">❌ {log.error}</div> |
|
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
<details className="mt-1"> |
|
|
|
|
|
<summary className="cursor-pointer text-blue-300">📤 Request Headers</summary> |
|
|
|
|
|
<pre className="mt-1 text-[9px] overflow-auto max-h-32 bg-black p-1 rounded"> |
|
|
|
|
|
{JSON.stringify(log.requestHeaders, null, 2)} |
|
|
</pre> |
|
|
</pre> |
|
|
</details> |
|
|
</details> |
|
|
|
|
|
|
|
|
{log.response && ( |
|
|
|
|
|
<details className="mt-2"> |
|
|
|
|
|
<summary className="cursor-pointer text-blue-300">Response</summary> |
|
|
|
|
|
<pre className="mt-1 text-[10px] overflow-auto"> |
|
|
|
|
|
{JSON.stringify(log.response, null, 2)} |
|
|
|
|
|
|
|
|
{log.requestBody && ( |
|
|
|
|
|
<details className="mt-1"> |
|
|
|
|
|
<summary className="cursor-pointer text-blue-300">📦 Request Body</summary> |
|
|
|
|
|
<pre className="mt-1 text-[9px] overflow-auto max-h-32 bg-black p-1 rounded"> |
|
|
|
|
|
{JSON.stringify(log.requestBody, null, 2)} |
|
|
|
|
|
</pre> |
|
|
|
|
|
</details> |
|
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
{log.responseData && ( |
|
|
|
|
|
<details className="mt-1"> |
|
|
|
|
|
<summary className="cursor-pointer text-blue-300">📥 Response</summary> |
|
|
|
|
|
<pre className="mt-1 text-[9px] overflow-auto max-h-32 bg-black p-1 rounded"> |
|
|
|
|
|
{JSON.stringify(log.responseData, null, 2)} |
|
|
</pre> |
|
|
</pre> |
|
|
</details> |
|
|
</details> |
|
|
)} |
|
|
)} |
|
|
|