├── 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 |
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 |
285 |
286 |
287 | >
288 | );
289 | }
290 |
291 | export default App;
292 |
--------------------------------------------------------------------------------