refactor(router): introduce app navigation manager

This commit is contained in:
2026-07-03 14:09:13 +08:00
parent 8a586e4471
commit 91dde42f92
23 changed files with 669 additions and 277 deletions
@@ -1,21 +1,17 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { ROUTE_BUILDERS } from "@/router/routes";
import { useAuthState } from "@/stores/auth/auth-context";
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
import type { ChatUnlockPaywallRequest } from "@/stores/chat/chat-state";
import { useUserState } from "@/stores/user/user-context";
import {
consumePendingChatUnlock,
peekPendingChatUnlock,
savePendingChatUnlock,
type PendingChatUnlock,
type PendingChatUnlockKind,
} from "@/lib/navigation/chat_unlock_session";
import { getDefaultPayChannelForCountryCode } from "@/lib/payment/default_pay_channel";
import { useAppNavigator } from "@/router/use-app-navigator";
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
import type { ChatUnlockPaywallRequest } from "@/stores/chat/chat-state";
import { useUserState } from "@/stores/user/user-context";
import { getInsufficientCreditsSubscriptionType } from "../chat-screen.helpers";
@@ -40,14 +36,10 @@ export function useChatUnlockNavigationFlow({
expectedKind,
expectedMessageId,
}: UseChatUnlockNavigationFlowInput): UseChatUnlockNavigationFlowOutput {
const router = useRouter();
const authState = useAuthState();
const navigator = useAppNavigator();
const userState = useUserState();
const chatState = useChatState();
const chatDispatch = useChatDispatch();
const isAuthenticatedUser =
authState.loginStatus !== "notLoggedIn" &&
authState.loginStatus !== "guest";
const unlockPaywallRequest = getScopedUnlockPaywallRequest({
request: chatState.unlockPaywallRequest,
expectedKind,
@@ -55,7 +47,7 @@ export function useChatUnlockNavigationFlow({
});
useEffect(() => {
if (!chatState.historyLoaded || !isAuthenticatedUser) return;
if (!chatState.historyLoaded || !navigator.isAuthenticatedUser) return;
let cancelled = false;
@@ -93,7 +85,7 @@ export function useChatUnlockNavigationFlow({
chatState.historyLoaded,
expectedKind,
expectedMessageId,
isAuthenticatedUser,
navigator.isAuthenticatedUser,
returnUrl,
]);
@@ -101,23 +93,17 @@ export function useChatUnlockNavigationFlow({
messageId: string,
kind: PendingChatUnlockKind,
): void {
if (!isAuthenticatedUser) {
void (async () => {
await savePendingChatUnlock({
messageId,
kind,
returnUrl,
stage: "auth",
});
router.push(ROUTE_BUILDERS.authWithRedirect(returnUrl));
})();
return;
}
chatDispatch({
type: "ChatUnlockMessageRequested",
navigator.startMessageUnlock({
messageId,
kind,
returnUrl,
onAuthenticated: () => {
chatDispatch({
type: "ChatUnlockMessageRequested",
messageId,
kind,
});
},
});
}
@@ -128,24 +114,13 @@ export function useChatUnlockNavigationFlow({
function confirmInsufficientCreditsDialog(): void {
if (!unlockPaywallRequest) return;
void (async () => {
await savePendingChatUnlock({
messageId: unlockPaywallRequest.messageId,
kind: unlockPaywallRequest.kind,
returnUrl,
stage: "payment",
});
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
const defaultPayChannel = getDefaultPayChannelForCountryCode(
userState.currentUser?.countryCode,
);
router.push(
ROUTE_BUILDERS.subscription(
getInsufficientCreditsSubscriptionType(userState.isVip),
{ payChannel: defaultPayChannel, returnTo: "chat" },
),
);
})();
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
navigator.openSubscriptionForPendingUnlock({
messageId: unlockPaywallRequest.messageId,
kind: unlockPaywallRequest.kind,
returnUrl,
type: getInsufficientCreditsSubscriptionType(userState.isVip),
});
}
return {
@@ -1,10 +1,9 @@
"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import type { LoginStatus } from "@/data/dto/auth";
import { ROUTE_BUILDERS } from "@/router/routes";
import { useAppNavigator } from "@/router/use-app-navigator";
import { usePaymentDispatch, usePaymentState } from "@/stores/payment";
import { useUserState } from "@/stores/user/user-context";
import { getDefaultPayChannelForCountryCode } from "@/lib/payment/default_pay_channel";
@@ -55,7 +54,7 @@ export function useFirstRechargeOfferBanner({
historyLoaded,
loginStatus,
}: UseFirstRechargeOfferBannerInput): UseFirstRechargeOfferBannerOutput {
const router = useRouter();
const navigator = useAppNavigator();
const payment = usePaymentState();
const paymentDispatch = usePaymentDispatch();
const userState = useUserState();
@@ -127,15 +126,7 @@ export function useFirstRechargeOfferBanner({
}
function claim(): void {
const defaultPayChannel = getDefaultPayChannelForCountryCode(
userState.currentUser?.countryCode,
);
router.push(
ROUTE_BUILDERS.subscription("vip", {
payChannel: defaultPayChannel,
returnTo: "chat",
}),
);
navigator.openSubscription({ type: "vip", returnTo: "chat" });
}
return {