refactor(app): tighten chat and sync boundaries
This commit is contained in:
@@ -7,18 +7,12 @@ import { useRouter } from "next/navigation";
|
||||
import { useAuthState } from "@/stores/auth/auth-context";
|
||||
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||||
import { useUserState } from "@/stores/user/user-context";
|
||||
import { ROUTE_BUILDERS, ROUTES } from "@/router/routes";
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import {
|
||||
openChatInExternalBrowser,
|
||||
recordExternalBrowserPromptShown,
|
||||
resolveExternalBrowserPromptEligibility,
|
||||
} from "@/lib/chat/chat_external_browser";
|
||||
import {
|
||||
consumePendingChatUnlock,
|
||||
peekPendingChatUnlock,
|
||||
savePendingChatUnlock,
|
||||
type PendingChatUnlockKind,
|
||||
} from "@/lib/navigation/chat_unlock_session";
|
||||
|
||||
import { MobileShell } from "@/app/_components/core";
|
||||
|
||||
@@ -27,7 +21,7 @@ import {
|
||||
ChatArea,
|
||||
ChatHeader,
|
||||
ChatInputBar,
|
||||
ChatQuotaExhaustedBanner,
|
||||
ChatInsufficientCreditsBanner,
|
||||
ExternalBrowserDialog,
|
||||
HistoryUnlockDialog,
|
||||
InsufficientCreditsDialog,
|
||||
@@ -40,6 +34,7 @@ import {
|
||||
isChatDevelopmentEnvironment,
|
||||
shouldStartExternalBrowserPrompt,
|
||||
} from "./chat-screen.helpers";
|
||||
import { useChatUnlockNavigationFlow } from "./hooks/use-chat-unlock-navigation-flow";
|
||||
import styles from "./components/chat-screen.module.css";
|
||||
|
||||
export function ChatScreen() {
|
||||
@@ -50,13 +45,15 @@ export function ChatScreen() {
|
||||
const router = useRouter();
|
||||
const [showExternalBrowserDialog, setShowExternalBrowserDialog] =
|
||||
useState(false);
|
||||
const unlockPaywallRequest = state.unlockPaywallRequest;
|
||||
const {
|
||||
unlockPaywallRequest,
|
||||
requestMessageUnlock,
|
||||
closeInsufficientCreditsDialog,
|
||||
confirmInsufficientCreditsDialog,
|
||||
} = useChatUnlockNavigationFlow({ returnUrl: ROUTES.chat });
|
||||
|
||||
// isGuest 派生(chat-screen 是唯一知道这个值的地方 —— chat 机器无 isGuest 字段)
|
||||
const isGuest = deriveIsGuest(authState.loginStatus);
|
||||
const isAuthenticatedUser =
|
||||
authState.loginStatus !== "notLoggedIn" &&
|
||||
authState.loginStatus !== "guest";
|
||||
|
||||
// 发送能力由后端统一返回,当前只处理积分不足引导。
|
||||
const showMessageLimitBanner =
|
||||
@@ -108,75 +105,11 @@ export function ChatScreen() {
|
||||
authState.loginStatus,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!state.historyLoaded || !isAuthenticatedUser) return;
|
||||
|
||||
const pending = peekPendingChatUnlock();
|
||||
if (!pending || pending.returnUrl !== ROUTES.chat) return;
|
||||
|
||||
const consumed = consumePendingChatUnlock();
|
||||
if (!consumed) return;
|
||||
|
||||
chatDispatch({
|
||||
type: "ChatUnlockMessageRequested",
|
||||
messageId: consumed.messageId,
|
||||
kind: consumed.kind,
|
||||
});
|
||||
}, [
|
||||
chatDispatch,
|
||||
isAuthenticatedUser,
|
||||
state.historyLoaded,
|
||||
]);
|
||||
|
||||
function handleCloseInsufficientCreditsDialog(): void {
|
||||
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
|
||||
}
|
||||
|
||||
function handleConfirmInsufficientCreditsDialog(): void {
|
||||
if (!unlockPaywallRequest) return;
|
||||
|
||||
savePendingChatUnlock({
|
||||
messageId: unlockPaywallRequest.messageId,
|
||||
kind: unlockPaywallRequest.kind,
|
||||
returnUrl: ROUTES.chat,
|
||||
stage: "payment",
|
||||
});
|
||||
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
|
||||
router.push(
|
||||
ROUTE_BUILDERS.subscription(
|
||||
getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||
{ returnTo: "chat" },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
async function handleOpenExternalBrowser(): Promise<void> {
|
||||
setShowExternalBrowserDialog(false);
|
||||
await openChatInExternalBrowser();
|
||||
}
|
||||
|
||||
function requestMessageUnlock(
|
||||
messageId: string,
|
||||
kind: PendingChatUnlockKind,
|
||||
): void {
|
||||
if (!isAuthenticatedUser) {
|
||||
savePendingChatUnlock({
|
||||
messageId,
|
||||
kind,
|
||||
returnUrl: ROUTES.chat,
|
||||
stage: "auth",
|
||||
});
|
||||
router.push(ROUTE_BUILDERS.authWithRedirect(ROUTES.chat));
|
||||
return;
|
||||
}
|
||||
|
||||
chatDispatch({
|
||||
type: "ChatUnlockMessageRequested",
|
||||
messageId,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
|
||||
function handleUnlockPrivateMessage(messageId: string): void {
|
||||
requestMessageUnlock(messageId, "private");
|
||||
}
|
||||
@@ -222,7 +155,7 @@ export function ChatScreen() {
|
||||
/>
|
||||
|
||||
{showMessageLimitBanner ? (
|
||||
<ChatQuotaExhaustedBanner
|
||||
<ChatInsufficientCreditsBanner
|
||||
title={messageLimitTitle}
|
||||
ctaLabel={messageLimitCtaLabel}
|
||||
onUnlock={handleMessageLimitUnlock}
|
||||
@@ -258,8 +191,8 @@ export function ChatScreen() {
|
||||
creditBalance={unlockPaywallRequest?.creditBalance ?? 0}
|
||||
requiredCredits={unlockPaywallRequest?.requiredCredits ?? 0}
|
||||
shortfallCredits={unlockPaywallRequest?.shortfallCredits ?? 0}
|
||||
onClose={handleCloseInsufficientCreditsDialog}
|
||||
onConfirm={handleConfirmInsufficientCreditsDialog}
|
||||
onClose={closeInsufficientCreditsDialog}
|
||||
onConfirm={confirmInsufficientCreditsDialog}
|
||||
/>
|
||||
</div>
|
||||
</MobileShell>
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
.panel {
|
||||
min-height: 170px;
|
||||
padding: 24px 16px 20px;
|
||||
border-top: 1px solid rgba(246, 87, 160, 0.14);
|
||||
background: #fff1f4;
|
||||
}
|
||||
|
||||
.voiceCard {
|
||||
width: 142px;
|
||||
min-height: 153px;
|
||||
padding: 12px 12px 14px;
|
||||
border: 1px solid rgba(30, 30, 30, 0.08);
|
||||
border-radius: 18px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 2px rgba(30, 30, 30, 0.03);
|
||||
color: #1e1e1e;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.voiceCard:focus-visible {
|
||||
outline: 2px solid var(--color-accent, #f84d96);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
|
||||
.voiceIcon {
|
||||
display: block;
|
||||
width: 78px;
|
||||
height: 78px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.voiceTitle,
|
||||
.voiceMinutes {
|
||||
font-family: var(--font-athelas), Athelas, serif;
|
||||
font-size: 14px;
|
||||
line-height: 1.08;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.voiceTitle {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.voiceMinutes {
|
||||
color: var(--color-accent, #f84d96);
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
|
||||
import styles from "./chat-input-action-panel.module.css";
|
||||
|
||||
export interface ChatInputActionPanelProps {
|
||||
voiceMinutesRemaining: number;
|
||||
onVoiceMessageClick?: () => void;
|
||||
}
|
||||
|
||||
export function ChatInputActionPanel({
|
||||
voiceMinutesRemaining,
|
||||
onVoiceMessageClick,
|
||||
}: ChatInputActionPanelProps) {
|
||||
return (
|
||||
<div className={styles.panel}>
|
||||
<button
|
||||
type="button"
|
||||
className={styles.voiceCard}
|
||||
onClick={onVoiceMessageClick}
|
||||
aria-label={`Voice message, ${voiceMinutesRemaining} minutes remaining`}
|
||||
>
|
||||
<Image
|
||||
src="/images/chat/ic_voicemessage_act.png"
|
||||
alt=""
|
||||
width={78}
|
||||
height={78}
|
||||
className={styles.voiceIcon}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className={styles.voiceTitle}>voice message</span>
|
||||
<span className={styles.voiceMinutes}>({voiceMinutesRemaining}min)</span>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
/* ChatQuotaExhaustedBanner — 游客配额耗尽横幅
|
||||
/* ChatInsufficientCreditsBanner — 积分不足横幅
|
||||
* 视觉规格(与设计稿对齐):
|
||||
* - 粉→品红渐变背景(与 splash gradient 一致)
|
||||
* - 居中文案 + 渐变 pill 按钮
|
||||
+11
-13
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
/**
|
||||
* ChatQuotaExhaustedBanner 发送受限横幅
|
||||
* ChatInsufficientCreditsBanner 积分不足横幅
|
||||
*
|
||||
* 何时显示:
|
||||
* - 后端返回 cannotSendReason="insufficient_credits"
|
||||
@@ -8,38 +8,36 @@
|
||||
* 视觉规格(与设计稿对齐):
|
||||
* - 粉→品红渐变背景(与 splash 渐变一致)
|
||||
* - 大字号粉色/白色提示文案
|
||||
* - 粉→品红渐变 pill 按钮 → 跳 /subscription
|
||||
* - 粉→品红渐变 pill 按钮 → 跳 /subscription?type=topup
|
||||
*
|
||||
* 业务关系:
|
||||
* - 与 `src/app/sidebar/components/vip-benefits-card.tsx` 的 "Activate VIP Membership" 行为一致
|
||||
* - 与订阅页 VIP 入口行为一致
|
||||
* - 与订阅页 top up 入口行为一致
|
||||
*
|
||||
* 不做的事:
|
||||
* - 不直接管理 state machine(chat 机器不感知 UI 层)
|
||||
* - 不显示具体剩余分钟数(已用 total<=0 表达"耗尽"足够)
|
||||
*/
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { ROUTES } from "@/router/routes";
|
||||
import { ROUTE_BUILDERS } from "@/router/routes";
|
||||
|
||||
import styles from "./chat-quota-exhausted-banner.module.css";
|
||||
import styles from "./chat-insufficient-credits-banner.module.css";
|
||||
|
||||
export interface ChatQuotaExhaustedBannerProps {
|
||||
export interface ChatInsufficientCreditsBannerProps {
|
||||
title?: string;
|
||||
ctaLabel?: string;
|
||||
/**
|
||||
* 自定义点击回调(不传则默认 router.push(ROUTES.subscription))
|
||||
* 自定义点击回调(不传则默认 router.push(topup subscription))
|
||||
* - 测试时可传 mock
|
||||
* - 嵌入其他场景时(如 nested overlay)可传自定义
|
||||
*/
|
||||
onUnlock?: () => void;
|
||||
}
|
||||
|
||||
export function ChatQuotaExhaustedBanner({
|
||||
export function ChatInsufficientCreditsBanner({
|
||||
title = "Insufficient credits\nTop up to continue chatting",
|
||||
ctaLabel = "Top up credits to continue",
|
||||
onUnlock,
|
||||
}: ChatQuotaExhaustedBannerProps) {
|
||||
}: ChatInsufficientCreditsBannerProps) {
|
||||
const router = useRouter();
|
||||
const titleLines = title.split("\n");
|
||||
|
||||
@@ -48,7 +46,7 @@ export function ChatQuotaExhaustedBanner({
|
||||
onUnlock();
|
||||
return;
|
||||
}
|
||||
router.push(`${ROUTES.subscription}?type=vip`);
|
||||
router.push(ROUTE_BUILDERS.subscription("topup"));
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -56,7 +54,7 @@ export function ChatQuotaExhaustedBanner({
|
||||
className={styles.banner}
|
||||
role="status"
|
||||
aria-live="polite"
|
||||
data-testid="chat-quota-exhausted-banner"
|
||||
data-testid="chat-insufficient-credits-banner"
|
||||
>
|
||||
<p className={styles.text}>
|
||||
{titleLines.map((line, index) => (
|
||||
@@ -6,10 +6,9 @@ export * from "./ai-disclosure-banner";
|
||||
export * from "./browser-hint-overlay";
|
||||
export * from "./chat-area";
|
||||
export * from "./chat-header";
|
||||
export * from "./chat-input-action-panel";
|
||||
export * from "./chat-insufficient-credits-banner";
|
||||
export * from "./chat-input-bar";
|
||||
export * from "./chat-input-text-field";
|
||||
export * from "./chat-quota-exhausted-banner";
|
||||
export * from "./chat-send-button";
|
||||
export * from "./date-header";
|
||||
export * from "./external-browser-dialog";
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
"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 { getInsufficientCreditsSubscriptionType } from "../chat-screen.helpers";
|
||||
|
||||
export interface UseChatUnlockNavigationFlowInput {
|
||||
returnUrl: string;
|
||||
expectedKind?: PendingChatUnlockKind;
|
||||
expectedMessageId?: string;
|
||||
}
|
||||
|
||||
export interface UseChatUnlockNavigationFlowOutput {
|
||||
unlockPaywallRequest: ChatUnlockPaywallRequest | null;
|
||||
requestMessageUnlock: (
|
||||
messageId: string,
|
||||
kind: PendingChatUnlockKind,
|
||||
) => void;
|
||||
closeInsufficientCreditsDialog: () => void;
|
||||
confirmInsufficientCreditsDialog: () => void;
|
||||
}
|
||||
|
||||
export function useChatUnlockNavigationFlow({
|
||||
returnUrl,
|
||||
expectedKind,
|
||||
expectedMessageId,
|
||||
}: UseChatUnlockNavigationFlowInput): UseChatUnlockNavigationFlowOutput {
|
||||
const router = useRouter();
|
||||
const authState = useAuthState();
|
||||
const userState = useUserState();
|
||||
const chatState = useChatState();
|
||||
const chatDispatch = useChatDispatch();
|
||||
const isAuthenticatedUser =
|
||||
authState.loginStatus !== "notLoggedIn" &&
|
||||
authState.loginStatus !== "guest";
|
||||
const unlockPaywallRequest = getScopedUnlockPaywallRequest({
|
||||
request: chatState.unlockPaywallRequest,
|
||||
expectedKind,
|
||||
expectedMessageId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!chatState.historyLoaded || !isAuthenticatedUser) return;
|
||||
|
||||
const pending = peekPendingChatUnlock();
|
||||
if (
|
||||
!pending ||
|
||||
pending.returnUrl !== returnUrl ||
|
||||
!matchesPendingUnlockScope({
|
||||
pending,
|
||||
expectedKind,
|
||||
expectedMessageId,
|
||||
})
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const consumed = consumePendingChatUnlock();
|
||||
if (!consumed) return;
|
||||
|
||||
chatDispatch({
|
||||
type: "ChatUnlockMessageRequested",
|
||||
messageId: consumed.messageId,
|
||||
kind: consumed.kind,
|
||||
});
|
||||
}, [
|
||||
chatDispatch,
|
||||
chatState.historyLoaded,
|
||||
expectedKind,
|
||||
expectedMessageId,
|
||||
isAuthenticatedUser,
|
||||
returnUrl,
|
||||
]);
|
||||
|
||||
function requestMessageUnlock(
|
||||
messageId: string,
|
||||
kind: PendingChatUnlockKind,
|
||||
): void {
|
||||
if (!isAuthenticatedUser) {
|
||||
savePendingChatUnlock({
|
||||
messageId,
|
||||
kind,
|
||||
returnUrl,
|
||||
stage: "auth",
|
||||
});
|
||||
router.push(ROUTE_BUILDERS.authWithRedirect(returnUrl));
|
||||
return;
|
||||
}
|
||||
|
||||
chatDispatch({
|
||||
type: "ChatUnlockMessageRequested",
|
||||
messageId,
|
||||
kind,
|
||||
});
|
||||
}
|
||||
|
||||
function closeInsufficientCreditsDialog(): void {
|
||||
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
|
||||
}
|
||||
|
||||
function confirmInsufficientCreditsDialog(): void {
|
||||
if (!unlockPaywallRequest) return;
|
||||
|
||||
savePendingChatUnlock({
|
||||
messageId: unlockPaywallRequest.messageId,
|
||||
kind: unlockPaywallRequest.kind,
|
||||
returnUrl,
|
||||
stage: "payment",
|
||||
});
|
||||
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
|
||||
router.push(
|
||||
ROUTE_BUILDERS.subscription(
|
||||
getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||
{ returnTo: "chat" },
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
unlockPaywallRequest,
|
||||
requestMessageUnlock,
|
||||
closeInsufficientCreditsDialog,
|
||||
confirmInsufficientCreditsDialog,
|
||||
};
|
||||
}
|
||||
|
||||
function getScopedUnlockPaywallRequest(input: {
|
||||
request: ChatUnlockPaywallRequest | null;
|
||||
expectedKind?: PendingChatUnlockKind;
|
||||
expectedMessageId?: string;
|
||||
}): ChatUnlockPaywallRequest | null {
|
||||
const { request, expectedKind, expectedMessageId } = input;
|
||||
if (!request) return null;
|
||||
if (expectedKind && request.kind !== expectedKind) return null;
|
||||
if (expectedMessageId && request.messageId !== expectedMessageId) return null;
|
||||
return request;
|
||||
}
|
||||
|
||||
function matchesPendingUnlockScope(input: {
|
||||
pending: PendingChatUnlock;
|
||||
expectedKind?: PendingChatUnlockKind;
|
||||
expectedMessageId?: string;
|
||||
}): boolean {
|
||||
const { pending, expectedKind, expectedMessageId } = input;
|
||||
if (expectedKind && pending.kind !== expectedKind) return false;
|
||||
if (expectedMessageId && pending.messageId !== expectedMessageId) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -1,25 +1,17 @@
|
||||
"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 { useUserState } from "@/stores/user/user-context";
|
||||
import {
|
||||
consumePendingChatUnlock,
|
||||
peekPendingChatUnlock,
|
||||
savePendingChatUnlock,
|
||||
} from "@/lib/navigation/chat_unlock_session";
|
||||
|
||||
import { getInsufficientCreditsSubscriptionType } from "../../chat-screen.helpers";
|
||||
import {
|
||||
FullscreenImageViewer,
|
||||
HistoryUnlockDialog,
|
||||
InsufficientCreditsDialog,
|
||||
} from "../../components";
|
||||
import { useChatUnlockNavigationFlow } from "../../hooks/use-chat-unlock-navigation-flow";
|
||||
import styles from "./chat-image-viewer-screen.module.css";
|
||||
|
||||
export interface ChatImageViewerScreenProps {
|
||||
@@ -30,95 +22,29 @@ export function ChatImageViewerScreen({
|
||||
messageId,
|
||||
}: ChatImageViewerScreenProps) {
|
||||
const router = useRouter();
|
||||
const authState = useAuthState();
|
||||
const userState = useUserState();
|
||||
const chatState = useChatState();
|
||||
const chatDispatch = useChatDispatch();
|
||||
const returnUrl = ROUTE_BUILDERS.chatImage(messageId);
|
||||
const unlockPaywallRequest =
|
||||
chatState.unlockPaywallRequest?.kind === "image" &&
|
||||
chatState.unlockPaywallRequest.messageId === messageId
|
||||
? chatState.unlockPaywallRequest
|
||||
: null;
|
||||
const isAuthenticatedUser =
|
||||
authState.loginStatus !== "notLoggedIn" &&
|
||||
authState.loginStatus !== "guest";
|
||||
const {
|
||||
unlockPaywallRequest,
|
||||
requestMessageUnlock,
|
||||
closeInsufficientCreditsDialog,
|
||||
confirmInsufficientCreditsDialog,
|
||||
} = useChatUnlockNavigationFlow({
|
||||
returnUrl,
|
||||
expectedKind: "image",
|
||||
expectedMessageId: messageId,
|
||||
});
|
||||
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,
|
||||
]);
|
||||
|
||||
const handleCloseInsufficientCreditsDialog = () => {
|
||||
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
|
||||
};
|
||||
|
||||
const handleConfirmInsufficientCreditsDialog = () => {
|
||||
if (!unlockPaywallRequest) return;
|
||||
|
||||
savePendingChatUnlock({
|
||||
messageId: unlockPaywallRequest.messageId,
|
||||
kind: unlockPaywallRequest.kind,
|
||||
returnUrl,
|
||||
stage: "payment",
|
||||
});
|
||||
chatDispatch({ type: "ChatUnlockPaywallNavigationConsumed" });
|
||||
router.push(
|
||||
ROUTE_BUILDERS.subscription(
|
||||
getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||
{ returnTo: "chat" },
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
router.push(ROUTES.chat);
|
||||
};
|
||||
|
||||
const handleUnlockImagePaywall = () => {
|
||||
if (!isAuthenticatedUser) {
|
||||
savePendingChatUnlock({
|
||||
messageId,
|
||||
kind: "image",
|
||||
returnUrl,
|
||||
stage: "auth",
|
||||
});
|
||||
router.push(ROUTE_BUILDERS.authWithRedirect(returnUrl));
|
||||
return;
|
||||
}
|
||||
|
||||
chatDispatch({
|
||||
type: "ChatUnlockMessageRequested",
|
||||
messageId,
|
||||
kind: "image",
|
||||
});
|
||||
requestMessageUnlock(messageId, "image");
|
||||
};
|
||||
|
||||
const historyUnlockDialog = (
|
||||
@@ -136,8 +62,8 @@ export function ChatImageViewerScreen({
|
||||
creditBalance={unlockPaywallRequest?.creditBalance ?? 0}
|
||||
requiredCredits={unlockPaywallRequest?.requiredCredits ?? 0}
|
||||
shortfallCredits={unlockPaywallRequest?.shortfallCredits ?? 0}
|
||||
onClose={handleCloseInsufficientCreditsDialog}
|
||||
onConfirm={handleConfirmInsufficientCreditsDialog}
|
||||
onClose={closeInsufficientCreditsDialog}
|
||||
onConfirm={confirmInsufficientCreditsDialog}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user