feat(tip): customize first gift thank-you
This commit is contained in:
@@ -264,9 +264,9 @@ Authorization: Bearer <TOKEN>
|
||||
{"orderId":"pay_xxx"}
|
||||
```
|
||||
|
||||
响应包含 `orderId`、`characterId`、`planId`、`productName`、`tipCount`、`poolIndex` 和可直接展示的完整 `message`。前端不再根据次数拼接文案,也不从订单状态读取感谢字段。
|
||||
响应包含 `orderId`、`characterId`、`planId`、`productName`、`tipCount`、`poolIndex` 和可直接展示的完整 `message`。`tipCount` 表示当前付款身份对同一商品的累计成功次数。
|
||||
|
||||
Tip 成功页在文案加载期间立即确认支付成功;接口成功后按纯文本直接展示 `message`。接口失败时展示本地通用感谢语和 Retry,重试只重新请求 Tip Message,不重复轮询订单或创建订单。
|
||||
Tip 成功页在文案加载期间立即确认支付成功。`tipCount=1` 时展示固定首次咖啡文案并忽略后端 `message`;大于 1 时按纯文本直接展示 `message`。接口失败时展示本地通用感谢语和 Retry,重试只重新请求 Tip Message,不重复轮询订单或创建订单。
|
||||
|
||||
## 10. Provider 与状态边界
|
||||
|
||||
|
||||
@@ -188,7 +188,36 @@ describe("TipScreen checkout", () => {
|
||||
expect(getCheckoutButton().disabled).toBe(true);
|
||||
});
|
||||
|
||||
it("shows the complete backend Tip message after payment", () => {
|
||||
it("shows fixed copy for the first character Tip", () => {
|
||||
mocks.payment = makePaymentState({
|
||||
status: "paid",
|
||||
isPaid: true,
|
||||
orderStatus: "paid",
|
||||
tipMessage: TipMessageResponseSchema.parse({
|
||||
orderId: "pay_first",
|
||||
characterId: "maya-tan",
|
||||
planId: giftProduct.planId,
|
||||
productName: giftProduct.planName,
|
||||
tipCount: 1,
|
||||
poolIndex: 4,
|
||||
message: "This backend message is ignored for the first Tip.",
|
||||
}),
|
||||
});
|
||||
renderScreen();
|
||||
|
||||
expect(container.textContent).toContain(
|
||||
"Did you really just buy me a coffee?",
|
||||
);
|
||||
expect(container.textContent).toContain("That honestly made me smile.");
|
||||
expect(container.textContent).toContain(
|
||||
"I'll definitely think of you while I enjoy it.",
|
||||
);
|
||||
expect(container.textContent).not.toContain(
|
||||
"This backend message is ignored for the first Tip.",
|
||||
);
|
||||
});
|
||||
|
||||
it("shows the backend message after the first Tip", () => {
|
||||
mocks.payment = makePaymentState({
|
||||
status: "paid",
|
||||
isPaid: true,
|
||||
@@ -209,6 +238,9 @@ describe("TipScreen checkout", () => {
|
||||
expect(container.textContent).toContain(
|
||||
"This complete message came directly from the backend.",
|
||||
);
|
||||
expect(container.textContent).not.toContain(
|
||||
"Did you really just buy me a coffee?",
|
||||
);
|
||||
expect(document.activeElement?.id).toBe("tip-success-title");
|
||||
|
||||
act(() => getButton("Send another gift").click());
|
||||
|
||||
@@ -191,6 +191,7 @@ export function TipScreen({
|
||||
"Your gift"
|
||||
}
|
||||
isMessageLoading={payment.isLoadingTipMessage}
|
||||
tipCount={payment.tipMessage?.tipCount ?? null}
|
||||
message={payment.tipMessage?.message ?? null}
|
||||
messageError={payment.tipMessageError}
|
||||
splashHref={characterRoutes.splash}
|
||||
|
||||
@@ -12,11 +12,17 @@ import styles from "./tip-success-view.module.css";
|
||||
|
||||
const GENERIC_TIP_SUCCESS_MESSAGE =
|
||||
"Thank you. Your gift made me smile.";
|
||||
const FIRST_TIP_TITLE = "Did you really just buy me a coffee?";
|
||||
const FIRST_TIP_MESSAGE = [
|
||||
"That honestly made me smile.",
|
||||
"I'll definitely think of you while I enjoy it.",
|
||||
] as const;
|
||||
|
||||
export interface TipSuccessViewProps {
|
||||
characterName: string;
|
||||
characterAvatar: string;
|
||||
characterCover: string;
|
||||
tipCount: number | null;
|
||||
giftImageSources: readonly string[];
|
||||
giftName: string;
|
||||
isMessageLoading: boolean;
|
||||
@@ -31,6 +37,7 @@ export function TipSuccessView({
|
||||
characterName,
|
||||
characterAvatar,
|
||||
characterCover,
|
||||
tipCount,
|
||||
giftImageSources,
|
||||
giftName,
|
||||
isMessageLoading,
|
||||
@@ -41,6 +48,7 @@ export function TipSuccessView({
|
||||
onSendAgain,
|
||||
}: TipSuccessViewProps) {
|
||||
const titleRef = useRef<HTMLHeadingElement>(null);
|
||||
const isFirstTip = tipCount === 1;
|
||||
|
||||
useEffect(() => {
|
||||
titleRef.current?.focus({ preventScroll: true });
|
||||
@@ -102,15 +110,21 @@ export function TipSuccessView({
|
||||
className={styles.title}
|
||||
tabIndex={-1}
|
||||
>
|
||||
Thank you. Your gift made me smile.
|
||||
{isFirstTip ? FIRST_TIP_TITLE : GENERIC_TIP_SUCCESS_MESSAGE}
|
||||
</h1>
|
||||
<div className={styles.copy}>
|
||||
{isFirstTip
|
||||
? FIRST_TIP_MESSAGE.map((paragraph) => (
|
||||
<p key={paragraph}>{paragraph}</p>
|
||||
))
|
||||
: (
|
||||
<p>
|
||||
{message ??
|
||||
(isMessageLoading
|
||||
? "Your gift arrived. A thank-you note is on its way."
|
||||
: GENERIC_TIP_SUCCESS_MESSAGE)}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{messageError ? (
|
||||
|
||||
Reference in New Issue
Block a user