refactor(app): remove unused components

This commit is contained in:
2026-07-13 14:02:27 +08:00
parent b89bde1b55
commit 34c6f5523c
29 changed files with 32 additions and 892 deletions
@@ -0,0 +1,18 @@
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";
import { SubscriptionCtaButton } from "../subscription-cta-button";
describe("subscription Tailwind components", () => {
it("renders SubscriptionCtaButton with loading state", () => {
const html = renderToStaticMarkup(
<SubscriptionCtaButton isLoading>Pay now</SubscriptionCtaButton>,
);
expect(html).toContain("min-h-[clamp(48px,9.63vw,52px)]");
expect(html).toContain("bg-[linear-gradient(269deg");
expect(html).toContain("animate-spin");
expect(html).toContain("disabled");
expect(html).toContain("Pay now");
});
});
-5
View File
@@ -2,15 +2,10 @@
* @file Automatically generated by barrelsby.
*/
export * from "./subscription-back-link";
export * from "./subscription-banner";
export * from "./subscription-benefits-card";
export * from "./subscription-checkout-button";
export * from "./subscription-coins-offer-section";
export * from "./subscription-cta-button";
export * from "./subscription-payment-method";
export * from "./subscription-payment-success-dialog";
export * from "./subscription-plan-card";
export * from "./subscription-user-row";
export * from "./subscription-vip-offer-section";
export * from "./stripe-payment-dialog";
@@ -1,20 +0,0 @@
.backLink {
display: inline-flex;
align-items: center;
justify-content: center;
padding: var(--spacing-sm, 8px);
margin-left: calc(-1 * var(--spacing-sm, 8px));
color: #000;
text-decoration: none;
border-radius: var(--radius-md);
}
.backLink:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.icon {
display: block;
color: inherit;
}
@@ -1,40 +0,0 @@
"use client";
/**
* 订阅页返回箭头
*
* 视觉规格(与设计稿对齐):
* - 24×24 chevron-left 图标
* - 黑色,2px 描边
* - 无 "Back" 文本
*/
import { ChevronLeft } from "lucide-react";
import Link from "next/link";
import { ROUTES } from "@/router/routes";
import styles from "./subscription-back-link.module.css";
export interface SubscriptionBackLinkProps {
className?: string;
href?: string;
}
export function SubscriptionBackLink({
className,
href = ROUTES.sidebar,
}: SubscriptionBackLinkProps) {
return (
<Link
href={href}
aria-label="Back"
className={[styles.backLink, className].filter(Boolean).join(" ")}
>
<ChevronLeft
size={24}
strokeWidth={2}
aria-hidden="true"
className={styles.icon}
/>
</Link>
);
}
@@ -1,29 +0,0 @@
.banner {
position: relative;
background: linear-gradient(90deg, #f96ade 0%, #ffb88a 100%);
border-radius: var(--responsive-card-radius-sm, var(--radius-xl));
padding: var(--responsive-card-padding, var(--spacing-lg))
var(--responsive-card-padding, var(--spacing-md));
text-align: center;
}
.text {
margin: 0;
font-size: var(--responsive-card-title, 17px);
font-weight: 600;
color: #ffffff;
line-height: 1.3;
}
/* 向下指的 CSS 三角 */
.pointer {
position: absolute;
bottom: clamp(-8px, -1.481vw, -6px);
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: clamp(8px, 1.852vw, 10px) solid transparent;
border-right: clamp(8px, 1.852vw, 10px) solid transparent;
border-top: clamp(8px, 1.852vw, 10px) solid #ffb88a;
}
@@ -1,30 +0,0 @@
"use client";
/**
* 订阅页渐变横幅
*
* 视觉规格(与设计稿对齐):
* - 粉→桃渐变背景(90deg)
* - 居中白色 17px/600 文字
* - 底部中央有一个向下指的 CSS 三角
*/
import styles from "./subscription-banner.module.css";
export interface SubscriptionBannerProps {
className?: string;
title?: string;
}
export function SubscriptionBanner({
className,
title = "Subscribe to become the VIP Member",
}: SubscriptionBannerProps) {
return (
<div
className={[styles.banner, className].filter(Boolean).join(" ")}
role="note"
>
<p className={styles.text}>{title}</p>
<span className={styles.pointer} aria-hidden="true" />
</div>
);
}
@@ -1,52 +0,0 @@
.card {
background-color: #ffffff;
border-radius: var(--responsive-card-radius-sm, 21px);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
overflow: hidden;
border: solid 1px rgba(0, 0, 0, 0.14);
}
.header {
background-color: #fce4ec;
border-radius: clamp(5px, 1.111vw, 6px);
padding: var(--responsive-card-padding, var(--spacing-md));
text-align: center;
}
.title {
font-size: var(--responsive-card-title, var(--font-size-lg));
font-weight: 600;
color: #f657a0;
line-height: 1.3;
}
.list {
list-style: none;
margin: var(--page-section-gap, 18px) 0;
padding: 0 0 0 var(--responsive-card-padding, 20px);
}
.item {
display: flex;
align-items: flex-start;
gap: 0;
padding: 0;
font-size: var(--responsive-body, var(--font-size-lg));
color: #1e1e1e;
line-height: normal;
}
.numeral {
flex: 0 0 auto;
font-weight: 500;
color: #1e1e1e;
}
.numeral::after {
content: " ";
white-space: pre;
}
.text {
flex: 1 1 auto;
}
@@ -1,41 +0,0 @@
"use client";
/**
* 订阅权益卡片
*
* 视觉规格(与设计稿对齐):
* - 白色卡片,--radius-xl 圆角
* - 顶部粉色渐变条:居中白色标题
* - 主体:1./2./3./4. 编号列表
* - 行间细分隔线
*/
import styles from "./subscription-benefits-card.module.css";
export interface SubscriptionBenefitsCardProps {
title: string;
items: readonly string[];
className?: string;
}
export function SubscriptionBenefitsCard({
title,
items,
className,
}: SubscriptionBenefitsCardProps) {
return (
<section
className={[styles.card, className].filter(Boolean).join(" ")}
>
<header className={styles.header}>
<h2 className={styles.title}>{title}</h2>
</header>
<ol className={styles.list}>
{items.map((item, idx) => (
<li key={idx} className={styles.item}>
<span className={styles.numeral}>{idx + 1}.</span>
<span className={styles.text}>{item}</span>
</li>
))}
</ol>
</section>
);
}
@@ -14,7 +14,6 @@ import { Logger } from "@/utils";
import { StripePaymentDialog } from "./stripe-payment-dialog";
import dialogStyles from "./stripe-payment-dialog.module.css";
import { SubscriptionCtaButton } from "./subscription-cta-button";
import styles from "./subscription-cta-button.module.css";
const log = new Logger("SubscriptionCheckoutButton");
@@ -71,7 +70,6 @@ export function SubscriptionCheckoutButton({
disabled={disabled}
isLoading={isLoading}
onClick={handleClick}
className={styles.button}
>
{label}
</SubscriptionCtaButton>
@@ -1,67 +0,0 @@
.button {
display: inline-flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: clamp(48px, 9.63vw, 52px);
padding: 0 var(--responsive-card-padding, var(--spacing-lg));
border: none;
border-radius: var(--radius-full, 999px);
background-image:
linear-gradient(
269deg,
#ff67e0 0%,
rgba(254, 104, 224, 0.5) 20%,
rgba(252, 105, 223, 0.79) 66%,
#f96ade 100%
),
linear-gradient(#f657a0, #f657a0);
background-blend-mode: normal, normal;
box-shadow: 0 5px 7px 0 rgba(247, 89, 168, 0.31);
color: #ffffff;
font-size: var(--responsive-body, var(--font-size-lg));
font-weight: 600;
cursor: pointer;
opacity: 0.85;
transition: filter 0.15s ease, transform 0.05s ease, opacity 0.15s ease;
}
.button:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.button:not(:disabled):active {
transform: scale(0.98);
}
.label {
display: inline-flex;
align-items: center;
gap: var(--spacing-sm);
flex: 1 1 auto;
justify-content: center;
text-align: center;
}
.spinner {
display: inline-block;
width: clamp(16px, 3.333vw, 18px);
height: clamp(16px, 3.333vw, 18px);
border-width: 2px;
border-style: solid;
border-color: #ffffff transparent transparent transparent;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
@@ -2,8 +2,6 @@
import type { ButtonHTMLAttributes, ReactNode } from "react";
import styles from "./subscription-cta-button.module.css";
export interface SubscriptionCtaButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
@@ -17,7 +15,12 @@ export function SubscriptionCtaButton({
disabled,
...rest
}: SubscriptionCtaButtonProps) {
const classes = [styles.button, className].filter(Boolean).join(" ");
const classes = [
"inline-flex min-h-[clamp(48px,9.63vw,52px)] w-full cursor-pointer items-center justify-center rounded-(--radius-full,999px) border-0 bg-[linear-gradient(269deg,#ff67e0_0%,rgba(254,104,224,0.5)_20%,rgba(252,105,223,0.79)_66%,#f96ade_100%),linear-gradient(#f657a0,#f657a0)] px-(--responsive-card-padding,var(--spacing-lg)) text-(length:--responsive-body,var(--font-size-lg)) font-semibold text-white opacity-85 shadow-[0_5px_7px_0_rgba(247,89,168,0.31)] transition-[filter,transform,opacity] duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--color-accent) active:enabled:scale-98 disabled:cursor-not-allowed disabled:opacity-50",
className,
]
.filter(Boolean)
.join(" ");
return (
<button
{...rest}
@@ -25,8 +28,13 @@ export function SubscriptionCtaButton({
disabled={disabled || isLoading}
className={classes}
>
<span className={styles.label}>
{isLoading ? <span className={styles.spinner} aria-hidden="true" /> : null}
<span className="inline-flex flex-auto items-center justify-center gap-sm text-center">
{isLoading ? (
<span
className="inline-block size-[clamp(16px,3.333vw,18px)] animate-spin rounded-full border-2 border-solid border-white border-r-transparent border-b-transparent border-l-transparent"
aria-hidden="true"
/>
) : null}
{children}
</span>
</button>
@@ -1,85 +0,0 @@
.card {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
min-height: clamp(136px, 28.519vw, 154px);
padding:
clamp(7px, 1.481vw, 8px)
clamp(4px, 0.926vw, 5px)
0;
background: #ffffff;
border: 1px solid transparent;
border-radius: var(--responsive-card-radius-sm, 20px);
border-style: solid;
box-shadow: 0 5px 7px 0 rgba(247, 89, 168, 0.1);
cursor: pointer;
text-align: center;
overflow: hidden;
transition: border-color 0.15s ease, box-shadow 0.15s ease,
transform 0.05s ease;
width: 100%;
font: inherit;
color: inherit;
}
.card:focus-visible {
outline: 2px solid var(--color-accent);
outline-offset: 2px;
}
.card:not(:disabled):active {
transform: scale(0.98);
}
.selected {
border-color: #f657a0;
border-width: 3px;
border-radius: var(--responsive-card-radius-sm, 20px);
}
.name {
margin-top: var(--spacing-lg);
font-size: var(--responsive-body, var(--font-size-lg));
font-weight: 500;
color: var(--color-auth-text-primary);
line-height: 1.2;
}
.priceBlock {
display: flex;
align-items: baseline;
justify-content: center;
margin-top: var(--spacing-md);
gap: 2px;
line-height: 1;
}
.currency {
font-size: var(--responsive-body, var(--font-size-lg));
}
.price {
font-size: clamp(32px, 7.407vw, var(--font-size-40));
line-height: 1;
}
.originalPrice {
margin: 4px 0 var(--spacing-sm);
font-size: var(--responsive-body, var(--font-size-lg));
color: #999999;
text-decoration: line-through;
line-height: 1;
}
.perDayBar {
align-self: stretch;
margin: auto calc(-1 * var(--spacing-xs)) 0;
padding: clamp(5px, 1.111vw, 6px) 0;
border-radius: 0;
color: #ffffff;
font-size: var(--responsive-caption, var(--font-size-md));
font-weight: 600;
text-align: center;
line-height: 1.2;
}
@@ -1,79 +0,0 @@
"use client";
/**
* 订阅套餐卡片
*
* 视觉规格(与设计稿对齐):
* - 卡片顶部:套餐名(12px / secondary
* - 中部:大字价格(28px / 300+ 划线原价(12px / line-through / 999
* - 底部 28px 高色条:白字 12px/600 每日折算价
* - 选中:2px 粉色边框 + 浅粉阴影
* - 三个卡片底部色条不同:粉 → 亮粉 → 紫
*/
import styles from "./subscription-plan-card.module.css";
const PER_DAY_COLORS = [
"#f657a0",
"#fa77e3",
"#bb72f9",
] as const;
export interface SubscriptionPlanView {
id: string;
name: string;
price: string;
originalPrice: string | null;
perDay: string;
currencySymbol: string;
}
export interface SubscriptionPlanCardProps {
plan: SubscriptionPlanView;
selected: boolean;
onClick: () => void;
gradientIndex?: number;
className?: string;
}
export function SubscriptionPlanCard({
plan,
selected,
onClick,
gradientIndex = 0,
className,
}: SubscriptionPlanCardProps) {
const classes = [
styles.card,
selected ? styles.selected : "",
selected && gradientIndex === 0 ? styles.firstSelected : "",
className,
]
.filter(Boolean)
.join(" ");
const perDayStyle = {
backgroundColor: PER_DAY_COLORS[gradientIndex % PER_DAY_COLORS.length],
};
return (
<button
type="button"
onClick={onClick}
aria-pressed={selected}
aria-label={`${plan.name} plan, ${plan.currencySymbol}${plan.price}`}
className={classes}
>
<p className={styles.name}>{plan.name}</p>
<div className={styles.priceBlock}>
<span className={styles.currency}>{plan.currencySymbol}</span>
<span className={styles.price}>{plan.price}</span>
</div>
<p className={styles.originalPrice}>
{plan.originalPrice ?? "\u00a0"}
</p>
<div className={styles.perDayBar} style={perDayStyle}>
{plan.perDay}
</div>
</button>
);
}
@@ -1,52 +0,0 @@
.row {
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.avatar {
flex: 0 0 auto;
width: var(--sidebar-avatar-size, 56px);
height: var(--sidebar-avatar-size, 56px);
border-radius: 50%;
background: var(--color-accent);
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
color: #ffffff;
}
.avatarImg {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.avatarIcon {
color: inherit;
}
.text {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1 1 auto;
min-width: 0;
}
.name {
margin: 0;
font-size: var(--responsive-body, var(--font-size-lg));
font-weight: 600;
color: #000;
line-height: 1.2;
}
.subtitle {
margin: 0;
font-size: var(--responsive-caption, var(--font-size-sm));
color: var(--color-text-secondary);
line-height: 1.2;
}
@@ -1,41 +0,0 @@
"use client";
/**
* 订阅页用户信息行
*
* 视觉规格(与设计稿对齐):
* - 56×56 粉色圆形头像
* - 头像右侧两行文本:用户名(16px/600)、副标题(12px/secondary
* - 无头像时显示默认用户图标
*/
import { UserMessageAvatar } from "@/app/_components";
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(" ")}>
<UserMessageAvatar
avatarUrl={avatarUrl}
className={styles.avatar}
size={56}
/>
<div className={styles.text}>
<p className={styles.name}>{name}</p>
<p className={styles.subtitle}>{subtitle}</p>
</div>
</div>
);
}