File size: 4,155 Bytes
a7b6b46 | 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 | import { useState, useEffect, useRef } from 'react';
import { motion } from 'framer-motion';
import { useGameStore } from '../../store/gameStore';
import { Card } from '../game/Card';
import { emitArrangeHand } from '../../socket/socket';
const AUTO_CONFIRM_TIMEOUT = 15000; // 15 seconds
interface ArrangeHandModalProps {
onClose: () => void;
}
export function ArrangeHandModal({ onClose }: ArrangeHandModalProps) {
const myHand = useGameStore((state) => state.myHand);
const [orderedCards, setOrderedCards] = useState(myHand.map(c => c.instanceId));
const [selectedIndex, setSelectedIndex] = useState<number | null>(null);
const [timeLeft, setTimeLeft] = useState(AUTO_CONFIRM_TIMEOUT / 1000);
const hasConfirmed = useRef(false);
const handleCardClick = (index: number) => {
if (selectedIndex === null) {
// First card selected
setSelectedIndex(index);
} else if (selectedIndex === index) {
// Clicked same card, deselect
setSelectedIndex(null);
} else {
// Second card selected - swap them
const newOrder = [...orderedCards];
[newOrder[selectedIndex], newOrder[index]] = [newOrder[index], newOrder[selectedIndex]];
setOrderedCards(newOrder);
setSelectedIndex(null);
}
};
const handleConfirm = () => {
if (hasConfirmed.current) return;
hasConfirmed.current = true;
emitArrangeHand(orderedCards);
onClose();
};
// Auto-confirm timer
useEffect(() => {
const interval = setInterval(() => {
setTimeLeft((prev) => {
if (prev <= 1) {
clearInterval(interval);
handleConfirm();
return 0;
}
return prev - 1;
});
}, 1000);
return () => clearInterval(interval);
}, []);
return (
<motion.div
className="modal-overlay"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<motion.div
className="modal-content max-w-2xl"
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
onClick={(e) => e.stopPropagation()}
>
<h2 className="modal-title">🔀 Arrange Your Cards</h2>
<p className="text-papyrus text-center mb-2">
Someone is about to steal from you!
</p>
<p className="text-yellow-400 text-center text-sm mb-2">
Tap two cards to swap their positions. Hide your good cards!
</p>
<p className={`text-center text-sm mb-4 ${timeLeft <= 3 ? 'text-red-400 font-bold animate-pulse' : 'text-egyptian-gold'}`}>
⏱️ Auto-confirm in {timeLeft}s
</p>
<div className="grid grid-cols-4 gap-2 mb-6 justify-items-center">
{orderedCards.map((instanceId, index) => {
const card = myHand.find(c => c.instanceId === instanceId);
if (!card) return null;
const isSelected = selectedIndex === index;
return (
<motion.div
key={instanceId}
className={`cursor-pointer rounded-lg ${isSelected ? 'ring-4 ring-egyptian-gold' : ''}`}
onClick={() => handleCardClick(index)}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
animate={isSelected ? { y: -10 } : { y: 0 }}
>
<Card card={card} size="small" />
<p className="text-center text-xs text-papyrus mt-1">#{index + 1}</p>
</motion.div>
);
})}
</div>
{selectedIndex !== null && (
<p className="text-egyptian-gold text-center text-sm mb-4">
Card #{selectedIndex + 1} selected. Tap another card to swap!
</p>
)}
<div className="flex gap-3">
<button onClick={handleConfirm} className="btn btn-secondary flex-1">
Keep Order
</button>
<button onClick={handleConfirm} className="btn btn-primary flex-1">
Confirm
</button>
</div>
</motion.div>
</motion.div>
);
}
|