Merge branch 'dev' into test

This commit is contained in:
2026-06-25 17:40:41 +08:00
4 changed files with 50 additions and 24 deletions
+5 -5
View File
@@ -5,18 +5,18 @@
* 流程:
* 1. 用户在支付服务托管页完成结账
* 2. 支付服务重定向到 `/subscription/success`
* 3. 这个页面显示 "购买成功" + 调 `getCurrentUser` 刷新 user
* 3. 这个页面显示 "购买成功" + 调 VIP 状态接口确认权益
* 4. 点 "返回订阅页" 跳回 `/subscription`
*
* 注意:权益发放由后端支付服务链路异步完成。
* 此页面轮询几次 getCurrentUser 等待 user 状态完成更新。
* 此页面轮询几次 VIP 状态接口等待权益完成更新。
*/
import { useEffect, useRef, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { MobileShell } from "@/app/_components/core/mobile-shell";
import { authRepository } from "@/data/repositories/auth_repository";
import { paymentRepository } from "@/data/repositories/payment_repository";
import { ROUTES } from "@/router/routes";
export default function SubscriptionSuccessPage() {
@@ -25,7 +25,7 @@ export default function SubscriptionSuccessPage() {
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const hasRefreshed = useRef(false);
// 轮询 getCurrentUser 等待后端权益发放完成
// 轮询 VIP 状态接口等待后端权益发放完成
useEffect(() => {
if (hasRefreshed.current) return;
hasRefreshed.current = true;
@@ -33,7 +33,7 @@ export default function SubscriptionSuccessPage() {
let cancelled = false;
const tryRefresh = async (attemptsLeft: number) => {
if (cancelled) return;
const r = await authRepository.getCurrentUser();
const r = await paymentRepository.getVipStatus();
if (cancelled) return;
if (r.success) {
if (r.data.isVip) {
+45
View File
@@ -0,0 +1,45 @@
import { describe, expect, it } from "vitest";
import { User } from "@/data/dto/user/user";
describe("User", () => {
it("parses the latest backend user payload", () => {
const user = User.fromJson({
id: "d7bb2b3d-e9c5-474c-85fe-f503048f30dd",
username: "guest_bbc90a38",
email: "device_bbc90a38b996187348f54cf2cb0b789e@guest.local",
platform: "web",
intimacy: 0,
dolBalance: 0,
personalityTraits: {
caring: 0.5,
playful: 0.5,
serious: 0.5,
cheerful: 0.5,
romantic: 0.5,
},
preferredLanguage: "zh",
createdAt: "2026-06-12T08:08:27.378706+00:00",
lastMessageAt: "2026-06-24T11:00:23.156561+00:00",
});
expect(user.toJson()).toEqual({
id: "d7bb2b3d-e9c5-474c-85fe-f503048f30dd",
username: "guest_bbc90a38",
email: "device_bbc90a38b996187348f54cf2cb0b789e@guest.local",
platform: "web",
intimacy: 0,
dolBalance: 0,
personalityTraits: {
caring: 0.5,
playful: 0.5,
serious: 0.5,
cheerful: 0.5,
romantic: 0.5,
},
preferredLanguage: "zh",
createdAt: "2026-06-12T08:08:27.378706+00:00",
lastMessageAt: "2026-06-24T11:00:23.156561+00:00",
});
});
});
-9
View File
@@ -15,19 +15,10 @@ export class User {
declare readonly platform: string;
declare readonly intimacy: number;
declare readonly dolBalance: number;
declare readonly relationshipStage: string;
declare readonly currentMood: string;
declare readonly personalityTraits: PersonalityTraits;
declare readonly preferredLanguage: string;
declare readonly createdAt: string;
declare readonly lastMessageAt: string;
declare readonly avatarUrl: string;
declare readonly loginProvider: string;
declare readonly isGuest: boolean;
/** 是否为 VIP 会员 */
declare readonly isVip: boolean;
/** 语音聊天剩余分钟数 */
declare readonly voiceMinutesRemaining: number;
private constructor(input: UserInput) {
const data = UserSchema.parse(input);
-10
View File
@@ -5,7 +5,6 @@
import { z } from "zod";
import { PersonalityTraitsSchema, PERSONALITY_TRAITS_DEFAULTS } from "./personality_traits";
import {
booleanOrFalse,
numberOrZero,
stringOrEmpty,
} from "../nullable-defaults";
@@ -17,20 +16,11 @@ export const UserSchema = z.object({
platform: stringOrEmpty,
intimacy: numberOrZero,
dolBalance: numberOrZero,
relationshipStage: stringOrEmpty,
currentMood: stringOrEmpty,
personalityTraits: PersonalityTraitsSchema.default(PERSONALITY_TRAITS_DEFAULTS),
preferredLanguage: stringOrEmpty,
createdAt: stringOrEmpty,
// 后端新游客返回 null("还没发过消息"),前端统一收敛为空字符串。
lastMessageAt: stringOrEmpty,
avatarUrl: stringOrEmpty,
loginProvider: stringOrEmpty,
isGuest: booleanOrFalse,
/** 是否为 VIP 会员(订阅中或未过期) */
isVip: booleanOrFalse,
/** 语音聊天剩余分钟数(订阅生效后每天补充;用完为 0 不变) */
voiceMinutesRemaining: numberOrZero,
});
export type UserInput = z.input<typeof UserSchema>;