80 lines
2.3 KiB
TypeScript
80 lines
2.3 KiB
TypeScript
"use client";
|
||
/**
|
||
* ChatQuotaExhaustedBanner 游客配额耗尽横幅
|
||
*
|
||
* 何时显示:
|
||
* - 后端返回 lockDetail.reason="daily_limit" 且 showUpgrade=true
|
||
*
|
||
* 视觉规格(与设计稿对齐):
|
||
* - 粉→品红渐变背景(与 splash 渐变一致)
|
||
* - 大字号粉色/白色提示文案("The limit for free chat times has been reached today")
|
||
* - 粉→品红渐变 pill 按钮"Unlock your membership to continue" → 跳 /subscription
|
||
*
|
||
* 业务关系:
|
||
* - 与 `src/app/sidebar/components/vip-benefits-card.tsx` 的 "Activate VIP Membership" 行为一致
|
||
* - 与 `src/app/sidebar/components/voice-package-card.tsx` 的 "Buy Package" 行为一致
|
||
*
|
||
* 不做的事:
|
||
* - 不直接管理 state machine(chat 机器不感知 UI 层)
|
||
* - 不显示具体剩余分钟数(已用 total<=0 表达"耗尽"足够)
|
||
*/
|
||
import { useRouter } from "next/navigation";
|
||
|
||
import { ROUTES } from "@/router/routes";
|
||
|
||
import styles from "./chat-quota-exhausted-banner.module.css";
|
||
|
||
export interface ChatQuotaExhaustedBannerProps {
|
||
title?: string;
|
||
ctaLabel?: string;
|
||
/**
|
||
* 自定义点击回调(不传则默认 router.push(ROUTES.subscription))
|
||
* - 测试时可传 mock
|
||
* - 嵌入其他场景时(如 nested overlay)可传自定义
|
||
*/
|
||
onUnlock?: () => void;
|
||
}
|
||
|
||
export function ChatQuotaExhaustedBanner({
|
||
title = "The limit for free chat times\nhas been reached today",
|
||
ctaLabel = "Unlock your membership to continue",
|
||
onUnlock,
|
||
}: ChatQuotaExhaustedBannerProps) {
|
||
const router = useRouter();
|
||
const titleLines = title.split("\n");
|
||
|
||
const handleClick = () => {
|
||
if (onUnlock) {
|
||
onUnlock();
|
||
return;
|
||
}
|
||
router.push(`${ROUTES.subscription}?type=vip`);
|
||
};
|
||
|
||
return (
|
||
<div
|
||
className={styles.banner}
|
||
role="status"
|
||
aria-live="polite"
|
||
data-testid="chat-quota-exhausted-banner"
|
||
>
|
||
<p className={styles.text}>
|
||
{titleLines.map((line, index) => (
|
||
<span key={`${line}-${index}`}>
|
||
{line}
|
||
{index < titleLines.length - 1 ? <br /> : null}
|
||
</span>
|
||
))}
|
||
</p>
|
||
<button
|
||
type="button"
|
||
className={styles.cta}
|
||
onClick={handleClick}
|
||
aria-label={ctaLabel}
|
||
>
|
||
{ctaLabel}
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|