refactor(router): centralize app route transitions

This commit is contained in:
2026-07-03 14:15:52 +08:00
parent 91dde42f92
commit 873a0a8bce
10 changed files with 188 additions and 133 deletions
+5 -5
View File
@@ -5,11 +5,11 @@
* *
*/ */
import { useEffect, useSyncExternalStore } from "react"; import { useEffect, useSyncExternalStore } from "react";
import { useRouter } from "next/navigation";
import { MobileShell } from "@/app/_components/core"; import { MobileShell } from "@/app/_components/core";
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context"; import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
import { ROUTES } from "@/router/routes"; import { ROUTES } from "@/router/routes";
import { useAppNavigator } from "@/router/use-app-navigator";
import { AuthBackground, AuthPanel } from "./components"; import { AuthBackground, AuthPanel } from "./components";
import styles from "./components/auth-screen.module.css"; import styles from "./components/auth-screen.module.css";
@@ -20,7 +20,7 @@ const log = new Logger("AppAuthAuthScreen");
export function AuthScreen() { export function AuthScreen() {
const state = useAuthState(); const state = useAuthState();
const authDispatch = useAuthDispatch(); const authDispatch = useAuthDispatch();
const router = useRouter(); const navigator = useAppNavigator();
const redirectTo = useSyncExternalStore( const redirectTo = useSyncExternalStore(
subscribeLocationSnapshot, subscribeLocationSnapshot,
readRedirectFromLocation, readRedirectFromLocation,
@@ -40,17 +40,17 @@ export function AuthScreen() {
}); });
if (state.pendingRedirect && state.loginStatus !== "notLoggedIn") { 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, redirectTo: safeRedirectTo,
}); });
authDispatch({ type: "AuthClearPendingRedirect" }); authDispatch({ type: "AuthClearPendingRedirect" });
router.replace(safeRedirectTo); navigator.replace(safeRedirectTo);
} }
}, [ }, [
state.loginStatus, state.loginStatus,
state.pendingRedirect, state.pendingRedirect,
authDispatch, authDispatch,
router, navigator,
safeRedirectTo, safeRedirectTo,
]); ]);
+3 -4
View File
@@ -2,9 +2,8 @@
/** /**
* 认证面板:顶层 switchFacebook / Email+ 悬浮返回按钮 * 认证面板:顶层 switchFacebook / Email+ 悬浮返回按钮
*/ */
import { useRouter } from "next/navigation";
import { BackButton } from "@/app/_components"; import { BackButton } from "@/app/_components";
import { useAppNavigator } from "@/router/use-app-navigator";
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context"; import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
import { AuthEmailPanel } from "./auth-email-panel"; import { AuthEmailPanel } from "./auth-email-panel";
@@ -14,7 +13,7 @@ import styles from "./auth-panel.module.css";
export function AuthPanel() { export function AuthPanel() {
const state = useAuthState(); const state = useAuthState();
const dispatch = useAuthDispatch(); const dispatch = useAuthDispatch();
const router = useRouter(); const navigator = useAppNavigator();
const switchToFacebook = () => const switchToFacebook = () =>
dispatch({ type: "AuthPanelModeChanged", mode: "facebook" }); dispatch({ type: "AuthPanelModeChanged", mode: "facebook" });
@@ -25,7 +24,7 @@ export function AuthPanel() {
if (state.authPanelMode === "email") { if (state.authPanelMode === "email") {
switchToFacebook(); switchToFacebook();
} else { } else {
router.back(); navigator.back();
} }
}; };
+3 -4
View File
@@ -9,10 +9,9 @@
* - 错误占位符(broken image icon * - 错误占位符(broken image icon
*/ */
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react"; import { useState } from "react";
import { ROUTE_BUILDERS } from "@/router/routes"; import { useAppNavigator } from "@/router/use-app-navigator";
import { import {
isBrowserLocalChatImageSource, isBrowserLocalChatImageSource,
normalizeChatImageSource, normalizeChatImageSource,
@@ -33,7 +32,7 @@ export function ImageBubble({
imageUrl, imageUrl,
imagePaywalled = false, imagePaywalled = false,
}: ImageBubbleProps) { }: ImageBubbleProps) {
const router = useRouter(); const navigator = useAppNavigator();
const [errorSrc, setErrorSrc] = useState<string | null>(null); const [errorSrc, setErrorSrc] = useState<string | null>(null);
const { mediaUrl, isUsingCachedMedia, reportMediaError } = const { mediaUrl, isUsingCachedMedia, reportMediaError } =
useCachedChatMediaUrl({ useCachedChatMediaUrl({
@@ -58,7 +57,7 @@ export function ImageBubble({
const openImage = () => { const openImage = () => {
if (!messageId) return; if (!messageId) return;
requestChatScrollSnapshotSave(messageId); requestChatScrollSnapshotSave(messageId);
router.push(ROUTE_BUILDERS.chatImage(messageId), { scroll: false }); navigator.openChatImage(messageId);
}; };
return ( return (
@@ -6,14 +6,13 @@
* 设计: * 设计:
* - 不走 Server `redirect()`:避免把 fbid 暴露在 URL 里。 * - 不走 Server `redirect()`:避免把 fbid 暴露在 URL 里。
* - deviceId / fbid / avatarUrl 统一交给 `lib/chat` 落地。 * - deviceId / fbid / avatarUrl 统一交给 `lib/chat` 落地。
* - 写入完成后 `router.replace('/chat')`URL 不留深链痕迹。 * - 写入完成后通过统一导航回到 `/chat`URL 不留深链痕迹。
*/ */
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { persistExternalBrowserChatDeepLink } from "@/lib/chat/chat_external_browser"; import { persistExternalBrowserChatDeepLink } from "@/lib/chat/chat_external_browser";
import { ROUTES } from "@/router/routes"; import { useAppNavigator } from "@/router/use-app-navigator";
import { Logger } from "@/utils"; import { Logger } from "@/utils";
const log = new Logger("AppChatDeviceidDeviceIdDeepLinkPersist"); const log = new Logger("AppChatDeviceidDeviceIdDeepLinkPersist");
@@ -29,7 +28,7 @@ export default function DeepLinkPersist({
fbid, fbid,
avatarUrl, avatarUrl,
}: DeepLinkPersistProps) { }: DeepLinkPersistProps) {
const router = useRouter(); const navigator = useAppNavigator();
const [done, setDone] = useState(false); const [done, setDone] = useState(false);
useEffect(() => { useEffect(() => {
@@ -45,10 +44,10 @@ export default function DeepLinkPersist({
log.warn("[DeepLinkPersist] failed to persist", err); log.warn("[DeepLinkPersist] failed to persist", err);
} finally { } finally {
setDone(true); setDone(true);
router.replace(ROUTES.chat); navigator.openChat({ replace: true });
} }
})(); })();
}, [avatarUrl, deviceId, fbid, router]); }, [avatarUrl, deviceId, fbid, navigator]);
return ( return (
<div <div
+1 -1
View File
@@ -3,7 +3,7 @@
* *
* 对齐 Flutter `lib/router/app_router.dart:60-78` 的 `GoRoute(path: '/chat/deviceid/:deviceId')` * 对齐 Flutter `lib/router/app_router.dart:60-78` 的 `GoRoute(path: '/chat/deviceid/:deviceId')`
* 把 `deviceId` 路径参数与 `fbid` / `avatarUrl` 查询参数透传给 Client 组件 * 把 `deviceId` 路径参数与 `fbid` / `avatarUrl` 查询参数透传给 Client 组件
* `<DeepLinkPersist>`,由它把数据写入 localStorage 后 `router.replace('/chat')`。 * `<DeepLinkPersist>`,由它把数据写入 localStorage 后通过统一导航回到 `/chat`。
* *
* Next.js 15+ 约定:`params` / `searchParams` 是 Promise,必须 await。 * Next.js 15+ 约定:`params` / `searchParams` 是 Promise,必须 await。
* Next.js 16 全局 helper `PageProps<'/literal'>` 由 `next dev` / `next build` / * Next.js 16 全局 helper `PageProps<'/literal'>` 由 `next dev` / `next build` /
@@ -1,9 +1,8 @@
"use client"; "use client";
import { useRouter } from "next/navigation";
import { MobileShell } from "@/app/_components/core"; 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 { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
import { import {
@@ -21,7 +20,7 @@ export interface ChatImageViewerScreenProps {
export function ChatImageViewerScreen({ export function ChatImageViewerScreen({
messageId, messageId,
}: ChatImageViewerScreenProps) { }: ChatImageViewerScreenProps) {
const router = useRouter(); const navigator = useAppNavigator();
const chatState = useChatState(); const chatState = useChatState();
const chatDispatch = useChatDispatch(); const chatDispatch = useChatDispatch();
const returnUrl = ROUTE_BUILDERS.chatImage(messageId); const returnUrl = ROUTE_BUILDERS.chatImage(messageId);
@@ -40,7 +39,7 @@ export function ChatImageViewerScreen({
); );
const handleClose = () => { const handleClose = () => {
router.push(ROUTES.chat, { scroll: false }); navigator.openChat({ scroll: false });
}; };
const handleUnlockImagePaywall = () => { const handleUnlockImagePaywall = () => {
+8 -9
View File
@@ -1,12 +1,11 @@
"use client"; "use client";
import { useEffect } from "react"; import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { MobileShell } from "@/app/_components/core"; import { MobileShell } from "@/app/_components/core";
import { hasBusinessLoginToken } from "@/lib/auth/auth_session"; import { hasBusinessLoginToken } from "@/lib/auth/auth_session";
import { useAppNavigator } from "@/router/use-app-navigator";
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context"; import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
import { ROUTES } from "@/router/routes";
import { pwaUtil, Logger } from "@/utils"; import { pwaUtil, Logger } from "@/utils";
import { import {
@@ -25,13 +24,13 @@ if (typeof window !== "undefined") {
} }
export function SplashScreen() { export function SplashScreen() {
const router = useRouter(); const navigator = useAppNavigator();
const state = useAuthState(); const state = useAuthState();
const authDispatch = useAuthDispatch(); const authDispatch = useAuthDispatch();
const handleSkip = () => { const handleSkip = () => {
authDispatch({ type: "AuthGuestLoginSubmitted" }); authDispatch({ type: "AuthGuestLoginSubmitted" });
router.replace(ROUTES.chat); navigator.openChat({ replace: true });
}; };
useEffect(() => { useEffect(() => {
@@ -49,13 +48,13 @@ export function SplashScreen() {
let cancelled = false; let cancelled = false;
void (async () => { void (async () => {
if (!cancelled && (await hasBusinessLoginToken())) { if (!cancelled && (await hasBusinessLoginToken())) {
router.replace(ROUTES.chat); navigator.openChat({ replace: true });
} }
})(); })();
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [router]); }, [navigator]);
// ───────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────
// useEffect ② 按钮刚派了事件(pendingRedirect=true + // useEffect ② 按钮刚派了事件(pendingRedirect=true +
@@ -76,12 +75,12 @@ export function SplashScreen() {
if (state.pendingRedirect && state.loginStatus !== "notLoggedIn") { if (state.pendingRedirect && state.loginStatus !== "notLoggedIn") {
log.debug( log.debug(
"[splash] useEffect ② → router.replace(/chat) [via pendingRedirect flag]", "[splash] useEffect ② → navigator.openChat [via pendingRedirect flag]",
); );
authDispatch({ type: "AuthClearPendingRedirect" }); authDispatch({ type: "AuthClearPendingRedirect" });
router.replace(ROUTES.chat); navigator.openChat({ replace: true });
} }
}, [state.loginStatus, state.pendingRedirect, authDispatch, router]); }, [state.loginStatus, state.pendingRedirect, authDispatch, navigator]);
return ( return (
<MobileShell background="var(--color-sidebar-background)"> <MobileShell background="var(--color-sidebar-background)">
+5 -5
View File
@@ -1,7 +1,6 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { MobileShell } from "@/app/_components/core"; import { MobileShell } from "@/app/_components/core";
import { import {
@@ -9,11 +8,12 @@ import {
getPendingPaymentOrder, getPendingPaymentOrder,
} from "@/lib/payment/pending_payment_order"; } from "@/lib/payment/pending_payment_order";
import { ROUTES } from "@/router/routes"; import { ROUTES } from "@/router/routes";
import { useAppNavigator } from "@/router/use-app-navigator";
type ReturnStatus = "loading" | "missing"; type ReturnStatus = "loading" | "missing";
export default function SubscriptionReturnPage() { export default function SubscriptionReturnPage() {
const router = useRouter(); const navigator = useAppNavigator();
const [status, setStatus] = useState<ReturnStatus>("loading"); const [status, setStatus] = useState<ReturnStatus>("loading");
useEffect(() => { useEffect(() => {
@@ -24,7 +24,7 @@ export default function SubscriptionReturnPage() {
if (cancelled) return; if (cancelled) return;
if (result.success && result.data !== null) { if (result.success && result.data !== null) {
router.replace(buildPendingPaymentSubscriptionUrl(result.data)); navigator.replace(buildPendingPaymentSubscriptionUrl(result.data));
return; return;
} }
@@ -35,7 +35,7 @@ export default function SubscriptionReturnPage() {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [router]); }, [navigator]);
return ( return (
<MobileShell> <MobileShell>
@@ -62,7 +62,7 @@ export default function SubscriptionReturnPage() {
{status === "missing" ? ( {status === "missing" ? (
<button <button
type="button" type="button"
onClick={() => router.replace(ROUTES.subscription)} onClick={() => navigator.replace(ROUTES.subscription)}
style={{ style={{
marginTop: "0.5rem", marginTop: "0.5rem",
padding: "0.75rem 1.5rem", padding: "0.75rem 1.5rem",
@@ -1,16 +1,12 @@
"use client"; "use client";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import {
consumeSubscriptionExitUrl,
peekSubscriptionExplicitExitUrl,
} from "@/lib/navigation/subscription_exit";
import { import {
clearPendingPaymentOrder, clearPendingPaymentOrder,
getPendingPaymentOrderForType, getPendingPaymentOrderForType,
} from "@/lib/payment/pending_payment_order"; } from "@/lib/payment/pending_payment_order";
import { useAppNavigator } from "@/router/use-app-navigator";
import { import {
usePaymentDispatch, usePaymentDispatch,
usePaymentState, usePaymentState,
@@ -33,7 +29,7 @@ export function useSubscriptionPaymentFlow({
returnTo, returnTo,
initialPayChannel, initialPayChannel,
}: UseSubscriptionPaymentFlowInput) { }: UseSubscriptionPaymentFlowInput) {
const router = useRouter(); const navigator = useAppNavigator();
const userDispatch = useUserDispatch(); const userDispatch = useUserDispatch();
const payment = usePaymentState(); const payment = usePaymentState();
const paymentDispatch = usePaymentDispatch(); const paymentDispatch = usePaymentDispatch();
@@ -148,22 +144,13 @@ export function useSubscriptionPaymentFlow({
}, [payment.currentOrderId, payment.isPaid, payment.status]); }, [payment.currentOrderId, payment.isPaid, payment.status]);
const handleBackClick = () => { const handleBackClick = () => {
void (async () => { navigator.exitSubscription(returnTo);
router.replace(await consumeSubscriptionExitUrl(returnTo));
})();
}; };
const handlePaymentSuccessClose = () => { const handlePaymentSuccessClose = () => {
void (async () => { setShowPaymentSuccessDialog(false);
setShowPaymentSuccessDialog(false); paymentDispatch({ type: "PaymentReset" });
paymentDispatch({ type: "PaymentReset" }); navigator.exitSubscriptionAfterSuccess(returnTo);
const pendingExitUrl = await peekSubscriptionExplicitExitUrl();
if (pendingExitUrl) {
router.replace(pendingExitUrl);
return;
}
router.replace(await consumeSubscriptionExitUrl(returnTo));
})();
}; };
return { return {
+148 -75
View File
@@ -1,8 +1,14 @@
"use client"; "use client";
import { useCallback, useMemo } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { NavigationStorage } from "@/data/storage/navigation"; import { NavigationStorage } from "@/data/storage/navigation";
import type { SubscriptionReturnTo } from "@/lib/navigation/subscription_exit";
import {
consumeSubscriptionExitUrl,
peekSubscriptionExplicitExitUrl,
} from "@/lib/navigation/subscription_exit";
import { getDefaultPayChannelForCountryCode } from "@/lib/payment/default_pay_channel"; import { getDefaultPayChannelForCountryCode } from "@/lib/payment/default_pay_channel";
import { useAuthState } from "@/stores/auth/auth-context"; import { useAuthState } from "@/stores/auth/auth-context";
import { useUserState } from "@/stores/user/user-context"; import { useUserState } from "@/stores/user/user-context";
@@ -25,8 +31,13 @@ import type {
export interface AppNavigator { export interface AppNavigator {
push: (href: string, options?: { scroll?: boolean }) => void; push: (href: string, options?: { scroll?: boolean }) => void;
replace: (href: string, options?: { scroll?: boolean }) => void; replace: (href: string, options?: { scroll?: boolean }) => void;
back: () => void;
openAuth: (redirectTo?: string) => void; openAuth: (redirectTo?: string) => void;
openChat: (options?: { replace?: boolean; scroll?: boolean }) => void;
openChatImage: (messageId: string) => void;
openSubscription: (input: OpenSubscriptionInput) => void; openSubscription: (input: OpenSubscriptionInput) => void;
exitSubscription: (returnTo: SubscriptionReturnTo) => void;
exitSubscriptionAfterSuccess: (returnTo: SubscriptionReturnTo) => void;
startMessageUnlock: (input: StartMessageUnlockInput) => void; startMessageUnlock: (input: StartMessageUnlockInput) => void;
openSubscriptionForPendingUnlock: ( openSubscriptionForPendingUnlock: (
input: OpenSubscriptionForPendingUnlockInput, input: OpenSubscriptionForPendingUnlockInput,
@@ -41,100 +52,162 @@ export function useAppNavigator(): AppNavigator {
const userState = useUserState(); const userState = useUserState();
const isAuthenticatedUser = resolveIsAuthenticatedUser(authState.loginStatus); const isAuthenticatedUser = resolveIsAuthenticatedUser(authState.loginStatus);
function push(href: string, options?: { scroll?: boolean }): void { const push = useCallback((href: string, options?: { scroll?: boolean }): void => {
router.push(href, options); router.push(href, options);
} }, [router]);
function replace(href: string, options?: { scroll?: boolean }): void { const replace = useCallback((href: string, options?: { scroll?: boolean }): void => {
router.replace(href, options); router.replace(href, options);
} }, [router]);
function openAuth(redirectTo: string = ROUTES.chat): void { const back = useCallback((): void => {
router.back();
}, [router]);
const openAuth = useCallback((redirectTo: string = ROUTES.chat): void => {
router.push(ROUTE_BUILDERS.authWithRedirect(redirectTo)); router.push(ROUTE_BUILDERS.authWithRedirect(redirectTo));
} }, [router]);
function getDefaultPayChannel() { const openChat = useCallback((options: { replace?: boolean; scroll?: boolean } = {}): void => {
const navOptions = { scroll: options.scroll ?? false };
if (options.replace) {
router.replace(ROUTES.chat, navOptions);
return;
}
router.push(ROUTES.chat, navOptions);
}, [router]);
const openChatImage = useCallback((messageId: string): void => {
router.push(ROUTE_BUILDERS.chatImage(messageId), { scroll: false });
}, [router]);
const getDefaultPayChannel = useCallback(() => {
return getDefaultPayChannelForCountryCode(userState.currentUser?.countryCode); return getDefaultPayChannelForCountryCode(userState.currentUser?.countryCode);
} }, [userState.currentUser?.countryCode]);
function openSubscription({ const openSubscription = useCallback(
type, ({
payChannel = getDefaultPayChannel(), type,
returnTo = null, payChannel = getDefaultPayChannel(),
replace: shouldReplace = false, returnTo = null,
}: OpenSubscriptionInput): void { replace: shouldReplace = false,
const target = ROUTE_BUILDERS.subscription(type, { }: OpenSubscriptionInput): void => {
payChannel, const target = ROUTE_BUILDERS.subscription(type, {
returnTo: returnTo ?? undefined,
});
const nextUrl = resolveAuthenticatedNavigation({
loginStatus: authState.loginStatus,
targetUrl: target,
});
if (shouldReplace) {
router.replace(nextUrl);
return;
}
router.push(nextUrl);
}
function startMessageUnlock({
messageId,
kind,
returnUrl,
stage = "auth",
onAuthenticated,
}: StartMessageUnlockInput): void {
if (isAuthenticatedUser) {
onAuthenticated();
return;
}
void (async () => {
await NavigationStorage.savePendingChatUnlock({
messageId,
kind,
returnUrl,
stage,
});
router.push(ROUTE_BUILDERS.authWithRedirect(returnUrl));
})();
}
function openSubscriptionForPendingUnlock({
messageId,
kind,
returnUrl,
type,
payChannel = getDefaultPayChannel(),
}: OpenSubscriptionForPendingUnlockInput): void {
void (async () => {
await NavigationStorage.savePendingChatUnlock({
messageId,
kind,
returnUrl,
stage: "payment",
});
openSubscription({
type,
payChannel, payChannel,
returnTo: "chat", returnTo: returnTo ?? undefined,
}); });
})();
}
return { const nextUrl = resolveAuthenticatedNavigation({
loginStatus: authState.loginStatus,
targetUrl: target,
});
if (shouldReplace) {
router.replace(nextUrl);
return;
}
router.push(nextUrl);
},
[authState.loginStatus, getDefaultPayChannel, router],
);
const startMessageUnlock = useCallback(
({
messageId,
kind,
returnUrl,
stage = "auth",
onAuthenticated,
}: StartMessageUnlockInput): void => {
if (isAuthenticatedUser) {
onAuthenticated();
return;
}
void (async () => {
await NavigationStorage.savePendingChatUnlock({
messageId,
kind,
returnUrl,
stage,
});
router.push(ROUTE_BUILDERS.authWithRedirect(returnUrl));
})();
},
[isAuthenticatedUser, router],
);
const openSubscriptionForPendingUnlock = useCallback(
({
messageId,
kind,
returnUrl,
type,
payChannel = getDefaultPayChannel(),
}: OpenSubscriptionForPendingUnlockInput): void => {
void (async () => {
await NavigationStorage.savePendingChatUnlock({
messageId,
kind,
returnUrl,
stage: "payment",
});
openSubscription({
type,
payChannel,
returnTo: "chat",
});
})();
},
[getDefaultPayChannel, openSubscription],
);
const exitSubscription = useCallback((returnTo: SubscriptionReturnTo): void => {
void (async () => {
router.replace(await consumeSubscriptionExitUrl(returnTo));
})();
}, [router]);
const exitSubscriptionAfterSuccess = useCallback((returnTo: SubscriptionReturnTo): void => {
void (async () => {
const pendingExitUrl = await peekSubscriptionExplicitExitUrl();
if (pendingExitUrl) {
router.replace(pendingExitUrl);
return;
}
router.replace(await consumeSubscriptionExitUrl(returnTo));
})();
}, [router]);
return useMemo(() => ({
push, push,
replace, replace,
back,
openAuth, openAuth,
openChat,
openChatImage,
openSubscription, openSubscription,
exitSubscription,
exitSubscriptionAfterSuccess,
startMessageUnlock, startMessageUnlock,
openSubscriptionForPendingUnlock, openSubscriptionForPendingUnlock,
getDefaultPayChannel, getDefaultPayChannel,
isAuthenticatedUser, isAuthenticatedUser,
}; }), [
back,
exitSubscription,
exitSubscriptionAfterSuccess,
getDefaultPayChannel,
isAuthenticatedUser,
openAuth,
openChat,
openChatImage,
openSubscription,
openSubscriptionForPendingUnlock,
push,
replace,
startMessageUnlock,
]);
} }
export type { Route }; export type { Route };