Merge branch 'dev' into test
This commit is contained in:
@@ -0,0 +1,80 @@
|
|||||||
|
import type { PaymentPlan } from "@/data/dto/payment";
|
||||||
|
|
||||||
|
import type { SubscriptionPlanView } from "./components/subscription-plan-card";
|
||||||
|
|
||||||
|
export type SubscriptionType = "vip" | "voice";
|
||||||
|
|
||||||
|
export const SUBSCRIPTION_BANNER_TITLES: Record<SubscriptionType, string> = {
|
||||||
|
vip: "Subscribe to become the VIP Member",
|
||||||
|
voice: "Buy Voice Message Package",
|
||||||
|
};
|
||||||
|
|
||||||
|
function isVipPlan(plan: PaymentPlan): boolean {
|
||||||
|
return plan.orderType.startsWith("vip_") || plan.planId.startsWith("vip_");
|
||||||
|
}
|
||||||
|
|
||||||
|
function isVoicePackagePlan(plan: PaymentPlan): boolean {
|
||||||
|
const orderType = plan.orderType.toLowerCase();
|
||||||
|
const planId = plan.planId.toLowerCase();
|
||||||
|
return (
|
||||||
|
plan.dolAmount !== null ||
|
||||||
|
orderType.startsWith("dol_") ||
|
||||||
|
planId.startsWith("dol_") ||
|
||||||
|
orderType.includes("voice") ||
|
||||||
|
planId.includes("voice")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isPlanForType(
|
||||||
|
plan: PaymentPlan,
|
||||||
|
subscriptionType: SubscriptionType,
|
||||||
|
): boolean {
|
||||||
|
return subscriptionType === "voice"
|
||||||
|
? isVoicePackagePlan(plan)
|
||||||
|
: isVipPlan(plan);
|
||||||
|
}
|
||||||
|
|
||||||
|
function currencySymbol(currency: string): string {
|
||||||
|
if (currency === "CNY") return "¥";
|
||||||
|
if (currency === "USD") return "$";
|
||||||
|
return `${currency} `;
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAmount(amountCents: number | null): string | null {
|
||||||
|
if (amountCents === null) return null;
|
||||||
|
return (amountCents / 100).toFixed(2).replace(/\.00$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
function planCaption(
|
||||||
|
plan: PaymentPlan,
|
||||||
|
subscriptionType: SubscriptionType,
|
||||||
|
): string {
|
||||||
|
if (subscriptionType === "voice") {
|
||||||
|
return plan.dolAmount === null
|
||||||
|
? "Voice package"
|
||||||
|
: `${plan.dolAmount} voice messages`;
|
||||||
|
}
|
||||||
|
if (plan.dailyPriceCents !== null) {
|
||||||
|
const dailyPrice = formatAmount(plan.dailyPriceCents);
|
||||||
|
return dailyPrice === null
|
||||||
|
? "Lifetime"
|
||||||
|
: `${currencySymbol(plan.currency)}${dailyPrice}/day`;
|
||||||
|
}
|
||||||
|
if (plan.vipDays === null) return "Lifetime";
|
||||||
|
const fallbackDailyPrice = plan.amountCents / Math.max(plan.vipDays, 1);
|
||||||
|
return `${currencySymbol(plan.currency)}${formatAmount(fallbackDailyPrice)}/day`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function toPlanView(
|
||||||
|
plan: PaymentPlan,
|
||||||
|
subscriptionType: SubscriptionType,
|
||||||
|
): SubscriptionPlanView {
|
||||||
|
return {
|
||||||
|
id: plan.planId,
|
||||||
|
name: plan.planName,
|
||||||
|
price: formatAmount(plan.amountCents) ?? "",
|
||||||
|
originalPrice: formatAmount(plan.originalAmountCents),
|
||||||
|
perDay: planCaption(plan, subscriptionType),
|
||||||
|
currencySymbol: currencySymbol(plan.currency),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -18,7 +18,6 @@ import { useEffect, useMemo, useRef } from "react";
|
|||||||
|
|
||||||
import { Checkbox, MobileShell } from "@/app/_components/core";
|
import { Checkbox, MobileShell } from "@/app/_components/core";
|
||||||
import { AppConstants } from "@/core/app_constants";
|
import { AppConstants } from "@/core/app_constants";
|
||||||
import type { PaymentPlan } from "@/data/dto/payment";
|
|
||||||
import { VIP_BENEFITS } from "@/data/constants/vip-benefits";
|
import { VIP_BENEFITS } from "@/data/constants/vip-benefits";
|
||||||
import { PendingPaymentOrderStorage } from "@/data/storage";
|
import { PendingPaymentOrderStorage } from "@/data/storage";
|
||||||
import {
|
import {
|
||||||
@@ -37,85 +36,15 @@ import {
|
|||||||
SubscriptionUserRow,
|
SubscriptionUserRow,
|
||||||
} from "./components";
|
} from "./components";
|
||||||
import styles from "./components/subscription-screen.module.css";
|
import styles from "./components/subscription-screen.module.css";
|
||||||
import type { SubscriptionPlanView } from "./components/subscription-plan-card";
|
import {
|
||||||
|
SUBSCRIPTION_BANNER_TITLES,
|
||||||
|
isPlanForType,
|
||||||
|
toPlanView,
|
||||||
|
type SubscriptionType,
|
||||||
|
} from "./subscription-screen.helpers";
|
||||||
|
|
||||||
const FALLBACK_USERNAME = "User name";
|
const FALLBACK_USERNAME = "User name";
|
||||||
export type SubscriptionType = "vip" | "voice";
|
export type { SubscriptionType } from "./subscription-screen.helpers";
|
||||||
|
|
||||||
const SUBSCRIPTION_BANNER_TITLES: Record<SubscriptionType, string> = {
|
|
||||||
vip: "Subscribe to become the VIP Member",
|
|
||||||
voice: "Buy Voice Message Package",
|
|
||||||
};
|
|
||||||
|
|
||||||
function isVipPlan(plan: PaymentPlan): boolean {
|
|
||||||
return plan.orderType.startsWith("vip_") || plan.planId.startsWith("vip_");
|
|
||||||
}
|
|
||||||
|
|
||||||
function isVoicePackagePlan(plan: PaymentPlan): boolean {
|
|
||||||
const orderType = plan.orderType.toLowerCase();
|
|
||||||
const planId = plan.planId.toLowerCase();
|
|
||||||
return (
|
|
||||||
plan.dolAmount !== null ||
|
|
||||||
orderType.startsWith("dol_") ||
|
|
||||||
planId.startsWith("dol_") ||
|
|
||||||
orderType.includes("voice") ||
|
|
||||||
planId.includes("voice")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPlanForType(
|
|
||||||
plan: PaymentPlan,
|
|
||||||
subscriptionType: SubscriptionType,
|
|
||||||
): boolean {
|
|
||||||
return subscriptionType === "voice"
|
|
||||||
? isVoicePackagePlan(plan)
|
|
||||||
: isVipPlan(plan);
|
|
||||||
}
|
|
||||||
|
|
||||||
function currencySymbol(currency: string): string {
|
|
||||||
if (currency === "CNY") return "¥";
|
|
||||||
if (currency === "USD") return "$";
|
|
||||||
return `${currency} `;
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatAmount(amountCents: number | null): string | null {
|
|
||||||
if (amountCents === null) return null;
|
|
||||||
return (amountCents / 100).toFixed(2).replace(/\.00$/, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
function planCaption(
|
|
||||||
plan: PaymentPlan,
|
|
||||||
subscriptionType: SubscriptionType,
|
|
||||||
): string {
|
|
||||||
if (subscriptionType === "voice") {
|
|
||||||
return plan.dolAmount === null
|
|
||||||
? "Voice package"
|
|
||||||
: `${plan.dolAmount} voice messages`;
|
|
||||||
}
|
|
||||||
if (plan.dailyPriceCents !== null) {
|
|
||||||
const dailyPrice = formatAmount(plan.dailyPriceCents);
|
|
||||||
return dailyPrice === null
|
|
||||||
? "Lifetime"
|
|
||||||
: `${currencySymbol(plan.currency)}${dailyPrice}/day`;
|
|
||||||
}
|
|
||||||
if (plan.vipDays === null) return "Lifetime";
|
|
||||||
const fallbackDailyPrice = plan.amountCents / Math.max(plan.vipDays, 1);
|
|
||||||
return `${currencySymbol(plan.currency)}${formatAmount(fallbackDailyPrice)}/day`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toPlanView(
|
|
||||||
plan: PaymentPlan,
|
|
||||||
subscriptionType: SubscriptionType,
|
|
||||||
): SubscriptionPlanView {
|
|
||||||
return {
|
|
||||||
id: plan.planId,
|
|
||||||
name: plan.planName,
|
|
||||||
price: formatAmount(plan.amountCents) ?? "",
|
|
||||||
originalPrice: formatAmount(plan.originalAmountCents),
|
|
||||||
perDay: planCaption(plan, subscriptionType),
|
|
||||||
currencySymbol: currencySymbol(plan.currency),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SubscriptionScreenProps {
|
export interface SubscriptionScreenProps {
|
||||||
subscriptionType?: SubscriptionType;
|
subscriptionType?: SubscriptionType;
|
||||||
|
|||||||
Reference in New Issue
Block a user