├── LICENSE ├── README.md ├── jsconfig.json ├── next.config.js ├── package.json ├── postcss.config.js ├── screenshots ├── create_room.png ├── default_room.png ├── login_page.png └── protected_room.png ├── src ├── components │ └── Sidebar.jsx ├── context │ └── connect.js ├── libraries │ └── withSession.js ├── pages │ ├── _app.jsx │ ├── api │ │ └── socket.js │ ├── index.jsx │ └── rooms │ │ ├── [id].jsx │ │ ├── create.jsx │ │ └── index.jsx └── styles │ └── globals.css └── tailwind.config.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 clqu 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 | # Chat App
2 | 3 | Login Page | Create Room | Default Room | Protected Room 4 | :-------------------------:|:-------------------------:|:-------------------------:|:-------------------------: 5 | ![img](https://raw.githubusercontent.com/clqu/nextjs-chat-application/main/screenshots/login_page.png) | ![img](https://raw.githubusercontent.com/clqu/nextjs-chat-application/main/screenshots/create_room.png) | ![img](https://raw.githubusercontent.com/clqu/nextjs-chat-application/main/screenshots/default_room.png) | ![img](https://raw.githubusercontent.com/clqu/nextjs-chat-application/main/screenshots/protected_room.png) 6 | 7 | #### [Demo](https://chatapp.clqu.repl.co) 8 | 9 | 10 | 11 | ## Getting Started 12 | 13 | ### 🛠 Development Server 14 | 15 | ```bash 16 | npm install --s --f && npm run dev 17 | # or 18 | yarn install && yarn dev 19 | ``` 20 | ### 🛠 Production Server 21 | ```bash 22 | npm install --s --f && npm run build && npm run start 23 | # or 24 | yarn install && yarn build && yarn start 25 | ``` 26 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 27 | You can start editing the page by modifying `src/pages/index.js`. The page auto-updates as you edit the file. 28 | 29 | 30 | ## Learn More 31 | 32 | To learn more about Next.js, take a look at the following resources: 33 | 34 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 35 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 36 | 37 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 38 | 39 | ## Deploy on Vercel 40 | 41 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new/import?s=https://github.com/clqu/clqu.live&utm_source=clqu.live) from the clqu. 42 | 43 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 44 | 45 | ## ⭐ Star 46 | - Don't forget to star this repo for support :) 47 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./src" 4 | }, 5 | } -------------------------------------------------------------------------------- /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 | "@headlessui/react": "^1.6.6", 13 | "next": "12.2.5", 14 | "next-iron-session": "^4.2.0", 15 | "react": "18.2.0", 16 | "react-dom": "18.2.0", 17 | "socket.io": "^4.5.1", 18 | "socket.io-client": "^4.5.1" 19 | }, 20 | "devDependencies": { 21 | "autoprefixer": "^10.4.8", 22 | "eslint": "8.22.0", 23 | "eslint-config-next": "12.2.5", 24 | "postcss": "^8.4.16", 25 | "tailwindcss": "^3.1.8" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /screenshots/create_room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clqu/nextjs-chat-application/519373ffdc1a486eb210530734609a1f8b46d342/screenshots/create_room.png -------------------------------------------------------------------------------- /screenshots/default_room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clqu/nextjs-chat-application/519373ffdc1a486eb210530734609a1f8b46d342/screenshots/default_room.png -------------------------------------------------------------------------------- /screenshots/login_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clqu/nextjs-chat-application/519373ffdc1a486eb210530734609a1f8b46d342/screenshots/login_page.png -------------------------------------------------------------------------------- /screenshots/protected_room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clqu/nextjs-chat-application/519373ffdc1a486eb210530734609a1f8b46d342/screenshots/protected_room.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 | 6 | export default function Sidebar() { 7 | const router = useRouter(); 8 | let [rooms, setRooms] = useState([]); 9 | let [user, setUser] = useState(null); 10 | let [isOpen, setIsOpen] = useState(false); 11 | let [protectedRoom, setProtected] = useState(false); 12 | let [password, setPassword] = useState(''); 13 | const { connection } = useConnection(); 14 | 15 | useEffect(() => { 16 | if (connection) { 17 | connection.emit('fetchUser'); 18 | connection.on('user', data => { 19 | if (data === null) { 20 | router.push('/'); 21 | } else { 22 | setUser(data); 23 | } 24 | }); 25 | 26 | return () => { 27 | connection.off('user', data => { 28 | if (data === null) { 29 | router.push('/'); 30 | } else { 31 | setUser(data); 32 | } 33 | }); 34 | } 35 | } 36 | }, [connection]); 37 | 38 | useEffect(() => { 39 | if (connection) { 40 | connection.emit('fetchRooms'); 41 | connection.on('rooms', data => { 42 | setRooms(data.rooms); 43 | }); 44 | 45 | return () => { 46 | connection.off('rooms', data => { 47 | if (data.isLogged) { 48 | setUser(data.user); 49 | } 50 | setRooms(data.rooms); 51 | }); 52 | } 53 | } 54 | }, []); 55 | 56 | const JoinRoom = room => { 57 | const { id, passwordProtected } = room; 58 | if (passwordProtected) { 59 | setIsOpen(true); 60 | setProtected(room); 61 | 62 | if (password) { 63 | connection.emit('joinRoom', { id, password }); 64 | } 65 | 66 | } else { 67 | connection.emit('joinRoom', { id }); 68 | } 69 | 70 | connection.off('joinRoom').on('joinRoom', data => { 71 | if (data.success) { 72 | setIsOpen(false); 73 | setPassword(''); 74 | router.push('/rooms/' + id); 75 | } else { 76 | if (data?.alreadyIn) { 77 | router.push('/rooms/' + id); 78 | } else { 79 | alert(data.error) 80 | } 81 | } 82 | }); 83 | } 84 | 85 | return <> 86 | 87 | 88 | { 89 | setIsOpen(false); 90 | setPassword(''); 91 | }}> 92 | 101 |
102 | 103 | 104 |
105 |
106 | 115 | 116 | 120 | Password Protected Room 121 | 122 |
{ 123 | e.preventDefault(); 124 | JoinRoom(protectedRoom); 125 | }}> 126 |
127 |

128 | This room is password protected. Please enter the password to join. 129 |

130 | 131 | setPassword(e.target.value)} 136 | /> 137 |
138 | 139 |
140 | 146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | 155 |
156 |
157 | Rooms 158 | 159 |
160 |
161 | {rooms.map(room => { 162 | return
JoinRoom(room)}> 163 | username 164 |
165 | {room.name} 166 | Created by {room?.owner?.username.split(0, 5) + '...'} 167 |
168 |
169 | {room.passwordProtected && 170 | 171 | 172 | } 173 | {room.users || 0}/{room.maxUsers} 174 |
175 |
176 | })} 177 |
178 | 179 |
180 |
181 | username 182 | {user?.username} 183 |
184 |
185 |
186 | 187 | } -------------------------------------------------------------------------------- /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 | 7 | function MyApp({ Component, pageProps }) { 8 | const router = useRouter(); 9 | return 10 | 11 | Chat App 12 | 13 | 14 | 15 | 16 | 17 | {router.pathname.includes('rooms') ? ( 18 |
19 | 20 |
21 | 22 |
23 |
24 | ) : } 25 |
26 | } 27 | 28 | export default MyApp; 29 | -------------------------------------------------------------------------------- /src/pages/api/socket.js: -------------------------------------------------------------------------------- 1 | import { Server } from "socket.io"; 2 | 3 | export default (async (req, res) => { 4 | if (res.socket.server.io) { 5 | res.end(); 6 | return; 7 | } 8 | 9 | const io = new Server(res.socket.server, { 10 | pingInterval: 10000, 11 | pingTimeout: 5000 12 | }); 13 | res.socket.server.io = io; 14 | 15 | io.use((socket, next) => { 16 | setInterval(() => { 17 | socket.emit("ping", "pong"); 18 | }, 1000); 19 | next(); 20 | }); 21 | 22 | io.on("connection", (socket) => { 23 | socket.join('global'); 24 | 25 | socket.on("login", async (data) => { 26 | const { username } = data; 27 | 28 | const allSockets = await io.fetchSockets(); 29 | const userSockets = allSockets.filter((s) => s?.data?.user?.username === username); 30 | 31 | if (userSockets.length > 0) return socket.emit("login", { error: "Username already taken" }); 32 | 33 | const user = { 34 | username 35 | }; 36 | 37 | socket.data.user = user; 38 | socket.emit("login", { 39 | success: true, 40 | data: user 41 | }); 42 | }); 43 | 44 | socket.on("fetchUser", () => { 45 | const user = socket.data.user; 46 | if (user) { 47 | socket.emit("user", user); 48 | } else { 49 | socket.emit("user", null); 50 | } 51 | }) 52 | 53 | socket.on("fetchRooms", () => { 54 | setInterval(async () => { 55 | const rooms = io.sockets.adapter.rooms; 56 | const allRooms = (await Promise.all(Object.keys(rooms).map(async (room) => { 57 | const sockets = await io.in(room).fetchSockets(); 58 | const users = sockets.map((s) => s.data.user); 59 | return { 60 | id: room, 61 | name: rooms[room]?.name, 62 | owner: rooms[room]?.owner, 63 | passwordProtected: rooms[room]?.password ? true : false, 64 | maxUsers: rooms[room]?.maxUsers, 65 | users: users.length 66 | }; 67 | }))).filter((r) => r.name !== 'global'); 68 | socket.emit("rooms", { 69 | isLogged: socket.data?.user !== undefined ? true : false, 70 | user: socket.data?.user, 71 | rooms: allRooms 72 | }); 73 | }, 1000); 74 | }); 75 | 76 | socket.on("createRoom", data => { 77 | const { name, password } = data; 78 | if (!name) return socket.emit("createRoom", { success: false, error: "Name is required" }); 79 | if (io.sockets.adapter.rooms[name]) return socket.emit("createRoom", { success: false, error: "Room already exists" }); 80 | let room = { 81 | id: Math.random().toString(36).substring(2, 9), 82 | name: name.replace(/[^a-zA-Z0-9 ]/g, ""), 83 | owner: socket.data.user, 84 | users: 1, 85 | maxUsers: 10 86 | }; 87 | 88 | if (password) room.password = password; 89 | 90 | 91 | io.sockets.adapter.rooms[room.id] = room; 92 | 93 | socket.rooms.forEach((user_room) => { 94 | socket.leave(user_room); 95 | updateMembers(user_room); 96 | socket.to(user_room).emit("message", { 97 | system: true, 98 | message: `${socket.data.user.username} left the room` 99 | }); 100 | }); 101 | socket.join(room.id); 102 | socket.emit("createRoom", { success: true, data: room }); 103 | }) 104 | 105 | socket.on("joinRoom", async data => { 106 | const { id, password } = data; 107 | if (!id) return socket.emit("joinRoom", { success: false, error: "Room id is required" }); 108 | if (!io.sockets.adapter.rooms[id]) return socket.emit("joinRoom", { success: false, error: "Room not found" }); 109 | 110 | const room = io.sockets.adapter.rooms[id]; 111 | if (room.password && room.password !== password) return socket.emit("joinRoom", { success: false, error: "Wrong password" }); 112 | const sockets = await io.in(id).fetchSockets(); 113 | if (sockets.length >= room.maxUsers) return socket.emit("joinRoom", { success: false, error: "Room is full" }); 114 | 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" }); 115 | 116 | socket.rooms.forEach((user_room) => { 117 | socket.leave(user_room); 118 | updateMembers(user_room); 119 | socket.to(user_room).emit("message", { 120 | system: true, 121 | message: `${socket.data.user.username} left the room` 122 | }); 123 | }); 124 | 125 | socket.join(id); 126 | 127 | updateMembers(id); 128 | socket.emit("joinRoom", { success: true, data: room }); 129 | socket.to(id).emit("message", { 130 | system: true, 131 | message: `${socket.data.user.username} joined the room` 132 | }); 133 | }); 134 | 135 | socket.on("leaveRoom", async () => { 136 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 137 | if (!room) return socket.emit("leaveRoom", { success: false, error: "You are not in a room" }); 138 | socket.leaveAll(); 139 | socket.join("global"); 140 | socket.emit("leaveRoom", { success: true }); 141 | 142 | updateMembers(room); 143 | socket.to(room).emit("message", { 144 | system: true, 145 | message: `${socket.data.user.username} left the room` 146 | }); 147 | }); 148 | 149 | socket.on("roomMembers", async () => { 150 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 151 | if (!room) return socket.emit("roomMembers", { success: false, error: "You are not in a room" }); 152 | 153 | updateMembers(room); 154 | }); 155 | 156 | function updateMembers(room) { 157 | io.in(room).fetchSockets().then(sockets => { 158 | const members = sockets.map(socket => socket.data.user); 159 | if (members.length > 0) { 160 | io.in(room).emit("roomMembers", { success: true, data: members }); 161 | } else { 162 | delete io.sockets.adapter.rooms[room]; 163 | } 164 | }); 165 | } 166 | 167 | socket.on("message", async data => { 168 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 169 | if (!room) return; 170 | 171 | const message = { 172 | user: socket.data.user, 173 | message: data.message, 174 | date: new Date() 175 | }; 176 | 177 | const sockets = await io.in(room).fetchSockets(); 178 | sockets.forEach(s => { 179 | s.emit("message", { 180 | ...message, 181 | self: s.id === socket.id 182 | }); 183 | }); 184 | }); 185 | 186 | socket.on("fetchRoom", async () => { 187 | const room = Array.from(socket.rooms).find(room => room !== socket.id); 188 | if (!room) return socket.emit("fetchRoom", { success: false, error: "You are not in a room" }); 189 | 190 | socket.emit("fetchRoom", { success: true, data: io.sockets.adapter.rooms[room] }); 191 | }); 192 | 193 | 194 | 195 | socket.on("disconnect", (data) => { 196 | socket.rooms.forEach(room => { 197 | socket.to(room).emit("message", { 198 | system: true, 199 | message: `${socket.data.user.username} left the room` 200 | }); 201 | 202 | updateMembers(room); 203 | }); 204 | socket.leaveAll(); 205 | }); 206 | }); 207 | 208 | res.end(); 209 | 210 | }); -------------------------------------------------------------------------------- /src/pages/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 Home() { 6 | const { connection } = useConnection(); 7 | const router = useRouter(); 8 | let [error, setError] = useState(null); 9 | 10 | const Login = event => { 11 | event.preventDefault(); 12 | const username = event.target.username.value; 13 | connection.emit('login', { username }); 14 | connection.on('login', data => { 15 | if (data.success) { 16 | router.push('/rooms'); 17 | } else { 18 | setError(data.error); 19 | } 20 | }); 21 | } 22 | 23 | useEffect(() => { 24 | if (connection) { 25 | connection.emit('fetchUser'); 26 | connection.on('user', data => { 27 | if (data !== null) { 28 | router.push('/rooms'); 29 | } 30 | }); 31 | } 32 | }, []); 33 | 34 | return <> 35 |
36 |
37 |
38 |
39 | Login 40 |
41 | {error &&
42 |

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

43 |
} 44 |
45 |
46 | 47 | 48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/pages/rooms/[id].jsx: -------------------------------------------------------------------------------- 1 | import { useConnection } from "context/connect"; 2 | import { useRouter } from "next/router"; 3 | import { useEffect, useState } from "react"; 4 | 5 | export default function Room() { 6 | const router = useRouter(); 7 | let [room, setRoom] = useState(null); 8 | let [members, setMembers] = useState([]); 9 | let [messages, setMessages] = useState([]); 10 | const { connection } = useConnection(); 11 | 12 | useEffect(() => { 13 | if (connection) { 14 | connection.off('message').on('message', data => { 15 | setMessages(messages => [...messages, data]); 16 | }); 17 | 18 | return () => { 19 | connection.off('message', data => { 20 | setMessages(messages => [...messages, data]); 21 | }); 22 | } 23 | } 24 | }, [connection]); 25 | 26 | useEffect(() => { 27 | if (connection) { 28 | const fetchRoomListener = data => { 29 | if (!data.success) router.push('/rooms'); 30 | setRoom(data.data); 31 | } 32 | const roomMembersListener = data => { 33 | if (!data.success) router.push('/rooms'); 34 | setMembers(data.data); 35 | } 36 | 37 | connection.emit('roomMembers'); 38 | connection.on('roomMembers', roomMembersListener); 39 | 40 | connection.emit('fetchRoom'); 41 | connection.on('fetchRoom', fetchRoomListener); 42 | 43 | return () => { 44 | connection.off('roomMembers', roomMembersListener); 45 | connection.off('fetchRoom', fetchRoomListener); 46 | } 47 | } 48 | }, [connection, router]); 49 | 50 | const LeaveRoom = () => { 51 | connection.emit('leaveRoom'); 52 | connection.on('leaveRoom', data => { 53 | if (data.success) { 54 | router.push('/rooms'); 55 | } 56 | }); 57 | } 58 | 59 | const dateNow = date => { 60 | const now = new Date(); 61 | const msgDate = new Date(date); 62 | if (now - msgDate < 1000 * 60) { 63 | if (Math.floor((now - msgDate) / 1000) === 1) { 64 | return Math.floor((now - msgDate) / 1000) + ' seconds ago'; 65 | } else { 66 | return 'now'; 67 | } 68 | } 69 | else if (now.getDate() === msgDate.getDate() && now.getMonth() === msgDate.getMonth() && now.getFullYear() === msgDate.getFullYear()) { 70 | const diff = now.getTime() - msgDate.getTime(); 71 | const minutes = Math.floor(diff / 1000 / 60); 72 | return `${minutes} minutes ago`; 73 | } 74 | else if (now.getDate() === msgDate.getDate() && now.getMonth() === msgDate.getMonth() && now.getFullYear() === msgDate.getFullYear()) { 75 | const diff = now.getTime() - msgDate.getTime(); 76 | const hours = Math.floor(diff / 1000 / 60 / 60); 77 | return `${hours} hours ago`; 78 | } 79 | else if (now.getMonth() === msgDate.getMonth() && now.getFullYear() === msgDate.getFullYear()) { 80 | const diff = now.getTime() - msgDate.getTime(); 81 | const days = Math.floor(diff / 1000 / 60 / 60 / 24); 82 | return `${days} days ago`; 83 | } 84 | else if (now.getFullYear() === msgDate.getFullYear()) { 85 | const diff = now.getTime() - msgDate.getTime(); 86 | const months = Math.floor(diff / 1000 / 60 / 60 / 24 / 30); 87 | return `${months} months ago`; 88 | } 89 | else { 90 | const diff = now.getTime() - msgDate.getTime(); 91 | const years = Math.floor(diff / 1000 / 60 / 60 / 24 / 30 / 12); 92 | return `${years} years ago`; 93 | } 94 | } 95 | 96 | return <> 97 |
98 |
99 |
100 |
101 | username 102 |
103 |

{room?.name}

104 |

{members?.length} members

105 |
106 |
107 |
108 | 113 |
114 |
115 |
116 |
117 | {messages.filter(Boolean).filter(el => { 118 | if (!el.system) { 119 | if (el.user) return true; 120 | if (el.message && el.message.length > 0) return true; 121 | 122 | return false; 123 | } else return true; 124 | }).map((message, index) => { 125 | if (message.system) { 126 | return
127 |

{message.message}

128 |
129 | } else { 130 | if (message.self) { 131 | return
132 |
133 |

{message.user.username}

134 |
135 |

{message.message}

136 |
137 |

{dateNow(message.date)}

138 |
139 | username 140 |
141 | } else { 142 | return
143 | username 144 |
145 |

{message.user.username}

146 |
147 |

{message.message}

148 |
149 |

{dateNow(message.date)}

150 |
151 |
152 | } 153 | } 154 | })} 155 |
156 |
157 | 158 |
159 |
{ 160 | e.preventDefault(); 161 | const message = e.target.message.value; 162 | if (message) { 163 | connection.emit('message', { message }); 164 | e.target.message.value = ''; 165 | } 166 | }}> 167 |
168 | 169 | 174 |
175 |
176 |
177 |
178 |
179 |
180 | {members?.map(member => ( 181 |
182 |
183 | username 184 |
185 |

{member?.username}

186 | {room?.owner?.username === member?.username && <> 187 |
188 |

Owner

189 |
190 | } 191 | 192 |
193 |
194 |
195 | ))} 196 |
197 |
198 |
199 | 200 | } -------------------------------------------------------------------------------- /src/pages/rooms/create.jsx: -------------------------------------------------------------------------------- 1 | import { useConnection } from 'context/connect' 2 | import { useRouter } from 'next/router'; 3 | import { useEffect, useState } from 'react'; 4 | 5 | export default function Home() { 6 | const { connection } = useConnection(); 7 | const router = useRouter(); 8 | let [error, setError] = useState(null); 9 | 10 | const CreateRoom = event => { 11 | event.preventDefault(); 12 | const name = event.target.name.value; 13 | const password = event.target.password.value; 14 | connection.emit('createRoom', { name, password }); 15 | connection.on('createRoom', data => { 16 | const result = data; 17 | 18 | if (result.success) { 19 | router.push('/rooms/' + result.data.id); 20 | } else { 21 | setError(data.message); 22 | } 23 | }); 24 | } 25 | 26 | useEffect(() => { 27 | if (connection) { 28 | connection.emit('fetchUser'); 29 | connection.on('user', data => { 30 | if (data === null) { 31 | router.push('/'); 32 | } 33 | }); 34 | 35 | return () => { 36 | connection.off('user', data => { 37 | if (data === null) { 38 | router.push('/'); 39 | } 40 | }); 41 | } 42 | } 43 | }, [connection]); 44 | 45 | return <> 46 |
47 |
48 |
49 |
50 | Create New Room 51 |
52 | {error &&
53 |

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

54 |
} 55 |
56 |
57 | 58 | 59 |
60 |
61 | 62 | 63 |
64 |
65 |
66 | 67 |
68 |
69 |
70 |
71 |
72 |
73 | 74 | } 75 | -------------------------------------------------------------------------------- /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 and start chatting!

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 | } -------------------------------------------------------------------------------- /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 | }, 14 | }, 15 | plugins: [], 16 | } 17 | --------------------------------------------------------------------------------