├── src ├── App.css ├── index.css ├── main.jsx ├── assets │ └── react.svg └── App.jsx ├── postcss.config.js ├── vite.config.js ├── .gitignore ├── index.html ├── tailwind.config.js ├── .eslintrc.cjs ├── README.md ├── package.json └── public └── vite.svg /src/App.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Chatly 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | safelist: [ 8 | "text-ctp-red", 9 | "text-ctp-green", 10 | "text-ctp-pink", 11 | "text-ctp-peach", 12 | "text-ctp-blue", 13 | "text-ctp-teal", 14 | "text-ctp-sky", 15 | ], 16 | plugins: [ 17 | require("@catppuccin/tailwindcss")({ 18 | prefix: "ctp", 19 | defaultFlavour: "mocha", 20 | }), 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chatly 2 | 3 | This repo provides the frontend for the Rust Socket.io video by 4 | [Dreams of Code](https://youtube.com/@dreamsofcode). 5 | 6 | ## Requirements 7 | 8 | - Node v18 9 | - npm 10 | 11 | ## Usage 12 | 13 | First clone this repo 14 | 15 | ```shell 16 | $ git clone git@github.com:dreamsofcode-io/chatly-web 17 | $ cd chatly-web 18 | ``` 19 | 20 | Then install the depencencies using npm 21 | 22 | ```shell 23 | $ npm install 24 | ``` 25 | 26 | Finally, run the development server 27 | 28 | ```shell 29 | $ npm run dev 30 | ``` 31 | 32 | ## Contributing 33 | 34 | This server works with socket.io and both emits events and listens to them. 35 | 36 | You should not need to change anything about this server in order to use it 37 | with the video. If you do find an issue, please raise an issue or submit a 38 | PR to resolve it. 39 | 40 | Thank you! 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chatly-web", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "socket.io-client": "^4.7.2" 16 | }, 17 | "devDependencies": { 18 | "@catppuccin/tailwindcss": "^0.1.6", 19 | "@headlessui/react": "^1.7.17", 20 | "@heroicons/react": "^2.0.18", 21 | "@types/react": "^18.2.37", 22 | "@types/react-dom": "^18.2.15", 23 | "@vitejs/plugin-react": "^4.2.0", 24 | "autoprefixer": "^10.4.16", 25 | "eslint": "^8.53.0", 26 | "eslint-plugin-react": "^7.33.2", 27 | "eslint-plugin-react-hooks": "^4.6.0", 28 | "eslint-plugin-react-refresh": "^0.4.4", 29 | "postcss": "^8.4.32", 30 | "tailwindcss": "^3.3.6", 31 | "vite": "^5.0.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { Fragment, useState, useRef, useEffect } from "react"; 2 | import "./App.css"; 3 | import { ChevronRightIcon } from "@heroicons/react/20/solid"; 4 | import { io } from "socket.io-client"; 5 | import { Dialog, Menu, Transition } from "@headlessui/react"; 6 | import { 7 | Bars3Icon, 8 | Cog6ToothIcon, 9 | XMarkIcon, 10 | } from "@heroicons/react/24/outline"; 11 | 12 | const classNames = (...classes) => { 13 | return classes.filter(Boolean).join(" "); 14 | }; 15 | 16 | const colorForName = (name) => { 17 | const colors = [ 18 | "ctp-green", 19 | "ctp-pink", 20 | "ctp-red", 21 | "ctp-peach", 22 | "ctp-blue", 23 | "ctp-teal", 24 | ]; 25 | 26 | name = name.toLowerCase(); 27 | 28 | let sum = 0; 29 | for (let i = 0; i < name.length; i++) { 30 | sum += name.charCodeAt(i); 31 | } 32 | let index = sum % colors.length; 33 | 34 | return colors[index]; 35 | }; 36 | 37 | function App() { 38 | const [messages, setMessages] = useState([]); 39 | const [input, setInput] = useState(""); 40 | const [currentRoom, setCurrentRoom] = useState("General"); 41 | const [name, setName] = useState(null); 42 | const [socket, setSocket] = useState(null); 43 | const [connected, setConnected] = useState(false); 44 | const onceRef = useRef(false); 45 | const [sidebarOpen, setSidebarOpen] = useState(false); 46 | 47 | useEffect(() => { 48 | setMessages([]); 49 | socket?.emit("join", currentRoom); 50 | }, [currentRoom]); 51 | 52 | useEffect(() => { 53 | if (onceRef.current) { 54 | return; 55 | } 56 | 57 | onceRef.current = true; 58 | 59 | const socket = io("ws://localhost:3000"); 60 | setSocket(socket); 61 | 62 | socket.on("connect", () => { 63 | console.log("Connected to socket server"); 64 | setName(`anon-${socket.id}`); 65 | setConnected(true); 66 | console.log("joining room", currentRoom); 67 | 68 | socket.emit("join", currentRoom); 69 | }); 70 | 71 | socket.on("message", (msg) => { 72 | console.log("Message received", msg); 73 | msg.date = new Date(msg.date); 74 | setMessages((messages) => [...messages, msg]); 75 | }); 76 | 77 | socket.on("messages", (msgs) => { 78 | console.log("Messages received", msgs); 79 | let messages = msgs.messages.map((msg) => { 80 | msg.date = new Date(msg.date); 81 | return msg; 82 | }); 83 | setMessages(messages); 84 | }); 85 | }, []); 86 | 87 | const sendMessage = (e) => { 88 | e.preventDefault(); 89 | socket?.emit("message", { 90 | text: input, 91 | room: currentRoom, 92 | }); 93 | setInput(""); 94 | }; 95 | 96 | const rooms = [ 97 | "General", 98 | "C++", 99 | "Rust", 100 | "Go", 101 | "Python", 102 | "Java", 103 | "JavaScript", 104 | ]; 105 | 106 | return ( 107 | <> 108 |
109 | 110 | 115 | 124 |
125 | 126 | 127 |
128 | 137 | 138 | 147 |
148 | 159 |
160 |
161 |
162 |
163 |

164 | Rooms 165 |

166 |
167 | 192 |
193 |
194 |
195 |
196 |
197 |
198 | 222 |
223 |
224 |
225 | 233 |
234 |

235 | {currentRoom} 236 |

237 |
238 |
239 | 240 |

241 | {currentRoom} 242 |

243 | 244 |
    245 | {messages?.map((msg, index) => ( 246 |
  • 250 |
    251 |
    252 |

    258 | {msg.user} 259 |

    260 |

    261 | {msg.date.toLocaleString()} 262 |

    263 |
    264 |

    {msg.text}

    265 |
    266 |
  • 267 | ))} 268 |
269 |
270 |
271 | setInput(e.target.value)} 275 | className="flex-1 p-2 rounded-l-md bg-ctp-text text-ctp-base placeholder-ctp-subtext0" 276 | placeholder="Enter something englightened..." 277 | /> 278 | 284 |
285 |
286 |
287 | 288 | ); 289 | } 290 | 291 | export default App; 292 | --------------------------------------------------------------------------------