feat(chat): unlock paid messages before payment

This commit is contained in:
2026-06-30 18:56:17 +08:00
parent 5f94105bc6
commit e7a9e7abe5
21 changed files with 774 additions and 31 deletions
@@ -1,14 +1,20 @@
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
import { MobileShell } from "@/app/_components/core";
import { ROUTE_BUILDERS, ROUTES } from "@/router/routes";
import { useAuthState } from "@/stores/auth/auth-context";
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
import { savePendingChatImageReturn } from "@/lib/navigation/chat_image_return_session";
import { useUserState } from "@/stores/user/user-context";
import {
consumePendingChatUnlock,
peekPendingChatUnlock,
savePendingChatUnlock,
} from "@/lib/navigation/chat_unlock_session";
import { getChatPaywallNavigationUrl } from "../../chat-screen.helpers";
import { getInsufficientCreditsSubscriptionType } from "../../chat-screen.helpers";
import { FullscreenImageViewer, HistoryUnlockDialog } from "../../components";
import styles from "./chat-image-viewer-screen.module.css";
@@ -21,22 +27,95 @@ export function ChatImageViewerScreen({
}: ChatImageViewerScreenProps) {
const router = useRouter();
const authState = useAuthState();
const userState = useUserState();
const chatState = useChatState();
const chatDispatch = useChatDispatch();
const returnUrl = ROUTE_BUILDERS.chatImage(messageId);
const isAuthenticatedUser =
authState.loginStatus !== "notLoggedIn" &&
authState.loginStatus !== "guest";
const message = chatState.messages.find(
(item) => item.id === messageId && item.imageUrl,
);
useEffect(() => {
if (!chatState.historyLoaded || !isAuthenticatedUser) return;
const pending = peekPendingChatUnlock();
if (
!pending ||
pending.kind !== "image" ||
pending.messageId !== messageId ||
pending.returnUrl !== returnUrl
) {
return;
}
const consumed = consumePendingChatUnlock();
if (!consumed) return;
chatDispatch({
type: "ChatUnlockMessageRequested",
messageId: consumed.messageId,
kind: consumed.kind,
});
}, [
chatDispatch,
chatState.historyLoaded,
isAuthenticatedUser,
messageId,
returnUrl,
]);
useEffect(() => {
const request = chatState.unlockPaywallRequest;
if (!request || request.kind !== "image" || request.messageId !== messageId) {
return;
}
savePendingChatUnlock({
messageId: request.messageId,
kind: request.kind,
returnUrl,
stage: "payment",
});
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
router.push(
ROUTE_BUILDERS.subscription(
getInsufficientCreditsSubscriptionType(userState.isVip),
{ returnTo: "chat" },
),
);
}, [
chatDispatch,
chatState.unlockPaywallRequest,
messageId,
returnUrl,
router,
userState.isVip,
]);
const handleClose = () => {
router.push(ROUTES.chat);
};
const handleUnlockImagePaywall = () => {
savePendingChatImageReturn({
if (!isAuthenticatedUser) {
savePendingChatUnlock({
messageId,
kind: "image",
returnUrl,
stage: "auth",
});
router.push(ROUTE_BUILDERS.authWithRedirect(returnUrl));
return;
}
chatDispatch({
type: "ChatUnlockMessageRequested",
messageId,
returnUrl: ROUTE_BUILDERS.chatImage(messageId),
kind: "image",
});
router.push(getChatPaywallNavigationUrl(authState.loginStatus));
};
const historyUnlockDialog = (
@@ -79,6 +158,10 @@ export function ChatImageViewerScreen({
messageId={messageId}
imageUrl={message.imageUrl ?? ""}
imagePaywalled={message.imagePaywalled === true}
isUnlockingImagePaywall={
chatState.isUnlockingMessage &&
chatState.unlockingMessageId === messageId
}
onUnlockImagePaywall={handleUnlockImagePaywall}
onClose={handleClose}
/>