diff --git a/src/app/subscription/subscription-screen.helpers.ts b/src/app/subscription/subscription-screen.helpers.ts new file mode 100644 index 00000000..2a520a0d --- /dev/null +++ b/src/app/subscription/subscription-screen.helpers.ts @@ -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 = { + 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), + }; +} diff --git a/src/app/subscription/subscription-screen.tsx b/src/app/subscription/subscription-screen.tsx index ba2179f4..471a71f0 100644 --- a/src/app/subscription/subscription-screen.tsx +++ b/src/app/subscription/subscription-screen.tsx @@ -18,7 +18,6 @@ import { useEffect, useMemo, useRef } from "react"; import { Checkbox, MobileShell } from "@/app/_components/core"; import { AppConstants } from "@/core/app_constants"; -import type { PaymentPlan } from "@/data/dto/payment"; import { VIP_BENEFITS } from "@/data/constants/vip-benefits"; import { PendingPaymentOrderStorage } from "@/data/storage"; import { @@ -37,85 +36,15 @@ import { SubscriptionUserRow, } from "./components"; 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"; -export type SubscriptionType = "vip" | "voice"; - -const SUBSCRIPTION_BANNER_TITLES: Record = { - 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 type { SubscriptionType } from "./subscription-screen.helpers"; export interface SubscriptionScreenProps { subscriptionType?: SubscriptionType;