feat: refactor homepage and navigation structure

Remove unused pages, redesign author page, update homepage with new chapter display, and modify bottom navigation.

#VERCEL_SKIP

Co-authored-by: null <4804959+fnvtk@users.noreply.github.com>
This commit is contained in:
v0
2026-01-14 06:39:23 +00:00
parent 78867aac6d
commit d5df83f35b
5 changed files with 664 additions and 662 deletions

View File

@@ -1,54 +1,251 @@
import Link from "next/link"
import { ChevronLeft } from "lucide-react"
import { Button } from "@/components/ui/button"
import { specialSections, FULL_BOOK_PRICE } from "@/lib/book-data"
import { getBookStructure } from "@/lib/book-file-system"
import { ChaptersList } from "@/components/chapters-list"
"use client"
import { BuyFullBookButton } from "@/components/buy-full-book-button"
import { useState } from "react"
import { useRouter } from "next/navigation"
import { ChevronLeft, ChevronRight, Lock, Unlock, Book, BookOpen, Sparkles } from "lucide-react"
import { useStore } from "@/lib/store"
import { bookData, getTotalSectionCount, specialSections, getFullBookPrice } from "@/lib/book-data"
import { AuthModal } from "@/components/modules/auth/auth-modal"
import { PaymentModal } from "@/components/payment-modal"
export const dynamic = 'force-dynamic';
export default function ChaptersPage() {
const router = useRouter()
const { user, isLoggedIn, hasPurchased } = useStore()
const [expandedPart, setExpandedPart] = useState<string | null>("part-1")
const [isAuthOpen, setIsAuthOpen] = useState(false)
const [isPaymentOpen, setIsPaymentOpen] = useState(false)
export default async function ChaptersPage() {
const parts = getBookStructure()
const totalSections = getTotalSectionCount()
const purchasedCount = user?.purchasedSections?.length || 0
const hasFullBook = user?.hasFullBook || false
const fullBookPrice = getFullBookPrice()
// Format special sections for the client component
const specialSectionsData = {
preface: { title: specialSections.preface.title },
epilogue: { title: specialSections.epilogue.title }
const handleSectionClick = (sectionId: string, isFree: boolean) => {
router.push(`/read/${sectionId}`)
}
const handleBuyFullBook = () => {
if (!isLoggedIn) {
setIsAuthOpen(true)
return
}
setIsPaymentOpen(true)
}
return (
<div className="min-h-screen bg-[#0a1628] text-white">
{/* Header */}
<header className="sticky top-0 z-50 bg-[#0a1628]/90 backdrop-blur-md border-b border-gray-800">
<div className="max-w-4xl mx-auto px-4 py-4 flex items-center justify-between">
<Link href="/" className="flex items-center gap-2 text-gray-400 hover:text-white transition-colors">
<ChevronLeft className="w-5 h-5" />
<span></span>
</Link>
<h1 className="text-lg font-semibold"></h1>
<BuyFullBookButton size="sm" price={9.9} className="bg-[#38bdac] hover:bg-[#2da396] text-white" />
<div className="min-h-screen bg-black text-white pb-24">
{/* 顶部导航 */}
<header className="sticky top-0 z-40 bg-black/90 backdrop-blur-xl border-b border-white/5">
<div className="px-4 py-3 flex items-center justify-between">
<button
onClick={() => router.push("/")}
className="w-9 h-9 rounded-full bg-[#1c1c1e] flex items-center justify-center"
>
<ChevronLeft className="w-5 h-5 text-gray-400" />
</button>
<h1 className="text-lg font-semibold text-[#ffd700]"></h1>
<div className="w-9" />
</div>
</header>
{/* Content */}
<main className="max-w-4xl mx-auto px-4 py-8">
<ChaptersList parts={parts} specialSections={specialSectionsData} />
{/* Bottom CTA */}
<div className="mt-12 bg-gradient-to-r from-[#38bdac]/20 to-[#0f2137] rounded-2xl p-6 border border-[#38bdac]/30">
<div className="flex flex-col md:flex-row items-center justify-between gap-4">
<div>
<h3 className="text-white text-lg font-semibold mb-1">,82%</h3>
<p className="text-gray-400">55,,</p>
</div>
<BuyFullBookButton size="lg" price={9.9} className="bg-[#38bdac] hover:bg-[#2da396] text-white px-8">
¥9.9
</BuyFullBookButton>
{/* 书籍信息卡片 */}
<div className="mx-4 mt-4 p-4 rounded-2xl bg-gradient-to-br from-[#1c1c1e] to-[#2c2c2e] border border-white/5">
<div className="flex items-center gap-3">
<div className="w-12 h-12 rounded-xl bg-gradient-to-br from-[#ff3b5c] to-[#ff6b8a] flex items-center justify-center">
<Book className="w-6 h-6 text-white" />
</div>
<div className="flex-1">
<h2 className="text-white font-semibold">SOUL的创业实验场</h2>
<p className="text-gray-500 text-xs mt-0.5">Soul派对房的真实商业故事</p>
</div>
<div className="text-right">
<div className="text-xl font-bold text-[#ff3b5c]">{totalSections}</div>
<div className="text-[10px] text-gray-500"></div>
</div>
</div>
{/* 购买全书按钮 */}
{!hasFullBook && (
<button
onClick={handleBuyFullBook}
className="w-full mt-4 py-3 rounded-xl bg-gradient-to-r from-[#ff3b5c] to-[#ff6b8a] text-white font-medium flex items-center justify-center gap-2 active:scale-[0.98] transition-transform"
>
<Sparkles className="w-4 h-4" />
<span> {totalSections} </span>
<span className="ml-1">¥{fullBookPrice}</span>
</button>
)}
</div>
{/* 目录内容 */}
<main className="px-4 py-4">
{/* 序言 */}
<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 border border-white/5"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-[#ff3b5c]/20 flex items-center justify-center">
<BookOpen className="w-4 h-4 text-[#ff3b5c]" />
</div>
<span className="text-sm font-medium text-white">6Soul开播?</span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-[#00E5FF]"></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 border border-white/5"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-[#ff3b5c] to-[#ff6b8a] flex items-center justify-center text-sm font-bold text-white">
{part.number}
</div>
<div className="text-left">
<div className="text-sm font-semibold text-white">{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 border border-white/5">
<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-[#ff3b5c] 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-[#00E5FF] px-1.5 py-0.5 rounded bg-[#00E5FF]/10">
</span>
) : isPurchased ? (
<span className="text-[10px] text-[#ff3b5c]"></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 border border-white/5"
>
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-[#ff3b5c]/20 flex items-center justify-center">
<BookOpen className="w-4 h-4 text-[#ff3b5c]" />
</div>
<span className="text-sm font-medium text-white"></span>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-[#00E5FF]"></span>
<ChevronRight className="w-4 h-4 text-gray-500" />
</div>
</button>
{/* 附录 */}
<div className="mb-3 p-3 rounded-xl bg-[#1c1c1e] border border-white/5">
<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>
{/* 底部导航 */}
<nav className="fixed bottom-0 left-0 right-0 bg-[#1c1c1e]/95 backdrop-blur-xl border-t border-white/5 pb-safe-bottom">
<div className="px-4 py-3">
<div className="flex items-center justify-between gap-2">
<button
onClick={() => router.push("/")}
className="flex-1 py-2.5 rounded-xl bg-[#2c2c2e] text-white text-sm font-medium text-center active:bg-[#3c3c3e]"
>
</button>
<button className="flex-1 py-2.5 rounded-xl bg-[#ff3b5c]/20 text-[#ff3b5c] text-sm font-medium text-center">
</button>
<button
onClick={() => router.push("/about")}
className="flex-1 py-2.5 rounded-xl bg-[#2c2c2e] text-white text-sm font-medium text-center active:bg-[#3c3c3e]"
>
</button>
<button
onClick={() => router.push("/my")}
className="flex-1 py-2.5 rounded-xl bg-[#2c2c2e] text-white text-sm font-medium text-center active:bg-[#3c3c3e]"
>
</button>
</div>
</div>
</nav>
{/* 登录弹窗 */}
<AuthModal isOpen={isAuthOpen} onClose={() => setIsAuthOpen(false)} />
{/* 支付弹窗 */}
<PaymentModal
isOpen={isPaymentOpen}
onClose={() => setIsPaymentOpen(false)}
type="fullbook"
sectionId=""
sectionTitle=""
amount={fullBookPrice}
onSuccess={() => window.location.reload()}
/>
</div>
)
}