diff --git a/src/app/auth/auth-screen.tsx b/src/app/auth/auth-screen.tsx index 6d611a65..62031029 100644 --- a/src/app/auth/auth-screen.tsx +++ b/src/app/auth/auth-screen.tsx @@ -5,11 +5,11 @@ * */ import { useEffect, useSyncExternalStore } from "react"; -import { useRouter } from "next/navigation"; import { MobileShell } from "@/app/_components/core"; import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context"; import { ROUTES } from "@/router/routes"; +import { useAppNavigator } from "@/router/use-app-navigator"; import { AuthBackground, AuthPanel } from "./components"; import styles from "./components/auth-screen.module.css"; @@ -20,7 +20,7 @@ const log = new Logger("AppAuthAuthScreen"); export function AuthScreen() { const state = useAuthState(); const authDispatch = useAuthDispatch(); - const router = useRouter(); + const navigator = useAppNavigator(); const redirectTo = useSyncExternalStore( subscribeLocationSnapshot, readRedirectFromLocation, @@ -40,17 +40,17 @@ export function AuthScreen() { }); if (state.pendingRedirect && state.loginStatus !== "notLoggedIn") { - log.debug("[auth-screen] useEffect → router.replace [via pendingRedirect flag]", { + log.debug("[auth-screen] useEffect → navigator.replace [via pendingRedirect flag]", { redirectTo: safeRedirectTo, }); authDispatch({ type: "AuthClearPendingRedirect" }); - router.replace(safeRedirectTo); + navigator.replace(safeRedirectTo); } }, [ state.loginStatus, state.pendingRedirect, authDispatch, - router, + navigator, safeRedirectTo, ]); diff --git a/src/app/auth/components/auth-panel.tsx b/src/app/auth/components/auth-panel.tsx index 86cc391e..c8e3f1e0 100644 --- a/src/app/auth/components/auth-panel.tsx +++ b/src/app/auth/components/auth-panel.tsx @@ -2,9 +2,8 @@ /** * 认证面板:顶层 switch(Facebook / Email)+ 悬浮返回按钮 */ -import { useRouter } from "next/navigation"; - import { BackButton } from "@/app/_components"; +import { useAppNavigator } from "@/router/use-app-navigator"; import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context"; import { AuthEmailPanel } from "./auth-email-panel"; @@ -14,7 +13,7 @@ import styles from "./auth-panel.module.css"; export function AuthPanel() { const state = useAuthState(); const dispatch = useAuthDispatch(); - const router = useRouter(); + const navigator = useAppNavigator(); const switchToFacebook = () => dispatch({ type: "AuthPanelModeChanged", mode: "facebook" }); @@ -25,7 +24,7 @@ export function AuthPanel() { if (state.authPanelMode === "email") { switchToFacebook(); } else { - router.back(); + navigator.back(); } }; diff --git a/src/app/chat/components/image-bubble.tsx b/src/app/chat/components/image-bubble.tsx index 335fb457..1ae37fbd 100644 --- a/src/app/chat/components/image-bubble.tsx +++ b/src/app/chat/components/image-bubble.tsx @@ -9,10 +9,9 @@ * - 错误占位符(broken image icon) */ import Image from "next/image"; -import { useRouter } from "next/navigation"; import { useState } from "react"; -import { ROUTE_BUILDERS } from "@/router/routes"; +import { useAppNavigator } from "@/router/use-app-navigator"; import { isBrowserLocalChatImageSource, normalizeChatImageSource, @@ -33,7 +32,7 @@ export function ImageBubble({ imageUrl, imagePaywalled = false, }: ImageBubbleProps) { - const router = useRouter(); + const navigator = useAppNavigator(); const [errorSrc, setErrorSrc] = useState(null); const { mediaUrl, isUsingCachedMedia, reportMediaError } = useCachedChatMediaUrl({ @@ -58,7 +57,7 @@ export function ImageBubble({ const openImage = () => { if (!messageId) return; requestChatScrollSnapshotSave(messageId); - router.push(ROUTE_BUILDERS.chatImage(messageId), { scroll: false }); + navigator.openChatImage(messageId); }; return ( diff --git a/src/app/chat/deviceid/[deviceId]/DeepLinkPersist.tsx b/src/app/chat/deviceid/[deviceId]/DeepLinkPersist.tsx index 62645b11..92733bdc 100644 --- a/src/app/chat/deviceid/[deviceId]/DeepLinkPersist.tsx +++ b/src/app/chat/deviceid/[deviceId]/DeepLinkPersist.tsx @@ -6,14 +6,13 @@ * 设计: * - 不走 Server `redirect()`:避免把 fbid 暴露在 URL 里。 * - deviceId / fbid / avatarUrl 统一交给 `lib/chat` 落地。 - * - 写入完成后 `router.replace('/chat')`,URL 不留深链痕迹。 + * - 写入完成后通过统一导航回到 `/chat`,URL 不留深链痕迹。 */ import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; import { persistExternalBrowserChatDeepLink } from "@/lib/chat/chat_external_browser"; -import { ROUTES } from "@/router/routes"; +import { useAppNavigator } from "@/router/use-app-navigator"; import { Logger } from "@/utils"; const log = new Logger("AppChatDeviceidDeviceIdDeepLinkPersist"); @@ -29,7 +28,7 @@ export default function DeepLinkPersist({ fbid, avatarUrl, }: DeepLinkPersistProps) { - const router = useRouter(); + const navigator = useAppNavigator(); const [done, setDone] = useState(false); useEffect(() => { @@ -45,10 +44,10 @@ export default function DeepLinkPersist({ log.warn("[DeepLinkPersist] failed to persist", err); } finally { setDone(true); - router.replace(ROUTES.chat); + navigator.openChat({ replace: true }); } })(); - }, [avatarUrl, deviceId, fbid, router]); + }, [avatarUrl, deviceId, fbid, navigator]); return (
`,由它把数据写入 localStorage 后 `router.replace('/chat')`。 + * ``,由它把数据写入 localStorage 后通过统一导航回到 `/chat`。 * * Next.js 15+ 约定:`params` / `searchParams` 是 Promise,必须 await。 * Next.js 16 全局 helper `PageProps<'/literal'>` 由 `next dev` / `next build` / diff --git a/src/app/chat/image/[messageId]/chat-image-viewer-screen.tsx b/src/app/chat/image/[messageId]/chat-image-viewer-screen.tsx index 07b66ac5..b0d07f29 100644 --- a/src/app/chat/image/[messageId]/chat-image-viewer-screen.tsx +++ b/src/app/chat/image/[messageId]/chat-image-viewer-screen.tsx @@ -1,9 +1,8 @@ "use client"; -import { useRouter } from "next/navigation"; - import { MobileShell } from "@/app/_components/core"; -import { ROUTE_BUILDERS, ROUTES } from "@/router/routes"; +import { ROUTE_BUILDERS } from "@/router/routes"; +import { useAppNavigator } from "@/router/use-app-navigator"; import { useChatDispatch, useChatState } from "@/stores/chat/chat-context"; import { @@ -21,7 +20,7 @@ export interface ChatImageViewerScreenProps { export function ChatImageViewerScreen({ messageId, }: ChatImageViewerScreenProps) { - const router = useRouter(); + const navigator = useAppNavigator(); const chatState = useChatState(); const chatDispatch = useChatDispatch(); const returnUrl = ROUTE_BUILDERS.chatImage(messageId); @@ -40,7 +39,7 @@ export function ChatImageViewerScreen({ ); const handleClose = () => { - router.push(ROUTES.chat, { scroll: false }); + navigator.openChat({ scroll: false }); }; const handleUnlockImagePaywall = () => { diff --git a/src/app/splash/splash-screen.tsx b/src/app/splash/splash-screen.tsx index 37a31ac4..25ac4bd7 100644 --- a/src/app/splash/splash-screen.tsx +++ b/src/app/splash/splash-screen.tsx @@ -1,12 +1,11 @@ "use client"; import { useEffect } from "react"; -import { useRouter } from "next/navigation"; import { MobileShell } from "@/app/_components/core"; import { hasBusinessLoginToken } from "@/lib/auth/auth_session"; +import { useAppNavigator } from "@/router/use-app-navigator"; import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context"; -import { ROUTES } from "@/router/routes"; import { pwaUtil, Logger } from "@/utils"; import { @@ -25,13 +24,13 @@ if (typeof window !== "undefined") { } export function SplashScreen() { - const router = useRouter(); + const navigator = useAppNavigator(); const state = useAuthState(); const authDispatch = useAuthDispatch(); const handleSkip = () => { authDispatch({ type: "AuthGuestLoginSubmitted" }); - router.replace(ROUTES.chat); + navigator.openChat({ replace: true }); }; useEffect(() => { @@ -49,13 +48,13 @@ export function SplashScreen() { let cancelled = false; void (async () => { if (!cancelled && (await hasBusinessLoginToken())) { - router.replace(ROUTES.chat); + navigator.openChat({ replace: true }); } })(); return () => { cancelled = true; }; - }, [router]); + }, [navigator]); // ───────────────────────────────────────────────────────────── // useEffect ② 按钮刚派了事件(pendingRedirect=true) + @@ -76,12 +75,12 @@ export function SplashScreen() { if (state.pendingRedirect && state.loginStatus !== "notLoggedIn") { log.debug( - "[splash] useEffect ② → router.replace(/chat) [via pendingRedirect flag]", + "[splash] useEffect ② → navigator.openChat [via pendingRedirect flag]", ); authDispatch({ type: "AuthClearPendingRedirect" }); - router.replace(ROUTES.chat); + navigator.openChat({ replace: true }); } - }, [state.loginStatus, state.pendingRedirect, authDispatch, router]); + }, [state.loginStatus, state.pendingRedirect, authDispatch, navigator]); return ( diff --git a/src/app/subscription/return/page.tsx b/src/app/subscription/return/page.tsx index a204ac0a..5e94eed8 100644 --- a/src/app/subscription/return/page.tsx +++ b/src/app/subscription/return/page.tsx @@ -1,7 +1,6 @@ "use client"; import { useEffect, useState } from "react"; -import { useRouter } from "next/navigation"; import { MobileShell } from "@/app/_components/core"; import { @@ -9,11 +8,12 @@ import { getPendingPaymentOrder, } from "@/lib/payment/pending_payment_order"; import { ROUTES } from "@/router/routes"; +import { useAppNavigator } from "@/router/use-app-navigator"; type ReturnStatus = "loading" | "missing"; export default function SubscriptionReturnPage() { - const router = useRouter(); + const navigator = useAppNavigator(); const [status, setStatus] = useState("loading"); useEffect(() => { @@ -24,7 +24,7 @@ export default function SubscriptionReturnPage() { if (cancelled) return; if (result.success && result.data !== null) { - router.replace(buildPendingPaymentSubscriptionUrl(result.data)); + navigator.replace(buildPendingPaymentSubscriptionUrl(result.data)); return; } @@ -35,7 +35,7 @@ export default function SubscriptionReturnPage() { return () => { cancelled = true; }; - }, [router]); + }, [navigator]); return ( @@ -62,7 +62,7 @@ export default function SubscriptionReturnPage() { {status === "missing" ? (