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