Files
soul/app/page.tsx
v0 1e25c7134a feat: complete product overhaul
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>
2026-01-14 05:24:13 +00:00

350 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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">6Soul开播?</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>
)
}