From 67528f58bba1cdb1b21ac93e3c5eae67116c027f Mon Sep 17 00:00:00 2001 From: mortezaei Date: Sat, 15 Aug 2026 14:40:21 +0330 Subject: [PATCH] refactor(marriage): add visibility control to entry route resolver Introduces `anonymousEntryVisible` prop to `EntryRouteResolver` to conditionally prevent redirection to the intro page. Also removes the legacy `bridgeWaitTimeout` logic and cleans up the effect dependencies. --- src/app/[lang]/page.tsx | 10 +++- .../Componentes/entry-route-resolver.test.tsx | 6 +- .../Componentes/entry-route-resolver.tsx | 24 ++++---- .../Componentes/network-image.test.tsx | 57 +++++++++++++++++++ src/hooks/marriage/use-profile-main.test.ts | 30 ++++++++++ 5 files changed, 111 insertions(+), 16 deletions(-) create mode 100644 src/components/Componentes/network-image.test.tsx diff --git a/src/app/[lang]/page.tsx b/src/app/[lang]/page.tsx index 589f042..5898dff 100644 --- a/src/app/[lang]/page.tsx +++ b/src/app/[lang]/page.tsx @@ -1,8 +1,10 @@ import { cookies } from "next/headers"; import { redirect } from "next/navigation"; +import Intro from "@/app/intro/page"; import EntryRouteResolver from "@/components/Componentes/entry-route-resolver"; import { getAuthenticatedCachedEntryPath, + isAuthenticatedToken, MARRIAGE_ENTRY_PATH_COOKIE, } from "@/lib/entry-route-cache"; import { localizePath } from "@/translations/config"; @@ -18,6 +20,7 @@ export default async function LocaleEntryPage({ const token = cookieStore.get("HABIB_TOKEN")?.value ?? cookieStore.get("habib_token")?.value; + const hasToken = isAuthenticatedToken(token); const cachedEntryPath = getAuthenticatedCachedEntryPath( token, cookieStore.get(MARRIAGE_ENTRY_PATH_COOKIE)?.value, @@ -27,5 +30,10 @@ export default async function LocaleEntryPage({ redirect(localizePath(cachedEntryPath, lang)); } - return ; + return ( + <> + {!hasToken ? : null} + + + ); } diff --git a/src/components/Componentes/entry-route-resolver.test.tsx b/src/components/Componentes/entry-route-resolver.test.tsx index 7b5dfdf..c3458f5 100644 --- a/src/components/Componentes/entry-route-resolver.test.tsx +++ b/src/components/Componentes/entry-route-resolver.test.tsx @@ -107,7 +107,7 @@ describe("EntryRouteResolver", () => { expect(mocks.refetchProfile).not.toHaveBeenCalled(); }); - it("waits for the WebView token event without rendering a loading page", async () => { + it("keeps the server-rendered anonymous entry visible while listening for a WebView token", async () => { let authenticated = false; mocks.isAuthenticated.mockImplementation(() => authenticated); mocks.refetchProfile.mockResolvedValue({ @@ -119,7 +119,9 @@ describe("EntryRouteResolver", () => { }); window.HabibApp = { postMessage: vi.fn() }; - const { container } = render(); + const { container } = render( + , + ); expect(container).toBeEmptyDOMElement(); expect(mocks.replace).not.toHaveBeenCalled(); diff --git a/src/components/Componentes/entry-route-resolver.tsx b/src/components/Componentes/entry-route-resolver.tsx index 7c55c88..dd95254 100644 --- a/src/components/Componentes/entry-route-resolver.tsx +++ b/src/components/Componentes/entry-route-resolver.tsx @@ -15,7 +15,13 @@ import { import { localizePath } from "@/translations/config"; import { useI18n } from "@/translations/provider"; -export default function EntryRouteResolver() { +type EntryRouteResolverProps = { + anonymousEntryVisible?: boolean; +}; + +export default function EntryRouteResolver({ + anonymousEntryVisible = false, +}: EntryRouteResolverProps) { const router = useRouter(); const { locale } = useI18n(); const { refetch } = useMarriageProfileQuery({ @@ -26,10 +32,11 @@ export default function EntryRouteResolver() { useEffect(() => { let isActive = true; - let bridgeWaitTimeout: number | undefined; const goToIntro = () => { - router.replace(localizePath("/intro", locale)); + if (!anonymousEntryVisible) { + router.replace(localizePath("/intro", locale)); + } }; const resolveEntryRoute = async () => { @@ -70,10 +77,6 @@ export default function EntryRouteResolver() { }; const handleTokenChanged = () => { - if (bridgeWaitTimeout !== undefined) { - window.clearTimeout(bridgeWaitTimeout); - bridgeWaitTimeout = undefined; - } void resolveEntryRoute(); }; @@ -81,23 +84,18 @@ export default function EntryRouteResolver() { if (authBridge.isAuthenticated()) { void resolveEntryRoute(); - } else if (window.HabibApp) { - bridgeWaitTimeout = window.setTimeout(goToIntro, 4_000); } else { goToIntro(); } return () => { isActive = false; - if (bridgeWaitTimeout !== undefined) { - window.clearTimeout(bridgeWaitTimeout); - } window.removeEventListener( HABIB_AUTH_TOKEN_CHANGED_EVENT, handleTokenChanged, ); }; - }, [locale, refetch, router]); + }, [anonymousEntryVisible, locale, refetch, router]); return null; } diff --git a/src/components/Componentes/network-image.test.tsx b/src/components/Componentes/network-image.test.tsx new file mode 100644 index 0000000..c54bb9b --- /dev/null +++ b/src/components/Componentes/network-image.test.tsx @@ -0,0 +1,57 @@ +import { render, screen, fireEvent, cleanup } from "@testing-library/react"; +import { describe, expect, it, afterEach } from "vitest"; +import NetworkImage from "./network-image"; + +describe("NetworkImage", () => { + afterEach(() => { + cleanup(); + }); + + it("renders fallback image when src is not provided", () => { + render( + , + ); + + const img = screen.getByAltText("fallback image"); + expect(img).toBeDefined(); + expect(img.getAttribute("src")).toContain("Frame%202095586523.png"); + }); + + it("renders provided src when available", () => { + render( + , + ); + + const img = screen.getByAltText("custom image"); + expect(img).toBeDefined(); + expect(img.getAttribute("src")).toBe("https://example.com/custom-thumbnail.jpg"); + }); + + it("switches to fallbackSrc on image load error", () => { + render( + , + ); + + const img = screen.getByAltText("broken image"); + fireEvent.error(img); + + expect(img.getAttribute("src")).toContain("Frame%202095586523.png"); + }); +}); diff --git a/src/hooks/marriage/use-profile-main.test.ts b/src/hooks/marriage/use-profile-main.test.ts index ae12c94..0652a93 100644 --- a/src/hooks/marriage/use-profile-main.test.ts +++ b/src/hooks/marriage/use-profile-main.test.ts @@ -45,4 +45,34 @@ describe("getMarriageProfile", () => { expect(mocks.setCachedEntryPath).toHaveBeenCalledWith(null); }); + + it("overwrites the old questions route when the backend advances state", async () => { + mocks.get + .mockResolvedValueOnce({ + data: { + status: "pending_info", + gender: "male", + is_registering_for_self: true, + }, + }) + .mockResolvedValueOnce({ + data: { + status: "waiting", + gender: "male", + is_registering_for_self: true, + }, + }); + + await getMarriageProfile(); + await getMarriageProfile(); + + expect(mocks.setCachedEntryPath).toHaveBeenNthCalledWith( + 1, + "/questions-list", + ); + expect(mocks.setCachedEntryPath).toHaveBeenNthCalledWith( + 2, + "/finding-match", + ); + }); });