feat(subscription): add VIP subscription page

Add a new /subscription route showing three VIP membership plans (Monthly,
Quarterly, Annual) with a pink→peach banner, per-plan gradient price bars,
a benefits card, a mock activate CTA, and an agreement checkbox.

- New reusable <Checkbox /> in src/app/_components/core for the legal
  agreement toggle
- Hardcoded SUBSCRIPTION_PLANS constant in src/data/constants
- New SubscriptionScreen composed of 7 colocated components (back link,
  user row, banner, plan card, benefits card, CTA button) with CSS
  Modules matching the existing design-token system
- Route registered as protected and reached from /sidebar "Membership"
- Mock CTA: window.alert + router.push(/chat) — no payment calls

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2026-06-15 17:48:41 +08:00
committed by chenhang
parent b9315c9f8b
commit e88a99626d
23 changed files with 1001 additions and 0 deletions
@@ -0,0 +1,61 @@
"use client";
/**
* 订阅页用户信息行
*
* 视觉规格(与设计稿对齐):
* - 56×56 粉色圆形头像
* - 头像右侧两行文本:用户名(16px/600)、副标题(12px/secondary
* - 无头像时显示默认用户图标
*/
import Image from "next/image";
import styles from "./subscription-user-row.module.css";
export interface SubscriptionUserRowProps {
name: string;
/** null → 显示默认用户图标 */
avatarUrl: string | null;
subtitle?: string;
className?: string;
}
export function SubscriptionUserRow({
name,
avatarUrl,
subtitle = "VIP membership not activated",
className,
}: SubscriptionUserRowProps) {
return (
<div className={[styles.row, className].filter(Boolean).join(" ")}>
<div className={styles.avatar} aria-hidden="true">
{avatarUrl ? (
<Image
src={avatarUrl}
alt=""
width={56}
height={56}
className={styles.avatarImg}
/>
) : (
<svg
width="28"
height="28"
viewBox="0 0 24 24"
fill="none"
className={styles.avatarIcon}
>
<circle cx="12" cy="8" r="4" fill="currentColor" />
<path
d="M4 20c0-4.418 3.582-7 8-7s8 2.582 8 7"
fill="currentColor"
/>
</svg>
)}
</div>
<div className={styles.text}>
<p className={styles.name}>{name}</p>
<p className={styles.subtitle}>{subtitle}</p>
</div>
</div>
);
}