File size: 5,858 Bytes
8fb4cca |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
import React, { useState, useEffect } from 'react';
import { X, Gift } from 'lucide-react';
import { Language, AdminConfig, UserAccount } from '../types';
import { TRANSLATIONS } from '../constants/translations';
// @ts-ignore
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];
// Determine target amount
const max = adminConfig.redPacketMax || 2000;
// If user exists, show their balance. If no user (guest), show default almost-max (e.g. max - 20)
const userBalance = user?.redPacketBalance ?? (max - 20);
useEffect(() => {
if (isVisible) {
setStage('closed');
// Animate amount up to userBalance
let current = 0;
// Animation speed
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']
});
// Ensure user gets this balance if they haven't already
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>
);
}; |