Browse Source

debug

master
mortezaei 2 months ago
parent
commit
023ae599e0
  1. 30
      next.config.ts
  2. 32
      src/app/layout.tsx
  3. 16
      src/app/loading.tsx
  4. 2
      src/app/providers.tsx
  5. 104
      src/components/dev/debug-toast.tsx
  6. 48
      src/lib/http.ts
  7. 27
      src/lib/performance.ts

30
next.config.ts

@ -2,7 +2,13 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: 'standalone', output: 'standalone',
// Compression
compress: true,
// Image optimization
images: { images: {
formats: ['image/avif', 'image/webp'],
remotePatterns: [ remotePatterns: [
{ {
protocol: "https", protocol: "https",
@ -10,6 +16,30 @@ const nextConfig: NextConfig = {
}, },
], ],
}, },
// Headers for caching and preload
async headers() {
return [
{
source: '/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=3600, stale-while-revalidate=86400',
},
],
},
{
source: '/fonts/:path*',
headers: [
{
key: 'Cache-Control',
value: 'public, max-age=31536000, immutable',
},
],
},
];
},
}; };
export default nextConfig; export default nextConfig;

32
src/app/layout.tsx

@ -1,15 +1,22 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Amiri } from "next/font/google"; import { Amiri } from "next/font/google";
import localFont from "next/font/local"; import localFont from "next/font/local";
import DevClickToComponent from "@/components/dev/dev-click-to-component";
import DebugToast from "@/components/dev/debug-toast";
import dynamic from "next/dynamic";
import Providers from "./providers"; import Providers from "./providers";
import "./globals.css"; import "./globals.css";
const DevClickToComponent = dynamic(
() => import("@/components/dev/dev-click-to-component"),
{ ssr: false }
);
const faminela = localFont({ const faminela = localFont({
src: "../../public/fonts/Faminela/Faminela.otf", src: "../../public/fonts/Faminela/Faminela.otf",
variable: "--font-faminela-local", variable: "--font-faminela-local",
display: "swap", display: "swap",
preload: true,
fallback: ["Arial", "sans-serif"],
}); });
const amiri = Amiri({ const amiri = Amiri({
@ -17,11 +24,19 @@ const amiri = Amiri({
subsets: ["arabic"], subsets: ["arabic"],
variable: "--font-amiri", variable: "--font-amiri",
display: "swap", display: "swap",
preload: true,
fallback: ["Arial", "sans-serif"],
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
title: "ازدواج حبیب",
description: "سامانه ازدواج اسلامی حبیب",
viewport: {
width: "device-width",
initialScale: 1,
maximumScale: 1,
},
themeColor: "#ffffff",
}; };
export default function RootLayout({ export default function RootLayout({
@ -32,11 +47,19 @@ export default function RootLayout({
return ( return (
<html lang="en"> <html lang="en">
<head> <head>
<link rel="preconnect" href="https://habibapp.com" />
<link rel="dns-prefetch" href="https://habibapp.com" />
<script <script
dangerouslySetInnerHTML={{ dangerouslySetInnerHTML={{
__html: ` __html: `
if (typeof window !== 'undefined') { if (typeof window !== 'undefined') {
window.onFlutterResponse = window.onFlutterResponse || function() {}; window.onFlutterResponse = window.onFlutterResponse || function() {};
// Performance monitoring
window.addEventListener('load', function() {
var perfData = performance.getEntriesByType('navigation')[0];
console.log('⏱️ Load time:', Math.round(perfData.loadEventEnd - perfData.fetchStart), 'ms');
});
} }
`, `,
}} }}
@ -46,7 +69,6 @@ export default function RootLayout({
<Providers> <Providers>
<div className="app-shell">{children}</div> <div className="app-shell">{children}</div>
</Providers> </Providers>
<DebugToast />
{process.env.NODE_ENV === "development" ? ( {process.env.NODE_ENV === "development" ? (
<DevClickToComponent /> <DevClickToComponent />
) : null} ) : null}

16
src/app/loading.tsx

@ -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>
);
}

2
src/app/providers.tsx

@ -15,6 +15,8 @@ export default function Providers({ children }: ProvidersProps) {
queries: { queries: {
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
retry: 1, retry: 1,
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 10 * 60 * 1000, // 10 minutes (formerly cacheTime)
}, },
}, },
}), }),

104
src/components/dev/debug-toast.tsx

@ -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>
);
}

48
src/lib/http.ts

@ -64,54 +64,10 @@ http.interceptors.request.use((config) => {
config.url = ""; config.url = "";
} }
(config as any).requestStartTime = Date.now();
return config; return config;
}); });
http.interceptors.response.use( http.interceptors.response.use(
(response) => {
const config = response.config as any;
const debugLog = {
id: Date.now().toString(),
timestamp: new Date().toLocaleString("fa-IR"),
url: config.url || "unknown",
fullUrl: config.baseURL ? `${config.baseURL}${config.url}` : config.url,
method: config.method?.toUpperCase() || "GET",
status: response.status,
statusText: response.statusText,
requestHeaders: config.headers || {},
requestBody: config.data,
responseData: response.data,
duration: Date.now() - (config.requestStartTime || Date.now()),
};
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("debug-log", { detail: debugLog }));
}
return response;
},
(error) => {
const config = error.config as any;
const debugLog = {
id: Date.now().toString(),
timestamp: new Date().toLocaleString("fa-IR"),
url: config?.url || "unknown",
fullUrl: config?.baseURL ? `${config.baseURL}${config.url}` : config?.url,
method: config?.method?.toUpperCase() || "GET",
status: error.response?.status || 0,
statusText: error.response?.statusText || "Network Error",
requestHeaders: config?.headers || {},
requestBody: config?.data,
responseData: error.response?.data,
error: error.message,
duration: Date.now() - (config?.requestStartTime || Date.now()),
};
if (typeof window !== "undefined") {
window.dispatchEvent(new CustomEvent("debug-log", { detail: debugLog }));
}
return Promise.reject(error);
}
(response) => response,
(error) => Promise.reject(error)
); );

27
src/lib/performance.ts

@ -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,
}));
}
});
}
Loading…
Cancel
Save