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,42 @@
"use client";
/**
* 订阅页主操作按钮
*
* 视觉规格(与设计稿对齐):
* - 全宽,52px 高,--radius-xxxl 圆角
* - 粉色渐变(#f96ADE → #f657A0
* - 白字 16px/600 + 粉色阴影
* - :disabled 灰化
*/
import type { ButtonHTMLAttributes, ReactNode } from "react";
import styles from "./subscription-cta-button.module.css";
export interface SubscriptionCtaButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
isLoading?: boolean;
}
export function SubscriptionCtaButton({
children,
isLoading = false,
className,
disabled,
...rest
}: SubscriptionCtaButtonProps) {
const classes = [styles.button, className].filter(Boolean).join(" ");
return (
<button
{...rest}
type="button"
disabled={disabled || isLoading}
className={classes}
>
<span className={styles.label}>
{isLoading ? <span className={styles.spinner} aria-hidden="true" /> : null}
{children}
</span>
</button>
);
}