diff --git a/src/app/finding-match/finding-match-client.tsx b/src/app/finding-match/finding-match-client.tsx index 6a619a6..fb12ca1 100644 --- a/src/app/finding-match/finding-match-client.tsx +++ b/src/app/finding-match/finding-match-client.tsx @@ -11,7 +11,6 @@ import MarriageAdvisorsOverlay, { useMarriageAdvisorsOverlay, } from "@/components/Componentes/marriage-advisors-overlay"; import Button from "@/components/Componentes/button"; -import { PageLoadingSkeleton } from "@/components/Componentes/page-loading-skeleton"; import { FixToTheEnd } from "@/components/Componentes/fix-to-the-end"; import { PageBackground } from "@/components/Componentes/page-background"; import PageHeader from "@/components/Componentes/page-header"; @@ -57,18 +56,14 @@ export default function FindingMatchClient() { } }, [profile, locale, router]); - // Signal Flutter to lift its loading cover once the profile is available. - useHabibWebReady(!!profile && !isLoading); + // Signal Flutter to lift its loading cover immediately so page renders with 0ms latency. + useHabibWebReady(true); const isRedirecting = useMemo(() => { if (!profile) return false; return getSubmitPath(profile) !== "/finding-match"; }, [profile]); - if ((isLoading && !profile) || !profile) { - return ; - } - const copy = { title: t["SEARCH IN PROGRESS"], description: diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7d5f4f4..d852bea 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -216,6 +216,10 @@ export default async function RootLayout({ if (!config) return; configApplied = true; window.__HABIB_BOOTSTRAP__ = config; + if (config.marriage) { + window.__HABIB_MARRIAGE_INITIAL_DATA__ = config.marriage; + window.dispatchEvent(new CustomEvent('habib:marriage-initial-data', { detail: config.marriage })); + } var safe = config.safeArea || {}; root.style.setProperty('--safe-top', (Number(safe.top) || 0) + 'px'); root.style.setProperty('--safe-bottom', (Number(safe.bottom) || 0) + 'px'); diff --git a/src/app/new-match/new-match-client.tsx b/src/app/new-match/new-match-client.tsx index 44669f5..489a896 100644 --- a/src/app/new-match/new-match-client.tsx +++ b/src/app/new-match/new-match-client.tsx @@ -488,6 +488,7 @@ export default function NewMatchClient() { const { isAdvisorOpen, openAdvisors, closeAdvisors } = useMarriageAdvisorsOverlay(); const { isProfileOpen, openProfile, closeProfile } = useMatchProfileOverlay(); + const [isOpeningProfile, setIsOpeningProfile] = useState(false); const { data: profile, isError, isLoading } = useMarriageProfileQuery(); const [isPaymentSheetOpen, setIsPaymentSheetOpen] = useState(false); const [isDeclineConfirmOpen, setIsDeclineConfirmOpen] = useState(false); @@ -594,8 +595,23 @@ export default function NewMatchClient() { } }, [profile, locale, router, isLoading, isError]); - // Signal Flutter to lift its loading cover once the profile is available. - useHabibWebReady(!!profile && !isLoading); + // Signal Flutter to lift its loading cover immediately with 0ms latency. + useHabibWebReady(true); + + const handleOpenProfile = () => { + if (profile) { + openProfile(); + } else { + setIsOpeningProfile(true); + } + }; + + useEffect(() => { + if (isOpeningProfile && profile) { + setIsOpeningProfile(false); + openProfile(); + } + }, [isOpeningProfile, profile, openProfile]); const isRedirecting = useMemo(() => { if (!profile) return false; @@ -625,65 +641,6 @@ export default function NewMatchClient() { } } - if ((isLoading && !profile) || !profile) { - return ( - <> - - - -
- {/* 1. Header Section Skeleton */} -
- - - -
- - {/* 2. Match Card Skeleton */} -
- {/* Name line */} - - - {/* Subtitle / Details lines */} -
- - -
- - {/* Button skeleton */} - -
- - {/* 3. Advisor Card Skeleton */} -
-
- -
- - -
- -
-
- - - -
- - -
-
-
-
- - ); - } - const isFemaleProfile = profile?.gender === "female"; const matchHeadingTitle = isFemaleProfile ? t["New Marriage Proposal"] @@ -868,11 +825,12 @@ export default function NewMatchClient() { {/* Button */} diff --git a/src/app/request-accepted/request-accepted-client.tsx b/src/app/request-accepted/request-accepted-client.tsx index 8dd67dd..2354021 100644 --- a/src/app/request-accepted/request-accepted-client.tsx +++ b/src/app/request-accepted/request-accepted-client.tsx @@ -20,7 +20,6 @@ import { LoadingThreeDot } from "@/components/Componentes/loading-three-dot"; import OutcomeSelectionSheet from "@/components/Componentes/outcome-selection-sheet"; import { PageBackground } from "@/components/Componentes/page-background"; import PageHeader from "@/components/Componentes/page-header"; -import { PageLoadingSkeleton } from "@/components/Componentes/page-loading-skeleton"; import SubscriptionRequiredSheet from "@/components/Componentes/subscription-required-sheet"; import SwipeButton from "@/components/Componentes/swipe-button"; import type { @@ -243,6 +242,7 @@ export default function RequestAcceptedClient() { const [hasConfirmedFemaleContact, setHasConfirmedFemaleContact] = useState(false); const [showPaymentSuccessToast, setShowPaymentSuccessToast] = useState(false); + const [isOpeningProfile, setIsOpeningProfile] = useState(false); const { data: profile, isLoading } = useMarriageProfileQuery(); const isFemaleProfile = profile?.gender === "female"; @@ -258,8 +258,23 @@ export default function RequestAcceptedClient() { } }, [profile, router, locale, noContactReportedSuccess]); - // Signal Flutter to lift its loading cover once the profile is available. - useHabibWebReady(!!profile && !isLoading); + // Signal Flutter to lift its loading cover immediately with 0ms latency. + useHabibWebReady(true); + + const handleOpenProfile = () => { + if (profile) { + openProfile(); + } else { + setIsOpeningProfile(true); + } + }; + + useEffect(() => { + if (isOpeningProfile && profile) { + setIsOpeningProfile(false); + openProfile(); + } + }, [isOpeningProfile, profile, openProfile]); const isRedirecting = useMemo(() => { if (!profile) return false; @@ -297,10 +312,6 @@ export default function RequestAcceptedClient() { enabled: false, }); - if ((isLoading && !profile) || !profile) { - return ; - } - const titleText = isFemaleProfile ? t["Request Approved"] : caseStatus === "payment_done" || caseStatus === "contacted" @@ -687,10 +698,15 @@ export default function RequestAcceptedClient() { <> )} diff --git a/src/app/request-sent/request-sent-client.tsx b/src/app/request-sent/request-sent-client.tsx index f6be4c2..6f1d08f 100644 --- a/src/app/request-sent/request-sent-client.tsx +++ b/src/app/request-sent/request-sent-client.tsx @@ -3,9 +3,9 @@ import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import { useEffect, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useHabibWebReady } from "@/hooks/use-habib-web-ready"; -import { PageLoadingSkeleton } from "@/components/Componentes/page-loading-skeleton"; +import { LoadingThreeDot } from "@/components/Componentes/loading-three-dot"; import AdvisorActionsCard from "@/components/Componentes/advisor-actions-card"; import MarriageAdvisorsOverlay, { useMarriageAdvisorsOverlay, @@ -33,6 +33,7 @@ export default function RequestSentClient() { useMarriageAdvisorsOverlay(); const { isProfileOpen, openProfile, closeProfile } = useMatchProfileOverlay(); + const [isOpeningProfile, setIsOpeningProfile] = useState(false); const { data: profile, isLoading } = useMarriageProfileQuery({ refetchInterval: 3000, }); @@ -47,17 +48,28 @@ export default function RequestSentClient() { } }, [profile, locale, router]); - // Signal Flutter to lift its loading cover once the profile is available. - useHabibWebReady(!!profile && !isLoading); + // Signal Flutter to lift its loading cover immediately with 0ms latency. + useHabibWebReady(true); const isRedirecting = useMemo(() => { if (!profile) return false; return getSubmitPath(profile) !== "/request-sent"; }, [profile]); - if ((isLoading && !profile) || !profile) { - return ; - } + const handleOpenProfile = () => { + if (profile) { + openProfile(); + } else { + setIsOpeningProfile(true); + } + }; + + useEffect(() => { + if (isOpeningProfile && profile) { + setIsOpeningProfile(false); + openProfile(); + } + }, [isOpeningProfile, profile, openProfile]); const copy = { advisorTitle: t["Get an advisor"], @@ -123,11 +135,16 @@ export default function RequestSentClient() { {/* Button */} diff --git a/src/data/countries.ts b/src/data/countries.ts index b57bb62..dd02a39 100644 --- a/src/data/countries.ts +++ b/src/data/countries.ts @@ -5110,6 +5110,7 @@ export function resolveCountryName( if (!trimmed) return ""; const isFa = String(locale || "en").toLowerCase().startsWith("fa"); + const iso = getCountryIsoCode(trimmed); // 1. If Persian locale, prioritize curated COUNTRIES_FA if (isFa) { @@ -5136,7 +5137,7 @@ export function resolveCountryName( // 2. Dynamic Intl.DisplayNames for the requested locale if (iso) { try { - const displayNames = new Intl.DisplayNames([isFa ? "fa" : "en"], { + const displayNames = new Intl.DisplayNames([locale], { type: "region", }); const name = displayNames.of(iso); diff --git a/src/hooks/marriage/use-profile-main.ts b/src/hooks/marriage/use-profile-main.ts index 7196011..4cd6f59 100644 --- a/src/hooks/marriage/use-profile-main.ts +++ b/src/hooks/marriage/use-profile-main.ts @@ -1,6 +1,8 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useEffect } from "react"; +import { authBridge } from "@/lib/auth-bridge"; import { setCachedMarriageEntryPath } from "@/lib/entry-route-cache"; import { getSubmitPath, @@ -11,6 +13,27 @@ import type { QueryOptions } from "./options"; import { marriageQueryKeys } from "./query-keys"; import type { MarriageProfileResponse } from "./types"; +export function getInitialMarriageProfile(): MarriageProfileResponse | undefined { + const data = authBridge.getMarriageData(); + if (!data) return undefined; + + return { + id: data.id ?? 0, + status: data.status, + gender: data.gender, + age: data.age ?? null, + is_registering_for_self: data.is_registering_for_self ?? null, + can_edit_profile: data.can_edit_profile ?? true, + can_message_expert: data.can_message_expert ?? true, + active_subscription: data.active_subscription ?? null, + is_ready_for_match: data.is_ready_for_match ?? true, + active_case: data.active_case ?? null, + needs_subscription: data.needs_subscription ?? false, + recommended_plan: data.recommended_plan ?? null, + match_summary: data.match_summary ?? null, + } as MarriageProfileResponse; +} + export async function getMarriageProfile() { console.log("🌐 [Profile API] Sending GET /api/marriage/profile/main/ ..."); try { @@ -44,9 +67,34 @@ export async function getMarriageProfile() { export function useMarriageProfileQuery( options?: QueryOptions, ) { + const queryClient = useQueryClient(); + + useEffect(() => { + const handleInitialData = (e: Event) => { + const customEvent = e as CustomEvent; + const initial = getInitialMarriageProfile(); + if (initial) { + queryClient.setQueryData(marriageQueryKeys.profile(), (old: any) => { + if (!old) return initial; + return { + ...old, + ...initial, + match_summary: initial.match_summary ?? old.match_summary, + }; + }); + } + }; + + window.addEventListener("habib:marriage-initial-data", handleInitialData); + return () => { + window.removeEventListener("habib:marriage-initial-data", handleInitialData); + }; + }, [queryClient]); + const query = useQuery({ staleTime: 5 * 60 * 1000, refetchOnWindowFocus: false, + initialData: getInitialMarriageProfile as any, ...options, queryFn: getMarriageProfile, queryKey: marriageQueryKeys.profile(), diff --git a/src/lib/auth-bridge.ts b/src/lib/auth-bridge.ts index 4bfc8a7..b698e60 100644 --- a/src/lib/auth-bridge.ts +++ b/src/lib/auth-bridge.ts @@ -23,6 +23,7 @@ function postFlutterMessage(payload: Record) { class AuthBridge { private token: string | null = null; private coins = 0; + private marriageData: Record | null = null; private isReady = false; private loginRequested = false; private readyCallbacks: Array<() => void> = []; @@ -33,6 +34,10 @@ class AuthBridge { this.init(); } + public getMarriageData(): Record | null { + return this.marriageData; + } + private syncFromStorage() { if (typeof window === "undefined") { return false; @@ -41,8 +46,15 @@ class AuthBridge { const win = window as Window & { HABIB_TOKEN?: string; HABIB_COINS?: number; + HABIB_MARRIAGE?: Record; + __HABIB_MARRIAGE_INITIAL_DATA__?: Record; }; + if (win.HABIB_MARRIAGE || win.__HABIB_MARRIAGE_INITIAL_DATA__) { + this.marriageData = + win.HABIB_MARRIAGE ?? win.__HABIB_MARRIAGE_INITIAL_DATA__ ?? null; + } + const token = win.HABIB_TOKEN ?? getClientCookie(TOKEN_COOKIE_NAME) ?? @@ -88,6 +100,17 @@ class AuthBridge { (event) => { if (event.action === "login") { this.handleLoginResponse(event.success); + } else if ( + event.action === "initial_config" && + event.data && + (event.data as any).marriage + ) { + this.marriageData = (event.data as any).marriage; + window.dispatchEvent( + new CustomEvent("habib:marriage-initial-data", { + detail: this.marriageData, + }), + ); } }, );