From 9b5f8ef66c30ccfcb3e42e4d7853f904e1c139e5 Mon Sep 17 00:00:00 2001 From: Lalit sharma Date: Wed, 28 Jun 2023 15:33:43 +0530 Subject: [PATCH 1/8] README Updated --- README.md | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 109 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 86e8218..eba9de1 100644 --- a/README.md +++ b/README.md @@ -1 +1,109 @@ -# Coderspace MERN WebRTC Project +![coderspace](https://socialify.git.ci/mrExplorist/coderspace/image?description=1&descriptionEditable=Elevate%20your%20Coding%20Experience%3A%20Coderspace%20-%20Unleash%20the%20Potential%20of%20Real-time%20Collaboration%20and%20Conference%20Meets&font=Source%20Code%20Pro&forks=1&issues=1&language=1&name=1&owner=1&pattern=Plus&theme=Light) + +# Coderspace 🚀 + +Elevate your Coding Experience: Coderspace - Unleash the Potential of Real-time Collaboration and Conference Meets! 💻🔥 + +## Features 🔥 + +- 💬 **Conference Meetings**: Conduct virtual conference meetings with multiple participants, including audio and video capabilities, screen sharing, and chat functionality. 🎤🎥🖥️💬 +- 🤝 **Pair Programming**: Collaborate with other developers in real-time, allowing simultaneous code editing, chat, and audio communication. 👥💻💬🎤 +- 🔄 **Code Sync**: Enable synchronized code editing in real-time, allowing all participants to see and interact with code changes as they happen. 🔄💻 +- 📝 **Live Whiteboard**: Facilitate brainstorming and visual explanations through a shared whiteboard where participants can draw, write, and annotate. 📝🎨 +- ✋ **Raise Hand**: Participants can raise their hand to indicate that they have a question or need assistance, triggering a visual notification for others. 🙋‍♂️🙋‍♀️✋ +- 🍞 **Toasts**: Display toast messages to participants when important events occur, such as someone raising their hand or joining/leaving the room. 🍞📢 + +## Technologies Used 💻 + +- **Frontend**: React, Redux +- **Backend**: Node.js, Express.js, MongoDB +- **Real-time Communication**: WebRTC, Socket.IO +- **Authentication**: Twilio API for OTP (One-Time Password) authentication +- **Deployment**: Docker, AWS (Amazon Web Services), or any preferred hosting service + +## Getting Started 🚀 + +To set up Coderspace locally for development and testing, follow these steps: + +- This documentation will guide you through the process of setting up Coderspace locally on your machine. By following these steps, you will be able to run the application, and integrate Twilio for OTP service. + +Prerequisites: + +- Node.js (v14.x or higher) and npm (v6.x or higher) installed on your machine +- MongoDB installed locally (v4.x or higher) +- Twilio account with Account SID, Auth Token, and a registered phone number + +Step 1: Clone the Repository + +1. Open your terminal or command prompt. +2. Change to the directory where you want to clone the repository. +3. Run the following command to clone the repository: + ``` + git clone https://github.com/your-username/coderspace.git + ``` + Replace `your-username` with your actual GitHub username. + +Step 2: Install Dependencies on frontend and backend: + +1. Navigate to the project's root directory: + ``` + cd coderspace + ``` +2. Run the following command to install the project dependencies in both frontend and backend directory: + ``` + npm install + ``` + +Step 3: Configure Twilio for OTP Service + +1. Sign up for a Twilio account at https://www.twilio.com and obtain your Account SID, Auth Token, and a registered phone number. +2. Open the `.env` file in the project's root directory. +3. Set the `SMS_SID` variable to your Twilio Account SID. +4. Set the `SMS_AUTH_TOKEN` variable to your Twilio Auth Token. +5. Set the `SMS_FROM_NUMBER` variable to your registered Twilio phone number. + +Step 4: Set Up Environment Variables + +1. In the project's root directory, locate the `.env.example` file. +2. Create a new file named `.env` in both backend and frontend directory and copy the contents of `.env.example` and `.env.dev` respectively. +3. Modify the values in the `.env` file in backend directory to match your local configuration. Make sure to set the necessary variables, such as database connection information, Twilio Account SID, Auth Token, and Twilio phone number. +4. Set the SECRETS in the `.env` file by `crypto.randomBytes(length).toString('hex')`. It is a Node.js method that generates a random string of bytes with the specified length and converts it to a hexadecimal string representation which you can use to generate strong SECRET VARIABLES. You can set secrets to be any random generated string. + +Step 5: Start MongoDB + +1. Open a new terminal or command prompt window. +2. Start the MongoDB service by running the following command: + ``` + mongod + ``` + +Step 6: Run the Application + +1. In the original terminal or command prompt window, navigate to the project's root directory if you're not already there. +2. Run the following command to start the application: + ``` + cd /frontend + npm start + cd ../backend + npm run dev + ``` + This will start the server and the client development server. + +Step 7: Access the Application + +1. Open your web browser and visit `http://localhost:3000` to access the Coderspace application. + +Congratulations! You have successfully set up Coderspaces locally, including the integration of Twilio for OTP service and now it's a time to collaborate .. + +Note: Remember to keep your local environment variables and configurations secure and do not commit them to any version control system. + +Step 8: Collaborate: + +Contributions to the collaboration feature in our Coderspace project are welcome! If you would like to contribute, please follow these guidelines: + +- Fork the repository and create a new branch for your feature or bug fix. +- Commit your changes with descriptive commit messages. +- Push your changes to your forked repository. +- Submit a pull request detailing your changes and explaining the purpose and benefits of the contribution. + +We appreciate your contributions to making Coderspace an even better coding community app! From 3fa846e51d8df8b190a94f2ff94628840478883d Mon Sep 17 00:00:00 2001 From: Lalit sharma Date: Thu, 29 Jun 2023 13:40:03 +0530 Subject: [PATCH 2/8] Implemented code sync editor home page with room creation each with random ID --- .gitignore | 1 + frontend/src/App.js | 134 +++++++++++------- frontend/src/pages/Editor/EditorHome.js | 95 +++++++++++++ .../src/pages/Editor/EditorHome.module.css | 123 ++++++++++++++++ frontend/src/pages/Rooms/Rooms.jsx | 11 ++ frontend/src/pages/Rooms/Rooms.module.css | 35 ++++- package-lock.json | 44 ++++++ package.json | 6 + 8 files changed, 398 insertions(+), 51 deletions(-) create mode 100644 frontend/src/pages/Editor/EditorHome.js create mode 100644 frontend/src/pages/Editor/EditorHome.module.css create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.gitignore b/.gitignore index 2fd2304..c255fe7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ build # misc .DS_Store .env +.env.development .env.local .env.development.local .env.test.local diff --git a/frontend/src/App.js b/frontend/src/App.js index 514254b..baa96e2 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -17,7 +17,9 @@ import { useState } from "react"; import { useLoadingWithRefresh } from "./hooks/useLoadingWithRefresh"; import Loader from "./components/shared/Loader/Loader"; import Room from "./pages/Room/Room"; - +import EditorHome from "./pages/Editor/EditorHome"; +import { Toaster } from "react-hot-toast"; +import EditorPage from "./pages/Editor/EditorPage"; // Root component function App() { @@ -28,58 +30,89 @@ function App() { return loading ? ( ) : ( - - - - - - - } - /> - {/* } /> */} + <> +
+ +
+ + - {/* element={} */} - - - - } - /> - - - - } - /> - - - - } - /> + + + + + } + /> + {/* } /> */} - - - - } - /> + {/* element={} */} + + + + } + /> + + + + } + /> + + + + } + /> + + + + + } + /> + + + + + } + /> + + + + } + /> - {/* element={} + {/* element={} } /> */} - {/* @@ -87,8 +120,9 @@ function App() { } /> */} - - +
+
+ ); } diff --git a/frontend/src/pages/Editor/EditorHome.js b/frontend/src/pages/Editor/EditorHome.js new file mode 100644 index 0000000..a4524b6 --- /dev/null +++ b/frontend/src/pages/Editor/EditorHome.js @@ -0,0 +1,95 @@ +import React, { useState } from "react"; +import Card from "../../components/shared/Card/Card"; +import { TbBinaryTree } from "react-icons/tb"; +import styles from "./EditorHome.module.css"; +import { useSelector } from "react-redux"; +import { v4 as uuidv4 } from "uuid"; +import toast from "react-hot-toast"; +import { useNavigate } from "react-router-dom"; +import { MdScreenShare } from "react-icons/md"; + +const EditorHome = () => { + const navigate = useNavigate(); + const [roomId, setRoomId] = useState(""); + const { user } = useSelector((state) => state.auth); + const [userName, setUserName] = useState(user?.name); + const createNewRoom = (e) => { + e.preventDefault(); + const id = uuidv4(); + setRoomId(id); + if (userName && roomId) { + toast.success("Created a new room"); + } + }; + + const joinRoom = () => { + if (!roomId || !userName) { + toast.error("Please enter the room id and username"); + return; + } + // Redirect + + navigate(`/editor/${roomId}`, { state: { userName } }); + }; + + return ( +
+ +
+ +
+ +
+ Code Sync +

Real-time Collaboration

+
+ +

Paste invitation ROOM ID

+
+ setRoomId(e.target.value)} + value={roomId} + onKeyUp={(e) => { + if (e.key === "Enter") { + joinRoom(); + } + }} + /> + setUserName(e.target.value)} + value={userName} + onKeyUp={(e) => { + if (e.key === "Enter") { + joinRoom(); + } + }} + /> +
+ + + + If you don't have an invite then create   + + new Room + + +
+ +
+ ); +}; + +export default EditorHome; diff --git a/frontend/src/pages/Editor/EditorHome.module.css b/frontend/src/pages/Editor/EditorHome.module.css new file mode 100644 index 0000000..80a1f0e --- /dev/null +++ b/frontend/src/pages/Editor/EditorHome.module.css @@ -0,0 +1,123 @@ +.mainWrapper { + display: flex; + align-items: center; + justify-content: center; + min-height: calc(100vh - 190px); + /* back + ground: #fff; */ +} + +/* align logo to the leftwithin the card container */ +.cardContainer { + display: flex; + align-items: center; + justify-content: flex-end; + background-color: #fff; + padding: 16px; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.syncLogo { + display: flex; + align-items: center; + justify-content: center; + width: 60px; + height: 60px; + margin-right: 16px; + background-color: #f8f8f8; + border-radius: 50%; +} +.right { + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; +} + +.heading { + width: 100%; + text-align: center; + font-size: 1.8rem; + font-weight: 600; + margin-top: 1rem; +} +.subHeading { + color: rgb(213, 252, 151); + font-size: 1.2rem; + font-weight: 500; +} + +footer { + position: fixed; + bottom: 0; + margin-bottom: 1rem; +} + +.mainLabel { + font-size: 16px; + font-weight: 600; + color: #487; + margin-top: 16px; + margin-right: auto; +} + +footer a { + color: #4aee88; + text-decoration: none; +} + +.inputGroup { + display: flex; + flex-direction: column; + margin-bottom: 1rem; + width: 100%; +} + +.inputBox { + width: 100%; + padding: 0.5rem; + border: 1px solid #4aee88; + border-radius: 0.5rem; + background-color: #eee; + font-size: 1rem; + font-weight: 600; + outline: none; + margin: 0.5rem 0; +} +.joinBtn { + padding: 0.5rem; + border: 1px solid #4aee88; + border-radius: 0.5rem; + color: #000; + font-size: 1rem; + font-weight: 500; + outline: none; + cursor: pointer; + transition: all 0.3s ease-in-out; + margin-left: auto; + width: 100px; +} +.joinBtn:hover { + background-color: #4aee88; + color: #000; +} + +.createInfo { + margin: 0 auto; + margin-top: 20px; +} + +.createNewBtn { + color: #4aee88; + text-decoration: none; + font-size: 1rem; + font-weight: 600; + border-bottom: 1px solid #4aee88; + transition: all 0.3s ease-in-out; + cursor: pointer; +} +.createNewBtn:hover { + color: #fff; + border-bottom: 1px solid #fff; +} diff --git a/frontend/src/pages/Rooms/Rooms.jsx b/frontend/src/pages/Rooms/Rooms.jsx index 3fca89c..19fb39e 100644 --- a/frontend/src/pages/Rooms/Rooms.jsx +++ b/frontend/src/pages/Rooms/Rooms.jsx @@ -5,6 +5,8 @@ import { BiSearchAlt2 } from "react-icons/bi"; import RoomCard from "../../components/RoomCard/RoomCard"; import AddRoomModal from "../../components/Modals/AddRoomModal/AddRoomModal"; import { getAllRooms } from "../../http"; +import { TbSourceCode } from "react-icons/tb"; +import { useNavigate } from "react-router-dom"; // const rooms = [ // { @@ -101,6 +103,7 @@ import { getAllRooms } from "../../http"; // ]; const Rooms = () => { + const navigate = useNavigate(); const [showModal, setShowModal] = useState(false); const [rooms, setRooms] = useState([]); @@ -134,6 +137,14 @@ const Rooms = () => { Start a room + + diff --git a/frontend/src/pages/Rooms/Rooms.module.css b/frontend/src/pages/Rooms/Rooms.module.css index 3ba56c9..ce85fbf 100644 --- a/frontend/src/pages/Rooms/Rooms.module.css +++ b/frontend/src/pages/Rooms/Rooms.module.css @@ -10,7 +10,11 @@ display: flex; align-items: center; } - +.right { + display: flex; + align-items: center; + justify-content: center; +} .heading { font-style: 20px; font-weight: bold; @@ -55,6 +59,22 @@ color: #fff; transition: all 0.3s ease-in-out; } + +.codeEditorButton { + margin-left: 20px; + display: flex; + align-items: center; + background: #d40e18; + padding: 5px 20px; + border-radius: 20px; + color: #fff; + transition: all 0.3s ease-in-out; + width: 160px; +} +.codeEditorButton span { + font-size: 15px; + margin-left: 10px; +} .startRoomButton span { font-size: 15px; margin-left: 10px; @@ -70,3 +90,16 @@ gap: 20px; margin-top: 60px; } + +@media screen and (max-width: 1200px) { + .right { + display: block; + } + .codeEditorButton { + margin-left: 0; + margin-top: 20px; + } + .roomList { + grid-template-columns: repeat(2, 1fr); + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..1f0fbcb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,44 @@ +{ + "name": "codespace", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "react-hot-toast": "^2.4.1", + "uuid": "^9.0.0" + } + }, + "node_modules/goober": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", + "integrity": "sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..363085b --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "react-hot-toast": "^2.4.1", + "uuid": "^9.0.0" + } +} From 75d79538548fbfd6e78ae0a3eeaedc9072845f1f Mon Sep 17 00:00:00 2001 From: Lalit sharma Date: Sat, 1 Jul 2023 22:56:13 +0530 Subject: [PATCH 3/8] [Feat :Added new feature: Real-time collaboration code editor] --- backend/actions.js | 5 + backend/server.js | 17 +- frontend/package-lock.json | 220 +++++++++++++++++- frontend/package.json | 5 + frontend/src/App.css | 7 + frontend/src/App.js | 45 +--- frontend/src/actions.js | 5 + frontend/src/components/Client/Client.js | 14 ++ .../src/components/Client/Client.module.css | 8 + frontend/src/components/Editor/Editor.js | 80 +++++++ frontend/src/hooks/useWebRTC.js | 11 +- .../Editor/{ => EditorHome}/EditorHome.js | 2 +- .../{ => EditorHome}/EditorHome.module.css | 1 + .../src/pages/Editor/EditorPage/EditorPage.js | 77 ++++++ .../Editor/EditorPage/EditorPage.module.css | 70 ++++++ package-lock.json | 44 ---- package.json | 6 - 17 files changed, 523 insertions(+), 94 deletions(-) create mode 100644 frontend/src/components/Client/Client.js create mode 100644 frontend/src/components/Client/Client.module.css create mode 100644 frontend/src/components/Editor/Editor.js rename frontend/src/pages/Editor/{ => EditorHome}/EditorHome.js (97%) rename frontend/src/pages/Editor/{ => EditorHome}/EditorHome.module.css (99%) create mode 100644 frontend/src/pages/Editor/EditorPage/EditorPage.js create mode 100644 frontend/src/pages/Editor/EditorPage/EditorPage.module.css delete mode 100644 package-lock.json delete mode 100644 package.json diff --git a/backend/actions.js b/backend/actions.js index 465f850..4ed2713 100644 --- a/backend/actions.js +++ b/backend/actions.js @@ -1,5 +1,10 @@ const ACTIONS = { JOIN: "join", + JOINED: "joined", + DISCONNECTED: "disconnected", + CODE_CHANGE: "code-change", + SYNC_CODE: "sync-code", + LEAVE: "leave", ADD_PEER: "add-peer", REMOVE_PEER: "remove-peer", diff --git a/backend/server.js b/backend/server.js index 9106a58..1843649 100644 --- a/backend/server.js +++ b/backend/server.js @@ -137,6 +137,22 @@ io.on("connection", (socket) => { }); }); + //& setting up an event listener for the ACTIONS.CODE_CHANGE event. When this event is received, it executes a callback function with the provided payload { roomId, userId, message }. + + socket.on(ACTIONS.CODE_CHANGE, ({ roomId, code }) => { + // console.log("Receiving", code); + + // io.to(roomId).emit(ACTIONS.CODE_CHANGE, { + // code, + // }); + + // doing the same thing as above but with socket.in the same room as the client we received from the code from and emitting the code to all the clients in the room except the client we received the code from (socket.id) using socket.in(roomId).emit() instead of io.to(roomId).emit(). This is because we don't want to send the code back to the client who sent it in the first place. This is a common pattern in Socket.IO to send events to all clients in a room except the sender.This is equivalent to the following code: socket.broadcast.to(roomId).emit(ACTIONS.CODE_CHANGE, { code }); + + socket.in(roomId).emit(ACTIONS.CODE_CHANGE, { + code, + }); + }); + // Leaving the room const leaveRoom = ({ roomId }) => { @@ -172,4 +188,3 @@ io.on("connection", (socket) => { }); server.listen(PORT, () => console.log(`Listening on port ${PORT}`)); -// what is this code all about ? diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5fa075b..77948de 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,20 +8,25 @@ "name": "frontend", "version": "0.1.0", "dependencies": { + "@codemirror/basic-setup": "^0.20.0", "@reduxjs/toolkit": "^1.9.5", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.4.0", + "codemirror": "^5.65.2", "freeice": "^2.2.2", "react": "^18.2.0", + "react-avatar": "^5.0.3", "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", "react-icons": "^4.8.0", "react-lottie-player": "^1.5.4", "react-redux": "^8.0.7", "react-router-dom": "^6.10.0", "react-scripts": "5.0.1", "socket.io-client": "^4.6.2", + "uuid": "^9.0.0", "web-vitals": "^2.1.4" } }, @@ -2019,6 +2024,91 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "license": "MIT" }, + "node_modules/@codemirror/autocomplete": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-0.20.3.tgz", + "integrity": "sha512-lYB+NPGP+LEzAudkWhLfMxhTrxtLILGl938w+RcFrGdrIc54A+UgmCoz+McE3IYRFp4xyQcL4uFJwo+93YdgHw==", + "dependencies": { + "@codemirror/language": "^0.20.0", + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0", + "@lezer/common": "^0.16.0" + } + }, + "node_modules/@codemirror/basic-setup": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/basic-setup/-/basic-setup-0.20.0.tgz", + "integrity": "sha512-W/ERKMLErWkrVLyP5I8Yh8PXl4r+WFNkdYVSzkXYPQv2RMPSkWpr2BgggiSJ8AHF/q3GuApncDD8I4BZz65fyg==", + "deprecated": "In version 6.0, this package has been renamed to just 'codemirror'", + "dependencies": { + "@codemirror/autocomplete": "^0.20.0", + "@codemirror/commands": "^0.20.0", + "@codemirror/language": "^0.20.0", + "@codemirror/lint": "^0.20.0", + "@codemirror/search": "^0.20.0", + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-0.20.0.tgz", + "integrity": "sha512-v9L5NNVA+A9R6zaFvaTbxs30kc69F6BkOoiEbeFw4m4I0exmDEKBILN6mK+GksJtvTzGBxvhAPlVFTdQW8GB7Q==", + "dependencies": { + "@codemirror/language": "^0.20.0", + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0", + "@lezer/common": "^0.16.0" + } + }, + "node_modules/@codemirror/language": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz", + "integrity": "sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==", + "dependencies": { + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0", + "@lezer/common": "^0.16.0", + "@lezer/highlight": "^0.16.0", + "@lezer/lr": "^0.16.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-0.20.3.tgz", + "integrity": "sha512-06xUScbbspZ8mKoODQCEx6hz1bjaq9m8W8DxdycWARMiiX1wMtfCh/MoHpaL7ws/KUMwlsFFfp2qhm32oaCvVA==", + "dependencies": { + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.2", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-0.20.1.tgz", + "integrity": "sha512-ROe6gRboQU5E4z6GAkNa2kxhXqsGNbeLEisbvzbOeB7nuDYXUZ70vGIgmqPu0tB+1M3F9yWk6W8k2vrFpJaD4Q==", + "dependencies": { + "@codemirror/state": "^0.20.0", + "@codemirror/view": "^0.20.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "0.20.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz", + "integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==" + }, + "node_modules/@codemirror/view": { + "version": "0.20.7", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz", + "integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==", + "dependencies": { + "@codemirror/state": "^0.20.0", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@csstools/normalize.css": { "version": "12.0.0", "resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-12.0.0.tgz", @@ -2868,6 +2958,27 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "license": "MIT" }, + "node_modules/@lezer/common": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz", + "integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==" + }, + "node_modules/@lezer/highlight": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz", + "integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==", + "dependencies": { + "@lezer/common": "^0.16.0" + } + }, + "node_modules/@lezer/lr": { + "version": "0.16.3", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz", + "integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==", + "dependencies": { + "@lezer/common": "^0.16.0" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -5458,6 +5569,14 @@ "node": ">=10" } }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "engines": { + "node": "*" + } + }, "node_modules/check-types": { "version": "11.2.2", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.2.tgz", @@ -5639,6 +5758,11 @@ "node": ">=4" } }, + "node_modules/codemirror": { + "version": "5.65.2", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.65.2.tgz", + "integrity": "sha512-SZM4Zq7XEC8Fhroqe3LxbEEX1zUPWH1wMr5zxiBuiUF64iYOUH/JI88v4tBag8MiBS8B8gRv8O1pPXGYXQ4ErA==" + }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -5894,6 +6018,11 @@ "node": ">=10" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -5908,6 +6037,14 @@ "node": ">= 8" } }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "engines": { + "node": "*" + } + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -8350,6 +8487,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.13", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", + "integrity": "sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -9003,6 +9148,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -9208,6 +9358,11 @@ "node": ">=0.10.0" } }, + "node_modules/is-retina": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz", + "integrity": "sha512-/tCmbIETZwCd8uHWO+GvbRa7jxwHFHdfetHfiwoP0aN9UDf3prUJMtKn7iBFYipYhqY1bSTjur8hC/Dakt8eyw==" + }, "node_modules/is-root": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-root/-/is-root-2.1.0.tgz", @@ -10743,6 +10898,16 @@ "tmpl": "1.0.5" } }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, "node_modules/mdn-data": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", @@ -13189,6 +13354,21 @@ "node": ">=14" } }, + "node_modules/react-avatar": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/react-avatar/-/react-avatar-5.0.3.tgz", + "integrity": "sha512-DNc+qkWH9QehSEZqHBhqpXWsPY+rU9W7kD68QFHfu8Atfsvx/3ML0DzAePgTUd96nCXQQ3KZMcC3LKYT8FiBIg==", + "dependencies": { + "is-retina": "^1.0.3", + "md5": "^2.0.0" + }, + "peerDependencies": { + "@babel/runtime": ">=7", + "core-js-pure": ">=3", + "prop-types": "^15.0.0 || ^16.0.0", + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -13251,6 +13431,21 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "license": "MIT" }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-icons": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.8.0.tgz", @@ -14357,6 +14552,14 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/source-list-map": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", @@ -14725,6 +14928,11 @@ "webpack": "^5.0.0" } }, + "node_modules/style-mod": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.3.tgz", + "integrity": "sha512-78Jv8kYJdjbvRwwijtCevYADfsI0lGzYJe4mMFdceO8l75DFFDoqBhR1jVDicDRRaX4//g1u9wKeo+ztc2h1Rw==" + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -15665,10 +15873,9 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", "bin": { "uuid": "dist/bin/uuid" } @@ -15714,6 +15921,11 @@ "browser-process-hrtime": "^1.0.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/w3c-xmlserializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 86ec920..25d1e08 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,20 +3,25 @@ "version": "0.1.0", "private": true, "dependencies": { + "@codemirror/basic-setup": "^0.20.0", "@reduxjs/toolkit": "^1.9.5", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.4.0", + "codemirror": "^5.65.2", "freeice": "^2.2.2", "react": "^18.2.0", + "react-avatar": "^5.0.3", "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", "react-icons": "^4.8.0", "react-lottie-player": "^1.5.4", "react-redux": "^8.0.7", "react-router-dom": "^6.10.0", "react-scripts": "5.0.1", "socket.io-client": "^4.6.2", + "uuid": "^9.0.0", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/frontend/src/App.css b/frontend/src/App.css index d088f59..fdda73b 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -58,3 +58,10 @@ body { justify-content: center; min-height: calc(100vh - 190px); } + +.CodeMirror { + min-height: calc(100vh - 100px); + font-size: 20px; + line-height: 1.6; + padding-top: 20px; +} diff --git a/frontend/src/App.js b/frontend/src/App.js index baa96e2..c87c21f 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -5,6 +5,7 @@ import { Route, BrowserRouter as Router, Routes, + useLocation, } from "react-router-dom"; import Activate from "./pages/Activate/Activate"; @@ -13,13 +14,15 @@ import Home from "./pages/Home/Home"; import Navigation from "./components/shared/Navigation/Navigation"; import Rooms from "./pages/Rooms/Rooms"; import { useSelector } from "react-redux"; -import { useState } from "react"; + import { useLoadingWithRefresh } from "./hooks/useLoadingWithRefresh"; import Loader from "./components/shared/Loader/Loader"; import Room from "./pages/Room/Room"; -import EditorHome from "./pages/Editor/EditorHome"; + import { Toaster } from "react-hot-toast"; -import EditorPage from "./pages/Editor/EditorPage"; +import EditorHome from "./pages/Editor/EditorHome/EditorHome"; +import EditorPage from "./pages/Editor/EditorPage/EditorPage"; + // Root component function App() { @@ -43,6 +46,7 @@ function App() { }} > + @@ -55,9 +59,7 @@ function App() { } /> - {/* } /> */} - {/* element={} */} } /> - - {/* element={} - } /> */} - - {/* - - - } - /> */} @@ -140,6 +130,7 @@ function SemiProtectedRoute({ children }) { ); } + function ProtectedRoute({ children }) { const { user, isAuth } = useSelector((state) => state.auth); return !isAuth ? ( @@ -151,24 +142,4 @@ function ProtectedRoute({ children }) { ); } -// const GuestRoute = ({ children, ...rest }) => { -// return ( -// { -// return isAuth ? : children; -// }} -// > -// ); -// }; -// function SemiProtectedRoute({ children }) { -// return !isAuth ? ( -// -// ) : isAuth && !user.isActivated ? ( -// <>{children} -// ) : ( -// -// ); -// } - export default App; diff --git a/frontend/src/actions.js b/frontend/src/actions.js index 6c9c0c5..8b09cbb 100644 --- a/frontend/src/actions.js +++ b/frontend/src/actions.js @@ -1,5 +1,10 @@ export const ACTIONS = { JOIN: "join", + JOINED: "joined", + DISCONNECTED: "disconnected", + CODE_CHANGE: "code-change", + SYNC_CODE: "sync-code", + LEAVE: "leave", ADD_PEER: "add-peer", REMOVE_PEER: "remove-peer", diff --git a/frontend/src/components/Client/Client.js b/frontend/src/components/Client/Client.js new file mode 100644 index 0000000..86ccbbc --- /dev/null +++ b/frontend/src/components/Client/Client.js @@ -0,0 +1,14 @@ +import React from "react"; +import styles from "./Client.module.css"; +import Avatar from "react-avatar"; + +const Client = ({ username }) => { + return ( +
+ + {username} +
+ ); +}; + +export default Client; diff --git a/frontend/src/components/Client/Client.module.css b/frontend/src/components/Client/Client.module.css new file mode 100644 index 0000000..a29d71b --- /dev/null +++ b/frontend/src/components/Client/Client.module.css @@ -0,0 +1,8 @@ +.client { + display: flex; + align-items: center; + flex-direction: column; +} +.userName { + color: #a0e4ba; +} diff --git a/frontend/src/components/Editor/Editor.js b/frontend/src/components/Editor/Editor.js new file mode 100644 index 0000000..1f8e9b8 --- /dev/null +++ b/frontend/src/components/Editor/Editor.js @@ -0,0 +1,80 @@ +import React, { useEffect, useRef } from "react"; +import Codemirror from "codemirror"; +import "codemirror/lib/codemirror.css"; +import "codemirror/theme/dracula.css"; +import "codemirror/mode/javascript/javascript.js"; +import "codemirror/addon/edit/closebrackets.js"; +import "codemirror/addon/edit/matchbrackets.js"; +import "codemirror/addon/edit/closetag.js"; +import "codemirror/addon/edit/matchtags.js"; +import "codemirror/addon/fold/foldgutter.js"; +import "codemirror/addon/fold/foldcode.js"; +import "codemirror/addon/fold/foldgutter.css"; +import "codemirror/addon/fold/brace-fold.js"; +import "codemirror/addon/fold/comment-fold.js"; + +import { ACTIONS } from "../../actions"; + +const Editor = ({ socketRef, roomId }) => { + const editorRef = useRef(null); + + useEffect(() => { + async function loadEditor() { + editorRef.current = Codemirror.fromTextArea( + document.getElementById("realTimeEditor"), + { + mode: { name: "javascript", json: true }, + lineNumbers: true, + theme: "dracula", + lineWrapping: true, + autoCloseBrackets: true, + matchBrackets: true, + autoCloseTags: true, + matchTags: true, + foldGutter: true, + gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], + } + ); + + // Listen for changes in the editor and send them to the server over Socket.io server socket connection + + editorRef.current.on("change", (instance, changes) => { + // console.log("changes", changes); + + const { origin } = changes; + + const code = instance.getValue(); + // console.log(code); + + if (origin !== "setValue") { + // console.log("emitting code change", code); + socketRef.current?.emit(ACTIONS.CODE_CHANGE, { + roomId, + code, + }); + } + // console.log(code); + }); + + // Listen for changes in the editor and send them to the server over Socket.io server socket connection + } + + loadEditor(); + }, []); + + useEffect(() => { + // console.log("socketRef.current", socketRef.current); + if (socketRef.current) { + socketRef.current?.on(ACTIONS.CODE_CHANGE, ({ code }) => { + // console.log("emitting code change", code); + if (code != null) { + editorRef.current?.setValue(code); + } + }); + } + }, [socketRef.current]); + + return ; +}; + +export default Editor; diff --git a/frontend/src/hooks/useWebRTC.js b/frontend/src/hooks/useWebRTC.js index 9aef4e6..38e44fd 100644 --- a/frontend/src/hooks/useWebRTC.js +++ b/frontend/src/hooks/useWebRTC.js @@ -3,6 +3,7 @@ import { ACTIONS } from "../actions"; import { socketInit } from "../socket"; import freeice from "freeice"; import { useStateWithCallback } from "./useStateWithCallback"; +import { toast } from "react-hot-toast"; export const useWebRTC = (roomId, user) => { const [clients, setClients] = useStateWithCallback([]); @@ -72,9 +73,11 @@ export const useWebRTC = (roomId, user) => { } async function handleNewPeer({ peerId, createOffer, user: remoteUser }) { if (peerId in connections.current) { - return console.warn( + console.warn( `You are already connected with ${peerId} (${user.name})` ); + } else { + toast.success(`${remoteUser.name} connected`); } // Create a new RTCPeerConnection and store it in connections @@ -148,6 +151,11 @@ export const useWebRTC = (roomId, user) => { delete connections.current[peerId]; delete audioElements.current[peerId]; + + toast.success( + `${clientsRef.current.find((c) => c.id === userId).name} disconnected` + ); + setClients((list) => list.filter((c) => c.id !== userId)); } async function handleIceCandidate({ peerId, icecandidate }) { @@ -249,6 +257,7 @@ export const useWebRTC = (roomId, user) => { return { clients, provideRef, + socket, handleMute, }; }; diff --git a/frontend/src/pages/Editor/EditorHome.js b/frontend/src/pages/Editor/EditorHome/EditorHome.js similarity index 97% rename from frontend/src/pages/Editor/EditorHome.js rename to frontend/src/pages/Editor/EditorHome/EditorHome.js index a4524b6..561313c 100644 --- a/frontend/src/pages/Editor/EditorHome.js +++ b/frontend/src/pages/Editor/EditorHome/EditorHome.js @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import Card from "../../components/shared/Card/Card"; +import Card from "../../../components/shared/Card/Card"; import { TbBinaryTree } from "react-icons/tb"; import styles from "./EditorHome.module.css"; import { useSelector } from "react-redux"; diff --git a/frontend/src/pages/Editor/EditorHome.module.css b/frontend/src/pages/Editor/EditorHome/EditorHome.module.css similarity index 99% rename from frontend/src/pages/Editor/EditorHome.module.css rename to frontend/src/pages/Editor/EditorHome/EditorHome.module.css index 80a1f0e..70ad651 100644 --- a/frontend/src/pages/Editor/EditorHome.module.css +++ b/frontend/src/pages/Editor/EditorHome/EditorHome.module.css @@ -98,6 +98,7 @@ footer a { margin-left: auto; width: 100px; } + .joinBtn:hover { background-color: #4aee88; color: #000; diff --git a/frontend/src/pages/Editor/EditorPage/EditorPage.js b/frontend/src/pages/Editor/EditorPage/EditorPage.js new file mode 100644 index 0000000..0939c00 --- /dev/null +++ b/frontend/src/pages/Editor/EditorPage/EditorPage.js @@ -0,0 +1,77 @@ +import React, { useState, useRef, useEffect } from "react"; +import Editor from "../../../components/Editor/Editor"; +import styles from "./EditorPage.module.css"; +import Client from "../../../components/Client/Client"; +import { MdScreenShare } from "react-icons/md"; +import { socketInit } from "../../../socket"; +import { ACTIONS } from "../../../actions"; +import { + useLocation, + useParams, + // Navigate, +} from "react-router-dom"; +import { toast } from "react-hot-toast"; +import { useSelector } from "react-redux"; +import { useWebRTC } from "../../../hooks/useWebRTC"; + +const EditorPage = () => { + const { id: roomId } = useParams(); + + // const location = useLocation(); + // const reactNavigator = useNavigate(); + + const { user } = useSelector((state) => state.auth); //taking user from global state + + const { clients, socket } = useWebRTC(roomId, user); + + // const socketRef = useRef(); + // useEffect(() => { + // const init = async () => { + // socketRef.current = await socketInit(); + // socketRef.current.on("connect_error", (err) => handleErrors(err)); + // socketRef.current.on("connect_failed", (err) => handleErrors(err)); + // console.log(socketRef.current); + + // function handleErrors(e) { + // console.log("socket error", e); + // toast.error("Socket connection failed, try again later."); + // reactNavigator("/"); + // } + + // socketRef.current.emit(ACTIONS.JOIN, { + // roomId, + // user: location.state?.username, + // }); + // }; + + // init(); + // }, []); + // if (!location.state) { + // return ; + // } + + return ( +
+
+
+
+ +
+

Connected

+
+ {clients.map((client) => ( + + ))} +
+
+ + +
+
+ +
+
+ ); +}; + +export default EditorPage; diff --git a/frontend/src/pages/Editor/EditorPage/EditorPage.module.css b/frontend/src/pages/Editor/EditorPage/EditorPage.module.css new file mode 100644 index 0000000..5be505d --- /dev/null +++ b/frontend/src/pages/Editor/EditorPage/EditorPage.module.css @@ -0,0 +1,70 @@ +.mainWrap { + display: grid; + grid-template-columns: 260px 1fr; + height: 90vh; +} + +.aside { + background-color: #1c1e29; + padding: 16px; + border-right: 2px solid #e0e0e0; + display: flex; + flex-direction: column; + border-radius: 0.5rem; +} + +.asideInner { + flex: 1; +} +.syncLogo { + display: flex; + align-items: center; + justify-content: center; + width: 60px; + height: 60px; + background-color: #f8f8f8; + border-radius: 10%; + margin-bottom: 1rem; + /* justify-content: space-between; */ +} + +.asideInner h3 { + font-size: 1rem; + font-weight: 600; + color: #4aee88; + border-bottom: 1px solid #4aee88; +} +.asideInner .logoName { + font-size: 1.5rem; + font-weight: 600; +} +.clientsList { + display: flex; + flex-wrap: wrap; + margin-top: 1rem; + gap: 10px; +} + +.copyBtn { + padding: 0.3rem; + border: 1px solid #4aee88; + border-radius: 0.5rem; + color: #000; + font-size: 1rem; + font-weight: 600; + outline: none; + cursor: pointer; + transition: all 0.3s ease-in-out; +} +.leaveBtn { + padding: 0.3rem; + border: 1px solid #4aee88; + border-radius: 0.5rem; + color: #000; + font-size: 1rem; + font-weight: 600; + outline: none; + cursor: pointer; + margin-top: 1rem; + background: #4aee88; +} diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 1f0fbcb..0000000 --- a/package-lock.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "name": "codespace", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "dependencies": { - "react-hot-toast": "^2.4.1", - "uuid": "^9.0.0" - } - }, - "node_modules/goober": { - "version": "2.1.13", - "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.13.tgz", - "integrity": "sha512-jFj3BQeleOoy7t93E9rZ2de+ScC4lQICLwiAQmKMg9F6roKGaLSHoCDYKkWlSafg138jejvq/mTdvmnwDQgqoQ==", - "peerDependencies": { - "csstype": "^3.0.10" - } - }, - "node_modules/react-hot-toast": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", - "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", - "dependencies": { - "goober": "^2.1.10" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": ">=16", - "react-dom": ">=16" - } - }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - } - } -} diff --git a/package.json b/package.json deleted file mode 100644 index 363085b..0000000 --- a/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "dependencies": { - "react-hot-toast": "^2.4.1", - "uuid": "^9.0.0" - } -} From 54f3317e2124e473d00070e19bc7ef39efa00dca Mon Sep 17 00:00:00 2001 From: Lalit sharma Date: Tue, 4 Jul 2023 23:04:37 +0530 Subject: [PATCH 4/8] some important changes --- README.md | 3 +-- backend/.env.dev | 13 ------------- backend/.env.example | 17 +++++++++++++++++ frontend/src/App.module.css | 6 ------ frontend/src/pages/RegSteps/StepOtp/StepOtp.jsx | 2 +- frontend/src/pages/Room/Room.jsx | 2 +- 6 files changed, 20 insertions(+), 23 deletions(-) delete mode 100644 backend/.env.dev create mode 100644 backend/.env.example diff --git a/README.md b/README.md index eba9de1..d0e66f0 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,7 @@ Elevate your Coding Experience: Coderspace - Unleash the Potential of Real-time - 🤝 **Pair Programming**: Collaborate with other developers in real-time, allowing simultaneous code editing, chat, and audio communication. 👥💻💬🎤 - 🔄 **Code Sync**: Enable synchronized code editing in real-time, allowing all participants to see and interact with code changes as they happen. 🔄💻 - 📝 **Live Whiteboard**: Facilitate brainstorming and visual explanations through a shared whiteboard where participants can draw, write, and annotate. 📝🎨 -- ✋ **Raise Hand**: Participants can raise their hand to indicate that they have a question or need assistance, triggering a visual notification for others. 🙋‍♂️🙋‍♀️✋ -- 🍞 **Toasts**: Display toast messages to participants when important events occur, such as someone raising their hand or joining/leaving the room. 🍞📢 +- ✋ **Raise Hand**: Participants can raise their hand to indicate that they have a question or need assistance, triggering a visual notification for others. ## Technologies Used 💻 diff --git a/backend/.env.dev b/backend/.env.dev deleted file mode 100644 index dbe78c4..0000000 --- a/backend/.env.dev +++ /dev/null @@ -1,13 +0,0 @@ -HASH_SECRET=4842eb067444da837acf4832311e0bbd6a8e6452da633ed3bc93afe0f66ea0095f4c2f4538b3a951211a9b3c463db70aae22117e9f14f391b91ab6ee72da2d1a -SMS_SID=ACfac66ed39655343fad1bf93e46bfe4f8 -SMS_AUTH_TOKEN=8859740e6881fc19ee280ae32e799e01 -SMS_FROM_NUMBER="+13203355676" - - -DB_URL=mongodb://mongodb:27017/coderspace -# DB_URL=mongodb+srv://MrExplorist:EcS81gdiDOFDBzkH@coderspace.hxef2w1.mongodb.net/?retryWrites=true&w=majority -JWT_ACCESS_TOKEN_SECRET=2cd9026bab6153cecdeb3ef1a2981b4b759819d774cde2a72252068f2f2a06e5885152e27de8a8dbb8090874a7f00e4aa92e6de7f4349276a645362ff33dcb32 -JWT_REFRESH_TOKEN_SECRET=a386573153bf950abcc028ab147959f695bcede7d2f7a24f35c9182cccc19d0421b6ab2977342f1bf0b8c607afbc5fe3cacb1fa802828dd62797d9c001ec4d08 -BASE_URL=http://localhost:5500 -FRONT_URL=http://localhost:3000 -NODE_ENV=development \ No newline at end of file diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..433952c --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,17 @@ + +#Secrets you will create by running crypto.randomBytes(anylengthhererecommended:12).toString('hex') by running node in terminal please ensure that each secret is unique +HASH_SECRET= +JWT_ACCESS_TOKEN_SECRET= +JWT_REFRESH_TOKEN_SECRET= + +# You will get this from twilio setup +SMS_SID= +SMS_AUTH_TOKEN= +SMS_FROM_NUMBER="" + +# Running mongodb locally + +DB_URL=mongodb://localhost:27017/coderspace +BASE_URL=http://localhost:5500 +FRONT_URL=http://localhost:3000 +NODE_ENV=development \ No newline at end of file diff --git a/frontend/src/App.module.css b/frontend/src/App.module.css index 7c17820..e69de29 100644 --- a/frontend/src/App.module.css +++ b/frontend/src/App.module.css @@ -1,6 +0,0 @@ -.cardWrapper { - display: flex; - align-items: center; - justify-content: center; - margin-top: 6rem; -} diff --git a/frontend/src/pages/RegSteps/StepOtp/StepOtp.jsx b/frontend/src/pages/RegSteps/StepOtp/StepOtp.jsx index 7766b9a..446cff2 100644 --- a/frontend/src/pages/RegSteps/StepOtp/StepOtp.jsx +++ b/frontend/src/pages/RegSteps/StepOtp/StepOtp.jsx @@ -28,7 +28,7 @@ const StepOtp = ({ onNext }) => { } return ( <> -
+
} diff --git a/frontend/src/pages/Room/Room.jsx b/frontend/src/pages/Room/Room.jsx index 48e73d5..79033e6 100644 --- a/frontend/src/pages/Room/Room.jsx +++ b/frontend/src/pages/Room/Room.jsx @@ -4,7 +4,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { useSelector } from "react-redux"; -import { BsArrowLeft, BsFillMicFill } from "react-icons/bs"; +import { BsArrowLeft } from "react-icons/bs"; import { IoMdMicOff } from "react-icons/io"; import { FaHandPeace, FaHandSparkles } from "react-icons/fa"; import styles from "./Room.module.css"; From 1ea4e255a82e9396fd6f87abba7268f69231bc25 Mon Sep 17 00:00:00 2001 From: Lalit sharma Date: Thu, 3 Aug 2023 09:58:45 +0530 Subject: [PATCH 5/8] =?UTF-8?q?copyRoomId=20and=20leave=20Room=20from=20th?= =?UTF-8?q?e=20editorRoom=20itself=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/components/Editor/Editor.js | 7 ++++- .../src/pages/Editor/EditorPage/EditorPage.js | 29 +++++++++++++++---- .../Editor/EditorPage/EditorPage.module.css | 4 +-- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/frontend/src/components/Editor/Editor.js b/frontend/src/components/Editor/Editor.js index 1f8e9b8..c0b27bd 100644 --- a/frontend/src/components/Editor/Editor.js +++ b/frontend/src/components/Editor/Editor.js @@ -33,7 +33,7 @@ const Editor = ({ socketRef, roomId }) => { matchTags: true, foldGutter: true, gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"], - } + }, ); // Listen for changes in the editor and send them to the server over Socket.io server socket connection @@ -72,6 +72,11 @@ const Editor = ({ socketRef, roomId }) => { } }); } + return () => { + if (socketRef.current) { + socketRef.current?.off(ACTIONS.CODE_CHANGE); + } + }; }, [socketRef.current]); return ; diff --git a/frontend/src/pages/Editor/EditorPage/EditorPage.js b/frontend/src/pages/Editor/EditorPage/EditorPage.js index 0939c00..9c1d065 100644 --- a/frontend/src/pages/Editor/EditorPage/EditorPage.js +++ b/frontend/src/pages/Editor/EditorPage/EditorPage.js @@ -7,6 +7,7 @@ import { socketInit } from "../../../socket"; import { ACTIONS } from "../../../actions"; import { useLocation, + useNavigate, useParams, // Navigate, } from "react-router-dom"; @@ -18,9 +19,9 @@ const EditorPage = () => { const { id: roomId } = useParams(); // const location = useLocation(); - // const reactNavigator = useNavigate(); + const reactNavigator = useNavigate(); - const { user } = useSelector((state) => state.auth); //taking user from global state + const { user } = useSelector(state => state.auth); //taking user from global state const { clients, socket } = useWebRTC(roomId, user); @@ -50,6 +51,20 @@ const EditorPage = () => { // return ; // } + const copyRoomId = async () => { + try { + await navigator.clipboard.writeText(roomId); + toast.success("ROOM ID copied to clipboard"); + } catch (error) { + toast.error("Failed to copy ROOM ID"); + console.log(error); + } + }; + + const leaveRoom = () => { + reactNavigator("/"); + }; + return (
@@ -59,13 +74,17 @@ const EditorPage = () => {

Connected

- {clients.map((client) => ( + {clients.map(client => ( ))}
- - + +
diff --git a/frontend/src/pages/Editor/EditorPage/EditorPage.module.css b/frontend/src/pages/Editor/EditorPage/EditorPage.module.css index 5be505d..0d2dd5d 100644 --- a/frontend/src/pages/Editor/EditorPage/EditorPage.module.css +++ b/frontend/src/pages/Editor/EditorPage/EditorPage.module.css @@ -5,12 +5,10 @@ } .aside { - background-color: #1c1e29; padding: 16px; - border-right: 2px solid #e0e0e0; + /* border-right: 2px solid #e0e0e0; */ display: flex; flex-direction: column; - border-radius: 0.5rem; } .asideInner { From 13bd9bced0c8d996d5f41b2ac849d151c0a2aac9 Mon Sep 17 00:00:00 2001 From: Lalit sharma Date: Thu, 3 Aug 2023 17:35:37 +0530 Subject: [PATCH 6/8] code sync on editor on join of new peer --- backend/server.js | 18 ++++++---- frontend/src/components/Editor/Editor.js | 3 +- frontend/src/hooks/useWebRTC.js | 35 +++++++++++-------- .../src/pages/Editor/EditorPage/EditorPage.js | 29 ++++++++------- 4 files changed, 48 insertions(+), 37 deletions(-) diff --git a/backend/server.js b/backend/server.js index 1843649..9dc422b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -55,7 +55,7 @@ const io = require("socket.io")(server, { const socketUserMap = {}; -io.on("connection", (socket) => { +io.on("connection", socket => { console.log("new connection", socket.id); socket.on(ACTIONS.JOIN, ({ roomId, user }) => { socketUserMap[socket.id] = user; @@ -66,7 +66,7 @@ io.on("connection", (socket) => { // Getting all the clients - clients.forEach((clientId) => { + clients.forEach(clientId => { io.to(clientId).emit(ACTIONS.ADD_PEER, { peerId: socket.id, createOffer: false, @@ -117,7 +117,7 @@ io.on("connection", (socket) => { socket.on(ACTIONS.MUTE, ({ roomId, userId }) => { const clients = Array.from(io.sockets.adapter.rooms.get(roomId) || []); - clients.forEach((clientId) => { + clients.forEach(clientId => { io.to(clientId).emit(ACTIONS.MUTE, { peerId: socket.id, userId, @@ -129,7 +129,7 @@ io.on("connection", (socket) => { socket.on(ACTIONS.UNMUTE, ({ roomId, userId }) => { const clients = Array.from(io.sockets.adapter.rooms.get(roomId) || []); - clients.forEach((clientId) => { + clients.forEach(clientId => { io.to(clientId).emit(ACTIONS.UNMUTE, { peerId: socket.id, userId, @@ -152,17 +152,21 @@ io.on("connection", (socket) => { code, }); }); - + socket.on(ACTIONS.SYNC_CODE, ({ peerId, code }) => { + io.to(peerId).emit(ACTIONS.CODE_CHANGE, { + code, + }); + }); // Leaving the room const leaveRoom = ({ roomId }) => { // getting all the rooms in the socket const { rooms } = socket; // room map - Array.from(rooms).forEach((roomId) => { + Array.from(rooms).forEach(roomId => { const clients = Array.from(io.sockets.adapter.rooms.get(roomId) || []); - clients.forEach((clientId) => { + clients.forEach(clientId => { io.to(clientId).emit(ACTIONS.REMOVE_PEER, { peerId: socket.id, userId: socketUserMap[socket.id]?.id, diff --git a/frontend/src/components/Editor/Editor.js b/frontend/src/components/Editor/Editor.js index c0b27bd..320a0e4 100644 --- a/frontend/src/components/Editor/Editor.js +++ b/frontend/src/components/Editor/Editor.js @@ -15,7 +15,7 @@ import "codemirror/addon/fold/comment-fold.js"; import { ACTIONS } from "../../actions"; -const Editor = ({ socketRef, roomId }) => { +const Editor = ({ socketRef, roomId, onCodeChange }) => { const editorRef = useRef(null); useEffect(() => { @@ -54,6 +54,7 @@ const Editor = ({ socketRef, roomId }) => { }); } // console.log(code); + onCodeChange(code); }); // Listen for changes in the editor and send them to the server over Socket.io server socket connection diff --git a/frontend/src/hooks/useWebRTC.js b/frontend/src/hooks/useWebRTC.js index 38e44fd..7a0e332 100644 --- a/frontend/src/hooks/useWebRTC.js +++ b/frontend/src/hooks/useWebRTC.js @@ -10,20 +10,21 @@ export const useWebRTC = (roomId, user) => { const audioElements = useRef({}); const connections = useRef({}); // to store all the peer connections we need a reference const socket = useRef(null); + const codeRef = useRef(null); const localMediaStream = useRef(null); // we are using ref because we don't want to re-render the UI when client list changes const clientsRef = useRef(null); const addNewClient = useCallback( (newClient, cb) => { - const lookingFor = clients.find((client) => client.id === newClient.id); + const lookingFor = clients.find(client => client.id === newClient.id); if (lookingFor === undefined) { // Add the new client to the list - setClients((existingClients) => [...existingClients, newClient], cb); + setClients(existingClients => [...existingClients, newClient], cb); } }, - [clients, setClients] + [clients, setClients], ); useEffect(() => { @@ -74,19 +75,24 @@ export const useWebRTC = (roomId, user) => { async function handleNewPeer({ peerId, createOffer, user: remoteUser }) { if (peerId in connections.current) { console.warn( - `You are already connected with ${peerId} (${user.name})` + `You are already connected with ${peerId} (${user.name})`, ); } else { - toast.success(`${remoteUser.name} connected`); + toast.success(`${remoteUser.name} joined the room`); } + socket.current.emit(ACTIONS.SYNC_CODE, { + code: codeRef.current, + peerId, + }); + // Create a new RTCPeerConnection and store it in connections connections.current[peerId] = new RTCPeerConnection({ iceServers: freeice(), }); // Handle new ice candidate on this peer connection - connections.current[peerId].onicecandidate = (event) => { + connections.current[peerId].onicecandidate = event => { socket.current.emit(ACTIONS.RELAY_ICE, { peerId, icecandidate: event.candidate, @@ -98,7 +104,7 @@ export const useWebRTC = (roomId, user) => { addNewClient({ ...remoteUser, muted: true }, () => { // get current users mute info const currentUser = clientsRef.current.find( - (client) => client.id === user.id + client => client.id === user.id, ); if (currentUser) { socket.current.emit(ACTIONS.MUTE_INFO, { @@ -126,7 +132,7 @@ export const useWebRTC = (roomId, user) => { }; // Add connection to peer connections track - localMediaStream.current.getTracks().forEach((track) => { + localMediaStream.current.getTracks().forEach(track => { connections.current[peerId].addTrack(track, localMediaStream.current); }); @@ -153,10 +159,10 @@ export const useWebRTC = (roomId, user) => { delete audioElements.current[peerId]; toast.success( - `${clientsRef.current.find((c) => c.id === userId).name} disconnected` + `${clientsRef.current.find(c => c.id === userId).name} disconnected`, ); - setClients((list) => list.filter((c) => c.id !== userId)); + setClients(list => list.filter(c => c.id !== userId)); } async function handleIceCandidate({ peerId, icecandidate }) { if (icecandidate) { @@ -168,7 +174,7 @@ export const useWebRTC = (roomId, user) => { sessionDescription: remoteSessionDescription, }) { connections.current[peerId].setRemoteDescription( - new RTCSessionDescription(remoteSessionDescription) + new RTCSessionDescription(remoteSessionDescription), ); // If session description is offer then create an answer @@ -186,10 +192,10 @@ export const useWebRTC = (roomId, user) => { } async function handleSetMute(mute, userId) { const clientIdx = clientsRef.current - .map((client) => client.id) + .map(client => client.id) .indexOf(userId); const allConnectedClients = JSON.parse( - JSON.stringify(clientsRef.current) + JSON.stringify(clientsRef.current), ); if (clientIdx > -1) { allConnectedClients[clientIdx].muted = mute; @@ -202,7 +208,7 @@ export const useWebRTC = (roomId, user) => { // / cleanup function defined in the return statement of the useEffect hook is responsible for cleaning up the resources when the component unmounts. It checks if localMediaStream exists, stops all tracks in the stream using getTracks().forEach((track) => track.stop()), and emits a LEAVE event to the server, passing the roomId. It also closes all the peer connections and removes the audio elements from the audioElements object. return () => { - localMediaStream.current.getTracks().forEach((track) => track.stop()); + localMediaStream.current.getTracks().forEach(track => track.stop()); socket.current.emit(ACTIONS.LEAVE, { roomId }); for (let peerId in connections.current) { connections.current[peerId].close(); @@ -259,5 +265,6 @@ export const useWebRTC = (roomId, user) => { provideRef, socket, handleMute, + codeRef, }; }; diff --git a/frontend/src/pages/Editor/EditorPage/EditorPage.js b/frontend/src/pages/Editor/EditorPage/EditorPage.js index 9c1d065..ad83f45 100644 --- a/frontend/src/pages/Editor/EditorPage/EditorPage.js +++ b/frontend/src/pages/Editor/EditorPage/EditorPage.js @@ -1,19 +1,12 @@ -import React, { useState, useRef, useEffect } from "react"; -import Editor from "../../../components/Editor/Editor"; -import styles from "./EditorPage.module.css"; -import Client from "../../../components/Client/Client"; -import { MdScreenShare } from "react-icons/md"; -import { socketInit } from "../../../socket"; -import { ACTIONS } from "../../../actions"; -import { - useLocation, - useNavigate, - useParams, - // Navigate, -} from "react-router-dom"; +import React from "react"; import { toast } from "react-hot-toast"; +import { MdScreenShare } from "react-icons/md"; import { useSelector } from "react-redux"; +import { useNavigate, useParams } from "react-router-dom"; +import Client from "../../../components/Client/Client"; +import Editor from "../../../components/Editor/Editor"; import { useWebRTC } from "../../../hooks/useWebRTC"; +import styles from "./EditorPage.module.css"; const EditorPage = () => { const { id: roomId } = useParams(); @@ -23,7 +16,7 @@ const EditorPage = () => { const { user } = useSelector(state => state.auth); //taking user from global state - const { clients, socket } = useWebRTC(roomId, user); + const { clients, socket, codeRef } = useWebRTC(roomId, user); // const socketRef = useRef(); // useEffect(() => { @@ -87,7 +80,13 @@ const EditorPage = () => {
- + { + codeRef.current = code; + }} + />
); From 2047ca571a6caeb4524eca1d8fdca764c85f43a9 Mon Sep 17 00:00:00 2001 From: Lalit sharma Date: Thu, 3 Aug 2023 17:53:00 +0530 Subject: [PATCH 7/8] small change in navbar --- frontend/src/components/shared/Navigation/Navigation.jsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/shared/Navigation/Navigation.jsx b/frontend/src/components/shared/Navigation/Navigation.jsx index 65d5489..890547e 100644 --- a/frontend/src/components/shared/Navigation/Navigation.jsx +++ b/frontend/src/components/shared/Navigation/Navigation.jsx @@ -1,7 +1,7 @@ import { Link } from "react-router-dom"; import React from "react"; import { TbBrandSpacehey } from "react-icons/tb"; -import { RiLogoutCircleRFill } from "react-icons/ri"; +import { IoLogOut } from "react-icons/io5"; import styles from "./Navigation.module.css"; import { logout } from "../../../http"; import { setAuth } from "../../../store/authSlice"; @@ -22,7 +22,7 @@ const Navigation = () => { const dispatch = useDispatch(); - const { isAuth, user } = useSelector((state) => state.auth); + const { isAuth, user } = useSelector(state => state.auth); async function logoutUser() { try { @@ -57,7 +57,7 @@ const Navigation = () => { {isAuth && (