refactor(router): centralize app route transitions
This commit is contained in:
@@ -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,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
/**
|
/**
|
||||||
* 认证面板:顶层 switch(Facebook / Email)+ 悬浮返回按钮
|
* 认证面板:顶层 switch(Facebook / 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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 = () => {
|
||||||
|
|||||||
@@ -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)">
|
||||||
|
|||||||
@@ -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" });
|
||||||
const pendingExitUrl = await peekSubscriptionExplicitExitUrl();
|
navigator.exitSubscriptionAfterSuccess(returnTo);
|
||||||
if (pendingExitUrl) {
|
|
||||||
router.replace(pendingExitUrl);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
router.replace(await consumeSubscriptionExitUrl(returnTo));
|
|
||||||
})();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -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,28 +52,46 @@ 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,
|
type,
|
||||||
payChannel = getDefaultPayChannel(),
|
payChannel = getDefaultPayChannel(),
|
||||||
returnTo = null,
|
returnTo = null,
|
||||||
replace: shouldReplace = false,
|
replace: shouldReplace = false,
|
||||||
}: OpenSubscriptionInput): void {
|
}: OpenSubscriptionInput): void => {
|
||||||
const target = ROUTE_BUILDERS.subscription(type, {
|
const target = ROUTE_BUILDERS.subscription(type, {
|
||||||
payChannel,
|
payChannel,
|
||||||
returnTo: returnTo ?? undefined,
|
returnTo: returnTo ?? undefined,
|
||||||
@@ -78,15 +107,18 @@ export function useAppNavigator(): AppNavigator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
router.push(nextUrl);
|
router.push(nextUrl);
|
||||||
}
|
},
|
||||||
|
[authState.loginStatus, getDefaultPayChannel, router],
|
||||||
|
);
|
||||||
|
|
||||||
function startMessageUnlock({
|
const startMessageUnlock = useCallback(
|
||||||
|
({
|
||||||
messageId,
|
messageId,
|
||||||
kind,
|
kind,
|
||||||
returnUrl,
|
returnUrl,
|
||||||
stage = "auth",
|
stage = "auth",
|
||||||
onAuthenticated,
|
onAuthenticated,
|
||||||
}: StartMessageUnlockInput): void {
|
}: StartMessageUnlockInput): void => {
|
||||||
if (isAuthenticatedUser) {
|
if (isAuthenticatedUser) {
|
||||||
onAuthenticated();
|
onAuthenticated();
|
||||||
return;
|
return;
|
||||||
@@ -101,15 +133,18 @@ export function useAppNavigator(): AppNavigator {
|
|||||||
});
|
});
|
||||||
router.push(ROUTE_BUILDERS.authWithRedirect(returnUrl));
|
router.push(ROUTE_BUILDERS.authWithRedirect(returnUrl));
|
||||||
})();
|
})();
|
||||||
}
|
},
|
||||||
|
[isAuthenticatedUser, router],
|
||||||
|
);
|
||||||
|
|
||||||
function openSubscriptionForPendingUnlock({
|
const openSubscriptionForPendingUnlock = useCallback(
|
||||||
|
({
|
||||||
messageId,
|
messageId,
|
||||||
kind,
|
kind,
|
||||||
returnUrl,
|
returnUrl,
|
||||||
type,
|
type,
|
||||||
payChannel = getDefaultPayChannel(),
|
payChannel = getDefaultPayChannel(),
|
||||||
}: OpenSubscriptionForPendingUnlockInput): void {
|
}: OpenSubscriptionForPendingUnlockInput): void => {
|
||||||
void (async () => {
|
void (async () => {
|
||||||
await NavigationStorage.savePendingChatUnlock({
|
await NavigationStorage.savePendingChatUnlock({
|
||||||
messageId,
|
messageId,
|
||||||
@@ -123,18 +158,56 @@ export function useAppNavigator(): AppNavigator {
|
|||||||
returnTo: "chat",
|
returnTo: "chat",
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
}
|
},
|
||||||
|
[getDefaultPayChannel, openSubscription],
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
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 };
|
||||||
|
|||||||
Reference in New Issue
Block a user