Spaces:
Paused
Paused
| import React, { useState, useEffect } from 'react'; | |
| import { X, Zap, Timer } from 'lucide-react'; | |
| import { Language, WeddingStyle, AdminConfig, UserAccount } from '../types'; | |
| import { TRANSLATIONS } from '../constants/translations'; | |
| interface SlashModalProps { | |
| isVisible: boolean; | |
| onClose: () => void; | |
| language: Language; | |
| style: WeddingStyle | null; | |
| onUnlock: () => void; | |
| adminConfig: AdminConfig; | |
| user?: UserAccount; | |
| onUpdateUser: (progress: Record<string, number>) => void; | |
| onOpenShare: () => void; | |
| } | |
| export const SlashModal: React.FC<SlashModalProps> = ({ | |
| isVisible, onClose, language, style, onUnlock, adminConfig, user, onUpdateUser, onOpenShare | |
| }) => { | |
| const t = TRANSLATIONS[language]; | |
| const [progress, setProgress] = useState(98); | |
| useEffect(() => { | |
| if (isVisible && style) { | |
| if (user?.slashProgress && user.slashProgress[style.id]) { | |
| setProgress(user.slashProgress[style.id]); | |
| } else { | |
| setProgress(98); | |
| } | |
| } | |
| }, [isVisible, style, user]); | |
| if (!isVisible || !style) return null; | |
| const styleName = (t.styles as any)[style.id] || style.name; | |
| const handleInvite = () => { | |
| onOpenShare(); | |
| // Update progress logic | |
| const remaining = 100 - progress; | |
| // Slash logic: Cut 50-80% of remaining | |
| const cutPercent = 0.5 + Math.random() * 0.3; | |
| let newProgress = progress + (remaining * cutPercent); | |
| // If very close, complete it (e.g. > 99.5) | |
| if (newProgress > 99.5) { | |
| newProgress = 100; | |
| } | |
| setProgress(newProgress); | |
| if (user) { | |
| const newMap = { ...(user.slashProgress || {}), [style.id]: newProgress }; | |
| onUpdateUser(newMap); | |
| } | |
| if (newProgress >= 100) { | |
| onUnlock(); | |
| } | |
| onClose(); | |
| }; | |
| return ( | |
| <div className="fixed inset-0 z-[110] flex items-center justify-center p-4 bg-black/70 backdrop-blur-sm animate-fade-in"> | |
| <div className="bg-gradient-to-b from-orange-500 to-red-600 rounded-2xl shadow-2xl w-full max-w-sm overflow-hidden relative text-white"> | |
| <button onClick={onClose} className="absolute top-2 right-2 p-1.5 bg-black/20 rounded-full hover:bg-black/40 z-20"> | |
| <X className="w-4 h-4" /> | |
| </button> | |
| <div className="p-6 text-center"> | |
| <div className="inline-block px-3 py-1 bg-black/30 rounded-full text-xs font-bold mb-4 flex items-center gap-1 mx-auto border border-white/20"> | |
| <Timer className="w-3 h-3" /> Ends in 23:59:10 | |
| </div> | |
| <h2 className="text-2xl font-bold italic drop-shadow-md">{t.pddSlashTitle}</h2> | |
| <p className="text-orange-100 text-sm mt-1">{t.pddSlashDesc}</p> | |
| {/* Product Card */} | |
| <div className="bg-white text-gray-900 rounded-xl p-3 mt-6 flex items-center gap-3 shadow-lg"> | |
| <div className="w-16 h-16 bg-gray-200 rounded-lg shrink-0 overflow-hidden relative"> | |
| <div className={`w-full h-full ${style.coverColor} opacity-50`}></div> | |
| <div className="absolute inset-0 flex items-center justify-center"> | |
| <span className="text-xs font-bold text-gray-500">IMG</span> | |
| </div> | |
| </div> | |
| <div className="text-left flex-1"> | |
| <p className="font-bold text-sm truncate">{styleName}</p> | |
| <p className="text-xs text-gray-500">VIP Premium Collection</p> | |
| <p className="text-red-500 font-bold text-sm mt-1">¥0.00 <span className="text-gray-400 line-through text-xs">¥199</span></p> | |
| </div> | |
| </div> | |
| {/* Progress Bar */} | |
| <div className="mt-6 relative"> | |
| <div className="flex justify-between text-xs font-bold mb-1"> | |
| <span className="text-yellow-200">{t.pddSlashProgress}</span> | |
| <span>{progress.toFixed(2)}%</span> | |
| </div> | |
| <div className="h-4 bg-black/30 rounded-full overflow-hidden border border-white/20"> | |
| <div className="h-full bg-gradient-to-r from-yellow-300 to-yellow-500 relative" style={{ width: `${progress}%` }}> | |
| <div className="absolute top-0 right-0 h-full w-2 bg-white/50 animate-pulse"></div> | |
| </div> | |
| </div> | |
| <div className="absolute -right-2 -top-2 bg-red-100 text-red-600 text-[10px] font-bold px-1.5 rounded-full border border-red-500 shadow-sm animate-bounce"> | |
| Only {(100 - progress).toFixed(2)}% left! | |
| </div> | |
| </div> | |
| <button | |
| onClick={handleInvite} | |
| className="w-full py-3 bg-yellow-400 hover:bg-yellow-300 text-red-700 font-extrabold text-lg rounded-full mt-6 shadow-xl shadow-orange-700/50 flex items-center justify-center gap-2 transform active:scale-95 transition-all" | |
| > | |
| <Zap className="w-5 h-5 fill-current" /> {t.pddSlashCta} | |
| </button> | |
| </div> | |
| {/* Social Proof List */} | |
| <div className="bg-white/10 p-4 border-t border-white/10"> | |
| <p className="text-xs text-white/60 mb-2 text-center">Friends who helped</p> | |
| <div className="flex justify-center -space-x-2"> | |
| {[1,2,3].map(i => ( | |
| <div key={i} className="w-8 h-8 rounded-full bg-gray-200 border-2 border-orange-500"></div> | |
| ))} | |
| <div className="w-8 h-8 rounded-full bg-gray-800 border-2 border-orange-500 flex items-center justify-center text-[10px] text-white"> | |
| +99 | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }; |