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;
|
loginStatus: LoginStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type InsufficientCreditsAction = "auth" | "topup";
|
||||||
|
|
||||||
|
export interface InsufficientCreditsMessageLimitView {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
ctaLabel: string;
|
||||||
|
action: InsufficientCreditsAction;
|
||||||
|
}
|
||||||
|
|
||||||
export function deriveIsGuest(loginStatus: LoginStatus): boolean {
|
export function deriveIsGuest(loginStatus: LoginStatus): boolean {
|
||||||
return loginStatus === "guest";
|
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 {
|
export function isChatDevelopmentEnvironment(): boolean {
|
||||||
return AppEnvUtil.isDevelopment();
|
return AppEnvUtil.isDevelopment();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
} from "./components";
|
} from "./components";
|
||||||
import {
|
import {
|
||||||
deriveIsGuest,
|
deriveIsGuest,
|
||||||
|
getInsufficientCreditsMessageLimitView,
|
||||||
getInsufficientCreditsSubscriptionType,
|
getInsufficientCreditsSubscriptionType,
|
||||||
isChatDevelopmentEnvironment,
|
isChatDevelopmentEnvironment,
|
||||||
shouldStartExternalBrowserPrompt,
|
shouldStartExternalBrowserPrompt,
|
||||||
@@ -61,9 +62,9 @@ export function ChatScreen() {
|
|||||||
const showMessageLimitBanner =
|
const showMessageLimitBanner =
|
||||||
state.upgradePromptVisible &&
|
state.upgradePromptVisible &&
|
||||||
state.upgradeReason === "insufficient_credits";
|
state.upgradeReason === "insufficient_credits";
|
||||||
const messageLimitTitle =
|
const messageLimitView = getInsufficientCreditsMessageLimitView(
|
||||||
"Insufficient credits\nTop up to continue chatting";
|
authState.loginStatus,
|
||||||
const messageLimitCtaLabel = "Top up credits to continue";
|
);
|
||||||
const firstRechargeOfferBanner = useFirstRechargeOfferBanner({
|
const firstRechargeOfferBanner = useFirstRechargeOfferBanner({
|
||||||
historyLoaded: state.historyLoaded,
|
historyLoaded: state.historyLoaded,
|
||||||
loginStatus: authState.loginStatus,
|
loginStatus: authState.loginStatus,
|
||||||
@@ -141,6 +142,11 @@ export function ChatScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleMessageLimitUnlock(): void {
|
function handleMessageLimitUnlock(): void {
|
||||||
|
if (messageLimitView.action === "auth") {
|
||||||
|
navigator.openAuth(ROUTES.chat);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
navigator.openSubscription({
|
navigator.openSubscription({
|
||||||
type: getInsufficientCreditsSubscriptionType(userState.isVip),
|
type: getInsufficientCreditsSubscriptionType(userState.isVip),
|
||||||
returnTo: "chat",
|
returnTo: "chat",
|
||||||
@@ -190,8 +196,9 @@ export function ChatScreen() {
|
|||||||
|
|
||||||
{showMessageLimitBanner ? (
|
{showMessageLimitBanner ? (
|
||||||
<ChatInsufficientCreditsBanner
|
<ChatInsufficientCreditsBanner
|
||||||
title={messageLimitTitle}
|
title={messageLimitView.title}
|
||||||
ctaLabel={messageLimitCtaLabel}
|
description={messageLimitView.description}
|
||||||
|
ctaLabel={messageLimitView.ctaLabel}
|
||||||
onUnlock={handleMessageLimitUnlock}
|
onUnlock={handleMessageLimitUnlock}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -25,11 +25,20 @@
|
|||||||
font-size: var(--responsive-card-title, 18px);
|
font-size: var(--responsive-card-title, 18px);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
margin: 0 0 var(--spacing-lg, 16px);
|
margin: 0 0 var(--spacing-sm, 8px);
|
||||||
/* 与 splash 标题的粉色字保持视觉一致(白底渐变叠加层需要白字保证可读性) */
|
/* 与 splash 标题的粉色字保持视觉一致(白底渐变叠加层需要白字保证可读性) */
|
||||||
opacity: 0.95;
|
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 {
|
.cta {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: var(--responsive-control-height, 48px);
|
min-height: var(--responsive-control-height, 48px);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import styles from "./chat-insufficient-credits-banner.module.css";
|
|||||||
|
|
||||||
export interface ChatInsufficientCreditsBannerProps {
|
export interface ChatInsufficientCreditsBannerProps {
|
||||||
title?: string;
|
title?: string;
|
||||||
|
description?: string;
|
||||||
ctaLabel?: string;
|
ctaLabel?: string;
|
||||||
/**
|
/**
|
||||||
* 自定义点击回调(不传则默认走统一订阅导航)
|
* 自定义点击回调(不传则默认走统一订阅导航)
|
||||||
@@ -33,6 +34,7 @@ export interface ChatInsufficientCreditsBannerProps {
|
|||||||
|
|
||||||
export function ChatInsufficientCreditsBanner({
|
export function ChatInsufficientCreditsBanner({
|
||||||
title = "Insufficient credits\nTop up to continue chatting",
|
title = "Insufficient credits\nTop up to continue chatting",
|
||||||
|
description,
|
||||||
ctaLabel = "Top up credits to continue",
|
ctaLabel = "Top up credits to continue",
|
||||||
onUnlock,
|
onUnlock,
|
||||||
}: ChatInsufficientCreditsBannerProps) {
|
}: ChatInsufficientCreditsBannerProps) {
|
||||||
@@ -62,6 +64,9 @@ export function ChatInsufficientCreditsBanner({
|
|||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</p>
|
</p>
|
||||||
|
{description ? (
|
||||||
|
<p className={styles.description}>{description}</p>
|
||||||
|
) : null}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className={styles.cta}
|
className={styles.cta}
|
||||||
|
|||||||
Reference in New Issue
Block a user