Refactor homepage, match feature, data storage, and my page; implement paid reading logic. #VERCEL_SKIP Co-authored-by: undefined <undefined+undefined@users.noreply.github.com>
350 lines
16 KiB
TypeScript
350 lines
16 KiB
TypeScript
"use client"
|
||
|
||
import { useState } from "react"
|
||
import { useRouter } from "next/navigation"
|
||
import { useStore } from "@/lib/store"
|
||
import { bookData, getTotalSectionCount, specialSections } from "@/lib/book-data"
|
||
import { Book, Lock, Unlock, ChevronRight, User, BookOpen } from "lucide-react"
|
||
|
||
export default function HomePage() {
|
||
const router = useRouter()
|
||
const { user, isLoggedIn, hasPurchased } = useStore()
|
||
const [activeTab, setActiveTab] = useState<"home" | "me">("home")
|
||
const [expandedPart, setExpandedPart] = useState<string | null>("part-1")
|
||
|
||
const totalSections = getTotalSectionCount()
|
||
const purchasedCount = user?.purchasedSections?.length || 0
|
||
const hasFullBook = user?.hasFullBook || false
|
||
|
||
// 点击章节
|
||
const handleSectionClick = (sectionId: string, isFree: boolean) => {
|
||
if (isFree || hasFullBook || hasPurchased(sectionId)) {
|
||
router.push(`/read/${sectionId}`)
|
||
} else {
|
||
router.push(`/read/${sectionId}`)
|
||
}
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-black text-white flex flex-col">
|
||
{/* 顶部固定区域 - 书籍信息 */}
|
||
<header className="flex-shrink-0 px-4 pt-safe-top pb-3 bg-gradient-to-b from-black to-transparent">
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-14 h-14 rounded-xl bg-gradient-to-br from-[#30d158] to-[#00c7be] flex items-center justify-center shadow-lg">
|
||
<Book className="w-7 h-7 text-white" />
|
||
</div>
|
||
<div className="flex-1">
|
||
<h1 className="text-lg font-bold tracking-tight">一场SOUL的创业实验场</h1>
|
||
<p className="text-xs text-gray-400 mt-0.5">来自Soul派对房的真实商业故事</p>
|
||
</div>
|
||
<div className="text-right">
|
||
<div className="text-2xl font-bold text-[#30d158]">{totalSections}</div>
|
||
<div className="text-[10px] text-gray-500">章节</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
|
||
{/* 主内容区域 - 可滚动的目录 */}
|
||
{activeTab === "home" ? (
|
||
<main className="flex-1 overflow-y-auto px-4 pb-20 scrollbar-hide">
|
||
{/* 序言入口 */}
|
||
<button
|
||
onClick={() => handleSectionClick("preface", true)}
|
||
className="w-full mb-3 p-3 rounded-xl bg-[#1c1c1e] flex items-center justify-between active:bg-[#2c2c2e] transition-colors"
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-8 h-8 rounded-lg bg-[#30d158]/20 flex items-center justify-center">
|
||
<BookOpen className="w-4 h-4 text-[#30d158]" />
|
||
</div>
|
||
<span className="text-sm font-medium">序言|为什么我每天早上6点在Soul开播?</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-xs text-[#30d158]">免费</span>
|
||
<ChevronRight className="w-4 h-4 text-gray-500" />
|
||
</div>
|
||
</button>
|
||
|
||
{/* 五篇目录 */}
|
||
{bookData.map((part) => (
|
||
<div key={part.id} className="mb-3">
|
||
{/* 篇标题 */}
|
||
<button
|
||
onClick={() => setExpandedPart(expandedPart === part.id ? null : part.id)}
|
||
className="w-full p-3 rounded-xl bg-[#1c1c1e] flex items-center justify-between active:bg-[#2c2c2e] transition-colors"
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#30d158] to-[#00c7be] flex items-center justify-center text-sm font-bold">
|
||
{part.number}
|
||
</div>
|
||
<div className="text-left">
|
||
<div className="text-sm font-semibold">{part.title}</div>
|
||
<div className="text-[10px] text-gray-500">{part.subtitle}</div>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-xs text-gray-500">
|
||
{part.chapters.reduce((acc, ch) => acc + ch.sections.length, 0)}章
|
||
</span>
|
||
<ChevronRight
|
||
className={`w-4 h-4 text-gray-500 transition-transform ${expandedPart === part.id ? "rotate-90" : ""}`}
|
||
/>
|
||
</div>
|
||
</button>
|
||
|
||
{/* 展开的章节列表 */}
|
||
{expandedPart === part.id && (
|
||
<div className="mt-2 ml-2 space-y-1">
|
||
{part.chapters.map((chapter) => (
|
||
<div key={chapter.id} className="rounded-lg bg-[#1c1c1e]/50 overflow-hidden">
|
||
<div className="px-3 py-2 text-xs font-medium text-gray-400 border-b border-white/5">
|
||
{chapter.title}
|
||
</div>
|
||
{chapter.sections.map((section) => {
|
||
const isPurchased = hasFullBook || hasPurchased(section.id)
|
||
const canRead = section.isFree || isPurchased
|
||
|
||
return (
|
||
<button
|
||
key={section.id}
|
||
onClick={() => handleSectionClick(section.id, section.isFree)}
|
||
className="w-full px-3 py-2.5 flex items-center justify-between border-b border-white/5 last:border-0 active:bg-white/5 transition-colors"
|
||
>
|
||
<div className="flex items-center gap-2 flex-1 min-w-0">
|
||
{canRead ? (
|
||
<Unlock className="w-3.5 h-3.5 text-[#30d158] flex-shrink-0" />
|
||
) : (
|
||
<Lock className="w-3.5 h-3.5 text-gray-500 flex-shrink-0" />
|
||
)}
|
||
<span className={`text-xs truncate ${canRead ? "text-white" : "text-gray-400"}`}>
|
||
{section.id} {section.title}
|
||
</span>
|
||
</div>
|
||
<div className="flex items-center gap-2 flex-shrink-0 ml-2">
|
||
{section.isFree ? (
|
||
<span className="text-[10px] text-[#30d158] px-1.5 py-0.5 rounded bg-[#30d158]/10">
|
||
免费
|
||
</span>
|
||
) : isPurchased ? (
|
||
<span className="text-[10px] text-[#30d158]">已购</span>
|
||
) : (
|
||
<span className="text-[10px] text-gray-500">¥{section.price}</span>
|
||
)}
|
||
<ChevronRight className="w-3.5 h-3.5 text-gray-600" />
|
||
</div>
|
||
</button>
|
||
)
|
||
})}
|
||
</div>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
))}
|
||
|
||
{/* 尾声入口 */}
|
||
<button
|
||
onClick={() => handleSectionClick("epilogue", true)}
|
||
className="w-full mb-3 p-3 rounded-xl bg-[#1c1c1e] flex items-center justify-between active:bg-[#2c2c2e] transition-colors"
|
||
>
|
||
<div className="flex items-center gap-3">
|
||
<div className="w-8 h-8 rounded-lg bg-[#30d158]/20 flex items-center justify-center">
|
||
<BookOpen className="w-4 h-4 text-[#30d158]" />
|
||
</div>
|
||
<span className="text-sm font-medium">尾声|这本书的真实目的</span>
|
||
</div>
|
||
<div className="flex items-center gap-2">
|
||
<span className="text-xs text-[#30d158]">免费</span>
|
||
<ChevronRight className="w-4 h-4 text-gray-500" />
|
||
</div>
|
||
</button>
|
||
|
||
{/* 附录 */}
|
||
<div className="mb-3 p-3 rounded-xl bg-[#1c1c1e]">
|
||
<div className="text-xs font-medium text-gray-400 mb-2">附录</div>
|
||
{specialSections.appendix.map((item) => (
|
||
<button
|
||
key={item.id}
|
||
onClick={() => handleSectionClick(item.id, true)}
|
||
className="w-full py-2 flex items-center justify-between border-b border-white/5 last:border-0 active:bg-white/5"
|
||
>
|
||
<span className="text-xs text-gray-300">{item.title}</span>
|
||
<ChevronRight className="w-3.5 h-3.5 text-gray-600" />
|
||
</button>
|
||
))}
|
||
</div>
|
||
</main>
|
||
) : (
|
||
<MyPage />
|
||
)}
|
||
|
||
{/* 底部导航 */}
|
||
<nav className="flex-shrink-0 fixed bottom-0 left-0 right-0 bg-[#1c1c1e]/90 backdrop-blur-xl border-t border-white/10 pb-safe-bottom">
|
||
<div className="flex justify-around py-2">
|
||
<button
|
||
onClick={() => setActiveTab("home")}
|
||
className={`flex flex-col items-center gap-0.5 px-6 py-1 ${activeTab === "home" ? "text-[#30d158]" : "text-gray-500"}`}
|
||
>
|
||
<Book className="w-5 h-5" />
|
||
<span className="text-[10px]">首页</span>
|
||
</button>
|
||
<button
|
||
onClick={() => setActiveTab("me")}
|
||
className={`flex flex-col items-center gap-0.5 px-6 py-1 ${activeTab === "me" ? "text-[#30d158]" : "text-gray-500"}`}
|
||
>
|
||
<User className="w-5 h-5" />
|
||
<span className="text-[10px]">我的</span>
|
||
</button>
|
||
</div>
|
||
</nav>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
function MyPage() {
|
||
const router = useRouter()
|
||
const { user, isLoggedIn, logout } = useStore()
|
||
const [showLogin, setShowLogin] = useState(false)
|
||
|
||
const purchasedCount = user?.purchasedSections?.length || 0
|
||
const hasFullBook = user?.hasFullBook || false
|
||
const earnings = user?.earnings || 0
|
||
const totalSections = getTotalSectionCount()
|
||
|
||
// 计算阅读进度
|
||
const readProgress = hasFullBook ? 100 : Math.round((purchasedCount / totalSections) * 100)
|
||
|
||
if (!isLoggedIn) {
|
||
return (
|
||
<div className="flex-1 flex flex-col items-center justify-center px-6 pb-20">
|
||
<div className="w-20 h-20 rounded-full bg-[#1c1c1e] flex items-center justify-center mb-4">
|
||
<User className="w-10 h-10 text-gray-500" />
|
||
</div>
|
||
<h2 className="text-lg font-semibold mb-2">登录后查看更多</h2>
|
||
<p className="text-sm text-gray-500 text-center mb-6">登录后可查看购买记录、阅读进度和收益</p>
|
||
<button
|
||
onClick={() => router.push("/login")}
|
||
className="px-8 py-3 bg-[#30d158] text-white font-medium rounded-xl active:scale-95 transition-transform"
|
||
>
|
||
立即登录
|
||
</button>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<main className="flex-1 overflow-y-auto px-4 pb-20 scrollbar-hide">
|
||
{/* 用户信息卡片 */}
|
||
<div className="p-4 rounded-2xl bg-gradient-to-br from-[#1c1c1e] to-[#2c2c2e] mb-4">
|
||
<div className="flex items-center gap-3 mb-4">
|
||
<div className="w-14 h-14 rounded-full bg-gradient-to-br from-[#30d158] to-[#00c7be] flex items-center justify-center text-xl font-bold">
|
||
{user?.nickname?.charAt(0) || "U"}
|
||
</div>
|
||
<div className="flex-1">
|
||
<div className="font-semibold">{user?.nickname || "用户"}</div>
|
||
<div className="text-xs text-gray-500">{user?.phone}</div>
|
||
</div>
|
||
{hasFullBook && (
|
||
<div className="px-2 py-1 bg-[#30d158]/20 rounded-lg">
|
||
<span className="text-xs text-[#30d158] font-medium">全书会员</span>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
{/* 数据统计 */}
|
||
<div className="grid grid-cols-3 gap-3">
|
||
<div className="p-3 rounded-xl bg-black/30 text-center">
|
||
<div className="text-xl font-bold text-[#30d158]">{hasFullBook ? totalSections : purchasedCount}</div>
|
||
<div className="text-[10px] text-gray-500 mt-1">已购章节</div>
|
||
</div>
|
||
<div className="p-3 rounded-xl bg-black/30 text-center">
|
||
<div className="text-xl font-bold text-white">{readProgress}%</div>
|
||
<div className="text-[10px] text-gray-500 mt-1">阅读进度</div>
|
||
</div>
|
||
<div className="p-3 rounded-xl bg-black/30 text-center">
|
||
<div className="text-xl font-bold text-[#ff9500]">¥{earnings.toFixed(2)}</div>
|
||
<div className="text-[10px] text-gray-500 mt-1">累计收益</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 阅读进度条 */}
|
||
<div className="p-4 rounded-xl bg-[#1c1c1e] mb-4">
|
||
<div className="flex justify-between items-center mb-2">
|
||
<span className="text-sm font-medium">阅读进度</span>
|
||
<span className="text-xs text-gray-500">
|
||
{hasFullBook ? totalSections : purchasedCount}/{totalSections}章
|
||
</span>
|
||
</div>
|
||
<div className="h-2 bg-black/50 rounded-full overflow-hidden">
|
||
<div
|
||
className="h-full bg-gradient-to-r from-[#30d158] to-[#00c7be] rounded-full transition-all duration-500"
|
||
style={{ width: `${readProgress}%` }}
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 收益明细 */}
|
||
<div className="p-4 rounded-xl bg-[#1c1c1e] mb-4">
|
||
<div className="flex justify-between items-center mb-3">
|
||
<span className="text-sm font-medium">收益明细</span>
|
||
<button className="text-xs text-[#30d158]">查看详情</button>
|
||
</div>
|
||
<div className="space-y-3">
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-gray-400">可提现</span>
|
||
<span className="text-sm font-medium text-[#30d158]">¥{(user?.pendingEarnings || 0).toFixed(2)}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-gray-400">已提现</span>
|
||
<span className="text-sm text-gray-400">¥{(user?.withdrawnEarnings || 0).toFixed(2)}</span>
|
||
</div>
|
||
<div className="flex justify-between items-center">
|
||
<span className="text-xs text-gray-400">推荐人数</span>
|
||
<span className="text-sm text-gray-400">{user?.referralCount || 0}人</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 购买全书入口 */}
|
||
{!hasFullBook && (
|
||
<button
|
||
onClick={() => router.push("/purchase")}
|
||
className="w-full p-4 rounded-xl bg-gradient-to-r from-[#30d158] to-[#00c7be] mb-4 active:scale-[0.98] transition-transform"
|
||
>
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<div className="text-sm font-semibold text-white">解锁全部 {totalSections} 章</div>
|
||
<div className="text-xs text-white/70 mt-0.5">一次购买,永久阅读</div>
|
||
</div>
|
||
<div className="text-right">
|
||
<div className="text-2xl font-bold text-white">¥9.9</div>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
)}
|
||
|
||
{/* 邀请码 */}
|
||
<div className="p-4 rounded-xl bg-[#1c1c1e] mb-4">
|
||
<div className="text-sm font-medium mb-2">我的邀请码</div>
|
||
<div className="flex items-center gap-2">
|
||
<div className="flex-1 p-2 bg-black/30 rounded-lg text-center font-mono text-[#30d158]">
|
||
{user?.referralCode || "N/A"}
|
||
</div>
|
||
<button className="px-4 py-2 bg-[#30d158]/20 text-[#30d158] text-sm rounded-lg active:bg-[#30d158]/30">
|
||
复制
|
||
</button>
|
||
</div>
|
||
<p className="text-[10px] text-gray-500 mt-2">邀请好友购买,获得90%佣金</p>
|
||
</div>
|
||
|
||
{/* 退出登录 */}
|
||
<button
|
||
onClick={logout}
|
||
className="w-full p-3 rounded-xl bg-[#1c1c1e] text-red-500 text-sm font-medium active:bg-[#2c2c2e] transition-colors"
|
||
>
|
||
退出登录
|
||
</button>
|
||
</main>
|
||
)
|
||
}
|