Place2Play / client /src /components /ChatBox.tsx
3v324v23's picture
feat: comprehensive game logic fixes, UX upgrades, and mobile optimization
2498190
'use client';
import { useState, useEffect, useRef } from 'react';
import { Send, X, MessageSquare, ShieldAlert } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
interface Message {
sender: string;
text: string;
type?: 'chat' | 'system';
avatar?: string;
}
interface ChatDrawerProps {
isOpen: boolean;
onClose: () => void;
messages: Message[];
onSendMessage: (text: string) => void;
me: any;
isDead?: boolean;
}
export default function ChatDrawer({ isOpen, onClose, messages, onSendMessage, me, isDead }: ChatDrawerProps) {
const [inputValue, setInputValue] = useState('');
const messagesEndRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isOpen) {
messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [messages, isOpen]);
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!inputValue.trim()) return;
onSendMessage(inputValue.trim());
setInputValue('');
};
return (
<AnimatePresence>
{isOpen && (
<>
{/* Backdrop */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-[100]"
/>
{/* Drawer */}
<motion.div
initial={{ x: '100%' }}
animate={{ x: 0 }}
exit={{ x: '100%' }}
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
className="fixed right-0 top-0 bottom-0 w-full md:max-w-md bg-space-950 border-l border-white/10 shadow-2xl z-[101] flex flex-col"
>
{/* Header */}
<div className="p-4 md:p-6 border-b border-white/5 flex justify-between items-center bg-space-900/50">
<div className="flex items-center gap-3">
<div className="p-2 bg-neon-cyan/10 rounded-xl">
<MessageSquare className="text-neon-cyan" size={20} />
</div>
<div>
<h2 className="text-white font-black text-lg tracking-tight uppercase">Mission Comms</h2>
<p className="text-[10px] font-bold text-white/30 uppercase tracking-widest">Secure Channel Active</p>
</div>
</div>
<button
onClick={onClose}
className="p-2 hover:bg-white/5 rounded-full text-white/30 hover:text-white transition-colors"
>
<X size={24} />
</button>
</div>
{/* Messages Area */}
<div className="flex-1 overflow-y-auto p-6 space-y-6 custom-scrollbar bg-[url('/images/grid.png')] bg-repeat opacity-95">
{messages.map((msg, i) => {
const isMe = msg.sender === me?.username;
const isSystem = msg.type === 'system';
if (isSystem) {
return (
<div key={i} className="flex justify-center">
<div className="bg-white/5 border border-white/10 px-4 py-1.5 rounded-full flex items-center gap-2">
<ShieldAlert size={12} className="text-neon-cyan" />
<span className="text-[10px] font-black text-white/40 uppercase tracking-wider">{msg.text}</span>
</div>
</div>
);
}
return (
<motion.div
key={i}
initial={{ opacity: 0, x: isMe ? 20 : -20 }}
animate={{ opacity: 1, x: 0 }}
className={`flex ${isMe ? 'justify-end' : 'justify-start'}`}
>
<div className={`flex gap-3 max-w-[85%] ${isMe ? 'flex-row-reverse' : 'flex-row'}`}>
{/* Avatar */}
<div className="w-8 h-8 rounded-full border border-white/10 overflow-hidden shrink-0 mt-1">
<img src={`/images/${msg.avatar || 'warga.jpeg'}`} className="w-full h-full object-cover" />
</div>
<div className={`flex flex-col ${isMe ? 'items-end' : 'items-start'}`}>
<span className="text-[10px] font-black text-white/30 uppercase tracking-tighter mb-1 ml-1">
{msg.sender}
</span>
<div className={`px-4 py-3 rounded-2xl text-sm font-bold shadow-xl ${
isMe
? 'bg-neon-cyan text-space-950 rounded-tr-none'
: 'bg-space-800 text-white border border-white/5 rounded-tl-none'
}`}>
{msg.text}
</div>
</div>
</div>
</motion.div>
);
})}
<div ref={messagesEndRef} />
</div>
{/* Input Area */}
<div className="p-4 md:p-6 bg-space-900/80 backdrop-blur-xl border-t border-white/10">
<form onSubmit={handleSubmit} className="relative group">
<div className="relative flex items-center bg-space-950/80 border-2 border-white/5 rounded-2xl md:rounded-3xl p-1 focus-within:border-neon-cyan/50 focus-within:shadow-[0_0_20px_rgba(34,211,238,0.15)] transition-all duration-300">
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
placeholder={isDead ? "CHANNEL TERKUNCI" : "KETIK PESAN..."}
disabled={isDead}
className="w-full bg-transparent border-none outline-none focus:outline-none focus:ring-0 py-4 md:py-5 px-6 text-white placeholder-white/10 text-base md:text-lg font-display font-bold tracking-wide uppercase"
/>
<button
type="submit"
disabled={!inputValue.trim() || isDead}
className="mr-1 md:mr-2 p-4 md:p-5 bg-neon-cyan text-space-950 rounded-xl md:rounded-2xl shadow-xl disabled:opacity-20 disabled:grayscale transition-all hover:scale-105 active:scale-95 group-focus-within:shadow-neon-cyan/20"
>
<Send size={24} strokeWidth={3} />
</button>
</div>
</form>
</div>
</motion.div>
</>
)}
</AnimatePresence>
);
}