| 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> |
| ); |
| }; |