├── .gitignore ├── LICENSE.md ├── README.md ├── jsconfig.json ├── logo.png ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── assets │ └── preview │ │ ├── chatAnon.png │ │ ├── chatChats.png │ │ ├── chatCreation.png │ │ ├── chatPassword.png │ │ ├── chatWaitingStranger.png │ │ └── setUsername.png ├── components │ └── Sidebar.jsx ├── config │ └── config.js ├── context │ └── connect.js ├── libraries │ └── withSession.js ├── pages │ ├── _app.jsx │ ├── api │ │ └── socket.js │ ├── index.jsx │ ├── random │ │ └── [id].jsx │ └── rooms │ │ ├── [id].jsx │ │ ├── create.jsx │ │ └── index.jsx └── styles │ └── globals.css └── tailwind.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .next 3 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zeroxy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 21 | [![Contributors][contributors-shield]][contributors-url] 22 | [![Forks][forks-shield]][forks-url] 23 | [![Stargazers][stars-shield]][stars-url] 24 | [![Issues][issues-shield]][issues-url] 25 | [![MIT License][license-shield]][license-url] 26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 | Logo 34 | 35 | 36 |

Sesh | Chat app with rooms & anon talks

37 | 38 |

39 | Chat with people of your interest or strangers! 40 |
41 | Explore the docs » 42 |
43 |
44 | View Demo 45 | · 46 | Report Bug 47 | · 48 | Request Feature 49 |

50 |
51 | 52 | 53 | 54 | 55 |
56 | Table of Contents 57 |
    58 |
  1. 59 | About The Project 60 | 63 |
  2. 64 |
  3. 65 | Getting Started 66 | 70 |
  4. 71 |
  5. Usage
  6. 72 |
  7. Roadmap
  8. 73 |
  9. Contributing
  10. 74 |
  11. License
  12. 75 |
  13. Contact
  14. 76 |
  15. Acknowledgments
  16. 77 |
78 |
79 | 80 | 81 | 82 | 83 | ## About The Project 84 | 85 | 86 | With this project you will be able to chat with people of your interest, creating or joining chat rooms. 87 | It also has an anonymous chat mode where you can chat completely anonymously. 88 | 89 | Characteristics: 90 | * Anonymous chat 91 | * Creation of rooms (also with password) 92 | * Join random chat rooms 93 | 94 | I welcome any code improvements, suggestions, or ideas! 95 | 96 | Preview of `Sesh`: 97 | 98 | 99 | 100 | Logo 101 | 102 | 103 | Logo 104 | 105 | 106 | Logo 107 | 108 | 109 | Logo 110 | 111 | 112 | Logo 113 | 114 | 115 | Logo 116 | 117 | 118 |

(back to top)

119 | 120 | ### Built With 121 | 122 | * [![Next][Next.js]][Next-url] 123 | * [![React][React.js]][React-url] 124 | * [![Tailwind][Tailwind.com]][Tailwind-url] 125 | 126 |

(back to top)

127 | 128 | 129 | 130 | 131 | ## Getting Started 132 | 133 | ### Prerequisites 134 | * npm 135 | ```sh 136 | npm install npm@latest -g 137 | ``` 138 | 139 | ### Installation 140 | 141 | 1. Clone the repo 142 | ```sh 143 | git clone https://github.com/zeroxydev/Random-Chat-App.git 144 | ``` 145 | 2. Install NPM packages 146 | ```sh 147 | npm install 148 | ``` 149 | 3. Run the project 150 | ```sh 151 | npm run dev 152 | ``` 153 | 154 |

(back to top)

155 | 156 | 157 | 158 | 159 | ## Usage 160 | 161 | This application does not require special use, you can implement functions, redesign the style... etc, or use it as it is. 162 | 163 |

(back to top)

164 | 165 | 166 | 167 | 168 | ## Roadmap 169 | 170 | - [x] Add anon chat 171 | - [x] Add rooms 172 | - [x] Add unic tags 173 | - [x] Add verified badge on specific names 174 | - [x] Add responsive design 175 | - [ ] Multi-language Support 176 | - [ ] Spanish 177 | 178 | See the [open issues](https://github.com/ZeroxyDev/Random-Chat-App/issues) for a full list of proposed features (and known issues). 179 | 180 |

(back to top)

181 | 182 | 183 | 184 | 185 | ## Contributing 186 | 187 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 188 | 189 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". 190 | Don't forget to give the project a star! Thanks again! 191 | 192 | 1. Fork the Project 193 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 194 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 195 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 196 | 5. Open a Pull Request 197 | 198 |

(back to top)

199 | 200 | 201 | 202 | 203 | ## License 204 | 205 | Distributed under the MIT License. See `LICENSE.txt` for more information. 206 | 207 |

(back to top)

208 | 209 | 210 | 211 | 212 | ## Contact 213 | 214 | ZeroxyDev - [@ZeroxyDev](https://twitter.com/ZeroxyDev) 215 | 216 | Project Link: [https://github.com/ZeroxyDev/Random-Chat-App](https://github.com/ZeroxyDev/Random-Chat-App) 217 | 218 |

(back to top)

219 | 220 | 221 | 222 | 223 | ## Acknowledgments 224 | 225 | * [Choose an Open Source License](https://choosealicense.com) 226 | * [GitHub Emoji Cheat Sheet](https://www.webpagefx.com/tools/emoji-cheat-sheet) 227 | * [Malven's Flexbox Cheatsheet](https://flexbox.malven.co/) 228 | * [Malven's Grid Cheatsheet](https://grid.malven.co/) 229 | * [Img Shields](https://shields.io) 230 | * [GitHub Pages](https://pages.github.com) 231 | * [Font Awesome](https://fontawesome.com) 232 | * [React Icons](https://react-icons.github.io/react-icons/search) 233 | 234 |

(back to top)

235 | 236 | 237 | 238 | 239 | 240 | [contributors-shield]: https://img.shields.io/github/contributors/ZeroxyDev/Random-Chat-App.svg?style=for-the-badge 241 | [contributors-url]: https://github.com/ZeroxyDev/Random-Chat-App/graphs/contributors 242 | [forks-shield]: https://img.shields.io/github/forks/ZeroxyDev/Random-Chat-App.svg?style=for-the-badge 243 | [forks-url]: https://github.com/ZeroxyDev/Random-Chat-App/network/members 244 | [stars-shield]: https://img.shields.io/github/stars/ZeroxyDev/Random-Chat-App.svg?style=for-the-badge 245 | [stars-url]: https://github.com/ZeroxyDev/Random-Chat-App/stargazers 246 | [issues-shield]: https://img.shields.io/github/issues/ZeroxyDev/Random-Chat-App.svg?style=for-the-badge 247 | [issues-url]: https://github.com/ZeroxyDev/Random-Chat-App/issues 248 | [license-shield]: https://img.shields.io/github/license/ZeroxyDev/Random-Chat-App.svg?style=for-the-badge 249 | [license-url]: https://github.com/ZeroxyDev/Random-Chat-App/blob/main/LICENSE.md 250 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 251 | [linkedin-url]: https://linkedin.com/in/zeroxydev 252 | [product-screenshot]: images/screenshot.png 253 | [Next.js]: https://img.shields.io/badge/next.js-000000?style=for-the-badge&logo=nextdotjs&logoColor=white 254 | [Next-url]: https://nextjs.org/ 255 | [React.js]: https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB 256 | [React-url]: https://reactjs.org/ 257 | [Vue.js]: https://img.shields.io/badge/Vue.js-35495E?style=for-the-badge&logo=vuedotjs&logoColor=4FC08D 258 | [Vue-url]: https://vuejs.org/ 259 | [Angular.io]: https://img.shields.io/badge/Angular-DD0031?style=for-the-badge&logo=angular&logoColor=white 260 | [Angular-url]: https://angular.io/ 261 | [Svelte.dev]: https://img.shields.io/badge/Svelte-4A4A55?style=for-the-badge&logo=svelte&logoColor=FF3E00 262 | [Svelte-url]: https://svelte.dev/ 263 | [Laravel.com]: https://img.shields.io/badge/Laravel-FF2D20?style=for-the-badge&logo=laravel&logoColor=white 264 | [Laravel-url]: https://laravel.com 265 | [Bootstrap.com]: https://img.shields.io/badge/Bootstrap-563D7C?style=for-the-badge&logo=bootstrap&logoColor=white 266 | [Bootstrap-url]: https://getbootstrap.com 267 | [Tailwind.com]: https://img.shields.io/badge/Tailwind-ffffff?style=for-the-badge&logo=tailwindcss&logoColor=38bdf8 268 | [Tailwind-url]: https://tailwindcss.com/ 269 | [JQuery.com]: https://img.shields.io/badge/jQuery-0769AD?style=for-the-badge&logo=jquery&logoColor=white 270 | [JQuery-url]: https://jquery.com 271 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src" 4 | }, 5 | } -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroxyDev/Random-Chat-App/829b6c5dc2af2a9717bd2669091c5d007614918f/logo.png -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | swcMinify: true, 5 | } 6 | 7 | module.exports = nextConfig 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nextjs-chat", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@fortawesome/free-solid-svg-icons": "^6.3.0", 13 | "@fortawesome/react-fontawesome": "^0.2.0", 14 | "@headlessui/react": "^1.6.6", 15 | "clsx": "^1.2.1", 16 | "framer-motion": "^9.0.7", 17 | "next": "12.2.5", 18 | "next-iron-session": "^4.2.0", 19 | "react": "18.2.0", 20 | "react-dom": "18.2.0", 21 | "socket.io": "^4.5.1", 22 | "socket.io-client": "^4.5.1" 23 | }, 24 | "devDependencies": { 25 | "autoprefixer": "^10.4.8", 26 | "eslint": "8.22.0", 27 | "eslint-config-next": "12.2.5", 28 | "postcss": "^8.4.16", 29 | "tailwindcss": "^3.1.8" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/assets/preview/chatAnon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroxyDev/Random-Chat-App/829b6c5dc2af2a9717bd2669091c5d007614918f/src/assets/preview/chatAnon.png -------------------------------------------------------------------------------- /src/assets/preview/chatChats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroxyDev/Random-Chat-App/829b6c5dc2af2a9717bd2669091c5d007614918f/src/assets/preview/chatChats.png -------------------------------------------------------------------------------- /src/assets/preview/chatCreation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroxyDev/Random-Chat-App/829b6c5dc2af2a9717bd2669091c5d007614918f/src/assets/preview/chatCreation.png -------------------------------------------------------------------------------- /src/assets/preview/chatPassword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroxyDev/Random-Chat-App/829b6c5dc2af2a9717bd2669091c5d007614918f/src/assets/preview/chatPassword.png -------------------------------------------------------------------------------- /src/assets/preview/chatWaitingStranger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroxyDev/Random-Chat-App/829b6c5dc2af2a9717bd2669091c5d007614918f/src/assets/preview/chatWaitingStranger.png -------------------------------------------------------------------------------- /src/assets/preview/setUsername.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZeroxyDev/Random-Chat-App/829b6c5dc2af2a9717bd2669091c5d007614918f/src/assets/preview/setUsername.png -------------------------------------------------------------------------------- /src/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import { Dialog, Transition } from "@headlessui/react"; 2 | import { useConnection } from "context/connect"; 3 | import { useRouter } from "next/router"; 4 | import { Fragment, useEffect, useState } from "react"; 5 | import { faCheckCircle, faComment, faLock, faPlus, faRandom, faRightToBracket, faShuffle, faUser, faUserSecret } from '@fortawesome/free-solid-svg-icons' 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 7 | import { motion, AnimatePresence } from "framer-motion" 8 | import { mainConfig } from "config/config"; 9 | 10 | export default function Sidebar() { 11 | 12 | const router = useRouter(); 13 | let [rooms, setRooms] = useState([]); 14 | let [user, setUser] = useState(null); 15 | let [isOpen, setIsOpen] = useState(false); 16 | let [protectedRoom, setProtected] = useState(false); 17 | let [password, setPassword] = useState(''); 18 | const { connection } = useConnection(); 19 | let [onroom, setOnroom] = useState(false); 20 | let [online, setOnline] = useState(0); 21 | const { pathname } = useRouter(); 22 | /* random user */ 23 | 24 | 25 | 26 | /* rooms */ 27 | 28 | useEffect(() => { 29 | if (connection) { 30 | connection.emit('fetchUser'); 31 | connection.on('user', data => { 32 | if (data === null) { 33 | router.push('/'); 34 | } else { 35 | setUser(data); 36 | } 37 | }); 38 | 39 | return () => { 40 | connection.off('user', data => { 41 | if (data === null) { 42 | router.push('/'); 43 | } else { 44 | setUser(data); 45 | } 46 | }); 47 | } 48 | } 49 | }, [connection]); 50 | 51 | useEffect(() => { 52 | if (connection) { 53 | connection.emit('fetchRooms'); 54 | connection.on('rooms', data => { 55 | setRooms(data.rooms.slice(-10)); 56 | }); 57 | 58 | return () => { 59 | connection.off('rooms', data => { 60 | if (data.isLogged) { 61 | setUser(data.user); 62 | } 63 | setRooms(data.rooms.slice(-10)); 64 | }); 65 | } 66 | } 67 | }, []); 68 | 69 | function getRandomInt(max) { 70 | if(rooms.length == 1){ 71 | max = 0 72 | } 73 | return Math.floor(Math.random() * max); 74 | } 75 | 76 | 77 | const filterroomsrandom2 = rooms.filter(arr => arr.name == 'random') 78 | const filterroomsrandom = filterroomsrandom2.filter(arr => arr.users < 2) 79 | const filterrooms2 = rooms.filter(arr => !arr.passwordProtected) 80 | const filterrooms = filterrooms2.filter(arr => arr.name != "random") 81 | 82 | async function joinrandom(){ 83 | if (filterrooms.length >= 1){ 84 | const randomroom = getRandomInt(filterrooms.length) 85 | JoinRoom(rooms[randomroom]) 86 | router.push(`/rooms/${rooms[randomroom].id}`) 87 | } 88 | } 89 | 90 | async function chatrandom(){ 91 | 92 | if(pathname != "/rooms"){ 93 | await LeaveRoom() 94 | } 95 | 96 | 97 | 98 | if (filterroomsrandom.length >= 1){ 99 | const randomroom = getRandomInt(filterroomsrandom.length) 100 | router.push(`/random/${rooms[randomroom].id}`) 101 | }else{ 102 | CreateRoom() 103 | } 104 | } 105 | 106 | 107 | function CreateRoom(){ 108 | const name = "random"; 109 | const password = ""; 110 | const maxUsers = 2; 111 | 112 | connection.emit('createRoom', { name, password, maxUsers }); 113 | connection.on('createRoom', data => { 114 | const result = data; 115 | if (result.success) { 116 | router.push(`/random/` + result.data.id) 117 | } else { 118 | 119 | } 120 | }); 121 | } 122 | 123 | 124 | useEffect(() => { 125 | if(router.pathname == "/rooms"){ 126 | setOnroom(false) 127 | }else{ 128 | setOnroom(true) 129 | } 130 | 131 | connection?.off('UsersOnline').on('UsersOnline', data => { 132 | if (data.success) { 133 | setOnline(data.users); 134 | } else { 135 | } 136 | }); 137 | }, [router]); 138 | 139 | const LeaveRoom = async () => { 140 | connection.emit('leaveRoom'); 141 | connection.on('leaveRoom', data => { 142 | if (data.success) { 143 | 144 | } 145 | 146 | }); 147 | } 148 | 149 | const JoinRoom = room => { 150 | const { id, passwordProtected } = room; 151 | if (passwordProtected) { 152 | setIsOpen(true); 153 | setProtected(room); 154 | 155 | if (password) { 156 | connection.emit('joinRoom', { id, password }); 157 | } 158 | 159 | } else { 160 | connection.emit('joinRoom', { id }); 161 | } 162 | 163 | connection.off('joinRoom').on('joinRoom', data => { 164 | if (data.success) { 165 | setIsOpen(false); 166 | setPassword(''); 167 | router.push('/rooms/' + id); 168 | } else { 169 | if (data?.alreadyIn) { 170 | router.push('/rooms/' + id); 171 | } else { 172 | alert(data.error) 173 | } 174 | } 175 | }); 176 | } 177 | 178 | return <> 179 | {router.pathname != "/random" && 184 | 185 | 186 | { 187 | setIsOpen(false); 188 | setPassword(''); 189 | }}> 190 | 199 |
200 | 201 | 202 |
203 |
204 | 213 | 214 | 218 | Password Protected Room 219 | 220 |
{ 221 | e.preventDefault(); 222 | JoinRoom(protectedRoom); 223 | }}> 224 |
225 |

226 | This room is password protected. Please enter the password to join. 227 |

228 | 229 | setPassword(e.target.value)} 234 | /> 235 |
236 | 237 |
238 | 245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 | 254 |
255 |
256 |

{online}

257 |
258 | 259 |
260 | 261 |
262 |
263 | {rooms.map(room => { 264 | return
265 | {room.name != "random" &&
JoinRoom(room)}> 266 | username 267 |
268 | {room.name} 269 | {!room?.owner?.verified ? Created by {room?.owner?.username.split(0, 5) + '...'} : Created by {room?.owner?.username.split(0, 5)} } 270 |
271 |
272 | {room.passwordProtected && } 273 | {room.users || 0}/{room.maxUsers} 274 |
275 |
} 276 |
277 | })} 278 |
279 | 280 |
281 |
282 | username 283 | {!user?.verified ? {user?.username} : {user?.username} } 284 |
285 |
286 |
287 | 288 | {!onroom &&
289 |
290 |

{online}

291 |
292 | 293 |
294 | 295 |
296 |
297 | {rooms.map(room => { 298 | return
299 | { room.name != "random" &&
JoinRoom(room)}> 300 | username 301 |
302 | {room.name} 303 | {!room?.owner?.verified ? Created by {room?.owner?.username.split(0, 5) + '...'} : Created by {room?.owner?.username.split(0, 5)} } 304 |
305 | {room.users || 0}/{room.maxUsers} 306 |
307 | {room.passwordProtected && } 308 |
309 |
} 310 |
311 | })} 312 |
313 | 314 |
315 |
316 | username 317 | {!user?.verified ? {user?.username} : {user?.username} } 318 |
319 |
320 |
} 321 |
} 322 | 323 | } -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | /* ---------------------------------------- 2 | MAIN CONFIG 3 | ---------------------------------------- */ 4 | export const mainConfig = { 5 | //General 6 | nameApp: "Sesh", 7 | imageApp: "https://i.ibb.co/n30R7NH/logo.png", 8 | //API 9 | avatarAPI: "https://api.dicebear.com/7.x/lorelei/svg?seed=", //API of avatars 10 | initialsAPI: "https://api.dicebear.com/7.x/initials/svg?seed=", // API of initials 11 | //Other 12 | verifiedNames: ["Aaron", "Developer"] // Names to get verified 13 | } 14 | 15 | /* ---------------------------------------- 16 | ANIMATIONS 17 | ---------------------------------------- */ 18 | //Animations for my messages 19 | export const animConfig = { 20 | initial: { opacity: 0, x: 20 }, 21 | animate: { opacity: 1, x: 0 }, 22 | exit: { opacity: 0, x: -20 }, 23 | transition: { duration: 0.2 }, 24 | } 25 | //Animations for other messages 26 | export const animConfigOther = { 27 | initial: { opacity: 0, x: -20 }, 28 | animate: { opacity: 1, x: 0 }, 29 | exit: { opacity: 0, x: -20 }, 30 | transition: { duration: 0.2 }, 31 | } -------------------------------------------------------------------------------- /src/context/connect.js: -------------------------------------------------------------------------------- 1 | import { useContext, createContext, useState, useEffect } from 'react'; 2 | import io from "socket.io-client"; 3 | 4 | const Context = createContext(); 5 | export const useConnection = () => useContext(Context); 6 | 7 | export const Provider = ({ children }) => { 8 | 9 | const [connection, setConnection] = useState(null); 10 | 11 | 12 | const data = { 13 | connection, 14 | }; 15 | 16 | useEffect(() => { 17 | fetch("/api/socket"); 18 | 19 | const socket = io(); 20 | socket.connect(); 21 | 22 | socket.on('connect', () => { 23 | setConnection(socket); 24 | }); 25 | 26 | 27 | return () => { 28 | socket.off('connect'); 29 | }; 30 | }, []); 31 | 32 | return ( 33 | 34 | {children} 35 | 36 | ); 37 | }; 38 | 39 | export default Context; 40 | -------------------------------------------------------------------------------- /src/libraries/withSession.js: -------------------------------------------------------------------------------- 1 | import { withIronSession } from "next-iron-session"; 2 | 3 | export default function withSession(app) { 4 | return withIronSession(app, { 5 | password: "bXlzcWxhc3N3b3JkMTIzNDU2Nzg5MA====", 6 | cookieName: "simple-nextjs-socketio-chat", 7 | cookieOptions: { 8 | secure: process.env.NODE_ENV === "production", 9 | } 10 | }); 11 | }; -------------------------------------------------------------------------------- /src/pages/_app.jsx: -------------------------------------------------------------------------------- 1 | import 'styles/globals.css' 2 | import { Provider as ConnectionProvider } from 'context/connect' 3 | import { useRouter } from 'next/router' 4 | import Sidebar from 'components/Sidebar'; 5 | import Head from 'next/head'; 6 | import { mainConfig } from 'config/config'; 7 | 8 | function MyApp({ Component, pageProps }) { 9 | const router = useRouter(); 10 | return 11 | 12 | {mainConfig.nameApp} | Chat with random people! 13 | 14 | 15 | {router.pathname.includes('rooms') ? ( 16 |
17 | 18 |
19 | 20 |
21 |
22 | ) : } 23 |
24 | } 25 | 26 | export default MyApp; 27 | -------------------------------------------------------------------------------- /src/pages/api/socket.js: -------------------------------------------------------------------------------- 1 | import { mainConfig } from "config/config"; 2 | import { Server } from "socket.io"; 3 | 4 | export default (async (req, res) => { 5 | if (res.socket.server.io) { 6 | res.end(); 7 | return; 8 | } 9 | 10 | const io = new Server(res.socket.server, { 11 | pingInterval: 10000, 12 | pingTimeout: 5000 13 | }); 14 | res.socket.server.io = io; 15 | 16 | io.use((socket, next) => { 17 | setInterval(() => { 18 | socket.emit("ping", "pong"); 19 | }, 1000); 20 | next(); 21 | }); 22 | 23 | io.on("connection", (socket) => { 24 | socket.join('global'); 25 | 26 | socket.on("login", async (data) => { 27 | const { username } = data; 28 | 29 | const allSockets = await io.fetchSockets(); 30 | const userSockets = allSockets.filter((s) => s?.data?.user?.username === username); 31 | 32 | 33 | if (userSockets.length > 0) return socket.emit("login", { error: "Username already taken"}); 34 | 35 | 36 | var user = { 37 | username, 38 | }; 39 | 40 | if(username.includes(mainConfig.verifiedNames)){ 41 | user = { 42 | username, 43 | verified: true, 44 | }; 45 | }else{ 46 | user = { 47 | username 48 | }; 49 | } 50 | 51 | 52 | 53 | socket.data.user = user; 54 | socket.emit("login", { 55 | success: true, 56 | data: user 57 | }); 58 | }); 59 | 60 | socket.on("fetchUser", () => { 61 | const user = socket.data.user; 62 | if (user) { 63 | socket.emit("user", user); 64 | } else { 65 | socket.emit("user", null); 66 | } 67 | }) 68 | 69 | socket.on("fetchRooms", () => { 70 | setInterval(async () => { 71 | const rooms = io.sockets.adapter.rooms; 72 | const allRooms = (await Promise.all(Object.keys(rooms).map(async (room) => { 73 | const sockets = await io.in(room).fetchSockets(); 74 | const users = sockets.map((s) => s.data.user); 75 | return { 76 | id: room, 77 | name: rooms[room]?.name, 78 | owner: rooms[room]?.owner, 79 | passwordProtected: rooms[room]?.password ? true : false, 80 | maxUsers: rooms[room]?.maxUsers, 81 | users: users.length 82 | }; 83 | }))).filter((r) => r.name !== 'global'); 84 | socket.emit("rooms", { 85 | isLogged: socket.data?.user !== undefined ? true : false, 86 | user: socket.data?.user, 87 | rooms: allRooms 88 | }); 89 | 90 | const allSockets = await io.fetchSockets(); 91 | const users = allSockets.filter((s) => s?.data?.user?.username); 92 | const usersonline = users.length; 93 | 94 | socket.emit("UsersOnline", { success: true, users: usersonline }); 95 | }, 1000); 96 | }); 97 | 98 | socket.on("UsersOnline", async () => { 99 | socket.emit("UsersOnline", { success: true }); 100 | }); 101 | 102 | socket.on("createRoom", data => { 103 | const { name, password, maxUsers } = data; 104 | if (!name) return socket.emit("createRoom", { success: false, error: "Name is required" }); 105 | if (io.sockets.adapter.rooms[name]) return socket.emit("createRoom", { success: false, error: "Room already exists" }); 106 | let room = { 107 | id: Math.random().toString(36).substring(2, 9), 108 | name: name.replace(/[^a-zA-Z0-9 ]/g, ""), 109 | owner: socket.data.user, 110 | users: 1, 111 | maxUsers: maxUsers, 112 | }; 113 | 114 | if (password) room.password = password; 115 | 116 | 117 | io.sockets.adapter.rooms[room.id] = room; 118 | 119 | socket.rooms.forEach((user_room) => { 120 | socket.leave(user_room); 121 | updateMembers(user_room); 122 | socket.to(user_room).emit("message", { 123 | system: true, 124 | message: `${socket.data.user.username} left the room` 125 | }); 126 | }); 127 | socket.join(room.id); 128 | socket.emit("createRoom", { success: true, data: room }); 129 | }) 130 | 131 | socket.on("joinRoom", async data => { 132 | const { id, password } = data; 133 | if (!id) return socket.emit("joinRoom", { success: false, error: "Room id is required" }); 134 | if (!io.sockets.adapter.rooms[id]) return socket.emit("joinRoom", { success: false, error: "Room not found" }); 135 | 136 | const room = io.sockets.adapter.rooms[id]; 137 | if (room.password && room.password !== password) return socket.emit("joinRoom", { success: false, error: "Wrong password" }); 138 | const sockets = await io.in(id).fetchSockets(); 139 | if (sockets.length >= room.maxUsers) return socket.emit("joinRoom", { success: false, error: "Room is full" }); 140 | if (sockets.find((s) => s.data.user.username === socket.data.user.username)) return socket.emit("joinRoom", { success: false, alreadyIn: true, error: "You are already in this room" }); 141 | 142 | socket.rooms.forEach((user_room) => { 143 | socket.leave(user_room); 144 | updateMembers(user_room); 145 | socket.to(user_room).emit("message", { 146 | system: true, 147 | message: `${socket.data.user.username} left the room` 148 | }); 149 | }); 150 | 151 | socket.join(id); 152 | 153 | updateMembers(id); 154 | socket.emit("joinRoom", { success: true, data: room }); 155 | socket.to(id).emit("message", { 156 | system: true, 157 | message: `${socket.data.user.username} joined the room` 158 | }); 159 | }); 160 | 161 | socket.on("joinRoomStranger", async data => { 162 | const { id, password } = data; 163 | if (!id) return socket.emit("joinRoomStranger", { success: false, error: "Room id is required" }); 164 | if (!io.sockets.adapter.rooms[id]) return socket.emit("joinRoomStranger", { success: false, error: "Room not found" }); 165 | 166 | const room = io.sockets.adapter.rooms[id]; 167 | if (room.password && room.password !== password) return socket.emit("joinRoomStranger", { success: false, error: "Wrong password" }); 168 | const sockets = await io.in(id).fetchSockets(); 169 | if (sockets.length >= room.maxUsers) return socket.emit("joinRoomStranger", { success: false, error: "Room is full" }); 170 | if (sockets.find((s) => s.data.user.username === socket.data.user.username)) return socket.emit("joinRoomStranger", { success: false, alreadyIn: true, error: "You are already in this room" }); 171 | 172 | socket.rooms.forEach((user_room) => { 173 | socket.leave(user_room); 174 | updateMembers(user_room); 175 | socket.to(user_room).emit("message", { 176 | system: true, 177 | message: `${socket.data.user.username} left the room` 178 | }); 179 | }); 180 | 181 | socket.join(id); 182 | 183 | updateMembers(id); 184 | socket.emit("joinRoomStranger", { success: true, data: room }); 185 | socket.to(id).emit("message", { 186 | system: true, 187 | message: `A stranger joins the room` 188 | }); 189 | }); 190 | 191 | socket.on("leaveRoom", async () => { 192 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 193 | if (!room) return socket.emit("leaveRoom", { success: false, error: "You are not in a room" }); 194 | socket.leaveAll(); 195 | socket.join("global"); 196 | socket.emit("leaveRoom", { success: true }); 197 | 198 | updateMembers(room); 199 | socket.to(room).emit("message", { 200 | system: true, 201 | message: `${socket.data.user.username} left the room` 202 | }); 203 | }); 204 | 205 | socket.on("leaveRoomStranger", async () => { 206 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 207 | if (!room) return socket.emit("leaveRoomStranger", { success: false, error: "You are not in a room" }); 208 | socket.leaveAll(); 209 | socket.join("global"); 210 | socket.emit("leaveRoomStranger", { success: true }); 211 | 212 | socket.to(room).emit("ClearMessages", { 213 | success: true, 214 | }); 215 | 216 | 217 | updateMembers(room); 218 | socket.to(room).emit("message", { 219 | system: true, 220 | message: `Stranger left the room` 221 | }); 222 | 223 | }); 224 | 225 | socket.on("ClearMessages", async () => { 226 | socket.emit("ClearMessages", { success: true }); 227 | }); 228 | 229 | socket.on("IsTypping", async () => { 230 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 231 | socket.to(room).emit("IsTypping", { 232 | success: true, 233 | user: socket.data.user, 234 | }); 235 | 236 | }); 237 | 238 | socket.on("roomMembers", async () => { 239 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 240 | if (!room) return socket.emit("roomMembers", { success: false, error: "You are not in a room" }); 241 | 242 | updateMembers(room); 243 | }); 244 | 245 | function updateMembers(room) { 246 | io.in(room).fetchSockets().then(sockets => { 247 | const members = sockets.map(socket => socket.data.user); 248 | if (members.length > 0) { 249 | io.in(room).emit("roomMembers", { success: true, data: members }); 250 | } else { 251 | delete io.sockets.adapter.rooms[room]; 252 | } 253 | }); 254 | } 255 | 256 | socket.on("message", async data => { 257 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 258 | if (!room) return; 259 | 260 | var message = { 261 | user: socket.data.user, 262 | message: data.message, 263 | date: new Date(), 264 | } 265 | 266 | if (data.file && data.type == "image/jpeg" || data.type == "image/png" ){ 267 | message = { 268 | user: socket.data.user, 269 | message: data.message, 270 | date: new Date(), 271 | file: data.file, 272 | type: data.type, 273 | }; 274 | } 275 | 276 | 277 | 278 | const sockets = await io.in(room).fetchSockets(); 279 | sockets.forEach(s => { 280 | s.emit("message", { 281 | ...message, 282 | self: s.id === socket.id 283 | }); 284 | }); 285 | }); 286 | 287 | socket.on("fetchRoom", async () => { 288 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 289 | if (!room) return socket.emit("fetchRoom", { success: false, error: "You are not in a room" }); 290 | 291 | socket.emit("fetchRoom", { success: true, data: io.sockets.adapter.rooms[room] }); 292 | }); 293 | 294 | 295 | 296 | socket.on("disconnect", (data) => { 297 | socket.rooms.forEach(room => { 298 | socket.to(room).emit("message", { 299 | system: true, 300 | message: `${socket.data.user.username} left the room` 301 | }); 302 | 303 | updateMembers(room); 304 | }); 305 | socket.leaveAll(); 306 | }); 307 | }); 308 | 309 | res.end(); 310 | 311 | }); -------------------------------------------------------------------------------- /src/pages/index.jsx: -------------------------------------------------------------------------------- 1 | import { useConnection } from 'context/connect' 2 | import { useRouter } from 'next/router'; 3 | import { useEffect, useState } from 'react'; 4 | import { motion, AnimatePresence } from "framer-motion" 5 | 6 | export default function Home() { 7 | const { connection } = useConnection(); 8 | const router = useRouter(); 9 | let [error, setError] = useState(null); 10 | 11 | const Login = event => { 12 | event.preventDefault(); 13 | const username = event.target.username.value; 14 | connection.emit('login', { username }); 15 | connection.on('login', data => { 16 | if (data.success) { 17 | router.push('/rooms'); 18 | } else { 19 | setError(data.error); 20 | } 21 | }); 22 | } 23 | 24 | useEffect(() => { 25 | if (connection) { 26 | connection.emit('fetchUser'); 27 | connection.on('user', data => { 28 | if (data !== null) { 29 | router.push('/rooms'); 30 | } 31 | }); 32 | } 33 | }, []); 34 | 35 | return <> 36 | 41 |
42 |
43 |
44 |
45 | Whats your name? 46 |
47 | {error &&
48 |

{error || "Something went wrong.."}

49 |
} 50 |
51 |
52 | 53 | 54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/pages/random/[id].jsx: -------------------------------------------------------------------------------- 1 | import { useConnection } from "context/connect"; 2 | import { useRouter } from "next/router"; 3 | import { useEffect, useState, useRef } from "react"; 4 | import { faCheckCircle, faCrown, faLink, faLock, faUser, faXmark } from '@fortawesome/free-solid-svg-icons' 5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 6 | import { motion, AnimatePresence } from "framer-motion" 7 | import cn from 'clsx'; 8 | import { animConfig, animConfigOther, mainConfig } from "config/config"; 9 | 10 | export default function Room() { 11 | const router = useRouter(); 12 | let [room, setRoom] = useState(null); 13 | let [members, setMembers] = useState([]); 14 | let [messages, setMessages] = useState([]); 15 | const { connection } = useConnection(); 16 | const { id } = router.query 17 | let [waiting, setWaiting] = useState(true); 18 | let [rooms, setRooms] = useState([]); 19 | let [error, setError] = useState(null); 20 | let [onroom, setOnroom] = useState(false); 21 | let [loaded, setLoaded] = useState(false); 22 | let [user, setUser] = useState(null); 23 | let [typping, setTypping] = useState([]); 24 | let [istypping, setIstypping] = useState(false); 25 | let [showMembers, setShowMembers] = useState(true); 26 | let [online, setOnline] = useState(0); 27 | 28 | const ref = useRef(); 29 | useEffect(() => { 30 | if (ref.current) { 31 | ref.current.scrollTop = ref.current.scrollHeight; 32 | } 33 | }, [messages]); 34 | 35 | const CreateRoom = event => { 36 | 37 | const name = id; 38 | const password = ""; 39 | const maxUsers = 2; 40 | 41 | connection.emit('createRoom', { name, password, maxUsers }); 42 | connection.on('createRoom', data => { 43 | const result = data; 44 | 45 | if (result.success) { 46 | router.push('/random/' + name); 47 | connection.emit('joinRoomStranger', { name }); 48 | } else { 49 | setError(data.message); 50 | } 51 | }); 52 | } 53 | 54 | useEffect(() => { 55 | if (connection) { 56 | connection.emit('fetchRooms'); 57 | connection.on('rooms', data => { 58 | setRooms(data.rooms); 59 | }); 60 | return () => { 61 | connection.off('rooms', data => { 62 | if (data.isLogged) { 63 | setUser(data.user); 64 | } 65 | setRooms(data.rooms); 66 | }); 67 | } 68 | } 69 | }, [router]); 70 | 71 | useEffect(() => { 72 | if (connection) { 73 | connection.emit('fetchUser'); 74 | connection.on('user', data => { 75 | if (data === null) { 76 | router.push('/'); 77 | } else { 78 | setUser(data); 79 | } 80 | }); 81 | 82 | return () => { 83 | connection.off('user', data => { 84 | if (data === null) { 85 | router.push('/'); 86 | } else { 87 | setUser(data); 88 | } 89 | }); 90 | } 91 | } 92 | }, [connection]); 93 | 94 | useEffect(() => { 95 | if(router.pathname == "/random"){ 96 | setOnroom(false) 97 | }else{ 98 | setOnroom(true) 99 | } 100 | 101 | if (!loaded){ 102 | joinrandom() 103 | setLoaded(true) 104 | } 105 | 106 | connection.off('UsersOnline').on('UsersOnline', data => { 107 | if (data.success) { 108 | setOnline(data.users); 109 | } else { 110 | } 111 | }); 112 | 113 | 114 | }, [router]); 115 | 116 | function makeid(length) { 117 | let result = ''; 118 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; 119 | const charactersLength = characters.length; 120 | let counter = 0; 121 | while (counter < length) { 122 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 123 | counter += 1; 124 | } 125 | return result; 126 | } 127 | 128 | function getRandomInt(max) { 129 | if(rooms.length == 1){ 130 | max = 0 131 | } 132 | return Math.floor(Math.random() * max); 133 | } 134 | 135 | async function joinrandom(){ 136 | if (connection.emit('joinRoomStranger', { id })){ 137 | router.push('/random/' + id); 138 | }else{ 139 | CreateRoom() 140 | } 141 | } 142 | 143 | useEffect(() => { 144 | if (connection) { 145 | connection.emit('fetchUser'); 146 | connection.on('user', data => { 147 | if (data === null) { 148 | router.push('/'); 149 | } 150 | }); 151 | 152 | return () => { 153 | connection.off('user', data => { 154 | if (data === null) { 155 | router.push('/'); 156 | } 157 | }); 158 | } 159 | } 160 | }, [connection]); 161 | useEffect(() => { 162 | if (connection) { 163 | connection.off('message').on('message', data => { 164 | setMessages(messages => [...messages, data]); 165 | }); 166 | 167 | connection.off('ClearMessages').on('ClearMessages', data => { 168 | const ss = messages.filter((s) => s.system) 169 | setMessages([{ 170 | "system": true, 171 | "message": "a stranger left the room" 172 | }]); 173 | }); 174 | 175 | return () => { 176 | connection.off('message', data => { 177 | setMessages(messages => [...messages, data]); 178 | }); 179 | } 180 | } 181 | 182 | 183 | }, [connection]); 184 | 185 | useEffect(() => { 186 | 187 | if (members.length < 2){ 188 | setMessages([{ 189 | "user": { 190 | "username": "Bot", 191 | "verified": true, 192 | }, 193 | "message": "Waiting stranger...", 194 | "date": new Date(), 195 | "self": false 196 | }]) 197 | }else{ 198 | const ss = messages.filter((s) => s.system) 199 | setMessages([{ 200 | "system": true, 201 | "message": "A stranger joined the room" 202 | }]); 203 | } 204 | 205 | }, [members, router]); 206 | 207 | useEffect(() => { 208 | 209 | connection?.off('IsTypping').on('IsTypping', data => { 210 | setIstypping(true) 211 | setTypping(data) 212 | if(!istypping){ 213 | setTimeout(function (){ 214 | setIstypping(false) 215 | }, 2000) 216 | } 217 | 218 | }); 219 | 220 | }, [istypping]); 221 | 222 | function typpingu(){ 223 | connection.emit('IsTypping'); 224 | } 225 | 226 | useEffect(() => { 227 | if (connection) { 228 | const fetchRoomListener = data => { 229 | if (!data.success) router.push('/rooms'); 230 | setRoom(data.data); 231 | } 232 | const roomMembersListener = data => { 233 | if (!data.success) router.push('/rooms'); 234 | setMembers(data.data); 235 | } 236 | 237 | connection.emit('roomMembers'); 238 | connection.on('roomMembers', roomMembersListener); 239 | 240 | connection.emit('fetchRoom'); 241 | connection.on('fetchRoom', fetchRoomListener); 242 | 243 | return () => { 244 | connection.off('roomMembers', roomMembersListener); 245 | connection.off('fetchRoom', fetchRoomListener); 246 | } 247 | } 248 | }, [connection, router]); 249 | 250 | const LeaveRoom = () => { 251 | connection.emit('leaveRoomStranger'); 252 | connection.on('leaveRoomStranger', data => { 253 | if (data.success) { 254 | router.push('/rooms'); 255 | } 256 | 257 | }); 258 | 259 | } 260 | 261 | /* IMAGES */ 262 | 263 | let [file, setFile] = useState(null); 264 | let [filetype, setFiletype] = useState([]); 265 | let [preview, setPreview] = useState([]); 266 | let [imgsrc, setImgsrc] = useState([]); 267 | 268 | const handleFiles = async (file) => { 269 | const selectedFile = file.target.files[0]; 270 | setFile(selectedFile) 271 | const url = window.URL.createObjectURL(selectedFile) || "" 272 | setFiletype(selectedFile.type) 273 | setPreview(url) 274 | } 275 | function resetFile(){ 276 | if (file){ 277 | setFile(null) 278 | setFiletype(null) 279 | } 280 | } 281 | function Image(props, type){ 282 | var blob = new Blob( [ props ], { type: type } ); 283 | var urlCreator = window.URL || window.webkitURL; 284 | var imageUrl = urlCreator.createObjectURL( blob ); 285 | return(imageUrl)} 286 | 287 | /* END IMAGES */ 288 | 289 | const dateNow = date => { 290 | const now = new Date(); 291 | const msgDate = new Date(date); 292 | if (now - msgDate < 1000 * 60) { 293 | if (Math.floor((now - msgDate) / 1000) === 1) { 294 | return Math.floor((now - msgDate) / 1000) + ' seconds ago'; 295 | } else { 296 | return 'now'; 297 | } 298 | } 299 | else if (now.getDate() === msgDate.getDate() && now.getMonth() === msgDate.getMonth() && now.getFullYear() === msgDate.getFullYear()) { 300 | const diff = now.getTime() - msgDate.getTime(); 301 | const minutes = Math.floor(diff / 1000 / 60); 302 | return `${minutes} minutes ago`; 303 | } 304 | else if (now.getDate() === msgDate.getDate() && now.getMonth() === msgDate.getMonth() && now.getFullYear() === msgDate.getFullYear()) { 305 | const diff = now.getTime() - msgDate.getTime(); 306 | const hours = Math.floor(diff / 1000 / 60 / 60); 307 | return `${hours} hours ago`; 308 | } 309 | else if (now.getMonth() === msgDate.getMonth() && now.getFullYear() === msgDate.getFullYear()) { 310 | const diff = now.getTime() - msgDate.getTime(); 311 | const days = Math.floor(diff / 1000 / 60 / 60 / 24); 312 | return `${days} days ago`; 313 | } 314 | else if (now.getFullYear() === msgDate.getFullYear()) { 315 | const diff = now.getTime() - msgDate.getTime(); 316 | const months = Math.floor(diff / 1000 / 60 / 60 / 24 / 30); 317 | return `${months} months ago`; 318 | } 319 | else { 320 | const diff = now.getTime() - msgDate.getTime(); 321 | const years = Math.floor(diff / 1000 / 60 / 60 / 24 / 30 / 12); 322 | return `${years} years ago`; 323 | } 324 | } 325 | return <> 326 | 327 |
328 |
329 |
330 |
331 | username 332 |
333 |

{members.filter(a=>a).length == 1 ?

Waiting stranger...

:

Anonymous Chat

}

334 |

{members.filter(a=>a).length == 1 ?

Searching stranger to talk...

:

This room is with a stranger.

}

335 |
336 |
337 |
338 | 343 | 346 |
347 |
348 |
349 |
350 | 351 |
354 | {messages.filter(Boolean).filter(el => { 355 | if (!el.system) { 356 | if (el.user) return true; 357 | if (el.message && el.message.length > 0) return true; 358 | 359 | return false; 360 | } else return true; 361 | }).map((message, index) => { 362 | if (message.system) { 363 | return
364 |

{message.message}

365 |
366 | } else { 367 | if (message.self) { 368 | return 371 |
372 | 373 | {!message.user.verified ?

Me

:

Me

} 374 |
375 | {message.file && } 376 |

{message.message}

377 |

{dateNow(message.date)}

378 |
379 |
380 | username 381 |
382 | } else { 383 | return 386 | username 387 |
388 | {message.user.verified ?

{message.user.username != "Bot" ? "Stranger" : "Bot"}

:

Stranger

} 389 |
390 | {message.file && } 391 |

{message.message}

392 |

{dateNow(message.date)}

393 |
394 |
395 | {members.length < 2 &&
} 396 |
397 | } 398 | } 399 | })} 400 |
401 |
402 | {members?.map(member => ( 403 |
{member?.username != user?.username && 404 |
405 | {istypping && typping?.user.username != user?.username && username} 406 | {istypping && typping?.user.username != user?.username &&

Stranger is typing...

} 407 |
} 408 |
409 | 410 | 411 | ))} 412 |
413 |
414 | 415 |
416 |
{ 417 | e.preventDefault(); 418 | const message = e.target.message.value; 419 | if(!message?.length > 0 && !file)return 420 | if (message && !file) { 421 | connection.emit('message', { message }); 422 | e.target.message.value = ''; 423 | }else{ 424 | connection.emit('message', { message, file: file, type: filetype }); 425 | e.target.message.value = ''; 426 | setFile(null) 427 | } 428 | }}> 429 | 430 | 431 |
432 | {file &&
433 | 434 | 435 |
} 436 |
437 |
438 | 439 |
440 | typpingu()} name="message" disabled={members.filter(a=>a).length == 1} type="text" className={members.filter(a=>a).length == 1 ? `bg-zinc-500/10 rounded-md w-full px-4 py-2 cursor-not-allowed text-white outline-none` : `bg-zinc-500/10 rounded-md w-full px-4 py-2 text-white outline-none`} autoComplete="off" placeholder={members.filter(a=>a).length == 1 ? "Waiting stranger..." : `Type a message...`} /> 441 | 447 | 452 |
453 |
454 |
455 |
456 | {showMembers &&
457 |
458 | {members?.map(member => ( 459 |
{member?.username != user?.username ? 460 |
461 |
462 | username 463 |
464 | {member?.verified ?

Stranger

:

Stranger

} 465 |
466 |
467 |
:
468 |
469 | username 470 |
{member?.verified ?

Me

:

Me

} 471 | 472 |
473 |
474 |
} 475 |
476 | 477 | 478 | ))} 479 |
480 |
} 481 |
482 | 483 | 484 | } -------------------------------------------------------------------------------- /src/pages/rooms/[id].jsx: -------------------------------------------------------------------------------- 1 | import { useConnection } from "context/connect"; 2 | import { useRouter } from "next/router"; 3 | import { useEffect, useState, useRef } from "react"; 4 | import { faCheckCircle, faCrown, faLink, faLock, faXmark, faUser } from '@fortawesome/free-solid-svg-icons' 5 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' 6 | import { motion, AnimatePresence } from "framer-motion" 7 | import cn from 'clsx'; 8 | import { animConfig, animConfigOther, mainConfig } from "config/config"; 9 | 10 | export default function Room() { 11 | const router = useRouter(); 12 | let [room, setRoom] = useState(null); 13 | let [members, setMembers] = useState([]); 14 | let [messages, setMessages] = useState([]); 15 | const { connection } = useConnection(); 16 | let [typping, setTypping] = useState([]); 17 | let [istypping, setIstypping] = useState(false); 18 | let [showMembers, setShowMembers] = useState(true); 19 | let [user, setUser] = useState(null); 20 | let [rooms, setRooms] = useState([]); 21 | let [membertyp, setMembertyp] = useState([]); 22 | const { id } = router.query 23 | 24 | useEffect(() => { 25 | if (connection) { 26 | connection.off('message').on('message', data => { 27 | setMessages(messages => [...messages, data]); 28 | }); 29 | 30 | return () => { 31 | connection.off('message', data => { 32 | setMessages(messages => [...messages, data]); 33 | }); 34 | } 35 | } 36 | }, [connection]); 37 | 38 | const ref = useRef(); 39 | useEffect(() => { 40 | if (ref.current) { 41 | ref.current.scrollTop = ref.current.scrollHeight; 42 | } 43 | }, [messages]); 44 | 45 | useEffect(() => { 46 | if (connection) { 47 | connection.emit('fetchRooms'); 48 | connection.on('rooms', data => { 49 | setRooms(data.rooms); 50 | }); 51 | return () => { 52 | connection.off('rooms', data => { 53 | if (data.isLogged) { 54 | setUser(data.user); 55 | } 56 | setRooms(data.rooms); 57 | }); 58 | } 59 | } 60 | }, [router]); 61 | 62 | useEffect(() => { 63 | if (connection) { 64 | const fetchRoomListener = data => { 65 | if (!data.success) router.push('/rooms'); 66 | setRoom(data.data); 67 | } 68 | const roomMembersListener = data => { 69 | if (!data.success) router.push('/rooms'); 70 | setMembers(data.data); 71 | } 72 | 73 | connection.emit('roomMembers'); 74 | connection.on('roomMembers', roomMembersListener); 75 | 76 | connection.emit('fetchRoom'); 77 | connection.on('fetchRoom', fetchRoomListener); 78 | 79 | return () => { 80 | connection.off('roomMembers', roomMembersListener); 81 | connection.off('fetchRoom', fetchRoomListener); 82 | } 83 | } 84 | }, [connection, router]); 85 | 86 | const LeaveRoom = () => { 87 | connection.emit('leaveRoom'); 88 | connection.on('leaveRoom', data => { 89 | if (data.success) { 90 | router.push('/rooms'); 91 | } 92 | }); 93 | } 94 | 95 | useEffect(() => { 96 | 97 | connection?.off('IsTypping').on('IsTypping', data => { 98 | setIstypping(true) 99 | setTypping(data) 100 | setMembertyp(data) 101 | if(!istypping){ 102 | setTimeout(function (){ 103 | setIstypping(false) 104 | }, 2000) 105 | } 106 | 107 | }); 108 | 109 | }, [istypping]); 110 | 111 | function typpingu(){ 112 | connection.emit('IsTypping'); 113 | } 114 | 115 | useEffect(() => { 116 | if (connection) { 117 | connection.emit('fetchUser'); 118 | connection.on('user', data => { 119 | if (data === null) { 120 | router.push('/'); 121 | } else { 122 | setUser(data); 123 | } 124 | }); 125 | 126 | return () => { 127 | connection.off('user', data => { 128 | if (data === null) { 129 | router.push('/'); 130 | } else { 131 | setUser(data); 132 | } 133 | }); 134 | } 135 | } 136 | }, [connection]); 137 | 138 | /* IMAGES */ 139 | 140 | let [file, setFile] = useState(null); 141 | let [filetype, setFiletype] = useState([]); 142 | let [preview, setPreview] = useState([]); 143 | let [imgsrc, setImgsrc] = useState([]); 144 | 145 | const handleFiles = async (file) => { 146 | const selectedFile = file.target.files[0]; 147 | setFile(selectedFile) 148 | const url = window.URL.createObjectURL(selectedFile) || "" 149 | setFiletype(selectedFile.type) 150 | setPreview(url) 151 | } 152 | function resetFile(){ 153 | if (file){ 154 | setFile(null) 155 | setFiletype(null) 156 | } 157 | } 158 | function Image(props, type){ 159 | var blob = new Blob( [ props ], { type: type } ); 160 | var urlCreator = window.URL || window.webkitURL; 161 | var imageUrl = urlCreator.createObjectURL( blob ); 162 | return(imageUrl)} 163 | 164 | /* END IMAGES */ 165 | 166 | 167 | 168 | 169 | const dateNow = date => { 170 | const now = new Date(); 171 | const msgDate = new Date(date); 172 | if (now - msgDate < 1000 * 60) { 173 | if (Math.floor((now - msgDate) / 1000) === 1) { 174 | return Math.floor((now - msgDate) / 1000) + ' seconds ago'; 175 | } else { 176 | return 'now'; 177 | } 178 | } 179 | else if (now.getDate() === msgDate.getDate() && now.getMonth() === msgDate.getMonth() && now.getFullYear() === msgDate.getFullYear()) { 180 | const diff = now.getTime() - msgDate.getTime(); 181 | const minutes = Math.floor(diff / 1000 / 60); 182 | return `${minutes} minutes ago`; 183 | } 184 | else if (now.getDate() === msgDate.getDate() && now.getMonth() === msgDate.getMonth() && now.getFullYear() === msgDate.getFullYear()) { 185 | const diff = now.getTime() - msgDate.getTime(); 186 | const hours = Math.floor(diff / 1000 / 60 / 60); 187 | return `${hours} hours ago`; 188 | } 189 | else if (now.getMonth() === msgDate.getMonth() && now.getFullYear() === msgDate.getFullYear()) { 190 | const diff = now.getTime() - msgDate.getTime(); 191 | const days = Math.floor(diff / 1000 / 60 / 60 / 24); 192 | return `${days} days ago`; 193 | } 194 | else if (now.getFullYear() === msgDate.getFullYear()) { 195 | const diff = now.getTime() - msgDate.getTime(); 196 | const months = Math.floor(diff / 1000 / 60 / 60 / 24 / 30); 197 | return `${months} months ago`; 198 | } 199 | else { 200 | const diff = now.getTime() - msgDate.getTime(); 201 | const years = Math.floor(diff / 1000 / 60 / 60 / 24 / 30 / 12); 202 | return `${years} years ago`; 203 | } 204 | } 205 | return <> 206 | 207 |
208 |
209 |
210 |
211 | username 212 |
213 |

{room?.name} {room?.password && }

214 |

{members?.length} {members?.length != 1 ? "members" : "member"} in chat

215 |
216 |
217 |
218 | 223 | 226 | 227 |
228 |
229 |
230 |
231 | 232 |
233 | {messages.filter(Boolean).filter(el => { 234 | if (!el.system) { 235 | if (el.user) return true; 236 | if (el.message && el.message.length > 0) return true; 237 | 238 | return false; 239 | } else return true; 240 | }).map((message, index) => { 241 | if (message.system) { 242 | return
243 |

{message.message}

244 |
245 | } else { 246 | if (message.self) { 247 | return 250 |
251 | 252 | {!message.user.verified ?

{message.user.username}

:

{message.user.username}

} 253 |
254 | {message.file && } 255 |

{message.message}

256 |

{dateNow(message.date)}

257 |
258 | 259 |
260 | username 261 |
262 | } else { 263 | return 266 | username 267 |
268 | {!message.user.verified ?

{message.user.username}

:

{message.user.username}

} 269 |
270 | {message.file && } 271 |

{message.message}

272 |

{dateNow(message.date)}

273 |
274 |
275 |
276 | } 277 | } 278 | })} 279 |
280 |
281 | 282 |
{membertyp?.username != user?.username && 283 |
284 | {istypping && typping?.user.username != user?.username && username} 285 | {istypping && typping?.user.username != user?.username &&

{typping?.user.username} is typing...

} 286 |
} 287 |
288 |
289 |
290 | 291 |
292 |
{ 293 | e.preventDefault(); 294 | const message = e.target.message.value; 295 | if(!message?.length > 0 && !file)return 296 | if (message && !file) { 297 | connection.emit('message', { message }); 298 | e.target.message.value = ''; 299 | }else{ 300 | connection.emit('message', { message, file: file, type: filetype }); 301 | e.target.message.value = ''; 302 | setFile(null) 303 | } 304 | }}> 305 | 306 | 307 |
308 | {file &&
309 | 310 | 311 |
} 312 |
313 |
314 | 315 |
316 | typpingu()} name="message" type="text" className="bg-zinc-500/10 rounded-md w-full px-4 py-2 text-white outline-none" autoComplete="off" placeholder="Type a message..." /> 317 | 323 | 328 |
329 |
330 |
331 |
332 | {showMembers &&
333 | 334 |
335 | {members?.map(member => ( 336 |
337 |
338 | username 339 |
340 |

{member?.username} {member?.verified && }

341 | {room?.owner?.username === member?.username && <> 342 |
343 |

344 |
345 | } 346 | 347 |
348 |
349 |
350 | ))} 351 |
352 |
} 353 |
354 | 355 | } -------------------------------------------------------------------------------- /src/pages/rooms/create.jsx: -------------------------------------------------------------------------------- 1 | import { useConnection } from 'context/connect' 2 | import { useRouter } from 'next/router'; 3 | import { useEffect, useState } from 'react'; 4 | import { motion, AnimatePresence } from "framer-motion" 5 | export default function Home() { 6 | const { connection } = useConnection(); 7 | const router = useRouter(); 8 | let [error, setError] = useState(null); 9 | 10 | 11 | /* max users permitted variable */ 12 | const maxusers = 30; 13 | 14 | const CreateRoom = event => { 15 | event.preventDefault(); 16 | var maxus = 0 17 | if(event.target.maxUsers.value > 1 && event.target.maxUsers.value <= maxusers){ 18 | maxus = event.target.maxUsers.value 19 | }else{ 20 | maxus = maxusers 21 | } 22 | 23 | const name = event.target.name.value; 24 | const password = event.target.password.value; 25 | const maxUsers = maxus || maxusers; 26 | 27 | connection.emit('createRoom', { name, password, maxUsers }); 28 | connection.on('createRoom', data => { 29 | const result = data; 30 | if (result.success) { 31 | router.push('/rooms/' + result.data.id); 32 | } else { 33 | setError(data.message); 34 | } 35 | }); 36 | } 37 | 38 | useEffect(() => { 39 | if (connection) { 40 | connection.emit('fetchUser'); 41 | connection.on('user', data => { 42 | if (data === null) { 43 | router.push('/'); 44 | } 45 | }); 46 | 47 | return () => { 48 | connection.off('user', data => { 49 | if (data === null) { 50 | router.push('/'); 51 | } 52 | }); 53 | } 54 | } 55 | }, [connection]); 56 | 57 | return <> 58 | 59 | 64 |
65 |
66 |
67 |
68 | Create New Room 69 |
70 | {error &&
71 |

{error || "Something went wrong.."}

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 | -------------------------------------------------------------------------------- /src/pages/rooms/index.jsx: -------------------------------------------------------------------------------- 1 | import { useConnection } from "context/connect"; 2 | import { useRouter } from "next/router"; 3 | import { useEffect, useState } from "react"; 4 | 5 | export default function Rooms() { 6 | return <> 7 |
8 |
9 |

Choose a room on the left or start chatting with stranger!

10 |
11 |
12 | 13 | } -------------------------------------------------------------------------------- /src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | 6 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap'); 7 | @import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css'); 8 | 9 | * { 10 | font-family: 'Poppins', sans-serif; 11 | } 12 | 13 | body { 14 | background-color: #111214; 15 | overflow-x: hidden; 16 | overflow-y: hidden; 17 | } 18 | 19 | /* width */ 20 | ::-webkit-scrollbar { 21 | width: 10px; 22 | } 23 | 24 | /* Track */ 25 | ::-webkit-scrollbar-track { 26 | border-radius: 10px; 27 | } 28 | 29 | /* Handle */ 30 | ::-webkit-scrollbar-thumb { 31 | background: rgb(24, 24, 24); 32 | border-radius: 10px; 33 | } 34 | 35 | /* loading */ 36 | 37 | .loading { 38 | display: inline-block; 39 | position: relative; 40 | width: 64px; 41 | height: 64px; 42 | } 43 | .loading div { 44 | position: absolute; 45 | background: #fff; 46 | opacity: 1; 47 | border-radius: 50%; 48 | animation: loading 1.4s cubic-bezier(0, 0.2, 0.8, 1) infinite; 49 | } 50 | .loading div:nth-child(2) { 51 | animation-delay: -.7s; 52 | } 53 | @keyframes loading { 54 | 0% { 55 | top: 28px; 56 | left: 28px; 57 | width: 0; 58 | height: 0; 59 | opacity: 1; 60 | } 61 | 100% { 62 | top: -1px; 63 | left: -1px; 64 | width: 58px; 65 | height: 58px; 66 | opacity: 0; 67 | } 68 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './src/**/*.{js,jsx,ts,tsx}' 5 | ], 6 | theme: { 7 | extend: { 8 | colors: { 9 | 'dark-1': '#111214', 10 | 'dark-2': '#0d0e11', 11 | 'dark-3': '#191c21', 12 | }, 13 | width: { 14 | 'fill-available': '-webkit-fill-available', // Añade la propiedad width: -webkit-fill-available; a la clase 'fill-available' 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | } 20 | --------------------------------------------------------------------------------