From 0bdba385b84d200ccc79cdcaedc11e180b0de25c Mon Sep 17 00:00:00 2001 From: Github2k10 Date: Sat, 13 Apr 2024 17:29:50 +0530 Subject: [PATCH 1/8] update --- Frontend/src/components/NavBar/NavBar.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Frontend/src/components/NavBar/NavBar.jsx b/Frontend/src/components/NavBar/NavBar.jsx index 57b0923..3e65429 100644 --- a/Frontend/src/components/NavBar/NavBar.jsx +++ b/Frontend/src/components/NavBar/NavBar.jsx @@ -7,7 +7,7 @@ import style from "./NavBar.module.scss"; import axios from "axios"; const NavBar = () => { - const [isLoggedIn, setLoggedIn] = useState(true); + const [isLoggedIn, setLoggedIn] = useState(false); useEffect(() => { axios @@ -21,7 +21,7 @@ const NavBar = () => { }) .catch((err) => { if (err.response.status == 401) { - setLoggedIn(true); + setLoggedIn(false); } console.log(err); From 18bae677b04351889b92bd25545db818b4dc8270 Mon Sep 17 00:00:00 2001 From: Github2k10 Date: Thu, 18 Apr 2024 21:37:29 +0530 Subject: [PATCH 2/8] updates --- .env | 1 + Frontend/.eslintrc.cjs => .eslintrc.cjs | 0 Frontend/.gitignore => .gitignore | 0 Backend/.env | 3 +- Backend/index.js | 8 +- Backend/middleware/authMiddleware.js | 4 +- Backend/models/user.js | 2 +- Backend/package.json | 3 +- Backend/routes/auth.js | 7 +- Frontend/README.md | 8 - Frontend/src/components/Editor/Editor.jsx | 14 -- .../src/components/Editor/Editor.module.scss | 0 Frontend/src/components/NavBar/NavBar.jsx | 93 -------- README.md | 9 +- Frontend/index.html => index.html | 0 .../package-lock.json => package-lock.json | 24 ++ Frontend/package.json => package.json | 1 + {Frontend/public => public}/vite.svg | 0 {Frontend/src => src}/App.jsx | 0 {Frontend/src => src}/App.module.scss | 0 {Frontend/src => src}/assets/background.svg | 0 .../src => src}/assets/icon-logo-light.png | Bin .../src => src}/assets/logo--codelab.png | Bin {Frontend/src => src}/assets/logo-Dark.png | Bin .../src => src}/assets/logo-icon-dark.png | Bin {Frontend/src => src}/assets/logo-light.png | Bin {Frontend/src => src}/assets/plus.svg | 0 {Frontend/src => src}/assets/right-arrow.svg | 0 {Frontend/src => src}/assets/share.svg | 0 src/components/Editor/Editor.jsx | 98 +++++++++ src/components/Editor/Editor.module.scss | 9 + .../components/ErrorPage/ErrorPage.jsx | 0 .../components/ErrorPage/ErrorPage.scss | 0 .../src => src}/components/Home/Home.jsx | 0 .../src => src}/components/Home/Home.scss | 0 .../src => src}/components/Login/Login.jsx | 32 ++- .../src => src}/components/Login/Login.scss | 0 src/components/NavBar/NavBar.jsx | 207 ++++++++++++++++++ .../components/NavBar/NavBar.module.scss | 62 ++++++ .../src => src}/components/SignUp/SignUp.jsx | 9 +- .../src => src}/components/SignUp/SignUp.scss | 0 {Frontend/src => src}/components/index.js | 4 +- src/constant/EditorHelper.jsx | 15 ++ {Frontend/src => src}/constant/images.js | 0 {Frontend/src => src}/constant/index.js | 0 {Frontend/src => src}/index.css | 0 {Frontend/src => src}/main.jsx | 9 +- src/services/Axios.js | 19 ++ src/services/socket.js | 17 ++ src/services/toast.js | 14 ++ {Frontend/src => src}/style/btn1.scss | 0 {Frontend/src => src}/style/btn2.scss | 0 Frontend/vite.config.js => vite.config.js | 0 53 files changed, 526 insertions(+), 146 deletions(-) create mode 100644 .env rename Frontend/.eslintrc.cjs => .eslintrc.cjs (100%) rename Frontend/.gitignore => .gitignore (100%) delete mode 100644 Frontend/README.md delete mode 100644 Frontend/src/components/Editor/Editor.jsx delete mode 100644 Frontend/src/components/Editor/Editor.module.scss delete mode 100644 Frontend/src/components/NavBar/NavBar.jsx rename Frontend/index.html => index.html (100%) rename Frontend/package-lock.json => package-lock.json (99%) rename Frontend/package.json => package.json (96%) rename {Frontend/public => public}/vite.svg (100%) rename {Frontend/src => src}/App.jsx (100%) rename {Frontend/src => src}/App.module.scss (100%) rename {Frontend/src => src}/assets/background.svg (100%) rename {Frontend/src => src}/assets/icon-logo-light.png (100%) rename {Frontend/src => src}/assets/logo--codelab.png (100%) rename {Frontend/src => src}/assets/logo-Dark.png (100%) rename {Frontend/src => src}/assets/logo-icon-dark.png (100%) rename {Frontend/src => src}/assets/logo-light.png (100%) rename {Frontend/src => src}/assets/plus.svg (100%) rename {Frontend/src => src}/assets/right-arrow.svg (100%) rename {Frontend/src => src}/assets/share.svg (100%) create mode 100644 src/components/Editor/Editor.jsx create mode 100644 src/components/Editor/Editor.module.scss rename {Frontend/src => src}/components/ErrorPage/ErrorPage.jsx (100%) rename {Frontend/src => src}/components/ErrorPage/ErrorPage.scss (100%) rename {Frontend/src => src}/components/Home/Home.jsx (100%) rename {Frontend/src => src}/components/Home/Home.scss (100%) rename {Frontend/src => src}/components/Login/Login.jsx (79%) rename {Frontend/src => src}/components/Login/Login.scss (100%) create mode 100644 src/components/NavBar/NavBar.jsx rename {Frontend/src => src}/components/NavBar/NavBar.module.scss (55%) rename {Frontend/src => src}/components/SignUp/SignUp.jsx (92%) rename {Frontend/src => src}/components/SignUp/SignUp.scss (100%) rename {Frontend/src => src}/components/index.js (63%) create mode 100644 src/constant/EditorHelper.jsx rename {Frontend/src => src}/constant/images.js (100%) rename {Frontend/src => src}/constant/index.js (100%) rename {Frontend/src => src}/index.css (100%) rename {Frontend/src => src}/main.jsx (78%) create mode 100644 src/services/Axios.js create mode 100644 src/services/socket.js create mode 100644 src/services/toast.js rename {Frontend/src => src}/style/btn1.scss (100%) rename {Frontend/src => src}/style/btn2.scss (100%) rename Frontend/vite.config.js => vite.config.js (100%) diff --git a/.env b/.env new file mode 100644 index 0000000..2b9961f --- /dev/null +++ b/.env @@ -0,0 +1 @@ +BASE_URL=https://code-lab-backend-one.vercel.app/ \ No newline at end of file diff --git a/Frontend/.eslintrc.cjs b/.eslintrc.cjs similarity index 100% rename from Frontend/.eslintrc.cjs rename to .eslintrc.cjs diff --git a/Frontend/.gitignore b/.gitignore similarity index 100% rename from Frontend/.gitignore rename to .gitignore diff --git a/Backend/.env b/Backend/.env index 22fdb16..2354f31 100644 --- a/Backend/.env +++ b/Backend/.env @@ -1,2 +1,3 @@ PORT=8000 -SECERT_KEY=wertyuikjhgfdrtyuioolkjhb \ No newline at end of file +SECERT_KEY=wertyuikjhgfdrtyuioolkjhb +mongodb_String="mongodb+srv://AnkitKumar:ZvPBXuG7kzUNSxJD@cluster0.vfkc89m.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" \ No newline at end of file diff --git a/Backend/index.js b/Backend/index.js index a5d9268..bdc5df0 100644 --- a/Backend/index.js +++ b/Backend/index.js @@ -3,12 +3,12 @@ const http = require("http"); const cors = require("cors"); const dotenv = require("dotenv"); const cookieParser = require("cookie-parser"); +dotenv.config(); const authRoute = require("./routes/auth"); const setupSocketIO = require("./socket/socket"); const protectedRoutes = require("./routes/protectedRoutes"); -dotenv.config(); const PORT = process.env.PORT; const corsOptions = { @@ -27,9 +27,13 @@ app.use(express.urlencoded({ extended: true })); app.use("/user", authRoute); app.use("/protected", protectedRoutes); +app.get("/", (req, res) => { + res.send("Greetings from CodeLab!!!") +}) + const server = http.createServer(app); setupSocketIO(server); server.listen(PORT, () => { - console.log(`Server started at http://localhost:${PORT}`); + console.log(`Server started`); }); diff --git a/Backend/middleware/authMiddleware.js b/Backend/middleware/authMiddleware.js index 905c952..8737c3c 100644 --- a/Backend/middleware/authMiddleware.js +++ b/Backend/middleware/authMiddleware.js @@ -18,14 +18,14 @@ const jwt = require("jsonwebtoken"); const verifyToken = (req, res, next) => { try { // Extract the token from the AuthorizationToken header + console.log(req.cookies); const token = req.cookies.AuthToken; - console.log("meddleware token: ", token) // If no token is provided, send a 401 Unauthorized response if (!token) { return res.status(401).json({ error: "Access denied" }); } - + // Verify the token and decode it const decode = jwt.verify(token, process.env.SECERT_KEY); diff --git a/Backend/models/user.js b/Backend/models/user.js index 441648b..8e30bc4 100644 --- a/Backend/models/user.js +++ b/Backend/models/user.js @@ -1,6 +1,6 @@ const mongoose = require("mongoose"); -mongoose.connect("mongodb://localhost:27017/test"); +mongoose.connect(process.env.mongodb_String); const userSchema = new mongoose.Schema({ username: { diff --git a/Backend/package.json b/Backend/package.json index ae865a8..84c3024 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -4,7 +4,8 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "nodemon index.js" }, "keywords": [], "author": "", diff --git a/Backend/routes/auth.js b/Backend/routes/auth.js index 2cd4607..4135f90 100644 --- a/Backend/routes/auth.js +++ b/Backend/routes/auth.js @@ -105,12 +105,11 @@ router.post("/register", async (req, res) => { } ); - res.cookie("AuthToken", token, { maxAge: 600000000, httpOnly: true }); - res.status(201).json({ userId: userData._id, username: userData.username, email: userData.email, + AuthToken: token, }); } catch (err) { // Handle any errors that occur during the registration process @@ -172,13 +171,11 @@ router.post("/login", async (req, res) => { } ); - res.cookie("AuthToken", token, { maxAge: 600000000, httpOnly: true }); - console.log("login token: ", token) - res.status(200).json({ userId: user._id, username: user.username, email: user.email, + AuthToken: token, }); } catch (err) { // Handle any errors that occur during the authentication process diff --git a/Frontend/README.md b/Frontend/README.md deleted file mode 100644 index f768e33..0000000 --- a/Frontend/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# React + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/Frontend/src/components/Editor/Editor.jsx b/Frontend/src/components/Editor/Editor.jsx deleted file mode 100644 index d3ea623..0000000 --- a/Frontend/src/components/Editor/Editor.jsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from "react"; - -import style from "./Editor.module.scss"; -import NavBar from "../NavBar/NavBar"; - -const Editor = () => { - return ( - <> - - - ); -}; - -export default Editor; diff --git a/Frontend/src/components/Editor/Editor.module.scss b/Frontend/src/components/Editor/Editor.module.scss deleted file mode 100644 index e69de29..0000000 diff --git a/Frontend/src/components/NavBar/NavBar.jsx b/Frontend/src/components/NavBar/NavBar.jsx deleted file mode 100644 index 3e65429..0000000 --- a/Frontend/src/components/NavBar/NavBar.jsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { useEffect, useState } from "react"; -import { Link } from "react-router-dom"; - -import { images } from "../../constant"; -import "../../style/btn2.scss"; -import style from "./NavBar.module.scss"; -import axios from "axios"; - -const NavBar = () => { - const [isLoggedIn, setLoggedIn] = useState(false); - - useEffect(() => { - axios - .get("http://localhost:8000/protected/isLoggedIn", { - withCredentials: true, - }) - .then((res) => { - if (res.response.status == 200) { - setLoggedIn(true); - } - }) - .catch((err) => { - if (err.response.status == 401) { - setLoggedIn(false); - } - - console.log(err); - }); - }, []); - - return ( - <> -
-
- - - -
-
-
- - New file -
-
- {isLoggedIn ? ( - -
-
- - - - Dashboard -
-
- - ) : ( -
- - - Sign in - - -
- )} -
-
-
- - ); -}; - -export default NavBar; diff --git a/README.md b/README.md index 445c8c1..f768e33 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ -# CodeLab \ No newline at end of file +# React + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh diff --git a/Frontend/index.html b/index.html similarity index 100% rename from Frontend/index.html rename to index.html diff --git a/Frontend/package-lock.json b/package-lock.json similarity index 99% rename from Frontend/package-lock.json rename to package-lock.json index 3bef014..13bb483 100644 --- a/Frontend/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "react": "^18.2.0", "react-cookie": "^7.1.4", "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", "react-monaco-editor": "^0.55.0", "react-router-dom": "^6.22.3", "sass": "^1.72.0", @@ -2708,6 +2709,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3841,6 +3850,21 @@ "react": "^18.2.0" } }, + "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-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/Frontend/package.json b/package.json similarity index 96% rename from Frontend/package.json rename to package.json index a4905a2..df2ffe2 100644 --- a/Frontend/package.json +++ b/package.json @@ -17,6 +17,7 @@ "react": "^18.2.0", "react-cookie": "^7.1.4", "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", "react-monaco-editor": "^0.55.0", "react-router-dom": "^6.22.3", "sass": "^1.72.0", diff --git a/Frontend/public/vite.svg b/public/vite.svg similarity index 100% rename from Frontend/public/vite.svg rename to public/vite.svg diff --git a/Frontend/src/App.jsx b/src/App.jsx similarity index 100% rename from Frontend/src/App.jsx rename to src/App.jsx diff --git a/Frontend/src/App.module.scss b/src/App.module.scss similarity index 100% rename from Frontend/src/App.module.scss rename to src/App.module.scss diff --git a/Frontend/src/assets/background.svg b/src/assets/background.svg similarity index 100% rename from Frontend/src/assets/background.svg rename to src/assets/background.svg diff --git a/Frontend/src/assets/icon-logo-light.png b/src/assets/icon-logo-light.png similarity index 100% rename from Frontend/src/assets/icon-logo-light.png rename to src/assets/icon-logo-light.png diff --git a/Frontend/src/assets/logo--codelab.png b/src/assets/logo--codelab.png similarity index 100% rename from Frontend/src/assets/logo--codelab.png rename to src/assets/logo--codelab.png diff --git a/Frontend/src/assets/logo-Dark.png b/src/assets/logo-Dark.png similarity index 100% rename from Frontend/src/assets/logo-Dark.png rename to src/assets/logo-Dark.png diff --git a/Frontend/src/assets/logo-icon-dark.png b/src/assets/logo-icon-dark.png similarity index 100% rename from Frontend/src/assets/logo-icon-dark.png rename to src/assets/logo-icon-dark.png diff --git a/Frontend/src/assets/logo-light.png b/src/assets/logo-light.png similarity index 100% rename from Frontend/src/assets/logo-light.png rename to src/assets/logo-light.png diff --git a/Frontend/src/assets/plus.svg b/src/assets/plus.svg similarity index 100% rename from Frontend/src/assets/plus.svg rename to src/assets/plus.svg diff --git a/Frontend/src/assets/right-arrow.svg b/src/assets/right-arrow.svg similarity index 100% rename from Frontend/src/assets/right-arrow.svg rename to src/assets/right-arrow.svg diff --git a/Frontend/src/assets/share.svg b/src/assets/share.svg similarity index 100% rename from Frontend/src/assets/share.svg rename to src/assets/share.svg diff --git a/src/components/Editor/Editor.jsx b/src/components/Editor/Editor.jsx new file mode 100644 index 0000000..f47840b --- /dev/null +++ b/src/components/Editor/Editor.jsx @@ -0,0 +1,98 @@ +import React, { useState, useRef, useCallback, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import Editor from "@monaco-editor/react"; +import { Toaster } from "react-hot-toast"; + +import notify from "../../services/toast"; +import initSocket from "../../services/socket"; +import style from "./Editor.module.scss"; +import NavBar from "../NavBar/NavBar"; + +const EditorLayout = () => { + const socketRef = useRef(null); + const navigate = useNavigate(); + const [code, setCode] = useState("// here is your code..."); + const editorDidMount = useCallback((editor, monaco) => { + editor.focus(); + }, []); + document.title = "Editor"; + + const { roomId } = useParams(); + const username = document.cookie.split(";")[1].split("=")[1]; + + const handleErrors = (e) => { + console.log("socket error", e); + notify("Socket connection failed, try again later."); + // navigate("/"); + }; + + useEffect(() => { + const init = async () => { + socketRef.current = await initSocket(); + socketRef.current.on("connect_error", (err) => handleErrors(err)); + socketRef.current.on("connect_failed", (err) => handleErrors(err)); + + socketRef.current.emit("JOIN", { roomId, username }); + + socketRef.current.on("JOINED", ({ code, clients }) => { + console.log(clients); + setCode(code); + }); + + socketRef.current.on("code change", ({ newCode }) => { + setCode(newCode); + }); + + socketRef.current.on("disconnected", ({ username }) => { + console.log(username); + }); + }; + + init(); + + window.addEventListener("beforeunload", leave); + + return () => { + window.removeEventListener("beforeunload", leave); + }; + }, []); + + const leave = () => { + if (socketRef.current) { + socketRef.current.emit("leave", roomId); + } + }; + + const options = { + selectOnLineNumbers: true, + }; + + const onChange = (newValue) => { + setCode(newValue); + if (socketRef.current) { + socketRef.current.emit("code change", { roomId, newCode: newValue }); + } + }; + return ( + <> + + +
+ +
+ + + + ); +}; + +export default EditorLayout; diff --git a/src/components/Editor/Editor.module.scss b/src/components/Editor/Editor.module.scss new file mode 100644 index 0000000..9b7d326 --- /dev/null +++ b/src/components/Editor/Editor.module.scss @@ -0,0 +1,9 @@ +.editor{ + width: 100%; + height: calc(100% - 70px); + padding-top: 70px; + z-index: 1; + position: relative; + + // border: 1px solid black; +} \ No newline at end of file diff --git a/Frontend/src/components/ErrorPage/ErrorPage.jsx b/src/components/ErrorPage/ErrorPage.jsx similarity index 100% rename from Frontend/src/components/ErrorPage/ErrorPage.jsx rename to src/components/ErrorPage/ErrorPage.jsx diff --git a/Frontend/src/components/ErrorPage/ErrorPage.scss b/src/components/ErrorPage/ErrorPage.scss similarity index 100% rename from Frontend/src/components/ErrorPage/ErrorPage.scss rename to src/components/ErrorPage/ErrorPage.scss diff --git a/Frontend/src/components/Home/Home.jsx b/src/components/Home/Home.jsx similarity index 100% rename from Frontend/src/components/Home/Home.jsx rename to src/components/Home/Home.jsx diff --git a/Frontend/src/components/Home/Home.scss b/src/components/Home/Home.scss similarity index 100% rename from Frontend/src/components/Home/Home.scss rename to src/components/Home/Home.scss diff --git a/Frontend/src/components/Login/Login.jsx b/src/components/Login/Login.jsx similarity index 79% rename from Frontend/src/components/Login/Login.jsx rename to src/components/Login/Login.jsx index a241f41..fe6d10c 100644 --- a/Frontend/src/components/Login/Login.jsx +++ b/src/components/Login/Login.jsx @@ -5,6 +5,9 @@ import { Link, useNavigate } from "react-router-dom"; import "./Login.scss"; +const baseURL = "https://code-lab-backend-one.vercel.app"; +// const baseURL = "http://localhost:8000"; + const Login = () => { const [cookies, setCookie] = useCookies(["user"]); const navigate = useNavigate(); @@ -17,28 +20,37 @@ const Login = () => { const password = e.target.password.value; axios - .post("http://localhost:8000/user/login", { - email: email, - password: password, - }) + .post( + `${baseURL}/user/login`, + { + email: email, + password: password, + }, + { + withCredentials: true, + } + ) .then((res) => { + console.log(res); setCookie("email", email, { path: "/" }); setCookie("username", res.data.username, { path: "/" }); setCookie("userId", res.data.userId, { path: "/" }); + setCookie("AuthToken", res.data.AuthToken, {path: "/"}) navigate("/"); }) .catch((err) => { - if(err.request.status == 400){ - alert("Email or Passwrod Missing.") + console.log(err) + if (err.request.status == 400) { + alert("Email or Passwrod Missing."); } - if(err.request.status == 401){ - alert("Authentication failed: Wrong Email or Password.") + if (err.request.status == 401) { + alert("Authentication failed: Wrong Email or Password."); } - if(err.request.status == 500){ - alert("Ops!!! Server Error.") + if (err.request.status == 500) { + alert("Ops!!! Server Error."); } }); diff --git a/Frontend/src/components/Login/Login.scss b/src/components/Login/Login.scss similarity index 100% rename from Frontend/src/components/Login/Login.scss rename to src/components/Login/Login.scss diff --git a/src/components/NavBar/NavBar.jsx b/src/components/NavBar/NavBar.jsx new file mode 100644 index 0000000..ae88e45 --- /dev/null +++ b/src/components/NavBar/NavBar.jsx @@ -0,0 +1,207 @@ +import React, { useEffect, useState, useRef } from "react"; +import { Link, useNavigate, useParams, useLocation } from "react-router-dom"; +import { v4 as uuidV4 } from "uuid"; +import { Toaster } from "react-hot-toast"; +import axios from "axios"; + +import notify from "../../services/toast"; +import { images } from "../../constant"; +import "../../style/btn1.scss"; +import "../../style/btn2.scss"; +import style from "./NavBar.module.scss"; + +const baseURL = "https://code-lab-backend-one.vercel.app"; +// const baseURL = "http://localhost:8000"; + +const NavBar = () => { + const param = useParams(); + const boxRef = useRef(null); + const navigate = useNavigate(); + const location = useLocation(); + const [isLoggedIn, setLoggedIn] = useState(false); + const [isBoxVisible, setIsBoxVisible] = useState(false); + + // console.log(location.pathname.split("/")); + const isONEditorPage = null; + const roomId = param.roomId; + const shareableLink = `http://localhost:5173/editor/${roomId}`; + + const createFile = () => { + const roomId = uuidV4(); + navigate(`/editor/${roomId}`); + }; + + const toggleBoxVisibility = () => { + setIsBoxVisible(!isBoxVisible); + }; + + useEffect(() => { + const handleClickOutside = (event) => { + if (boxRef.current && !boxRef.current.contains(event.target)) { + setIsBoxVisible(false); + } + }; + + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); + }; + }, [boxRef]); + + const copyLinkHandler = () => { + navigator.clipboard.writeText(shareableLink); + setIsBoxVisible(false); + notify("Link Copied Successfully."); + }; + + useEffect(() => { + const bearer = document.cookie + .split("; ") + .filter((item) => item.includes("AuthToken")); + + var token = null; + + if(bearer.length > 0){ + token = bearer[0].split("=")[1]; + } + + if (token) { + axios + .get(`${baseURL}/protected/isLoggedIn`, { + withCredentials: true, + headers: { + "Content-Type": "application/json", + Authorization: "Bearer " + token, + }, + }) + .then((res) => { + if (res.status === 200) { + setLoggedIn(true); + } + }) + .catch((err) => { + if (err && err.response && err.response.status === 401) { + setLoggedIn(false); + } + }); + } + }, []); + + return ( + <> +
+
+ + + +
+ {roomId ? ( +
+
+ + + + + Share +
+ + {isBoxVisible && ( +
{ + event.stopPropagation(); + }} + > +

Share this link with other people:

+
+ + + + + + + + Copy link + +
+
+ )} +
+ ) : ( +
createFile()}> +
+ + New file +
+
+ )} + {isLoggedIn ? ( + +
+
+ + + + Dashboard +
+
+ + ) : ( +
+ + + Sign in + + +
+ )} +
+
+
+ + + ); +}; + +export default NavBar; diff --git a/Frontend/src/components/NavBar/NavBar.module.scss b/src/components/NavBar/NavBar.module.scss similarity index 55% rename from Frontend/src/components/NavBar/NavBar.module.scss rename to src/components/NavBar/NavBar.module.scss index c8fc55c..7e7fa25 100644 --- a/Frontend/src/components/NavBar/NavBar.module.scss +++ b/src/components/NavBar/NavBar.module.scss @@ -1,6 +1,7 @@ .nav{ width: 100%; background-color: #fefaf3; + z-index: 5; padding: 10px 20px; position: fixed; @@ -52,6 +53,66 @@ } } + .share{ + .img{ + width: 120px; + padding: 5px 10px 5px 0px; + position: relative; + cursor: pointer; + font-size: 16px; + font-weight: 500; + + svg{ + padding: 5px; + width: 23px; + } + } + + .shareNav{ + position: absolute; + width: 350px; + height: fit-content; + right: 8%; + margin-top: 10px; + padding: 30px; + border-radius: 10px; + background-color: white; + box-shadow: rgba(0, 0, 0, 0.02) 0px 1px 3px 0px, rgba(27, 31, 35, 0.15) 0px 0px 0px 1px; + + div{ + display: flex; + justify-content: space-between; + align-items: center; + margin: 10px 0px; + + input{ + padding: 10px; + border-radius: 5px; + outline: none; + font-size: 14px; + border: 1px solid gray; + } + + span{ + color: white; + padding: 3px 15px 5px 10px; + border-radius: 25px; + background-color: #5f5fff; + + svg{ + height: 20px; + width: 20px; + margin: 5px; + } + + &:active{ + transform: scale(0.95); + } + } + } + } + } + .dashboard{ color: white; border-radius: 10px; @@ -82,6 +143,7 @@ a{ text-decoration: none; color: black; + white-space: nowrap; span{ font-size: 17px; diff --git a/Frontend/src/components/SignUp/SignUp.jsx b/src/components/SignUp/SignUp.jsx similarity index 92% rename from Frontend/src/components/SignUp/SignUp.jsx rename to src/components/SignUp/SignUp.jsx index e6d9289..2008d01 100644 --- a/Frontend/src/components/SignUp/SignUp.jsx +++ b/src/components/SignUp/SignUp.jsx @@ -5,6 +5,9 @@ import { Link, useNavigate } from "react-router-dom"; import "./SignUp.scss"; +const baseURL = "https://code-lab-backend-one.vercel.app"; +// const baseURL = "http://localhost:8000"; + const SignUp = () => { const [cookies, setCookie] = useCookies(["username"]); const navigate = useNavigate(); @@ -32,15 +35,19 @@ const SignUp = () => { } axios - .post("http://localhost:8000/user/register", { + .post(`${baseURL}/user/register`, { username: username, email: email, password: password, + }, { + withCredentials: true }) .then((res) => { setCookie("email", email, { path: "/" }); setCookie("username", res.data.username, { path: "/" }); setCookie("userId", res.data.userId, { path: "/" }); + setCookie("AuthToken", res.data.AuthToken, {path: "/"}) + navigate("/"); }) .catch((error) => { diff --git a/Frontend/src/components/SignUp/SignUp.scss b/src/components/SignUp/SignUp.scss similarity index 100% rename from Frontend/src/components/SignUp/SignUp.scss rename to src/components/SignUp/SignUp.scss diff --git a/Frontend/src/components/index.js b/src/components/index.js similarity index 63% rename from Frontend/src/components/index.js rename to src/components/index.js index 7006910..f9b698c 100644 --- a/Frontend/src/components/index.js +++ b/src/components/index.js @@ -3,6 +3,6 @@ import NavBar from "./NavBar/NavBar"; import ErrorPage from "./ErrorPage/ErrorPage"; import SignUp from "./SignUp/SignUp"; import Login from "./Login/Login"; -import Editor from "./Editor/Editor"; +import EditorLayout from "./Editor/Editor"; -export { Home, NavBar, ErrorPage, SignUp, Login, Editor }; +export { Home, NavBar, ErrorPage, SignUp, Login, EditorLayout }; diff --git a/src/constant/EditorHelper.jsx b/src/constant/EditorHelper.jsx new file mode 100644 index 0000000..38e26d8 --- /dev/null +++ b/src/constant/EditorHelper.jsx @@ -0,0 +1,15 @@ +import React, { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import { v4 as uuidV4 } from "uuid"; + +const EditorHelper = () => { + const navigate = useNavigate(); + const roomId = uuidV4(); + + useEffect(() => { + navigate(`/editor/${roomId}`); + }, []); + return <>; +}; + +export default EditorHelper; diff --git a/Frontend/src/constant/images.js b/src/constant/images.js similarity index 100% rename from Frontend/src/constant/images.js rename to src/constant/images.js diff --git a/Frontend/src/constant/index.js b/src/constant/index.js similarity index 100% rename from Frontend/src/constant/index.js rename to src/constant/index.js diff --git a/Frontend/src/index.css b/src/index.css similarity index 100% rename from Frontend/src/index.css rename to src/index.css diff --git a/Frontend/src/main.jsx b/src/main.jsx similarity index 78% rename from Frontend/src/main.jsx rename to src/main.jsx index 9ad80ed..14efe2a 100644 --- a/Frontend/src/main.jsx +++ b/src/main.jsx @@ -1,9 +1,9 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import { v4 as uuidV4 } from "uuid"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; -import { Editor, ErrorPage, Login, SignUp } from "./components"; +import { EditorLayout, ErrorPage, Login, SignUp } from "./components"; +import EditorHelper from "./constant/EditorHelper.jsx"; import App from "./App.jsx"; import "./index.css"; @@ -24,12 +24,11 @@ const router = new createBrowserRouter([ }, { path: "/editor", - element:
-
+ element: , }, { path: "/editor/:roomId", - element: , + element: , }, ]); diff --git a/src/services/Axios.js b/src/services/Axios.js new file mode 100644 index 0000000..2a5cef5 --- /dev/null +++ b/src/services/Axios.js @@ -0,0 +1,19 @@ +import axios from 'axios'; +import { useCookies } from "react-cookie"; + +const axiosInstance = () => { + const {cookies, setCookie} = useCookies(); + + console.log(cookies); + + return axios.create({ + baseURL: 'https://code-lab-backend-one.vercel.app', + withCredentials: true, + headers: { + 'Content-Type': 'application/json', + 'Cookie': cookies + } + }); +} + +export default axiosInstance; \ No newline at end of file diff --git a/src/services/socket.js b/src/services/socket.js new file mode 100644 index 0000000..6a5866c --- /dev/null +++ b/src/services/socket.js @@ -0,0 +1,17 @@ +import { io } from "socket.io-client"; + +const baseURL = "https://code-lab-backend-one.vercel.app"; +// const baseURL = "http://localhost:8000"; + +const initSocket = async () => { + const option = { + "force new connection": false, + reconnectionAttempt: false, + timeout: 10000, + transport: ["websocket"], + }; + + return io(baseURL, option); +}; + +export default initSocket; diff --git a/src/services/toast.js b/src/services/toast.js new file mode 100644 index 0000000..be9737a --- /dev/null +++ b/src/services/toast.js @@ -0,0 +1,14 @@ +import toast from "react-hot-toast"; + +const notify = (message) => + toast(message, { + duration: 2000, + position: "top-center", + style: { + backgroundColor: "#5f5fff", + color: "white", + width: "250px", + }, + }); + +export default notify \ No newline at end of file diff --git a/Frontend/src/style/btn1.scss b/src/style/btn1.scss similarity index 100% rename from Frontend/src/style/btn1.scss rename to src/style/btn1.scss diff --git a/Frontend/src/style/btn2.scss b/src/style/btn2.scss similarity index 100% rename from Frontend/src/style/btn2.scss rename to src/style/btn2.scss diff --git a/Frontend/vite.config.js b/vite.config.js similarity index 100% rename from Frontend/vite.config.js rename to vite.config.js From db3d1e9e7b7b5e0b0a3c81ea037a1ed1dcf2769d Mon Sep 17 00:00:00 2001 From: Github2k10 Date: Thu, 18 Apr 2024 23:00:47 +0530 Subject: [PATCH 3/8] update --- README.md | 9 +-------- src/components/Login/Login.jsx | 3 --- src/components/NavBar/NavBar.jsx | 1 - src/components/SignUp/SignUp.jsx | 2 -- src/services/Axios.js | 19 ------------------- 5 files changed, 1 insertion(+), 33 deletions(-) delete mode 100644 src/services/Axios.js diff --git a/README.md b/README.md index f768e33..313d1b5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1 @@ -# React + Vite - -This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. - -Currently, two official plugins are available: - -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +# CodeLab (In Development phase) \ No newline at end of file diff --git a/src/components/Login/Login.jsx b/src/components/Login/Login.jsx index fe6d10c..3833f34 100644 --- a/src/components/Login/Login.jsx +++ b/src/components/Login/Login.jsx @@ -26,9 +26,6 @@ const Login = () => { email: email, password: password, }, - { - withCredentials: true, - } ) .then((res) => { console.log(res); diff --git a/src/components/NavBar/NavBar.jsx b/src/components/NavBar/NavBar.jsx index ae88e45..6d778cb 100644 --- a/src/components/NavBar/NavBar.jsx +++ b/src/components/NavBar/NavBar.jsx @@ -68,7 +68,6 @@ const NavBar = () => { if (token) { axios .get(`${baseURL}/protected/isLoggedIn`, { - withCredentials: true, headers: { "Content-Type": "application/json", Authorization: "Bearer " + token, diff --git a/src/components/SignUp/SignUp.jsx b/src/components/SignUp/SignUp.jsx index 2008d01..30129bd 100644 --- a/src/components/SignUp/SignUp.jsx +++ b/src/components/SignUp/SignUp.jsx @@ -39,8 +39,6 @@ const SignUp = () => { username: username, email: email, password: password, - }, { - withCredentials: true }) .then((res) => { setCookie("email", email, { path: "/" }); diff --git a/src/services/Axios.js b/src/services/Axios.js deleted file mode 100644 index 2a5cef5..0000000 --- a/src/services/Axios.js +++ /dev/null @@ -1,19 +0,0 @@ -import axios from 'axios'; -import { useCookies } from "react-cookie"; - -const axiosInstance = () => { - const {cookies, setCookie} = useCookies(); - - console.log(cookies); - - return axios.create({ - baseURL: 'https://code-lab-backend-one.vercel.app', - withCredentials: true, - headers: { - 'Content-Type': 'application/json', - 'Cookie': cookies - } - }); -} - -export default axiosInstance; \ No newline at end of file From d7776a3111e2b7632d635433136637ff9c9f036f Mon Sep 17 00:00:00 2001 From: Github2k10 Date: Fri, 19 Apr 2024 16:55:29 +0530 Subject: [PATCH 4/8] update --- Backend/.env | 2 +- Backend/index.js | 1 - Backend/socket/socket.js | 5 +++ src/App.jsx | 15 ++++++-- src/components/Editor/Editor.jsx | 63 ++++++++++++++++---------------- src/components/Login/Login.jsx | 2 +- src/components/NavBar/NavBar.jsx | 5 +-- src/components/SignUp/SignUp.jsx | 2 +- src/services/actions.js | 20 ++++++++++ src/services/socket.js | 2 +- 10 files changed, 74 insertions(+), 43 deletions(-) create mode 100644 src/services/actions.js diff --git a/Backend/.env b/Backend/.env index 2354f31..288d8dc 100644 --- a/Backend/.env +++ b/Backend/.env @@ -1,3 +1,3 @@ PORT=8000 SECERT_KEY=wertyuikjhgfdrtyuioolkjhb -mongodb_String="mongodb+srv://AnkitKumar:ZvPBXuG7kzUNSxJD@cluster0.vfkc89m.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" \ No newline at end of file +MONGODB_STRING="mongodb+srv://AnkitKumar:ZvPBXuG7kzUNSxJD@cluster0.vfkc89m.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" \ No newline at end of file diff --git a/Backend/index.js b/Backend/index.js index bdc5df0..39c8a80 100644 --- a/Backend/index.js +++ b/Backend/index.js @@ -21,7 +21,6 @@ const app = express(); app.use(cookieParser()); app.use(cors(corsOptions)); -app.options("*", cors(corsOptions)); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use("/user", authRoute); diff --git a/Backend/socket/socket.js b/Backend/socket/socket.js index 68d314b..4e55422 100644 --- a/Backend/socket/socket.js +++ b/Backend/socket/socket.js @@ -57,3 +57,8 @@ const setupSocketIO = (server) => { }; module.exports = setupSocketIO; + + +//https://707e5cbe-f302-4ed9-b77e-c2853dfd23cd@api.glitch.com/git/uttermost-somber-close +//https://707e5cbe-f302-4ed9-b77e-c2853dfd23cd@api.glitch.com/git/uttermost-somber-close +//uttermost-somber-close \ No newline at end of file diff --git a/src/App.jsx b/src/App.jsx index 34f105d..ad5f7d3 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,11 +1,18 @@ -import React, { useCallback, useState } from "react"; -import Editor from "@monaco-editor/react"; +import React from "react"; +import { v4 as uuidV4 } from "uuid"; +import { useNavigate } from "react-router-dom"; import style from "./App.module.scss"; import { NavBar } from "./components"; -import { images } from "./constant"; function App() { + const navigate = useNavigate(); + + const createFile = () => { + const roomId = uuidV4(); + navigate(`/editor/${roomId}`); + }; + return ( <> @@ -22,7 +29,7 @@ function App() { interviews, pair programming, teaching... you name it.

-
+
diff --git a/src/components/Login/Login.jsx b/src/components/Login/Login.jsx index 3833f34..f121659 100644 --- a/src/components/Login/Login.jsx +++ b/src/components/Login/Login.jsx @@ -5,7 +5,7 @@ import { Link, useNavigate } from "react-router-dom"; import "./Login.scss"; -const baseURL = "https://code-lab-backend-one.vercel.app"; +const baseURL = "https://beneficial-coherent-butterfly.glitch.me/"; // const baseURL = "http://localhost:8000"; const Login = () => { diff --git a/src/components/NavBar/NavBar.jsx b/src/components/NavBar/NavBar.jsx index 6d778cb..df4f6f7 100644 --- a/src/components/NavBar/NavBar.jsx +++ b/src/components/NavBar/NavBar.jsx @@ -10,7 +10,7 @@ import "../../style/btn1.scss"; import "../../style/btn2.scss"; import style from "./NavBar.module.scss"; -const baseURL = "https://code-lab-backend-one.vercel.app"; +const baseURL = "https://beneficial-coherent-butterfly.glitch.me/"; // const baseURL = "http://localhost:8000"; const NavBar = () => { @@ -21,10 +21,9 @@ const NavBar = () => { const [isLoggedIn, setLoggedIn] = useState(false); const [isBoxVisible, setIsBoxVisible] = useState(false); - // console.log(location.pathname.split("/")); const isONEditorPage = null; const roomId = param.roomId; - const shareableLink = `http://localhost:5173/editor/${roomId}`; + const shareableLink = `${baseURL}editor/${roomId}`; const createFile = () => { const roomId = uuidV4(); diff --git a/src/components/SignUp/SignUp.jsx b/src/components/SignUp/SignUp.jsx index 30129bd..0f61263 100644 --- a/src/components/SignUp/SignUp.jsx +++ b/src/components/SignUp/SignUp.jsx @@ -5,7 +5,7 @@ import { Link, useNavigate } from "react-router-dom"; import "./SignUp.scss"; -const baseURL = "https://code-lab-backend-one.vercel.app"; +const baseURL = "https://beneficial-coherent-butterfly.glitch.me/"; // const baseURL = "http://localhost:8000"; const SignUp = () => { diff --git a/src/services/actions.js b/src/services/actions.js new file mode 100644 index 0000000..76e8ada --- /dev/null +++ b/src/services/actions.js @@ -0,0 +1,20 @@ +const ACTIONS = { + JOIN_REQUEST: "join-request", + JOIN_ACCEPTED: "join-accepted", + USER_JOINED: "user-joined", + USER_DISCONNECTED: "user-disconnected", + SYNC_FILES: "sync-files", + FILE_CREATED: "file-created", + FILE_UPDATED: "file-updated", + FILE_RENAMED: "file-renamed", + FILE_DELETED: "file-deleted", + USER_OFFLINE: "offline", + USER_ONLINE: "online", + SEND_MESSAGE: "send-message", + RECEIVE_MESSAGE: "receive-message", + TYPING_START: "typing-start", + TYPING_PAUSE: "typing-pause", + USERNAME_EXISTS: "username-exists", +} + +export default ACTIONS; \ No newline at end of file diff --git a/src/services/socket.js b/src/services/socket.js index 6a5866c..be06ca1 100644 --- a/src/services/socket.js +++ b/src/services/socket.js @@ -1,6 +1,6 @@ import { io } from "socket.io-client"; -const baseURL = "https://code-lab-backend-one.vercel.app"; +const baseURL = "https://beneficial-coherent-butterfly.glitch.me/"; // const baseURL = "http://localhost:8000"; const initSocket = async () => { From f6edfe69f1cbef77d11f603b685e7d60d9ef2b18 Mon Sep 17 00:00:00 2001 From: Github2k10 Date: Fri, 19 Apr 2024 16:57:29 +0530 Subject: [PATCH 5/8] update --- src/components/NavBar/NavBar.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/NavBar/NavBar.jsx b/src/components/NavBar/NavBar.jsx index df4f6f7..d9c956c 100644 --- a/src/components/NavBar/NavBar.jsx +++ b/src/components/NavBar/NavBar.jsx @@ -23,7 +23,7 @@ const NavBar = () => { const isONEditorPage = null; const roomId = param.roomId; - const shareableLink = `${baseURL}editor/${roomId}`; + const shareableLink = `https://codelab-live.netlify.app/editor/${roomId}`; const createFile = () => { const roomId = uuidV4(); From 816b565f86a293164cf7488a5ca09f32d3cf58f8 Mon Sep 17 00:00:00 2001 From: Github2k10 Date: Fri, 19 Apr 2024 17:02:00 +0530 Subject: [PATCH 6/8] update --- src/main.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main.jsx b/src/main.jsx index 14efe2a..6c7053a 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -22,14 +22,14 @@ const router = new createBrowserRouter([ path: "/login", element: , }, - { - path: "/editor", - element: , - }, { path: "/editor/:roomId", element: , }, + { + path: "/editor", + element: , + }, ]); ReactDOM.createRoot(document.getElementById("root")).render( From 60abe6fe8ec19445bb136ddc49b1cce59bb4215a Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sun, 14 Jun 2026 11:20:08 +0530 Subject: [PATCH 7/8] upgrading framework to next.js --- .eslintrc.cjs | 17 +- .gitignore | 1 + Backend/.env | 3 - Backend/.gitignore | 24 - Backend/README.md | 1 - Backend/index.js | 38 - Backend/middleware/authMiddleware.js | 45 - Backend/models/user.js | 22 - Backend/package-lock.json | 1840 --------------------- Backend/package.json | 23 - Backend/routes/auth.js | 189 --- Backend/routes/protectedRoutes.js | 36 - index.html | 14 - jsconfig.json | 7 + next.config.js | 4 + package.json | 31 +- public/vite.svg | 1 - server.js | 24 + src/app/api/protected/addRoom/route.js | 28 + src/app/api/protected/isLoggedIn/route.js | 11 + src/app/api/protected/logout/route.js | 14 + src/app/api/user/login/route.js | 48 + src/app/api/user/register/route.js | 61 + src/app/editor/[roomId]/page.jsx | 12 + src/app/editor/page.jsx | 16 + src/app/layout.jsx | 14 + src/app/login/page.jsx | 7 + src/app/not-found.jsx | 27 + src/{App.jsx => app/page.jsx} | 17 +- src/app/register/page.jsx | 7 + src/components/Editor/Editor.jsx | 32 +- src/components/ErrorPage/ErrorPage.jsx | 9 - src/components/ErrorPage/ErrorPage.scss | 0 src/components/Home/Home.jsx | 9 - src/components/Home/Home.scss | 0 src/components/Login/Login.jsx | 39 +- src/components/NavBar/NavBar.jsx | 26 +- src/components/SignUp/SignUp.jsx | 31 +- src/components/index.js | 4 +- src/constant/EditorHelper.jsx | 15 - src/lib/db.js | 24 + src/lib/middleware/auth.js | 16 + src/lib/models/user.js | 10 + {Backend/socket => src/lib}/socket.js | 42 +- src/main.jsx | 39 - src/services/actions.js | 20 - src/services/socket.js | 17 - vite.config.js | 7 - 48 files changed, 430 insertions(+), 2492 deletions(-) delete mode 100644 Backend/.env delete mode 100644 Backend/.gitignore delete mode 100644 Backend/README.md delete mode 100644 Backend/index.js delete mode 100644 Backend/middleware/authMiddleware.js delete mode 100644 Backend/models/user.js delete mode 100644 Backend/package-lock.json delete mode 100644 Backend/package.json delete mode 100644 Backend/routes/auth.js delete mode 100644 Backend/routes/protectedRoutes.js delete mode 100644 index.html create mode 100644 jsconfig.json create mode 100644 next.config.js delete mode 100644 public/vite.svg create mode 100644 server.js create mode 100644 src/app/api/protected/addRoom/route.js create mode 100644 src/app/api/protected/isLoggedIn/route.js create mode 100644 src/app/api/protected/logout/route.js create mode 100644 src/app/api/user/login/route.js create mode 100644 src/app/api/user/register/route.js create mode 100644 src/app/editor/[roomId]/page.jsx create mode 100644 src/app/editor/page.jsx create mode 100644 src/app/layout.jsx create mode 100644 src/app/login/page.jsx create mode 100644 src/app/not-found.jsx rename src/{App.jsx => app/page.jsx} (84%) create mode 100644 src/app/register/page.jsx delete mode 100644 src/components/ErrorPage/ErrorPage.jsx delete mode 100644 src/components/ErrorPage/ErrorPage.scss delete mode 100644 src/components/Home/Home.jsx delete mode 100644 src/components/Home/Home.scss delete mode 100644 src/constant/EditorHelper.jsx create mode 100644 src/lib/db.js create mode 100644 src/lib/middleware/auth.js create mode 100644 src/lib/models/user.js rename {Backend/socket => src/lib}/socket.js (51%) delete mode 100644 src/main.jsx delete mode 100644 src/services/actions.js delete mode 100644 src/services/socket.js delete mode 100644 vite.config.js diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 3e212e1..a817a2a 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,21 +1,6 @@ module.exports = { root: true, - env: { browser: true, es2020: true }, extends: [ - 'eslint:recommended', - 'plugin:react/recommended', - 'plugin:react/jsx-runtime', - 'plugin:react-hooks/recommended', + 'next/core-web-vitals', ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, - settings: { react: { version: '18.2' } }, - plugins: ['react-refresh'], - rules: { - 'react/jsx-no-target-blank': 'off', - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, } diff --git a/.gitignore b/.gitignore index a547bf3..41f028b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.next/ # Editor directories and files .vscode/* diff --git a/Backend/.env b/Backend/.env deleted file mode 100644 index 288d8dc..0000000 --- a/Backend/.env +++ /dev/null @@ -1,3 +0,0 @@ -PORT=8000 -SECERT_KEY=wertyuikjhgfdrtyuioolkjhb -MONGODB_STRING="mongodb+srv://AnkitKumar:ZvPBXuG7kzUNSxJD@cluster0.vfkc89m.mongodb.net/?retryWrites=true&w=majority&appName=Cluster0" \ No newline at end of file diff --git a/Backend/.gitignore b/Backend/.gitignore deleted file mode 100644 index a547bf3..0000000 --- a/Backend/.gitignore +++ /dev/null @@ -1,24 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? diff --git a/Backend/README.md b/Backend/README.md deleted file mode 100644 index 445c8c1..0000000 --- a/Backend/README.md +++ /dev/null @@ -1 +0,0 @@ -# CodeLab \ No newline at end of file diff --git a/Backend/index.js b/Backend/index.js deleted file mode 100644 index 39c8a80..0000000 --- a/Backend/index.js +++ /dev/null @@ -1,38 +0,0 @@ -const express = require("express"); -const http = require("http"); -const cors = require("cors"); -const dotenv = require("dotenv"); -const cookieParser = require("cookie-parser"); -dotenv.config(); - -const authRoute = require("./routes/auth"); -const setupSocketIO = require("./socket/socket"); -const protectedRoutes = require("./routes/protectedRoutes"); - -const PORT = process.env.PORT; - -const corsOptions = { - origin: "http://localhost:5173", - methods: ["POST", "PUT", "GET", "OPTIONS", "HEAD"], - credentials: true, -}; - -const app = express(); - -app.use(cookieParser()); -app.use(cors(corsOptions)); -app.use(express.json()); -app.use(express.urlencoded({ extended: true })); -app.use("/user", authRoute); -app.use("/protected", protectedRoutes); - -app.get("/", (req, res) => { - res.send("Greetings from CodeLab!!!") -}) - -const server = http.createServer(app); -setupSocketIO(server); - -server.listen(PORT, () => { - console.log(`Server started`); -}); diff --git a/Backend/middleware/authMiddleware.js b/Backend/middleware/authMiddleware.js deleted file mode 100644 index 8737c3c..0000000 --- a/Backend/middleware/authMiddleware.js +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Middleware to verify JWT token. - * - * This middleware function is used to verify the JWT token provided in the request header. - * It checks if the token exists and is valid. If the token is valid, it decodes the token, - * attaches the decoded user information to the request object, and calls the next middleware - * in the stack. If the token is missing or invalid, it sends an appropriate error response. - * - * @param {Object} req - The request object. - * @param {Object} res - The response object. - * @param {Function} next - The next middleware function in the stack. - * - * @returns {void} - If the token is valid, it calls the next middleware. - * @returns {Object} 401 - If the token is missing or invalid, it sends an error response. - */ -const jwt = require("jsonwebtoken"); - -const verifyToken = (req, res, next) => { - try { - // Extract the token from the AuthorizationToken header - console.log(req.cookies); - const token = req.cookies.AuthToken; - - // If no token is provided, send a 401 Unauthorized response - if (!token) { - return res.status(401).json({ error: "Access denied" }); - } - - // Verify the token and decode it - const decode = jwt.verify(token, process.env.SECERT_KEY); - - // Attach the decoded user information to the request object - req.userId = decode.userId; - req.username = decode.username; - req.email = decode.email; - - // Proceed to the next middleware - next(); - } catch (err) { - // If the token is invalid, send a 401 Unauthorized response - return res.status(401).json({ error: "Invalid token" }); - } -}; - -module.exports = verifyToken; diff --git a/Backend/models/user.js b/Backend/models/user.js deleted file mode 100644 index 8e30bc4..0000000 --- a/Backend/models/user.js +++ /dev/null @@ -1,22 +0,0 @@ -const mongoose = require("mongoose"); - -mongoose.connect(process.env.mongodb_String); - -const userSchema = new mongoose.Schema({ - username: { - type: String, - required: true, - }, - email: { - type: String, - required: true, - unique: true, - }, - password: { - type: String, - required: true, - }, - rooms: [], -}); - -module.exports = mongoose.model("users", userSchema); diff --git a/Backend/package-lock.json b/Backend/package-lock.json deleted file mode 100644 index aa1654a..0000000 --- a/Backend/package-lock.json +++ /dev/null @@ -1,1840 +0,0 @@ -{ - "name": "backend", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "backend", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "bcrypt": "^5.1.1", - "cookie-parser": "^1.4.6", - "cors": "^2.8.5", - "dotenv": "^16.4.5", - "express": "^4.19.2", - "jsonwebtoken": "^9.0.2", - "mongoose": "^8.2.4", - "socket.io": "^4.7.5" - } - }, - "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", - "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", - "dependencies": { - "detect-libc": "^2.0.0", - "https-proxy-agent": "^5.0.0", - "make-dir": "^3.1.0", - "node-fetch": "^2.6.7", - "nopt": "^5.0.0", - "npmlog": "^5.0.1", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.11" - }, - "bin": { - "node-pre-gyp": "bin/node-pre-gyp" - } - }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.5.tgz", - "integrity": "sha512-XLNOMH66KhJzUJNwT/qlMnS4WsNDWD5ASdyaSH3EtK+F4r/CFGa3jT4GNi4mfOitGvWXtdLgQJkQjxSVrio+jA==", - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==" - }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" - }, - "node_modules/@types/cors": { - "version": "2.8.17", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", - "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "20.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz", - "integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==" - }, - "node_modules/@types/whatwg-url": { - "version": "11.0.4", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.4.tgz", - "integrity": "sha512-lXCmTWSHJvf0TRSO58nm978b8HJ/EdsSsEKLd3ODHFjo+3VGAyyTp4v50nWvwtzBxSMQrVOK7tcuN0zGPLICMw==", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/aproba": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", - "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" - }, - "node_modules/are-we-there-yet": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", - "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/bcrypt": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.1.1.tgz", - "integrity": "sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==", - "hasInstallScript": true, - "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.11", - "node-addon-api": "^5.0.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/bson": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.5.0.tgz", - "integrity": "sha512-DXf1BTAS8vKyR90BO4x5v3rKVarmkdkzwOrnYDFdjAY694ILNDkmA3uRh1xXJEl+C1DAh8XCvAQ+Gh3kzubtpg==", - "engines": { - "node": ">=16.20.1" - } - }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "engines": { - "node": ">=10" - } - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "bin": { - "color-support": "bin.js" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", - "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", - "dependencies": { - "cookie": "0.4.1", - "cookie-signature": "1.0.6" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/cookie-parser/node_modules/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/engine.io": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", - "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", - "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/engine.io/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/engine.io/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gauge": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", - "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", - "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.2", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.1", - "object-assign": "^4.1.1", - "signal-exit": "^3.0.0", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, - "node_modules/jsonwebtoken/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/jwa": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", - "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", - "dependencies": { - "buffer-equal-constant-time": "1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", - "dependencies": { - "jwa": "^1.4.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/kareem": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", - "integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==" - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minipass": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", - "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mongodb": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.3.0.tgz", - "integrity": "sha512-tt0KuGjGtLUhLoU263+xvQmPHEGTw5LbcNC73EoFRYgSHwZt5tsoJC110hDyO1kjQzpgNrpdcSza9PknWN4LrA==", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.0", - "bson": "^6.2.0", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.0.tgz", - "integrity": "sha512-t1Vf+m1I5hC2M5RJx/7AtxgABy1cZmIPQRMXw+gEIPn/cZNF3Oiy+l0UIypUwVB5trcWHq3crg2g3uAR9aAwsQ==", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^13.0.0" - } - }, - "node_modules/mongoose": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.2.4.tgz", - "integrity": "sha512-da/r6zpG+2eAXuhBGUnL6jcBd03zlytoCc5/wq+LyTsmrY9hhPQmSpnugwnfqldtBmUOhB6iMLoV4hNtHRq+ww==", - "dependencies": { - "bson": "^6.2.0", - "kareem": "2.5.1", - "mongodb": "6.3.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "16.0.1" - }, - "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/mpath": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", - "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/mquery/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mquery/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-addon-api": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", - "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==" - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/npmlog": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", - "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dependencies": { - "are-we-there-yet": "^2.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^3.0.0", - "set-blocking": "^2.0.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/semver": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sift": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", - "integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/socket.io": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", - "integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==", - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "cors": "~2.8.5", - "debug": "~4.3.2", - "engine.io": "~6.5.2", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.2.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", - "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", - "dependencies": { - "debug": "~4.3.4", - "ws": "~8.11.0" - } - }, - "node_modules/socket.io-adapter/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-adapter/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/socket.io-parser": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", - "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io-parser/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/socket.io/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/socket.io/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tar": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", - "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^5.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tr46": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz", - "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==", - "dependencies": { - "punycode": "^2.3.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz", - "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==", - "dependencies": { - "tr46": "^4.1.1", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - } - } -} diff --git a/Backend/package.json b/Backend/package.json deleted file mode 100644 index 84c3024..0000000 --- a/Backend/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "backend", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "start": "nodemon index.js" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "bcrypt": "^5.1.1", - "cookie-parser": "^1.4.6", - "cors": "^2.8.5", - "dotenv": "^16.4.5", - "express": "^4.19.2", - "jsonwebtoken": "^9.0.2", - "mongoose": "^8.2.4", - "socket.io": "^4.7.5" - } -} diff --git a/Backend/routes/auth.js b/Backend/routes/auth.js deleted file mode 100644 index 4135f90..0000000 --- a/Backend/routes/auth.js +++ /dev/null @@ -1,189 +0,0 @@ -const express = require("express"); -const jwt = require("jsonwebtoken"); -const bcrypt = require("bcrypt"); -const router = express.Router(); - -const userModel = require("../models/user"); - -/** - * Validates the complexity of a password. - * - * This function checks if a given password meets the following criteria: - * - At least 8 characters long. - * - Contains at least one uppercase letter. - * - Contains at least one lowercase letter. - * - Contains at least one digit. - * - Contains at least one special character from the set [@$!%*?&]. - * - * @param {string} password - The password to validate. - * @returns {boolean} - Returns `true` if the password meets all the criteria, otherwise `false`. - * - * @example - * const isValid = passwordValidater('Password123!'); - * console.log(isValid); // true - * - * @example - * const isValid = passwordValidater('password'); - * console.log(isValid); // false - */ -const passwordValidater = (password) => { - const regex = - /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/; - return regex.test(password); -}; - -/** - * Register a new user. - * - * This route handles user registration. It validates the request body for required fields - * (username, email, password), checks if the password meets the complexity requirements, - * and ensures that no user already exists with the provided email. If all checks pass, - * it hashes the password, creates a new user in the database. - * - * @route POST /register - * @group Authentication - Operations about user authentication - * @param {string} username.body.required - The username of the new user. - * @param {string} email.body.required - The email of the new user. - * @param {string} password.body.required - The password of the new user. - * @returns {object} 201 - Successfully registered a new user. Returns the user's details and a JWT token. - * @returns {object} 400 - Missing required fields or password does not meet complexity requirements. - * @returns {object} 409 - A user already exists with the given email ID. - * @returns {object} 500 - Internal server error. - */ -router.post("/register", async (req, res) => { - try { - const { username, email, password } = req.body; - - // Check for required fields - if (!username) { - res.status(400).json({ message: "Username Required" }); - return; - } - if (!email) { - res.status(400).json({ message: "Email Required" }); - return; - } - if (!password) { - res.status(400).json({ message: "Password Required" }); - return; - } - - // Validate password complexity - if (!passwordValidater(password)) { - res.status(400).json({ - message: - "Password must contain at least 8 characters, 1 uppercase, 1 lowercase, 1 number, and 1 special character", - }); - return; - } - - // Check if user already exists - let user = await userModel.findOne({ email: email }); - if (user) { - res - .status(409) - .json({ message: "User already exists with given email Id" }); - return; - } - - // Hash password and create new user - const hashedPassword = await bcrypt.hash(password, 10); - const userData = new userModel({ - username: username, - email: email, - password: hashedPassword, - }); - - await userData.save(); - - // Generate and return JWT token - const token = jwt.sign( - { userId: userData._id, username: username, email: email }, - process.env.SECERT_KEY, - { - expiresIn: "24h", - } - ); - - res.status(201).json({ - userId: userData._id, - username: userData.username, - email: userData.email, - AuthToken: token, - }); - } catch (err) { - // Handle any errors that occur during the registration process - res.status(500).json({ - message: "Unable to register user", - error: err, - }); - } -}); - -/** - * Authenticate a user and return a JWT token. - * - * This route handles user authentication. It validates the request body for required fields - * (email and password), checks if the user exists in the database, and verifies the password. - * If the user is authenticated successfully, it returns the user's details. - * - * @route POST /login - * @group Authentication - Operations about user authentication - * @param {string} email.body.required - The email of the user. - * @param {string} password.body.required - The password of the user. - * @returns {object} 400 - Missing required fields (email or password). - * @returns {object} 401 - User not found or authentication failed (wrong password). - * @returns {object} 200 - Successfully authenticated. Returns the user's details and a JWT token. - * @returns {object} 500 - Internal server error. - */ -router.post("/login", async (req, res) => { - try { - const { email, password } = req.body; - - // Check for required fields - if (!email) { - return res.status(400).json({ message: "Email Required" }); - } - if (!password) { - return res.status(400).json({ message: "Password Required" }); - } - - // Find user by email - const user = await userModel.findOne({ email: email }); - if (!user) { - return res.status(401).json({ message: "User Not Found" }); - } - - // Verify password - const encryptedPassword = await bcrypt.compare(password, user.password); - if (!encryptedPassword) { - return res - .status(401) - .json({ message: "Authentication failed: Wrong Password" }); - } - - // Generate and return JWT token - const token = jwt.sign( - { userId: user._id, username: user.username, email: email }, - process.env.SECERT_KEY, - { - expiresIn: "24h", - } - ); - - res.status(200).json({ - userId: user._id, - username: user.username, - email: user.email, - AuthToken: token, - }); - } catch (err) { - // Handle any errors that occur during the authentication process - return res.status(500).json({ - message: "Unable to authenticate user", - error: err, - }); - } -}); - -module.exports = router; diff --git a/Backend/routes/protectedRoutes.js b/Backend/routes/protectedRoutes.js deleted file mode 100644 index 2fd3ac8..0000000 --- a/Backend/routes/protectedRoutes.js +++ /dev/null @@ -1,36 +0,0 @@ -const express = require("express"); -const router = express.Router(); - -const userModel = require("../models/user"); -const verifyToken = require("../middleware/authMiddleware"); - -router.post("/addRoom", verifyToken, async (req, res) => { - try { - const { roomId } = req.body; - - if (!roomId) { - return res.status(400).json({ error: "Room Id is required" }); - } - - const user = await userModel.findById(req.userId); - user.rooms.push(roomId); - await user.save(); - - return res.status(201).json({ message: "Room added successfully" }); - } catch (err) { - console.error(err); - return res.status(500).json({ error: "Internal server error" }); - } -}); - -router.get("/isLoggedIn", verifyToken, (req, res) => { - return res.status(200).json({ login: true, message: "User Logged In" }); -}); - -router.get("/logout", verifyToken, (req, res) => { - res.cookie("AuthToken", "", { maxAge: 0 }); - - return res.status(200).json({ message: "Logout successful" }); -}); - -module.exports = router; diff --git a/index.html b/index.html deleted file mode 100644 index dd8b4c4..0000000 --- a/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - CodeLab - - -
- - - diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..2a2e4b3 --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "paths": { + "@/*": ["./*"] + } + } +} diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..4678774 --- /dev/null +++ b/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +export default nextConfig; diff --git a/package.json b/package.json index df2ffe2..ecb0f4d 100644 --- a/package.json +++ b/package.json @@ -1,38 +1,33 @@ { - "name": "frontend", + "name": "codelab", "private": true, - "proxy": "http://localhost:8000", - "version": "0.0.0", + "version": "0.0.1", "type": "module", "scripts": { - "dev": "vite", - "build": "vite build", - "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", - "preview": "vite preview" + "dev": "node server.js", + "build": "next build", + "start": "NODE_ENV=production node server.js", + "lint": "next lint" }, "dependencies": { "@monaco-editor/react": "^4.6.0", "axios": "^1.6.8", + "bcryptjs": "^2.4.3", + "dotenv": "^17.4.2", + "jsonwebtoken": "^9.0.3", "monaco-editor": "^0.44.0", + "mongoose": "^8.24.0", + "next": "^14.2.0", "react": "^18.2.0", - "react-cookie": "^7.1.4", "react-dom": "^18.2.0", "react-hot-toast": "^2.4.1", - "react-monaco-editor": "^0.55.0", - "react-router-dom": "^6.22.3", "sass": "^1.72.0", - "scss": "^0.2.4", + "socket.io": "^4.8.3", "socket.io-client": "^4.7.5", "uuid": "^9.0.1" }, "devDependencies": { - "@types/react": "^18.2.66", - "@types/react-dom": "^18.2.22", - "@vitejs/plugin-react": "^4.2.1", "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.1", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.6", - "vite": "^5.2.0" + "eslint-config-next": "^14.2.0" } } diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..9a0816e --- /dev/null +++ b/server.js @@ -0,0 +1,24 @@ +import { createServer } from "http"; +import next from "next"; +import { config } from "dotenv"; +import { setupSocketIO } from "./src/lib/socket.js"; + +config(); + +const dev = process.env.NODE_ENV !== "production"; +const app = next({ dev }); +const handle = app.getRequestHandler(); + +const port = process.env.PORT || 3000; + +app.prepare().then(() => { + const server = createServer((req, res) => { + handle(req, res); + }); + + setupSocketIO(server); + + server.listen(port, () => { + console.log(`> Ready on http://localhost:${port}`); + }); +}); diff --git a/src/app/api/protected/addRoom/route.js b/src/app/api/protected/addRoom/route.js new file mode 100644 index 0000000..0b9cdd4 --- /dev/null +++ b/src/app/api/protected/addRoom/route.js @@ -0,0 +1,28 @@ +import { NextResponse } from "next/server"; +import dbConnect from "@/src/lib/db"; +import User from "@/src/lib/models/user"; +import { verifyToken } from "@/src/lib/middleware/auth"; + +export async function POST(request) { + try { + const auth = verifyToken(request); + if (auth.error) { + return NextResponse.json({ error: auth.error }, { status: auth.status }); + } + + await dbConnect(); + + const { roomId } = await request.json(); + if (!roomId) { + return NextResponse.json({ error: "Room Id is required" }, { status: 400 }); + } + + const user = await User.findById(auth.userId); + user.rooms.push(roomId); + await user.save(); + + return NextResponse.json({ message: "Room added successfully" }, { status: 201 }); + } catch (err) { + return NextResponse.json({ error: "Internal server error" }, { status: 500 }); + } +} diff --git a/src/app/api/protected/isLoggedIn/route.js b/src/app/api/protected/isLoggedIn/route.js new file mode 100644 index 0000000..45da78a --- /dev/null +++ b/src/app/api/protected/isLoggedIn/route.js @@ -0,0 +1,11 @@ +import { NextResponse } from "next/server"; +import { verifyToken } from "@/src/lib/middleware/auth"; + +export async function GET(request) { + const auth = verifyToken(request); + if (auth.error) { + return NextResponse.json({ error: auth.error }, { status: auth.status }); + } + + return NextResponse.json({ login: true, message: "User Logged In" }, { status: 200 }); +} diff --git a/src/app/api/protected/logout/route.js b/src/app/api/protected/logout/route.js new file mode 100644 index 0000000..8cf2780 --- /dev/null +++ b/src/app/api/protected/logout/route.js @@ -0,0 +1,14 @@ +import { NextResponse } from "next/server"; +import { verifyToken } from "@/src/lib/middleware/auth"; + +export async function GET(request) { + const auth = verifyToken(request); + if (auth.error) { + return NextResponse.json({ error: auth.error }, { status: auth.status }); + } + + const response = NextResponse.json({ message: "Logout successful" }, { status: 200 }); + response.cookies.set("AuthToken", "", { maxAge: 0, path: "/" }); + + return response; +} diff --git a/src/app/api/user/login/route.js b/src/app/api/user/login/route.js new file mode 100644 index 0000000..8be5fdd --- /dev/null +++ b/src/app/api/user/login/route.js @@ -0,0 +1,48 @@ +import { NextResponse } from "next/server"; +import bcrypt from "bcryptjs"; +import jwt from "jsonwebtoken"; +import dbConnect from "@/src/lib/db"; +import User from "@/src/lib/models/user"; + +export async function POST(request) { + try { + await dbConnect(); + + const { email, password } = await request.json(); + + if (!email) { + return NextResponse.json({ message: "Email Required" }, { status: 400 }); + } + if (!password) { + return NextResponse.json({ message: "Password Required" }, { status: 400 }); + } + + const user = await User.findOne({ email }); + if (!user) { + return NextResponse.json({ message: "User Not Found" }, { status: 401 }); + } + + const passwordMatch = await bcrypt.compare(password, user.password); + if (!passwordMatch) { + return NextResponse.json({ message: "Authentication failed: Wrong Password" }, { status: 401 }); + } + + const token = jwt.sign( + { userId: user._id, username: user.username, email }, + process.env.SECERT_KEY, + { expiresIn: "24h" } + ); + + return NextResponse.json( + { + userId: user._id, + username: user.username, + email: user.email, + AuthToken: token, + }, + { status: 200 } + ); + } catch (err) { + return NextResponse.json({ message: "Unable to authenticate user", error: err.message }, { status: 500 }); + } +} diff --git a/src/app/api/user/register/route.js b/src/app/api/user/register/route.js new file mode 100644 index 0000000..18e51d2 --- /dev/null +++ b/src/app/api/user/register/route.js @@ -0,0 +1,61 @@ +import { NextResponse } from "next/server"; +import bcrypt from "bcryptjs"; +import jwt from "jsonwebtoken"; +import dbConnect from "@/src/lib/db"; +import User from "@/src/lib/models/user"; + +const passwordValidater = (password) => { + const regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/; + return regex.test(password); +}; + +export async function POST(request) { + try { + await dbConnect(); + + const { username, email, password } = await request.json(); + + if (!username) { + return NextResponse.json({ message: "Username Required" }, { status: 400 }); + } + if (!email) { + return NextResponse.json({ message: "Email Required" }, { status: 400 }); + } + if (!password) { + return NextResponse.json({ message: "Password Required" }, { status: 400 }); + } + + if (!passwordValidater(password)) { + return NextResponse.json( + { message: "Password must contain at least 8 characters, 1 uppercase, 1 lowercase, 1 number, and 1 special character" }, + { status: 400 } + ); + } + + const existingUser = await User.findOne({ email }); + if (existingUser) { + return NextResponse.json({ message: "User already exists with given email Id" }, { status: 409 }); + } + + const hashedPassword = await bcrypt.hash(password, 10); + const userData = await User.create({ username, email, password: hashedPassword }); + + const token = jwt.sign( + { userId: userData._id, username, email }, + process.env.SECERT_KEY, + { expiresIn: "24h" } + ); + + return NextResponse.json( + { + userId: userData._id, + username: userData.username, + email: userData.email, + AuthToken: token, + }, + { status: 201 } + ); + } catch (err) { + return NextResponse.json({ message: "Unable to register user", error: err.message }, { status: 500 }); + } +} diff --git a/src/app/editor/[roomId]/page.jsx b/src/app/editor/[roomId]/page.jsx new file mode 100644 index 0000000..12acd0f --- /dev/null +++ b/src/app/editor/[roomId]/page.jsx @@ -0,0 +1,12 @@ +"use client"; + +import dynamic from "next/dynamic"; + +const EditorLayout = dynamic( + () => import("@/src/components/Editor/Editor"), + { ssr: false } +); + +export default function EditorPage({ params }) { + return ; +} diff --git a/src/app/editor/page.jsx b/src/app/editor/page.jsx new file mode 100644 index 0000000..88d2c3d --- /dev/null +++ b/src/app/editor/page.jsx @@ -0,0 +1,16 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { v4 as uuidV4 } from "uuid"; + +export default function EditorRedirect() { + const router = useRouter(); + const roomId = uuidV4(); + + useEffect(() => { + router.replace(`/editor/${roomId}`); + }, []); + + return null; +} diff --git a/src/app/layout.jsx b/src/app/layout.jsx new file mode 100644 index 0000000..a255179 --- /dev/null +++ b/src/app/layout.jsx @@ -0,0 +1,14 @@ +import "@/src/index.css"; + +export const metadata = { + title: "CodeLab", + description: "Collaborative online real-time code editor", +}; + +export default function RootLayout({ children }) { + return ( + + {children} + + ); +} diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx new file mode 100644 index 0000000..d4b2906 --- /dev/null +++ b/src/app/login/page.jsx @@ -0,0 +1,7 @@ +import Login from "@/src/components/Login/Login"; + +export const dynamic = "force-dynamic"; + +export default function LoginPage() { + return ; +} diff --git a/src/app/not-found.jsx b/src/app/not-found.jsx new file mode 100644 index 0000000..350de68 --- /dev/null +++ b/src/app/not-found.jsx @@ -0,0 +1,27 @@ +import Link from "next/link"; + +export default function NotFound() { + return ( +
+

404

+

Page Not Found

+

+ The page you are looking for does not exist. +

+ + Go Home + +
+ ); +} diff --git a/src/App.jsx b/src/app/page.jsx similarity index 84% rename from src/App.jsx rename to src/app/page.jsx index ad5f7d3..09b67e3 100644 --- a/src/App.jsx +++ b/src/app/page.jsx @@ -1,16 +1,17 @@ -import React from "react"; +"use client"; + import { v4 as uuidV4 } from "uuid"; -import { useNavigate } from "react-router-dom"; +import { useRouter } from "next/navigation"; -import style from "./App.module.scss"; -import { NavBar } from "./components"; +import style from "@/src/App.module.scss"; +import { NavBar } from "@/src/components"; -function App() { - const navigate = useNavigate(); +export default function Home() { + const router = useRouter(); const createFile = () => { const roomId = uuidV4(); - navigate(`/editor/${roomId}`); + router.push(`/editor/${roomId}`); }; return ( @@ -52,5 +53,3 @@ function App() { ); } - -export default App; diff --git a/src/app/register/page.jsx b/src/app/register/page.jsx new file mode 100644 index 0000000..54e11e5 --- /dev/null +++ b/src/app/register/page.jsx @@ -0,0 +1,7 @@ +import SignUp from "@/src/components/SignUp/SignUp"; + +export const dynamic = "force-dynamic"; + +export default function RegisterPage() { + return ; +} diff --git a/src/components/Editor/Editor.jsx b/src/components/Editor/Editor.jsx index 2d4accf..535f12c 100644 --- a/src/components/Editor/Editor.jsx +++ b/src/components/Editor/Editor.jsx @@ -1,6 +1,7 @@ -import React, { useState, useCallback, useEffect, useMemo } from "react"; -import { useParams, useNavigate } from "react-router-dom"; -import Editor from "@monaco-editor/react"; +"use client"; + +import { useState, useCallback, useEffect, useMemo } from "react"; +import dynamic from "next/dynamic"; import { Toaster } from "react-hot-toast"; import { io } from "socket.io-client" @@ -8,30 +9,29 @@ import notify from "../../services/toast"; import style from "./Editor.module.scss"; import NavBar from "../NavBar/NavBar"; -const EditorLayout = () => { +const MonacoEditor = dynamic(() => import("@monaco-editor/react"), { ssr: false }); + +const EditorLayout = ({ roomId }) => { const socketRef = useMemo( () => - io("https://beneficial-coherent-butterfly.glitch.me/", { - reconnectionAttempts: 2, - }), + io({ reconnectionAttempts: 2 }), [], -) - const navigate = useNavigate(); + ) const [code, setCode] = useState("// here is your code..."); const editorDidMount = useCallback((editor, monaco) => { editor.focus(); }, []); - document.title = "Editor"; - const { roomId } = useParams(); + useEffect(() => { + document.title = "Editor"; + }, []); const cookies = document.cookie; - let username = cookies ? cookies.split("; ").filter(cookie => cookie.includes("username"))[0].split("=")[1 ] : "Unkown User"; + let username = cookies ? cookies.split("; ").filter(cookie => cookie.includes("username"))[0]?.split("=")[1] : "Unkown User"; const handleErrors = (e) => { console.log("socket error", e); notify("Socket connection failed, try again later."); - navigate("/"); }; useEffect(() => { @@ -39,8 +39,7 @@ const EditorLayout = () => { socketRef.on("connect_failed", (err) => handleErrors(err)); socketRef.emit("JOIN", { roomId, username }); - socketRef.on("JOINED", ({ code, clients }) => { - // console.log(clients); + socketRef.on("JOINED", ({ code }) => { setCode(code); }); @@ -74,12 +73,13 @@ const EditorLayout = () => { socketRef.emit("code change", { roomId, newCode: newValue }); } }; + return ( <>
- { - return <>; -}; - -export default ErrorPage; diff --git a/src/components/ErrorPage/ErrorPage.scss b/src/components/ErrorPage/ErrorPage.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/Home/Home.jsx b/src/components/Home/Home.jsx deleted file mode 100644 index 0023411..0000000 --- a/src/components/Home/Home.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; - -import "./Home.scss" - -const Home = () => { - return <>; -}; - -export default Home; diff --git a/src/components/Home/Home.scss b/src/components/Home/Home.scss deleted file mode 100644 index e69de29..0000000 diff --git a/src/components/Login/Login.jsx b/src/components/Login/Login.jsx index f121659..982a099 100644 --- a/src/components/Login/Login.jsx +++ b/src/components/Login/Login.jsx @@ -1,17 +1,14 @@ -import React from "react"; +"use client"; + +import { useEffect } from "react"; import axios from "axios"; -import { useCookies } from "react-cookie"; -import { Link, useNavigate } from "react-router-dom"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; import "./Login.scss"; -const baseURL = "https://beneficial-coherent-butterfly.glitch.me/"; -// const baseURL = "http://localhost:8000"; - const Login = () => { - const [cookies, setCookie] = useCookies(["user"]); - const navigate = useNavigate(); - document.title = "Login"; + const router = useRouter(); const login = (e) => { e.preventDefault(); @@ -21,25 +18,23 @@ const Login = () => { axios .post( - `${baseURL}/user/login`, + "/api/user/login", { email: email, password: password, }, ) .then((res) => { - console.log(res); - setCookie("email", email, { path: "/" }); - setCookie("username", res.data.username, { path: "/" }); - setCookie("userId", res.data.userId, { path: "/" }); - setCookie("AuthToken", res.data.AuthToken, {path: "/"}) + document.cookie = `email=${email}; path=/`; + document.cookie = `username=${res.data.username}; path=/`; + document.cookie = `userId=${res.data.userId}; path=/`; + document.cookie = `AuthToken=${res.data.AuthToken}; path=/`; - navigate("/"); + router.push("/"); }) .catch((err) => { - console.log(err) if (err.request.status == 400) { - alert("Email or Passwrod Missing."); + alert("Email or Password Missing."); } if (err.request.status == 401) { @@ -54,6 +49,10 @@ const Login = () => { e.target.reset(); }; + useEffect(() => { + document.title = "Login"; + }, []); + return (
@@ -95,13 +94,13 @@ const Login = () => { Remember me
- Forget Password? + Forget Password?

- New on our platform? Create an account + New on our platform? Create an account

diff --git a/src/components/NavBar/NavBar.jsx b/src/components/NavBar/NavBar.jsx index d9c956c..2cd9b5b 100644 --- a/src/components/NavBar/NavBar.jsx +++ b/src/components/NavBar/NavBar.jsx @@ -1,5 +1,8 @@ -import React, { useEffect, useState, useRef } from "react"; -import { Link, useNavigate, useParams, useLocation } from "react-router-dom"; +"use client"; + +import { useEffect, useState, useRef } from "react"; +import Link from "next/link"; +import { useParams, useRouter } from "next/navigation"; import { v4 as uuidV4 } from "uuid"; import { Toaster } from "react-hot-toast"; import axios from "axios"; @@ -10,24 +13,19 @@ import "../../style/btn1.scss"; import "../../style/btn2.scss"; import style from "./NavBar.module.scss"; -const baseURL = "https://beneficial-coherent-butterfly.glitch.me/"; -// const baseURL = "http://localhost:8000"; - const NavBar = () => { const param = useParams(); const boxRef = useRef(null); - const navigate = useNavigate(); - const location = useLocation(); + const router = useRouter(); const [isLoggedIn, setLoggedIn] = useState(false); const [isBoxVisible, setIsBoxVisible] = useState(false); - const isONEditorPage = null; const roomId = param.roomId; - const shareableLink = `https://codelab-live.netlify.app/editor/${roomId}`; + const shareableLink = typeof window !== "undefined" ? `${window.location.origin}/editor/${roomId}` : ""; const createFile = () => { const roomId = uuidV4(); - navigate(`/editor/${roomId}`); + router.push(`/editor/${roomId}`); }; const toggleBoxVisibility = () => { @@ -66,7 +64,7 @@ const NavBar = () => { if (token) { axios - .get(`${baseURL}/protected/isLoggedIn`, { + .get("/api/protected/isLoggedIn", { headers: { "Content-Type": "application/json", Authorization: "Bearer " + token, @@ -89,7 +87,7 @@ const NavBar = () => { <>
- +
@@ -167,7 +165,7 @@ const NavBar = () => {
)} {isLoggedIn ? ( - +
{ ) : (
- + Sign in diff --git a/src/components/SignUp/SignUp.jsx b/src/components/SignUp/SignUp.jsx index 0f61263..40546f2 100644 --- a/src/components/SignUp/SignUp.jsx +++ b/src/components/SignUp/SignUp.jsx @@ -1,17 +1,13 @@ -import React from "react"; +"use client"; + import axios from "axios"; -import { useCookies } from "react-cookie"; -import { Link, useNavigate } from "react-router-dom"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; import "./SignUp.scss"; -const baseURL = "https://beneficial-coherent-butterfly.glitch.me/"; -// const baseURL = "http://localhost:8000"; - const SignUp = () => { - const [cookies, setCookie] = useCookies(["username"]); - const navigate = useNavigate(); - document.title = "Register"; + const router = useRouter(); const passwordValidater = (password) => { const regex = @@ -28,25 +24,24 @@ const SignUp = () => { if (!passwordValidater(password)) { alert( - "Password must contain at least 8 characters, 1 upp…e, 1 lowercase, 1 number, and 1 special character" + "Password must contain at least 8 characters, 1 uppercase, 1 lowercase, 1 number, and 1 special character" ); - return; } axios - .post(`${baseURL}/user/register`, { + .post("/api/user/register", { username: username, email: email, password: password, }) .then((res) => { - setCookie("email", email, { path: "/" }); - setCookie("username", res.data.username, { path: "/" }); - setCookie("userId", res.data.userId, { path: "/" }); - setCookie("AuthToken", res.data.AuthToken, {path: "/"}) + document.cookie = `email=${email}; path=/`; + document.cookie = `username=${res.data.username}; path=/`; + document.cookie = `userId=${res.data.userId}; path=/`; + document.cookie = `AuthToken=${res.data.AuthToken}; path=/`; - navigate("/"); + router.push("/"); }) .catch((error) => { alert(error.response.data.message); @@ -105,7 +100,7 @@ const SignUp = () => {

- Already have an account? Sign in instead + Already have an account? Sign in instead

diff --git a/src/components/index.js b/src/components/index.js index f9b698c..0932eb0 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,8 +1,6 @@ -import Home from "./Home/Home"; import NavBar from "./NavBar/NavBar"; -import ErrorPage from "./ErrorPage/ErrorPage"; import SignUp from "./SignUp/SignUp"; import Login from "./Login/Login"; import EditorLayout from "./Editor/Editor"; -export { Home, NavBar, ErrorPage, SignUp, Login, EditorLayout }; +export { NavBar, SignUp, Login, EditorLayout }; diff --git a/src/constant/EditorHelper.jsx b/src/constant/EditorHelper.jsx deleted file mode 100644 index 38e26d8..0000000 --- a/src/constant/EditorHelper.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import React, { useEffect } from "react"; -import { useNavigate } from "react-router-dom"; -import { v4 as uuidV4 } from "uuid"; - -const EditorHelper = () => { - const navigate = useNavigate(); - const roomId = uuidV4(); - - useEffect(() => { - navigate(`/editor/${roomId}`); - }, []); - return <>; -}; - -export default EditorHelper; diff --git a/src/lib/db.js b/src/lib/db.js new file mode 100644 index 0000000..c812acc --- /dev/null +++ b/src/lib/db.js @@ -0,0 +1,24 @@ +import mongoose from "mongoose"; + +const MONGODB_URI = process.env.MONGODB_URI; + +let cached = global._mongoose; + +if (!cached) { + cached = global._mongoose = { conn: null, promise: null }; +} + +async function dbConnect() { + if (cached.conn) { + return cached.conn; + } + + if (!cached.promise) { + cached.promise = mongoose.connect(MONGODB_URI).then((mongoose) => mongoose); + } + + cached.conn = await cached.promise; + return cached.conn; +} + +export default dbConnect; diff --git a/src/lib/middleware/auth.js b/src/lib/middleware/auth.js new file mode 100644 index 0000000..06b677c --- /dev/null +++ b/src/lib/middleware/auth.js @@ -0,0 +1,16 @@ +import jwt from "jsonwebtoken"; + +export function verifyToken(request) { + const token = request.cookies.get("AuthToken")?.value; + + if (!token) { + return { error: "Access denied", status: 401 }; + } + + try { + const decode = jwt.verify(token, process.env.SECERT_KEY); + return { userId: decode.userId, username: decode.username, email: decode.email }; + } catch { + return { error: "Invalid token", status: 401 }; + } +} diff --git a/src/lib/models/user.js b/src/lib/models/user.js new file mode 100644 index 0000000..66fd14e --- /dev/null +++ b/src/lib/models/user.js @@ -0,0 +1,10 @@ +import mongoose from "mongoose"; + +const userSchema = new mongoose.Schema({ + username: { type: String, required: true }, + email: { type: String, required: true, unique: true }, + password: { type: String, required: true }, + rooms: [], +}); + +export default mongoose.models.users || mongoose.model("users", userSchema); diff --git a/Backend/socket/socket.js b/src/lib/socket.js similarity index 51% rename from Backend/socket/socket.js rename to src/lib/socket.js index 4e55422..935e511 100644 --- a/Backend/socket/socket.js +++ b/src/lib/socket.js @@ -1,13 +1,12 @@ -const { Server } = require("socket.io"); +import { Server } from "socket.io"; -const setupSocketIO = (server) => { - const roomsAndClientsmap = {}; +const roomsAndClientsmap = {}; +export function setupSocketIO(server) { const io = new Server(server, { cors: { origin: "*", methods: ["GET", "POST", "PUT", "DELETE"], - allowedHeaders: ["my-custom-header"], credentials: true, }, }); @@ -15,6 +14,7 @@ const setupSocketIO = (server) => { io.on("connection", (socket) => { socket.on("JOIN", ({ roomId, username }) => { socket.join(roomId); + if (roomId in roomsAndClientsmap) { roomsAndClientsmap[roomId].clients.push({ socketId: socket.id, @@ -23,12 +23,7 @@ const setupSocketIO = (server) => { } else { roomsAndClientsmap[roomId] = { code: "// your code...", - clients: [ - { - socketId: socket.id, - username: username, - }, - ], + clients: [{ socketId: socket.id, username }], }; } @@ -39,26 +34,21 @@ const setupSocketIO = (server) => { }); socket.on("code change", ({ roomId, newCode }) => { - roomsAndClientsmap[roomId].code = newCode; + if (roomsAndClientsmap[roomId]) { + roomsAndClientsmap[roomId].code = newCode; + } socket.in(roomId).emit("code change", { newCode }); }); socket.on("leave", (roomId) => { - const clients = roomsAndClientsmap[roomId].clients; - const index = clients.findIndex((cli) => cli.socketId == socket.id); - const client = clients[index]; - - if (index !== -1) clients.splice(index, 1); - - socket.in(roomId).emit("disconnected", { username: client.username }); + const clients = roomsAndClientsmap[roomId]?.clients; + if (clients) { + const index = clients.findIndex((cli) => cli.socketId == socket.id); + const client = clients[index]; + if (index !== -1) clients.splice(index, 1); + socket.in(roomId).emit("disconnected", { username: client?.username }); + } socket.leave(); }); }); -}; - -module.exports = setupSocketIO; - - -//https://707e5cbe-f302-4ed9-b77e-c2853dfd23cd@api.glitch.com/git/uttermost-somber-close -//https://707e5cbe-f302-4ed9-b77e-c2853dfd23cd@api.glitch.com/git/uttermost-somber-close -//uttermost-somber-close \ No newline at end of file +} diff --git a/src/main.jsx b/src/main.jsx deleted file mode 100644 index 6c7053a..0000000 --- a/src/main.jsx +++ /dev/null @@ -1,39 +0,0 @@ -import React from "react"; -import ReactDOM from "react-dom/client"; -import { createBrowserRouter, RouterProvider } from "react-router-dom"; - -import { EditorLayout, ErrorPage, Login, SignUp } from "./components"; -import EditorHelper from "./constant/EditorHelper.jsx"; -import App from "./App.jsx"; -import "./index.css"; - -const router = new createBrowserRouter([ - { - path: "/", - element: , - errorElement: , - }, - { - path: "/register", - element: , - errorElement: , - }, - { - path: "/login", - element: , - }, - { - path: "/editor/:roomId", - element: , - }, - { - path: "/editor", - element: , - }, -]); - -ReactDOM.createRoot(document.getElementById("root")).render( - - - -); diff --git a/src/services/actions.js b/src/services/actions.js deleted file mode 100644 index 76e8ada..0000000 --- a/src/services/actions.js +++ /dev/null @@ -1,20 +0,0 @@ -const ACTIONS = { - JOIN_REQUEST: "join-request", - JOIN_ACCEPTED: "join-accepted", - USER_JOINED: "user-joined", - USER_DISCONNECTED: "user-disconnected", - SYNC_FILES: "sync-files", - FILE_CREATED: "file-created", - FILE_UPDATED: "file-updated", - FILE_RENAMED: "file-renamed", - FILE_DELETED: "file-deleted", - USER_OFFLINE: "offline", - USER_ONLINE: "online", - SEND_MESSAGE: "send-message", - RECEIVE_MESSAGE: "receive-message", - TYPING_START: "typing-start", - TYPING_PAUSE: "typing-pause", - USERNAME_EXISTS: "username-exists", -} - -export default ACTIONS; \ No newline at end of file diff --git a/src/services/socket.js b/src/services/socket.js deleted file mode 100644 index be06ca1..0000000 --- a/src/services/socket.js +++ /dev/null @@ -1,17 +0,0 @@ -import { io } from "socket.io-client"; - -const baseURL = "https://beneficial-coherent-butterfly.glitch.me/"; -// const baseURL = "http://localhost:8000"; - -const initSocket = async () => { - const option = { - "force new connection": false, - reconnectionAttempt: false, - timeout: 10000, - transport: ["websocket"], - }; - - return io(baseURL, option); -}; - -export default initSocket; diff --git a/vite.config.js b/vite.config.js deleted file mode 100644 index 5a33944..0000000 --- a/vite.config.js +++ /dev/null @@ -1,7 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], -}) From 5beb10fc73f17b3631d7903eb7ea86cef3e05e8a Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Sun, 21 Jun 2026 20:45:49 +0530 Subject: [PATCH 8/8] layout and theme improvements --- next-env.d.ts | 5 + package.json | 6 +- src/app/api/projects/[id]/duplicate/route.ts | 70 ++++ src/app/api/projects/[id]/route.ts | 166 ++++++++ src/app/api/projects/route.ts | 102 +++++ src/app/dashboard/page.tsx | 251 +++++++++++ .../page.jsx => [projectId]/page.tsx} | 4 +- src/app/editor/page.jsx | 16 - src/app/editor/page.tsx | 46 ++ src/app/layout.jsx | 7 +- src/app/not-found.jsx | 10 +- src/app/page.jsx | 393 ++++++++++++++++-- src/app/profile/page.tsx | 179 ++++++++ src/components/Editor/Editor.jsx | 316 ++++++++++++-- src/components/Editor/Editor.module.scss | 275 +++++++++++- src/components/Login/Login.jsx | 2 +- src/components/Login/Login.scss | 46 +- src/components/NavBar/NavBar.module.scss | 225 ++++++---- .../NavBar/{NavBar.jsx => NavBar.tsx} | 141 ++++--- src/components/SignUp/SignUp.jsx | 2 +- src/components/SignUp/SignUp.scss | 44 +- .../dashboard/CreateProjectDialog.tsx | 217 ++++++++++ src/components/dashboard/ProjectActions.tsx | 190 +++++++++ src/components/dashboard/ProjectCard.tsx | 203 +++++++++ src/components/dashboard/ProjectGrid.tsx | 90 ++++ src/components/dashboard/SearchBar.tsx | 92 ++++ src/context/ThemeContext.tsx | 43 ++ src/index.css | 90 +++- src/lib/middleware/auth.js | 16 - src/lib/middleware/auth.ts | 41 ++ src/lib/models/project.ts | 76 ++++ src/lib/models/user.js | 10 - src/lib/models/user.ts | 25 ++ src/lib/socket.js | 13 +- src/services/toast.js | 5 - src/types/global.d.ts | 16 + src/types/index.ts | 44 ++ tsconfig.json | 27 ++ 38 files changed, 3173 insertions(+), 331 deletions(-) create mode 100644 next-env.d.ts create mode 100644 src/app/api/projects/[id]/duplicate/route.ts create mode 100644 src/app/api/projects/[id]/route.ts create mode 100644 src/app/api/projects/route.ts create mode 100644 src/app/dashboard/page.tsx rename src/app/editor/{[roomId]/page.jsx => [projectId]/page.tsx} (52%) delete mode 100644 src/app/editor/page.jsx create mode 100644 src/app/editor/page.tsx create mode 100644 src/app/profile/page.tsx rename src/components/NavBar/{NavBar.jsx => NavBar.tsx} (58%) create mode 100644 src/components/dashboard/CreateProjectDialog.tsx create mode 100644 src/components/dashboard/ProjectActions.tsx create mode 100644 src/components/dashboard/ProjectCard.tsx create mode 100644 src/components/dashboard/ProjectGrid.tsx create mode 100644 src/components/dashboard/SearchBar.tsx create mode 100644 src/context/ThemeContext.tsx delete mode 100644 src/lib/middleware/auth.js create mode 100644 src/lib/middleware/auth.ts create mode 100644 src/lib/models/project.ts delete mode 100644 src/lib/models/user.js create mode 100644 src/lib/models/user.ts create mode 100644 src/types/global.d.ts create mode 100644 src/types/index.ts create mode 100644 tsconfig.json diff --git a/next-env.d.ts b/next-env.d.ts new file mode 100644 index 0000000..40c3d68 --- /dev/null +++ b/next-env.d.ts @@ -0,0 +1,5 @@ +/// +/// + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/package.json b/package.json index ecb0f4d..13d740b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,11 @@ "uuid": "^9.0.1" }, "devDependencies": { + "@types/node": "^25.9.3", + "@types/react": "^19.2.17", + "@types/uuid": "^10.0.0", "eslint": "^8.57.0", - "eslint-config-next": "^14.2.0" + "eslint-config-next": "^14.2.0", + "typescript": "^6.0.3" } } diff --git a/src/app/api/projects/[id]/duplicate/route.ts b/src/app/api/projects/[id]/duplicate/route.ts new file mode 100644 index 0000000..9dd3444 --- /dev/null +++ b/src/app/api/projects/[id]/duplicate/route.ts @@ -0,0 +1,70 @@ +import { NextRequest, NextResponse } from "next/server"; +import dbConnect from "@/src/lib/db"; +import Project from "@/src/lib/models/project"; +import { verifyToken } from "@/src/lib/middleware/auth"; +import type { AuthSuccess } from "@/src/lib/middleware/auth"; + +function isAuthSuccess( + auth: ReturnType +): auth is AuthSuccess { + return "userId" in auth; +} + +export async function POST( + request: NextRequest, + { params }: { params: { id: string } } +) { + const authResult = verifyToken(request); + if (!isAuthSuccess(authResult)) { + return NextResponse.json({ error: authResult.error }, { status: authResult.status }); + } + const auth = authResult; + + try { + await dbConnect(); + + const original = await Project.findById(params.id); + if (!original) { + return NextResponse.json( + { success: false, error: "Project not found" }, + { status: 404 } + ); + } + + const userId = auth.userId; + const isOwner = original.owner.toString() === userId; + const isCollaborator = original.collaborators?.some( + (c) => c.userId.toString() === userId + ); + + if (!isOwner && !isCollaborator) { + return NextResponse.json( + { success: false, error: "Access denied" }, + { status: 403 } + ); + } + + const duplicate = await Project.create({ + title: `${original.title} (Copy)`, + description: original.description, + programmingLanguage: original.programmingLanguage, + visibility: "private", + owner: auth.userId, + collaborators: [ + { userId: auth.userId, role: "owner", joinedAt: new Date() }, + ], + code: original.code || "// your code...", + }); + + return NextResponse.json( + { success: true, data: duplicate }, + { status: 201 } + ); + } catch (err) { + console.error("Error duplicating project:", err); + return NextResponse.json( + { success: false, error: "Failed to duplicate project" }, + { status: 500 } + ); + } +} diff --git a/src/app/api/projects/[id]/route.ts b/src/app/api/projects/[id]/route.ts new file mode 100644 index 0000000..d4d1d60 --- /dev/null +++ b/src/app/api/projects/[id]/route.ts @@ -0,0 +1,166 @@ +import { NextRequest, NextResponse } from "next/server"; +import dbConnect from "@/src/lib/db"; +import Project from "@/src/lib/models/project"; +import { verifyToken } from "@/src/lib/middleware/auth"; +import type { AuthSuccess } from "@/src/lib/middleware/auth"; + +function isAuthSuccess( + auth: ReturnType +): auth is AuthSuccess { + return "userId" in auth; +} + +export async function GET( + request: NextRequest, + { params }: { params: { id: string } } +) { + const authResult = verifyToken(request); + const isAuthenticated = isAuthSuccess(authResult); + const userId = isAuthenticated ? (authResult as AuthSuccess).userId : null; + + try { + await dbConnect(); + + const project = await Project.findById(params.id) + .populate("owner", "username email avatar") + .populate("collaborators.userId", "username email avatar") + .lean(); + + if (!project) { + return NextResponse.json( + { success: false, error: "Project not found" }, + { status: 404 } + ); + } + + const hasOwner = !!project.owner; + const isOwner = hasOwner && userId && String(project.owner) === userId; + const isCollaborator = + userId && + project.collaborators?.some((c) => String(c.userId) === userId); + + if (hasOwner && !isOwner && !isCollaborator && project.visibility !== "public") { + return NextResponse.json( + { success: false, error: "Access denied" }, + { status: 403 } + ); + } + + return NextResponse.json({ success: true, data: project }, { status: 200 }); + } catch (err) { + console.error("Error fetching project:", err); + return NextResponse.json( + { success: false, error: "Failed to fetch project" }, + { status: 500 } + ); + } +} + +export async function PUT( + request: NextRequest, + { params }: { params: { id: string } } +) { + const authResult = verifyToken(request); + const isAuthenticated = isAuthSuccess(authResult); + const userId = isAuthenticated ? (authResult as AuthSuccess).userId : null; + + try { + await dbConnect(); + + const project = await Project.findById(params.id); + if (!project) { + return NextResponse.json( + { success: false, error: "Project not found" }, + { status: 404 } + ); + } + + const hasOwner = !!project.owner; + const isOwner = hasOwner && userId && project.owner.toString() === userId; + const isEditor = userId && + project.collaborators?.some( + (c) => c.userId.toString() === userId && c.role === "editor" + ); + + if (hasOwner && !isOwner && !isEditor) { + return NextResponse.json( + { success: false, error: "Access denied" }, + { status: 403 } + ); + } + + const body = await request.json(); + const allowedFields = [ + "title", + "description", + "programmingLanguage", + "visibility", + "code", + "archived", + ]; + + for (const field of allowedFields) { + if (body[field] !== undefined) { + if (field === "title" && !body.title?.trim()) { + return NextResponse.json( + { success: false, error: "Title cannot be empty" }, + { status: 400 } + ); + } + (project as unknown as Record)[field] = body[field]; + } + } + + await project.save(); + + return NextResponse.json({ success: true, data: project }, { status: 200 }); + } catch (err) { + console.error("Error updating project:", err); + return NextResponse.json( + { success: false, error: "Failed to update project" }, + { status: 500 } + ); + } +} + +export async function DELETE( + request: NextRequest, + { params }: { params: { id: string } } +) { + const authResult = verifyToken(request); + const isAuthenticated = isAuthSuccess(authResult); + const userId = isAuthenticated ? (authResult as AuthSuccess).userId : null; + + try { + await dbConnect(); + + const project = await Project.findById(params.id); + if (!project) { + return NextResponse.json( + { success: false, error: "Project not found" }, + { status: 404 } + ); + } + + const hasOwner = !!project.owner; + if (hasOwner && (!userId || project.owner.toString() !== userId)) { + return NextResponse.json( + { success: false, error: "Only the owner can delete a project" }, + { status: 403 } + ); + } + + await Project.findByIdAndDelete(params.id); + + return NextResponse.json( + { success: true, message: "Project deleted" }, + { status: 200 } + ); + } catch (err) { + console.error("Error deleting project:", err); + return NextResponse.json( + { success: false, error: "Failed to delete project" }, + { status: 500 } + ); + } +} diff --git a/src/app/api/projects/route.ts b/src/app/api/projects/route.ts new file mode 100644 index 0000000..daf740f --- /dev/null +++ b/src/app/api/projects/route.ts @@ -0,0 +1,102 @@ +import { NextRequest, NextResponse } from "next/server"; +import dbConnect from "@/src/lib/db"; +import Project from "@/src/lib/models/project"; +import { verifyToken } from "@/src/lib/middleware/auth"; +import type { AuthSuccess } from "@/src/lib/middleware/auth"; + +function isAuthSuccess( + auth: ReturnType +): auth is AuthSuccess { + return "userId" in auth; +} + +export async function GET(request: NextRequest) { + const authResult = verifyToken(request); + if (!isAuthSuccess(authResult)) { + return NextResponse.json({ error: authResult.error }, { status: authResult.status }); + } + const auth = authResult; + + try { + await dbConnect(); + + const { searchParams } = new URL(request.url); + const search = searchParams.get("search") || ""; + const archived = searchParams.get("archived"); + + const query: Record = { + $or: [ + { owner: auth.userId }, + { "collaborators.userId": auth.userId }, + ], + }; + + if (archived === "true") query.archived = true; + else if (archived !== "all") query.archived = false; + + if (search) { + query.$text = { $search: search }; + } + + const projects = await Project.find(query) + .populate("owner", "username email avatar") + .populate("collaborators.userId", "username email avatar") + .sort({ updatedAt: -1 }) + .lean(); + + return NextResponse.json({ success: true, data: projects }, { status: 200 }); + } catch (err) { + console.error("Error fetching projects:", err); + return NextResponse.json( + { success: false, error: "Failed to fetch projects" }, + { status: 500 } + ); + } +} + +export async function POST(request: NextRequest) { + const authResult = verifyToken(request); + const isAuthenticated = isAuthSuccess(authResult); + const auth = isAuthenticated ? (authResult as AuthSuccess) : null; + + try { + await dbConnect(); + + const body = await request.json(); + const { title, description, programmingLanguage, visibility } = body; + + if (!title || !title.trim()) { + return NextResponse.json( + { success: false, error: "Title is required" }, + { status: 400 } + ); + } + + const project = await Project.create({ + title: title.trim(), + description: description?.trim() || "", + programmingLanguage: programmingLanguage || "javascript", + visibility: visibility || "private", + owner: auth?.userId || null, + ...(auth + ? { + collaborators: [ + { userId: auth.userId, role: "owner", joinedAt: new Date() }, + ], + } + : {}), + code: "// your code...", + }); + + return NextResponse.json( + { success: true, data: project }, + { status: 201 } + ); + } catch (err) { + console.error("Error creating project:", err); + return NextResponse.json( + { success: false, error: "Failed to create project" }, + { status: 500 } + ); + } +} diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx new file mode 100644 index 0000000..699c154 --- /dev/null +++ b/src/app/dashboard/page.tsx @@ -0,0 +1,251 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { useRouter } from "next/navigation"; +import axios from "axios"; +import toast from "react-hot-toast"; + +import { NavBar } from "@/src/components"; +import SearchBar from "@/src/components/dashboard/SearchBar"; +import ProjectGrid from "@/src/components/dashboard/ProjectGrid"; +import CreateProjectDialog from "@/src/components/dashboard/CreateProjectDialog"; + +interface Project { + _id: string; + title: string; + description?: string; + programmingLanguage: string; + visibility: "public" | "private"; + updatedAt: string; + createdAt: string; + owner: { _id: string; username: string }; + archived: boolean; +} + +export default function DashboardPage() { + const router = useRouter(); + const [projects, setProjects] = useState([]); + const [filtered, setFiltered] = useState([]); + const [loading, setLoading] = useState(true); + const [showCreate, setShowCreate] = useState(false); + const [search, setSearch] = useState(""); + + const fetchProjects = useCallback(async () => { + try { + const res = await axios.get("/api/projects"); + if (res.data.success) { + setProjects(res.data.data); + setFiltered(res.data.data); + } + } catch { + toast.error("Failed to load projects"); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchProjects(); + }, [fetchProjects]); + + useEffect(() => { + if (!search) { + setFiltered(projects); + } else { + const q = search.toLowerCase(); + setFiltered( + projects.filter( + (p) => + p.title.toLowerCase().includes(q) || + p.description?.toLowerCase().includes(q) || + p.programmingLanguage.toLowerCase().includes(q) + ) + ); + } + }, [search, projects]); + + const handleCreate = async (data: { + title: string; + description: string; + programmingLanguage: string; + visibility: "public" | "private"; + }) => { + try { + const res = await axios.post("/api/projects", data); + if (res.data.success) { + router.push(`/editor/${res.data.data._id}`); + } + } catch { + toast.error("Failed to create project"); + } + }; + + const handleRename = async (id: string, title: string) => { + try { + await axios.put(`/api/projects/${id}`, { title }); + toast.success("Project renamed"); + fetchProjects(); + } catch { + toast.error("Failed to rename project"); + } + }; + + const handleArchive = async (id: string) => { + try { + await axios.put(`/api/projects/${id}`, { archived: true }); + toast.success("Project archived"); + fetchProjects(); + } catch { + toast.error("Failed to archive project"); + } + }; + + const handleDelete = async (id: string) => { + if (!window.confirm("Delete this project permanently? This cannot be undone.")) return; + try { + await axios.delete(`/api/projects/${id}`); + toast.success("Project deleted"); + fetchProjects(); + } catch { + toast.error("Failed to delete project"); + } + }; + + const handleDuplicate = async (id: string) => { + try { + await axios.post(`/api/projects/${id}/duplicate`); + toast.success("Project duplicated"); + fetchProjects(); + } catch { + toast.error("Failed to duplicate project"); + } + }; + + return ( + <> + +
+
+
+

Dashboard

+

Manage your projects

+
+
+ + +
+
+ + {loading ? ( +
+ {[1, 2, 3, 4, 5, 6].map((i) => ( +
+
+
+
+
+ ))} +
+ ) : ( + + )} +
+ + setShowCreate(false)} + onCreate={handleCreate} + /> + + ); +} + +const styles: Record = { + page: { + padding: "80px 32px 32px", + maxWidth: "1200px", + margin: "0 auto", + minHeight: "100vh", + background: "var(--bg-primary)", + }, + top: { + display: "flex", + justifyContent: "space-between", + alignItems: "center", + marginBottom: "32px", + flexWrap: "wrap", + gap: "16px", + }, + topLeft: {}, + topRight: { + display: "flex", + alignItems: "center", + gap: "12px", + }, + heading: { + fontSize: "28px", + fontWeight: 700, + color: "var(--text-primary)", + margin: 0, + }, + subtitle: { + fontSize: "14px", + color: "var(--text-muted)", + margin: "4px 0 0", + }, + createBtn: { + display: "flex", + alignItems: "center", + gap: "6px", + padding: "10px 20px", + background: "var(--brand)", + color: "#fff", + border: "none", + borderRadius: "8px", + fontSize: "14px", + fontWeight: 500, + cursor: "pointer", + whiteSpace: "nowrap", + }, + loading: { + display: "grid", + gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", + gap: "16px", + }, + skeleton: { + background: "var(--bg-secondary)", + borderRadius: "12px", + border: "1px solid var(--border-color)", + overflow: "hidden", + }, + skelHeader: { + height: "44px", + background: "linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)", + backgroundSize: "200% 100%", + animation: "shimmer 1.5s infinite", + }, + skelBody: { + height: "80px", + background: "linear-gradient(90deg, #f5f5f5 25%, #e8e8e8 50%, #f5f5f5 75%)", + backgroundSize: "200% 100%", + animation: "shimmer 1.5s infinite", + }, + skelFooter: { + height: "36px", + background: "linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%)", + backgroundSize: "200% 100%", + animation: "shimmer 1.5s infinite", + }, +}; diff --git a/src/app/editor/[roomId]/page.jsx b/src/app/editor/[projectId]/page.tsx similarity index 52% rename from src/app/editor/[roomId]/page.jsx rename to src/app/editor/[projectId]/page.tsx index 12acd0f..ea980a2 100644 --- a/src/app/editor/[roomId]/page.jsx +++ b/src/app/editor/[projectId]/page.tsx @@ -7,6 +7,6 @@ const EditorLayout = dynamic( { ssr: false } ); -export default function EditorPage({ params }) { - return ; +export default function EditorPage({ params }: { params: { projectId: string } }) { + return ; } diff --git a/src/app/editor/page.jsx b/src/app/editor/page.jsx deleted file mode 100644 index 88d2c3d..0000000 --- a/src/app/editor/page.jsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; - -import { useEffect } from "react"; -import { useRouter } from "next/navigation"; -import { v4 as uuidV4 } from "uuid"; - -export default function EditorRedirect() { - const router = useRouter(); - const roomId = uuidV4(); - - useEffect(() => { - router.replace(`/editor/${roomId}`); - }, []); - - return null; -} diff --git a/src/app/editor/page.tsx b/src/app/editor/page.tsx new file mode 100644 index 0000000..f4e95a9 --- /dev/null +++ b/src/app/editor/page.tsx @@ -0,0 +1,46 @@ +"use client"; + +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; + +const ADJECTIVES = [ + "cool", "fast", "bright", "quiet", "swift", "bold", "calm", + "eager", "fierce", "gentle", "happy", "keen", "lively", "mighty", + "neat", "proud", "quick", "rare", "sharp", "tough", "vivid", + "warm", "zesty", "agile", "brave", "crisp", +]; +const NOUNS = [ + "alpha", "beta", "delta", "echo", "falcon", "gamma", "hawk", + "jade", "kappa", "lion", "nova", "omega", "pixel", "quantum", + "raven", "sigma", "tiger", "ultra", "vertex", "wave", "zeta", + "blitz", "cipher", "drift", "ember", "frost", +]; + +function randomName(): string { + const a = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]; + const n = NOUNS[Math.floor(Math.random() * NOUNS.length)]; + return `${a}-${n}`; +} + +export default function EditorNew() { + const router = useRouter(); + + useEffect(() => { + fetch("/api/projects", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ title: randomName() }), + }) + .then((res) => res.json()) + .then((data) => { + if (data.success) { + router.replace(`/editor/${data.data._id}`); + } + }) + .catch(() => { + router.replace("/dashboard"); + }); + }, [router]); + + return null; +} diff --git a/src/app/layout.jsx b/src/app/layout.jsx index a255179..b42fa64 100644 --- a/src/app/layout.jsx +++ b/src/app/layout.jsx @@ -1,4 +1,5 @@ import "@/src/index.css"; +import { ThemeProvider } from "@/src/context/ThemeContext"; export const metadata = { title: "CodeLab", @@ -7,8 +8,10 @@ export const metadata = { export default function RootLayout({ children }) { return ( - - {children} + + + {children} + ); } diff --git a/src/app/not-found.jsx b/src/app/not-found.jsx index 350de68..2df1e32 100644 --- a/src/app/not-found.jsx +++ b/src/app/not-found.jsx @@ -2,10 +2,10 @@ import Link from "next/link"; export default function NotFound() { return ( -
-

404

-

Page Not Found

-

+

+

404

+

Page Not Found

+

The page you are looking for does not exist.

{ - const roomId = uuidV4(); - router.push(`/editor/${roomId}`); - }; + useEffect(() => { + const cookies = document.cookie.split("; "); + if (cookies.find((c) => c.startsWith("AuthToken="))) { + router.replace("/dashboard"); + } + }, [router]); return ( - <> +
-
-
-
-
-

Unleash the Power of Collaboration⚡️

-

- CodeLab is a collaborative online real-time code editor for - technical -
- interviews, pair programming, teaching... you name it. -

- -
- - New file + {/* ── Hero ── */} +
+ + v2.0 — Now in public beta + + +

+ CODE IN{" "} + UNISON +

+ +

+ The collaborative engine for technical interviews, pair programming, + and engineering education. Real-time, zero latency. +

+ +
+ + Launch Editor + + + Open Dashboard + +
+ + {/* ── Editor mock ── */} +
+
+ + + + + project_delta.py + +
+
+ import{" "} + asyncio +
+ from{" "} + collab{" "} + import{" "} + Session +
+
+ session{" "} + ={" "} + await{" "} + Session( + "delta") +
+ await{" "} + session. + sync() +
+
+ # 3 collaborators connected +
+
+
+ {["#5f5fff", "#ffbd2e", "#28c840"].map((c, i) => ( +
+ ))}
+ + 3 users editing project_delta.py +
+
+ + {/* ── Features ── */} +
+
+

+ {'// Built for engineering teams'} +

+ +
+ {[ + { + title: "Zero-latency sync", + desc: "CRDT-backed editing keeps every keystroke in lockstep across continents.", + num: "01", + }, + { + title: "Live execution", + desc: "Run 40+ languages in isolated containers — no setup, no sandbox surprises.", + num: "02", + }, + { + title: "Interview-grade", + desc: "Persistent projects with real-time sync for seamless team collaboration.", + num: "03", + }, + ].map((f) => ( +
+
+ {f.num} +
+

+ {f.title} +

+

+ {f.desc} +

+
+ ))} +
+
+
+ + {/* ── CTA ── */} +
+

+ Start coding together{" "} + in seconds. +

+

+ No installs. No accounts for guests. Just share a link. +

+ + Create a Room +
- + + {/* ── Footer ── */} +
+ © {new Date().getFullYear()} CodeLab — Collaborative real-time code editor +
+
); } diff --git a/src/app/profile/page.tsx b/src/app/profile/page.tsx new file mode 100644 index 0000000..f7aec2e --- /dev/null +++ b/src/app/profile/page.tsx @@ -0,0 +1,179 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; +import axios from "axios"; + +import { NavBar } from "@/src/components"; + +interface ProfileData { + username: string; + email: string; + userId: string; + joinedAt?: string; +} + +export default function ProfilePage() { + const router = useRouter(); + const [profile, setProfile] = useState(null); + const [loading, setLoading] = useState(true); + const [projectCount, setProjectCount] = useState(0); + + useEffect(() => { + const cookies = document.cookie.split("; "); + const username = cookies.find((c) => c.startsWith("username="))?.split("=")[1]; + const email = cookies.find((c) => c.startsWith("email="))?.split("=")[1]; + const userId = cookies.find((c) => c.startsWith("userId="))?.split("=")[1]; + + if (!username) { + router.push("/login"); + return; + } + + setProfile({ + username: decodeURIComponent(username), + email: decodeURIComponent(email || ""), + userId: userId || "", + }); + + axios.get("/api/projects").then((res) => { + if (res.data.success) { + setProjectCount(res.data.data.length); + } + }).finally(() => setLoading(false)); + }, [router]); + + if (loading || !profile) { + return ( + <> + +
+
+
+ + ); + } + + return ( + <> + +
+
+
+ {profile.username.charAt(0).toUpperCase()} +
+

{profile.username}

+

{profile.email}

+ +
+
+ {projectCount} + Projects +
+
+ +
+ + Go to Dashboard + +
+
+
+ + ); +} + +const styles: Record = { + page: { + padding: "100px 24px 32px", + maxWidth: "600px", + margin: "0 auto", + minHeight: "100vh", + background: "var(--bg-primary)", + display: "flex", + justifyContent: "center", + }, + loader: { + display: "flex", + justifyContent: "center", + alignItems: "center", + height: "100vh", + }, + spinner: { + width: "32px", + height: "32px", + border: "3px solid #e5e7eb", + borderTopColor: "var(--brand)", + borderRadius: "50%", + animation: "spin 0.8s linear infinite", + }, + card: { + background: "var(--bg-secondary)", + borderRadius: "16px", + border: "1px solid var(--border-color)", + padding: "40px 32px", + width: "100%", + display: "flex", + flexDirection: "column", + alignItems: "center", + textAlign: "center", + }, + avatarLarge: { + width: "80px", + height: "80px", + borderRadius: "50%", + backgroundColor: "var(--brand)", + color: "#fff", + fontSize: "32px", + fontWeight: 700, + display: "flex", + alignItems: "center", + justifyContent: "center", + marginBottom: "16px", + }, + name: { + fontSize: "24px", + fontWeight: 700, + color: "var(--text-primary)", + margin: 0, + }, + email: { + fontSize: "14px", + color: "var(--text-muted)", + margin: "4px 0 24px", + }, + stats: { + display: "flex", + gap: "32px", + marginBottom: "28px", + }, + stat: { + display: "flex", + flexDirection: "column", + alignItems: "center", + }, + statValue: { + fontSize: "24px", + fontWeight: 700, + color: "var(--text-primary)", + }, + statLabel: { + fontSize: "13px", + color: "var(--text-muted)", + }, + actions: { + display: "flex", + gap: "12px", + }, + actionBtn: { + display: "inline-flex", + padding: "10px 24px", + background: "var(--brand)", + color: "#fff", + borderRadius: "8px", + fontSize: "14px", + fontWeight: 500, + textDecoration: "none", + }, +}; diff --git a/src/components/Editor/Editor.jsx b/src/components/Editor/Editor.jsx index 535f12c..8ec20f7 100644 --- a/src/components/Editor/Editor.jsx +++ b/src/components/Editor/Editor.jsx @@ -1,24 +1,46 @@ "use client"; -import { useState, useCallback, useEffect, useMemo } from "react"; +import { useState, useCallback, useEffect, useMemo, useRef } from "react"; import dynamic from "next/dynamic"; import { Toaster } from "react-hot-toast"; -import { io } from "socket.io-client" +import { io } from "socket.io-client"; import notify from "../../services/toast"; +import { useTheme } from "../../context/ThemeContext"; import style from "./Editor.module.scss"; import NavBar from "../NavBar/NavBar"; const MonacoEditor = dynamic(() => import("@monaco-editor/react"), { ssr: false }); -const EditorLayout = ({ roomId }) => { - const socketRef = useMemo( - () => - io({ reconnectionAttempts: 2 }), - [], - ) - const [code, setCode] = useState("// here is your code..."); - const editorDidMount = useCallback((editor, monaco) => { +const LANGUAGES = [ + "javascript", "typescript", "python", "html", "css", + "rust", "go", "java", "cpp", "ruby", "php", +]; + +const EditorLayout = ({ projectId }) => { + const { theme } = useTheme(); + const socketRef = useMemo(() => io({ reconnectionAttempts: 2 }), []); + const [code, setCode] = useState("// your code..."); + const [project, setProject] = useState(null); + const [language, setLanguage] = useState("javascript"); + const [clients, setClients] = useState([]); + const [messages, setMessages] = useState([]); + const [chatDraft, setChatDraft] = useState(""); + const [editingTitle, setEditingTitle] = useState(false); + const [titleDraft, setTitleDraft] = useState(""); + const [saving, setSaving] = useState(false); + const chatEndRef = useRef(null); + const titleRef = useRef(null); + + const monacoTheme = theme === "dark" ? "vs-dark" : "light"; + + const cookies = document.cookie; + const username = useMemo(() => { + const match = cookies?.split("; ").find((c) => c.startsWith("username=")); + return match ? match.split("=")[1] : "Unknown User"; + }, []); + + const editorDidMount = useCallback((editor) => { editor.focus(); }, []); @@ -26,72 +48,284 @@ const EditorLayout = ({ roomId }) => { document.title = "Editor"; }, []); - const cookies = document.cookie; - let username = cookies ? cookies.split("; ").filter(cookie => cookie.includes("username"))[0]?.split("=")[1] : "Unkown User"; + useEffect(() => { + chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }, [messages]); - const handleErrors = (e) => { - console.log("socket error", e); + useEffect(() => { + fetch(`/api/projects/${projectId}`) + .then((r) => r.json()) + .then((res) => { + if (res.success) { + setProject(res.data); + setCode(res.data.code || "// your code..."); + setLanguage(res.data.programmingLanguage || "javascript"); + } + }) + .catch(() => {}); + }, [projectId]); + + const handleErrors = () => { notify("Socket connection failed, try again later."); }; + const leave = () => { + if (socketRef) { + socketRef.emit("leave", projectId); + } + }; + useEffect(() => { - socketRef.on("connect_error", (err) => handleErrors(err)); - socketRef.on("connect_failed", (err) => handleErrors(err)); - socketRef.emit("JOIN", { roomId, username }); + socketRef.on("connect_error", () => handleErrors()); + socketRef.on("connect_failed", () => handleErrors()); + socketRef.emit("JOIN", { roomId: projectId, username }); - socketRef.on("JOINED", ({ code }) => { - setCode(code); + socketRef.on("JOINED", ({ code: incomingCode, clients: connected }) => { + if (incomingCode) setCode(incomingCode); + if (connected) setClients(connected); }); socketRef.on("code change", ({ newCode }) => { setCode(newCode); }); - socketRef.on("disconnected", ({ username }) => { - console.log(username); + socketRef.on("user joined", ({ username: u, clients: connected }) => { + if (connected) setClients(connected); + }); + + socketRef.on("disconnected", ({ username: u, clients: connected }) => { + if (connected) setClients(connected); + }); + + socketRef.on("chat", ({ username: u, message }) => { + setMessages((prev) => [...prev, { username: u, message }]); }); window.addEventListener("beforeunload", leave); return () => { window.removeEventListener("beforeunload", leave); }; - }, []); - - const leave = () => { - if (socketRef) { - socketRef.emit("leave", roomId); - } - }; + }, [projectId]); const options = { selectOnLineNumbers: true, + minimap: { enabled: false }, + fontSize: 14, + fontFamily: "'Cascadia Code', 'Fira Code', 'JetBrains Mono', Consolas, monospace", + lineNumbers: "on", + renderWhitespace: "selection", + bracketPairColorization: { enabled: true }, + autoClosingBrackets: "always", + scrollBeyondLastLine: false, + padding: { top: 16, bottom: 16 }, + smoothScrolling: true, + cursorBlinking: "smooth", + cursorSmoothCaretAnimation: "on", + accessibilitySupport: "on", + ariaLabel: "Code editor", }; const onCodeChange = (newValue) => { setCode(newValue); if (socketRef) { - socketRef.emit("code change", { roomId, newCode: newValue }); + socketRef.emit("code change", { roomId: projectId, newCode: newValue }); + } + }; + + const handleLangChange = (e) => { + const newLang = e.target.value; + setLanguage(newLang); + fetch(`/api/projects/${projectId}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ programmingLanguage: newLang }), + }).catch(() => notify("Failed to update language")); + }; + + const startEditingTitle = () => { + setTitleDraft(project?.title || ""); + setEditingTitle(true); + }; + + const saveTitle = () => { + const trimmed = titleDraft.trim(); + if (!trimmed) { + setEditingTitle(false); + return; + } + setProject((prev) => ({ ...prev, title: trimmed })); + setEditingTitle(false); + fetch(`/api/projects/${projectId}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ title: trimmed }), + }).catch(() => notify("Failed to update title")); + }; + + const handleTitleKey = (e) => { + if (e.key === "Enter") saveTitle(); + if (e.key === "Escape") setEditingTitle(false); + }; + + const handleSave = async () => { + setSaving(true); + try { + await fetch(`/api/projects/${projectId}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ code }), + }); + notify("Saved"); + } catch { + notify("Failed to save"); + } finally { + setSaving(false); } }; + const handleChatSend = (e) => { + e.preventDefault(); + if (!chatDraft.trim()) return; + socketRef.emit("chat", { roomId: projectId, username, message: chatDraft.trim() }); + setChatDraft(""); + }; + + const initial = username ? username.charAt(0).toUpperCase() : "?"; + return ( <> -
- +
+
+ +
+ + {/* ── Right panel ── */} +
- + ); }; diff --git a/src/components/Editor/Editor.module.scss b/src/components/Editor/Editor.module.scss index 9b7d326..0a9a77a 100644 --- a/src/components/Editor/Editor.module.scss +++ b/src/components/Editor/Editor.module.scss @@ -1,9 +1,272 @@ -.editor{ - width: 100%; - height: calc(100% - 70px); +.editor { + flex: 1; + min-width: 0; + position: relative; +} + +.editorWrap { + display: flex; + height: calc(100vh - 70px); padding-top: 70px; - z-index: 1; + background: var(--bg-primary); + transition: background 0.2s ease; +} + +/* ── Right panel ── */ +.panel { + width: 280px; + flex-shrink: 0; + background: var(--bg-secondary); + border-left: 1px solid var(--border-color); + display: flex; + flex-direction: column; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); + transition: background 0.2s ease, border-color 0.2s ease; +} + +.panel::-webkit-scrollbar { + width: 4px; +} + +.panel::-webkit-scrollbar-track { + background: var(--scrollbar-track); +} + +.panel::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 2px; +} + +.panelSection { + padding: 12px 16px; + border-bottom: 1px solid var(--border-color); + transition: border-color 0.2s ease; +} + +.panelSection:last-child { + border-bottom: none; + flex: 1; + display: flex; + flex-direction: column; + min-height: 0; +} + +.panelLabel { + font-size: 10px; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.1em; + color: var(--text-muted); + margin-bottom: 8px; +} + +.panelLabelRow { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 8px; +} + +.saveBtnSmall { + background: none; + border: 1px solid var(--border-color); + color: var(--text-muted); + font-size: 10px; + font-weight: 600; + padding: 2px 10px; + border-radius: 4px; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s, color 0.15s, background 0.15s; +} + +.saveBtnSmall:hover { + border-color: var(--brand); + color: var(--brand); + background: var(--brand-light); +} + +.saveBtnSmallSaving { + opacity: 0.4; + pointer-events: none; +} + +.panelValue { + font-size: 13px; + color: var(--text-primary); +} + +.projectNameEdit { + font-size: 13px; + color: var(--text-primary); + background: var(--bg-input); + border: 1px solid var(--brand); + padding: 4px 8px; + border-radius: 4px; + outline: none; + width: 100%; + font-family: inherit; + box-sizing: border-box; +} + +.projectNameDisplay { + font-size: 13px; + color: var(--text-primary); + cursor: text; + padding: 4px 8px; + border-radius: 4px; + border: 1px solid transparent; + transition: border-color 0.15s, background 0.15s; +} + +.projectNameDisplay:hover { + border-color: var(--border-color); + background: var(--bg-tertiary); +} + +.langSelect { + width: 100%; + background: var(--bg-input); + border: 1px solid var(--border-color); + color: var(--text-primary); + font-size: 13px; + padding: 6px 10px; + border-radius: 4px; + outline: none; + cursor: pointer; + font-family: inherit; + transition: border-color 0.15s; + appearance: auto; +} + +.langSelect:focus { + border-color: var(--brand); +} + +/* ── Collaborators (stacked circles) ── */ +.collabCount { + font-size: 12px; + color: var(--text-muted); +} + +.collabStack { + display: flex; + align-items: center; +} + +.collabCircle { + width: 28px; + height: 28px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 700; + flex-shrink: 0; + border: 2px solid; position: relative; + cursor: default; + transition: transform 0.15s; +} + +.collabCircle:hover { + transform: translateY(-2px); +} + +/* ── Chat ── */ +.chatMessages { + flex: 1; + overflow-y: auto; + overflow-x: hidden; + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 8px; + scrollbar-width: thin; + scrollbar-color: var(--scrollbar-thumb) var(--scrollbar-track); +} + +.chatMessages::-webkit-scrollbar { + width: 4px; +} + +.chatMessages::-webkit-scrollbar-track { + background: var(--scrollbar-track); +} + +.chatMessages::-webkit-scrollbar-thumb { + background: var(--scrollbar-thumb); + border-radius: 2px; +} + +.chatMsg { + font-size: 12px; + line-height: 1.4; +} + +.chatName { + font-weight: 600; +} + +.chatText { + color: var(--text-secondary); +} + +.chatEmpty { + color: var(--text-faint); + font-size: 11px; + text-align: center; + padding: 12px 0; +} + +.chatInputWrap { + display: flex; + gap: 6px; + margin-top: auto; +} + +.chatInput { + flex: 1; + background: var(--bg-input); + border: 1px solid var(--border-color); + color: var(--text-primary); + font-size: 12px; + padding: 7px 10px; + border-radius: 4px; + outline: none; + font-family: inherit; + transition: border-color 0.15s; +} + +.chatInput::placeholder { + color: var(--text-faint); +} + +.chatInput:focus { + border-color: var(--brand); +} + +.chatSend { + background: var(--brand); + color: white; + border: none; + border-radius: 4px; + padding: 0 12px; + cursor: pointer; + font-size: 12px; + font-weight: 600; + transition: background 0.15s; + font-family: inherit; +} + +.chatSend:hover { + background: var(--brand-hover); +} - // border: 1px solid black; -} \ No newline at end of file +.chatSend:focus-visible { + outline: 2px solid var(--brand); + outline-offset: 2px; +} diff --git a/src/components/Login/Login.jsx b/src/components/Login/Login.jsx index 982a099..8691451 100644 --- a/src/components/Login/Login.jsx +++ b/src/components/Login/Login.jsx @@ -30,7 +30,7 @@ const Login = () => { document.cookie = `userId=${res.data.userId}; path=/`; document.cookie = `AuthToken=${res.data.AuthToken}; path=/`; - router.push("/"); + router.push("/dashboard"); }) .catch((err) => { if (err.request.status == 400) { diff --git a/src/components/Login/Login.scss b/src/components/Login/Login.scss index d924981..1983eb0 100644 --- a/src/components/Login/Login.scss +++ b/src/components/Login/Login.scss @@ -4,6 +4,8 @@ align-items: center; width: 100%; height: 100%; + min-height: 100vh; + background: var(--bg-primary); .login{ display: flex; @@ -17,7 +19,7 @@ h1{ font-size: 24px; font-weight: 500; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); line-height: 38px; padding-bottom: 5px; } @@ -26,11 +28,11 @@ font-size: 15px; font-weight: 500; line-height: 22px; - color: rgba(47, 43, 61, 0.7); + color: var(--text-secondary); } - + .example{ - background-color: #e8e7fd; + background-color: var(--brand-light); padding: 10px; border-radius: 5px; margin: 20px 0px; @@ -40,7 +42,7 @@ font-size: 13px; font-weight: 500; line-height: 20px; - color: #7367f0; + color: var(--brand); } } @@ -49,7 +51,7 @@ font-size: 13px; font-weight: 500; line-height: 20px; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); } .input-tag{ @@ -60,17 +62,22 @@ font-size: 15px; line-height: 24px; font-weight: 500; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); + background: var(--bg-input); - border: 1px solid rgba(47, 43, 61, 0.3); + border: 1px solid var(--border-color); border-radius: 5px; + &::placeholder{ + color: var(--text-faint); + } + &:hover{ - border-color: rgba(47, 43, 61, 0.6); + border-color: var(--border-hover); } &:focus-visible{ - border-color: #7367f0; + border-color: var(--brand); } } @@ -84,7 +91,7 @@ justify-content: center; align-items: center; font-size: 15px; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); input{ width: 16px; @@ -94,7 +101,7 @@ } a{ - color: #7367f0; + color: var(--brand); text-decoration: none; font-weight: 500; } @@ -104,7 +111,7 @@ width: 100%; text-align: center; padding: 8px; - background-color: #7367f0; + background: var(--brand); border-radius: 5px; border: 0px; margin: 20px 0px; @@ -112,6 +119,13 @@ font-size: 15px; line-height: 22px; color: #ffffff; + cursor: pointer; + font-weight: 600; + transition: background 0.15s; + + &:hover{ + background: var(--brand-hover); + } } } @@ -121,14 +135,14 @@ font-size: 15px; line-height: 22px; font-weight: 400; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); a{ text-decoration: none; font-weight: 500; - color: #7367f0; + color: var(--brand); } } } } -} \ No newline at end of file +} diff --git a/src/components/NavBar/NavBar.module.scss b/src/components/NavBar/NavBar.module.scss index 7e7fa25..9393fc6 100644 --- a/src/components/NavBar/NavBar.module.scss +++ b/src/components/NavBar/NavBar.module.scss @@ -1,160 +1,245 @@ .nav{ width: 100%; - background-color: #fefaf3; + background: var(--bg-primary); z-index: 5; - - padding: 10px 20px; + padding: 10px 24px; position: fixed; + top: 0; + left: 0; + box-sizing: border-box; + border-bottom: 1px solid var(--border-color); + transition: background 0.2s ease, border-color 0.2s ease; .nav__menu{ height: 50px; - width: calc(100% - 40px); - display: flex; justify-content: space-between; align-items: center; + gap: 24px; + + > a { + display: flex; + align-items: center; + flex-shrink: 0; - img{ - width: 120px; + img{ + width: 120px; + display: block; + } } } .nav__list{ - width: 20%; display: flex; - justify-content: space-around; align-items: center; + gap: 12px; + flex-shrink: 0; .new_file{ + a { + text-decoration: none; + } + div{ - width: 120px; - padding: 5px 10px 5px 0px; - position: relative; - will-change: transform; - transform: translateY(0); + display: flex; + align-items: center; + gap: 6px; + padding: 6px 16px 6px 10px; cursor: pointer; - font-size: 16px; + font-size: 14px; font-weight: 600; transition: transform 150ms, box-shadow 150ms; + white-space: nowrap; + color: var(--brand); + border: 2px solid var(--brand); + border-radius: 50px; svg{ - padding: 5px 10px; - width: 15px; + width: 14px; + height: 14px; + flex-shrink: 0; } &:hover{ - transform: translateY(-2px); + transform: translateY(-1px); } &:active{ - box-shadow: none; transform: translateY(0); } } } .share{ + position: relative; + .img{ - width: 120px; - padding: 5px 10px 5px 0px; - position: relative; + display: flex; + align-items: center; + gap: 6px; + padding: 6px 16px 6px 10px; cursor: pointer; - font-size: 16px; - font-weight: 500; + font-size: 14px; + font-weight: 600; + white-space: nowrap; + color: var(--brand); + border: 2px solid var(--brand); + border-radius: 50px; + transition: transform 150ms, box-shadow 150ms; svg{ - padding: 5px; - width: 23px; + width: 18px; + flex-shrink: 0; + } + + &:hover{ + transform: translateY(-1px); + } + + &:active{ + transform: translateY(0); } } .shareNav{ position: absolute; - width: 350px; - height: fit-content; - right: 8%; - margin-top: 10px; - padding: 30px; + width: 360px; + right: 0; + top: calc(100% + 8px); + padding: 24px; border-radius: 10px; - background-color: white; - box-shadow: rgba(0, 0, 0, 0.02) 0px 1px 3px 0px, rgba(27, 31, 35, 0.15) 0px 0px 0px 1px; + background: var(--bg-elevated); + border: 1px solid var(--border-color); + box-shadow: var(--shadow-md); + z-index: 10; + + p{ + font-size: 14px; + color: var(--text-secondary); + margin: 0 0 12px; + } div{ display: flex; - justify-content: space-between; + gap: 8px; align-items: center; - margin: 10px 0px; input{ - padding: 10px; - border-radius: 5px; + flex: 1; + padding: 8px 12px; + border-radius: 6px; outline: none; - font-size: 14px; - border: 1px solid gray; + font-size: 13px; + border: 1px solid var(--border-color); + background: var(--bg-input); + color: var(--text-primary); + min-width: 0; } span{ + display: flex; + align-items: center; + gap: 4px; color: white; - padding: 3px 15px 5px 10px; - border-radius: 25px; - background-color: #5f5fff; + padding: 8px 16px; + border-radius: 8px; + background-color: var(--brand); + font-size: 13px; + font-weight: 500; + cursor: pointer; + flex-shrink: 0; svg{ - height: 20px; - width: 20px; - margin: 5px; + width: 16px; + height: 16px; } &:active{ - transform: scale(0.95); + transform: scale(0.96); } } } } } - .dashboard{ - color: white; - border-radius: 10px; - background-color: #5f5fff; + .themeToggle { + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border: 1px solid var(--border-color); + border-radius: 50%; + background: var(--bg-secondary); + color: var(--text-secondary); + cursor: pointer; + transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.15s; + flex-shrink: 0; + + svg { + width: 18px; + height: 18px; + } - div{ - width: 120px; - padding: 5px 10px 5px 0px; - position: relative; - cursor: pointer; - font-size: 16px; + &:hover { + border-color: var(--brand); + color: var(--brand); + transform: scale(1.05); + } + + &:active { + transform: scale(0.95); + } + } + + .profile{ + a{ + text-decoration: none; + } + + .avatar{ + display: flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 50%; + background-color: var(--brand); + color: white; + font-size: 14px; font-weight: 600; + cursor: pointer; + transition: transform 150ms; - svg{ - padding: 5px; - width: 24px; + &:hover{ + transform: scale(1.05); } - } - &:active{ - box-shadow: none; - transform: scale(0.95) + &:active{ + transform: scale(0.96); + } } } .signin{ - a{ text-decoration: none; - color: black; + color: var(--text-secondary); white-space: nowrap; span{ - font-size: 17px; + font-size: 15px; + font-weight: 500; cursor: pointer; - + display: flex; + align-items: center; + gap: 4px; + img{ - padding: 5px 10px; - width: 20px; + width: 16px; } } } } } -} \ No newline at end of file +} diff --git a/src/components/NavBar/NavBar.jsx b/src/components/NavBar/NavBar.tsx similarity index 58% rename from src/components/NavBar/NavBar.jsx rename to src/components/NavBar/NavBar.tsx index 2cd9b5b..95e02ef 100644 --- a/src/components/NavBar/NavBar.jsx +++ b/src/components/NavBar/NavBar.tsx @@ -2,39 +2,38 @@ import { useEffect, useState, useRef } from "react"; import Link from "next/link"; -import { useParams, useRouter } from "next/navigation"; -import { v4 as uuidV4 } from "uuid"; +import { useParams } from "next/navigation"; import { Toaster } from "react-hot-toast"; import axios from "axios"; import notify from "../../services/toast"; import { images } from "../../constant"; -import "../../style/btn1.scss"; -import "../../style/btn2.scss"; +import { useTheme } from "../../context/ThemeContext"; import style from "./NavBar.module.scss"; const NavBar = () => { - const param = useParams(); - const boxRef = useRef(null); - const router = useRouter(); + const params = useParams() as { projectId?: string; roomId?: string }; + const boxRef = useRef(null); + const [username, setUsername] = useState(""); const [isLoggedIn, setLoggedIn] = useState(false); const [isBoxVisible, setIsBoxVisible] = useState(false); + const { theme, toggleTheme } = useTheme(); - const roomId = param.roomId; - const shareableLink = typeof window !== "undefined" ? `${window.location.origin}/editor/${roomId}` : ""; + const projectId = params?.projectId || params?.roomId; + const shareableLink = + typeof window !== "undefined" && projectId + ? `${window.location.origin}/editor/${projectId}` + : ""; - const createFile = () => { - const roomId = uuidV4(); - router.push(`/editor/${roomId}`); - }; + const initial = username ? username.charAt(0).toUpperCase() : "?"; const toggleBoxVisibility = () => { setIsBoxVisible(!isBoxVisible); }; useEffect(() => { - const handleClickOutside = (event) => { - if (boxRef.current && !boxRef.current.contains(event.target)) { + const handleClickOutside = (event: MouseEvent) => { + if (boxRef.current && !boxRef.current.contains(event.target as Node)) { setIsBoxVisible(false); } }; @@ -52,17 +51,16 @@ const NavBar = () => { }; useEffect(() => { - const bearer = document.cookie - .split("; ") - .filter((item) => item.includes("AuthToken")); - - var token = null; + const cookies = document.cookie.split("; "); + const tokenCookie = cookies.find((c) => c.startsWith("AuthToken=")); + const userCookie = cookies.find((c) => c.startsWith("username=")); - if(bearer.length > 0){ - token = bearer[0].split("=")[1]; + if (userCookie) { + setUsername(decodeURIComponent(userCookie.split("=")[1])); } - if (token) { + if (tokenCookie) { + const token = tokenCookie.split("=")[1]; axios .get("/api/protected/isLoggedIn", { headers: { @@ -75,10 +73,8 @@ const NavBar = () => { setLoggedIn(true); } }) - .catch((err) => { - if (err && err.response && err.response.status === 401) { - setLoggedIn(false); - } + .catch(() => { + setLoggedIn(false); }); } }, []); @@ -88,12 +84,12 @@ const NavBar = () => {
- + CodeLab
- {roomId ? ( + {projectId ? (
-
+
{

Share this link with other people:

- + {
)}
- ) : ( -
createFile()}> -
- - New file -
-
- )} + ) : null} + + + {isLoggedIn ? ( - -
-
- - - - Dashboard +
+ +
+ {initial}
-
- + +
) : (
- + Sign in @@ -195,7 +186,15 @@ const NavBar = () => {
- + ); }; diff --git a/src/components/SignUp/SignUp.jsx b/src/components/SignUp/SignUp.jsx index 40546f2..2ba78c4 100644 --- a/src/components/SignUp/SignUp.jsx +++ b/src/components/SignUp/SignUp.jsx @@ -41,7 +41,7 @@ const SignUp = () => { document.cookie = `userId=${res.data.userId}; path=/`; document.cookie = `AuthToken=${res.data.AuthToken}; path=/`; - router.push("/"); + router.push("/dashboard"); }) .catch((error) => { alert(error.response.data.message); diff --git a/src/components/SignUp/SignUp.scss b/src/components/SignUp/SignUp.scss index 712dcf5..b2f0a87 100644 --- a/src/components/SignUp/SignUp.scss +++ b/src/components/SignUp/SignUp.scss @@ -4,6 +4,8 @@ align-items: center; width: 100%; height: 100%; + min-height: 100vh; + background: var(--bg-primary); .login{ display: flex; @@ -16,7 +18,7 @@ h1{ font-size: 24px; font-weight: 500; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); line-height: 38px; padding-bottom: 5px; } @@ -25,11 +27,11 @@ font-size: 15px; font-weight: 500; line-height: 22px; - color: rgba(47, 43, 61, 0.7); + color: var(--text-secondary); } - + .example{ - background-color: #e8e7fd; + background-color: var(--brand-light); padding: 10px; border-radius: 5px; margin: 20px 0px; @@ -38,7 +40,7 @@ font-size: 13px; font-weight: 500; line-height: 20px; - color: #7367f0; + color: var(--brand); } } @@ -47,10 +49,10 @@ font-size: 13px; font-weight: 500; line-height: 20px; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); &::placeholder{ - color: rgba(47, 43, 61, 0.3); + color: var(--text-faint); } } @@ -62,17 +64,18 @@ font-size: 15px; line-height: 24px; font-weight: 500; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); + background: var(--bg-input); - border: 1px solid rgba(47, 43, 61, 0.3); + border: 1px solid var(--border-color); border-radius: 5px; &:hover{ - border-color: rgba(47, 43, 61, 0.6); + border-color: var(--border-hover); } &:focus-visible{ - border-color: #7367f0; + border-color: var(--brand); } } @@ -86,7 +89,7 @@ justify-content: center; align-items: center; font-size: 15px; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); input{ width: 16px; @@ -96,7 +99,7 @@ } a{ - color: #7367f0; + color: var(--brand); text-decoration: none; font-weight: 500; } @@ -106,7 +109,7 @@ width: 100%; text-align: center; padding: 8px; - background-color: #7367f0; + background: var(--brand); border-radius: 5px; border: 0px; margin: 20px 0px; @@ -114,6 +117,13 @@ font-size: 15px; line-height: 22px; color: #ffffff; + cursor: pointer; + font-weight: 600; + transition: background 0.15s; + + &:hover{ + background: var(--brand-hover); + } } } @@ -123,14 +133,14 @@ font-size: 15px; line-height: 22px; font-weight: 400; - color: rgba(47, 43, 61, 0.9); + color: var(--text-primary); a{ text-decoration: none; font-weight: 500; - color: #7367f0; + color: var(--brand); } } } } -} \ No newline at end of file +} diff --git a/src/components/dashboard/CreateProjectDialog.tsx b/src/components/dashboard/CreateProjectDialog.tsx new file mode 100644 index 0000000..21b9c31 --- /dev/null +++ b/src/components/dashboard/CreateProjectDialog.tsx @@ -0,0 +1,217 @@ +"use client"; + +import { useState } from "react"; + +interface CreateProjectDialogProps { + open: boolean; + onClose: () => void; + onCreate: (data: { + title: string; + description: string; + programmingLanguage: string; + visibility: "public" | "private"; + }) => void; +} + +const LANGUAGES = [ + "javascript", + "typescript", + "python", + "html", + "css", + "json", +]; + +export default function CreateProjectDialog({ + open, + onClose, + onCreate, +}: CreateProjectDialogProps) { + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [language, setLanguage] = useState("javascript"); + const [visibility, setVisibility] = useState<"public" | "private">("private"); + + if (!open) return null; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!title.trim()) return; + onCreate({ title: title.trim(), description: description.trim(), programmingLanguage: language, visibility }); + setTitle(""); + setDescription(""); + setLanguage("javascript"); + setVisibility("private"); + }; + + return ( +
+
e.stopPropagation()}> +
+

Create New Project

+ +
+ +
+
+ + setTitle(e.target.value)} + placeholder="My Awesome Project" + required + autoFocus + /> +
+ +
+ +