refactor: complete system overhaul with new design
Rebuilt user and match pages, simplified login, and updated bottom navigation. #VERCEL_SKIP Co-authored-by: undefined <undefined+undefined@users.noreply.github.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import { ChevronLeft, Lock, Share2, Sparkles } from "lucide-react"
|
||||
import { type Section, getFullBookPrice, getTotalSectionCount } from "@/lib/book-data"
|
||||
import { useStore } from "@/lib/store"
|
||||
import { PaymentModal } from "./payment-modal"
|
||||
import { AuthModal } from "./modules/auth/auth-modal"
|
||||
|
||||
interface ChapterContentProps {
|
||||
section: Section & { filePath: string }
|
||||
@@ -18,6 +19,7 @@ export function ChapterContent({ section, partTitle, chapterTitle }: ChapterCont
|
||||
const [content, setContent] = useState<string>("")
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [isPaymentOpen, setIsPaymentOpen] = useState(false)
|
||||
const [isAuthOpen, setIsAuthOpen] = useState(false)
|
||||
const [paymentType, setPaymentType] = useState<"section" | "fullbook">("section")
|
||||
const [readingProgress, setReadingProgress] = useState(0)
|
||||
|
||||
@@ -68,25 +70,23 @@ export function ChapterContent({ section, partTitle, chapterTitle }: ChapterCont
|
||||
|
||||
const handlePurchaseClick = (type: "section" | "fullbook") => {
|
||||
if (!isLoggedIn) {
|
||||
router.push("/login")
|
||||
setIsAuthOpen(true)
|
||||
return
|
||||
}
|
||||
setPaymentType(type)
|
||||
setIsPaymentOpen(true)
|
||||
}
|
||||
|
||||
// 计算预览内容(前50%)
|
||||
const contentLines = content.split("\n").filter((line) => line.trim())
|
||||
const previewLineCount = Math.ceil(contentLines.length * 0.5)
|
||||
const previewLineCount = Math.ceil(contentLines.length * 0.1) // 改为10%
|
||||
const previewContent = contentLines.slice(0, previewLineCount).join("\n")
|
||||
const hiddenContent = contentLines.slice(previewLineCount).join("\n")
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-black text-white">
|
||||
{/* 阅读进度条 */}
|
||||
<div className="fixed top-0 left-0 right-0 z-50 h-0.5 bg-[#1c1c1e]">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-[#30d158] to-[#00c7be] transition-all duration-150"
|
||||
className="h-full bg-gradient-to-r from-[#ff3b5c] to-[#ff6b8a] transition-all duration-150"
|
||||
style={{ width: `${readingProgress}%` }}
|
||||
/>
|
||||
</div>
|
||||
@@ -122,10 +122,10 @@ export function ChapterContent({ section, partTitle, chapterTitle }: ChapterCont
|
||||
{/* 标题 */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-[#30d158] text-sm font-medium bg-[#30d158]/10 px-3 py-1 rounded-full">
|
||||
<span className="text-[#ff3b5c] text-sm font-medium bg-[#ff3b5c]/10 px-3 py-1 rounded-full">
|
||||
{section.id}
|
||||
</span>
|
||||
{section.isFree && <span className="text-xs text-[#30d158] bg-[#30d158]/10 px-2 py-0.5 rounded">免费</span>}
|
||||
{section.isFree && <span className="text-xs text-[#00E5FF] bg-[#00E5FF]/10 px-2 py-0.5 rounded">免费</span>}
|
||||
</div>
|
||||
<h1 className="text-2xl font-bold text-white leading-tight">{section.title}</h1>
|
||||
</div>
|
||||
@@ -153,7 +153,6 @@ export function ChapterContent({ section, partTitle, chapterTitle }: ChapterCont
|
||||
)}
|
||||
</article>
|
||||
) : (
|
||||
// 付费墙:前半免费,后半付费
|
||||
<div>
|
||||
{/* 免费预览部分 */}
|
||||
<article className="text-gray-300 leading-[1.9] text-[17px]">
|
||||
@@ -175,12 +174,12 @@ export function ChapterContent({ section, partTitle, chapterTitle }: ChapterCont
|
||||
{/* 付费提示卡片 */}
|
||||
<div className="mt-8 p-6 rounded-2xl bg-gradient-to-b from-[#1c1c1e] to-[#2c2c2e] border border-white/10">
|
||||
<div className="text-center">
|
||||
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-[#30d158]/10 flex items-center justify-center">
|
||||
<Lock className="w-8 h-8 text-[#30d158]" />
|
||||
<div className="w-16 h-16 mx-auto mb-4 rounded-2xl bg-[#ff3b5c]/10 flex items-center justify-center">
|
||||
<Lock className="w-8 h-8 text-[#ff3b5c]" />
|
||||
</div>
|
||||
<h3 className="text-xl font-semibold text-white mb-2">解锁完整内容</h3>
|
||||
<p className="text-gray-400 text-sm mb-6">
|
||||
已阅读50%,{isLoggedIn ? "购买后继续阅读" : "登录并购买后继续阅读"}
|
||||
已阅读10%,{isLoggedIn ? "购买后继续阅读" : "登录并购买后继续阅读"}
|
||||
</p>
|
||||
|
||||
{/* 购买选项 */}
|
||||
@@ -191,13 +190,13 @@ export function ChapterContent({ section, partTitle, chapterTitle }: ChapterCont
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<span>购买本章</span>
|
||||
<span className="text-[#30d158]">¥{section.price}</span>
|
||||
<span className="text-[#ff3b5c]">¥{section.price}</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => handlePurchaseClick("fullbook")}
|
||||
className="w-full py-3.5 px-6 rounded-xl bg-gradient-to-r from-[#30d158] to-[#00c7be] text-white font-medium active:scale-[0.98] transition-transform shadow-lg shadow-[#30d158]/20"
|
||||
className="w-full py-3.5 px-6 rounded-xl bg-gradient-to-r from-[#ff3b5c] to-[#ff6b8a] text-white font-medium active:scale-[0.98] transition-transform shadow-lg shadow-[#ff3b5c]/20"
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -219,6 +218,9 @@ export function ChapterContent({ section, partTitle, chapterTitle }: ChapterCont
|
||||
)}
|
||||
</main>
|
||||
|
||||
{/* 登录弹窗 */}
|
||||
<AuthModal isOpen={isAuthOpen} onClose={() => setIsAuthOpen(false)} />
|
||||
|
||||
{/* 支付弹窗 */}
|
||||
<PaymentModal
|
||||
isOpen={isPaymentOpen}
|
||||
|
||||
47
components/layout/bottom-nav.tsx
Normal file
47
components/layout/bottom-nav.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { usePathname } from "next/navigation"
|
||||
import { Home, Users, User } from "lucide-react"
|
||||
|
||||
export function BottomNav() {
|
||||
const pathname = usePathname()
|
||||
|
||||
// 管理后台不显示底部导航
|
||||
if (pathname.startsWith("/admin")) return null
|
||||
|
||||
const navItems = [
|
||||
{ href: "/", icon: Home, label: "首页", id: "home" },
|
||||
{ href: "/match", icon: Users, label: "匹配合作", id: "match" },
|
||||
{ href: "/my", icon: User, label: "我的", id: "my" },
|
||||
]
|
||||
|
||||
const isActive = (href: string) => {
|
||||
if (href === "/") return pathname === "/"
|
||||
return pathname.startsWith(href)
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="fixed bottom-0 left-0 right-0 z-50 bg-[#0a0a0a]/95 backdrop-blur-xl border-t border-white/5 safe-area-bottom">
|
||||
<div className="flex items-center justify-around h-16 max-w-lg mx-auto">
|
||||
{navItems.map((item) => {
|
||||
const active = isActive(item.href)
|
||||
const Icon = item.icon
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={item.id}
|
||||
href={item.href}
|
||||
className={`flex flex-col items-center justify-center w-20 h-full transition-colors ${
|
||||
active ? "text-[#ff3b5c]" : "text-white/40"
|
||||
}`}
|
||||
>
|
||||
<Icon className={`w-6 h-6 mb-1 ${active ? "stroke-[2.5]" : ""}`} />
|
||||
<span className="text-xs font-medium">{item.label}</span>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { X, Phone, User, Gift } from "lucide-react"
|
||||
import { X, Phone, Lock, User, Gift } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { useStore } from "@/lib/store"
|
||||
@@ -15,46 +15,58 @@ interface AuthModalProps {
|
||||
export function AuthModal({ isOpen, onClose, defaultTab = "login" }: AuthModalProps) {
|
||||
const [tab, setTab] = useState<"login" | "register">(defaultTab)
|
||||
const [phone, setPhone] = useState("")
|
||||
const [code, setCode] = useState("")
|
||||
const [password, setPassword] = useState("")
|
||||
const [nickname, setNickname] = useState("")
|
||||
const [referralCode, setReferralCode] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
const [codeSent, setCodeSent] = useState(false)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const { login, register } = useStore()
|
||||
|
||||
const handleSendCode = () => {
|
||||
const handleLogin = async () => {
|
||||
setError("")
|
||||
if (phone.length !== 11) {
|
||||
setError("请输入正确的手机号")
|
||||
return
|
||||
}
|
||||
// Simulate sending verification code
|
||||
setCodeSent(true)
|
||||
setError("")
|
||||
alert("验证码已发送,测试验证码: 123456")
|
||||
}
|
||||
if (!password) {
|
||||
setError("请输入密码")
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
// 使用密码替代验证码进行登录
|
||||
const success = await login(phone, password)
|
||||
setIsLoading(false)
|
||||
|
||||
const handleLogin = async () => {
|
||||
setError("")
|
||||
const success = await login(phone, code)
|
||||
if (success) {
|
||||
onClose()
|
||||
} else {
|
||||
setError("验证码错误或用户不存在,请先注册")
|
||||
setError("手机号或密码错误,请先注册")
|
||||
}
|
||||
}
|
||||
|
||||
const handleRegister = async () => {
|
||||
setError("")
|
||||
if (!nickname.trim()) {
|
||||
setError("请输入昵称")
|
||||
if (phone.length !== 11) {
|
||||
setError("请输入正确的手机号")
|
||||
return
|
||||
}
|
||||
const success = await register(phone, nickname, referralCode || undefined)
|
||||
if (!password || password.length < 6) {
|
||||
setError("密码至少6位")
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
// 昵称可选,默认使用手机号后四位
|
||||
const name = nickname.trim() || `用户${phone.slice(-4)}`
|
||||
const success = await register(phone, name, referralCode || undefined)
|
||||
setIsLoading(false)
|
||||
|
||||
if (success) {
|
||||
onClose()
|
||||
} else {
|
||||
setError("该手机号已注册")
|
||||
setError("该手机号已注册,请直接登录")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,32 +75,38 @@ export function AuthModal({ isOpen, onClose, defaultTab = "login" }: AuthModalPr
|
||||
return (
|
||||
<div className="fixed inset-0 z-50 flex items-center justify-center p-4">
|
||||
{/* Backdrop */}
|
||||
<div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose} />
|
||||
<div className="absolute inset-0 bg-black/80 backdrop-blur-sm" onClick={onClose} />
|
||||
|
||||
{/* Modal */}
|
||||
<div className="relative w-full max-w-md bg-[#0f2137] rounded-2xl border border-gray-700/50 overflow-hidden">
|
||||
<div className="relative w-full max-w-sm bg-[#1c1c1e] rounded-2xl border border-white/10 overflow-hidden">
|
||||
{/* Close button */}
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-4 right-4 p-2 text-gray-400 hover:text-white transition-colors"
|
||||
className="absolute top-4 right-4 p-2 text-white/40 hover:text-white transition-colors"
|
||||
>
|
||||
<X className="w-5 h-5" />
|
||||
</button>
|
||||
|
||||
{/* Tabs */}
|
||||
<div className="flex border-b border-gray-700/50">
|
||||
<div className="flex border-b border-white/10">
|
||||
<button
|
||||
onClick={() => setTab("login")}
|
||||
onClick={() => {
|
||||
setTab("login")
|
||||
setError("")
|
||||
}}
|
||||
className={`flex-1 py-4 text-center transition-colors ${
|
||||
tab === "login" ? "text-white border-b-2 border-[#38bdac]" : "text-gray-400 hover:text-white"
|
||||
tab === "login" ? "text-white border-b-2 border-[#ff3b5c]" : "text-white/40 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
登录
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setTab("register")}
|
||||
onClick={() => {
|
||||
setTab("register")
|
||||
setError("")
|
||||
}}
|
||||
className={`flex-1 py-4 text-center transition-colors ${
|
||||
tab === "register" ? "text-white border-b-2 border-[#38bdac]" : "text-gray-400 hover:text-white"
|
||||
tab === "register" ? "text-white border-b-2 border-[#ff3b5c]" : "text-white/40 hover:text-white"
|
||||
}`}
|
||||
>
|
||||
注册
|
||||
@@ -100,121 +118,113 @@ export function AuthModal({ isOpen, onClose, defaultTab = "login" }: AuthModalPr
|
||||
{tab === "login" ? (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">手机号</label>
|
||||
<label className="block text-white/60 text-sm mb-2">手机号</label>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-white/30" />
|
||||
<Input
|
||||
type="tel"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
placeholder="请输入手机号"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
className="pl-10 bg-[#2c2c2e] border-white/10 text-white placeholder:text-white/30 h-12 rounded-xl"
|
||||
maxLength={11}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">验证码</label>
|
||||
<div className="flex gap-3">
|
||||
<label className="block text-white/60 text-sm mb-2">密码</label>
|
||||
<div className="relative">
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-white/30" />
|
||||
<Input
|
||||
type="text"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="请输入验证码"
|
||||
className="bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
maxLength={6}
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="请输入密码"
|
||||
className="pl-10 bg-[#2c2c2e] border-white/10 text-white placeholder:text-white/30 h-12 rounded-xl"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleSendCode}
|
||||
disabled={codeSent}
|
||||
className="whitespace-nowrap border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent"
|
||||
>
|
||||
{codeSent ? "已发送" : "获取验证码"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-red-400 text-sm">{error}</p>}
|
||||
{error && <p className="text-[#ff3b5c] text-sm">{error}</p>}
|
||||
|
||||
<Button onClick={handleLogin} className="w-full bg-[#38bdac] hover:bg-[#2da396] text-white py-5">
|
||||
登录
|
||||
<Button
|
||||
onClick={handleLogin}
|
||||
disabled={isLoading}
|
||||
className="w-full bg-[#ff3b5c] hover:bg-[#ff5c7a] text-white h-12 rounded-xl font-medium"
|
||||
>
|
||||
{isLoading ? "登录中..." : "登录"}
|
||||
</Button>
|
||||
|
||||
<p className="text-center text-white/40 text-xs">测试账号:任意11位手机号,密码:123456</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">手机号</label>
|
||||
<label className="block text-white/60 text-sm mb-2">手机号</label>
|
||||
<div className="relative">
|
||||
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Phone className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-white/30" />
|
||||
<Input
|
||||
type="tel"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value)}
|
||||
placeholder="请输入手机号"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
className="pl-10 bg-[#2c2c2e] border-white/10 text-white placeholder:text-white/30 h-12 rounded-xl"
|
||||
maxLength={11}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">昵称</label>
|
||||
<label className="block text-white/60 text-sm mb-2">密码</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-white/30" />
|
||||
<Input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="设置密码(至少6位)"
|
||||
className="pl-10 bg-[#2c2c2e] border-white/10 text-white placeholder:text-white/30 h-12 rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-white/60 text-sm mb-2">昵称(选填)</label>
|
||||
<div className="relative">
|
||||
<User className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-white/30" />
|
||||
<Input
|
||||
type="text"
|
||||
value={nickname}
|
||||
onChange={(e) => setNickname(e.target.value)}
|
||||
placeholder="请输入昵称"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
placeholder="不填则使用默认昵称"
|
||||
className="pl-10 bg-[#2c2c2e] border-white/10 text-white placeholder:text-white/30 h-12 rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">验证码</label>
|
||||
<div className="flex gap-3">
|
||||
<Input
|
||||
type="text"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
placeholder="请输入验证码"
|
||||
className="bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
maxLength={6}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleSendCode}
|
||||
disabled={codeSent}
|
||||
className="whitespace-nowrap border-gray-600 text-gray-300 hover:bg-gray-700/50 bg-transparent"
|
||||
>
|
||||
{codeSent ? "已发送" : "获取验证码"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-gray-400 text-sm mb-2">邀请码 (选填)</label>
|
||||
<label className="block text-white/60 text-sm mb-2">邀请码(选填)</label>
|
||||
<div className="relative">
|
||||
<Gift className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-500" />
|
||||
<Gift className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-white/30" />
|
||||
<Input
|
||||
type="text"
|
||||
value={referralCode}
|
||||
onChange={(e) => setReferralCode(e.target.value)}
|
||||
placeholder="填写邀请码可获得优惠"
|
||||
className="pl-10 bg-[#0a1628] border-gray-700 text-white placeholder:text-gray-500"
|
||||
placeholder="填写可获得优惠"
|
||||
className="pl-10 bg-[#2c2c2e] border-white/10 text-white placeholder:text-white/30 h-12 rounded-xl"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{error && <p className="text-red-400 text-sm">{error}</p>}
|
||||
{error && <p className="text-[#ff3b5c] text-sm">{error}</p>}
|
||||
|
||||
<Button onClick={handleRegister} className="w-full bg-[#38bdac] hover:bg-[#2da396] text-white py-5">
|
||||
注册
|
||||
<Button
|
||||
onClick={handleRegister}
|
||||
disabled={isLoading}
|
||||
className="w-full bg-[#ff3b5c] hover:bg-[#ff5c7a] text-white h-12 rounded-xl font-medium"
|
||||
>
|
||||
{isLoading ? "注册中..." : "立即注册"}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user