diff --git a/src/app/_components/core/dialog.module.css b/src/app/_components/core/dialog.module.css deleted file mode 100644 index 1240a98d..00000000 --- a/src/app/_components/core/dialog.module.css +++ /dev/null @@ -1,25 +0,0 @@ -.scrim { - position: fixed; - inset: 0; - z-index: 50; - display: flex; - align-items: center; - justify-content: center; - padding: - calc(var(--spacing-md, 12px) + var(--app-safe-top, 0px)) - calc(var(--spacing-md, 12px) + var(--app-safe-right, 0px)) - calc(var(--spacing-md, 12px) + var(--app-safe-bottom, 0px)) - calc(var(--spacing-md, 12px) + var(--app-safe-left, 0px)); -} - -.panel { - position: relative; - width: 100%; - max-width: var(--dialog-max-width, 380px); - background: var(--color-dialog-background); - border: var(--border-light) solid var(--color-dialog-border); - border-radius: var(--responsive-card-radius, var(--radius-xl)); - box-shadow: 0 24px 48px rgba(0, 0, 0, 0.45); - color: var(--color-text-primary); - overflow: hidden; -} diff --git a/src/app/_components/core/dialog.tsx b/src/app/_components/core/dialog.tsx deleted file mode 100644 index eeb4c6e3..00000000 --- a/src/app/_components/core/dialog.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client"; -/** - * 通用 Dialog 基础组件(headless + 简单样式) - * - * - * username_dialog, pronouns_dialog, subscription_dialog, environment_dialog, auth_other_options_dialog, - * facebook_login_dialog, external_browser_dialog)。 - * - * 设计目标:~70 行覆盖所有 9 种 dialog 的通用逻辑(portal, ESC, scroll lock, click-outside), - * 调用方只关心内容布局。 - * - * 注意:项目不引入 Radix/shadcn,纯依赖 React + CSS Modules。 - */ -import { type ReactNode } from "react"; - -import { ModalPortal } from "./modal-portal"; -import styles from "./dialog.module.css"; - -export interface DialogProps { - open: boolean; - onClose: () => void; - children: ReactNode; - /** 内容最大宽度(px)。默认 360。 */ - maxWidth?: number; - /** 蒙层透明度(0-1)。默认 0.5。 */ - scrimOpacity?: number; - /** 禁用点击外部 / ESC 关闭。 */ - persistent?: boolean; - /** ARIA 标签。 */ - ariaLabel?: string; -} - -export function Dialog({ - open, - onClose, - children, - maxWidth = 360, - scrimOpacity = 0.5, - persistent = false, - ariaLabel, -}: DialogProps) { - return ( - - {children} - - ); -} diff --git a/src/app/_components/core/index.ts b/src/app/_components/core/index.ts index c7db7ac9..67cf80af 100644 --- a/src/app/_components/core/index.ts +++ b/src/app/_components/core/index.ts @@ -5,7 +5,6 @@ export * from "./app-bottom-nav"; export * from "./bottom-sheet"; export * from "./checkbox"; -export * from "./dialog"; export * from "./loading-indicator"; export * from "./mobile-shell"; export * from "./page-loading-fallback"; diff --git a/src/app/auth/components/__tests__/tailwind-components.test.tsx b/src/app/auth/components/__tests__/tailwind-components.test.tsx index b357c136..aa00706f 100644 --- a/src/app/auth/components/__tests__/tailwind-components.test.tsx +++ b/src/app/auth/components/__tests__/tailwind-components.test.tsx @@ -8,7 +8,6 @@ import { AuthFacebookPanel } from "../auth-facebook-panel"; import { AuthLegalText } from "../auth-legal-text"; import { AuthPrimaryButton } from "../auth-primary-button"; import { AuthSocialButton } from "../auth-social-button"; -import { AuthSocialButtons } from "../auth-social-buttons"; import { AuthTextField } from "../auth-text-field"; import { EmailLoginForm } from "../email-login-form"; import { EmailRegisterForm } from "../email-register-form"; @@ -86,24 +85,6 @@ describe("auth Tailwind components", () => { expect(html).toContain("Continue"); }); - it("renders AuthSocialButtons with provider-specific Tailwind styles", () => { - const html = renderToStaticMarkup( - {}} - onGoogle={() => {}} - onApple={() => {}} - showApple - />, - ); - - expect(html).toContain("bg-facebook-blue"); - expect(html).toContain("bg-white"); - expect(html).toContain("bg-black"); - expect(html).toContain("Continue with Facebook"); - expect(html).toContain("Continue with Google"); - expect(html).toContain("Continue with Apple"); - }); - it("renders AuthLegalText with Tailwind checkbox and links", () => { const html = renderToStaticMarkup(); diff --git a/src/app/auth/components/auth-social-buttons.tsx b/src/app/auth/components/auth-social-buttons.tsx deleted file mode 100644 index acd20af6..00000000 --- a/src/app/auth/components/auth-social-buttons.tsx +++ /dev/null @@ -1,82 +0,0 @@ -"use client"; -/** - * 社交登录按钮组合 - * - * - * - * 包含 Facebook / Google / Apple 三个按钮的竖排列表。 - */ -import { type ReactNode } from "react"; - -import { AuthProviderIcon } from "./auth-provider-icon"; - -export interface AuthSocialButtonsProps { - onFacebook: () => void; - onGoogle: () => void; - onApple?: () => void; - loadingProvider?: "facebook" | "google" | "apple" | null; - showApple?: boolean; - /** 自定义子节点(如嵌入 GIS 按钮) */ - googleSlot?: ReactNode; -} - -export function AuthSocialButtons({ - onFacebook, - onGoogle, - onApple, - loadingProvider, - showApple = false, - googleSlot, -}: AuthSocialButtonsProps) { - const buttonClassName = - "inline-flex min-h-(--button-height) w-full cursor-pointer items-center justify-center gap-sm rounded-full border border-chat-input-border bg-[rgba(255,255,255,0.04)] px-lg text-(length:--responsive-body) font-medium text-text-primary transition-colors duration-150 enabled:hover:bg-[rgba(255,255,255,0.08)] disabled:cursor-not-allowed disabled:opacity-50"; - const iconClassName = "inline-flex size-5 items-center justify-center"; - - return ( - - - - - - Continue with Facebook - - - {googleSlot ? ( - - {googleSlot} - - ) : ( - - - - - Continue with Google - - )} - - {showApple && onApple ? ( - - - - - Continue with Apple - - ) : null} - - ); -} diff --git a/src/app/auth/components/index.ts b/src/app/auth/components/index.ts index 48014875..889a9712 100644 --- a/src/app/auth/components/index.ts +++ b/src/app/auth/components/index.ts @@ -12,7 +12,6 @@ export * from "./auth-panel"; export * from "./auth-primary-button"; export * from "./auth-provider-icon"; export * from "./auth-social-button"; -export * from "./auth-social-buttons"; export * from "./auth-text-field"; export * from "./auth-validators"; export * from "./email-login-form"; diff --git a/src/app/chat/components/__tests__/tailwind-components.test.tsx b/src/app/chat/components/__tests__/tailwind-components.test.tsx index a73f52da..3bc479ac 100644 --- a/src/app/chat/components/__tests__/tailwind-components.test.tsx +++ b/src/app/chat/components/__tests__/tailwind-components.test.tsx @@ -13,7 +13,6 @@ import { MessageAvatar } from "../message-avatar"; import { PrivateMessageCard } from "../private-message-card"; import { PwaInstallDialog } from "../pwa-install-dialog"; import { TextBubble } from "../text-bubble"; -import { VoiceUnlockOptionsDialog } from "../voice-unlock-options-dialog"; vi.mock("@/router/use-app-navigator", () => ({ useAppNavigator: () => ({ @@ -296,37 +295,4 @@ describe("chat Tailwind components", () => { expect(html).toContain("Top up now"); }); - it("renders VoiceUnlockOptionsDialog action choices", () => { - expect( - renderToStaticMarkup( - undefined} - onBuyVoicePackage={() => undefined} - onActivateVip={() => undefined} - />, - ), - ).toBe(""); - - const html = renderToStaticMarkup( - undefined} - onBuyVoicePackage={() => undefined} - onActivateVip={() => undefined} - />, - ); - - expect(html).toContain('aria-labelledby="voice-unlock-options-title"'); - expect(html).toContain("z-72"); - expect(html).toContain("Unlock voice message"); - expect(html).toContain("购买语音包"); - expect(html).toContain("开通 VIP"); - expect(html).toContain("免费10条语言消息+无限畅聊"); - expect(html).toContain("Cancel"); - expect(html).toContain("bg-[linear-gradient(90deg,#ff67e0_0%,#ff52a2_100%)]"); - expect(html).toContain( - "bg-[linear-gradient(#ffffff,#ffffff)_padding-box,linear-gradient(90deg,#ff67e0,#ff52a2)_border-box]", - ); - }); }); diff --git a/src/app/chat/components/index.ts b/src/app/chat/components/index.ts index f6161756..34c914e3 100644 --- a/src/app/chat/components/index.ts +++ b/src/app/chat/components/index.ts @@ -26,4 +26,3 @@ export * from "./pwa-install-dialog"; export * from "./pwa-install-overlay"; export * from "./text-bubble"; export * from "./voice-bubble"; -export * from "./voice-unlock-options-dialog"; diff --git a/src/app/chat/components/pwa-install-overlay.tsx b/src/app/chat/components/pwa-install-overlay.tsx index 911ad1b5..186e450c 100644 --- a/src/app/chat/components/pwa-install-overlay.tsx +++ b/src/app/chat/components/pwa-install-overlay.tsx @@ -11,7 +11,7 @@ * - 生产环境有效;开发环境 1s 后立即弹出(测试用) * * 注意:PWA install prompt API(`beforeinstallprompt`)在浏览器差异较大, - * 本轮仅实现"显示时机"逻辑,具体 install prompt 调用由 Dialog 内部处理。 + * 本轮仅实现"显示时机"逻辑,具体 install prompt 调用由 PwaInstallDialog 处理。 */ import { useEffect } from "react"; diff --git a/src/app/chat/components/voice-unlock-options-dialog.tsx b/src/app/chat/components/voice-unlock-options-dialog.tsx deleted file mode 100644 index f85f3210..00000000 --- a/src/app/chat/components/voice-unlock-options-dialog.tsx +++ /dev/null @@ -1,66 +0,0 @@ -"use client"; - -export interface VoiceUnlockOptionsDialogProps { - open: boolean; - onClose: () => void; - onBuyVoicePackage: () => void; - onActivateVip: () => void; -} - -export function VoiceUnlockOptionsDialog({ - open, - onClose, - onBuyVoicePackage, - onActivateVip, -}: VoiceUnlockOptionsDialogProps) { - if (!open) return null; - - return ( - - - - Unlock voice message - - - Choose how you would like to unlock this voice message. - - - - - 购买语音包 - - - 开通 VIP - - 免费10条语言消息+无限畅聊 - - - - - - Cancel - - - - ); -} diff --git a/src/app/splash/components/index.ts b/src/app/splash/components/index.ts index dbfff45c..ded07bcc 100644 --- a/src/app/splash/components/index.ts +++ b/src/app/splash/components/index.ts @@ -6,6 +6,4 @@ export * from "./splash-background"; export * from "./splash-button"; export * from "./splash-content"; export * from "./splash-latest-message"; -export * from "./splash-loading"; export * from "./splash-logo"; -export * from "./splash-progress"; diff --git a/src/app/splash/components/splash-loading.tsx b/src/app/splash/components/splash-loading.tsx deleted file mode 100644 index 4fb0f4ea..00000000 --- a/src/app/splash/components/splash-loading.tsx +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Splash 加载状态 - * - * - * - * 包装 ``,splash 阶段可能用于等待 auth 检查。 - */ -import { LoadingIndicator } from "@/app/_components/core/loading-indicator"; - -export function SplashLoading() { - return ; -} diff --git a/src/app/splash/components/splash-progress.module.css b/src/app/splash/components/splash-progress.module.css deleted file mode 100644 index 87cb66ed..00000000 --- a/src/app/splash/components/splash-progress.module.css +++ /dev/null @@ -1,28 +0,0 @@ -.wrapper { - position: relative; - width: 100%; - height: var(--responsive-inline-gap-xs, 4px); - border-radius: var(--radius-full); - background: rgba(255, 255, 255, 0.1); - overflow: hidden; -} - -.bar { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 30%; - border-radius: var(--radius-full); - background: linear-gradient(90deg, var(--color-accent), var(--color-facebook-button-gradient-end)); - animation: progress 1.5s ease-in-out infinite; -} - -@keyframes progress { - 0% { - left: -30%; - } - 100% { - left: 100%; - } -} diff --git a/src/app/splash/components/splash-progress.tsx b/src/app/splash/components/splash-progress.tsx deleted file mode 100644 index 17b21a48..00000000 --- a/src/app/splash/components/splash-progress.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Splash 进度条 - * - * - * - * 当前实现:简单 CSS 动画进度条,0 → 100% 1.5s。 - */ -import styles from "./splash-progress.module.css"; - -export function SplashProgress() { - return ( - - - - ); -} diff --git a/src/app/subscription/components/__tests__/tailwind-components.test.tsx b/src/app/subscription/components/__tests__/tailwind-components.test.tsx new file mode 100644 index 00000000..293c704e --- /dev/null +++ b/src/app/subscription/components/__tests__/tailwind-components.test.tsx @@ -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( + Pay now, + ); + + 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"); + }); +}); diff --git a/src/app/subscription/components/index.ts b/src/app/subscription/components/index.ts index 8b02f37f..d5bc594f 100644 --- a/src/app/subscription/components/index.ts +++ b/src/app/subscription/components/index.ts @@ -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"; diff --git a/src/app/subscription/components/subscription-back-link.module.css b/src/app/subscription/components/subscription-back-link.module.css deleted file mode 100644 index 5380a6ad..00000000 --- a/src/app/subscription/components/subscription-back-link.module.css +++ /dev/null @@ -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; -} diff --git a/src/app/subscription/components/subscription-back-link.tsx b/src/app/subscription/components/subscription-back-link.tsx deleted file mode 100644 index 7411d657..00000000 --- a/src/app/subscription/components/subscription-back-link.tsx +++ /dev/null @@ -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 ( - - - - ); -} diff --git a/src/app/subscription/components/subscription-banner.module.css b/src/app/subscription/components/subscription-banner.module.css deleted file mode 100644 index 9f6b67ef..00000000 --- a/src/app/subscription/components/subscription-banner.module.css +++ /dev/null @@ -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; -} diff --git a/src/app/subscription/components/subscription-banner.tsx b/src/app/subscription/components/subscription-banner.tsx deleted file mode 100644 index 0531c9d3..00000000 --- a/src/app/subscription/components/subscription-banner.tsx +++ /dev/null @@ -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 ( - - {title} - - - ); -} diff --git a/src/app/subscription/components/subscription-benefits-card.module.css b/src/app/subscription/components/subscription-benefits-card.module.css deleted file mode 100644 index 33a3ad20..00000000 --- a/src/app/subscription/components/subscription-benefits-card.module.css +++ /dev/null @@ -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; -} diff --git a/src/app/subscription/components/subscription-benefits-card.tsx b/src/app/subscription/components/subscription-benefits-card.tsx deleted file mode 100644 index 4a191433..00000000 --- a/src/app/subscription/components/subscription-benefits-card.tsx +++ /dev/null @@ -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 ( - - - {title} - - - {items.map((item, idx) => ( - - {idx + 1}. - {item} - - ))} - - - ); -} diff --git a/src/app/subscription/components/subscription-checkout-button.tsx b/src/app/subscription/components/subscription-checkout-button.tsx index 9ce81d8f..84433cf3 100644 --- a/src/app/subscription/components/subscription-checkout-button.tsx +++ b/src/app/subscription/components/subscription-checkout-button.tsx @@ -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} diff --git a/src/app/subscription/components/subscription-cta-button.module.css b/src/app/subscription/components/subscription-cta-button.module.css deleted file mode 100644 index 3431e54a..00000000 --- a/src/app/subscription/components/subscription-cta-button.module.css +++ /dev/null @@ -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); - } -} diff --git a/src/app/subscription/components/subscription-cta-button.tsx b/src/app/subscription/components/subscription-cta-button.tsx index f138befd..3d20219f 100644 --- a/src/app/subscription/components/subscription-cta-button.tsx +++ b/src/app/subscription/components/subscription-cta-button.tsx @@ -2,8 +2,6 @@ import type { ButtonHTMLAttributes, ReactNode } from "react"; -import styles from "./subscription-cta-button.module.css"; - export interface SubscriptionCtaButtonProps extends ButtonHTMLAttributes { 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 ( - - {isLoading ? : null} + + {isLoading ? ( + + ) : null} {children} diff --git a/src/app/subscription/components/subscription-plan-card.module.css b/src/app/subscription/components/subscription-plan-card.module.css deleted file mode 100644 index 764cde99..00000000 --- a/src/app/subscription/components/subscription-plan-card.module.css +++ /dev/null @@ -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; -} diff --git a/src/app/subscription/components/subscription-plan-card.tsx b/src/app/subscription/components/subscription-plan-card.tsx deleted file mode 100644 index feb4e572..00000000 --- a/src/app/subscription/components/subscription-plan-card.tsx +++ /dev/null @@ -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 ( - - {plan.name} - - {plan.currencySymbol} - {plan.price} - - - {plan.originalPrice ?? "\u00a0"} - - - {plan.perDay} - - - ); -} diff --git a/src/app/subscription/components/subscription-user-row.module.css b/src/app/subscription/components/subscription-user-row.module.css deleted file mode 100644 index 99e6024a..00000000 --- a/src/app/subscription/components/subscription-user-row.module.css +++ /dev/null @@ -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; -} diff --git a/src/app/subscription/components/subscription-user-row.tsx b/src/app/subscription/components/subscription-user-row.tsx deleted file mode 100644 index 08bc931e..00000000 --- a/src/app/subscription/components/subscription-user-row.tsx +++ /dev/null @@ -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 ( - - - - {name} - {subtitle} - - - ); -}
- Choose how you would like to unlock this voice message. -
{title}
{plan.name}
- {plan.originalPrice ?? "\u00a0"} -
{name}
{subtitle}