From 91dde42f9222f56192a7efbee12553d1211ae5b8 Mon Sep 17 00:00:00 2001 From: chenhang Date: Fri, 3 Jul 2026 14:09:13 +0800 Subject: [PATCH] refactor(router): introduce app navigation manager --- src/app/chat/chat-screen.helpers.ts | 35 ---- src/app/chat/chat-screen.tsx | 20 +-- src/app/chat/components/chat-header.tsx | 8 +- .../chat-insufficient-credits-banner.tsx | 10 +- .../hooks/use-chat-unlock-navigation-flow.ts | 71 +++------ .../hooks/use-first-recharge-offer-banner.ts | 15 +- src/app/sidebar/sidebar-screen.tsx | 25 +-- src/data/storage/index.ts | 1 + .../__tests__/navigation_storage.test.ts | 71 +++++++++ src/data/storage/navigation/index.ts | 1 + .../storage/navigation/navigation_storage.ts | 150 ++++++++++++++++++ .../navigation/chat_image_return_session.ts | 49 +----- src/lib/navigation/chat_unlock_session.ts | 81 ++-------- src/providers/root-providers.tsx | 2 + .../__tests__/navigation-resolver.test.ts | 74 +++++++++ src/router/__tests__/route-meta.test.ts | 32 ++++ src/router/app-navigation-guard.tsx | 33 ++++ src/router/navigation-resolver.ts | 41 +++++ src/router/navigation-types.ts | 31 ++++ src/router/route-meta.ts | 23 +++ src/router/routes.ts | 20 --- src/router/use-app-navigator.ts | 140 ++++++++++++++++ src/stores/sync/chat-auth-sync.tsx | 13 -- 23 files changed, 669 insertions(+), 277 deletions(-) create mode 100644 src/data/storage/navigation/__tests__/navigation_storage.test.ts create mode 100644 src/data/storage/navigation/index.ts create mode 100644 src/data/storage/navigation/navigation_storage.ts create mode 100644 src/router/__tests__/navigation-resolver.test.ts create mode 100644 src/router/__tests__/route-meta.test.ts create mode 100644 src/router/app-navigation-guard.tsx create mode 100644 src/router/navigation-resolver.ts create mode 100644 src/router/navigation-types.ts create mode 100644 src/router/route-meta.ts create mode 100644 src/router/use-app-navigator.ts diff --git a/src/app/chat/chat-screen.helpers.ts b/src/app/chat/chat-screen.helpers.ts index 73d521ff..dbb4e122 100644 --- a/src/app/chat/chat-screen.helpers.ts +++ b/src/app/chat/chat-screen.helpers.ts @@ -1,8 +1,6 @@ "use client"; import type { LoginStatus } from "@/data/dto/auth"; -import type { PayChannel } from "@/data/dto/payment"; -import { ROUTE_BUILDERS } from "@/router/routes"; import { AppEnvUtil, BrowserDetector } from "@/utils"; export interface ExternalBrowserPromptState { @@ -31,41 +29,8 @@ export function shouldStartExternalBrowserPrompt({ ); } -export function getChatPaywallSubscriptionUrl( - payChannel: PayChannel = "stripe", -): string { - return ROUTE_BUILDERS.subscription("vip", { - payChannel, - returnTo: "chat", - }); -} - -export function getChatCreditsTopUpSubscriptionUrl( - payChannel: PayChannel = "stripe", -): string { - return ROUTE_BUILDERS.subscription("topup", { - payChannel, - returnTo: "chat", - }); -} - export function getInsufficientCreditsSubscriptionType( isVip: boolean, ): "vip" | "topup" { return isVip ? "topup" : "vip"; } - -export function getChatPaywallNavigationUrl( - loginStatus: LoginStatus, - type: "vip" | "topup" = "vip", - payChannel: PayChannel = "stripe", -): string { - const subscriptionUrl = - type === "topup" - ? getChatCreditsTopUpSubscriptionUrl(payChannel) - : getChatPaywallSubscriptionUrl(payChannel); - if (deriveIsGuest(loginStatus)) { - return ROUTE_BUILDERS.authWithRedirect(subscriptionUrl); - } - return subscriptionUrl; -} diff --git a/src/app/chat/chat-screen.tsx b/src/app/chat/chat-screen.tsx index 8cdce24d..8a840d4e 100644 --- a/src/app/chat/chat-screen.tsx +++ b/src/app/chat/chat-screen.tsx @@ -2,18 +2,17 @@ import { useEffect, useRef, useState } from "react"; import Image from "next/image"; -import { useRouter } from "next/navigation"; import { useAuthState } from "@/stores/auth/auth-context"; import { useChatDispatch, useChatState } from "@/stores/chat/chat-context"; import { useUserState } from "@/stores/user/user-context"; import { ROUTES } from "@/router/routes"; +import { useAppNavigator } from "@/router/use-app-navigator"; import { openChatInExternalBrowser, recordExternalBrowserPromptShown, resolveExternalBrowserPromptEligibility, } from "@/lib/chat/chat_external_browser"; -import { getDefaultPayChannelForCountryCode } from "@/lib/payment/default_pay_channel"; import { MobileShell } from "@/app/_components/core"; @@ -31,7 +30,6 @@ import { } from "./components"; import { deriveIsGuest, - getChatPaywallNavigationUrl, getInsufficientCreditsSubscriptionType, isChatDevelopmentEnvironment, shouldStartExternalBrowserPrompt, @@ -45,7 +43,7 @@ export function ChatScreen() { const chatDispatch = useChatDispatch(); const authState = useAuthState(); const userState = useUserState(); - const router = useRouter(); + const navigator = useAppNavigator(); const [showExternalBrowserDialog, setShowExternalBrowserDialog] = useState(false); const { @@ -122,16 +120,10 @@ export function ChatScreen() { } function handleMessageLimitUnlock(): void { - const defaultPayChannel = getDefaultPayChannelForCountryCode( - userState.currentUser?.countryCode, - ); - router.push( - getChatPaywallNavigationUrl( - authState.loginStatus, - getInsufficientCreditsSubscriptionType(userState.isVip), - defaultPayChannel, - ), - ); + navigator.openSubscription({ + type: getInsufficientCreditsSubscriptionType(userState.isVip), + returnTo: "chat", + }); } function handleUnlockVoiceMessage(messageId: string): void { diff --git a/src/app/chat/components/chat-header.tsx b/src/app/chat/components/chat-header.tsx index 8a63cb28..0506a315 100644 --- a/src/app/chat/components/chat-header.tsx +++ b/src/app/chat/components/chat-header.tsx @@ -7,9 +7,9 @@ */ import type { ReactNode } from "react"; import { Lock, Menu } from "lucide-react"; -import { useRouter } from "next/navigation"; import { ROUTES } from "@/router/routes"; +import { useAppNavigator } from "@/router/use-app-navigator"; import styles from "./chat-header.module.css"; @@ -19,7 +19,7 @@ export interface ChatHeaderProps { } export function ChatHeader({ isGuest, offerBanner }: ChatHeaderProps) { - const router = useRouter(); + const navigator = useAppNavigator(); if (isGuest) { return ( @@ -27,7 +27,7 @@ export function ChatHeader({ isGuest, offerBanner }: ChatHeaderProps) {