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:
@@ -2,56 +2,60 @@
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { Home, User, Handshake } from "lucide-react"
|
||||
import { useState } from "react"
|
||||
import { MatchModal } from "@/components/match-modal"
|
||||
import { Home, User } from "lucide-react"
|
||||
|
||||
export function BottomNav() {
|
||||
const pathname = usePathname()
|
||||
const [showMatch, setShowMatch] = useState(false)
|
||||
|
||||
// 在管理后台不显示底部导航
|
||||
if (pathname.startsWith("/admin")) {
|
||||
// 在文档页面和管理后台不显示底部导航
|
||||
if (pathname.startsWith("/documentation") || pathname.startsWith("/admin")) {
|
||||
return null
|
||||
}
|
||||
|
||||
const navItems = [
|
||||
{ href: "/", icon: Home, label: "首页" },
|
||||
{ href: "/match", emoji: "🤝", label: "匹配合作" },
|
||||
{ href: "/my", icon: User, label: "我的" },
|
||||
]
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* iOS风格底部导航 - 只有3个按钮,匹配在当前页面弹窗 */}
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-40 bg-black/80 backdrop-blur-xl border-t border-white/[0.06] safe-bottom">
|
||||
{/* iOS风格底部导航 - 只有3个按钮 */}
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-40 glass-nav safe-bottom">
|
||||
<div className="flex items-center justify-around py-2 max-w-lg mx-auto">
|
||||
{/* 首页 */}
|
||||
<Link href="/" className="flex flex-col items-center py-2 px-6 transition-all">
|
||||
<Home
|
||||
className={`w-5 h-5 mb-0.5 ${pathname === "/" ? "text-[var(--app-brand)]" : "text-white/40"}`}
|
||||
strokeWidth={pathname === "/" ? 2.5 : 1.5}
|
||||
/>
|
||||
<span className={`text-[10px] ${pathname === "/" ? "text-[var(--app-brand)]" : "text-white/40"}`}>
|
||||
首页
|
||||
</span>
|
||||
</Link>
|
||||
{navItems.map((item, index) => {
|
||||
const isActive = pathname === item.href || (item.href === '/match' && pathname.startsWith('/match'))
|
||||
const Icon = item.icon
|
||||
|
||||
{/* 匹配合作 - 点击弹出弹窗而不是跳转 */}
|
||||
<button onClick={() => setShowMatch(true)} className="flex flex-col items-center py-2 px-6 transition-all">
|
||||
<Handshake className="w-5 h-5 mb-0.5 text-white/40" strokeWidth={1.5} />
|
||||
<span className="text-[10px] text-white/40">匹配</span>
|
||||
</button>
|
||||
|
||||
{/* 我的 */}
|
||||
<Link href="/my" className="flex flex-col items-center py-2 px-6 transition-all">
|
||||
<User
|
||||
className={`w-5 h-5 mb-0.5 ${pathname.startsWith("/my") ? "text-[var(--app-brand)]" : "text-white/40"}`}
|
||||
strokeWidth={pathname.startsWith("/my") ? 2.5 : 1.5}
|
||||
/>
|
||||
<span className={`text-[10px] ${pathname.startsWith("/my") ? "text-[var(--app-brand)]" : "text-white/40"}`}>
|
||||
我的
|
||||
</span>
|
||||
</Link>
|
||||
return (
|
||||
<Link
|
||||
key={index}
|
||||
href={item.href!}
|
||||
className="flex flex-col items-center py-2 px-4 sm:px-6 touch-feedback transition-all duration-200"
|
||||
>
|
||||
<div className={`w-7 h-7 flex items-center justify-center mb-1 transition-colors ${
|
||||
isActive ? "text-[var(--app-brand)]" : "text-[var(--app-text-tertiary)]"
|
||||
}`}>
|
||||
{item.emoji ? (
|
||||
<span className="text-2xl">{item.emoji}</span>
|
||||
) : (
|
||||
<Icon className="w-6 h-6" strokeWidth={isActive ? 2.5 : 1.5} />
|
||||
)}
|
||||
</div>
|
||||
<span className={`text-[10px] font-medium transition-colors ${
|
||||
isActive ? "text-[var(--app-brand)]" : "text-[var(--app-text-tertiary)]"
|
||||
}`}>
|
||||
{item.label}
|
||||
</span>
|
||||
{/* 激活指示器 */}
|
||||
{isActive && (
|
||||
<div className="absolute -bottom-0.5 w-1 h-1 rounded-full bg-[var(--app-brand)]" />
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* 匹配弹窗 */}
|
||||
<MatchModal isOpen={showMatch} onClose={() => setShowMatch(false)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
532
components/home-screen.tsx
Normal file
532
components/home-screen.tsx
Normal file
@@ -0,0 +1,532 @@
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { useState, useEffect, useRef } from "react"
|
||||
import { type Part, getAllSections, getFullBookPrice, specialSections } from "@/lib/book-data"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { BookOpen, Lock, Check, Sparkles, ChevronRight, User, TrendingUp } from "lucide-react"
|
||||
import { AuthModal } from "./modules/auth/auth-modal"
|
||||
import { PaymentModal } from "./modules/payment/payment-modal"
|
||||
import { ReadingModal } from "./reading-modal"
|
||||
import { MatchingCircle } from "./matching-circle"
|
||||
|
||||
interface HomeScreenProps {
|
||||
parts: Part[]
|
||||
}
|
||||
|
||||
export function HomeScreen({ parts }: HomeScreenProps) {
|
||||
const [activeTab, setActiveTab] = useState<"home" | "match" | "my">("home")
|
||||
const [selectedSection, setSelectedSection] = useState<{ id: string; title: string; filePath: string } | null>(null)
|
||||
const [isAuthOpen, setIsAuthOpen] = useState(false)
|
||||
const [isPaymentOpen, setIsPaymentOpen] = useState(false)
|
||||
const [paymentType, setPaymentType] = useState<"section" | "fullbook">("section")
|
||||
const [paymentSectionId, setPaymentSectionId] = useState<string>("")
|
||||
const [paymentSectionTitle, setPaymentSectionTitle] = useState<string>("")
|
||||
const [paymentAmount, setPaymentAmount] = useState(1)
|
||||
|
||||
const { user, isLoggedIn, hasPurchased } = useStore()
|
||||
const [mounted, setMounted] = useState(false)
|
||||
|
||||
const allSections = getAllSections()
|
||||
const fullBookPrice = getFullBookPrice()
|
||||
const totalSections = allSections.length
|
||||
const purchasedCount = user?.hasFullBook ? totalSections : user?.purchasedSections?.length || 0
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true)
|
||||
}, [])
|
||||
|
||||
// 点击章节
|
||||
const handleSectionClick = (section: {
|
||||
id: string
|
||||
title: string
|
||||
filePath: string
|
||||
isFree: boolean
|
||||
price: number
|
||||
}) => {
|
||||
const canAccess = section.isFree || (isLoggedIn && hasPurchased(section.id))
|
||||
|
||||
if (canAccess) {
|
||||
// 直接打开阅读弹窗
|
||||
setSelectedSection({ id: section.id, title: section.title, filePath: section.filePath })
|
||||
} else {
|
||||
// 需要购买
|
||||
if (!isLoggedIn) {
|
||||
setIsAuthOpen(true)
|
||||
} else {
|
||||
setPaymentSectionId(section.id)
|
||||
setPaymentSectionTitle(section.title)
|
||||
setPaymentAmount(section.price)
|
||||
setPaymentType("section")
|
||||
setIsPaymentOpen(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 购买全书
|
||||
const handleBuyFullBook = () => {
|
||||
if (!isLoggedIn) {
|
||||
setIsAuthOpen(true)
|
||||
return
|
||||
}
|
||||
setPaymentType("fullbook")
|
||||
setPaymentAmount(fullBookPrice)
|
||||
setIsPaymentOpen(true)
|
||||
}
|
||||
|
||||
if (!mounted) {
|
||||
return (
|
||||
<div className="h-screen bg-black flex items-center justify-center">
|
||||
<div className="w-8 h-8 border-2 border-[var(--app-brand)] border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-black text-white flex flex-col overflow-hidden">
|
||||
{/* 主内容区域 - 根据Tab切换 */}
|
||||
<div className="flex-1 overflow-hidden">
|
||||
{activeTab === "home" && (
|
||||
<HomeTab
|
||||
parts={parts}
|
||||
totalSections={totalSections}
|
||||
fullBookPrice={fullBookPrice}
|
||||
purchasedCount={purchasedCount}
|
||||
isLoggedIn={isLoggedIn}
|
||||
hasPurchased={hasPurchased}
|
||||
onSectionClick={handleSectionClick}
|
||||
onBuyFullBook={handleBuyFullBook}
|
||||
/>
|
||||
)}
|
||||
|
||||
{activeTab === "match" && <MatchTab />}
|
||||
|
||||
{activeTab === "my" && (
|
||||
<MyTab
|
||||
user={user}
|
||||
isLoggedIn={isLoggedIn}
|
||||
totalSections={totalSections}
|
||||
purchasedCount={purchasedCount}
|
||||
onLogin={() => setIsAuthOpen(true)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 底部导航 - 固定三个Tab */}
|
||||
<nav className="flex-shrink-0 glass-nav safe-bottom">
|
||||
<div className="flex items-center justify-around py-2">
|
||||
<TabButton
|
||||
active={activeTab === "home"}
|
||||
onClick={() => setActiveTab("home")}
|
||||
icon={<BookOpen className="w-5 h-5" />}
|
||||
label="首页"
|
||||
/>
|
||||
<TabButton
|
||||
active={activeTab === "match"}
|
||||
onClick={() => setActiveTab("match")}
|
||||
icon={<Sparkles className="w-5 h-5" />}
|
||||
label="匹配"
|
||||
/>
|
||||
<TabButton
|
||||
active={activeTab === "my"}
|
||||
onClick={() => setActiveTab("my")}
|
||||
icon={<User className="w-5 h-5" />}
|
||||
label="我的"
|
||||
/>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{/* 阅读弹窗 - 原地展示内容 */}
|
||||
{selectedSection && (
|
||||
<ReadingModal
|
||||
section={selectedSection}
|
||||
onClose={() => setSelectedSection(null)}
|
||||
onPurchase={(sectionId, title, price) => {
|
||||
setPaymentSectionId(sectionId)
|
||||
setPaymentSectionTitle(title)
|
||||
setPaymentAmount(price)
|
||||
setPaymentType("section")
|
||||
setIsPaymentOpen(true)
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 弹窗 */}
|
||||
<AuthModal isOpen={isAuthOpen} onClose={() => setIsAuthOpen(false)} />
|
||||
<PaymentModal
|
||||
isOpen={isPaymentOpen}
|
||||
onClose={() => setIsPaymentOpen(false)}
|
||||
type={paymentType}
|
||||
sectionId={paymentSectionId}
|
||||
sectionTitle={paymentSectionTitle}
|
||||
amount={paymentAmount}
|
||||
onSuccess={() => {
|
||||
setIsPaymentOpen(false)
|
||||
window.location.reload()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Tab按钮组件
|
||||
function TabButton({
|
||||
active,
|
||||
onClick,
|
||||
icon,
|
||||
label,
|
||||
}: {
|
||||
active: boolean
|
||||
onClick: () => void
|
||||
icon: React.ReactNode
|
||||
label: string
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`flex flex-col items-center gap-1 px-6 py-2 transition-all touch-feedback ${
|
||||
active ? "text-[var(--app-brand)]" : "text-[var(--app-text-tertiary)]"
|
||||
}`}
|
||||
>
|
||||
{icon}
|
||||
<span className="text-xs">{label}</span>
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// 首页Tab - 书籍总览+完整目录
|
||||
function HomeTab({
|
||||
parts,
|
||||
totalSections,
|
||||
fullBookPrice,
|
||||
purchasedCount,
|
||||
isLoggedIn,
|
||||
hasPurchased,
|
||||
onSectionClick,
|
||||
onBuyFullBook,
|
||||
}: {
|
||||
parts: Part[]
|
||||
totalSections: number
|
||||
fullBookPrice: number
|
||||
purchasedCount: number
|
||||
isLoggedIn: boolean
|
||||
hasPurchased: (id: string) => boolean
|
||||
onSectionClick: (section: any) => void
|
||||
onBuyFullBook: () => void
|
||||
}) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
return (
|
||||
<div ref={scrollRef} className="h-full overflow-y-auto scrollbar-hide">
|
||||
{/* 书籍总览区 - 精简版 */}
|
||||
<div className="px-4 pt-8 pb-4">
|
||||
<div className="text-center mb-6">
|
||||
<div className="inline-flex items-center gap-2 px-3 py-1.5 glass-card mb-4">
|
||||
<Sparkles className="w-3.5 h-3.5 text-[var(--app-brand)]" />
|
||||
<span className="text-[var(--app-brand)] text-xs">Soul · 派对房</span>
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold mb-2">一场SOUL的创业实验场</h1>
|
||||
<p className="text-[var(--app-text-tertiary)] text-sm">来自Soul派对房的真实商业故事</p>
|
||||
</div>
|
||||
|
||||
{/* 价格信息 */}
|
||||
<div className="glass-card p-4 mb-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-6">
|
||||
<div>
|
||||
<p className="text-2xl font-bold text-[var(--app-brand)]">¥{fullBookPrice}</p>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs">整本价格</p>
|
||||
</div>
|
||||
<div className="w-px h-10 bg-[var(--app-separator)]" />
|
||||
<div>
|
||||
<p className="text-2xl font-bold">{totalSections}</p>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs">商业案例</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={onBuyFullBook} className="btn-ios text-sm px-4 py-2">
|
||||
购买全书
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 完整目录 - 一次性展示所有章节 */}
|
||||
<div className="px-4 pb-24">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-[var(--app-text-secondary)] text-sm font-medium">全书目录</h2>
|
||||
<span className="text-[var(--app-text-tertiary)] text-xs">
|
||||
已购 {purchasedCount}/{totalSections}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 序言 */}
|
||||
<SectionItem
|
||||
id="preface"
|
||||
number="序"
|
||||
title="为什么我每天早上6点在Soul开播?"
|
||||
isFree={true}
|
||||
isPurchased={true}
|
||||
onClick={() =>
|
||||
onSectionClick({
|
||||
id: "preface",
|
||||
title: specialSections.preface.title,
|
||||
filePath: specialSections.preface.filePath,
|
||||
isFree: true,
|
||||
price: 0,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
||||
{/* 所有篇章和小节 */}
|
||||
{parts.map((part) => (
|
||||
<div key={part.id} className="mb-4">
|
||||
{/* 篇章标题 */}
|
||||
<div className="flex items-center gap-3 py-3 px-2">
|
||||
<div className="w-8 h-8 rounded-lg bg-[var(--app-brand-light)] flex items-center justify-center">
|
||||
<span className="text-[var(--app-brand)] font-bold text-sm">{part.number}</span>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-white font-semibold text-sm">{part.title}</h3>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs">{part.subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 该篇章下的所有小节 */}
|
||||
<div className="glass-card overflow-hidden">
|
||||
{part.chapters.map((chapter) =>
|
||||
chapter.sections.map((section, sectionIndex) => {
|
||||
const isPurchased = isLoggedIn && hasPurchased(section.id)
|
||||
return (
|
||||
<SectionItem
|
||||
key={section.id}
|
||||
id={section.id}
|
||||
number={section.id}
|
||||
title={section.title}
|
||||
isFree={section.isFree}
|
||||
isPurchased={isPurchased}
|
||||
price={section.price}
|
||||
isLast={sectionIndex === chapter.sections.length - 1}
|
||||
onClick={() => onSectionClick(section)}
|
||||
/>
|
||||
)
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
{/* 尾声 */}
|
||||
<SectionItem
|
||||
id="epilogue"
|
||||
number="尾"
|
||||
title="努力不是关键,选择才是"
|
||||
isFree={true}
|
||||
isPurchased={true}
|
||||
onClick={() =>
|
||||
onSectionClick({
|
||||
id: "epilogue",
|
||||
title: specialSections.epilogue.title,
|
||||
filePath: specialSections.epilogue.filePath,
|
||||
isFree: true,
|
||||
price: 0,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 章节列表项
|
||||
function SectionItem({
|
||||
id,
|
||||
number,
|
||||
title,
|
||||
isFree,
|
||||
isPurchased,
|
||||
price = 1,
|
||||
isLast = false,
|
||||
onClick,
|
||||
}: {
|
||||
id: string
|
||||
number: string
|
||||
title: string
|
||||
isFree: boolean
|
||||
isPurchased: boolean
|
||||
price?: number
|
||||
isLast?: boolean
|
||||
onClick: () => void
|
||||
}) {
|
||||
const canAccess = isFree || isPurchased
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`w-full flex items-center gap-3 px-4 py-3 text-left transition-all touch-feedback ${
|
||||
!isLast ? "border-b border-[var(--app-separator)]" : ""
|
||||
}`}
|
||||
>
|
||||
{/* 状态图标 */}
|
||||
<div
|
||||
className={`w-6 h-6 rounded-full flex items-center justify-center flex-shrink-0 ${
|
||||
canAccess ? "bg-[var(--app-brand-light)]" : "bg-[var(--app-bg-tertiary)]"
|
||||
}`}
|
||||
>
|
||||
{canAccess ? (
|
||||
<Check className="w-3.5 h-3.5 text-[var(--app-brand)]" />
|
||||
) : (
|
||||
<Lock className="w-3 h-3 text-[var(--app-text-tertiary)]" />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 编号和标题 */}
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[var(--app-brand)] text-xs font-medium">{number}</span>
|
||||
<span className={`text-sm truncate ${canAccess ? "text-white" : "text-[var(--app-text-secondary)]"}`}>
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 价格/状态 */}
|
||||
<div className="flex-shrink-0">
|
||||
{isFree ? (
|
||||
<span className="text-[var(--app-brand)] text-xs">免费</span>
|
||||
) : isPurchased ? (
|
||||
<span className="text-[var(--app-text-tertiary)] text-xs">已购</span>
|
||||
) : (
|
||||
<span className="text-[var(--ios-orange)] text-xs">¥{price}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<ChevronRight className="w-4 h-4 text-[var(--app-text-tertiary)] flex-shrink-0" />
|
||||
</button>
|
||||
)
|
||||
}
|
||||
|
||||
// 匹配Tab - 圆形UI,高级感
|
||||
function MatchTab() {
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center justify-center px-4">
|
||||
<MatchingCircle />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 我的Tab - 数据中心
|
||||
function MyTab({
|
||||
user,
|
||||
isLoggedIn,
|
||||
totalSections,
|
||||
purchasedCount,
|
||||
onLogin,
|
||||
}: {
|
||||
user: any
|
||||
isLoggedIn: boolean
|
||||
totalSections: number
|
||||
purchasedCount: number
|
||||
onLogin: () => void
|
||||
}) {
|
||||
if (!isLoggedIn) {
|
||||
return (
|
||||
<div className="h-full flex flex-col items-center justify-center px-4">
|
||||
<div className="w-20 h-20 rounded-full bg-[var(--app-bg-secondary)] flex items-center justify-center mb-4">
|
||||
<User className="w-10 h-10 text-[var(--app-text-tertiary)]" />
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold mb-2">登录查看更多</h2>
|
||||
<p className="text-[var(--app-text-tertiary)] text-sm mb-6 text-center">查看购买记录、阅读进度、分销收益</p>
|
||||
<button onClick={onLogin} className="btn-ios px-8">
|
||||
立即登录
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const readingProgress = user?.hasFullBook ? 100 : Math.round((purchasedCount / totalSections) * 100)
|
||||
const earnings = user?.earnings || 0
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-y-auto scrollbar-hide px-4 pt-8 pb-24">
|
||||
{/* 用户信息 */}
|
||||
<div className="flex items-center gap-4 mb-8">
|
||||
<div className="w-16 h-16 rounded-full bg-[var(--app-brand-light)] flex items-center justify-center">
|
||||
<User className="w-8 h-8 text-[var(--app-brand)]" />
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold">{user?.nickname || "用户"}</h2>
|
||||
<p className="text-[var(--app-text-tertiary)] text-sm">{user?.phone}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 数据卡片 - 清晰可视化 */}
|
||||
<div className="grid grid-cols-2 gap-3 mb-6">
|
||||
{/* 已购章节 */}
|
||||
<div className="glass-card p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<BookOpen className="w-4 h-4 text-[var(--ios-blue)]" />
|
||||
<span className="text-[var(--app-text-tertiary)] text-xs">已购章节</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-white">{user?.hasFullBook ? "全部" : purchasedCount}</p>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs">共 {totalSections} 章</p>
|
||||
</div>
|
||||
|
||||
{/* 累计收益 */}
|
||||
<div className="glass-card p-4">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<TrendingUp className="w-4 h-4 text-[var(--app-brand)]" />
|
||||
<span className="text-[var(--app-text-tertiary)] text-xs">累计收益</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-[var(--app-brand)]">¥{earnings.toFixed(1)}</p>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs">分销所得</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 阅读进度 */}
|
||||
<div className="glass-card p-4 mb-6">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<span className="text-[var(--app-text-secondary)] text-sm">阅读进度</span>
|
||||
<span className="text-[var(--app-brand)] font-semibold">{readingProgress}%</span>
|
||||
</div>
|
||||
<div className="progress-bar">
|
||||
<div className="progress-bar-fill" style={{ width: `${readingProgress}%` }} />
|
||||
</div>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs mt-2">
|
||||
{user?.hasFullBook ? "已拥有全书" : `还差 ${totalSections - purchasedCount} 章解锁全部内容`}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 邀请码 */}
|
||||
<div className="glass-card p-4 mb-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs mb-1">我的邀请码</p>
|
||||
<code className="text-[var(--app-brand)] font-mono text-lg">{user?.referralCode}</code>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(user?.referralCode || "")
|
||||
alert("邀请码已复制!")
|
||||
}}
|
||||
className="btn-ios-secondary text-sm px-4 py-2"
|
||||
>
|
||||
复制
|
||||
</button>
|
||||
</div>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs mt-3">分享给好友,他人购买你可获得 90% 返利</p>
|
||||
</div>
|
||||
|
||||
{/* 退出登录 */}
|
||||
<button
|
||||
onClick={() => {
|
||||
useStore.getState().logout()
|
||||
window.location.reload()
|
||||
}}
|
||||
className="w-full text-center py-3 text-red-400 text-sm"
|
||||
>
|
||||
退出登录
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,215 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { X, Copy, Check, RefreshCw } from "lucide-react"
|
||||
import { motion, AnimatePresence } from "framer-motion"
|
||||
|
||||
interface MatchUser {
|
||||
id: string
|
||||
nickname: string
|
||||
tags: string[]
|
||||
matchScore: number
|
||||
concept: string
|
||||
wechat: string
|
||||
}
|
||||
|
||||
interface MatchModalProps {
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function MatchModal({ isOpen, onClose }: MatchModalProps) {
|
||||
const [isMatching, setIsMatching] = useState(false)
|
||||
const [currentMatch, setCurrentMatch] = useState<MatchUser | null>(null)
|
||||
const [copied, setCopied] = useState(false)
|
||||
|
||||
const getMockMatch = (): MatchUser => {
|
||||
const data = [
|
||||
{ nickname: "创业小白", tags: ["电商", "私域"], concept: "想找供应链合作伙伴", wechat: "soul_biz_001" },
|
||||
{ nickname: "私域达人", tags: ["内容", "直播"], concept: "擅长内容运营,找技术合伙人", wechat: "soul_biz_002" },
|
||||
{ nickname: "供应链老兵", tags: ["供应链", "跨境"], concept: "有工厂资源,找流量渠道", wechat: "soul_biz_003" },
|
||||
{ nickname: "技术宅", tags: ["AI", "开发"], concept: "会写代码,想找商业方向", wechat: "soul_biz_004" },
|
||||
]
|
||||
const item = data[Math.floor(Math.random() * data.length)]
|
||||
return {
|
||||
id: `user_${Date.now()}`,
|
||||
...item,
|
||||
matchScore: Math.floor(Math.random() * 20) + 80,
|
||||
}
|
||||
}
|
||||
|
||||
const startMatch = () => {
|
||||
setIsMatching(true)
|
||||
setCurrentMatch(null)
|
||||
setCopied(false)
|
||||
|
||||
setTimeout(() => {
|
||||
setIsMatching(false)
|
||||
setCurrentMatch(getMockMatch())
|
||||
}, 2000)
|
||||
}
|
||||
|
||||
const copyWechat = () => {
|
||||
if (currentMatch) {
|
||||
navigator.clipboard.writeText(currentMatch.wechat)
|
||||
setCopied(true)
|
||||
setTimeout(() => setCopied(false), 2000)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setCurrentMatch(null)
|
||||
setIsMatching(false)
|
||||
}
|
||||
}, [isOpen])
|
||||
|
||||
if (!isOpen) return null
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
{/* 背景遮罩 */}
|
||||
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onClose} />
|
||||
|
||||
{/* 弹窗内容 */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.95 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.95 }}
|
||||
className="relative w-full max-w-sm bg-[#111] rounded-3xl overflow-hidden border border-white/10"
|
||||
>
|
||||
{/* 关闭按钮 */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 w-8 h-8 rounded-full bg-white/10 flex items-center justify-center z-10"
|
||||
>
|
||||
<X className="w-4 h-4 text-white/60" />
|
||||
</button>
|
||||
|
||||
<div className="p-6 pt-12">
|
||||
<AnimatePresence mode="wait">
|
||||
{/* 初始状态 - 开始匹配 */}
|
||||
{!isMatching && !currentMatch && (
|
||||
<motion.div
|
||||
key="idle"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="text-center"
|
||||
>
|
||||
<h2 className="text-xl font-bold text-white mb-2">寻找合作伙伴</h2>
|
||||
<p className="text-white/50 text-sm mb-8">找到志同道合的创业伙伴</p>
|
||||
|
||||
{/* 匹配按钮 - 美化的圆形按钮 */}
|
||||
<button onClick={startMatch} className="relative w-40 h-40 mx-auto mb-8 group">
|
||||
{/* 外圈光环 */}
|
||||
<div className="absolute inset-0 rounded-full bg-gradient-to-br from-[var(--app-brand)]/20 to-purple-500/20 animate-pulse" />
|
||||
<div className="absolute inset-2 rounded-full bg-gradient-to-br from-[var(--app-brand)]/30 to-purple-500/30" />
|
||||
|
||||
{/* 主按钮 */}
|
||||
<div className="absolute inset-4 rounded-full bg-gradient-to-br from-[var(--app-brand)] to-purple-500 flex flex-col items-center justify-center shadow-lg shadow-[var(--app-brand)]/30 group-active:scale-95 transition-transform">
|
||||
<span className="text-4xl mb-1">🤝</span>
|
||||
<span className="text-white font-medium text-sm">开始匹配</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<div className="space-y-2 text-left">
|
||||
<div className="flex items-center gap-2 text-white/50 text-xs">
|
||||
<span>💼</span>
|
||||
<span>基于创业方向智能匹配</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-white/50 text-xs">
|
||||
<span>🔒</span>
|
||||
<span>信息安全,双向确认才能联系</span>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* 匹配中 */}
|
||||
{isMatching && (
|
||||
<motion.div
|
||||
key="matching"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="text-center py-12"
|
||||
>
|
||||
<motion.div
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 2, repeat: Number.POSITIVE_INFINITY, ease: "linear" }}
|
||||
className="w-20 h-20 mx-auto mb-6 rounded-full border-4 border-[var(--app-brand)]/30 border-t-[var(--app-brand)]"
|
||||
/>
|
||||
<p className="text-white font-medium">正在匹配中...</p>
|
||||
<p className="text-white/40 text-sm mt-1">寻找最佳合作伙伴</p>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* 匹配成功 */}
|
||||
{currentMatch && !isMatching && (
|
||||
<motion.div
|
||||
key="matched"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0 }}
|
||||
>
|
||||
<div className="text-center mb-4">
|
||||
<span className="text-3xl">✨</span>
|
||||
<h3 className="text-lg font-bold text-white mt-2">匹配成功!</h3>
|
||||
</div>
|
||||
|
||||
{/* 用户卡片 */}
|
||||
<div className="bg-white/5 rounded-2xl p-4 mb-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<h4 className="text-white font-medium">{currentMatch.nickname}</h4>
|
||||
<div className="flex gap-1 mt-1">
|
||||
{currentMatch.tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="px-2 py-0.5 rounded-full text-[10px] bg-[var(--app-brand)]/20 text-[var(--app-brand)]"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className="text-2xl font-bold text-[var(--app-brand)]">{currentMatch.matchScore}%</span>
|
||||
<p className="text-white/40 text-[10px]">匹配度</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-white/60 text-sm">{currentMatch.concept}</p>
|
||||
</div>
|
||||
|
||||
{/* 微信号 */}
|
||||
<div className="bg-white/5 rounded-xl p-3 mb-4">
|
||||
<p className="text-white/40 text-xs mb-1">微信号</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<code className="text-[var(--app-brand)] font-mono">{currentMatch.wechat}</code>
|
||||
<button
|
||||
onClick={copyWechat}
|
||||
className="flex items-center gap-1 px-3 py-1.5 rounded-lg bg-[var(--app-brand)] text-white text-xs"
|
||||
>
|
||||
{copied ? <Check className="w-3 h-3" /> : <Copy className="w-3 h-3" />}
|
||||
{copied ? "已复制" : "复制"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 重新匹配 */}
|
||||
<button
|
||||
onClick={startMatch}
|
||||
className="w-full py-3 rounded-xl bg-white/5 text-white/60 text-sm flex items-center justify-center gap-2"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
换一个
|
||||
</button>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
171
components/matching-circle.tsx
Normal file
171
components/matching-circle.tsx
Normal file
@@ -0,0 +1,171 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Sparkles, Users, BookOpen } from "lucide-react"
|
||||
import { getAllSections } from "@/lib/book-data"
|
||||
import { useStore } from "@/lib/store"
|
||||
|
||||
export function MatchingCircle() {
|
||||
const [isMatching, setIsMatching] = useState(false)
|
||||
const [matchProgress, setMatchProgress] = useState(0)
|
||||
const [matchResult, setMatchResult] = useState<{
|
||||
section: { id: string; title: string }
|
||||
reason: string
|
||||
compatibility: number
|
||||
} | null>(null)
|
||||
|
||||
const { user, isLoggedIn } = useStore()
|
||||
const allSections = getAllSections()
|
||||
|
||||
// 开始匹配
|
||||
const startMatching = () => {
|
||||
if (isMatching) return
|
||||
|
||||
setIsMatching(true)
|
||||
setMatchProgress(0)
|
||||
setMatchResult(null)
|
||||
|
||||
// 模拟匹配进度
|
||||
const interval = setInterval(() => {
|
||||
setMatchProgress((prev) => {
|
||||
if (prev >= 100) {
|
||||
clearInterval(interval)
|
||||
// 匹配完成,生成结果
|
||||
const randomSection = allSections[Math.floor(Math.random() * allSections.length)]
|
||||
const reasons = [
|
||||
"与你的创业方向高度匹配",
|
||||
"适合你当前的发展阶段",
|
||||
"契合你的商业思维模式",
|
||||
"与你的行业背景相关",
|
||||
"符合你的学习偏好",
|
||||
]
|
||||
setMatchResult({
|
||||
section: { id: randomSection.id, title: randomSection.title },
|
||||
reason: reasons[Math.floor(Math.random() * reasons.length)],
|
||||
compatibility: Math.floor(Math.random() * 20) + 80,
|
||||
})
|
||||
setIsMatching(false)
|
||||
return 100
|
||||
}
|
||||
return prev + 2
|
||||
})
|
||||
}, 50)
|
||||
}
|
||||
|
||||
// 保存匹配结果到本地
|
||||
useEffect(() => {
|
||||
if (matchResult && isLoggedIn) {
|
||||
const savedResults = JSON.parse(localStorage.getItem("match_results") || "[]")
|
||||
savedResults.unshift({
|
||||
...matchResult,
|
||||
userId: user?.id,
|
||||
matchedAt: new Date().toISOString(),
|
||||
})
|
||||
// 只保留最近10条
|
||||
localStorage.setItem("match_results", JSON.stringify(savedResults.slice(0, 10)))
|
||||
}
|
||||
}, [matchResult, isLoggedIn, user?.id])
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-sm text-center">
|
||||
{/* 匹配圆环 */}
|
||||
<div className="relative w-64 h-64 mx-auto mb-8">
|
||||
{/* 外圈装饰 */}
|
||||
<div className="absolute inset-0 rounded-full border-2 border-[var(--app-border)] opacity-30" />
|
||||
<div className="absolute inset-2 rounded-full border border-[var(--app-border)] opacity-20" />
|
||||
<div className="absolute inset-4 rounded-full border border-[var(--app-border)] opacity-10" />
|
||||
|
||||
{/* 进度圆环 */}
|
||||
<svg className="absolute inset-0 w-full h-full -rotate-90">
|
||||
<circle cx="128" cy="128" r="120" fill="none" stroke="var(--app-bg-tertiary)" strokeWidth="4" />
|
||||
<circle
|
||||
cx="128"
|
||||
cy="128"
|
||||
r="120"
|
||||
fill="none"
|
||||
stroke="var(--app-brand)"
|
||||
strokeWidth="4"
|
||||
strokeLinecap="round"
|
||||
strokeDasharray={`${2 * Math.PI * 120}`}
|
||||
strokeDashoffset={`${2 * Math.PI * 120 * (1 - matchProgress / 100)}`}
|
||||
className="transition-all duration-100"
|
||||
style={{
|
||||
filter: isMatching ? "drop-shadow(0 0 10px var(--app-brand))" : "none",
|
||||
}}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
{/* 中心内容 */}
|
||||
<div className="absolute inset-8 rounded-full glass-card flex flex-col items-center justify-center">
|
||||
{isMatching ? (
|
||||
<>
|
||||
<Sparkles className="w-10 h-10 text-[var(--app-brand)] animate-pulse mb-2" />
|
||||
<p className="text-[var(--app-brand)] text-2xl font-bold">{matchProgress}%</p>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs">正在匹配...</p>
|
||||
</>
|
||||
) : matchResult ? (
|
||||
<>
|
||||
<div className="text-[var(--app-brand)] text-3xl font-bold mb-1">{matchResult.compatibility}%</div>
|
||||
<p className="text-white text-xs mb-1">匹配度</p>
|
||||
<p className="text-[var(--app-text-tertiary)] text-[10px]">{matchResult.reason}</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Users className="w-10 h-10 text-[var(--app-text-tertiary)] mb-2" />
|
||||
<p className="text-white text-sm">寻找合作伙伴</p>
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs">智能匹配商业故事</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 浮动装饰点 */}
|
||||
{isMatching && (
|
||||
<>
|
||||
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-2 h-2 rounded-full bg-[var(--app-brand)] animate-ping" />
|
||||
<div
|
||||
className="absolute bottom-0 left-1/2 -translate-x-1/2 w-2 h-2 rounded-full bg-[var(--ios-blue)] animate-ping"
|
||||
style={{ animationDelay: "0.5s" }}
|
||||
/>
|
||||
<div
|
||||
className="absolute left-0 top-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-[var(--ios-purple)] animate-ping"
|
||||
style={{ animationDelay: "0.25s" }}
|
||||
/>
|
||||
<div
|
||||
className="absolute right-0 top-1/2 -translate-y-1/2 w-2 h-2 rounded-full bg-[var(--ios-teal)] animate-ping"
|
||||
style={{ animationDelay: "0.75s" }}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 匹配结果 */}
|
||||
{matchResult && (
|
||||
<div className="glass-card p-4 mb-6 text-left">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 rounded-xl bg-[var(--app-brand-light)] flex items-center justify-center">
|
||||
<BookOpen className="w-5 h-5 text-[var(--app-brand)]" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-[var(--app-brand)] text-xs mb-0.5">{matchResult.section.id}</p>
|
||||
<p className="text-white text-sm truncate">{matchResult.section.title}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 匹配按钮 */}
|
||||
<button
|
||||
onClick={startMatching}
|
||||
disabled={isMatching}
|
||||
className={`btn-ios w-full flex items-center justify-center gap-2 ${
|
||||
isMatching ? "opacity-50 cursor-not-allowed" : "glow"
|
||||
}`}
|
||||
>
|
||||
<Sparkles className="w-5 h-5" />
|
||||
<span>{isMatching ? "匹配中..." : matchResult ? "重新匹配" : "开始匹配"}</span>
|
||||
</button>
|
||||
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs mt-4">基于你的阅读偏好,智能推荐适合你的商业故事</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
125
components/reading-modal.tsx
Normal file
125
components/reading-modal.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { X, Lock, Sparkles } from "lucide-react"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { getFullBookPrice } from "@/lib/book-data"
|
||||
|
||||
interface ReadingModalProps {
|
||||
section: { id: string; title: string; filePath: string }
|
||||
onClose: () => void
|
||||
onPurchase: (sectionId: string, title: string, price: number) => void
|
||||
}
|
||||
|
||||
export function ReadingModal({ section, onClose, onPurchase }: ReadingModalProps) {
|
||||
const [content, setContent] = useState("")
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const { isLoggedIn, hasPurchased } = useStore()
|
||||
|
||||
const isFree = section.id === "preface" || section.id === "epilogue"
|
||||
const canAccess = isFree || (isLoggedIn && hasPurchased(section.id))
|
||||
const fullBookPrice = getFullBookPrice()
|
||||
|
||||
useEffect(() => {
|
||||
async function loadContent() {
|
||||
try {
|
||||
const response = await fetch(`/api/content?path=${encodeURIComponent(section.filePath)}`)
|
||||
if (response.ok) {
|
||||
const data = await response.json()
|
||||
setContent(data.content || "")
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to load content:", error)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
loadContent()
|
||||
}, [section.filePath])
|
||||
|
||||
// 计算显示内容
|
||||
const displayContent = canAccess ? content : content.slice(0, Math.floor(content.length * 0.3))
|
||||
const showPaywall = !canAccess && content.length > 0
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 bg-black/90 modal-overlay">
|
||||
<div className="h-full flex flex-col">
|
||||
{/* Header */}
|
||||
<header className="flex-shrink-0 glass-nav px-4 py-3 flex items-center justify-between safe-top">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="w-9 h-9 rounded-full bg-[var(--app-bg-secondary)] flex items-center justify-center touch-feedback"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
<h1 className="text-white font-semibold text-sm truncate flex-1 mx-4 text-center">{section.title}</h1>
|
||||
<div className="w-9" />
|
||||
</header>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex-1 overflow-y-auto scrollbar-hide">
|
||||
<div className="max-w-2xl mx-auto px-5 py-6">
|
||||
{isLoading ? (
|
||||
<div className="space-y-4">
|
||||
{[...Array(10)].map((_, i) => (
|
||||
<div key={i} className="skeleton h-4 rounded" style={{ width: `${Math.random() * 40 + 60}%` }} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<article className="book-content relative">
|
||||
<div className="text-[var(--app-text-secondary)] leading-[1.9] text-[17px]">
|
||||
{displayContent.split("\n").map(
|
||||
(paragraph, index) =>
|
||||
paragraph.trim() && (
|
||||
<p key={index} className="mb-6 text-justify">
|
||||
{paragraph}
|
||||
</p>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 付费墙渐变 */}
|
||||
{showPaywall && (
|
||||
<div className="absolute bottom-0 left-0 right-0 h-48 bg-gradient-to-t from-black to-transparent" />
|
||||
)}
|
||||
</article>
|
||||
|
||||
{/* 付费提示 - 在阅读中途触发 */}
|
||||
{showPaywall && (
|
||||
<div className="mt-8 glass-card-heavy p-8 text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-[var(--app-bg-secondary)] flex items-center justify-center">
|
||||
<Lock className="w-8 h-8 text-[var(--app-text-tertiary)]" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold mb-2">解锁完整内容</h3>
|
||||
<p className="text-[var(--app-text-secondary)] mb-6">您已阅读30%,解锁后可阅读完整内容</p>
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<button
|
||||
onClick={() => onPurchase(section.id, section.title, 1)}
|
||||
className="btn-ios-secondary py-3"
|
||||
>
|
||||
购买本节 ¥1
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onPurchase(section.id, section.title, fullBookPrice)}
|
||||
className="btn-ios py-3 glow flex items-center justify-center gap-2"
|
||||
>
|
||||
<Sparkles className="w-4 h-4" />
|
||||
购买全书 ¥{fullBookPrice} 省82%
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="text-[var(--app-text-tertiary)] text-xs mt-4">
|
||||
分享给好友,他人购买你可获得 90% 返利
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user