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:
v0
2026-01-14 05:17:59 +00:00
parent f3195d9331
commit 59ca3b2bbd
8 changed files with 1124 additions and 628 deletions

View 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>
)
}