7 changed files with 104 additions and 155 deletions
-
30next.config.ts
-
32src/app/layout.tsx
-
16src/app/loading.tsx
-
2src/app/providers.tsx
-
104src/components/dev/debug-toast.tsx
-
48src/lib/http.ts
-
27src/lib/performance.ts
@ -0,0 +1,16 @@ |
|||||
|
export default function Loading() { |
||||
|
return ( |
||||
|
<div className="min-h-screen bg-white p-4 animate-pulse"> |
||||
|
<div className="h-8 bg-gray-200 rounded-lg w-3/4 mb-6"></div> |
||||
|
<div className="space-y-3"> |
||||
|
<div className="h-4 bg-gray-200 rounded w-full"></div> |
||||
|
<div className="h-4 bg-gray-200 rounded w-5/6"></div> |
||||
|
<div className="h-4 bg-gray-200 rounded w-4/6"></div> |
||||
|
</div> |
||||
|
<div className="mt-8 space-y-4"> |
||||
|
<div className="h-24 bg-gray-200 rounded-lg"></div> |
||||
|
<div className="h-24 bg-gray-200 rounded-lg"></div> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
@ -1,104 +0,0 @@ |
|||||
"use client"; |
|
||||
|
|
||||
import { useEffect, useState } from "react"; |
|
||||
|
|
||||
interface DebugLog { |
|
||||
id: string; |
|
||||
timestamp: string; |
|
||||
url: string; |
|
||||
fullUrl?: string; |
|
||||
method: string; |
|
||||
status: number; |
|
||||
statusText?: string; |
|
||||
requestHeaders: Record<string, string>; |
|
||||
requestBody?: any; |
|
||||
responseData?: any; |
|
||||
error?: string; |
|
||||
duration?: number; |
|
||||
} |
|
||||
|
|
||||
export default function DebugToast() { |
|
||||
const [logs, setLogs] = useState<DebugLog[]>([]); |
|
||||
const [isOpen, setIsOpen] = useState(true); |
|
||||
|
|
||||
useEffect(() => { |
|
||||
const handleDebugLog = (event: any) => { |
|
||||
setLogs((prev) => [event.detail, ...prev].slice(0, 20)); |
|
||||
}; |
|
||||
|
|
||||
window.addEventListener("debug-log", handleDebugLog); |
|
||||
return () => window.removeEventListener("debug-log", handleDebugLog); |
|
||||
}, []); |
|
||||
|
|
||||
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 ( |
|
||||
<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 |
|
||||
onClick={() => setIsOpen(!isOpen)} |
|
||||
className="text-white hover:text-blue-300 px-2" |
|
||||
> |
|
||||
{isOpen ? "−" : "+"} |
|
||||
</button> |
|
||||
</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> |
|
||||
|
|
||||
<div className="mb-1 text-gray-300 break-all text-[10px]"> |
|
||||
{log.fullUrl || log.url} |
|
||||
</div> |
|
||||
|
|
||||
<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> |
|
||||
</details> |
|
||||
|
|
||||
{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> |
|
||||
</details> |
|
||||
)} |
|
||||
</div> |
|
||||
))} |
|
||||
</div> |
|
||||
); |
|
||||
} |
|
||||
@ -0,0 +1,27 @@ |
|||||
|
export function measurePerformance() { |
||||
|
if (typeof window === 'undefined') return; |
||||
|
|
||||
|
window.addEventListener('load', () => { |
||||
|
const perfData = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming; |
||||
|
|
||||
|
const metrics = { |
||||
|
dns: perfData.domainLookupEnd - perfData.domainLookupStart, |
||||
|
tcp: perfData.connectEnd - perfData.connectStart, |
||||
|
ttfb: perfData.responseStart - perfData.requestStart, |
||||
|
download: perfData.responseEnd - perfData.responseStart, |
||||
|
domInteractive: perfData.domInteractive - perfData.fetchStart, |
||||
|
domComplete: perfData.domComplete - perfData.fetchStart, |
||||
|
loadComplete: perfData.loadEventEnd - perfData.fetchStart, |
||||
|
}; |
||||
|
|
||||
|
console.log('⏱️ Performance Metrics:', metrics); |
||||
|
|
||||
|
// ارسال به analytics (اختیاری)
|
||||
|
if ((window as any).HabibApp) { |
||||
|
(window as any).HabibApp.postMessage(JSON.stringify({ |
||||
|
action: 'performance_metrics', |
||||
|
data: metrics, |
||||
|
})); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue