ai / components /RedPacketModal.tsx
Lianjx's picture
Upload 75 files
8fb4cca verified
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>
);
};