├── .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 |
51 |
52 |
53 |
54 |
55 |
56 | Table of Contents
57 |
58 |
59 | About The Project
60 |
63 |
64 |
65 | Getting Started
66 |
70 |
71 | Usage
72 | Roadmap
73 | Contributing
74 | License
75 | Contact
76 | Acknowledgments
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 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
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 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
{online}
257 |
258 | router.push('/rooms/create')} className="m-2 w-full rounded-md px-4 py-2 text-gray-300 bg-zinc-500/10 hover:bg-zinc-500/20 focus:outline-none focus:ring-2 focus:ring-gray-700 focus:ring-opacity-50 transition-all duration-200 flex flex-row items-center justify-center">Create
259 | joinrandom()} className="m-2 w-full rounded-md px-4 py-2 text-gray-300 bg-zinc-500/10 hover:bg-zinc-500/20 focus:outline-none focus:ring-2 focus:ring-gray-700 focus:ring-opacity-50 transition-all duration-200 flex flex-row items-center justify-center">Random
260 |
chatrandom()} className="m-2 w-full rounded-md px-4 py-2 text-gray-300 bg-zinc-500/10 hover:bg-zinc-500/20 focus:outline-none focus:ring-2 focus:ring-gray-700 focus:ring-opacity-50 transition-all duration-200 flex flex-row items-center justify-center">Chat with stranger
261 |
262 |
263 | {rooms.map(room => {
264 | return
265 | {room.name != "random" &&
JoinRoom(room)}>
266 |
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 |
283 | {!user?.verified ?
{user?.username} :
{user?.username} }
284 |
285 |
286 |
287 |
288 | {!onroom &&
289 |
290 |
{online}
291 |
292 | router.push('/rooms/create')} className="m-2 w-full rounded-md px-4 py-2 text-gray-300 bg-zinc-500/10 hover:bg-zinc-500/20 focus:outline-none focus:ring-2 focus:ring-gray-700 focus:ring-opacity-50 transition-all duration-200 flex flex-row items-center justify-center">Create
293 | joinrandom()} className="m-2 w-full rounded-md px-4 py-2 text-gray-300 bg-zinc-500/10 hover:bg-zinc-500/20 focus:outline-none focus:ring-2 focus:ring-gray-700 focus:ring-opacity-50 transition-all duration-200 flex flex-row items-center justify-center">Random
294 |
chatrandom()} className="m-2 w-full rounded-md px-4 py-2 text-gray-300 bg-zinc-500/10 hover:bg-zinc-500/20 focus:outline-none focus:ring-2 focus:ring-gray-700 focus:ring-opacity-50 transition-all duration-200 flex flex-row items-center justify-center">Chat with stranger
295 |
296 |
297 | {rooms.map(room => {
298 | return
299 | { room.name != "random" &&
JoinRoom(room)}>
300 |
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 |
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 |
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 |
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 |
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 |
339 |
340 |
341 |
342 |
343 |
setShowMembers(!showMembers)} className="bg-zinc-500/10 hidden 2xl:flex hover:bg-zinc-500/20 rounded-full p-2 mr-2">
344 |
345 |
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 |
381 |
382 | } else {
383 | return
386 |
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 &&
}
406 | {istypping && typping?.user.username != user?.username &&
Stranger is typing...
}
407 |
}
408 |
409 |
410 |
411 | ))}
412 |
413 |
414 |
415 |
455 |
456 | {showMembers &&
457 |
458 | {members?.map(member => (
459 |
{member?.username != user?.username ?
460 |
461 |
462 |
463 |
464 | {member?.verified ?
Stranger
:
Stranger
}
465 |
466 |
467 |
:
468 |
469 |
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 |
212 |
213 |
{room?.name} {room?.password && }
214 |
{members?.length} {members?.length != 1 ? "members" : "member"} in chat
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
setShowMembers(!showMembers)} className="bg-zinc-500/10 hidden 2xl:flex hover:bg-zinc-500/20 rounded-full p-2 mr-2">
224 |
225 |
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 |
261 |
262 | } else {
263 | return
266 |
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 &&
}
285 | {istypping && typping?.user.username != user?.username &&
{typping?.user.username} is typing...
}
286 |
}
287 |
288 |
289 |
290 |
291 |
331 |
332 | {showMembers &&
333 |
334 |
335 | {members?.map(member => (
336 |
337 |
338 |
339 |
340 |
{member?.username} {member?.verified && }
341 | {room?.owner?.username === member?.username && <>
342 |
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 |
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 |
--------------------------------------------------------------------------------