Files
soul/app/match/page.tsx

874 lines
35 KiB
TypeScript
Raw Normal View History

"use client"
import { useState, useEffect } from "react"
import { motion, AnimatePresence } from "framer-motion"
import { Users, X, CheckCircle, Loader2, Lock, Zap, Gift } from "lucide-react"
import { Home, List, User } from "lucide-react"
import { useRouter } from "next/navigation"
import { useStore } from "@/lib/store"
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: "创业合伙", matchLabel: "创业伙伴", icon: "⭐", color: "#00E5FF", matchFromDB: true, showJoinAfterMatch: false },
{ id: "investor", label: "资源对接", matchLabel: "资源对接", icon: "👥", color: "#7B61FF", matchFromDB: false, showJoinAfterMatch: true },
{ id: "mentor", label: "导师顾问", matchLabel: "商业顾问", icon: "❤️", color: "#E91E63", matchFromDB: false, showJoinAfterMatch: true },
{ id: "team", label: "团队招募", matchLabel: "加入项目", icon: "🎮", color: "#4CAF50", matchFromDB: false, showJoinAfterMatch: true },
]
const FREE_MATCH_LIMIT = 1 // 每日免费匹配次数改为1次
const MATCH_UNLOCK_PRICE = 1 // 每次解锁需要购买1个小节
// 获取本地存储的联系方式
const getStoredContact = (): { phone: string; wechat: string } => {
if (typeof window !== "undefined") {
return {
phone: localStorage.getItem("user_phone") || "",
wechat: localStorage.getItem("user_wechat") || "",
}
}
return { phone: "", wechat: "" }
}
// 获取今日匹配次数
const getTodayMatchCount = (): number => {
if (typeof window !== "undefined") {
const today = new Date().toISOString().split('T')[0]
const stored = localStorage.getItem("match_count_data")
if (stored) {
const data = JSON.parse(stored)
if (data.date === today) {
return data.count
}
}
}
return 0
}
// 保存今日匹配次数
const saveTodayMatchCount = (count: number) => {
if (typeof window !== "undefined") {
const today = new Date().toISOString().split('T')[0]
localStorage.setItem("match_count_data", JSON.stringify({ date: today, count }))
}
}
// 保存联系方式到本地存储
const saveContact = (phone: string, wechat: string) => {
if (typeof window !== "undefined") {
if (phone) localStorage.setItem("user_phone", phone)
if (wechat) localStorage.setItem("user_wechat", wechat)
}
}
export default function MatchPage() {
const [mounted, setMounted] = useState(false)
const [isMatching, setIsMatching] = useState(false)
const [currentMatch, setCurrentMatch] = useState<MatchUser | null>(null)
const [matchAttempts, setMatchAttempts] = useState(0)
const [selectedType, setSelectedType] = useState("partner")
const [todayMatchCount, setTodayMatchCount] = useState(0)
const router = useRouter()
const { user, isLoggedIn, purchaseSection } = useStore()
const [showJoinModal, setShowJoinModal] = useState(false)
const [showUnlockModal, setShowUnlockModal] = useState(false)
const [joinType, setJoinType] = useState<string | null>(null)
const [phoneNumber, setPhoneNumber] = useState("")
const [wechatId, setWechatId] = useState("")
const [contactType, setContactType] = useState<"phone" | "wechat">("phone")
const [isJoining, setIsJoining] = useState(false)
const [joinSuccess, setJoinSuccess] = useState(false)
const [joinError, setJoinError] = useState("")
const [isUnlocking, setIsUnlocking] = useState(false)
// 检查用户是否有购买权限(购买过任意内容)
const hasPurchased = user?.hasFullBook || (user?.purchasedSections && user.purchasedSections.length > 0)
// 总共获得的匹配次数 = 每日免费(1) + 已购小节数量
// 如果购买了全书,则拥有无限匹配机会
const totalMatchesAllowed = user?.hasFullBook ? 999999 : FREE_MATCH_LIMIT + (user?.purchasedSections?.length || 0)
// 剩余可用次数
const matchesRemaining = user?.hasFullBook ? 999999 : Math.max(0, totalMatchesAllowed - todayMatchCount)
// 是否需要付费(总次数用完)
const needPayToMatch = !user?.hasFullBook && matchesRemaining <= 0
// 初始化
useEffect(() => {
setMounted(true)
const storedContact = getStoredContact()
if (storedContact.phone) {
setPhoneNumber(storedContact.phone)
}
if (storedContact.wechat) {
setWechatId(storedContact.wechat)
}
if (user?.phone) {
setPhoneNumber(user.phone)
}
// 读取今日匹配次数
setTodayMatchCount(getTodayMatchCount())
}, [user])
if (!mounted) return null // 彻底解决 Hydration 错误
const handleJoinClick = (typeId: string) => {
setJoinType(typeId)
setShowJoinModal(true)
setJoinSuccess(false)
setJoinError("")
}
const handleJoinSubmit = async () => {
const contact = contactType === "phone" ? phoneNumber : wechatId
if (contactType === "phone" && (!phoneNumber || phoneNumber.length !== 11)) {
setJoinError("请输入正确的11位手机号")
return
}
if (contactType === "wechat" && (!wechatId || wechatId.length < 6)) {
setJoinError("请输入正确的微信号至少6位")
return
}
setIsJoining(true)
setJoinError("")
try {
const response = await fetch("/api/ckb/join", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
type: joinType,
phone: contactType === "phone" ? phoneNumber : "",
wechat: contactType === "wechat" ? wechatId : "",
userId: user?.id,
}),
})
const result = await response.json()
if (result.success) {
saveContact(phoneNumber, wechatId)
setJoinSuccess(true)
setTimeout(() => {
setShowJoinModal(false)
setJoinSuccess(false)
}, 2000)
} else {
setJoinError(result.message || "加入失败,请稍后重试")
}
} catch (error) {
setJoinError("网络错误,请检查网络后重试")
} finally {
setIsJoining(false)
}
}
// 购买解锁匹配次数
const handleUnlockMatch = async () => {
if (!isLoggedIn) {
alert("请先登录")
return
}
setIsUnlocking(true)
try {
// 模拟购买过程实际应该调用支付API
// 这里简化为直接购买成功
await new Promise((resolve) => setTimeout(resolve, 1500))
// 购买成功后重置今日匹配次数增加3次
const newCount = Math.max(0, todayMatchCount - 3)
saveTodayMatchCount(newCount)
setTodayMatchCount(newCount)
setShowUnlockModal(false)
alert("解锁成功已获得3次匹配机会")
} catch (error) {
alert("解锁失败,请重试")
} finally {
setIsUnlocking(false)
}
}
// 上报匹配行为到CKB
const reportMatchToCKB = async (matchedUser: MatchUser) => {
try {
await fetch("/api/ckb/match", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
matchType: selectedType,
phone: phoneNumber || user?.phone || "",
wechat: wechatId || user?.wechat || "",
userId: user?.id || "",
nickname: user?.nickname || "",
matchedUser: {
id: matchedUser.id,
nickname: matchedUser.nickname,
matchScore: matchedUser.matchScore,
},
}),
})
} catch (error) {
console.error("上报匹配失败:", error)
}
}
const startMatch = () => {
// 检查是否有购买权限
if (!hasPurchased) {
return
}
// 检查是否需要付费
if (needPayToMatch) {
setShowUnlockModal(true)
return
}
setIsMatching(true)
setMatchAttempts(0)
setCurrentMatch(null)
const interval = setInterval(() => {
setMatchAttempts((prev) => prev + 1)
}, 1000)
setTimeout(
() => {
clearInterval(interval)
setIsMatching(false)
const matchedUser = getMockMatch()
setCurrentMatch(matchedUser)
// 增加今日匹配次数
const newCount = todayMatchCount + 1
setTodayMatchCount(newCount)
saveTodayMatchCount(newCount)
// 上报匹配行为
reportMatchToCKB(matchedUser)
// 如果是需要弹出加入弹窗的类型,自动弹出
const currentType = matchTypes.find(t => t.id === selectedType)
if (currentType?.showJoinAfterMatch) {
setJoinType(selectedType)
setShowJoinModal(true)
setJoinSuccess(false)
setJoinError("")
}
},
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 = () => {
// 检查是否需要付费
if (needPayToMatch) {
setShowUnlockModal(true)
return
}
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 currentType = matchTypes.find((t) => t.id === selectedType)
const currentTypeLabel = currentType?.label || "创业合伙"
const currentMatchLabel = currentType?.matchLabel || "创业伙伴"
const joinTypeLabel = matchTypes.find((t) => t.id === joinType)?.matchLabel || ""
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>
{/* 今日匹配次数显示 - 仅在总次数用完时显示 */}
{hasPurchased && (
<div className="px-6 mb-4">
<div className={`flex items-center justify-between p-3 rounded-xl bg-[#1c1c1e] border ${matchesRemaining <= 0 && !user?.hasFullBook ? 'border-[#FFD700]/20' : 'border-white/5'}`}>
<div className="flex items-center gap-2">
<Zap className={`w-5 h-5 ${matchesRemaining <= 0 && !user?.hasFullBook ? 'text-[#FFD700]' : 'text-[#00E5FF]'}`} />
<span className="text-white/70 text-sm">
{user?.hasFullBook ? "无限匹配机会" : matchesRemaining <= 0 ? "今日匹配机会已用完" : "剩余匹配机会"}
</span>
</div>
<div className="flex items-center gap-2">
<span className={`text-lg font-bold ${matchesRemaining > 0 ? 'text-[#00E5FF]' : 'text-red-400'}`}>
{user?.hasFullBook ? "无限" : `${matchesRemaining}/${totalMatchesAllowed}`}
</span>
{matchesRemaining <= 0 && !user?.hasFullBook && (
<button
onClick={() => router.push('/chapters')}
className="px-3 py-1.5 rounded-full bg-[#FFD700]/20 text-[#FFD700] text-xs font-medium"
>
+1
</button>
)}
</div>
</div>
</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={hasPurchased ? startMatch : undefined}
className={`relative w-[280px] h-[280px] mb-8 ${hasPurchased ? 'cursor-pointer' : 'cursor-not-allowed'}`}
whileTap={hasPurchased ? { scale: 0.95 } : undefined}
>
{/* 外层光环 */}
<motion.div
className="absolute inset-[-30px] rounded-full"
style={{
background: hasPurchased
? "radial-gradient(circle, transparent 50%, rgba(0, 229, 255, 0.1) 70%, transparent 100%)"
: "radial-gradient(circle, transparent 50%, rgba(100, 100, 100, 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 ${hasPurchased ? 'border-[#00E5FF]/30' : 'border-gray-600/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: hasPurchased
? "linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)"
: "linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 50%, #1a1a1a 100%)",
boxShadow: hasPurchased
? "0 0 60px rgba(0, 229, 255, 0.3), inset 0 0 60px rgba(123, 97, 255, 0.2)"
: "0 0 30px rgba(100, 100, 100, 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: hasPurchased
? "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%)"
: "radial-gradient(circle at 30% 30%, rgba(100, 100, 100, 0.2) 0%, transparent 50%)",
}}
/>
{/* 中心图标 */}
{hasPurchased ? (
needPayToMatch ? (
<>
<Zap className="w-12 h-12 text-[#FFD700] 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"></div>
</>
) : (
<>
<Users 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">{currentMatchLabel}</div>
</>
)
) : (
<>
<Lock className="w-12 h-12 text-gray-500 mb-3 relative z-10" />
<div className="text-xl font-bold text-gray-400 mb-1 relative z-10"></div>
<div className="text-sm text-gray-500 relative z-10">9.9使</div>
</>
)}
</motion.div>
</motion.div>
{/* 当前模式显示 */}
<p className="text-white/50 text-sm mb-4">
: <span className={hasPurchased ? "text-[#00E5FF]" : "text-gray-500"}>{currentTypeLabel}</span>
</p>
{/* 购买提示 */}
{!hasPurchased && (
<div className="w-full mb-6 p-4 rounded-xl bg-gradient-to-r from-[#00E5FF]/10 to-transparent border border-[#00E5FF]/20">
<div className="flex items-center justify-between">
<div>
<p className="text-white font-medium"></p>
<p className="text-gray-400 text-sm mt-1">9.93</p>
</div>
<button
onClick={() => router.push('/chapters')}
className="px-4 py-2 rounded-lg bg-[#00E5FF] text-black text-sm font-medium"
>
</button>
</div>
</div>
)}
{/* 分隔线 */}
<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 }}
>
<Users 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">{currentMatchLabel}...</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 flex items-center justify-center gap-2"
>
{matchesRemaining <= 0 && (
<span className="text-xs px-2 py-0.5 rounded-full bg-[#FFD700]/20 text-[#FFD700]"></span>
)}
</button>
</div>
</motion.div>
)}
</AnimatePresence>
{/* 解锁匹配次数弹窗 */}
<AnimatePresence>
{showUnlockModal && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center px-6"
onClick={() => !isUnlocking && setShowUnlockModal(false)}
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
onClick={(e) => e.stopPropagation()}
className="bg-[#1c1c1e] rounded-2xl w-full max-w-sm overflow-hidden"
>
<div className="p-6 text-center">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-[#FFD700]/20 flex items-center justify-center">
<Zap className="w-8 h-8 text-[#FFD700]" />
</div>
<h3 className="text-xl font-bold text-white mb-2"></h3>
<p className="text-white/60 text-sm mb-6">
1
</p>
<div className="bg-black/30 rounded-xl p-4 mb-6">
<div className="flex items-center justify-between mb-2">
<span className="text-white/60"></span>
<span className="text-white font-medium"></span>
</div>
<div className="flex items-center justify-between">
<span className="text-white/60"></span>
<span className="text-[#00E5FF] font-medium">+1</span>
</div>
</div>
<div className="space-y-3">
<button
onClick={() => {
setShowUnlockModal(false)
router.push('/chapters')
}}
className="w-full py-3 rounded-xl bg-[#FFD700] text-black font-medium"
>
(¥1/)
</button>
<button
onClick={() => setShowUnlockModal(false)}
className="w-full py-3 rounded-xl bg-white/5 text-white/60"
>
</button>
</div>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
{/* 加入类型弹窗 */}
<AnimatePresence>
{showJoinModal && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-50 flex items-center justify-center px-6"
onClick={() => !isJoining && setShowJoinModal(false)}
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
onClick={(e) => e.stopPropagation()}
className="bg-[#1c1c1e] rounded-2xl w-full max-w-sm overflow-hidden"
>
{/* 弹窗头部 */}
<div className="flex items-center justify-between p-4 border-b border-white/10">
<h3 className="text-lg font-semibold text-white">{joinTypeLabel}</h3>
<button
onClick={() => !isJoining && setShowJoinModal(false)}
className="w-8 h-8 rounded-full bg-white/10 flex items-center justify-center"
>
<X className="w-4 h-4 text-white/60" />
</button>
</div>
{/* 弹窗内容 */}
<div className="p-5">
{joinSuccess ? (
<motion.div initial={{ scale: 0.8 }} animate={{ scale: 1 }} className="text-center py-8">
<CheckCircle className="w-16 h-16 text-[#00E5FF] mx-auto mb-4" />
<p className="text-white text-lg font-medium mb-2">!</p>
<p className="text-white/60 text-sm"></p>
</motion.div>
) : (
<>
<p className="text-white/60 text-sm mb-4">
{user?.phone ? "已检测到您的绑定信息,可直接提交或修改" : "请填写您的联系方式以便我们联系您"}
</p>
{/* 联系方式类型切换 */}
<div className="flex gap-2 mb-4">
<button
onClick={() => setContactType("phone")}
className={`flex-1 py-2 px-4 rounded-lg text-sm font-medium transition-colors ${
contactType === "phone"
? "bg-[#00E5FF]/20 text-[#00E5FF] border border-[#00E5FF]/30"
: "bg-white/5 text-gray-400 border border-white/10"
}`}
>
</button>
<button
onClick={() => setContactType("wechat")}
className={`flex-1 py-2 px-4 rounded-lg text-sm font-medium transition-colors ${
contactType === "wechat"
? "bg-[#07C160]/20 text-[#07C160] border border-[#07C160]/30"
: "bg-white/5 text-gray-400 border border-white/10"
}`}
>
</button>
</div>
{/* 联系方式输入 */}
<div className="mb-4">
<label className="block text-white/40 text-xs mb-2">
{contactType === "phone" ? "手机号" : "微信号"}
</label>
{contactType === "phone" ? (
<input
type="tel"
value={phoneNumber}
onChange={(e) => setPhoneNumber(e.target.value.replace(/\D/g, "").slice(0, 11))}
placeholder="请输入11位手机号"
className="w-full px-4 py-3 rounded-xl bg-black/30 border border-white/10 text-white placeholder-white/30 focus:outline-none focus:border-[#00E5FF]/50"
disabled={isJoining}
/>
) : (
<input
type="text"
value={wechatId}
onChange={(e) => setWechatId(e.target.value)}
placeholder="请输入微信号"
className="w-full px-4 py-3 rounded-xl bg-black/30 border border-white/10 text-white placeholder-white/30 focus:outline-none focus:border-[#07C160]/50"
disabled={isJoining}
/>
)}
</div>
{/* 错误提示 */}
{joinError && <p className="text-red-400 text-sm mb-4">{joinError}</p>}
{/* 提交按钮 */}
<button
onClick={handleJoinSubmit}
disabled={isJoining || (contactType === "phone" ? !phoneNumber : !wechatId)}
className="w-full py-3 rounded-xl bg-[#00E5FF] text-black font-medium flex items-center justify-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
>
{isJoining ? (
<>
<Loader2 className="w-5 h-5 animate-spin" />
...
</>
) : (
"确认加入"
)}
</button>
<p className="text-white/30 text-xs text-center mt-3"></p>
</>
)}
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
<nav className="fixed bottom-0 left-0 right-0 bg-[#1c1c1e]/95 backdrop-blur-xl border-t border-white/5 pb-safe-bottom">
<div className="px-4 py-2">
<div className="flex items-center justify-around">
<button onClick={() => router.push("/")} className="flex flex-col items-center py-2 px-4">
<Home className="w-5 h-5 text-gray-500 mb-1" />
<span className="text-gray-500 text-xs"></span>
</button>
<button onClick={() => router.push("/chapters")} className="flex flex-col items-center py-2 px-4">
<List className="w-5 h-5 text-gray-500 mb-1" />
<span className="text-gray-500 text-xs"></span>
</button>
{/* 找伙伴按钮 - 当前页面高亮 */}
<button className="flex flex-col items-center py-2 px-6 -mt-4">
<div className="w-14 h-14 rounded-full bg-gradient-to-br from-[#00CED1] to-[#20B2AA] flex items-center justify-center shadow-lg shadow-[#00CED1]/30">
<Users className="w-7 h-7 text-white" />
</div>
<span className="text-[#00CED1] text-xs font-medium mt-1"></span>
</button>
<button onClick={() => router.push("/my")} className="flex flex-col items-center py-2 px-4">
<User className="w-5 h-5 text-gray-500 mb-1" />
<span className="text-gray-500 text-xs"></span>
</button>
</div>
</div>
</nav>
</div>
)
}