MINGLEHUB / src /App.tsx
VISHAL18for4's picture
Upload App.tsx
d4eae5e verified
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { io, Socket } from 'socket.io-client';
import {
Video, VideoOff, Mic, MicOff, Send, SkipForward,
StopCircle, MessageSquare, User, Loader2, Camera,
Shield, Globe, Zap, Target, ChevronDown, ChevronUp,
MessageCircle, Sparkles, Users, Lock
} from 'lucide-react';
import { motion, AnimatePresence } from 'motion/react';
type Page = 'landing' | 'setup' | 'chat';
type ChatType = 'text' | 'video';
type Message = { sender: 'me' | 'stranger' | 'system'; text: string };
// ✅ FIXED: ExpressTURN free TURN server (replaces broken openrelay)
const STUN_SERVERS = {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun1.l.google.com:19302" },
{ urls: "stun:stun2.l.google.com:19302" },
{
urls: "turn:free.expressturn.com:3478",
username: "000000002092388734",
credential: "hLJdGxtDrgYmtIB4JWcBgXm6Xw0="
},
{
urls: "turn:free.expressturn.com:3478?transport=tcp",
username: "000000002092388734",
credential: "hLJdGxtDrgYmtIB4JWcBgXm6Xw0="
}
]
};
export default function App() {
const [currentPage, setCurrentPage] = useState<Page>('landing');
const [chatType, setChatType] = useState<ChatType>('text');
const [interests, setInterests] = useState('');
const [onlineCount, setOnlineCount] = useState(0);
const [messages, setMessages] = useState<Message[]>([]);
const [inputText, setInputText] = useState('');
const [isMuted, setIsMuted] = useState(false);
const [isVideoOff, setIsVideoOff] = useState(false);
const [showPermissionModal, setShowPermissionModal] = useState(false);
const [isSearching, setIsSearching] = useState(false);
const [isConnected, setIsConnected] = useState(false); // ✅ NEW: track if matched
const [error, setError] = useState<string | null>(null);
const socketRef = useRef<Socket | null>(null);
const localStreamRef = useRef<MediaStream | null>(null);
const pcRef = useRef<RTCPeerConnection | null>(null);
const localVideoRef = useRef<HTMLVideoElement>(null);
const remoteVideoRef = useRef<HTMLVideoElement>(null);
const chatEndRef = useRef<HTMLDivElement>(null);
const hasJoinedQueueRef = useRef(false);
const isMatchedRef = useRef(false); // ✅ NEW: prevent re-joining after match
// Initialize socket
useEffect(() => {
const socket = io({ transports: ["websocket"] });
socketRef.current = socket;
socket.on('online_count', (count: number) => {
setOnlineCount(count);
});
socket.on('waiting', () => {
setIsSearching(true);
setIsConnected(false);
setMessages([{ sender: 'system', text: 'Looking for a stranger...' }]);
});
socket.on('matched', async ({ role, type }) => {
// ✅ FIXED: Mark as matched immediately so no more queue joins happen
isMatchedRef.current = true;
hasJoinedQueueRef.current = true;
setIsSearching(false);
setIsConnected(true);
setMessages([{ sender: 'system', text: `You are now chatting with a random stranger. Say hi!` }]);
if (type === 'video') {
await setupWebRTC(role);
}
});
socket.on('signal', async (data) => {
if (!pcRef.current) return;
try {
if (data.type === 'offer') {
await pcRef.current.setRemoteDescription(new RTCSessionDescription(data));
const answer = await pcRef.current.createAnswer();
await pcRef.current.setLocalDescription(answer);
socketRef.current?.emit('signal', answer);
} else if (data.type === 'answer') {
await pcRef.current.setRemoteDescription(new RTCSessionDescription(data));
} else if (data.type === 'ice') {
await pcRef.current.addIceCandidate(new RTCIceCandidate(data.candidate));
}
} catch (err) {
console.error('Signaling error:', err);
}
});
socket.on('receive-message', (text: string) => {
setMessages(prev => [...prev, { sender: 'stranger', text }]);
});
socket.on('partner-disconnected', () => {
cleanupPC();
isMatchedRef.current = false;
setIsConnected(false);
setMessages(prev => [...prev, { sender: 'system', text: 'Stranger has disconnected.' }]);
});
return () => {
socket.disconnect();
};
}, []);
useEffect(() => {
chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]);
const cleanupPC = () => {
if (pcRef.current) {
pcRef.current.close();
pcRef.current = null;
}
if (remoteVideoRef.current) {
remoteVideoRef.current.srcObject = null;
}
if (localStreamRef.current) {
localStreamRef.current.getTracks().forEach(track => track.stop());
localStreamRef.current = null;
}
if (localVideoRef.current) {
localVideoRef.current.srcObject = null;
}
};
const setupWebRTC = async (role: 'initiator' | 'receiver') => {
cleanupPC();
// ✅ Get fresh media stream before creating peer connection
const stream = await getMedia();
if (!stream) {
console.error('No media stream available for WebRTC');
return;
}
const pc = new RTCPeerConnection(STUN_SERVERS);
pcRef.current = pc;
// ✅ Log ICE connection state for debugging
pc.oniceconnectionstatechange = () => {
console.log('ICE state:', pc.iceConnectionState);
};
pc.onicecandidate = (event) => {
if (event.candidate) {
socketRef.current?.emit('signal', { type: 'ice', candidate: event.candidate });
}
};
pc.ontrack = (event) => {
console.log('Got remote track!', event.streams[0]);
if (remoteVideoRef.current && event.streams[0]) {
remoteVideoRef.current.srcObject = event.streams[0];
// ✅ Force play on mobile
remoteVideoRef.current.play().catch(e => console.log('Play error:', e));
}
};
// ✅ Add tracks from fresh stream
stream.getTracks().forEach(track => {
pc.addTrack(track, stream);
});
if (role === 'initiator') {
const offer = await pc.createOffer({
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await pc.setLocalDescription(offer);
socketRef.current?.emit('signal', offer);
}
};
const getMedia = async () => {
if (localStreamRef.current) return localStreamRef.current;
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user', width: { ideal: 640 }, height: { ideal: 480 } },
audio: true
});
localStreamRef.current = stream;
if (localVideoRef.current) {
localVideoRef.current.srcObject = stream;
localVideoRef.current.play().catch(e => console.log('Local play error:', e));
}
return stream;
} catch (err) {
console.error('Media access error:', err);
setError('Please allow camera & microphone access to use video chat.');
return null;
}
};
const startChatting = async (type: ChatType) => {
setChatType(type);
if (type === 'video') {
const hasPermission = localStorage.getItem('camera_permission_granted');
if (!hasPermission) {
setShowPermissionModal(true);
return;
}
const stream = await getMedia();
if (!stream) return;
}
setCurrentPage('chat');
setMessages([]);
isMatchedRef.current = false; // ✅ Reset match state
if (!hasJoinedQueueRef.current) {
hasJoinedQueueRef.current = true;
socketRef.current?.emit('join-queue', { type, interests });
}
};
const handlePermissionConfirm = async () => {
setShowPermissionModal(false);
const stream = await getMedia();
if (stream) {
localStorage.setItem('camera_permission_granted', 'true');
setCurrentPage('chat');
setMessages([]);
isMatchedRef.current = false; // ✅ Reset match state
if (!hasJoinedQueueRef.current) {
hasJoinedQueueRef.current = true;
socketRef.current?.emit('join-queue', { type: 'video', interests });
}
}
};
const handleNext = () => {
cleanupPC();
setMessages([]);
isMatchedRef.current = false; // ✅ Reset for next match
hasJoinedQueueRef.current = true;
setIsConnected(false);
socketRef.current?.emit('leave-chat');
socketRef.current?.emit('join-queue', { type: chatType, interests });
};
const handleStop = () => {
cleanupPC();
hasJoinedQueueRef.current = false;
isMatchedRef.current = false;
socketRef.current?.emit('leave-chat');
setCurrentPage('setup');
setMessages([]);
setIsSearching(false);
setIsConnected(false);
};
const sendMessage = (e: React.FormEvent) => {
e.preventDefault();
if (inputText.trim()) {
socketRef.current?.emit('send-message', inputText);
setMessages(prev => [...prev, { sender: 'me', text: inputText }]);
setInputText('');
}
};
const toggleMute = () => {
if (localStreamRef.current) {
localStreamRef.current.getAudioTracks().forEach(track => {
track.enabled = !track.enabled;
});
setIsMuted(prev => !prev);
}
};
const toggleVideo = () => {
if (localStreamRef.current) {
localStreamRef.current.getVideoTracks().forEach(track => {
track.enabled = !track.enabled;
});
setIsVideoOff(prev => !prev);
}
};
const FeatureCard = ({ icon: Icon, title, desc }: { icon: any, title: string, desc: string }) => (
<div className="p-6 rounded-2xl flex flex-col items-center text-center gap-3 bg-zinc-900/40 border border-zinc-800/50 hover:border-indigo-500/30 transition-all group">
<div className="p-3 rounded-xl bg-indigo-500/10 text-indigo-400 group-hover:scale-110 transition-transform">
<Icon size={24} />
</div>
<h3 className="text-sm font-bold uppercase tracking-widest text-zinc-200">{title}</h3>
<p className="text-[10px] text-zinc-500 leading-relaxed">{desc}</p>
</div>
);
const FAQItem = ({ question }: { question: string }) => {
const [isOpen, setIsOpen] = useState(false);
return (
<div className="border-b border-zinc-800/50">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full py-5 flex items-center justify-between text-left group"
>
<span className="text-sm font-medium text-zinc-300 group-hover:text-indigo-400 transition-colors">{question}</span>
{isOpen ? <ChevronUp size={18} className="text-indigo-500" /> : <ChevronDown size={18} className="text-zinc-600" />}
</button>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="overflow-hidden"
>
<div className="pb-5 text-xs text-zinc-500 leading-relaxed">
MingleHub uses advanced matching algorithms to connect you with people who share your interests while maintaining a safe and moderated environment for everyone.
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
};
return (
<div className="h-screen font-sans overflow-hidden bg-[#050505] text-white">
<AnimatePresence mode="wait">
{/* PAGE 1: LANDING */}
{currentPage === 'landing' && (
<motion.div
key="landing"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="h-full flex flex-col"
>
<header className="h-20 border-b border-zinc-900 flex items-center justify-between px-8">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-indigo-600 rounded-xl flex items-center justify-center shadow-lg shadow-indigo-600/20">
<Sparkles size={24} className="text-white" />
</div>
<span className="text-2xl font-black tracking-tighter uppercase italic">MingleHub</span>
</div>
<div className="hidden md:flex items-center gap-8 text-[10px] font-bold uppercase tracking-widest text-zinc-500">
<button
onClick={() => window.open(window.location.href, '_blank')}
className="flex items-center gap-2 px-4 py-2 bg-zinc-900 border border-zinc-800 rounded-lg hover:text-indigo-400 hover:border-indigo-500/30 transition-all"
>
<Globe size={14} />
Open in New Tab
</button>
<a href="#" className="hover:text-indigo-400 transition-colors">Safety</a>
<a href="#" className="hover:text-indigo-400 transition-colors">Community</a>
<a href="#" className="hover:text-indigo-400 transition-colors">Support</a>
</div>
</header>
<main className="flex-1 overflow-y-auto">
<div className="max-w-4xl mx-auto py-20 px-8 space-y-24">
<div className="text-center space-y-8">
<motion.div
initial={{ y: 20, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ delay: 0.2 }}
>
<h1 className="text-5xl md:text-7xl font-black tracking-tight leading-none">
Connect with the <br />
<span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-purple-400">World Instantly.</span>
</h1>
</motion.div>
<p className="max-w-xl mx-auto text-zinc-400 text-lg leading-relaxed">
MingleHub is a premium anonymous video and text chat platform. Meet interesting people, make new friends, and explore the world from your screen.
</p>
<div className="pt-8 flex flex-col items-center gap-6">
<button
onClick={() => setCurrentPage('setup')}
className="group relative px-10 py-5 bg-indigo-600 hover:bg-indigo-700 text-white font-black rounded-2xl transition-all transform active:scale-95 shadow-xl shadow-indigo-600/20 overflow-hidden"
>
<span className="relative z-10 flex items-center gap-2">
START MINGLING NOW
<Zap size={18} className="group-hover:animate-pulse" />
</span>
</button>
<div className="flex items-center gap-2 text-[10px] font-bold text-zinc-600 uppercase tracking-widest">
<Users size={14} className="text-indigo-500" />
{onlineCount.toLocaleString()} PEOPLE CURRENTLY ONLINE
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<FeatureCard icon={Lock} title="Private & Secure" desc="End-to-end encrypted signaling and anonymous profiles keep your identity safe." />
<FeatureCard icon={Globe} title="Global Reach" desc="Connect with people from over 190 countries instantly with low latency." />
<FeatureCard icon={Zap} title="Zero Lag" desc="Optimized WebRTC infrastructure ensures smooth video and instant messaging." />
</div>
<div className="space-y-8 pt-12">
<h2 className="text-3xl font-black text-center">Common Questions</h2>
<div className="max-w-2xl mx-auto space-y-2">
<FAQItem question="How does MingleHub ensure user safety?" />
<FAQItem question="Do I need to create an account to chat?" />
<FAQItem question="Is MingleHub free to use?" />
<FAQItem question="Can I use MingleHub on my mobile device?" />
</div>
</div>
<footer className="pt-20 pb-12 border-t border-zinc-900 flex flex-col md:flex-row items-center justify-between gap-8 text-[10px] text-zinc-600 uppercase tracking-widest font-bold">
<div className="flex items-center gap-2">
<Sparkles size={14} className="text-indigo-500" />
<span>© 2026 MingleHub Global</span>
</div>
<div className="flex gap-8">
<a href="#" className="hover:text-indigo-400">Terms</a>
<a href="#" className="hover:text-indigo-400">Privacy</a>
<a href="#" className="hover:text-indigo-400">Guidelines</a>
<a href="#" className="hover:text-indigo-400">Contact</a>
</div>
</footer>
</div>
</main>
</motion.div>
)}
{/* PAGE 2: SETUP */}
{currentPage === 'setup' && (
<motion.div
key="setup"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="h-full flex flex-col"
>
<header className="h-20 border-b border-zinc-900 flex items-center justify-between px-8">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-indigo-600 rounded-xl flex items-center justify-center">
<Sparkles size={24} className="text-white" />
</div>
<span className="text-2xl font-black tracking-tighter uppercase italic">MingleHub</span>
</div>
<div className="flex items-center gap-6">
<button
onClick={() => window.open(window.location.href, '_blank')}
className="hidden md:flex items-center gap-2 px-4 py-2 bg-zinc-900 border border-zinc-800 rounded-lg text-[10px] font-bold uppercase tracking-widest text-zinc-500 hover:text-indigo-400 hover:border-indigo-500/30 transition-all"
>
<Globe size={14} />
Open in New Tab
</button>
<button
onClick={() => setCurrentPage('landing')}
className="text-[10px] font-bold text-zinc-500 hover:text-white uppercase tracking-widest transition-colors"
>
Back to Home
</button>
</div>
</header>
<main className="flex-1 overflow-y-auto">
<div className="max-w-xl mx-auto py-20 px-8 space-y-12">
<div className="text-center space-y-4">
<h1 className="text-4xl font-black tracking-tight">Configure Chat</h1>
<p className="text-zinc-500 text-sm">Choose how you want to connect today.</p>
</div>
<div className="space-y-10">
<div className="grid grid-cols-2 gap-4">
<button
onClick={() => startChatting('text')}
className={`p-8 rounded-3xl border-2 transition-all flex flex-col items-center gap-4 group ${chatType === 'text' ? 'border-indigo-600 bg-indigo-600/10' : 'border-zinc-800 bg-zinc-900/50 hover:border-zinc-700'}`}
>
<div className={`p-4 rounded-2xl transition-colors ${chatType === 'text' ? 'bg-indigo-600 text-white' : 'bg-zinc-800 text-zinc-500 group-hover:text-zinc-300'}`}>
<MessageSquare size={32} />
</div>
<span className="font-bold uppercase tracking-widest text-sm">Text Chat</span>
</button>
<button
onClick={() => startChatting('video')}
className={`p-8 rounded-3xl border-2 transition-all flex flex-col items-center gap-4 group ${chatType === 'video' ? 'border-indigo-600 bg-indigo-600/10' : 'border-zinc-800 bg-zinc-900/50 hover:border-zinc-700'}`}
>
<div className={`p-4 rounded-2xl transition-colors ${chatType === 'video' ? 'bg-indigo-600 text-white' : 'bg-zinc-800 text-zinc-500 group-hover:text-zinc-300'}`}>
<Video size={32} />
</div>
<span className="font-bold uppercase tracking-widest text-sm">Video Chat</span>
</button>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between">
<label className="text-xs font-bold uppercase tracking-widest text-zinc-500">Your Interests</label>
<span className="text-[10px] text-zinc-600 font-bold uppercase tracking-widest">Optional</span>
</div>
<input
type="text"
value={interests}
onChange={(e) => setInterests(e.target.value)}
placeholder="e.g. Music, Travel, Coding..."
className="w-full px-6 py-4 bg-zinc-900 border border-zinc-800 rounded-2xl text-sm focus:outline-none focus:border-indigo-600 transition-all text-white placeholder:text-zinc-700"
/>
</div>
<div className="p-6 bg-indigo-600/5 border border-indigo-500/10 rounded-2xl flex items-start gap-4">
<Shield size={20} className="text-indigo-500 shrink-0 mt-1" />
<div className="space-y-1">
<h4 className="text-xs font-bold uppercase tracking-widest text-indigo-400">Safety First</h4>
<p className="text-[10px] text-zinc-500 leading-relaxed">
MingleHub is a moderated community. Please be respectful to others. Harassment or inappropriate behavior will result in a permanent ban.
</p>
</div>
</div>
<button
onClick={() => startChatting(chatType)}
className="w-full py-5 bg-indigo-600 hover:bg-indigo-700 text-white font-black rounded-2xl transition-all transform active:scale-95 shadow-xl shadow-indigo-600/20"
>
ENTER CHAT ROOM
</button>
</div>
</div>
</main>
</motion.div>
)}
{/* PAGE 3: CHAT */}
{currentPage === 'chat' && (
<motion.div
key="chat"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="h-full flex flex-col bg-[#050505]"
>
<header className="h-16 border-b border-zinc-900 flex items-center justify-between px-6">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center">
<Sparkles size={18} className="text-white" />
</div>
<span className="font-black tracking-tighter text-sm uppercase italic">MingleHub</span>
</div>
<div className="flex items-center gap-6">
<div className="flex items-center gap-2 px-3 py-1 bg-green-500/10 border border-green-500/20 rounded-full">
<div className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse" />
<span className="text-[9px] font-bold text-green-500 uppercase tracking-widest">Live</span>
</div>
{chatType === 'video' && (
<div className="flex items-center gap-2">
<button
onClick={toggleMute}
className={`p-2 rounded-lg transition-colors ${isMuted ? 'bg-red-500/20 text-red-400' : 'text-zinc-500 hover:text-white'}`}
>
{isMuted ? <MicOff size={18} /> : <Mic size={18} />}
</button>
<button
onClick={toggleVideo}
className={`p-2 rounded-lg transition-colors ${isVideoOff ? 'bg-red-500/20 text-red-400' : 'text-zinc-500 hover:text-white'}`}
>
{isVideoOff ? <VideoOff size={18} /> : <Video size={18} />}
</button>
</div>
)}
<button
onClick={handleStop}
className="p-2 text-zinc-600 hover:text-white transition-colors"
>
<StopCircle size={22} />
</button>
</div>
</header>
<main className="flex-1 flex flex-col md:flex-row overflow-hidden">
<div className="flex-1 flex flex-col bg-black relative">
{chatType === 'video' ? (
<div className="flex-1 grid grid-rows-2 md:grid-rows-1 md:grid-cols-2 gap-1 p-1">
<div className="relative bg-zinc-900 rounded-2xl overflow-hidden border border-zinc-800/50">
<video
ref={remoteVideoRef}
autoPlay
playsInline
className="w-full h-full object-cover"
/>
<div className="absolute top-4 left-4 px-3 py-1 bg-black/60 backdrop-blur-md rounded-lg text-[9px] font-bold uppercase tracking-widest border border-white/5">Stranger</div>
{!isConnected && !isSearching && (
<div className="absolute inset-0 flex items-center justify-center flex-col gap-4">
<User size={48} className="text-zinc-800" />
<p className="text-zinc-600 text-[10px] font-bold uppercase tracking-widest">Waiting for video...</p>
</div>
)}
</div>
<div className="relative bg-zinc-900 rounded-2xl overflow-hidden border border-zinc-800/50">
<video
ref={localVideoRef}
autoPlay
muted
playsInline
className="w-full h-full object-cover scale-x-[-1]"
/>
<div className="absolute top-4 left-4 px-3 py-1 bg-black/60 backdrop-blur-md rounded-lg text-[9px] font-bold uppercase tracking-widest border border-white/5">You</div>
{isVideoOff && (
<div className="absolute inset-0 bg-zinc-950 flex items-center justify-center">
<VideoOff size={32} className="text-zinc-800" />
</div>
)}
</div>
</div>
) : (
<div className="flex-1 flex items-center justify-center flex-col gap-8 p-12 text-center">
<div className="w-28 h-28 bg-indigo-600/10 rounded-full flex items-center justify-center border border-indigo-500/20">
<MessageSquare size={48} className="text-indigo-500" />
</div>
<div className="space-y-3">
<h2 className="text-3xl font-black tracking-tight">Secure Text Chat</h2>
<p className="text-zinc-500 text-sm max-w-xs mx-auto">You are now connected with a stranger. Be kind and enjoy the conversation.</p>
</div>
</div>
)}
{isSearching && (
<div className="absolute inset-0 z-50 bg-black/90 backdrop-blur-md flex flex-col items-center justify-center gap-8">
<div className="relative">
<div className="w-20 h-20 border-4 border-indigo-500/20 border-t-indigo-500 rounded-full animate-spin" />
<div className="absolute inset-0 flex items-center justify-center">
<Globe size={28} className="text-zinc-700" />
</div>
</div>
<div className="text-center space-y-3">
<h3 className="text-2xl font-black tracking-tight">Searching...</h3>
<p className="text-zinc-500 text-[10px] font-bold uppercase tracking-[0.2em]">Matching with a global stranger</p>
</div>
</div>
)}
</div>
<div className="w-full md:w-[450px] flex flex-col border-l border-zinc-900 bg-[#080808]">
<div className="flex-1 overflow-y-auto p-6 space-y-6 scrollbar-hide">
<AnimatePresence initial={false}>
{messages.map((msg, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className={`flex flex-col ${msg.sender === 'me' ? 'items-end' : msg.sender === 'system' ? 'items-center' : 'items-start'}`}
>
{msg.sender === 'system' ? (
<div className="px-5 py-2 bg-zinc-900/40 border border-zinc-800/50 rounded-full text-[9px] font-bold text-zinc-500 uppercase tracking-[0.15em]">
{msg.text}
</div>
) : (
<>
<span className="text-[8px] font-black uppercase tracking-widest text-zinc-600 mb-1.5 px-1">
{msg.sender === 'me' ? 'You' : 'Stranger'}
</span>
<div className={`px-5 py-3 rounded-2xl text-[13px] max-w-[85%] leading-relaxed ${
msg.sender === 'me'
? 'bg-indigo-600 text-white rounded-tr-none shadow-lg shadow-indigo-600/10'
: 'bg-zinc-900 text-zinc-200 rounded-tl-none border border-zinc-800/50'
}`}>
{msg.text}
</div>
</>
)}
</motion.div>
))}
</AnimatePresence>
<div ref={chatEndRef} />
</div>
<div className="p-6 border-t border-zinc-900 space-y-6 bg-[#080808]">
<div className="flex gap-3">
<button
onClick={handleStop}
className="px-6 py-4 bg-zinc-900 hover:bg-zinc-800 text-zinc-400 font-bold rounded-2xl transition-all text-xs uppercase tracking-widest"
>
Stop
</button>
<button
onClick={handleNext}
className="flex-1 py-4 bg-indigo-600 hover:bg-indigo-700 text-white font-black rounded-2xl transition-all flex items-center justify-center gap-3 shadow-lg shadow-indigo-600/20 uppercase tracking-widest text-xs"
>
<SkipForward size={18} />
Next Partner
</button>
</div>
<form onSubmit={sendMessage} className="relative">
<input
type="text"
value={inputText}
onChange={(e) => setInputText(e.target.value)}
placeholder="Type something..."
className="w-full bg-zinc-900 border border-zinc-800 rounded-2xl py-5 pl-6 pr-16 text-sm focus:outline-none focus:border-indigo-600 transition-all text-white placeholder:text-zinc-700"
/>
<button
type="submit"
disabled={!inputText.trim()}
className="absolute right-3 top-1/2 -translate-y-1/2 p-3 text-indigo-500 hover:text-indigo-400 disabled:text-zinc-800 transition-colors"
>
<Send size={22} />
</button>
</form>
</div>
</div>
</main>
</motion.div>
)}
</AnimatePresence>
{/* Permission Modal */}
<AnimatePresence>
{showPermissionModal && (
<div className="fixed inset-0 z-[100] flex items-center justify-center p-6">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="absolute inset-0 bg-black/95 backdrop-blur-2xl"
/>
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
className="relative w-full max-w-sm bg-zinc-900 border border-zinc-800 rounded-[40px] p-12 text-center space-y-10 shadow-2xl"
>
<div className="w-28 h-28 bg-indigo-600/10 rounded-full flex items-center justify-center mx-auto border border-indigo-500/20">
<Camera size={56} className="text-indigo-500" />
</div>
<div className="space-y-4">
<h2 className="text-4xl font-black tracking-tight text-white leading-none">Enable <br /> Camera</h2>
<p className="text-zinc-500 text-sm leading-relaxed">
To connect via video, MingleHub requires access to your camera and microphone.
</p>
</div>
<div className="flex flex-col gap-4">
<button
onClick={handlePermissionConfirm}
className="w-full py-6 bg-indigo-600 text-white font-black rounded-3xl hover:bg-indigo-700 transition-all shadow-xl shadow-indigo-600/20 uppercase tracking-widest text-xs"
>
ALLOW ACCESS
</button>
<button
onClick={() => setShowPermissionModal(false)}
className="w-full py-6 bg-zinc-800 text-zinc-500 font-bold rounded-3xl hover:bg-zinc-700 transition-all uppercase tracking-widest text-[10px]"
>
MAYBE LATER
</button>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
</div>
);
}