diff --git a/src/app/sidebar/components/guest-panel.tsx b/src/app/sidebar/components/guest-panel.tsx new file mode 100644 index 00000000..917447c0 --- /dev/null +++ b/src/app/sidebar/components/guest-panel.tsx @@ -0,0 +1,39 @@ +"use client"; + +import Link from "next/link"; + +import { ROUTES } from "@/router/routes"; + +import styles from "./sidebar-screen.module.css"; + +/** + * 未登录态欢迎面板 + * + * 显示欢迎语与 Sign in / Sign up 入口。 + */ +export function GuestPanel() { + return ( +
+

Welcome to CozSweet

+

+ Sign in to sync your chats and unlock VIP. +

+
+ + Sign up + + + Sign in + +
+
+ ); +} diff --git a/src/app/sidebar/components/index.ts b/src/app/sidebar/components/index.ts index 16945812..49b73419 100644 --- a/src/app/sidebar/components/index.ts +++ b/src/app/sidebar/components/index.ts @@ -2,4 +2,7 @@ * @file Automatically generated by barrelsby. */ +export * from "./guest-panel"; export * from "./sidebar-screen"; +export * from "./user-info-card"; +export * from "./vip-cta"; diff --git a/src/app/sidebar/components/sidebar-screen.module.css b/src/app/sidebar/components/sidebar-screen.module.css index 5a6b2540..008326cf 100644 --- a/src/app/sidebar/components/sidebar-screen.module.css +++ b/src/app/sidebar/components/sidebar-screen.module.css @@ -47,3 +47,229 @@ align-items: flex-start; gap: var(--spacing-lg, 16px); } + +/* ============================================================ + * 用户信息区 + * ============================================================ */ +.profileArea { + padding: 0 var(--spacing-md, 12px); + display: flex; + flex-direction: column; + align-items: stretch; + gap: var(--spacing-lg, 16px); + width: 100%; +} + +.userInfoCard { + display: flex; + align-items: center; + gap: var(--spacing-md, 12px); + padding: var(--spacing-lg, 16px); + background: var(--color-settings-card-background, #ffffff); + border-radius: var(--radius-lg, 12px); + color: #000; + width: 100%; + box-sizing: border-box; +} + +.userInfoAvatar { + border-radius: var(--radius-full, 999px); + object-fit: cover; + flex-shrink: 0; +} + +.userInfoAvatarFallback { + width: 64px; + height: 64px; + border-radius: var(--radius-full, 999px); + background: var(--color-settings-row-background, #f2f2f2); + color: #000; + display: inline-flex; + align-items: center; + justify-content: center; + font-size: var(--font-size-xl, 20px); + font-weight: 600; + flex-shrink: 0; +} + +.userInfoText { + display: flex; + flex-direction: column; + gap: var(--spacing-xs, 4px); + flex: 1 1 auto; + min-width: 0; +} + +.userInfoNameRow { + display: flex; + align-items: center; + gap: var(--spacing-sm, 8px); + min-width: 0; +} + +.userInfoName { + font-size: var(--font-size-lg, 16px); + font-weight: 600; + color: #000; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + min-width: 0; +} + +.userInfoEmail { + font-size: var(--font-size-sm, 14px); + color: var(--color-text-secondary, #666); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.userInfoCoinRow { + display: inline-flex; + align-items: center; + gap: var(--spacing-xs, 4px); + font-size: var(--font-size-sm, 14px); + color: var(--color-text-secondary, #666); +} + +.vipBadge { + display: inline-flex; + align-items: center; + padding: 2px var(--spacing-sm, 8px); + border-radius: var(--radius-full, 999px); + background: var(--color-accent, #7c5cff); + color: var(--color-text-primary, #ffffff); + font-size: var(--font-size-xs, 12px); + font-weight: 600; + letter-spacing: 0.5px; + flex-shrink: 0; +} + +.vipCta { + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-md, 12px) var(--spacing-lg, 16px); + border-radius: var(--radius-full, 999px); + background: linear-gradient( + 90deg, + var(--color-button-gradient-start, #7c5cff), + var(--color-button-gradient-end, #b48bff) + ); + color: var(--color-text-primary, #ffffff); + font-size: var(--font-size-lg, 16px); + font-weight: 600; + border: 0; + cursor: pointer; + width: 100%; + text-decoration: none; +} + +.vipCta:hover { + opacity: 0.9; +} + +.vipCta:focus-visible { + outline: 2px solid var(--color-accent, #7c5cff); + outline-offset: 2px; +} + +/* ============================================================ + * 未登录态 GuestPanel + * ============================================================ */ +.guestPanel { + display: flex; + flex-direction: column; + gap: var(--spacing-md, 12px); + padding: var(--spacing-lg, 16px); + background: var(--color-settings-card-background, #ffffff); + border-radius: var(--radius-lg, 12px); + color: #000; + width: 100%; + box-sizing: border-box; +} + +.guestTitle { + font-size: var(--font-size-xl, 20px); + font-weight: 600; + color: #000; + margin: 0; +} + +.guestSubtitle { + font-size: var(--font-size-sm, 14px); + color: var(--color-text-secondary, #666); + margin: 0; +} + +.guestActions { + display: flex; + flex-direction: column; + gap: var(--spacing-sm, 8px); + margin-top: var(--spacing-sm, 8px); +} + +.guestPrimaryLink { + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-md, 12px) var(--spacing-lg, 16px); + border-radius: var(--radius-full, 999px); + background: linear-gradient( + 90deg, + var(--color-button-gradient-start, #7c5cff), + var(--color-button-gradient-end, #b48bff) + ); + color: var(--color-text-primary, #ffffff); + font-size: var(--font-size-lg, 16px); + font-weight: 600; + text-decoration: none; + border: 0; +} + +.guestPrimaryLink:hover { + opacity: 0.9; +} + +.guestSecondaryLink { + display: flex; + align-items: center; + justify-content: center; + padding: var(--spacing-md, 12px) var(--spacing-lg, 16px); + border-radius: var(--radius-full, 999px); + background: transparent; + color: #000; + font-size: var(--font-size-lg, 16px); + font-weight: 600; + text-decoration: none; + border: 1px solid var(--color-auth-border, #d0d0d0); +} + +.guestSecondaryLink:hover { + background: var(--color-settings-row-background, #f2f2f2); +} + +/* ============================================================ + * 骨架占位 + * ============================================================ */ +.skeleton { + display: block; + background: var(--color-settings-row-background, #e5e5e5); + border-radius: var(--radius-md, 8px); +} + +.skeletonMd { + width: 60%; + height: 18px; +} + +.skeletonSm { + width: 80%; + height: 14px; +} + +.skeletonXs { + width: 40%; + height: 14px; +} diff --git a/src/app/sidebar/components/sidebar-screen.tsx b/src/app/sidebar/components/sidebar-screen.tsx index e689c446..af66deec 100644 --- a/src/app/sidebar/components/sidebar-screen.tsx +++ b/src/app/sidebar/components/sidebar-screen.tsx @@ -7,21 +7,33 @@ import { useRouter } from "next/navigation"; import { MobileShell } from "@/app/_components/core/mobile-shell"; import { SettingsSection } from "@/app/_components/core/settings-section"; import { useUserDispatch, useUserState } from "@/stores/user/user-context"; -import { useAuthDispatch } from "@/stores/auth/auth-context"; +import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context"; import { ROUTES } from "@/router/routes"; import type { UserView } from "@/models/user/user"; +import { GuestPanel } from "./guest-panel"; +import { UserInfoCard } from "./user-info-card"; +import { VipCta } from "./vip-cta"; + import styles from "./sidebar-screen.module.css"; /** * 侧边栏主屏幕 * + * 根据 `loginStatus + currentUser.isVip` 派生三种状态: + * - guest:未登录,展示欢迎面板 + * - member:已登录未购买 VIP,展示用户信息 + Get VIP CTA + * - vip:已登录且购买 VIP,展示用户信息(含 VIP 徽章) + Manage VIP CTA + * * 原始 Dart: lib/ui/sidebar/sidebar_screen.dart + profile_view.dart */ +type SidebarState = "guest" | "member" | "vip"; + export function SidebarScreen() { const user = useUserState(); const userDispatch = useUserDispatch(); + const auth = useAuthState(); const authDispatch = useAuthDispatch(); const router = useRouter(); @@ -45,6 +57,14 @@ export function SidebarScreen() { } }, [user.currentUser, authDispatch, router]); + // 状态派生:未登录 / 已登录未 VIP / 已登录 VIP + const sidebarState: SidebarState = + auth.loginStatus === "notLoggedIn" + ? "guest" + : user.currentUser?.isVip === true + ? "vip" + : "member"; + return (
@@ -71,25 +91,53 @@ export function SidebarScreen() {
- {/* 设置区域:与 Flutter SettingsSection 对齐 */} -
- router.push(ROUTES.subscription), - }, - { - id: "logout", - title: "Log out", - destructive: true, - onClick: () => userDispatch({ type: "UserLogout" }), - }, - ]} - /> + {/* 用户信息区:三种状态 */} +
+ {sidebarState === "guest" ? ( + + ) : user.currentUser == null ? ( + // 已登录但 currentUser 仍在初始化 → 骨架占位 +