Files
soul/app/match/page.tsx
v0 78867aac6d 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>
2026-01-14 05:41:44 +00:00

384 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import { useState } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { Mic } from "lucide-react"
interface MatchUser {
id: string
nickname: string
avatar: string
tags: string[]
matchScore: number
concept: string
wechat: string
commonInterests: Array<{ icon: string; text: string }>
}
const matchTypes = [
{ id: "partner", label: "创业合伙", icon: "⭐", color: "#00E5FF" },
{ id: "investor", label: "资源对接", icon: "👥", color: "#7B61FF" },
{ id: "mentor", label: "导师顾问", icon: "❤️", color: "#E91E63" },
{ id: "team", label: "团队招募", icon: "🎮", color: "#4CAF50" },
]
export default function MatchPage() {
const [isMatching, setIsMatching] = useState(false)
const [currentMatch, setCurrentMatch] = useState<MatchUser | null>(null)
const [matchAttempts, setMatchAttempts] = useState(0)
const [selectedType, setSelectedType] = useState("partner")
const [activeTab, setActiveTab] = useState<"voice" | "party" | "team">("voice")
const startMatch = () => {
setIsMatching(true)
setMatchAttempts(0)
setCurrentMatch(null)
const interval = setInterval(() => {
setMatchAttempts((prev) => prev + 1)
}, 1000)
setTimeout(
() => {
clearInterval(interval)
setIsMatching(false)
setCurrentMatch(getMockMatch())
},
Math.random() * 3000 + 3000,
)
}
const getMockMatch = (): MatchUser => {
const nicknames = ["创业先锋", "资源整合者", "私域专家", "商业导师", "连续创业者"]
const randomIndex = Math.floor(Math.random() * nicknames.length)
const concepts = [
"专注私域流量运营5年帮助100+品牌实现从0到1的增长。",
"连续创业者,擅长商业模式设计和资源整合。",
"在Soul分享真实创业故事希望找到志同道合的合作伙伴。",
]
const wechats = ["soul_partner_1", "soul_business_2024", "soul_startup_fan"]
return {
id: `user_${Date.now()}`,
nickname: nicknames[randomIndex],
avatar: `https://picsum.photos/200/200?random=${randomIndex}`,
tags: ["创业者", "私域运营", matchTypes.find((t) => t.id === selectedType)?.label || ""],
matchScore: Math.floor(Math.random() * 20) + 80,
concept: concepts[randomIndex % concepts.length],
wechat: wechats[randomIndex % wechats.length],
commonInterests: [
{ icon: "📚", text: "都在读《创业实验》" },
{ icon: "💼", text: "对私域运营感兴趣" },
{ icon: "🎯", text: "相似的创业方向" },
],
}
}
const nextMatch = () => {
setCurrentMatch(null)
setTimeout(() => startMatch(), 500)
}
const handleAddWechat = () => {
if (!currentMatch) return
navigator.clipboard
.writeText(currentMatch.wechat)
.then(() => {
alert(`微信号已复制:${currentMatch.wechat}\n\n请打开微信添加好友备注"创业合作"即可。`)
})
.catch(() => {
alert(`微信号:${currentMatch.wechat}\n\n请手动复制并添加好友。`)
})
}
const currentTypeLabel = matchTypes.find((t) => t.id === selectedType)?.label || "创业合伙"
return (
<div className="min-h-screen bg-black pb-24">
{/* 顶部标题 */}
<div className="flex items-center justify-between px-6 pt-6 pb-4">
<h1 className="text-2xl font-bold text-white"></h1>
<button className="w-10 h-10 rounded-full bg-[#1c1c1e] flex items-center justify-center">
<svg className="w-5 h-5 text-white/60" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4"
/>
</svg>
</button>
</div>
{/* Tab切换 */}
<div className="mx-6 mb-8 p-1 bg-[#1c1c1e] rounded-full flex">
<button
onClick={() => setActiveTab("voice")}
className={`flex-1 py-3 rounded-full text-sm font-medium transition-all ${
activeTab === "voice" ? "bg-[#00E5FF] text-black" : "text-white/60"
}`}
>
</button>
<button
onClick={() => setActiveTab("party")}
className={`flex-1 py-3 rounded-full text-sm font-medium transition-all ${
activeTab === "party" ? "bg-[#00E5FF] text-black" : "text-white/60"
}`}
>
</button>
<button
onClick={() => setActiveTab("team")}
className={`flex-1 py-3 rounded-full text-sm font-medium transition-all ${
activeTab === "team" ? "bg-[#00E5FF] text-black" : "text-white/60"
}`}
>
</button>
</div>
<AnimatePresence mode="wait">
{!isMatching && !currentMatch && (
<motion.div
key="idle"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="flex flex-col items-center px-6"
>
{/* 中央匹配圆环 */}
<motion.div
onClick={startMatch}
className="relative w-[280px] h-[280px] mb-8 cursor-pointer"
whileTap={{ scale: 0.95 }}
>
{/* 外层光环 */}
<motion.div
className="absolute inset-[-30px] rounded-full"
style={{
background: "radial-gradient(circle, transparent 50%, rgba(0, 229, 255, 0.1) 70%, transparent 100%)",
}}
animate={{
scale: [1, 1.1, 1],
opacity: [0.5, 0.8, 0.5],
}}
transition={{
duration: 2,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
/>
{/* 中间光环 */}
<motion.div
className="absolute inset-[-15px] rounded-full border-2 border-[#00E5FF]/30"
animate={{
scale: [1, 1.05, 1],
opacity: [0.3, 0.6, 0.3],
}}
transition={{
duration: 1.5,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
/>
{/* 内层渐变球 */}
<motion.div
className="absolute inset-0 rounded-full flex flex-col items-center justify-center overflow-hidden"
style={{
background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)",
boxShadow: "0 0 60px rgba(0, 229, 255, 0.3), inset 0 0 60px rgba(123, 97, 255, 0.2)",
}}
animate={{
y: [0, -5, 0],
}}
transition={{
duration: 3,
repeat: Number.POSITIVE_INFINITY,
ease: "easeInOut",
}}
>
{/* 内部渐变光效 */}
<div
className="absolute inset-0 rounded-full"
style={{
background:
"radial-gradient(circle at 30% 30%, rgba(123, 97, 255, 0.4) 0%, transparent 50%), radial-gradient(circle at 70% 70%, rgba(233, 30, 99, 0.3) 0%, transparent 50%)",
}}
/>
{/* 中心图标 */}
<Mic className="w-12 h-12 text-white/90 mb-3 relative z-10" />
<div className="text-xl font-bold text-white mb-1 relative z-10"></div>
<div className="text-sm text-white/60 relative z-10">{currentTypeLabel}</div>
</motion.div>
</motion.div>
{/* 当前模式显示 */}
<p className="text-white/50 text-sm mb-8">
: <span className="text-[#00E5FF]">{currentTypeLabel}</span>
</p>
{/* 分隔线 */}
<div className="w-full h-px bg-white/10 mb-6" />
{/* 选择匹配类型 */}
<p className="text-white/40 text-sm mb-4"></p>
<div className="grid grid-cols-4 gap-3 w-full">
{matchTypes.map((type) => (
<button
key={type.id}
onClick={() => setSelectedType(type.id)}
className={`p-4 rounded-xl flex flex-col items-center gap-2 transition-all ${
selectedType === type.id
? "bg-[#00E5FF]/10 border border-[#00E5FF]/50"
: "bg-[#1c1c1e] border border-transparent"
}`}
>
<span className="text-2xl">{type.icon}</span>
<span className={`text-xs ${selectedType === type.id ? "text-[#00E5FF]" : "text-white/60"}`}>
{type.label}
</span>
</button>
))}
</div>
</motion.div>
)}
{isMatching && (
<motion.div
key="matching"
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
className="text-center px-6"
>
{/* 匹配动画 */}
<div className="relative w-[200px] h-[200px] mx-auto mb-8">
<motion.div
className="absolute inset-0 rounded-full bg-gradient-to-br from-[#00E5FF] via-[#7B61FF] to-[#E91E63]"
animate={{ rotate: 360 }}
transition={{ duration: 3, repeat: Number.POSITIVE_INFINITY, ease: "linear" }}
/>
<div className="absolute inset-2 rounded-full bg-black flex items-center justify-center">
<motion.div
animate={{ scale: [1, 1.2, 1] }}
transition={{ duration: 1, repeat: Number.POSITIVE_INFINITY }}
>
<Mic className="w-12 h-12 text-[#00E5FF]" />
</motion.div>
</div>
{/* 扩散波纹 */}
{[1, 2, 3].map((ring) => (
<motion.div
key={ring}
className="absolute inset-0 rounded-full border-2 border-[#00E5FF]/30"
animate={{
scale: [1, 2],
opacity: [0.6, 0],
}}
transition={{
duration: 2,
repeat: Number.POSITIVE_INFINITY,
delay: ring * 0.5,
}}
/>
))}
</div>
<h2 className="text-xl font-semibold mb-2 text-white">...</h2>
<p className="text-white/50 mb-8"> {matchAttempts} </p>
<button
onClick={() => setIsMatching(false)}
className="px-8 py-3 rounded-full bg-[#1c1c1e] text-white border border-white/10"
>
</button>
</motion.div>
)}
{currentMatch && !isMatching && (
<motion.div
key="matched"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
className="px-6"
>
{/* 成功动画 */}
<motion.div
className="text-center mb-6"
initial={{ scale: 0 }}
animate={{ scale: 1 }}
transition={{ type: "spring", bounce: 0.5 }}
>
<span className="text-6xl"></span>
</motion.div>
{/* 用户卡片 */}
<div className="bg-[#1c1c1e] rounded-2xl p-5 mb-4 border border-white/5">
<div className="flex items-center gap-4 mb-4">
<img
src={currentMatch.avatar || "/placeholder.svg"}
alt={currentMatch.nickname}
className="w-16 h-16 rounded-full border-2 border-[#00E5FF]"
/>
<div className="flex-1">
<h3 className="text-lg font-semibold text-white mb-2">{currentMatch.nickname}</h3>
<div className="flex flex-wrap gap-1">
{currentMatch.tags.map((tag) => (
<span key={tag} className="px-2 py-0.5 rounded text-xs bg-[#00E5FF]/20 text-[#00E5FF]">
{tag}
</span>
))}
</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-[#00E5FF]">{currentMatch.matchScore}%</div>
<div className="text-xs text-white/50"></div>
</div>
</div>
{/* 共同兴趣 */}
<div className="pt-4 border-t border-white/10 mb-4">
<h4 className="text-sm text-white/60 mb-2"></h4>
<div className="space-y-2">
{currentMatch.commonInterests.map((interest, i) => (
<div key={i} className="flex items-center gap-2 text-sm text-white/80">
<span>{interest.icon}</span>
<span>{interest.text}</span>
</div>
))}
</div>
</div>
{/* 核心理念 */}
<div className="pt-4 border-t border-white/10">
<h4 className="text-sm text-white/60 mb-2"></h4>
<p className="text-sm text-white/70">{currentMatch.concept}</p>
</div>
</div>
{/* 操作按钮 */}
<div className="space-y-3">
<button onClick={handleAddWechat} className="w-full py-4 rounded-xl bg-[#00E5FF] text-black font-medium">
</button>
<button
onClick={nextMatch}
className="w-full py-4 rounded-xl bg-[#1c1c1e] text-white border border-white/10"
>
🔄
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</div>
)
}