Merge branch 'dev' into test
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user