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/api/proxy/route.ts b/src/app/api/proxy/route.ts index bd0ceaa..9fa7e54 100644 --- a/src/app/api/proxy/route.ts +++ b/src/app/api/proxy/route.ts @@ -9,6 +9,8 @@ const REQUEST_HEADERS_TO_FORWARD = [ "accept", "accept-language", "authorization", + "cookie", + "token", "content-type", "x-csrf-token", "x-csrftoken", @@ -94,20 +96,48 @@ function getTargetUrl(request: NextRequest) { return targetUrl; } +function getCookieValue(cookieHeader: string, name: string) { + const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const pattern = new RegExp(`(?:^|;\\s*)${escapedName}=([^;]*)`); + const match = cookieHeader.match(pattern); + + return match ? decodeURIComponent(match[1]) : null; +} + function getRequestHeaders(request: NextRequest, targetUrl: URL) { const headers = new Headers(); const authKey = process.env.NEXT_PUBLIC_AUTH_KEY; + const cookieHeader = request.headers.get("cookie") ?? ""; for (const header of REQUEST_HEADERS_TO_FORWARD) { const value = request.headers.get(header); if (value) { - headers.set(header, value); + if (header === "token") { + // Convert token header to Authorization + headers.set("authorization", `Token ${value}`); + } else { + headers.set(header, value); + } + } + } + + // Prefer a cookie-based token when the browser session is anonymous. + if (!headers.has("authorization")) { + const cookieToken = + getCookieValue(cookieHeader, "HABIB_TOKEN") ?? + getCookieValue(cookieHeader, "habib_token") ?? + getCookieValue(cookieHeader, "token") ?? + getCookieValue(cookieHeader, "auth_token"); + + if (cookieToken) { + headers.set("authorization", `Token ${cookieToken}`); } } - if (authKey) { - headers.set("authorization", `token ${authKey}`); + // Override with authKey if set and no authorization header exists. + if (authKey && !headers.has("authorization")) { + headers.set("authorization", `Token ${authKey}`); } headers.set("accept-encoding", "identity"); diff --git a/src/app/intro/page.tsx b/src/app/intro/page.tsx index 2117e45..d37b896 100644 --- a/src/app/intro/page.tsx +++ b/src/app/intro/page.tsx @@ -1,30 +1,31 @@ "use client"; import Image from "next/image"; -import { useState } from "react"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; import Button from "@/components/ui/button"; import NavigationButton from "@/components/ui/navigation-button"; import ReportActionsSheet from "@/components/ui/report-actions-sheet"; +import { authBridge } from "@/lib/auth-bridge"; import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; +import type { MarriageProfileResponse } from "@/hooks/marriage/types"; import { localizePath } from "@/i18n/config"; import { useI18n } from "@/i18n/provider"; -export default function Intro() { - const { dictionary: t, locale } = useI18n(); - const { data: profile } = useMarriageProfileQuery(); - const [isReportSheetOpen, setIsReportSheetOpen] = useState(false); +function getSubmitPath(profile: MarriageProfileResponse | undefined) { const isInCase = profile?.status === "in_case"; const isFemaleAcceptedFlow = isInCase && (profile?.active_case?.status === "payment_pending" || profile?.active_case?.status === "female_accepted" || profile?.active_case?.status === "payment_done"); - const submitPath = isFemaleAcceptedFlow + + return isFemaleAcceptedFlow ? "/request-accepted" : isInCase && profile?.active_case?.status === "male_accepted" ? "/request-sent" : profile?.status === "pending_onboarding" - ? "/rules" + ? "/terms" : profile?.status === "pending_info" ? "/questions-list" : profile?.status === "waiting" @@ -32,7 +33,47 @@ export default function Intro() { : profile?.status === "in_case" || profile?.status === "matched" ? "/new-match" : "/terms"; - const submitHref = localizePath(submitPath, locale); +} + +export default function Intro() { + const router = useRouter(); + const { dictionary: t, locale } = useI18n(); + const { data: profile, refetch } = useMarriageProfileQuery({ + retry: false, + }); + const [isReportSheetOpen, setIsReportSheetOpen] = useState(false); + const [isSubmitting, setIsSubmitting] = useState(false); + + useEffect(() => { + if (!profile) { + return; + } + + router.replace(localizePath(getSubmitPath(profile), locale)); + }, [locale, profile, router]); + + const handleSubmit = async () => { + if (isSubmitting) { + return; + } + + setIsSubmitting(true); + + try { + if (!authBridge.isAuthenticated()) { + const token = await authBridge.ensureToken(); + if (!token) { + return; + } + } + + const profileResponse = profile ?? (await refetch()).data; + const nextPath = localizePath(getSubmitPath(profileResponse), locale); + router.push(nextPath); + } finally { + setIsSubmitting(false); + } + }; return (
@@ -123,7 +164,9 @@ export default function Intro() { />
- +
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c2cf5ec..278017b 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,15 +1,16 @@ import type { Metadata } from "next"; import { Amiri } from "next/font/google"; import localFont from "next/font/local"; -import Script from "next/script"; -import DevClickToComponent from "@/components/dev/dev-click-to-component"; import Providers from "./providers"; import "./globals.css"; +import DevClickToComponent from "@/components/dev/dev-click-to-component"; 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 +18,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: "Habib Marriage", + description: "Islamic Marriage Platform", + viewport: { + width: "device-width", + initialScale: 1, + maximumScale: 1, + }, + themeColor: "#ffffff", }; export default function RootLayout({ @@ -32,13 +41,104 @@ export default function RootLayout({ return ( -