93 lines
3.5 KiB
TypeScript
93 lines
3.5 KiB
TypeScript
"use client";
|
||
/**
|
||
* ChatInsufficientCreditsBanner 积分不足横幅
|
||
*
|
||
* 何时显示:
|
||
* - 后端返回 canSendMessage=false
|
||
*
|
||
* 视觉规格(与设计稿对齐):
|
||
* - 粉→品红渐变背景(与 splash 渐变一致)
|
||
* - 大字号粉色/白色提示文案
|
||
* - 粉→品红渐变 pill 按钮 → 跳 /subscription?type=topup
|
||
*
|
||
* 业务关系:
|
||
* - 与订阅页 top up 入口行为一致
|
||
*
|
||
* 不做的事:
|
||
* - 不直接管理 state machine(chat 机器不感知 UI 层)
|
||
*/
|
||
import { useAppNavigator } from "@/router/use-app-navigator";
|
||
import type { PaymentGuidance } from "@/data/schemas/chat";
|
||
|
||
export interface ChatInsufficientCreditsBannerProps {
|
||
title?: string;
|
||
description?: string;
|
||
ctaLabel?: string | null;
|
||
guidance?: PaymentGuidance | null;
|
||
/**
|
||
* 自定义点击回调(不传则默认走统一订阅导航)
|
||
* - 测试时可传 mock
|
||
* - 嵌入其他场景时(如 nested overlay)可传自定义
|
||
*/
|
||
onUnlock?: () => void;
|
||
}
|
||
|
||
export function ChatInsufficientCreditsBanner({
|
||
title = "Insufficient credits\nTop up to continue chatting",
|
||
description,
|
||
ctaLabel = "Top up credits to continue",
|
||
guidance = null,
|
||
onUnlock,
|
||
}: ChatInsufficientCreditsBannerProps) {
|
||
const navigator = useAppNavigator();
|
||
const activeGuidance = guidance;
|
||
const titleLines = title.split("\n");
|
||
|
||
const handleClick = () => {
|
||
if (onUnlock) {
|
||
onUnlock();
|
||
return;
|
||
}
|
||
navigator.openSubscription({ type: "topup", returnTo: "chat" });
|
||
};
|
||
|
||
return (
|
||
<div
|
||
className="mx-(--chat-inline-padding,16px) mb-(--page-section-gap,16px) shrink-0 rounded-(--responsive-card-radius-sm,24px) bg-[linear-gradient(135deg,#f96ade_0%,#f657a0_100%)] px-(--responsive-card-padding,16px) py-(--page-section-gap-lg,24px) text-center shadow-[0_4px_16px_rgba(248,77,150,0.25)]"
|
||
role="status"
|
||
aria-live="polite"
|
||
data-testid="chat-insufficient-credits-banner"
|
||
data-payment-guidance-id={activeGuidance?.guidanceId}
|
||
>
|
||
<p className="m-0 mb-(--spacing-sm,8px) text-(length:--responsive-card-title,18px) font-semibold leading-[1.4] text-white opacity-95">
|
||
{titleLines.map((line, index) => (
|
||
<span key={`${line}-${index}`}>
|
||
{line}
|
||
{index < titleLines.length - 1 ? <br /> : null}
|
||
</span>
|
||
))}
|
||
</p>
|
||
{description ? (
|
||
<p className="mx-auto mb-(--spacing-lg,16px) mt-0 max-w-[min(100%,360px)] text-(length:--responsive-caption,14px) font-semibold leading-[1.4] text-[rgba(255,255,255,0.88)]">
|
||
{description}
|
||
</p>
|
||
) : null}
|
||
{activeGuidance?.copy ? (
|
||
<p className="mx-auto mb-(--spacing-lg,16px) mt-0 max-w-[min(100%,360px)] text-(length:--responsive-caption,14px) font-semibold leading-[1.4] text-white">
|
||
{activeGuidance.copy}
|
||
</p>
|
||
) : null}
|
||
{ctaLabel ? <button
|
||
type="button"
|
||
data-analytics-key="chat.topup"
|
||
data-analytics-label="Top up chat credits"
|
||
className="min-h-(--responsive-control-height,48px) w-full cursor-pointer rounded-full border-0 bg-[linear-gradient(135deg,#ffb8e0_0%,#f57ec0_100%)] px-(--responsive-card-padding-lg,24px) text-(length:--responsive-card-title,18px) font-bold text-white shadow-[0_2px_8px_rgba(0,0,0,0.15)] transition-[box-shadow,transform] duration-150 hover:shadow-[0_4px_12px_rgba(0,0,0,0.2)] active:scale-98"
|
||
onClick={handleClick}
|
||
aria-label={ctaLabel}
|
||
>
|
||
{ctaLabel}
|
||
</button> : null}
|
||
</div>
|
||
);
|
||
}
|