From 1bc9d1d9a2edd1e5629f55c3fcbdece554e796fa Mon Sep 17 00:00:00 2001 From: mortezaei Date: Fri, 15 May 2026 05:28:17 +0330 Subject: [PATCH 01/21] feat: add 'token' to request headers to forward --- src/app/api/proxy/route.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/api/proxy/route.ts b/src/app/api/proxy/route.ts index bd0ceaa..7923dd3 100644 --- a/src/app/api/proxy/route.ts +++ b/src/app/api/proxy/route.ts @@ -9,6 +9,7 @@ const REQUEST_HEADERS_TO_FORWARD = [ "accept", "accept-language", "authorization", + "token", "content-type", "x-csrf-token", "x-csrftoken", From b3b6ddd6d5382710f2c1a0963381be7c88c0b6a9 Mon Sep 17 00:00:00 2001 From: mortezaei Date: Fri, 15 May 2026 05:41:45 +0330 Subject: [PATCH 02/21] feat: add 'token' to request headers to forward --- src/app/questions-list/page.tsx | 3 +-- src/components/questions/required-steps-card.tsx | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/app/questions-list/page.tsx b/src/app/questions-list/page.tsx index 6a54b88..9cd4b90 100644 --- a/src/app/questions-list/page.tsx +++ b/src/app/questions-list/page.tsx @@ -48,9 +48,8 @@ export default function QuestionsListPage() { const profileContext = useMemo( () => ({ gender: profile?.gender, - age: profile?.age, }), - [profile?.gender, profile?.age], + [profile?.gender], ); const allRequiredSectionsCompleted = useMemo(() => { if (!sections?.length) { diff --git a/src/components/questions/required-steps-card.tsx b/src/components/questions/required-steps-card.tsx index 115f1fe..5e8d31e 100644 --- a/src/components/questions/required-steps-card.tsx +++ b/src/components/questions/required-steps-card.tsx @@ -34,9 +34,8 @@ export default function RequiredStepsCard() { const profileContext = useMemo( () => ({ gender: profile?.gender, - age: profile?.age, }), - [profile?.gender, profile?.age], + [profile?.gender], ); const fallbackRequiredSteps: RequiredStep[] = getQuestionListItems(locale) From b3d540e8a774de3a3ad86c08f41613f710dbca6a Mon Sep 17 00:00:00 2001 From: mortezaei Date: Fri, 15 May 2026 05:54:41 +0330 Subject: [PATCH 03/21] debug --- src/app/layout.tsx | 1 + src/components/dev/debug-toast.tsx | 70 ++++++++++++++++++++++++++++++ src/lib/http.ts | 24 ++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/components/dev/debug-toast.tsx diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4ef905c..edc849b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -45,6 +45,7 @@ export default function RootLayout({
{children}
+ {process.env.NODE_ENV === "development" ? ( ) : null} diff --git a/src/components/dev/debug-toast.tsx b/src/components/dev/debug-toast.tsx new file mode 100644 index 0000000..baadcec --- /dev/null +++ b/src/components/dev/debug-toast.tsx @@ -0,0 +1,70 @@ +"use client"; + +import { useEffect, useState } from "react"; + +interface DebugLog { + id: string; + timestamp: string; + url: string; + method: string; + status: number; + headers: Record; + response?: any; +} + +export default function DebugToast() { + const [logs, setLogs] = useState([]); + const [isOpen, setIsOpen] = useState(false); + + useEffect(() => { + const handleDebugLog = (event: CustomEvent) => { + setLogs((prev) => [event.detail, ...prev].slice(0, 10)); + setIsOpen(true); + }; + + window.addEventListener("debug-log" as any, handleDebugLog); + return () => window.removeEventListener("debug-log" as any, handleDebugLog); + }, []); + + if (!isOpen || logs.length === 0) return null; + + return ( +
+
+

🐛 Debug Log

+ +
+ + {logs.map((log) => ( +
+
+ {log.method} {log.status} +
+
{log.url}
+
{log.timestamp}
+ +
+ Headers +
+              {JSON.stringify(log.headers, null, 2)}
+            
+
+ + {log.response && ( +
+ Response +
+                {JSON.stringify(log.response, null, 2)}
+              
+
+ )} +
+ ))} +
+ ); +} diff --git a/src/lib/http.ts b/src/lib/http.ts index d0dc6de..1b091c0 100644 --- a/src/lib/http.ts +++ b/src/lib/http.ts @@ -66,3 +66,27 @@ http.interceptors.request.use((config) => { return config; }); + +http.interceptors.response.use( + (response) => response, + (error) => { + if (error.response?.status === 401) { + const debugLog = { + id: Date.now().toString(), + timestamp: new Date().toLocaleString("fa-IR"), + url: error.config?.url || "unknown", + method: error.config?.method?.toUpperCase() || "GET", + status: 401, + headers: error.config?.headers || {}, + response: error.response?.data, + }; + + if (typeof window !== "undefined") { + window.dispatchEvent( + new CustomEvent("debug-log", { detail: debugLog }) + ); + } + } + return Promise.reject(error); + } +); From ba72b94a3b0755c3b9160ffdbd5c9e0b56ba7af8 Mon Sep 17 00:00:00 2001 From: mortezaei Date: Fri, 15 May 2026 05:56:14 +0330 Subject: [PATCH 04/21] debug --- src/app/layout.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/layout.tsx b/src/app/layout.tsx index edc849b..ec92091 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata } from "next"; import { Amiri } from "next/font/google"; import localFont from "next/font/local"; import DevClickToComponent from "@/components/dev/dev-click-to-component"; +import DebugToast from "@/components/dev/debug-toast"; import Providers from "./providers"; import "./globals.css"; From 0c86e0ca865f518f3caed0d06a120bc1bdd70306 Mon Sep 17 00:00:00 2001 From: mortezaei Date: Fri, 15 May 2026 06:04:41 +0330 Subject: [PATCH 05/21] debug --- src/components/dev/debug-toast.tsx | 100 +++++++++++++++++++---------- src/lib/http.ts | 59 ++++++++++++----- 2 files changed, 109 insertions(+), 50 deletions(-) diff --git a/src/components/dev/debug-toast.tsx b/src/components/dev/debug-toast.tsx index baadcec..5b5a788 100644 --- a/src/components/dev/debug-toast.tsx +++ b/src/components/dev/debug-toast.tsx @@ -6,60 +6,94 @@ interface DebugLog { id: string; timestamp: string; url: string; + fullUrl?: string; method: string; status: number; - headers: Record; - response?: any; + statusText?: string; + requestHeaders: Record; + requestBody?: any; + responseData?: any; + error?: string; + duration?: number; } export default function DebugToast() { const [logs, setLogs] = useState([]); - const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen] = useState(true); useEffect(() => { - const handleDebugLog = (event: CustomEvent) => { - 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 ( -
-
-

🐛 Debug Log

- +
+
+

🔍 API Debug ({logs.length})

+
+ + +
- {logs.map((log) => ( -
-
- {log.method} {log.status} + {isOpen && logs.map((log) => ( +
+
+ {log.method} {log.status} {log.statusText} + {log.duration && {log.duration}ms} +
+ +
+ {log.fullUrl || log.url}
-
{log.url}
-
{log.timestamp}
-
- Headers -
-              {JSON.stringify(log.headers, null, 2)}
+          
{log.timestamp}
+ + {log.error && ( +
❌ {log.error}
+ )} + +
+ 📤 Request Headers +
+              {JSON.stringify(log.requestHeaders, null, 2)}
             
- {log.response && ( -
- Response -
-                {JSON.stringify(log.response, null, 2)}
+          {log.requestBody && (
+            
+ 📦 Request Body +
+                {JSON.stringify(log.requestBody, null, 2)}
+              
+
+ )} + + {log.responseData && ( +
+ 📥 Response +
+                {JSON.stringify(log.responseData, null, 2)}
               
)} diff --git a/src/lib/http.ts b/src/lib/http.ts index 1b091c0..4dfa1c6 100644 --- a/src/lib/http.ts +++ b/src/lib/http.ts @@ -64,29 +64,54 @@ http.interceptors.request.use((config) => { config.url = ""; } + (config as any).requestStartTime = Date.now(); return config; }); http.interceptors.response.use( - (response) => response, + (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) => { - if (error.response?.status === 401) { - const debugLog = { - id: Date.now().toString(), - timestamp: new Date().toLocaleString("fa-IR"), - url: error.config?.url || "unknown", - method: error.config?.method?.toUpperCase() || "GET", - status: 401, - headers: error.config?.headers || {}, - response: error.response?.data, - }; - - if (typeof window !== "undefined") { - window.dispatchEvent( - new CustomEvent("debug-log", { detail: debugLog }) - ); - } + 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); } ); From 023ae599e0805ead174963fdd2dd4053e2255bd7 Mon Sep 17 00:00:00 2001 From: mortezaei Date: Fri, 15 May 2026 07:01:28 +0330 Subject: [PATCH 06/21] debug --- next.config.ts | 30 +++++++++ src/app/layout.tsx | 32 +++++++-- src/app/loading.tsx | 16 +++++ src/app/providers.tsx | 2 + src/components/dev/debug-toast.tsx | 104 ----------------------------- src/lib/http.ts | 48 +------------ src/lib/performance.ts | 27 ++++++++ 7 files changed, 104 insertions(+), 155 deletions(-) create mode 100644 src/app/loading.tsx delete mode 100644 src/components/dev/debug-toast.tsx create mode 100644 src/lib/performance.ts diff --git a/next.config.ts b/next.config.ts index 418d1d3..485fd4e 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,7 +2,13 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: 'standalone', + + // Compression + compress: true, + + // Image optimization images: { + formats: ['image/avif', 'image/webp'], remotePatterns: [ { 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; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index ec92091..107dd78 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,15 +1,22 @@ import type { Metadata } from "next"; import { Amiri } from "next/font/google"; 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 "./globals.css"; +const DevClickToComponent = dynamic( + () => import("@/components/dev/dev-click-to-component"), + { ssr: false } +); + + const faminela = localFont({ src: "../../public/fonts/Faminela/Faminela.otf", variable: "--font-faminela-local", display: "swap", + preload: true, + fallback: ["Arial", "sans-serif"], }); const amiri = Amiri({ @@ -17,11 +24,19 @@ const amiri = Amiri({ subsets: ["arabic"], variable: "--font-amiri", display: "swap", + preload: true, + fallback: ["Arial", "sans-serif"], }); 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({ @@ -32,11 +47,19 @@ export default function RootLayout({ return ( + +