diff --git a/src/app/globals.css b/src/app/globals.css index d28f2c4..eb4e4fd 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -3,8 +3,10 @@ /* ─── App fonts matching Flutter Najm & Hosseinieh ─── */ @font-face { font-family: 'Faminela'; - src: url('/assets/fonts/faminela.ttf') format('truetype'); - font-weight: 400; + src: url('/fonts/Faminela/Faminela.otf') format('opentype'), + url('/assets/fonts/Faminela.otf') format('opentype'), + url('/assets/fonts/faminela.ttf') format('truetype'); + font-weight: 400 900; font-style: normal; font-display: swap; } @@ -314,7 +316,17 @@ body { } .font-faminela { - font-family: var(--marriage-font-brand); + font-family: 'Faminela', var(--marriage-font-sans); + font-weight: 400; +} + +html[lang='fa'] .font-faminela, +html[lang='ar'] .font-faminela, +body[data-lang='fa'] .font-faminela, +body[data-lang='ar'] .font-faminela, +.lang-fa .font-faminela, +.lang-ar .font-faminela { + font-weight: 700; } .app-shell { diff --git a/src/components/Componentes/information-sheet.tsx b/src/components/Componentes/information-sheet.tsx index 3327b8b..69c2728 100644 --- a/src/components/Componentes/information-sheet.tsx +++ b/src/components/Componentes/information-sheet.tsx @@ -506,6 +506,8 @@ export function InformationSheet({ }; }, [isRendered]); + const isBackdropPointerDownRef = useRef(false); + if (!isRendered || !mounted) { return null; } @@ -523,16 +525,25 @@ export function InformationSheet({ aria-label={t["Information sheet"]} tabIndex={-1} dir={isRtl ? "rtl" : "ltr"} + onPointerDown={(event) => { + isBackdropPointerDownRef.current = event.target === event.currentTarget; + }} onClick={(event) => { - if (closeOnOutside && event.target === event.currentTarget) { + if ( + closeOnOutside && + isBackdropPointerDownRef.current && + event.target === event.currentTarget + ) { + console.log("[InformationSheet] Backdrop clicked -> closeSheet"); closeSheet(); } + isBackdropPointerDownRef.current = false; }} onKeyDown={(event) => { if ( closeOnOutside && event.target === event.currentTarget && - (event.key === "Escape" || event.key === "Enter" || event.key === " ") + event.key === "Escape" ) { event.preventDefault(); closeSheet(); diff --git a/src/components/Componentes/page-header.tsx b/src/components/Componentes/page-header.tsx index 9a3d2a4..198c986 100644 --- a/src/components/Componentes/page-header.tsx +++ b/src/components/Componentes/page-header.tsx @@ -83,7 +83,7 @@ export function PageHeader({ )} -

+

{t["Marriage"] || "Marriage"}

diff --git a/src/hooks/marriage/use-profile-main.ts b/src/hooks/marriage/use-profile-main.ts index 84e8fa5..340b051 100644 --- a/src/hooks/marriage/use-profile-main.ts +++ b/src/hooks/marriage/use-profile-main.ts @@ -39,30 +39,50 @@ export function getInitialMarriageProfile( } as MarriageProfileResponse; } - // Priority 2: Use locally persisted profile from previous successful server response + const flutterData = authBridge.getMarriageData(); const persisted = getScopedMarriageProfile(); + + // If Flutter provided match_summary (e.g. fresh match on new/current device) and persisted doesn't have it, prefer Flutter data + if (flutterData?.match_summary && !persisted?.match_summary) { + return { + id: flutterData.id ?? persisted?.id ?? 0, + status: flutterData.status ?? persisted?.status, + gender: flutterData.gender ?? persisted?.gender, + age: flutterData.age ?? persisted?.age ?? null, + is_registering_for_self: flutterData.is_registering_for_self ?? persisted?.is_registering_for_self ?? null, + can_edit_profile: flutterData.can_edit_profile ?? persisted?.can_edit_profile ?? true, + can_message_expert: flutterData.can_message_expert ?? persisted?.can_message_expert ?? true, + active_subscription: flutterData.active_subscription ?? persisted?.active_subscription ?? null, + is_ready_for_match: flutterData.is_ready_for_match ?? persisted?.is_ready_for_match ?? true, + active_case: flutterData.active_case ?? persisted?.active_case ?? null, + needs_subscription: flutterData.needs_subscription ?? persisted?.needs_subscription ?? false, + recommended_plan: flutterData.recommended_plan ?? persisted?.recommended_plan ?? null, + match_summary: flutterData.match_summary ?? null, + } as MarriageProfileResponse; + } + + // Priority 2: Use locally persisted profile from previous successful server response if (persisted) { return persisted; } // Priority 3: Fallback to Flutter native bridge data - const data = authBridge.getMarriageData(); - if (!data) return undefined; + if (!flutterData) 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, + id: flutterData.id ?? 0, + status: flutterData.status, + gender: flutterData.gender, + age: flutterData.age ?? null, + is_registering_for_self: flutterData.is_registering_for_self ?? null, + can_edit_profile: flutterData.can_edit_profile ?? true, + can_message_expert: flutterData.can_message_expert ?? true, + active_subscription: flutterData.active_subscription ?? null, + is_ready_for_match: flutterData.is_ready_for_match ?? true, + active_case: flutterData.active_case ?? null, + needs_subscription: flutterData.needs_subscription ?? false, + recommended_plan: flutterData.recommended_plan ?? null, + match_summary: flutterData.match_summary ?? null, } as MarriageProfileResponse; } @@ -102,6 +122,33 @@ export async function getMarriageProfile() { export function useMarriageProfileQuery( options?: QueryOptions, ) { + const queryClient = useQueryClient(); + + useEffect(() => { + const handleInitialData = (e: any) => { + const data = e.detail; + if (data) { + queryClient.setQueryData( + marriageQueryKeys.profile(), + (old: MarriageProfileResponse | undefined) => { + if (!old) return getInitialMarriageProfile(data); + return { + ...old, + ...data, + match_summary: data.match_summary ?? old.match_summary, + active_case: data.active_case ?? old.active_case, + }; + }, + ); + } + }; + + window.addEventListener("habib:marriage-initial-data", handleInitialData); + return () => { + window.removeEventListener("habib:marriage-initial-data", handleInitialData); + }; + }, [queryClient]); + const query = useQuery({ staleTime: 30 * 1000, refetchOnWindowFocus: false, diff --git a/src/hooks/use-hardware-back-handler.ts b/src/hooks/use-hardware-back-handler.ts index 059f66d..cfdeff6 100644 --- a/src/hooks/use-hardware-back-handler.ts +++ b/src/hooks/use-hardware-back-handler.ts @@ -52,6 +52,19 @@ export function useHardwareBackHandler( * Returns true if a handler consumed the event, or false if Flutter should handle/close. */ export function handleHardwareBackSync(): boolean { + if (typeof document !== "undefined" && document.activeElement instanceof HTMLElement) { + const tag = document.activeElement.tagName; + if ( + tag === "INPUT" || + tag === "TEXTAREA" || + document.activeElement.isContentEditable + ) { + console.log("[HardwareBackSync] Active input focused — blurring input instead of navigating back"); + document.activeElement.blur(); + return true; + } + } + if (backHandlerStack.length === 0) { return false; } @@ -76,6 +89,19 @@ export function handleHardwareBackSync(): boolean { * { handled: false } if Flutter should close the WebView screen. */ export async function handleHardwareBack(): Promise<{ handled: boolean }> { + if (typeof document !== "undefined" && document.activeElement instanceof HTMLElement) { + const tag = document.activeElement.tagName; + if ( + tag === "INPUT" || + tag === "TEXTAREA" || + document.activeElement.isContentEditable + ) { + console.log("[HardwareBack] Active input focused — blurring input instead of navigating back"); + document.activeElement.blur(); + return { handled: true }; + } + } + if (backHandlerStack.length === 0) { return { handled: false }; }