├── README.md ├── public ├── Online.png ├── People.png ├── Person.png ├── Talkto.png ├── apple.png ├── login.png ├── logo.png ├── signIn.png ├── Rectangle 820.png ├── Rectangle 824.png ├── Rectangle 826.png ├── icons │ ├── clicks.png │ ├── call-stop.png │ ├── sound-box.png │ ├── zoom-chats.png │ └── double-danger.png ├── router.json └── vite.svg ├── src ├── App.css ├── App.jsx ├── layouts │ ├── Main.jsx │ └── Sidebar.jsx ├── index.css ├── main.jsx ├── router │ └── index.jsx ├── pages │ ├── Homepage.jsx │ ├── VideoChat.jsx │ ├── StartChatPage.jsx │ ├── SignIn.jsx │ ├── SignUp.jsx │ ├── CallingPage.jsx │ ├── MainPage.jsx │ └── ChatPage.jsx └── assets │ └── react.svg ├── vite.config.js ├── .gitignore ├── index.html ├── package.json └── eslint.config.js /README.md: -------------------------------------------------------------------------------- 1 | # ZoomerangZ-desktop-Frontend -------------------------------------------------------------------------------- /public/Online.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/Online.png -------------------------------------------------------------------------------- /public/People.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/People.png -------------------------------------------------------------------------------- /public/Person.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/Person.png -------------------------------------------------------------------------------- /public/Talkto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/Talkto.png -------------------------------------------------------------------------------- /public/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/apple.png -------------------------------------------------------------------------------- /public/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/login.png -------------------------------------------------------------------------------- /public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/logo.png -------------------------------------------------------------------------------- /public/signIn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/signIn.png -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #161515; /* change to your desired background */ 3 | } 4 | -------------------------------------------------------------------------------- /public/Rectangle 820.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/Rectangle 820.png -------------------------------------------------------------------------------- /public/Rectangle 824.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/Rectangle 824.png -------------------------------------------------------------------------------- /public/Rectangle 826.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/Rectangle 826.png -------------------------------------------------------------------------------- /public/icons/clicks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/icons/clicks.png -------------------------------------------------------------------------------- /public/icons/call-stop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/icons/call-stop.png -------------------------------------------------------------------------------- /public/icons/sound-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/icons/sound-box.png -------------------------------------------------------------------------------- /public/icons/zoom-chats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/icons/zoom-chats.png -------------------------------------------------------------------------------- /public/icons/double-danger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vector-6/React-Chatting/HEAD/public/icons/double-danger.png -------------------------------------------------------------------------------- /public/router.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "path": "/", 4 | "component": "HomePage" 5 | }, 6 | { 7 | "path": "/homepage", 8 | "component": "HomePage" 9 | } 10 | ] -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import './App.css'; 2 | import Router from './router'; 3 | import Main from './layouts/Main'; 4 | 5 | function App() { 6 | return ; 7 | } 8 | 9 | export default App; 10 | -------------------------------------------------------------------------------- /src/layouts/Main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Main = ({ children }) => { 4 | return
{children}
; 5 | }; 6 | 7 | export default Main; 8 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | ::-webkit-scrollbar{ 4 | width: 20px; 5 | 6 | } 7 | ::-webkit-slider-thumb{ 8 | color: #349393; 9 | } 10 | ::-webkit-scrollbar-track{ 11 | background: 349393; 12 | } -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.jsx' 5 | 6 | createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | import tailwindcss from '@tailwindcss/vite' 4 | 5 | // https://vite.dev/config/ 6 | export default defineConfig({ 7 | plugins: [ 8 | tailwindcss(), 9 | react(), 10 | ], 11 | }) 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zoomerang-frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@tailwindcss/vite": "^4.0.17", 14 | "lucide-react": "^0.484.0", 15 | "react": "^19.0.0", 16 | "react-dom": "^19.0.0", 17 | "react-router-dom": "^7.4.0", 18 | "swiper": "^11.2.6", 19 | "tailwindcss": "^4.0.17" 20 | }, 21 | "devDependencies": { 22 | "@eslint/js": "^9.21.0", 23 | "@types/react": "^19.0.10", 24 | "@types/react-dom": "^19.0.4", 25 | "@vitejs/plugin-react-swc": "^3.8.0", 26 | "eslint": "^9.21.0", 27 | "eslint-plugin-react-hooks": "^5.1.0", 28 | "eslint-plugin-react-refresh": "^0.4.19", 29 | "globals": "^15.15.0", 30 | "vite": "^6.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | 6 | export default [ 7 | { ignores: ['dist'] }, 8 | { 9 | files: ['**/*.{js,jsx}'], 10 | languageOptions: { 11 | ecmaVersion: 2020, 12 | globals: globals.browser, 13 | parserOptions: { 14 | ecmaVersion: 'latest', 15 | ecmaFeatures: { jsx: true }, 16 | sourceType: 'module', 17 | }, 18 | }, 19 | plugins: { 20 | 'react-hooks': reactHooks, 21 | 'react-refresh': reactRefresh, 22 | }, 23 | rules: { 24 | ...js.configs.recommended.rules, 25 | ...reactHooks.configs.recommended.rules, 26 | 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], 27 | 'react-refresh/only-export-components': [ 28 | 'warn', 29 | { allowConstantExport: true }, 30 | ], 31 | }, 32 | }, 33 | ] 34 | -------------------------------------------------------------------------------- /src/router/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; 3 | import Homepage from '../pages/Homepage'; 4 | import SignIn from '../pages/SignIn'; 5 | import SignUp from '../pages/SignUp'; 6 | import MainPage from '../pages/MainPage'; 7 | import StartChatPage from '../pages/StartChatPage'; 8 | import ChatPage from '../pages/ChatPage'; 9 | import CallingPage from '../pages/CallingPage'; 10 | import VideoChat from '../pages/VideoChat'; 11 | import Sidebar from '../layouts/Sidebar'; 12 | import Main from '../layouts/Main'; 13 | 14 | const AppRouter = () => { 15 | return ( 16 | 17 |
18 | 19 | 20 | } /> 21 | } /> 22 | } /> 23 | } /> 24 | } /> 25 | } /> 26 | } /> 27 | } /> 28 | 29 |
30 |
31 | ); 32 | }; 33 | 34 | export default AppRouter; 35 | -------------------------------------------------------------------------------- /src/pages/Homepage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import logo from '../../public/logo.png'; 3 | import { Link } from 'react-router-dom'; 4 | 5 | const Homepage = () => { 6 | return ( 7 |
8 | {/* Header */} 9 |
10 |
11 | 12 | 13 |
14 |
15 |
16 | 17 | {/* Main Content */} 18 |
19 |

Enjoy The World In Briefs

20 |

21 | Create viral videos effortlessly with Zoomerang! Trendy effects,
seamless transitions, and powerful 22 | tools all in one app! 23 |

24 | 25 | 26 | 32 | 33 |
34 |
35 | ); 36 | }; 37 | 38 | export default Homepage; 39 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/pages/VideoChat.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function MainPage() { 4 | const funcButtons = [ 5 | { 6 | id: 'dopple', 7 | label: 'Dopple Danger', 8 | img: 'double-danger.png', 9 | }, 10 | { 11 | id: 'clicks', 12 | label: 'Clicks', 13 | img: 'clicks.png', 14 | }, 15 | { 16 | id: 'zoom-chats', 17 | label: 'Zoom Chats', 18 | img: 'zoom-chats.png', 19 | }, 20 | { 21 | id: 'sound-box', 22 | label: 'SoundBox', 23 | img: 'sound-box.png', 24 | }, 25 | { 26 | id: 'call-stop', 27 | label: '', 28 | img: 'call-stop.png', 29 | }, 30 | ]; 31 | return ( 32 |
33 |
34 |
35 | {/* video group */} 36 |
37 |
38 |
39 |
40 |
41 |
42 | {/* button group */} 43 |
44 | {funcButtons.map((item) => { 45 | return ( 46 | 54 | ); 55 | })} 56 |
57 |
58 | {/* zoom chat section */} 59 |
60 |
61 | ); 62 | } 63 | -------------------------------------------------------------------------------- /src/pages/StartChatPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import logo from '../../public/logo.png'; 3 | import talkTo from '../../public/Talkto.png'; 4 | import Online from '../../public/Online.png'; 5 | import People from '../../public/People.png'; 6 | 7 | export default function StartChatPage() { 8 | const [activeButton, setActiveButton] = useState('Trending'); 9 | 10 | const handleClick = (buttonName) => { 11 | setActiveButton(buttonName); 12 | }; 13 | const [activeTab, setActiveTab] = useState('home'); 14 | 15 | // Real profile images 16 | const profileImages = [ 17 | 'https://i.pravatar.cc/150?img=4', 18 | 'https://i.pravatar.cc/150?img=5', 19 | 'https://i.pravatar.cc/150?img=6', 20 | 'https://i.pravatar.cc/150?img=1', 21 | 'https://i.pravatar.cc/150?img=7', 22 | 'https://i.pravatar.cc/150?img=8', 23 | 'https://i.pravatar.cc/150?img=9', 24 | 'https://i.pravatar.cc/150?img=2', 25 | 'https://i.pravatar.cc/150?img=3', 26 | 'https://i.pravatar.cc/150?img=5', 27 | 'https://i.pravatar.cc/150?img=4', 28 | 'https://i.pravatar.cc/150?img=7', 29 | 30 | 'https://i.pravatar.cc/150?img=3', 31 | 'https://i.pravatar.cc/150?img=4', 32 | 'https://i.pravatar.cc/150?img=5', 33 | 'https://i.pravatar.cc/150?img=6', 34 | 'https://i.pravatar.cc/150?img=1', 35 | 'https://i.pravatar.cc/150?img=7', 36 | 'https://i.pravatar.cc/150?img=8', 37 | 'https://i.pravatar.cc/150?img=9', 38 | 'https://i.pravatar.cc/150?img=2', 39 | 'https://i.pravatar.cc/150?img=3', 40 | 'https://i.pravatar.cc/150?img=5', 41 | 'https://i.pravatar.cc/150?img=4', 42 | 'https://i.pravatar.cc/150?img=7', 43 | 44 | 'https://i.pravatar.cc/150?img=3', 45 | 46 | 'https://i.pravatar.cc/150?img=3', 47 | 'https://i.pravatar.cc/150?img=4', 48 | 'https://i.pravatar.cc/150?img=5', 49 | 'https://i.pravatar.cc/150?img=6', 50 | 'https://i.pravatar.cc/150?img=1', 51 | 'https://i.pravatar.cc/150?img=7', 52 | 'https://i.pravatar.cc/150?img=8', 53 | 'https://i.pravatar.cc/150?img=9', 54 | 'https://i.pravatar.cc/150?img=2', 55 | 'https://i.pravatar.cc/150?img=3', 56 | 'https://i.pravatar.cc/150?img=5', 57 | 'https://i.pravatar.cc/150?img=4', 58 | 'https://i.pravatar.cc/150?img=7', 59 | 60 | 'https://i.pravatar.cc/150?img=3', 61 | 'https://i.pravatar.cc/150?img=3', 62 | ]; 63 | return ( 64 |
65 | {/* Main content */} 66 |
67 | {/* Top avatar scroll */} 68 | 69 | {/* Footer */} 70 |
71 | 74 | 75 |
76 | 84 | 92 |
93 |
94 |
95 |
96 | ); 97 | } 98 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/layouts/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Home, Users, Mail, Star, LogIn, Menu } from 'lucide-react'; 3 | import logo from '../../public/logo.png'; 4 | import talkTo from '../../public/Talkto.png'; 5 | import Avatar from '../../public/login.png'; 6 | import Person from '../../public/Person.png'; 7 | import { useLocation } from 'react-router-dom'; 8 | 9 | export default function Sidebar({ activeTab, setActiveTab }) { 10 | const [isOpen, setIsOpen] = useState(false); 11 | const location = useLocation(); 12 | 13 | const navItems = [ 14 | { id: 'home', label: 'Home', Icon: Home }, 15 | { id: 'news', label: 'News', Icon: Users }, 16 | { id: 'contact', label: 'Contact', Icon: Mail }, 17 | { id: 'premium', label: 'Premium', Icon: Star }, 18 | ]; 19 | 20 | // Hide sidebar on auth pages 21 | const isAuthPage = ['/', '/signIn', '/signUp'].includes(location.pathname); 22 | if (isAuthPage) return null; 23 | 24 | return ( 25 | <> 26 | {/* Toggle button for mobile */} 27 | 33 | 34 | {/* Sidebar */} 35 |
40 | {/* Logo */} 41 |
42 | Logo 43 |
44 | 45 | {/* Navigation */} 46 | 67 | 68 | {/* Centered Person Image */} 69 |
70 | Person 71 |
72 | 73 | {/* Bottom section */} 74 |
75 | {/* Login button */} 76 | 80 | 81 | {/* Talk to + Avatar */} 82 |
83 | 86 | 89 |
90 |
91 |
92 | 93 | ); 94 | } 95 | -------------------------------------------------------------------------------- /src/pages/SignIn.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Eye, EyeOff } from 'lucide-react'; 3 | import logo from '../../public/logo.png'; 4 | import Sign from '../../public/signIn.png'; 5 | import appleLogo from '../../public/apple.png'; 6 | 7 | const SignIn = () => { 8 | const [showPassword, setShowPassword] = useState(false); 9 | const [username, setUsername] = useState(''); 10 | const [password, setPassword] = useState(''); 11 | 12 | const handleSignIn = (e) => { 13 | e.preventDefault(); 14 | // Add sign-in logic here 15 | console.log('Sign in attempt', { username, password }); 16 | }; 17 | 18 | return ( 19 |
20 |
21 |
22 | 23 | 24 |
25 |
26 |
27 | 28 |
29 |
30 |

Sign in

31 |

32 | Welcome to Zoomerang-Z New User{' '} 33 | 34 | Join Us 35 | 36 |

37 | 38 |
39 | setUsername(e.target.value)} 44 | className="w-full p-3 bg-[#758084] text-white rounded-lg border-none focus:outline-none focus:ring-2 focus:ring-teal-500" 45 | /> 46 | 47 |
48 | setPassword(e.target.value)} 53 | className="w-full p-3 bg-[#758084] text-white rounded-lg border-none focus:outline-none focus:ring-2 focus:ring-teal-500" 54 | /> 55 | 62 |
63 | 64 | 70 |
71 | 72 |
73 |
74 | Or 75 |
76 |
77 | 78 |
79 | 99 | 102 |
103 | 104 |
105 | Not a member?{' '} 106 | 107 | Register Now 108 | 109 |
110 |
111 | 112 |
113 | 114 |
115 |
116 |
117 | ); 118 | }; 119 | 120 | export default SignIn; 121 | -------------------------------------------------------------------------------- /src/pages/SignUp.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Eye, EyeOff } from 'lucide-react'; 3 | import logo from '../../public/logo.png'; 4 | import Sign from '../../public/signIn.png'; 5 | import appleLogo from '../../public/apple.png'; 6 | 7 | const SignUp = () => { 8 | const [showPassword, setShowPassword] = useState(false); 9 | const [username, setUsername] = useState(''); 10 | const [password, setPassword] = useState(''); 11 | 12 | const handleSignIn = (e) => { 13 | e.preventDefault(); 14 | // Add sign-in logic here 15 | console.log('Sign in attempt', { username, password }); 16 | }; 17 | 18 | return ( 19 |
20 |
21 |
22 | 23 | 24 |
25 |
26 |
27 | 28 |
29 |
30 |

Sign Up

31 |

32 | Welcome to Zoomerang-Z New User{' '} 33 | 34 | Join Us 35 | 36 |

37 | 38 |
39 | setUsername(e.target.value)} 44 | className="w-full p-3 bg-[#758084] text-white rounded-lg border-none focus:outline-none focus:ring-2 focus:ring-teal-500" 45 | /> 46 | 47 |
48 | setPassword(e.target.value)} 53 | className="w-full p-3 bg-[#758084] text-white rounded-lg border-none focus:outline-none focus:ring-2 focus:ring-teal-500" 54 | /> 55 | 62 |
63 |
64 | setPassword(e.target.value)} 69 | className="w-full p-3 bg-[#758084] text-white rounded-lg border-none focus:outline-none focus:ring-2 focus:ring-teal-500" 70 | /> 71 | 78 |
79 | 80 | 87 |
88 | 89 |
90 |
91 | Or 92 |
93 |
94 | 95 |
96 | 116 | 119 |
120 | 121 |
122 | Have an Account?{' '} 123 | 124 | Sign In 125 | 126 |
127 |
128 | 129 |
130 | 131 |
132 |
133 |
134 | ); 135 | }; 136 | 137 | export default SignUp; 138 | -------------------------------------------------------------------------------- /src/pages/CallingPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import logo from '../../public/logo.png'; 3 | import { Link, Mic, MicOff, Video, VideoOff, Phone, MessageSquare, Volume2, VolumeX } from 'lucide-react'; 4 | 5 | export default function CallingPage() { 6 | const [isPanelOpen, setIsPanelOpen] = useState(false); 7 | const [isCameraOn, setIsCameraOn] = useState(true); 8 | const [isMicOn, setIsMicOn] = useState(true); 9 | const [isSoundOn, setIsSoundOn] = useState(true); 10 | const [remoteStream, setRemoteStream] = useState(null); 11 | const [localStream, setLocalStream] = useState(null); 12 | const [messages, setMessages] = useState([]); 13 | const [newMessage, setNewMessage] = useState(''); 14 | const [meetingLink, setMeetingLink] = useState(''); 15 | const localVideoRef = useRef(null); 16 | const remoteVideoRef = useRef(null); 17 | 18 | useEffect(() => { 19 | const initStreams = async () => { 20 | try { 21 | const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); 22 | setLocalStream(stream); 23 | if (localVideoRef.current) { 24 | localVideoRef.current.srcObject = stream; 25 | } 26 | } catch (error) { 27 | console.error('Error accessing media devices:', error); 28 | } 29 | }; 30 | 31 | initStreams(); 32 | }, []); 33 | 34 | const handleEndCall = () => { 35 | if (localStream) { 36 | localStream.getTracks().forEach((track) => track.stop()); 37 | } 38 | // Add call end logic here 39 | }; 40 | 41 | const handleSendMessage = (e) => { 42 | e.preventDefault(); 43 | if (newMessage.trim()) { 44 | const message = { 45 | text: newMessage, 46 | timestamp: new Date().toISOString(), 47 | sender: 'me', 48 | }; 49 | setMessages((prev) => [...prev, message]); 50 | setNewMessage(''); 51 | } 52 | }; 53 | 54 | const handleJoinMeeting = (e) => { 55 | e.preventDefault(); 56 | if (meetingLink.trim()) { 57 | // Add your meeting join logic here 58 | console.log('Joining meeting with link:', meetingLink); 59 | } 60 | }; 61 | 62 | return ( 63 |
64 | {/* Main content */} 65 |
66 | {/* Header with meeting link input */} 67 |
68 |
69 |
70 | 71 | setMeetingLink(e.target.value)} 75 | placeholder="Enter meeting link or code" 76 | className="flex-1 bg-transparent text-sm focus:outline-none" 77 | /> 78 |
79 | 85 |
86 |
87 | 88 | {/* Main video area */} 89 |
90 | {remoteStream ? ( 91 |
106 | 107 | {/* Bottom controls */} 108 |
109 | 115 | 121 | 127 | 130 |
131 |
132 | 133 |
134 | 137 |
138 | 139 | {/* Right side panel */} 140 |
143 | {/* Local video */} 144 |
145 |
146 | {isCameraOn && localStream ? ( 147 |
160 |
161 | 162 | {/* Chat area */} 163 |
164 |
165 |

Chat

166 |
167 | 168 | {/* Messages */} 169 |
170 | {messages.map((message, index) => ( 171 |
172 |
175 |

{message.text}

176 |

{new Date(message.timestamp).toLocaleTimeString()}

177 |
178 |
179 | ))} 180 |
181 | 182 | {/* Message input */} 183 |
184 |
185 | setNewMessage(e.target.value)} 189 | placeholder="Type a message..." 190 | className="flex-1 bg-gray-800 rounded-full px-4 py-2 text-sm focus:outline-none" 191 | /> 192 | 195 |
196 |
197 |
198 |
199 |
200 | ); 201 | } 202 | -------------------------------------------------------------------------------- /src/pages/MainPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import logo from '../../public/logo.png'; 3 | import talkTo from '../../public/Talkto.png'; 4 | import Online from '../../public/Online.png'; 5 | import People from '../../public/People.png'; 6 | 7 | export default function MainPage() { 8 | const [activeButton, setActiveButton] = useState('Trending'); 9 | 10 | const handleClick = (buttonName) => { 11 | setActiveButton(buttonName); 12 | }; 13 | const [activeTab, setActiveTab] = useState('home'); 14 | 15 | // Real profile images 16 | const profileImages = [ 17 | 'https://i.pravatar.cc/150?img=4', 18 | 'https://i.pravatar.cc/150?img=5', 19 | 'https://i.pravatar.cc/150?img=6', 20 | 'https://i.pravatar.cc/150?img=1', 21 | 'https://i.pravatar.cc/150?img=7', 22 | 'https://i.pravatar.cc/150?img=8', 23 | 'https://i.pravatar.cc/150?img=9', 24 | 'https://i.pravatar.cc/150?img=2', 25 | 'https://i.pravatar.cc/150?img=3', 26 | 'https://i.pravatar.cc/150?img=5', 27 | 'https://i.pravatar.cc/150?img=4', 28 | 'https://i.pravatar.cc/150?img=7', 29 | 30 | 'https://i.pravatar.cc/150?img=3', 31 | 'https://i.pravatar.cc/150?img=4', 32 | 'https://i.pravatar.cc/150?img=5', 33 | 'https://i.pravatar.cc/150?img=6', 34 | 'https://i.pravatar.cc/150?img=1', 35 | 'https://i.pravatar.cc/150?img=7', 36 | 'https://i.pravatar.cc/150?img=8', 37 | 'https://i.pravatar.cc/150?img=9', 38 | 'https://i.pravatar.cc/150?img=2', 39 | 'https://i.pravatar.cc/150?img=3', 40 | 'https://i.pravatar.cc/150?img=5', 41 | 'https://i.pravatar.cc/150?img=4', 42 | 'https://i.pravatar.cc/150?img=7', 43 | 44 | 'https://i.pravatar.cc/150?img=3', 45 | 46 | 'https://i.pravatar.cc/150?img=3', 47 | 'https://i.pravatar.cc/150?img=4', 48 | 'https://i.pravatar.cc/150?img=5', 49 | 'https://i.pravatar.cc/150?img=6', 50 | 'https://i.pravatar.cc/150?img=1', 51 | 'https://i.pravatar.cc/150?img=7', 52 | 'https://i.pravatar.cc/150?img=8', 53 | 'https://i.pravatar.cc/150?img=9', 54 | 'https://i.pravatar.cc/150?img=2', 55 | 'https://i.pravatar.cc/150?img=3', 56 | 'https://i.pravatar.cc/150?img=5', 57 | 'https://i.pravatar.cc/150?img=4', 58 | 'https://i.pravatar.cc/150?img=7', 59 | 60 | 'https://i.pravatar.cc/150?img=3', 61 | 'https://i.pravatar.cc/150?img=3', 62 | ]; 63 | return ( 64 |
65 | {/* Main content */} 66 |
67 | {/* Top avatar scroll */} 68 |
69 |
70 | {profileImages.map((img, i) => ( 71 |
72 |
73 | {`User 74 |
75 |
76 | ))} 77 |
78 |
79 | 80 | {/* Main banner */} 81 |
82 |
83 |
84 | 85 |
86 |
87 |

Snap. Share. Connect....

88 |

89 | Zoomerang-Z - Take, trim, and make the trick. Share, chat, and go viral. #stayzoomedout 90 |

91 |
92 |
93 |
94 | 95 | 96 | 99 |
100 |
101 | 102 | {/* User Grid */} 103 |
104 |
105 | {/* Top Row */} 106 | {Array(6) 107 | .fill() 108 | .map((_, i) => ( 109 |
110 | {i % 2 === 0 ? ( 111 | // Split view (2 users) 112 | <> 113 |
114 | {`User 120 |
121 |
122 | {`User 128 |
129 | 130 | ) : ( 131 | // Single user 132 | {`User 138 | )} 139 | {/* Live tag */} 140 | {i % 3 === 2 && ( 141 |
LIVE
142 | )} 143 |
144 | ))} 145 | 146 | {/* Bottom Row - Duplicate of top row for this example */} 147 | {Array(6) 148 | .fill() 149 | .map((_, i) => ( 150 |
151 | {i % 2 === 0 ? ( 152 | // Split view 153 | <> 154 |
155 | {`User 161 |
162 |
163 | {`User 169 |
170 | 171 | ) : ( 172 | // Single user 173 | {`User 179 | )} 180 | {/* Live tag */} 181 | {i % 3 === 2 && ( 182 |
LIVE
183 | )} 184 |
185 | ))} 186 |
187 |
188 | 189 | {/* Footer */} 190 |
191 | 194 | 195 |
196 | 204 | 212 |
213 |
214 |
215 |
216 | ); 217 | } 218 | -------------------------------------------------------------------------------- /src/pages/ChatPage.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef } from 'react'; 2 | import logo from '../../public/logo.png'; 3 | import talkTo from '../../public/Talkto.png'; 4 | import Online from '../../public/Online.png'; 5 | import People from '../../public/People.png'; 6 | import { Settings, User, LogOut, Send, Smile, Paperclip, X } from 'lucide-react'; 7 | 8 | // Common emojis list 9 | const commonEmojis = [ 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 | '💙', 126 | '💜', 127 | '🖤', 128 | '🤍', 129 | '🤎', 130 | '💔', 131 | '❣️', 132 | '💕', 133 | '💞', 134 | '💓', 135 | '💗', 136 | '💖', 137 | '💘', 138 | '💝', 139 | '💟', 140 | '👍', 141 | '👎', 142 | '👏', 143 | '🙌', 144 | '👋', 145 | '🤝', 146 | '✌️', 147 | '🤞', 148 | '🤟', 149 | '🤘', 150 | '👌', 151 | '👈', 152 | '👉', 153 | '👆', 154 | '👇', 155 | '☝️', 156 | '✋', 157 | '🤚', 158 | '🖐️', 159 | '🖖', 160 | '👋', 161 | '🤙', 162 | '💪', 163 | '🦾', 164 | '🦿', 165 | '🦵', 166 | '🦶', 167 | '👂', 168 | '🦻', 169 | '👃', 170 | '🧠', 171 | '🦷', 172 | '🦴', 173 | '👀', 174 | '👁️', 175 | '👅', 176 | '👄', 177 | '💋', 178 | '🩸', 179 | '💦', 180 | ]; 181 | 182 | export default function StartChatPage() { 183 | const [activeButton, setActiveButton] = useState('Trending'); 184 | const [messages, setMessages] = useState([]); 185 | const [newMessage, setNewMessage] = useState(''); 186 | const [ws, setWs] = useState(null); 187 | const messagesEndRef = useRef(null); 188 | const [activeTab, setActiveTab] = useState('home'); 189 | const [selectedUser, setSelectedUser] = useState(null); 190 | const [showProfileMenu, setShowProfileMenu] = useState(false); 191 | const [userStatus, setUserStatus] = useState('online'); 192 | const [displayName, setDisplayName] = useState('You'); 193 | const [isEditingName, setIsEditingName] = useState(false); 194 | const [unreadCounts, setUnreadCounts] = useState({ 195 | 1: 3, // John Doe has 3 unread messages 196 | 2: 1, // Jane Smith has 1 unread message 197 | 3: 0, // Mike Johnson has no unread messages 198 | }); 199 | const [users, setUsers] = useState([ 200 | { id: 1, name: 'John Doe', status: 'online', avatar: logo, lastSeen: '2 minutes ago' }, 201 | { id: 2, name: 'Jane Smith', status: 'offline', avatar: logo, lastSeen: '1 hour ago' }, 202 | { id: 3, name: 'Mike Johnson', status: 'online', avatar: logo, lastSeen: '5 minutes ago' }, 203 | ]); 204 | const [showEmojiPicker, setShowEmojiPicker] = useState(false); 205 | const emojiPickerRef = useRef(null); 206 | 207 | useEffect(() => { 208 | // Initialize WebSocket connection 209 | const socket = new WebSocket('ws://localhost:8080'); 210 | setWs(socket); 211 | 212 | socket.onmessage = (event) => { 213 | const message = JSON.parse(event.data); 214 | setMessages((prevMessages) => [...prevMessages, message]); 215 | }; 216 | 217 | return () => { 218 | socket.close(); 219 | }; 220 | }, []); 221 | 222 | useEffect(() => { 223 | messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); 224 | }, [messages]); 225 | 226 | useEffect(() => { 227 | // Close emoji picker when clicking outside 228 | const handleClickOutside = (event) => { 229 | if (emojiPickerRef.current && !emojiPickerRef.current.contains(event.target)) { 230 | setShowEmojiPicker(false); 231 | } 232 | }; 233 | 234 | document.addEventListener('mousedown', handleClickOutside); 235 | return () => { 236 | document.removeEventListener('mousedown', handleClickOutside); 237 | }; 238 | }, []); 239 | 240 | const handleEmojiSelect = (emoji) => { 241 | setNewMessage((prev) => prev + emoji); 242 | setShowEmojiPicker(false); 243 | }; 244 | 245 | const handleSendMessage = (e) => { 246 | e.preventDefault(); 247 | if (newMessage.trim() && ws) { 248 | const message = { 249 | text: newMessage, 250 | timestamp: new Date().toISOString(), 251 | sender: 'me', 252 | senderName: displayName, 253 | }; 254 | setMessages((prevMessages) => [...prevMessages, message]); 255 | ws.send(JSON.stringify(message)); 256 | setNewMessage(''); 257 | } 258 | }; 259 | 260 | const handleClick = (buttonName) => { 261 | setActiveButton(buttonName); 262 | }; 263 | 264 | const handleUserClick = (user) => { 265 | setSelectedUser(user); 266 | // Reset unread count when user is selected 267 | setUnreadCounts((prev) => ({ 268 | ...prev, 269 | [user.id]: 0, 270 | })); 271 | }; 272 | 273 | const handleStatusChange = (status) => { 274 | setUserStatus(status); 275 | setShowProfileMenu(false); 276 | }; 277 | 278 | const handleNameChange = (e) => { 279 | e.preventDefault(); 280 | setDisplayName(e.target.nameInput.value); 281 | setIsEditingName(false); 282 | }; 283 | 284 | const handleFileUpload = (e) => { 285 | const file = e.target.files[0]; 286 | if (file) { 287 | // Handle file upload logic here 288 | console.log('File selected:', file.name); 289 | } 290 | }; 291 | 292 | return ( 293 |
294 | {/* Main content */} 295 |
296 | {/* Top bar with user info */} 297 | {selectedUser && ( 298 |
299 |
300 |
301 | {selectedUser.name} 302 |
307 |
308 |
309 |

{selectedUser.name}

310 |

311 | {selectedUser.status === 'online' ? 'Online' : `Last seen ${selectedUser.lastSeen}`} 312 |

313 |
314 |
315 |
316 | )} 317 | 318 | {/* Chat messages area */} 319 |
320 | {messages.map((message, index) => ( 321 |
322 |
323 |

{message.senderName || 'User'}

324 |

{message.text}

325 |

{new Date(message.timestamp).toLocaleTimeString()}

326 |
327 |
328 | ))} 329 |
330 |
331 | 332 | {/* Message input area */} 333 |
334 |
335 |
336 | Profile setShowProfileMenu(!showProfileMenu)} 341 | /> 342 | {/* Profile Menu */} 343 | {showProfileMenu && ( 344 |
345 |
346 | {/* Display Name */} 347 |
348 | {isEditingName ? ( 349 | 350 | 356 | 359 | 360 | ) : ( 361 |
362 | Display Name: {displayName} 363 | 366 |
367 | )} 368 |
369 | 370 | {/* Status */} 371 |
372 |

Status:

373 |
374 | 382 | 390 |
391 |
392 | 393 | {/* Settings */} 394 | 398 |
399 |
400 | )} 401 |
402 | 403 |
404 | setNewMessage(e.target.value)} 408 | placeholder="Type a message..." 409 | className="flex-1 bg-transparent py-3 focus:outline-none" 410 | /> 411 | 412 | {/* Emoji button and picker */} 413 |
414 | 421 | 422 | {showEmojiPicker && ( 423 |
424 |
425 |

Emojis

426 | 429 |
430 |
431 | {commonEmojis.map((emoji, index) => ( 432 | 439 | ))} 440 |
441 |
442 | )} 443 |
444 | 445 | {/* File upload button */} 446 | 450 |
451 | 452 | {/* Send button */} 453 | 460 | 461 |
462 |
463 | 464 | {/* Users list */} 465 |
466 |

Online Users

467 |
468 | {users.map((user) => ( 469 |
handleUserClick(user)} 472 | className={`flex items-center justify-between p-3 hover:bg-gray-800 rounded-lg cursor-pointer ${ 473 | selectedUser?.id === user.id ? 'bg-gray-800' : '' 474 | }`} 475 | > 476 |
477 |
478 | {user.name} 479 |
484 |
485 |
486 |

{user.name}

487 |

488 | {user.status === 'online' ? 'Online' : `Last seen ${user.lastSeen}`} 489 |

490 |
491 |
492 | {/* Unread message count */} 493 | {unreadCounts[user.id] > 0 && ( 494 |
495 | {unreadCounts[user.id]} 496 |
497 | )} 498 |
499 | ))} 500 |
501 |
502 |
503 | ); 504 | } 505 | --------------------------------------------------------------------------------