refactor: full product interaction system redesign
Refactor homepage, reading modal, matching feature, and user profile for improved UX #VERCEL_SKIP Co-authored-by: undefined <undefined+undefined@users.noreply.github.com>
This commit is contained in:
238
app/my/page.tsx
238
app/my/page.tsx
@@ -2,177 +2,149 @@
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import Link from "next/link"
|
||||
import { User, ShoppingBag, Share2, LogOut, ChevronRight, BookOpen, Copy, Check } from "lucide-react"
|
||||
import { User, ShoppingBag, Share2, LogOut, ChevronRight, BookOpen } from "lucide-react"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { AuthModal } from "@/components/modules/auth/auth-modal"
|
||||
import { getFullBookPrice, getAllSections } from "@/lib/book-data"
|
||||
import { getFullBookPrice } from "@/lib/book-data"
|
||||
|
||||
export default function MyPage() {
|
||||
const { user, isLoggedIn, logout } = useStore()
|
||||
const [showAuthModal, setShowAuthModal] = useState(false)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
const copyCode = () => {
|
||||
if (user?.referralCode) {
|
||||
navigator.clipboard.writeText(user.referralCode)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
}
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="min-h-screen bg-black flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-t-2 border-b-2 border-[var(--app-brand)]" />
|
||||
<div className="min-h-screen bg-app-bg flex items-center justify-center">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-app-brand" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!isLoggedIn) {
|
||||
return (
|
||||
<main className="min-h-screen bg-black text-white pb-20 flex flex-col items-center justify-center px-4">
|
||||
<div className="w-12 h-12 rounded-full bg-white/5 flex items-center justify-center mb-4">
|
||||
<User className="w-6 h-6 text-white/40" />
|
||||
<main className="min-h-screen bg-app-bg text-app-text pb-20 flex flex-col items-center justify-center">
|
||||
<div className="p-4 w-full">
|
||||
<div className="max-w-xs mx-auto text-center">
|
||||
<div className="w-14 h-14 mx-auto mb-3 rounded-full bg-app-card flex items-center justify-center">
|
||||
<User className="w-7 h-7 text-app-text-muted" />
|
||||
</div>
|
||||
<h2 className="text-base font-semibold mb-1">登录后查看更多</h2>
|
||||
<p className="text-app-text-muted text-xs mb-4">查看购买记录、分销收益</p>
|
||||
<button
|
||||
onClick={() => setShowAuthModal(true)}
|
||||
className="bg-app-brand hover:bg-app-brand-hover text-white px-6 py-2.5 rounded-full font-medium text-sm"
|
||||
>
|
||||
立即登录
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<h2 className="text-base font-medium mb-1">登录后查看更多</h2>
|
||||
<p className="text-white/40 text-xs mb-6">查看购买记录、分销收益</p>
|
||||
<button
|
||||
onClick={() => setShowAuthModal(true)}
|
||||
className="bg-[var(--app-brand)] text-white px-8 py-2.5 rounded-full font-medium text-sm"
|
||||
>
|
||||
立即登录
|
||||
</button>
|
||||
<AuthModal isOpen={showAuthModal} onClose={() => setShowAuthModal(false)} />
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
||||
const totalSections = getAllSections().length
|
||||
const purchasedCount = user?.hasFullBook ? totalSections : user?.purchasedSections.length || 0
|
||||
const purchaseProgress = Math.round((purchasedCount / totalSections) * 100)
|
||||
const fullBookPrice = getFullBookPrice()
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black text-white pb-20">
|
||||
{/* 用户信息头部 */}
|
||||
<div className="px-4 pt-10 pb-6">
|
||||
<div className="flex items-center gap-3 mb-6">
|
||||
<div className="w-12 h-12 rounded-full bg-[var(--app-brand)]/20 flex items-center justify-center">
|
||||
<User className="w-6 h-6 text-[var(--app-brand)]" />
|
||||
<main className="min-h-screen bg-app-bg text-app-text pb-20">
|
||||
{/* User Profile Header */}
|
||||
<div className="bg-gradient-to-b from-app-card to-app-bg p-4 pt-8">
|
||||
<div className="max-w-xs mx-auto">
|
||||
<div className="flex items-center gap-3 mb-3">
|
||||
<div className="w-11 h-11 rounded-full bg-app-brand/20 flex items-center justify-center">
|
||||
<User className="w-5 h-5 text-app-brand" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-sm font-semibold">{user?.nickname || "用户"}</h2>
|
||||
<p className="text-app-text-muted text-xs">{user?.phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-base font-medium">{user?.nickname || "用户"}</h2>
|
||||
<p className="text-white/40 text-xs">{user?.phone}</p>
|
||||
|
||||
{/* Stats */}
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div className="bg-app-card/60 rounded-lg p-2.5 text-center">
|
||||
<p className="text-base font-bold text-app-brand">
|
||||
{user?.hasFullBook ? "全部" : user?.purchasedSections.length || 0}
|
||||
</p>
|
||||
<p className="text-app-text-muted text-xs">已购章节</p>
|
||||
</div>
|
||||
<div className="bg-app-card/60 rounded-lg p-2.5 text-center">
|
||||
<p className="text-base font-bold text-app-brand">¥{(user?.earnings || 0).toFixed(1)}</p>
|
||||
<p className="text-app-text-muted text-xs">累计收益</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据统计卡片 */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-4">
|
||||
{/* 购买进度 */}
|
||||
<div className="bg-white/[0.03] rounded-2xl p-4 border border-white/[0.06]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-white/40 text-xs">购买进度</span>
|
||||
<span className="text-[var(--app-brand)] text-xs font-medium">{purchaseProgress}%</span>
|
||||
</div>
|
||||
<div className="h-1.5 bg-white/10 rounded-full overflow-hidden mb-2">
|
||||
<div
|
||||
className="h-full bg-[var(--app-brand)] rounded-full transition-all"
|
||||
style={{ width: `${purchaseProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className="text-white text-lg font-bold">
|
||||
{user?.hasFullBook ? "全书" : `${purchasedCount}/${totalSections}`}
|
||||
</p>
|
||||
<p className="text-white/30 text-[10px]">已购章节</p>
|
||||
</div>
|
||||
|
||||
{/* 分销收益 */}
|
||||
<div className="bg-white/[0.03] rounded-2xl p-4 border border-white/[0.06]">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-white/40 text-xs">累计收益</span>
|
||||
<Share2 className="w-3.5 h-3.5 text-white/30" />
|
||||
</div>
|
||||
<p className="text-[var(--app-brand)] text-lg font-bold mb-1">¥{(user?.earnings || 0).toFixed(2)}</p>
|
||||
<p className="text-white/30 text-[10px]">推荐{user?.referralCount || 0}人</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 购买整本书提示 */}
|
||||
{!user?.hasFullBook && (
|
||||
<Link href="/chapters" className="block mb-4">
|
||||
<div className="bg-gradient-to-r from-[var(--app-brand)]/10 to-purple-500/10 rounded-2xl p-4 border border-[var(--app-brand)]/20">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<BookOpen className="w-5 h-5 text-[var(--app-brand)]" />
|
||||
<div>
|
||||
<p className="text-white text-sm font-medium">购买整本书</p>
|
||||
<p className="text-white/40 text-xs">解锁全部{totalSections}章内容</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-[var(--app-brand)] font-bold">¥{fullBookPrice}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 菜单列表 */}
|
||||
<div className="px-4">
|
||||
<div className="bg-white/[0.03] rounded-2xl overflow-hidden border border-white/[0.06]">
|
||||
<Link href="/my/purchases" className="flex items-center justify-between p-4 border-b border-white/[0.04]">
|
||||
<div className="flex items-center gap-3">
|
||||
<ShoppingBag className="w-4 h-4 text-white/40" />
|
||||
<span className="text-sm">我的购买</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-white/40 text-xs">{purchasedCount}章</span>
|
||||
<ChevronRight className="w-4 h-4 text-white/20" />
|
||||
</div>
|
||||
</Link>
|
||||
{/* Menu Items */}
|
||||
<div className="p-4">
|
||||
<div className="max-w-xs mx-auto space-y-2">
|
||||
{/* Purchase prompt */}
|
||||
{!user?.hasFullBook && (
|
||||
<Link href="/chapters" className="block">
|
||||
<div className="bg-gradient-to-r from-app-brand/20 to-app-card rounded-lg p-3 border border-app-brand/30">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<BookOpen className="w-4 h-4 text-app-brand" />
|
||||
<span className="text-app-text text-sm">购买整本书</span>
|
||||
</div>
|
||||
<span className="text-app-brand font-bold text-sm">¥{fullBookPrice}</span>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)}
|
||||
|
||||
<Link href="/my/referral" className="flex items-center justify-between p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<Share2 className="w-4 h-4 text-white/40" />
|
||||
<span className="text-sm">分销收益</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[var(--app-brand)] text-xs">¥{(user?.earnings || 0).toFixed(1)}</span>
|
||||
<ChevronRight className="w-4 h-4 text-white/20" />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
{/* Menu List - simplified, removed settings and docs */}
|
||||
<div className="bg-app-card/60 rounded-lg overflow-hidden">
|
||||
<Link href="/my/purchases" className="flex items-center justify-between p-3 border-b border-app-border">
|
||||
<div className="flex items-center gap-2">
|
||||
<ShoppingBag className="w-4 h-4 text-app-text-muted" />
|
||||
<span className="text-sm">我的购买</span>
|
||||
</div>
|
||||
<ChevronRight className="w-4 h-4 text-app-text-muted" />
|
||||
</Link>
|
||||
|
||||
{/* 邀请码 */}
|
||||
<div className="bg-white/[0.03] rounded-2xl p-4 border border-white/[0.06] mt-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-white/40 text-xs mb-1">我的邀请码</p>
|
||||
<code className="text-[var(--app-brand)] font-mono text-sm">{user?.referralCode}</code>
|
||||
</div>
|
||||
<button
|
||||
onClick={copyCode}
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-lg bg-white/5 text-white/60 text-xs"
|
||||
>
|
||||
{copied ? <Check className="w-3 h-3" /> : <Copy className="w-3 h-3" />}
|
||||
{copied ? "已复制" : "复制"}
|
||||
</button>
|
||||
<Link href="/my/referral" className="flex items-center justify-between p-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Share2 className="w-4 h-4 text-app-text-muted" />
|
||||
<span className="text-sm">分销收益</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<span className="text-app-brand text-xs">¥{(user?.earnings || 0).toFixed(1)}</span>
|
||||
<ChevronRight className="w-4 h-4 text-app-text-muted" />
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
<p className="text-white/30 text-[10px] mt-2">分享给好友,好友购买你获得90%佣金</p>
|
||||
</div>
|
||||
|
||||
{/* 退出登录 */}
|
||||
<button
|
||||
onClick={logout}
|
||||
className="w-full flex items-center justify-center gap-2 p-3 mt-6 text-white/40 text-sm"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
<span>退出登录</span>
|
||||
</button>
|
||||
{/* Referral Code */}
|
||||
<div className="bg-app-card/60 rounded-lg p-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-app-text-muted text-xs">我的邀请码</p>
|
||||
<code className="text-app-brand font-mono text-sm">{user?.referralCode}</code>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => navigator.clipboard.writeText(user?.referralCode || "")}
|
||||
className="text-app-text-muted text-xs hover:text-app-text px-2 py-1 rounded bg-app-card"
|
||||
>
|
||||
复制
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Logout */}
|
||||
<button
|
||||
onClick={logout}
|
||||
className="w-full flex items-center justify-center gap-2 p-2.5 text-app-text-muted hover:text-red-400 transition-colors text-sm"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
<span>退出登录</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
|
||||
136
app/page.tsx
136
app/page.tsx
@@ -1,133 +1,9 @@
|
||||
"use client"
|
||||
import { HomeScreen } from "@/components/home-screen"
|
||||
import { getBookStructure } from "@/lib/book-file-system"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import Link from "next/link"
|
||||
import { ChevronRight, Sparkles, User } from "lucide-react"
|
||||
import { bookData, getAllSections, getFullBookPrice } from "@/lib/book-data"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { AuthModal } from "@/components/modules/auth/auth-modal"
|
||||
export const dynamic = "force-dynamic"
|
||||
|
||||
export default function HomePage() {
|
||||
const [sectionsCount, setSectionsCount] = useState(64)
|
||||
const [isAuthOpen, setIsAuthOpen] = useState(false)
|
||||
const { isLoggedIn, user } = useStore()
|
||||
|
||||
useEffect(() => {
|
||||
const sections = getAllSections()
|
||||
setSectionsCount(sections.length)
|
||||
}, [])
|
||||
|
||||
// 计算每篇的章节数
|
||||
const partsWithCount = bookData.map((part) => ({
|
||||
...part,
|
||||
chaptersCount: part.chapters.length,
|
||||
sectionsCount: part.chapters.reduce((acc, c) => acc + c.sections.length, 0),
|
||||
}))
|
||||
|
||||
return (
|
||||
<main className="min-h-screen bg-black text-white pb-20 overflow-hidden">
|
||||
{/* 背景 */}
|
||||
<div className="fixed inset-0 bg-gradient-to-b from-black via-[#0a0a0a] to-[#080808] -z-10" />
|
||||
<div className="fixed top-0 left-1/2 -translate-x-1/2 w-[400px] h-[400px] bg-[var(--app-brand)] opacity-[0.04] blur-[100px] rounded-full -z-10" />
|
||||
|
||||
{/* 顶部用户状态栏 */}
|
||||
<div className="px-4 pt-10 pb-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<Sparkles className="w-4 h-4 text-[var(--app-brand)]" />
|
||||
<span className="text-[var(--app-brand)] text-xs font-medium">Soul · 派对房</span>
|
||||
</div>
|
||||
{isLoggedIn ? (
|
||||
<Link href="/my" className="flex items-center gap-1.5 text-xs text-white/60">
|
||||
<User className="w-3.5 h-3.5" />
|
||||
<span>{user?.nickname}</span>
|
||||
</Link>
|
||||
) : (
|
||||
<button onClick={() => setIsAuthOpen(true)} className="text-xs text-[var(--app-brand)]">
|
||||
登录
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 书籍信息头部 */}
|
||||
<div className="px-4 pb-6 text-center">
|
||||
<h1 className="text-2xl font-bold mb-2 leading-tight">
|
||||
一场SOUL的
|
||||
<span className="text-[var(--app-brand)]">创业实验场</span>
|
||||
</h1>
|
||||
<p className="text-white/50 text-xs mb-3">来自Soul派对房的真实商业故事</p>
|
||||
|
||||
{/* 统计数据 */}
|
||||
<div className="flex items-center justify-center gap-6 text-xs">
|
||||
<div>
|
||||
<span className="text-[var(--app-brand)] font-bold text-lg">{sectionsCount}</span>
|
||||
<span className="text-white/40 ml-1">章节</span>
|
||||
</div>
|
||||
<div className="w-px h-4 bg-white/10" />
|
||||
<div>
|
||||
<span className="text-white font-bold text-lg">5</span>
|
||||
<span className="text-white/40 ml-1">大篇</span>
|
||||
</div>
|
||||
<div className="w-px h-4 bg-white/10" />
|
||||
<div>
|
||||
<span className="text-white/60">作者</span>
|
||||
<span className="text-[var(--app-brand)] ml-1 font-medium">卡若</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 目录列表 - 紧凑显示在首页 */}
|
||||
<div className="px-4">
|
||||
<div className="bg-white/[0.03] rounded-2xl overflow-hidden border border-white/[0.06]">
|
||||
{partsWithCount.map((part, index) => (
|
||||
<Link key={part.id} href={`/chapters?part=${part.id}`} className="block">
|
||||
<div
|
||||
className={`flex items-center gap-3 p-3.5 active:bg-white/5 transition-colors ${
|
||||
index !== partsWithCount.length - 1 ? "border-b border-white/[0.04]" : ""
|
||||
}`}
|
||||
>
|
||||
{/* 序号 */}
|
||||
<div className="w-8 h-8 rounded-lg bg-[var(--app-brand)]/10 flex items-center justify-center flex-shrink-0">
|
||||
<span className="text-[var(--app-brand)] font-bold text-xs">{part.number}</span>
|
||||
</div>
|
||||
|
||||
{/* 内容 */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-white font-medium text-sm">{part.title}</h3>
|
||||
<span className="text-white/30 text-[10px]">{part.sectionsCount}章</span>
|
||||
</div>
|
||||
<p className="text-white/40 text-xs truncate">{part.subtitle}</p>
|
||||
</div>
|
||||
|
||||
<ChevronRight className="w-4 h-4 text-white/20 flex-shrink-0" />
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* 序言和尾声 */}
|
||||
<div className="grid grid-cols-2 gap-2 mt-3">
|
||||
<Link href="/read/preface" className="block">
|
||||
<div className="bg-white/[0.03] rounded-xl p-3 border border-white/[0.06] active:bg-white/5">
|
||||
<p className="text-white/30 text-[10px] mb-1">序言</p>
|
||||
<p className="text-white text-xs leading-relaxed line-clamp-2">为什么我每天早上6点在Soul开播?</p>
|
||||
</div>
|
||||
</Link>
|
||||
<Link href="/read/epilogue" className="block">
|
||||
<div className="bg-white/[0.03] rounded-xl p-3 border border-white/[0.06] active:bg-white/5">
|
||||
<p className="text-white/30 text-[10px] mb-1">尾声</p>
|
||||
<p className="text-white text-xs leading-relaxed line-clamp-2">努力不是关键,选择才是</p>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{/* 底部提示 */}
|
||||
<p className="text-center text-white/30 text-[10px] mt-4 mb-2">首章免费 · 单章¥1 · 全书¥{getFullBookPrice()}</p>
|
||||
</div>
|
||||
|
||||
<AuthModal isOpen={isAuthOpen} onClose={() => setIsAuthOpen(false)} />
|
||||
</main>
|
||||
)
|
||||
export default async function HomePage() {
|
||||
const parts = getBookStructure()
|
||||
return <HomeScreen parts={parts} />
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user