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:
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