Refactor homepage, reading modal, matching feature, and user profile for improved UX #VERCEL_SKIP Co-authored-by: undefined <undefined+undefined@users.noreply.github.com>
172 lines
6.4 KiB
TypeScript
172 lines
6.4 KiB
TypeScript
"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>
|
||
)
|
||
}
|