diff --git a/src/app/chat/components/chat-quota-exhausted-banner.tsx b/src/app/chat/components/chat-quota-exhausted-banner.tsx
index 36d16c06..93358e59 100644
--- a/src/app/chat/components/chat-quota-exhausted-banner.tsx
+++ b/src/app/chat/components/chat-quota-exhausted-banner.tsx
@@ -12,7 +12,7 @@
*
* 业务关系:
* - 与 `src/app/sidebar/components/vip-benefits-card.tsx` 的 "Activate VIP Membership" 行为一致
- * - 与 `src/app/sidebar/components/voice-package-card.tsx` 的 "Buy Package" 行为一致
+ * - 与订阅页 VIP 入口行为一致
*
* 不做的事:
* - 不直接管理 state machine(chat 机器不感知 UI 层)
diff --git a/src/app/sidebar/components/index.ts b/src/app/sidebar/components/index.ts
index 04faf02b..2851c683 100644
--- a/src/app/sidebar/components/index.ts
+++ b/src/app/sidebar/components/index.ts
@@ -6,4 +6,3 @@ export * from "./back-bar";
export * from "./sidebar-wallet-card";
export * from "./user-header";
export * from "./vip-benefits-card";
-export * from "./voice-package-card";
diff --git a/src/app/sidebar/components/sidebar-wallet-card.module.css b/src/app/sidebar/components/sidebar-wallet-card.module.css
index 5950cc62..4c4d7208 100644
--- a/src/app/sidebar/components/sidebar-wallet-card.module.css
+++ b/src/app/sidebar/components/sidebar-wallet-card.module.css
@@ -98,26 +98,52 @@
transform: translateY(1px);
}
+.actionColumn {
+ display: flex;
+ flex: 0 0 auto;
+ flex-direction: column;
+ gap: 9px;
+ align-items: stretch;
+}
+
+.activateButton,
.topUpButton {
display: inline-flex;
min-width: 92px;
- min-height: 48px;
+ min-height: 44px;
align-items: center;
justify-content: center;
padding: 0 18px;
- border: 0;
+ border: 1px solid transparent;
border-radius: 18px;
+ cursor: pointer;
+ font: inherit;
+ font-size: 15px;
+ font-weight: 850;
+ letter-spacing: 0.1px;
+ transition: box-shadow 0.18s ease, opacity 0.18s ease, transform 0.18s ease,
+ background 0.18s ease;
+}
+
+.activateButton {
+ background: #ffffff;
+ color: #f657a0;
+ border-color: rgba(246, 87, 160, 0.28);
+ box-shadow: 0 10px 22px rgba(246, 87, 160, 0.12);
+}
+
+.topUpButton {
background:
linear-gradient(135deg, #f657a0 0%, #ff7ac3 100%),
#f657a0;
color: #ffffff;
- cursor: pointer;
- font: inherit;
- font-size: 16px;
- font-weight: 850;
- letter-spacing: 0.1px;
box-shadow: 0 12px 26px rgba(246, 87, 160, 0.28);
- transition: box-shadow 0.18s ease, opacity 0.18s ease, transform 0.18s ease;
+}
+
+.activateButton:hover {
+ background: #fff5fa;
+ box-shadow: 0 14px 28px rgba(246, 87, 160, 0.18);
+ transform: translateY(-1px);
}
.topUpButton:hover {
@@ -125,12 +151,14 @@
transform: translateY(-1px);
}
+.activateButton:active,
.topUpButton:active {
opacity: 0.92;
transform: translateY(1px);
}
.rulesButton:focus-visible,
+.activateButton:focus-visible,
.topUpButton:focus-visible {
outline: 2px solid #f657a0;
outline-offset: 3px;
@@ -142,11 +170,12 @@
padding: 16px;
}
+ .activateButton,
.topUpButton {
min-width: 78px;
- min-height: 44px;
+ min-height: 40px;
padding: 0 14px;
- font-size: 15px;
+ font-size: 14px;
}
.balanceValue {
diff --git a/src/app/sidebar/components/sidebar-wallet-card.tsx b/src/app/sidebar/components/sidebar-wallet-card.tsx
index c2ce1566..c0987eb9 100644
--- a/src/app/sidebar/components/sidebar-wallet-card.tsx
+++ b/src/app/sidebar/components/sidebar-wallet-card.tsx
@@ -6,6 +6,7 @@ import styles from "./sidebar-wallet-card.module.css";
export interface SidebarWalletCardProps {
creditBalance: number;
+ onActivateVip?: () => void;
onTopUp: () => void;
onRulesClick: () => void;
}
@@ -15,6 +16,7 @@ const formatCoins = (value: number): string =>
export function SidebarWalletCard({
creditBalance,
+ onActivateVip,
onTopUp,
onRulesClick,
}: SidebarWalletCardProps) {
@@ -42,9 +44,20 @@ export function SidebarWalletCard({
-
+
+ {onActivateVip ? (
+
+ ) : null}
+
+
);
}
diff --git a/src/app/sidebar/components/voice-package-card.module.css b/src/app/sidebar/components/voice-package-card.module.css
deleted file mode 100644
index 9d37891c..00000000
--- a/src/app/sidebar/components/voice-package-card.module.css
+++ /dev/null
@@ -1,136 +0,0 @@
-.card {
- background-image:
- linear-gradient(
- 180deg,
- rgba(250, 208, 198, 0.57) 0%,
- rgba(250, 176, 165, 0.57) 0%,
- rgba(250, 143, 132, 0.57) 0%,
- rgba(251, 176, 156, 0.57) 8%,
- rgba(251, 208, 180, 0.57) 16%,
- rgba(255, 255, 255, 0.57) 38%,
- rgba(255, 255, 255, 0.57) 100%
- ),
- linear-gradient(#ffffff, #ffffff);
- background-blend-mode: normal, normal;
- border: solid 1px #fbbcb6;
- border-radius: 21px;
- width: 100%;
- padding: var(--sidebar-card-padding-y, 18px)
- var(--sidebar-card-padding-right, 12px)
- var(--sidebar-card-padding-y, 18px)
- var(--sidebar-card-padding-x, 18px);
- box-sizing: border-box;
- overflow: hidden;
-}
-
-.content {
- display: flex;
- align-items: flex-start;
- justify-content: space-between;
- gap: var(--spacing-md, 12px);
-}
-
-.copy {
- display: flex;
- flex: 1 1 auto;
- min-width: 0;
- flex-direction: column;
- align-items: flex-start;
- gap: var(--spacing-sm, 8px);
-}
-
-.header {
- padding: 0;
-}
-
-.title {
- font-size: var(--font-responsive-xl, 20px);
- font-weight: 700;
- color: var(--color-accent, #f84d96);
-}
-
-.micIcon {
- flex: 0 0 auto;
- width: var(--sidebar-voice-image-size, 100px);
- height: var(--sidebar-voice-image-size, 100px);
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
-.micImage {
- display: block;
- width: var(--sidebar-voice-image-size, 100px);
- height: var(--sidebar-voice-image-size, 100px);
- object-fit: contain;
-}
-
-.usage {
- display: inline-flex;
- align-items: baseline;
- flex-wrap: wrap;
- margin: 0;
- padding: 0;
- font-size: var(--font-size-md, 14px);
- color: var(--color-text-secondary, #9e9e9e);
- line-height: 1.4;
-}
-
-.usedNumber {
- display: inline-flex;
- color: #fa438b;
- font-size: var(--font-responsive-number-lg, 30px);
- font-weight: 700;
- line-height: 1;
-}
-
-.totalNumber {
- display: inline-flex;
- color: #000000;
- font-size: var(--font-responsive-number-lg, 30px);
- font-weight: 700;
- line-height: 1;
-}
-
-.usageUnit {
- color: var(--color-text-secondary, #9e9e9e);
- display: inline-flex;
- font: 400 var(--font-responsive-lg, 18px) / 1 var(--font-sans);
-}
-
-.usageText {
- color: #adabab;
- font-size: var(--font-responsive-lg, 18px);
- font-weight: 400;
-}
-
-.footer {
- padding: 0;
-}
-
-.buyBtn {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- padding: var(--spacing-xs) var(--spacing-lg, 16px);
- border-radius: 22px;
- background-image:
- linear-gradient(91deg, #f67382 0%, #f66690 44%, #f657a0 100%),
- linear-gradient(#f69757, #f69757);
- background-blend-mode: normal, normal;
- color: var(--color-text-primary, #ffffff);
- font-size: var(--font-responsive-md, 16px);
- font-weight: 600;
- border: 0;
- cursor: pointer;
- box-sizing: border-box;
-}
-
-.buyBtn:hover {
- opacity: 0.9;
-}
-
-.buyBtn:focus-visible {
- outline: 2px solid var(--color-accent, #f84d96);
- outline-offset: 2px;
-}
diff --git a/src/app/sidebar/components/voice-package-card.tsx b/src/app/sidebar/components/voice-package-card.tsx
deleted file mode 100644
index 640c5835..00000000
--- a/src/app/sidebar/components/voice-package-card.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-"use client";
-
-import Image from "next/image";
-
-import styles from "./voice-package-card.module.css";
-
-export interface VoicePackageCardProps {
- /** 已使用分钟数 */
- usedMinutes: number;
- /** 总分钟数 */
- totalMinutes: number;
- /** "Buy Package" 按钮点击 */
- onBuyPackage: () => void;
-}
-
-/**
- * Voice Message Package 卡片
- *
- * 视觉规格(与设计稿对齐):
- * - 白色卡片 + 粉色→橙色渐变描边
- * - 标题 "Voice Message Package"(粉色加粗)
- * - "0min/0min used/remaining" 文案(前缀粉色加粗)
- * - 底部 "Buy Package" 粉色胶囊按钮
- */
-export function VoicePackageCard({
- usedMinutes,
- totalMinutes,
- onBuyPackage,
-}: VoicePackageCardProps) {
- return (
-
-
-
-
- Voice Message Package
-
-
-
- {usedMinutes}
- min/
- {totalMinutes}
- min
- {"\u00a0\u00a0"}used/remaining
-
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/src/app/sidebar/sidebar-screen.tsx b/src/app/sidebar/sidebar-screen.tsx
index a9bd35cf..2097db7e 100644
--- a/src/app/sidebar/sidebar-screen.tsx
+++ b/src/app/sidebar/sidebar-screen.tsx
@@ -20,23 +20,8 @@ import styles from "./components/sidebar-screen.module.css";
const FALLBACK_USERNAME = "User name";
const VIP_SUBSCRIPTION_URL = `${ROUTES.subscription}?type=vip`;
+const TOP_UP_SUBSCRIPTION_URL = `${ROUTES.subscription}?type=topup`;
-const STATUS_LABELS: Record = {
- guest: "Sign in to keep your coins and unlock more features.",
- member: "Manage your wallet, subscription and account settings.",
- vip: "Your VIP benefits are active. Enjoy the full experience.",
-};
-
-/**
- * 侧边栏主屏幕
- *
- * 根据 `loginStatus + currentUser.isVip` 派生三种状态:
- * - guest:未登录
- * - member:已登录未购买 VIP
- * - vip:已登录且购买 VIP
- *
- *
- */
export function SidebarScreen() {
const router = useRouter();
const user = useUserState();
@@ -74,12 +59,6 @@ export function SidebarScreen() {
-
- Cozsweet
- Account
- {STATUS_LABELS[sidebarState]}
-
-
router.push(VIP_SUBSCRIPTION_URL)}
+ onActivateVip={
+ sidebarState === "vip"
+ ? undefined
+ : () => router.push(VIP_SUBSCRIPTION_URL)
+ }
+ onTopUp={() => router.push(TOP_UP_SUBSCRIPTION_URL)}
onRulesClick={() => router.push(ROUTES.coinsRules)}
/>
- {/*
-
- router.push(VOICE_SUBSCRIPTION_URL)}
- />
-
- */}
-
{sidebarState !== "guest" ? (
Other
diff --git a/src/app/subscription/components/subscription-checkout-button.tsx b/src/app/subscription/components/subscription-checkout-button.tsx
index 90444f36..b71d3c27 100644
--- a/src/app/subscription/components/subscription-checkout-button.tsx
+++ b/src/app/subscription/components/subscription-checkout-button.tsx
@@ -23,7 +23,7 @@ const log = new Logger("SubscriptionCheckoutButton");
export interface SubscriptionCheckoutButtonProps {
/** 是否可用(未选套餐 / 未勾选协议 → false) */
disabled?: boolean;
- subscriptionType: "vip" | "voice";
+ subscriptionType: "vip" | "topup";
returnTo?: "chat" | null;
}
@@ -97,13 +97,19 @@ export function SubscriptionCheckoutButton({
const isLoading =
payment.isCreatingOrder ||
payment.isPollingOrder;
+ const readyLabel =
+ subscriptionType === "topup" ? "Pay and Top Up" : "Pay and Activiate";
+ const agreementLabel =
+ subscriptionType === "topup"
+ ? "Confirm the agreement and Top Up"
+ : "Confirm the agreement and Activate";
const label = payment.isPollingOrder
? "Processing payment..."
: payment.isCreatingOrder
? "Creating order..."
: payment.agreed
- ? "Pay and Activiate"
- : "Confirm the agreement and Activate";
+ ? readyLabel
+ : agreementLabel;
const handleClick = () => {
if (disabled || isLoading) return;
@@ -244,7 +250,7 @@ function isEzpayPayment(payParams: Record): boolean {
interface RedirectToEzpayInput {
orderId: string | null;
paymentUrl: string;
- subscriptionType: "vip" | "voice";
+ subscriptionType: "vip" | "topup";
returnTo?: "chat" | null;
onFailed: (errorMessage: string) => void;
}
diff --git a/src/app/subscription/components/subscription-payment-success-dialog.tsx b/src/app/subscription/components/subscription-payment-success-dialog.tsx
index 8e827f20..67f45240 100644
--- a/src/app/subscription/components/subscription-payment-success-dialog.tsx
+++ b/src/app/subscription/components/subscription-payment-success-dialog.tsx
@@ -4,7 +4,7 @@ import styles from "./subscription-payment-success-dialog.module.css";
export interface SubscriptionPaymentSuccessDialogProps {
open: boolean;
- subscriptionType: "vip" | "voice";
+ subscriptionType: "vip" | "topup";
onClose: () => void;
}
@@ -16,8 +16,8 @@ export function SubscriptionPaymentSuccessDialog({
if (!open) return null;
const content =
- subscriptionType === "voice"
- ? "Your voice package purchase was completed successfully."
+ subscriptionType === "topup"
+ ? "Your coins have been added to your wallet successfully."
: "Your VIP membership has been activated successfully.";
return (
diff --git a/src/app/subscription/return/page.tsx b/src/app/subscription/return/page.tsx
index 405b02cb..50d2d2f4 100644
--- a/src/app/subscription/return/page.tsx
+++ b/src/app/subscription/return/page.tsx
@@ -10,7 +10,7 @@ import { ROUTES } from "@/router/routes";
type ReturnStatus = "loading" | "missing";
function buildSubscriptionUrl(
- subscriptionType: "vip" | "voice",
+ subscriptionType: "vip" | "topup",
returnTo?: "chat",
): string {
const params = new URLSearchParams({
diff --git a/src/app/subscription/subscription-page-client.tsx b/src/app/subscription/subscription-page-client.tsx
index 76322519..f6b1faf9 100644
--- a/src/app/subscription/subscription-page-client.tsx
+++ b/src/app/subscription/subscription-page-client.tsx
@@ -8,7 +8,7 @@ import {
} from "./subscription-screen";
function toSubscriptionType(value: string | null): SubscriptionType {
- return value === "voice" ? "voice" : "vip";
+ return value === "topup" ? "topup" : "vip";
}
function toReturnTo(value: string | null): "chat" | null {
diff --git a/src/app/subscription/subscription-screen.helpers.ts b/src/app/subscription/subscription-screen.helpers.ts
index c122144a..3e8d4225 100644
--- a/src/app/subscription/subscription-screen.helpers.ts
+++ b/src/app/subscription/subscription-screen.helpers.ts
@@ -1,15 +1,9 @@
import type { PaymentPlan } from "@/data/dto/payment";
import type { CoinsOfferPlanView } from "./components/subscription-coins-offer-section";
-import type { SubscriptionPlanView } from "./components/subscription-plan-card";
import type { VipOfferPlanView } from "./components/subscription-vip-offer-section";
-export type SubscriptionType = "vip" | "voice";
-
-export const SUBSCRIPTION_BANNER_TITLES: Record = {
- vip: "Subscribe to become the VIP Member",
- voice: "Buy Voice Message Package",
-};
+export type SubscriptionType = "vip" | "topup";
export function isVipPlan(plan: PaymentPlan): boolean {
return plan.vipDays !== null;
@@ -19,32 +13,6 @@ export function isCreditPlan(plan: PaymentPlan): boolean {
return plan.dolAmount !== null;
}
-function isVoicePackagePlan(plan: PaymentPlan): boolean {
- const orderType = plan.orderType.toLowerCase();
- const planId = plan.planId.toLowerCase();
- return (
- isCreditPlan(plan) ||
- 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 {
- const normalized = currency.toUpperCase();
- if (normalized === "CNY") return "¥";
- if (normalized === "USD") return "$";
- return `${currency} `;
-}
-
function formatAmount(amountCents: number | null): string | null {
if (amountCents === null) return null;
if (!Number.isFinite(amountCents)) return null;
@@ -78,40 +46,6 @@ function vipOfferTitle(plan: PaymentPlan): string {
return plan.planName;
}
-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),
- };
-}
-
export function toVipOfferPlanView(plan: PaymentPlan): VipOfferPlanView {
return {
id: plan.planId,
diff --git a/src/app/subscription/subscription-screen.tsx b/src/app/subscription/subscription-screen.tsx
index 3ba92e55..ca9ef45f 100644
--- a/src/app/subscription/subscription-screen.tsx
+++ b/src/app/subscription/subscription-screen.tsx
@@ -9,33 +9,26 @@ import {
usePaymentDispatch,
usePaymentState,
} from "@/stores/payment/payment-context";
-import { useUserDispatch, useUserState } from "@/stores/user/user-context";
+import { useUserDispatch } from "@/stores/user/user-context";
import { ROUTES } from "@/router/routes";
import {
SubscriptionBackLink,
- SubscriptionBanner,
SubscriptionCheckoutButton,
SubscriptionCoinsOfferSection,
SubscriptionPaymentMethod,
SubscriptionPaymentSuccessDialog,
- SubscriptionPlanCard,
- SubscriptionUserRow,
SubscriptionVipOfferSection,
} from "./components";
import styles from "./components/subscription-screen.module.css";
import {
- SUBSCRIPTION_BANNER_TITLES,
isCreditPlan,
- isPlanForType,
isVipPlan,
toCoinsOfferPlanView,
- toPlanView,
toVipOfferPlanView,
type SubscriptionType,
} from "./subscription-screen.helpers";
-const FALLBACK_USERNAME = "User name";
export type { SubscriptionType } from "./subscription-screen.helpers";
export interface SubscriptionScreenProps {
@@ -50,7 +43,6 @@ export function SubscriptionScreen({
returnTo = null,
}: SubscriptionScreenProps) {
const router = useRouter();
- const user = useUserState();
const userDispatch = useUserDispatch();
const payment = usePaymentState();
const paymentDispatch = usePaymentDispatch();
@@ -59,7 +51,7 @@ export function SubscriptionScreen({
const successDialogShownOrderRef = useRef(null);
const [showPaymentSuccessDialog, setShowPaymentSuccessDialog] =
useState(false);
- const isVipSubscription = subscriptionType === "vip";
+ const canSubscribeVip = subscriptionType === "vip";
useEffect(() => {
if (payment.status === "idle") {
@@ -139,14 +131,6 @@ export function SubscriptionScreen({
void PendingPaymentOrderStorage.clearPendingOrder();
}, [payment.currentOrderId, payment.isPaid, payment.status]);
- const plans = useMemo(
- () =>
- payment.plans
- .filter((plan) => isPlanForType(plan, subscriptionType))
- .slice(0, 3)
- .map((plan) => toPlanView(plan, subscriptionType)),
- [payment.plans, subscriptionType],
- );
const vipOfferPlans = useMemo(
() =>
payment.plans
@@ -161,28 +145,18 @@ export function SubscriptionScreen({
);
const selectedPlan =
- (isVipSubscription
+ (canSubscribeVip
? [...vipOfferPlans, ...directCoinsPlans].find(
(plan) => plan.id === payment.selectedPlanId,
)
- : plans.find((plan) => plan.id === payment.selectedPlanId)) ?? null;
+ : directCoinsPlans.find((plan) => plan.id === payment.selectedPlanId)) ??
+ null;
const isPaymentBusy =
payment.isCreatingOrder ||
payment.isPollingOrder;
const canActivate =
selectedPlan !== null && payment.agreed && !isPaymentBusy;
- const name = user.currentUser?.username ?? FALLBACK_USERNAME;
- const avatarUrl = user.avatarUrl ?? user.currentUser?.avatarUrl ?? null;
- const plansStatusMessage =
- subscriptionType === "voice"
- ? "Voice packages are unavailable."
- : "Membership plans are unavailable.";
- const plansAriaLabel =
- subscriptionType === "voice"
- ? "Choose a voice package"
- : "Choose a subscription plan";
-
const finishPaymentSuccessClose = () => {
setShowPaymentSuccessDialog(false);
paymentDispatch({ type: "PaymentReset" });
@@ -196,7 +170,7 @@ export function SubscriptionScreen({
};
useEffect(() => {
- if (isVipSubscription) {
+ if (canSubscribeVip) {
const firstVipPlanId = vipOfferPlans[0]?.id ?? "";
const hasSelectedVipPlan = vipOfferPlans.some(
(plan) => plan.id === payment.selectedPlanId,
@@ -219,28 +193,21 @@ export function SubscriptionScreen({
return;
}
- if (payment.isLoadingPlans || payment.plans.length === 0) return;
+ if (payment.isLoadingPlans || directCoinsPlans.length === 0) return;
if (selectedPlan !== null) return;
- const nextPlan = payment.plans.find((plan) =>
- isPlanForType(plan, subscriptionType),
- );
- if (!nextPlan) return;
-
paymentDispatch({
type: "PaymentPlanSelected",
- planId: nextPlan.planId,
+ planId: directCoinsPlans[0]?.id ?? "",
});
}, [
+ canSubscribeVip,
+ directCoinsPlans,
payment.isLoadingPlans,
- payment.plans,
paymentDispatch,
payment.selectedPlanId,
payment.status,
- directCoinsPlans,
- isVipSubscription,
selectedPlan,
- subscriptionType,
vipOfferPlans,
]);
@@ -251,8 +218,8 @@ export function SubscriptionScreen({
- {isVipSubscription ? (
-
+
+ {canSubscribeVip ? (
-
- paymentDispatch({
- type: "PaymentPlanSelected",
- planId,
- })
- }
- />
-
- ) : (
- <>
-
-
-
-
-
- {plans.length > 0 ? (
- plans.map((plan, index) => (
-
- paymentDispatch({
- type: "PaymentPlanSelected",
- planId: plan.id,
- })
- }
- />
- ))
- ) : (
-
- {payment.isLoadingPlans
- ? "Loading membership plans..."
- : payment.errorMessage ?? plansStatusMessage}
-
- )}
-
- >
- )}
+ ) : null}
+
+ paymentDispatch({
+ type: "PaymentPlanSelected",
+ planId,
+ })
+ }
+ />
+
- Welcome to VIP. Enjoy unlimited chat, free voice minutes, and
- more.
+ Welcome to VIP. Enjoy unlimited chat and more premium benefits.