VISHAL18for4 commited on
Commit
d4eae5e
·
verified ·
1 Parent(s): 50ab7ec

Upload App.tsx

Browse files
Files changed (1) hide show
  1. src/App.tsx +758 -0
src/App.tsx ADDED
@@ -0,0 +1,758 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect, useRef, useState, useCallback } from 'react';
2
+ import { io, Socket } from 'socket.io-client';
3
+ import {
4
+ Video, VideoOff, Mic, MicOff, Send, SkipForward,
5
+ StopCircle, MessageSquare, User, Loader2, Camera,
6
+ Shield, Globe, Zap, Target, ChevronDown, ChevronUp,
7
+ MessageCircle, Sparkles, Users, Lock
8
+ } from 'lucide-react';
9
+ import { motion, AnimatePresence } from 'motion/react';
10
+
11
+ type Page = 'landing' | 'setup' | 'chat';
12
+ type ChatType = 'text' | 'video';
13
+ type Message = { sender: 'me' | 'stranger' | 'system'; text: string };
14
+
15
+ // ✅ FIXED: ExpressTURN free TURN server (replaces broken openrelay)
16
+ const STUN_SERVERS = {
17
+ iceServers: [
18
+ { urls: "stun:stun.l.google.com:19302" },
19
+ { urls: "stun:stun1.l.google.com:19302" },
20
+ { urls: "stun:stun2.l.google.com:19302" },
21
+ {
22
+ urls: "turn:free.expressturn.com:3478",
23
+ username: "000000002092388734",
24
+ credential: "hLJdGxtDrgYmtIB4JWcBgXm6Xw0="
25
+ },
26
+ {
27
+ urls: "turn:free.expressturn.com:3478?transport=tcp",
28
+ username: "000000002092388734",
29
+ credential: "hLJdGxtDrgYmtIB4JWcBgXm6Xw0="
30
+ }
31
+ ]
32
+ };
33
+
34
+ export default function App() {
35
+ const [currentPage, setCurrentPage] = useState<Page>('landing');
36
+ const [chatType, setChatType] = useState<ChatType>('text');
37
+ const [interests, setInterests] = useState('');
38
+ const [onlineCount, setOnlineCount] = useState(0);
39
+ const [messages, setMessages] = useState<Message[]>([]);
40
+ const [inputText, setInputText] = useState('');
41
+ const [isMuted, setIsMuted] = useState(false);
42
+ const [isVideoOff, setIsVideoOff] = useState(false);
43
+ const [showPermissionModal, setShowPermissionModal] = useState(false);
44
+ const [isSearching, setIsSearching] = useState(false);
45
+ const [isConnected, setIsConnected] = useState(false); // ✅ NEW: track if matched
46
+ const [error, setError] = useState<string | null>(null);
47
+
48
+ const socketRef = useRef<Socket | null>(null);
49
+ const localStreamRef = useRef<MediaStream | null>(null);
50
+ const pcRef = useRef<RTCPeerConnection | null>(null);
51
+ const localVideoRef = useRef<HTMLVideoElement>(null);
52
+ const remoteVideoRef = useRef<HTMLVideoElement>(null);
53
+ const chatEndRef = useRef<HTMLDivElement>(null);
54
+ const hasJoinedQueueRef = useRef(false);
55
+ const isMatchedRef = useRef(false); // ✅ NEW: prevent re-joining after match
56
+
57
+ // Initialize socket
58
+ useEffect(() => {
59
+ const socket = io({ transports: ["websocket"] });
60
+ socketRef.current = socket;
61
+
62
+ socket.on('online_count', (count: number) => {
63
+ setOnlineCount(count);
64
+ });
65
+
66
+ socket.on('waiting', () => {
67
+ setIsSearching(true);
68
+ setIsConnected(false);
69
+ setMessages([{ sender: 'system', text: 'Looking for a stranger...' }]);
70
+ });
71
+
72
+ socket.on('matched', async ({ role, type }) => {
73
+ // ✅ FIXED: Mark as matched immediately so no more queue joins happen
74
+ isMatchedRef.current = true;
75
+ hasJoinedQueueRef.current = true;
76
+ setIsSearching(false);
77
+ setIsConnected(true);
78
+ setMessages([{ sender: 'system', text: `You are now chatting with a random stranger. Say hi!` }]);
79
+
80
+ if (type === 'video') {
81
+ await setupWebRTC(role);
82
+ }
83
+ });
84
+
85
+ socket.on('signal', async (data) => {
86
+ if (!pcRef.current) return;
87
+ try {
88
+ if (data.type === 'offer') {
89
+ await pcRef.current.setRemoteDescription(new RTCSessionDescription(data));
90
+ const answer = await pcRef.current.createAnswer();
91
+ await pcRef.current.setLocalDescription(answer);
92
+ socketRef.current?.emit('signal', answer);
93
+ } else if (data.type === 'answer') {
94
+ await pcRef.current.setRemoteDescription(new RTCSessionDescription(data));
95
+ } else if (data.type === 'ice') {
96
+ await pcRef.current.addIceCandidate(new RTCIceCandidate(data.candidate));
97
+ }
98
+ } catch (err) {
99
+ console.error('Signaling error:', err);
100
+ }
101
+ });
102
+
103
+ socket.on('receive-message', (text: string) => {
104
+ setMessages(prev => [...prev, { sender: 'stranger', text }]);
105
+ });
106
+
107
+ socket.on('partner-disconnected', () => {
108
+ cleanupPC();
109
+ isMatchedRef.current = false;
110
+ setIsConnected(false);
111
+ setMessages(prev => [...prev, { sender: 'system', text: 'Stranger has disconnected.' }]);
112
+ });
113
+
114
+ return () => {
115
+ socket.disconnect();
116
+ };
117
+ }, []);
118
+
119
+ useEffect(() => {
120
+ chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
121
+ }, [messages]);
122
+
123
+ const cleanupPC = () => {
124
+ if (pcRef.current) {
125
+ pcRef.current.close();
126
+ pcRef.current = null;
127
+ }
128
+ if (remoteVideoRef.current) {
129
+ remoteVideoRef.current.srcObject = null;
130
+ }
131
+ if (localStreamRef.current) {
132
+ localStreamRef.current.getTracks().forEach(track => track.stop());
133
+ localStreamRef.current = null;
134
+ }
135
+ if (localVideoRef.current) {
136
+ localVideoRef.current.srcObject = null;
137
+ }
138
+ };
139
+
140
+ const setupWebRTC = async (role: 'initiator' | 'receiver') => {
141
+ cleanupPC();
142
+
143
+ // ✅ Get fresh media stream before creating peer connection
144
+ const stream = await getMedia();
145
+ if (!stream) {
146
+ console.error('No media stream available for WebRTC');
147
+ return;
148
+ }
149
+
150
+ const pc = new RTCPeerConnection(STUN_SERVERS);
151
+ pcRef.current = pc;
152
+
153
+ // ✅ Log ICE connection state for debugging
154
+ pc.oniceconnectionstatechange = () => {
155
+ console.log('ICE state:', pc.iceConnectionState);
156
+ };
157
+
158
+ pc.onicecandidate = (event) => {
159
+ if (event.candidate) {
160
+ socketRef.current?.emit('signal', { type: 'ice', candidate: event.candidate });
161
+ }
162
+ };
163
+
164
+ pc.ontrack = (event) => {
165
+ console.log('Got remote track!', event.streams[0]);
166
+ if (remoteVideoRef.current && event.streams[0]) {
167
+ remoteVideoRef.current.srcObject = event.streams[0];
168
+ // ✅ Force play on mobile
169
+ remoteVideoRef.current.play().catch(e => console.log('Play error:', e));
170
+ }
171
+ };
172
+
173
+ // ✅ Add tracks from fresh stream
174
+ stream.getTracks().forEach(track => {
175
+ pc.addTrack(track, stream);
176
+ });
177
+
178
+ if (role === 'initiator') {
179
+ const offer = await pc.createOffer({
180
+ offerToReceiveAudio: true,
181
+ offerToReceiveVideo: true
182
+ });
183
+ await pc.setLocalDescription(offer);
184
+ socketRef.current?.emit('signal', offer);
185
+ }
186
+ };
187
+
188
+ const getMedia = async () => {
189
+ if (localStreamRef.current) return localStreamRef.current;
190
+ try {
191
+ const stream = await navigator.mediaDevices.getUserMedia({
192
+ video: { facingMode: 'user', width: { ideal: 640 }, height: { ideal: 480 } },
193
+ audio: true
194
+ });
195
+ localStreamRef.current = stream;
196
+ if (localVideoRef.current) {
197
+ localVideoRef.current.srcObject = stream;
198
+ localVideoRef.current.play().catch(e => console.log('Local play error:', e));
199
+ }
200
+ return stream;
201
+ } catch (err) {
202
+ console.error('Media access error:', err);
203
+ setError('Please allow camera & microphone access to use video chat.');
204
+ return null;
205
+ }
206
+ };
207
+
208
+ const startChatting = async (type: ChatType) => {
209
+ setChatType(type);
210
+ if (type === 'video') {
211
+ const hasPermission = localStorage.getItem('camera_permission_granted');
212
+ if (!hasPermission) {
213
+ setShowPermissionModal(true);
214
+ return;
215
+ }
216
+ const stream = await getMedia();
217
+ if (!stream) return;
218
+ }
219
+
220
+ setCurrentPage('chat');
221
+ setMessages([]);
222
+ isMatchedRef.current = false; // ✅ Reset match state
223
+ if (!hasJoinedQueueRef.current) {
224
+ hasJoinedQueueRef.current = true;
225
+ socketRef.current?.emit('join-queue', { type, interests });
226
+ }
227
+ };
228
+
229
+ const handlePermissionConfirm = async () => {
230
+ setShowPermissionModal(false);
231
+ const stream = await getMedia();
232
+ if (stream) {
233
+ localStorage.setItem('camera_permission_granted', 'true');
234
+ setCurrentPage('chat');
235
+ setMessages([]);
236
+ isMatchedRef.current = false; // ✅ Reset match state
237
+ if (!hasJoinedQueueRef.current) {
238
+ hasJoinedQueueRef.current = true;
239
+ socketRef.current?.emit('join-queue', { type: 'video', interests });
240
+ }
241
+ }
242
+ };
243
+
244
+ const handleNext = () => {
245
+ cleanupPC();
246
+ setMessages([]);
247
+ isMatchedRef.current = false; // ✅ Reset for next match
248
+ hasJoinedQueueRef.current = true;
249
+ setIsConnected(false);
250
+ socketRef.current?.emit('leave-chat');
251
+ socketRef.current?.emit('join-queue', { type: chatType, interests });
252
+ };
253
+
254
+ const handleStop = () => {
255
+ cleanupPC();
256
+ hasJoinedQueueRef.current = false;
257
+ isMatchedRef.current = false;
258
+ socketRef.current?.emit('leave-chat');
259
+ setCurrentPage('setup');
260
+ setMessages([]);
261
+ setIsSearching(false);
262
+ setIsConnected(false);
263
+ };
264
+
265
+ const sendMessage = (e: React.FormEvent) => {
266
+ e.preventDefault();
267
+ if (inputText.trim()) {
268
+ socketRef.current?.emit('send-message', inputText);
269
+ setMessages(prev => [...prev, { sender: 'me', text: inputText }]);
270
+ setInputText('');
271
+ }
272
+ };
273
+
274
+ const toggleMute = () => {
275
+ if (localStreamRef.current) {
276
+ localStreamRef.current.getAudioTracks().forEach(track => {
277
+ track.enabled = !track.enabled;
278
+ });
279
+ setIsMuted(prev => !prev);
280
+ }
281
+ };
282
+
283
+ const toggleVideo = () => {
284
+ if (localStreamRef.current) {
285
+ localStreamRef.current.getVideoTracks().forEach(track => {
286
+ track.enabled = !track.enabled;
287
+ });
288
+ setIsVideoOff(prev => !prev);
289
+ }
290
+ };
291
+
292
+ const FeatureCard = ({ icon: Icon, title, desc }: { icon: any, title: string, desc: string }) => (
293
+ <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">
294
+ <div className="p-3 rounded-xl bg-indigo-500/10 text-indigo-400 group-hover:scale-110 transition-transform">
295
+ <Icon size={24} />
296
+ </div>
297
+ <h3 className="text-sm font-bold uppercase tracking-widest text-zinc-200">{title}</h3>
298
+ <p className="text-[10px] text-zinc-500 leading-relaxed">{desc}</p>
299
+ </div>
300
+ );
301
+
302
+ const FAQItem = ({ question }: { question: string }) => {
303
+ const [isOpen, setIsOpen] = useState(false);
304
+ return (
305
+ <div className="border-b border-zinc-800/50">
306
+ <button
307
+ onClick={() => setIsOpen(!isOpen)}
308
+ className="w-full py-5 flex items-center justify-between text-left group"
309
+ >
310
+ <span className="text-sm font-medium text-zinc-300 group-hover:text-indigo-400 transition-colors">{question}</span>
311
+ {isOpen ? <ChevronUp size={18} className="text-indigo-500" /> : <ChevronDown size={18} className="text-zinc-600" />}
312
+ </button>
313
+ <AnimatePresence>
314
+ {isOpen && (
315
+ <motion.div
316
+ initial={{ height: 0, opacity: 0 }}
317
+ animate={{ height: 'auto', opacity: 1 }}
318
+ exit={{ height: 0, opacity: 0 }}
319
+ className="overflow-hidden"
320
+ >
321
+ <div className="pb-5 text-xs text-zinc-500 leading-relaxed">
322
+ MingleHub uses advanced matching algorithms to connect you with people who share your interests while maintaining a safe and moderated environment for everyone.
323
+ </div>
324
+ </motion.div>
325
+ )}
326
+ </AnimatePresence>
327
+ </div>
328
+ );
329
+ };
330
+
331
+ return (
332
+ <div className="h-screen font-sans overflow-hidden bg-[#050505] text-white">
333
+ <AnimatePresence mode="wait">
334
+ {/* PAGE 1: LANDING */}
335
+ {currentPage === 'landing' && (
336
+ <motion.div
337
+ key="landing"
338
+ initial={{ opacity: 0 }}
339
+ animate={{ opacity: 1 }}
340
+ exit={{ opacity: 0 }}
341
+ className="h-full flex flex-col"
342
+ >
343
+ <header className="h-20 border-b border-zinc-900 flex items-center justify-between px-8">
344
+ <div className="flex items-center gap-3">
345
+ <div className="w-10 h-10 bg-indigo-600 rounded-xl flex items-center justify-center shadow-lg shadow-indigo-600/20">
346
+ <Sparkles size={24} className="text-white" />
347
+ </div>
348
+ <span className="text-2xl font-black tracking-tighter uppercase italic">MingleHub</span>
349
+ </div>
350
+ <div className="hidden md:flex items-center gap-8 text-[10px] font-bold uppercase tracking-widest text-zinc-500">
351
+ <button
352
+ onClick={() => window.open(window.location.href, '_blank')}
353
+ 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"
354
+ >
355
+ <Globe size={14} />
356
+ Open in New Tab
357
+ </button>
358
+ <a href="#" className="hover:text-indigo-400 transition-colors">Safety</a>
359
+ <a href="#" className="hover:text-indigo-400 transition-colors">Community</a>
360
+ <a href="#" className="hover:text-indigo-400 transition-colors">Support</a>
361
+ </div>
362
+ </header>
363
+
364
+ <main className="flex-1 overflow-y-auto">
365
+ <div className="max-w-4xl mx-auto py-20 px-8 space-y-24">
366
+ <div className="text-center space-y-8">
367
+ <motion.div
368
+ initial={{ y: 20, opacity: 0 }}
369
+ animate={{ y: 0, opacity: 1 }}
370
+ transition={{ delay: 0.2 }}
371
+ >
372
+ <h1 className="text-5xl md:text-7xl font-black tracking-tight leading-none">
373
+ Connect with the <br />
374
+ <span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-400 to-purple-400">World Instantly.</span>
375
+ </h1>
376
+ </motion.div>
377
+
378
+ <p className="max-w-xl mx-auto text-zinc-400 text-lg leading-relaxed">
379
+ MingleHub is a premium anonymous video and text chat platform. Meet interesting people, make new friends, and explore the world from your screen.
380
+ </p>
381
+
382
+ <div className="pt-8 flex flex-col items-center gap-6">
383
+ <button
384
+ onClick={() => setCurrentPage('setup')}
385
+ 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"
386
+ >
387
+ <span className="relative z-10 flex items-center gap-2">
388
+ START MINGLING NOW
389
+ <Zap size={18} className="group-hover:animate-pulse" />
390
+ </span>
391
+ </button>
392
+ <div className="flex items-center gap-2 text-[10px] font-bold text-zinc-600 uppercase tracking-widest">
393
+ <Users size={14} className="text-indigo-500" />
394
+ {onlineCount.toLocaleString()} PEOPLE CURRENTLY ONLINE
395
+ </div>
396
+ </div>
397
+ </div>
398
+
399
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
400
+ <FeatureCard icon={Lock} title="Private & Secure" desc="End-to-end encrypted signaling and anonymous profiles keep your identity safe." />
401
+ <FeatureCard icon={Globe} title="Global Reach" desc="Connect with people from over 190 countries instantly with low latency." />
402
+ <FeatureCard icon={Zap} title="Zero Lag" desc="Optimized WebRTC infrastructure ensures smooth video and instant messaging." />
403
+ </div>
404
+
405
+ <div className="space-y-8 pt-12">
406
+ <h2 className="text-3xl font-black text-center">Common Questions</h2>
407
+ <div className="max-w-2xl mx-auto space-y-2">
408
+ <FAQItem question="How does MingleHub ensure user safety?" />
409
+ <FAQItem question="Do I need to create an account to chat?" />
410
+ <FAQItem question="Is MingleHub free to use?" />
411
+ <FAQItem question="Can I use MingleHub on my mobile device?" />
412
+ </div>
413
+ </div>
414
+
415
+ <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">
416
+ <div className="flex items-center gap-2">
417
+ <Sparkles size={14} className="text-indigo-500" />
418
+ <span>© 2026 MingleHub Global</span>
419
+ </div>
420
+ <div className="flex gap-8">
421
+ <a href="#" className="hover:text-indigo-400">Terms</a>
422
+ <a href="#" className="hover:text-indigo-400">Privacy</a>
423
+ <a href="#" className="hover:text-indigo-400">Guidelines</a>
424
+ <a href="#" className="hover:text-indigo-400">Contact</a>
425
+ </div>
426
+ </footer>
427
+ </div>
428
+ </main>
429
+ </motion.div>
430
+ )}
431
+
432
+ {/* PAGE 2: SETUP */}
433
+ {currentPage === 'setup' && (
434
+ <motion.div
435
+ key="setup"
436
+ initial={{ opacity: 0 }}
437
+ animate={{ opacity: 1 }}
438
+ exit={{ opacity: 0 }}
439
+ className="h-full flex flex-col"
440
+ >
441
+ <header className="h-20 border-b border-zinc-900 flex items-center justify-between px-8">
442
+ <div className="flex items-center gap-3">
443
+ <div className="w-10 h-10 bg-indigo-600 rounded-xl flex items-center justify-center">
444
+ <Sparkles size={24} className="text-white" />
445
+ </div>
446
+ <span className="text-2xl font-black tracking-tighter uppercase italic">MingleHub</span>
447
+ </div>
448
+ <div className="flex items-center gap-6">
449
+ <button
450
+ onClick={() => window.open(window.location.href, '_blank')}
451
+ 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"
452
+ >
453
+ <Globe size={14} />
454
+ Open in New Tab
455
+ </button>
456
+ <button
457
+ onClick={() => setCurrentPage('landing')}
458
+ className="text-[10px] font-bold text-zinc-500 hover:text-white uppercase tracking-widest transition-colors"
459
+ >
460
+ Back to Home
461
+ </button>
462
+ </div>
463
+ </header>
464
+
465
+ <main className="flex-1 overflow-y-auto">
466
+ <div className="max-w-xl mx-auto py-20 px-8 space-y-12">
467
+ <div className="text-center space-y-4">
468
+ <h1 className="text-4xl font-black tracking-tight">Configure Chat</h1>
469
+ <p className="text-zinc-500 text-sm">Choose how you want to connect today.</p>
470
+ </div>
471
+
472
+ <div className="space-y-10">
473
+ <div className="grid grid-cols-2 gap-4">
474
+ <button
475
+ onClick={() => startChatting('text')}
476
+ 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'}`}
477
+ >
478
+ <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'}`}>
479
+ <MessageSquare size={32} />
480
+ </div>
481
+ <span className="font-bold uppercase tracking-widest text-sm">Text Chat</span>
482
+ </button>
483
+ <button
484
+ onClick={() => startChatting('video')}
485
+ 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'}`}
486
+ >
487
+ <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'}`}>
488
+ <Video size={32} />
489
+ </div>
490
+ <span className="font-bold uppercase tracking-widest text-sm">Video Chat</span>
491
+ </button>
492
+ </div>
493
+
494
+ <div className="space-y-4">
495
+ <div className="flex items-center justify-between">
496
+ <label className="text-xs font-bold uppercase tracking-widest text-zinc-500">Your Interests</label>
497
+ <span className="text-[10px] text-zinc-600 font-bold uppercase tracking-widest">Optional</span>
498
+ </div>
499
+ <input
500
+ type="text"
501
+ value={interests}
502
+ onChange={(e) => setInterests(e.target.value)}
503
+ placeholder="e.g. Music, Travel, Coding..."
504
+ 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"
505
+ />
506
+ </div>
507
+
508
+ <div className="p-6 bg-indigo-600/5 border border-indigo-500/10 rounded-2xl flex items-start gap-4">
509
+ <Shield size={20} className="text-indigo-500 shrink-0 mt-1" />
510
+ <div className="space-y-1">
511
+ <h4 className="text-xs font-bold uppercase tracking-widest text-indigo-400">Safety First</h4>
512
+ <p className="text-[10px] text-zinc-500 leading-relaxed">
513
+ MingleHub is a moderated community. Please be respectful to others. Harassment or inappropriate behavior will result in a permanent ban.
514
+ </p>
515
+ </div>
516
+ </div>
517
+
518
+ <button
519
+ onClick={() => startChatting(chatType)}
520
+ 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"
521
+ >
522
+ ENTER CHAT ROOM
523
+ </button>
524
+ </div>
525
+ </div>
526
+ </main>
527
+ </motion.div>
528
+ )}
529
+
530
+ {/* PAGE 3: CHAT */}
531
+ {currentPage === 'chat' && (
532
+ <motion.div
533
+ key="chat"
534
+ initial={{ opacity: 0 }}
535
+ animate={{ opacity: 1 }}
536
+ exit={{ opacity: 0 }}
537
+ className="h-full flex flex-col bg-[#050505]"
538
+ >
539
+ <header className="h-16 border-b border-zinc-900 flex items-center justify-between px-6">
540
+ <div className="flex items-center gap-3">
541
+ <div className="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center">
542
+ <Sparkles size={18} className="text-white" />
543
+ </div>
544
+ <span className="font-black tracking-tighter text-sm uppercase italic">MingleHub</span>
545
+ </div>
546
+ <div className="flex items-center gap-6">
547
+ <div className="flex items-center gap-2 px-3 py-1 bg-green-500/10 border border-green-500/20 rounded-full">
548
+ <div className="w-1.5 h-1.5 bg-green-500 rounded-full animate-pulse" />
549
+ <span className="text-[9px] font-bold text-green-500 uppercase tracking-widest">Live</span>
550
+ </div>
551
+ {chatType === 'video' && (
552
+ <div className="flex items-center gap-2">
553
+ <button
554
+ onClick={toggleMute}
555
+ className={`p-2 rounded-lg transition-colors ${isMuted ? 'bg-red-500/20 text-red-400' : 'text-zinc-500 hover:text-white'}`}
556
+ >
557
+ {isMuted ? <MicOff size={18} /> : <Mic size={18} />}
558
+ </button>
559
+ <button
560
+ onClick={toggleVideo}
561
+ className={`p-2 rounded-lg transition-colors ${isVideoOff ? 'bg-red-500/20 text-red-400' : 'text-zinc-500 hover:text-white'}`}
562
+ >
563
+ {isVideoOff ? <VideoOff size={18} /> : <Video size={18} />}
564
+ </button>
565
+ </div>
566
+ )}
567
+ <button
568
+ onClick={handleStop}
569
+ className="p-2 text-zinc-600 hover:text-white transition-colors"
570
+ >
571
+ <StopCircle size={22} />
572
+ </button>
573
+ </div>
574
+ </header>
575
+
576
+ <main className="flex-1 flex flex-col md:flex-row overflow-hidden">
577
+ <div className="flex-1 flex flex-col bg-black relative">
578
+ {chatType === 'video' ? (
579
+ <div className="flex-1 grid grid-rows-2 md:grid-rows-1 md:grid-cols-2 gap-1 p-1">
580
+ <div className="relative bg-zinc-900 rounded-2xl overflow-hidden border border-zinc-800/50">
581
+ <video
582
+ ref={remoteVideoRef}
583
+ autoPlay
584
+ playsInline
585
+ className="w-full h-full object-cover"
586
+ />
587
+ <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>
588
+ {!isConnected && !isSearching && (
589
+ <div className="absolute inset-0 flex items-center justify-center flex-col gap-4">
590
+ <User size={48} className="text-zinc-800" />
591
+ <p className="text-zinc-600 text-[10px] font-bold uppercase tracking-widest">Waiting for video...</p>
592
+ </div>
593
+ )}
594
+ </div>
595
+ <div className="relative bg-zinc-900 rounded-2xl overflow-hidden border border-zinc-800/50">
596
+ <video
597
+ ref={localVideoRef}
598
+ autoPlay
599
+ muted
600
+ playsInline
601
+ className="w-full h-full object-cover scale-x-[-1]"
602
+ />
603
+ <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>
604
+ {isVideoOff && (
605
+ <div className="absolute inset-0 bg-zinc-950 flex items-center justify-center">
606
+ <VideoOff size={32} className="text-zinc-800" />
607
+ </div>
608
+ )}
609
+ </div>
610
+ </div>
611
+ ) : (
612
+ <div className="flex-1 flex items-center justify-center flex-col gap-8 p-12 text-center">
613
+ <div className="w-28 h-28 bg-indigo-600/10 rounded-full flex items-center justify-center border border-indigo-500/20">
614
+ <MessageSquare size={48} className="text-indigo-500" />
615
+ </div>
616
+ <div className="space-y-3">
617
+ <h2 className="text-3xl font-black tracking-tight">Secure Text Chat</h2>
618
+ <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>
619
+ </div>
620
+ </div>
621
+ )}
622
+
623
+ {isSearching && (
624
+ <div className="absolute inset-0 z-50 bg-black/90 backdrop-blur-md flex flex-col items-center justify-center gap-8">
625
+ <div className="relative">
626
+ <div className="w-20 h-20 border-4 border-indigo-500/20 border-t-indigo-500 rounded-full animate-spin" />
627
+ <div className="absolute inset-0 flex items-center justify-center">
628
+ <Globe size={28} className="text-zinc-700" />
629
+ </div>
630
+ </div>
631
+ <div className="text-center space-y-3">
632
+ <h3 className="text-2xl font-black tracking-tight">Searching...</h3>
633
+ <p className="text-zinc-500 text-[10px] font-bold uppercase tracking-[0.2em]">Matching with a global stranger</p>
634
+ </div>
635
+ </div>
636
+ )}
637
+ </div>
638
+
639
+ <div className="w-full md:w-[450px] flex flex-col border-l border-zinc-900 bg-[#080808]">
640
+ <div className="flex-1 overflow-y-auto p-6 space-y-6 scrollbar-hide">
641
+ <AnimatePresence initial={false}>
642
+ {messages.map((msg, i) => (
643
+ <motion.div
644
+ key={i}
645
+ initial={{ opacity: 0, y: 10 }}
646
+ animate={{ opacity: 1, y: 0 }}
647
+ className={`flex flex-col ${msg.sender === 'me' ? 'items-end' : msg.sender === 'system' ? 'items-center' : 'items-start'}`}
648
+ >
649
+ {msg.sender === 'system' ? (
650
+ <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]">
651
+ {msg.text}
652
+ </div>
653
+ ) : (
654
+ <>
655
+ <span className="text-[8px] font-black uppercase tracking-widest text-zinc-600 mb-1.5 px-1">
656
+ {msg.sender === 'me' ? 'You' : 'Stranger'}
657
+ </span>
658
+ <div className={`px-5 py-3 rounded-2xl text-[13px] max-w-[85%] leading-relaxed ${
659
+ msg.sender === 'me'
660
+ ? 'bg-indigo-600 text-white rounded-tr-none shadow-lg shadow-indigo-600/10'
661
+ : 'bg-zinc-900 text-zinc-200 rounded-tl-none border border-zinc-800/50'
662
+ }`}>
663
+ {msg.text}
664
+ </div>
665
+ </>
666
+ )}
667
+ </motion.div>
668
+ ))}
669
+ </AnimatePresence>
670
+ <div ref={chatEndRef} />
671
+ </div>
672
+
673
+ <div className="p-6 border-t border-zinc-900 space-y-6 bg-[#080808]">
674
+ <div className="flex gap-3">
675
+ <button
676
+ onClick={handleStop}
677
+ 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"
678
+ >
679
+ Stop
680
+ </button>
681
+ <button
682
+ onClick={handleNext}
683
+ 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"
684
+ >
685
+ <SkipForward size={18} />
686
+ Next Partner
687
+ </button>
688
+ </div>
689
+
690
+ <form onSubmit={sendMessage} className="relative">
691
+ <input
692
+ type="text"
693
+ value={inputText}
694
+ onChange={(e) => setInputText(e.target.value)}
695
+ placeholder="Type something..."
696
+ 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"
697
+ />
698
+ <button
699
+ type="submit"
700
+ disabled={!inputText.trim()}
701
+ 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"
702
+ >
703
+ <Send size={22} />
704
+ </button>
705
+ </form>
706
+ </div>
707
+ </div>
708
+ </main>
709
+ </motion.div>
710
+ )}
711
+ </AnimatePresence>
712
+
713
+ {/* Permission Modal */}
714
+ <AnimatePresence>
715
+ {showPermissionModal && (
716
+ <div className="fixed inset-0 z-[100] flex items-center justify-center p-6">
717
+ <motion.div
718
+ initial={{ opacity: 0 }}
719
+ animate={{ opacity: 1 }}
720
+ exit={{ opacity: 0 }}
721
+ className="absolute inset-0 bg-black/95 backdrop-blur-2xl"
722
+ />
723
+ <motion.div
724
+ initial={{ opacity: 0, scale: 0.9, y: 20 }}
725
+ animate={{ opacity: 1, scale: 1, y: 0 }}
726
+ exit={{ opacity: 0, scale: 0.9, y: 20 }}
727
+ 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"
728
+ >
729
+ <div className="w-28 h-28 bg-indigo-600/10 rounded-full flex items-center justify-center mx-auto border border-indigo-500/20">
730
+ <Camera size={56} className="text-indigo-500" />
731
+ </div>
732
+ <div className="space-y-4">
733
+ <h2 className="text-4xl font-black tracking-tight text-white leading-none">Enable <br /> Camera</h2>
734
+ <p className="text-zinc-500 text-sm leading-relaxed">
735
+ To connect via video, MingleHub requires access to your camera and microphone.
736
+ </p>
737
+ </div>
738
+ <div className="flex flex-col gap-4">
739
+ <button
740
+ onClick={handlePermissionConfirm}
741
+ 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"
742
+ >
743
+ ALLOW ACCESS
744
+ </button>
745
+ <button
746
+ onClick={() => setShowPermissionModal(false)}
747
+ 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]"
748
+ >
749
+ MAYBE LATER
750
+ </button>
751
+ </div>
752
+ </motion.div>
753
+ </div>
754
+ )}
755
+ </AnimatePresence>
756
+ </div>
757
+ );
758
+ }