|
|
import React, { useState, useEffect } from 'react'; |
|
|
import { X, Gift } from 'lucide-react'; |
|
|
import { Language, AdminConfig, UserAccount } from '../types'; |
|
|
import { TRANSLATIONS } from '../constants/translations'; |
|
|
|
|
|
import confetti from 'canvas-confetti'; |
|
|
|
|
|
interface RedPacketModalProps { |
|
|
isVisible: boolean; |
|
|
onClose: () => void; |
|
|
language: Language; |
|
|
adminConfig: AdminConfig; |
|
|
user?: UserAccount; |
|
|
onUpdateUser: (balance: number) => void; |
|
|
onOpenShare: () => void; |
|
|
} |
|
|
|
|
|
export const RedPacketModal: React.FC<RedPacketModalProps> = ({ |
|
|
isVisible, onClose, language, adminConfig, user, onUpdateUser, onOpenShare |
|
|
}) => { |
|
|
const [stage, setStage] = useState<'closed' | 'opened'>('closed'); |
|
|
const [amount, setAmount] = useState(0); |
|
|
const t = TRANSLATIONS[language]; |
|
|
|
|
|
|
|
|
const max = adminConfig.redPacketMax || 2000; |
|
|
|
|
|
const userBalance = user?.redPacketBalance ?? (max - 20); |
|
|
|
|
|
useEffect(() => { |
|
|
if (isVisible) { |
|
|
setStage('closed'); |
|
|
|
|
|
|
|
|
let current = 0; |
|
|
|
|
|
const step = userBalance / 40; |
|
|
const interval = setInterval(() => { |
|
|
current += step; |
|
|
if (current >= userBalance) { |
|
|
current = userBalance; |
|
|
clearInterval(interval); |
|
|
} |
|
|
setAmount(Math.floor(current)); |
|
|
}, 30); |
|
|
return () => clearInterval(interval); |
|
|
} |
|
|
}, [isVisible, userBalance]); |
|
|
|
|
|
if (!isVisible) return null; |
|
|
|
|
|
const handleOpen = () => { |
|
|
setStage('opened'); |
|
|
confetti({ |
|
|
particleCount: 150, |
|
|
spread: 80, |
|
|
origin: { y: 0.6 }, |
|
|
colors: ['#fbbf24', '#ef4444', '#ffffff'] |
|
|
}); |
|
|
|
|
|
if (user && (user.redPacketBalance === undefined || user.redPacketBalance === 0)) { |
|
|
onUpdateUser(userBalance); |
|
|
} |
|
|
}; |
|
|
|
|
|
const handleWithdraw = () => { |
|
|
onOpenShare(); |
|
|
onClose(); |
|
|
}; |
|
|
|
|
|
return ( |
|
|
<div className="fixed inset-0 z-[120] flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm animate-fade-in"> |
|
|
<div className="relative w-full max-w-sm"> |
|
|
<button onClick={onClose} className="absolute -top-10 right-0 p-2 bg-white/20 rounded-full text-white hover:bg-white/40"> |
|
|
<X className="w-5 h-5" /> |
|
|
</button> |
|
|
|
|
|
{stage === 'closed' ? ( |
|
|
// Stage 1: The Big Red Envelope |
|
|
<div className="bg-gradient-to-b from-red-500 to-red-600 rounded-3xl shadow-2xl overflow-hidden text-center p-8 relative animate-bounce-slow"> |
|
|
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-32 h-32 bg-red-700 rounded-b-full opacity-50"></div> |
|
|
<div className="relative z-10 pt-10"> |
|
|
<p className="text-yellow-200 text-lg font-bold mb-2">{t.pddRedPacketTitle}</p> |
|
|
<h3 className="text-3xl font-bold text-white mb-8">{t.pddRedPacketDesc}</h3> |
|
|
|
|
|
<button |
|
|
onClick={handleOpen} |
|
|
className="w-24 h-24 rounded-full bg-yellow-400 border-4 border-yellow-200 text-red-600 font-bold text-xl shadow-lg hover:scale-110 transition-transform flex items-center justify-center mx-auto" |
|
|
> |
|
|
<span className="animate-pulse">{t.pddRedPacketCta}</span> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
) : ( |
|
|
// Stage 2: The "Almost There" Trap |
|
|
<div className="bg-white rounded-3xl shadow-2xl overflow-hidden relative"> |
|
|
<div className="bg-red-500 p-6 text-center pb-12"> |
|
|
<p className="text-white/80 text-sm font-bold uppercase tracking-wider">Account Balance</p> |
|
|
<h2 className="text-5xl font-bold text-white mt-2">¥{amount}</h2> |
|
|
</div> |
|
|
|
|
|
<div className="px-6 -mt-8 relative z-10"> |
|
|
<div className="bg-white rounded-xl shadow-lg p-6 border border-gray-100 text-center"> |
|
|
<p className="text-gray-800 font-bold text-lg">{t.pddWithdrawTitle}</p> |
|
|
<div className="w-full h-3 bg-gray-100 rounded-full mt-3 overflow-hidden"> |
|
|
<div className="h-full bg-red-500 animate-pulse" style={{ width: `${(amount / max) * 100}%` }}></div> |
|
|
</div> |
|
|
<p className="text-xs text-red-500 mt-2 font-bold">{t.pddWithdrawDesc}</p> |
|
|
|
|
|
<button |
|
|
onClick={handleWithdraw} |
|
|
className="w-full py-3 bg-red-500 text-white rounded-full font-bold mt-4 shadow-lg shadow-red-200 hover:bg-red-600 flex items-center justify-center gap-2" |
|
|
> |
|
|
<Gift className="w-4 h-4" /> Share to Withdraw |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div className="p-6 bg-gray-50"> |
|
|
<div className="space-y-3"> |
|
|
{['User 123 withdrew ¥2000', 'Amy unlocked VIP', 'Mike got ¥500'].map((txt, i) => ( |
|
|
<div key={i} className="flex items-center gap-2 text-xs text-gray-500"> |
|
|
<div className="w-6 h-6 bg-gray-200 rounded-full"></div> |
|
|
<span>{txt}</span> |
|
|
<span className="ml-auto text-gray-400">1m ago</span> |
|
|
</div> |
|
|
))} |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
)} |
|
|
</div> |
|
|
</div> |
|
|
); |
|
|
}; |