feat(chat): tailor insufficient credits guidance
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getInsufficientCreditsMessageLimitView } from "../chat-screen.helpers";
|
||||
|
||||
describe("getInsufficientCreditsMessageLimitView", () => {
|
||||
it("asks guests to log in for more free chats", () => {
|
||||
expect(getInsufficientCreditsMessageLimitView("guest")).toEqual({
|
||||
title: "Log in to get more free chats",
|
||||
description: "Free chats refresh every day. Log in to get more chances.",
|
||||
ctaLabel: "Log in 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. Log in to get more chances.",
|
||||
ctaLabel: "Log in to continue",
|
||||
action: "auth",
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["email", "facebook", "google", "apple"] as const)(
|
||||
"asks %s users to top up credits",
|
||||
(loginStatus) => {
|
||||
expect(getInsufficientCreditsMessageLimitView(loginStatus)).toEqual({
|
||||
title: "Insufficient credits\nTop up to continue chatting",
|
||||
description: "Free chats refresh every day. You can also top up credits now.",
|
||||
ctaLabel: "Top up credits to continue",
|
||||
action: "topup",
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -9,10 +9,39 @@ export interface ExternalBrowserPromptState {
|
||||
loginStatus: LoginStatus;
|
||||
}
|
||||
|
||||
export type InsufficientCreditsAction = "auth" | "topup";
|
||||
|
||||
export interface InsufficientCreditsMessageLimitView {
|
||||
title: string;
|
||||
description: string;
|
||||
ctaLabel: string;
|
||||
action: InsufficientCreditsAction;
|
||||
}
|
||||
|
||||
export function deriveIsGuest(loginStatus: LoginStatus): boolean {
|
||||
return loginStatus === "guest";
|
||||
}
|
||||
|
||||
export function getInsufficientCreditsMessageLimitView(
|
||||
loginStatus: LoginStatus,
|
||||
): InsufficientCreditsMessageLimitView {
|
||||
if (loginStatus === "guest" || loginStatus === "notLoggedIn") {
|
||||
return {
|
||||
title: "Log in to get more free chats",
|
||||
description: "Free chats refresh every day. Log in to get more chances.",
|
||||
ctaLabel: "Log in to continue",
|
||||
action: "auth",
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
title: "Insufficient credits",
|
||||
description: "Free chats refresh every day. You can also top up credits now.",
|
||||
ctaLabel: "Top up credits to continue",
|
||||
action: "topup",
|
||||
};
|
||||
}
|
||||
|
||||
export function isChatDevelopmentEnvironment(): boolean {
|
||||
return AppEnvUtil.isDevelopment();
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
} from "./components";
|
||||
import {
|
||||
deriveIsGuest,
|
||||
getInsufficientCreditsMessageLimitView,
|
||||
getInsufficientCreditsSubscriptionType,
|
||||
isChatDevelopmentEnvironment,
|
||||
shouldStartExternalBrowserPrompt,
|
||||
@@ -61,9 +62,9 @@ export function ChatScreen() {
|
||||
const showMessageLimitBanner =
|
||||
state.upgradePromptVisible &&
|
||||
state.upgradeReason === "insufficient_credits";
|
||||
const messageLimitTitle =
|
||||
"Insufficient credits\nTop up to continue chatting";
|
||||
const messageLimitCtaLabel = "Top up credits to continue";
|
||||
const messageLimitView = getInsufficientCreditsMessageLimitView(
|
||||
authState.loginStatus,
|
||||
);
|
||||
const firstRechargeOfferBanner = useFirstRechargeOfferBanner({
|
||||
historyLoaded: state.historyLoaded,
|
||||
loginStatus: authState.loginStatus,
|
||||
@@ -141,6 +142,11 @@ export function ChatScreen() {
|
||||
}
|
||||
|
||||
function handleMessageLimitUnlock(): void {
|
||||
if (messageLimitView.action === "auth") {
|
||||
navigator.openAuth(ROUTES.chat);
|
||||
return;
|
||||
}
|
||||
|
||||
navigator.openSubscription({
|
||||
type: getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||
returnTo: "chat",
|
||||
@@ -190,8 +196,9 @@ export function ChatScreen() {
|
||||
|
||||
{showMessageLimitBanner ? (
|
||||
<ChatInsufficientCreditsBanner
|
||||
title={messageLimitTitle}
|
||||
ctaLabel={messageLimitCtaLabel}
|
||||
title={messageLimitView.title}
|
||||
description={messageLimitView.description}
|
||||
ctaLabel={messageLimitView.ctaLabel}
|
||||
onUnlock={handleMessageLimitUnlock}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -25,11 +25,20 @@
|
||||
font-size: var(--responsive-card-title, 18px);
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
margin: 0 0 var(--spacing-lg, 16px);
|
||||
margin: 0 0 var(--spacing-sm, 8px);
|
||||
/* 与 splash 标题的粉色字保持视觉一致(白底渐变叠加层需要白字保证可读性) */
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
.description {
|
||||
max-width: min(100%, 360px);
|
||||
margin: 0 auto var(--spacing-lg, 16px);
|
||||
color: rgba(255, 255, 255, 0.88);
|
||||
font-size: var(--responsive-caption, 13px);
|
||||
font-weight: 600;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.cta {
|
||||
width: 100%;
|
||||
min-height: var(--responsive-control-height, 48px);
|
||||
|
||||
@@ -22,6 +22,7 @@ import styles from "./chat-insufficient-credits-banner.module.css";
|
||||
|
||||
export interface ChatInsufficientCreditsBannerProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
ctaLabel?: string;
|
||||
/**
|
||||
* 自定义点击回调(不传则默认走统一订阅导航)
|
||||
@@ -33,6 +34,7 @@ export interface ChatInsufficientCreditsBannerProps {
|
||||
|
||||
export function ChatInsufficientCreditsBanner({
|
||||
title = "Insufficient credits\nTop up to continue chatting",
|
||||
description,
|
||||
ctaLabel = "Top up credits to continue",
|
||||
onUnlock,
|
||||
}: ChatInsufficientCreditsBannerProps) {
|
||||
@@ -62,6 +64,9 @@ export function ChatInsufficientCreditsBanner({
|
||||
</span>
|
||||
))}
|
||||
</p>
|
||||
{description ? (
|
||||
<p className={styles.description}>{description}</p>
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
className={styles.cta}
|
||||
|
||||
Reference in New Issue
Block a user