fix(chat): unify insufficient credits prompt

This commit is contained in:
2026-07-16 11:37:11 +08:00
parent 4d24fbc849
commit 764bb5a862
4 changed files with 7 additions and 53 deletions
@@ -11,35 +11,13 @@ afterEach(() => {
}); });
describe("getInsufficientCreditsMessageLimitView", () => { describe("getInsufficientCreditsMessageLimitView", () => {
it("asks guests to log in for more free chats", () => { it("shows the same top-up prompt without checking login status", () => {
expect(getInsufficientCreditsMessageLimitView("guest")).toEqual({ expect(getInsufficientCreditsMessageLimitView()).toEqual({
title: "Log in to get more free chats", title: "Insufficient credits",
description: "Free chats refresh every day.", description: "Free chats refresh every day.",
ctaLabel: "Log in to continue", ctaLabel: "Top up credits to continue",
action: "auth",
}); });
}); });
it("treats the transient not logged in state like guest", () => {
expect(getInsufficientCreditsMessageLimitView("notLoggedIn")).toEqual({
title: "Log in to get more free chats",
description: "Free chats refresh every day.",
ctaLabel: "Log in to continue",
action: "auth",
});
});
it.each(["email", "facebook", "google"] as const)(
"asks %s users to top up credits",
(loginStatus) => {
expect(getInsufficientCreditsMessageLimitView(loginStatus)).toEqual({
title: "Insufficient credits",
description: "Free chats refresh every day.",
ctaLabel: "Top up credits to continue",
action: "topup",
});
},
);
}); });
describe("shouldShowMessageLimitBanner", () => { describe("shouldShowMessageLimitBanner", () => {
+2 -16
View File
@@ -12,13 +12,10 @@ export interface ExternalBrowserPromptState {
isInAppBrowser?: boolean; isInAppBrowser?: boolean;
} }
export type InsufficientCreditsAction = "auth" | "topup";
export interface InsufficientCreditsMessageLimitView { export interface InsufficientCreditsMessageLimitView {
title: string; title: string;
description: string; description: string;
ctaLabel: string; ctaLabel: string;
action: InsufficientCreditsAction;
} }
export interface MessageLimitBannerState { export interface MessageLimitBannerState {
@@ -37,23 +34,12 @@ export function shouldShowMessageLimitBanner({
return upgradePromptVisible && upgradeReason === "insufficient_credits"; return upgradePromptVisible && upgradeReason === "insufficient_credits";
} }
export function getInsufficientCreditsMessageLimitView( export function getInsufficientCreditsMessageLimitView():
loginStatus: LoginStatus, InsufficientCreditsMessageLimitView {
): InsufficientCreditsMessageLimitView {
if (loginStatus === "guest" || loginStatus === "notLoggedIn") {
return {
title: "Log in to get more free chats",
description: "Free chats refresh every day.",
ctaLabel: "Log in to continue",
action: "auth",
};
}
return { return {
title: "Insufficient credits", title: "Insufficient credits",
description: "Free chats refresh every day.", description: "Free chats refresh every day.",
ctaLabel: "Top up credits to continue", ctaLabel: "Top up credits to continue",
action: "topup",
}; };
} }
-1
View File
@@ -88,7 +88,6 @@ export function ChatScreen() {
const messageLimitBanner = useChatMessageLimitBanner({ const messageLimitBanner = useChatMessageLimitBanner({
upgradePromptVisible: state.upgradePromptVisible, upgradePromptVisible: state.upgradePromptVisible,
upgradeReason: state.upgradeReason, upgradeReason: state.upgradeReason,
loginStatus: authState.loginStatus,
}); });
const firstRechargeOfferBanner = useFirstRechargeOfferBanner({ const firstRechargeOfferBanner = useFirstRechargeOfferBanner({
historyLoaded: state.historyLoaded, historyLoaded: state.historyLoaded,
@@ -2,9 +2,7 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import type { LoginStatus } from "@/data/dto/auth";
import { behaviorAnalytics } from "@/lib/analytics"; import { behaviorAnalytics } from "@/lib/analytics";
import { ROUTES } from "@/router/routes";
import { useAppNavigator } from "@/router/use-app-navigator"; import { useAppNavigator } from "@/router/use-app-navigator";
import type { ChatUpgradeReason } from "@/stores/chat/chat-state"; import type { ChatUpgradeReason } from "@/stores/chat/chat-state";
import { useUserSelector } from "@/stores/user/user-context"; import { useUserSelector } from "@/stores/user/user-context";
@@ -19,7 +17,6 @@ import {
export interface UseChatMessageLimitBannerInput { export interface UseChatMessageLimitBannerInput {
upgradePromptVisible: boolean; upgradePromptVisible: boolean;
upgradeReason: ChatUpgradeReason | null; upgradeReason: ChatUpgradeReason | null;
loginStatus: LoginStatus;
} }
export interface ChatMessageLimitBannerView export interface ChatMessageLimitBannerView
@@ -31,11 +28,10 @@ export interface ChatMessageLimitBannerView
export function useChatMessageLimitBanner({ export function useChatMessageLimitBanner({
upgradePromptVisible, upgradePromptVisible,
upgradeReason, upgradeReason,
loginStatus,
}: UseChatMessageLimitBannerInput): ChatMessageLimitBannerView { }: UseChatMessageLimitBannerInput): ChatMessageLimitBannerView {
const navigator = useAppNavigator(); const navigator = useAppNavigator();
const isVip = useUserSelector((state) => state.context.isVip); const isVip = useUserSelector((state) => state.context.isVip);
const view = getInsufficientCreditsMessageLimitView(loginStatus); const view = getInsufficientCreditsMessageLimitView();
const visible = shouldShowMessageLimitBanner({ const visible = shouldShowMessageLimitBanner({
upgradePromptVisible, upgradePromptVisible,
upgradeReason, upgradeReason,
@@ -59,11 +55,6 @@ export function useChatMessageLimitBanner({
}, [isVip, visible]); }, [isVip, visible]);
function unlock(): void { function unlock(): void {
if (view.action === "auth") {
navigator.openAuth(ROUTES.chat);
return;
}
navigator.openSubscription({ navigator.openSubscription({
type: getInsufficientCreditsSubscriptionType(isVip), type: getInsufficientCreditsSubscriptionType(isVip),
returnTo: "chat", returnTo: "chat",