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 (
- <>
-
-
-
-

-
-
-
- {isLoggedIn ? (
-
-
-
- ) : (
-
-
-
- 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 ? (
+
+
+
+ {isBoxVisible && (
+
{
+ event.stopPropagation();
+ }}
+ >
+
Share this link with other people:
+
+
+
+
+ Copy link
+
+
+
+ )}
+
+ ) : (
+
+ )}
+ {isLoggedIn ? (
+
+
+
+ ) : (
+
+
+
+ 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 ? (
-
+
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 ── */}
+
+
);
}
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 = () => {
-

+
- {roomId ? (
+ {projectId ? (
-
+
- ) : (
-
- )}
+ ) : null}
+
+
+
{isLoggedIn ? (
-
-
-
-
-
Dashboard
+
-
+
+
) : (
-
+
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
+
+
+
+
+
+
+ );
+}
+
+const styles: Record
= {
+ overlay: {
+ position: "fixed",
+ inset: 0,
+ background: "rgba(0,0,0,0.4)",
+ display: "flex",
+ justifyContent: "center",
+ alignItems: "center",
+ zIndex: 100,
+ },
+ dialog: {
+ background: "var(--bg-secondary)",
+ borderRadius: "12px",
+ width: "480px",
+ maxWidth: "90vw",
+ padding: "24px",
+ boxShadow: "var(--shadow-md)",
+ border: "1px solid var(--border-color)",
+ },
+ header: {
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ marginBottom: "20px",
+ },
+ heading: {
+ fontSize: "18px",
+ fontWeight: 600,
+ color: "var(--text-primary)",
+ margin: 0,
+ },
+ closeBtn: {
+ background: "none",
+ border: "none",
+ cursor: "pointer",
+ color: "var(--text-muted)",
+ padding: "4px",
+ borderRadius: "4px",
+ },
+ field: {
+ marginBottom: "16px",
+ flex: 1,
+ },
+ label: {
+ display: "block",
+ fontSize: "13px",
+ fontWeight: 500,
+ color: "var(--text-secondary)",
+ marginBottom: "6px",
+ },
+ input: {
+ width: "100%",
+ padding: "8px 12px",
+ fontSize: "14px",
+ border: "1px solid var(--border-color)",
+ borderRadius: "6px",
+ outline: "none",
+ boxSizing: "border-box",
+ fontFamily: "inherit",
+ background: "var(--bg-input)",
+ color: "var(--text-primary)",
+ },
+ row: {
+ display: "flex",
+ gap: "12px",
+ },
+ footer: {
+ display: "flex",
+ justifyContent: "flex-end",
+ gap: "8px",
+ marginTop: "8px",
+ },
+ cancelBtn: {
+ padding: "8px 16px",
+ fontSize: "14px",
+ background: "var(--bg-tertiary)",
+ border: "1px solid var(--border-color)",
+ borderRadius: "6px",
+ cursor: "pointer",
+ color: "var(--text-secondary)",
+ },
+ submitBtn: {
+ padding: "8px 20px",
+ fontSize: "14px",
+ background: "var(--brand)",
+ color: "#fff",
+ border: "none",
+ borderRadius: "6px",
+ cursor: "pointer",
+ fontWeight: 500,
+ },
+};
diff --git a/src/components/dashboard/ProjectActions.tsx b/src/components/dashboard/ProjectActions.tsx
new file mode 100644
index 0000000..736517f
--- /dev/null
+++ b/src/components/dashboard/ProjectActions.tsx
@@ -0,0 +1,190 @@
+"use client";
+
+import { useState, useRef, useEffect } from "react";
+
+interface ProjectActionsProps {
+ projectId: string;
+ projectTitle: string;
+ onRename: (id: string, title: string) => void;
+ onArchive: (id: string) => void;
+ onDelete: (id: string) => void;
+ onDuplicate: (id: string) => void;
+}
+
+export default function ProjectActions({
+ projectId,
+ projectTitle,
+ onRename,
+ onArchive,
+ onDelete,
+ onDuplicate,
+}: ProjectActionsProps) {
+ const [open, setOpen] = useState(false);
+ const [renaming, setRenaming] = useState(false);
+ const [newTitle, setNewTitle] = useState(projectTitle);
+ const ref = useRef(null);
+
+ useEffect(() => {
+ const handler = (e: MouseEvent) => {
+ if (ref.current && !ref.current.contains(e.target as Node)) {
+ setOpen(false);
+ setRenaming(false);
+ }
+ };
+ document.addEventListener("mousedown", handler);
+ return () => document.removeEventListener("mousedown", handler);
+ }, []);
+
+ const handleRename = () => {
+ if (renaming && newTitle.trim()) {
+ onRename(projectId, newTitle.trim());
+ setRenaming(false);
+ setOpen(false);
+ } else {
+ setNewTitle(projectTitle);
+ setRenaming(true);
+ }
+ };
+
+ return (
+
+
+
+ {open && (
+
e.stopPropagation()}>
+ {renaming ? (
+
+ setNewTitle(e.target.value)}
+ onKeyDown={(e) => {
+ if (e.key === "Enter") handleRename();
+ if (e.key === "Escape") setRenaming(false);
+ }}
+ autoFocus
+ onClick={(e) => e.stopPropagation()}
+ />
+
+
+ ) : (
+ <>
+
+
+
+
+
+ >
+ )}
+
+ )}
+
+ );
+}
+
+const styles: Record = {
+ trigger: {
+ background: "none",
+ border: "none",
+ cursor: "pointer",
+ padding: "4px",
+ borderRadius: "4px",
+ color: "var(--text-muted)",
+ display: "flex",
+ alignItems: "center",
+ },
+ menu: {
+ position: "absolute",
+ right: 0,
+ top: "100%",
+ background: "var(--bg-elevated)",
+ border: "1px solid var(--border-color)",
+ borderRadius: "8px",
+ boxShadow: "var(--shadow-md)",
+ minWidth: "160px",
+ zIndex: 50,
+ padding: "4px",
+ },
+ item: {
+ display: "block",
+ width: "100%",
+ padding: "8px 12px",
+ background: "none",
+ border: "none",
+ textAlign: "left",
+ fontSize: "13px",
+ cursor: "pointer",
+ borderRadius: "4px",
+ color: "var(--text-secondary)",
+ },
+ divider: {
+ height: "1px",
+ background: "var(--border-color)",
+ margin: "4px 0",
+ },
+ renameBox: {
+ padding: "8px",
+ display: "flex",
+ gap: "6px",
+ },
+ renameInput: {
+ flex: 1,
+ padding: "6px 8px",
+ fontSize: "13px",
+ border: "1px solid var(--border-color)",
+ borderRadius: "4px",
+ outline: "none",
+ background: "var(--bg-input)",
+ color: "var(--text-primary)",
+ },
+ renameBtn: {
+ padding: "6px 12px",
+ fontSize: "13px",
+ background: "var(--brand)",
+ color: "#fff",
+ border: "none",
+ borderRadius: "4px",
+ cursor: "pointer",
+ },
+};
diff --git a/src/components/dashboard/ProjectCard.tsx b/src/components/dashboard/ProjectCard.tsx
new file mode 100644
index 0000000..9992694
--- /dev/null
+++ b/src/components/dashboard/ProjectCard.tsx
@@ -0,0 +1,203 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import ProjectActions from "./ProjectActions";
+
+interface ProjectCardProps {
+ project: {
+ _id: string;
+ title: string;
+ description?: string;
+ programmingLanguage: string;
+ visibility: "public" | "private";
+ updatedAt: string;
+ createdAt: string;
+ owner: { _id: string; username: string };
+ archived: boolean;
+ };
+ onRename: (id: string, title: string) => void;
+ onArchive: (id: string) => void;
+ onDelete: (id: string) => void;
+ onDuplicate: (id: string) => void;
+}
+
+const languageColors: Record = {
+ javascript: "#f7df1e",
+ typescript: "#3178c6",
+ python: "#3572A5",
+ html: "#e34c26",
+ css: "#563d7c",
+ json: "#292929",
+ default: "#6b7280",
+};
+
+const languageBgColors: Record = {
+ javascript: "#fef9e5",
+ typescript: "#e8f0fe",
+ python: "#eaf2e9",
+ html: "#fde9e7",
+ css: "#ede7f5",
+ json: "#e8e8e8",
+ default: "#f3f4f6",
+};
+
+export default function ProjectCard({
+ project,
+ onRename,
+ onArchive,
+ onDelete,
+ onDuplicate,
+}: ProjectCardProps) {
+ const router = useRouter();
+ const lang = project.programmingLanguage;
+ const dotColor = languageColors[lang] || languageColors.default;
+ const badgeBg = languageBgColors[lang] || languageBgColors.default;
+
+ const handleClick = () => {
+ router.push(`/editor/${project._id}`);
+ };
+
+ const timeAgo = (dateStr: string) => {
+ const now = new Date();
+ const date = new Date(dateStr);
+ const diffMs = now.getTime() - date.getTime();
+ const diffMins = Math.floor(diffMs / 60000);
+ if (diffMins < 1) return "Just now";
+ if (diffMins < 60) return `${diffMins}m ago`;
+ const diffHours = Math.floor(diffMins / 60);
+ if (diffHours < 24) return `${diffHours}h ago`;
+ const diffDays = Math.floor(diffHours / 24);
+ if (diffDays < 30) return `${diffDays}d ago`;
+ return date.toLocaleDateString();
+ };
+
+ return (
+
+
+
+
+
+ {lang}
+
+
+ {project.visibility === "public" ? "Public" : "Private"}
+
+
+
+
+
+
+
{project.title}
+ {project.description && (
+
{project.description}
+ )}
+
+
+
+ {project.owner?.username || "Unknown"}
+ {timeAgo(project.updatedAt)}
+
+
+ );
+}
+
+const styles: Record = {
+ card: {
+ background: "var(--bg-secondary)",
+ borderRadius: "12px",
+ border: "1px solid var(--border-color)",
+ overflow: "hidden",
+ cursor: "default",
+ transition: "box-shadow 0.15s, transform 0.15s",
+ display: "flex",
+ flexDirection: "column",
+ },
+ top: {
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ padding: "14px 16px",
+ borderBottom: "1px solid var(--border-color)",
+ gap: "8px",
+ },
+ meta: {
+ display: "flex",
+ alignItems: "center",
+ gap: "8px",
+ minWidth: 0,
+ flex: 1,
+ },
+ dot: {
+ width: "10px",
+ height: "10px",
+ borderRadius: "50%",
+ flexShrink: 0,
+ },
+ langBadge: {
+ fontSize: "11px",
+ fontWeight: 600,
+ padding: "2px 8px",
+ borderRadius: "4px",
+ textTransform: "capitalize",
+ flexShrink: 0,
+ },
+ visibilityBadge: {
+ fontSize: "11px",
+ color: "var(--text-muted)",
+ background: "var(--bg-tertiary)",
+ padding: "2px 8px",
+ borderRadius: "4px",
+ flexShrink: 0,
+ },
+ body: {
+ padding: "16px",
+ flex: 1,
+ cursor: "pointer",
+ display: "flex",
+ flexDirection: "column",
+ gap: "6px",
+ },
+ title: {
+ fontSize: "16px",
+ fontWeight: 600,
+ color: "var(--text-primary)",
+ margin: 0,
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
+ },
+ description: {
+ fontSize: "13px",
+ color: "var(--text-muted)",
+ margin: 0,
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
+ lineHeight: "1.4",
+ },
+ footer: {
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+ padding: "10px 16px",
+ borderTop: "1px solid var(--border-color)",
+ fontSize: "12px",
+ color: "var(--text-muted)",
+ },
+ owner: {
+ overflow: "hidden",
+ textOverflow: "ellipsis",
+ whiteSpace: "nowrap",
+ minWidth: 0,
+ },
+ time: {
+ flexShrink: 0,
+ },
+};
diff --git a/src/components/dashboard/ProjectGrid.tsx b/src/components/dashboard/ProjectGrid.tsx
new file mode 100644
index 0000000..182e216
--- /dev/null
+++ b/src/components/dashboard/ProjectGrid.tsx
@@ -0,0 +1,90 @@
+"use client";
+
+import ProjectCard from "./ProjectCard";
+
+interface Project {
+ _id: string;
+ title: string;
+ description?: string;
+ programmingLanguage: string;
+ visibility: "public" | "private";
+ updatedAt: string;
+ createdAt: string;
+ owner: { _id: string; username: string };
+ archived: boolean;
+}
+
+interface ProjectGridProps {
+ projects: Project[];
+ onRename: (id: string, title: string) => void;
+ onArchive: (id: string) => void;
+ onDelete: (id: string) => void;
+ onDuplicate: (id: string) => void;
+}
+
+export default function ProjectGrid({
+ projects,
+ onRename,
+ onArchive,
+ onDelete,
+ onDuplicate,
+}: ProjectGridProps) {
+ if (projects.length === 0) {
+ return (
+
+
+
No projects yet
+
Create your first project to get started.
+
+ );
+ }
+
+ return (
+
+ {projects.map((project) => (
+
+ ))}
+
+ );
+}
+
+const styles: Record = {
+ grid: {
+ display: "grid",
+ gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))",
+ gap: "16px",
+ alignItems: "stretch",
+ },
+ empty: {
+ display: "flex",
+ flexDirection: "column",
+ alignItems: "center",
+ justifyContent: "center",
+ padding: "80px 20px",
+ color: "var(--text-muted)",
+ textAlign: "center",
+ },
+ emptyTitle: {
+ fontSize: "18px",
+ fontWeight: 600,
+ margin: "16px 0 4px",
+ color: "var(--text-muted)",
+ },
+ emptyText: {
+ fontSize: "14px",
+ margin: 0,
+ color: "var(--text-faint)",
+ },
+};
diff --git a/src/components/dashboard/SearchBar.tsx b/src/components/dashboard/SearchBar.tsx
new file mode 100644
index 0000000..cfd9797
--- /dev/null
+++ b/src/components/dashboard/SearchBar.tsx
@@ -0,0 +1,92 @@
+"use client";
+
+import { useState, useCallback } from "react";
+
+interface SearchBarProps {
+ onSearch: (query: string) => void;
+ placeholder?: string;
+}
+
+export default function SearchBar({ onSearch, placeholder = "Search projects..." }: SearchBarProps) {
+ const [value, setValue] = useState("");
+
+ const handleChange = useCallback(
+ (e: React.ChangeEvent) => {
+ const v = e.target.value;
+ setValue(v);
+ onSearch(v);
+ },
+ [onSearch]
+ );
+
+ const handleClear = useCallback(() => {
+ setValue("");
+ onSearch("");
+ }, [onSearch]);
+
+ return (
+
+
+
+ {value && (
+
+ )}
+
+ );
+}
+
+const styles: Record = {
+ wrapper: {
+ position: "relative",
+ width: "260px",
+ },
+ icon: {
+ position: "absolute",
+ left: "10px",
+ top: "50%",
+ transform: "translateY(-50%)",
+ width: "16px",
+ height: "16px",
+ color: "var(--text-muted)",
+ flexShrink: 0,
+ },
+ input: {
+ width: "100%",
+ padding: "9px 32px 9px 34px",
+ borderRadius: "8px",
+ border: "1px solid var(--border-color)",
+ fontSize: "14px",
+ outline: "none",
+ boxSizing: "border-box",
+ backgroundColor: "var(--bg-input)",
+ color: "var(--text-primary)",
+ lineHeight: "20px",
+ },
+ clear: {
+ position: "absolute",
+ right: "6px",
+ top: "50%",
+ transform: "translateY(-50%)",
+ background: "none",
+ border: "none",
+ cursor: "pointer",
+ color: "var(--text-muted)",
+ padding: "4px",
+ display: "flex",
+ alignItems: "center",
+ },
+};
diff --git a/src/context/ThemeContext.tsx b/src/context/ThemeContext.tsx
new file mode 100644
index 0000000..7afe628
--- /dev/null
+++ b/src/context/ThemeContext.tsx
@@ -0,0 +1,43 @@
+"use client";
+
+import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react";
+
+type Theme = "light" | "dark";
+
+interface ThemeContextValue {
+ theme: Theme;
+ toggleTheme: () => void;
+}
+
+const ThemeContext = createContext({
+ theme: "light",
+ toggleTheme: () => {},
+});
+
+export function ThemeProvider({ children }: { children: ReactNode }) {
+ const [theme, setTheme] = useState("light");
+
+ useEffect(() => {
+ const stored = localStorage.getItem("codelab-theme") as Theme | null;
+ const initial = stored === "dark" ? "dark" : "light";
+ setTheme(initial);
+ document.documentElement.setAttribute("data-theme", initial);
+ }, []);
+
+ const toggleTheme = useCallback(() => {
+ setTheme((prev) => {
+ const next = prev === "light" ? "dark" : "light";
+ localStorage.setItem("codelab-theme", next);
+ document.documentElement.setAttribute("data-theme", next);
+ return next;
+ });
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export const useTheme = () => useContext(ThemeContext);
diff --git a/src/index.css b/src/index.css
index 13727d4..7ab45eb 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,23 +1,89 @@
-@import url('https://fonts.googleapis.com/css2?family=Darker+Grotesque:wght@300..900&family=Outfit:wght@100..900&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@300..900&display=swap');
+* {
+ margin: 0;
+ padding: 0;
+ box-shadow: none;
+ box-sizing: border-box;
+}
-:root {
- font-family: "Outfit", sans-serif;
- --primary-color: #f8f7fa;
- --white-color: #ffffff;
- --blue-color: #7367f0;
- --dark-blue-color: #675cd8;
-
+html, body {
+ font-family: "DM Sans", sans-serif;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
-* {
- margin: 0;
- padding: 0;
- box-shadow: none;
+/* ── Light theme (default) ── */
+[data-theme="light"] {
+ --bg-primary: #fefaf3;
+ --bg-secondary: #ffffff;
+ --bg-tertiary: #fcf8f2;
+ --bg-elevated: #ffffff;
+ --bg-input: #ffffff;
+
+ --text-primary: #111827;
+ --text-secondary: #374151;
+ --text-muted: #6b7280;
+ --text-faint: #9ca3af;
+
+ --border-color: #e5e7eb;
+ --border-subtle: rgba(0, 0, 0, 0.06);
+ --border-hover: #d1d5db;
+
+ --brand: #5f5fff;
+ --brand-hover: #4a4ad4;
+ --brand-light: #eeedff;
+
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
+ --shadow-md: 0 4px 24px rgba(0, 0, 0, 0.08);
+
+ --scrollbar-thumb: #d1d5db;
+ --scrollbar-track: transparent;
+
+ color-scheme: light;
+}
+
+/* ── Dark theme ── */
+[data-theme="dark"] {
+ --bg-primary: #1e1e1e;
+ --bg-secondary: #252526;
+ --bg-tertiary: #2d2d2d;
+ --bg-elevated: #333333;
+ --bg-input: #1e1e1e;
+
+ --text-primary: #cccccc;
+ --text-secondary: #9ca3af;
+ --text-muted: #6b7280;
+ --text-faint: #555555;
+
+ --border-color: #2d2d2d;
+ --border-subtle: rgba(255, 255, 255, 0.06);
+ --border-hover: #3d3d3d;
+
+ --brand: #5f5fff;
+ --brand-hover: #6b6bff;
+ --brand-light: rgba(95, 95, 255, 0.15);
+
+ --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.2);
+ --shadow-md: 0 4px 24px rgba(0, 0, 0, 0.3);
+
+ --scrollbar-thumb: #3d3d3d;
+ --scrollbar-track: transparent;
+
+ color-scheme: dark;
+}
+
+body {
+ background: var(--bg-primary);
+ color: var(--text-primary);
+ transition: background 0.2s ease, color 0.2s ease;
+}
+
+::selection {
+ background: var(--brand-light);
+ color: var(--text-primary);
}
.app__flex{
diff --git a/src/lib/middleware/auth.js b/src/lib/middleware/auth.js
deleted file mode 100644
index 06b677c..0000000
--- a/src/lib/middleware/auth.js
+++ /dev/null
@@ -1,16 +0,0 @@
-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/middleware/auth.ts b/src/lib/middleware/auth.ts
new file mode 100644
index 0000000..a6032c7
--- /dev/null
+++ b/src/lib/middleware/auth.ts
@@ -0,0 +1,41 @@
+import jwt from "jsonwebtoken";
+import { NextRequest } from "next/server";
+
+export interface AuthSuccess {
+ userId: string;
+ username: string;
+ email: string;
+}
+
+interface AuthError {
+ error: string;
+ status: number;
+}
+
+export type AuthResult = AuthSuccess | AuthError;
+
+export function verifyToken(request: NextRequest): AuthResult {
+ 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 as string
+ ) as {
+ userId: string;
+ username: string;
+ email: string;
+ };
+ return {
+ userId: decode.userId,
+ username: decode.username,
+ email: decode.email,
+ };
+ } catch {
+ return { error: "Invalid token", status: 401 };
+ }
+}
diff --git a/src/lib/models/project.ts b/src/lib/models/project.ts
new file mode 100644
index 0000000..ff162a5
--- /dev/null
+++ b/src/lib/models/project.ts
@@ -0,0 +1,76 @@
+import mongoose, { Schema, Document, Model, Types } from "mongoose";
+import "./user";
+
+export type ProjectVisibility = "public" | "private";
+export type CollaboratorRole = "owner" | "editor" | "viewer";
+
+export interface ICollaborator {
+ userId: Types.ObjectId;
+ role: CollaboratorRole;
+ joinedAt: Date;
+}
+
+export interface IProjectDocument extends Document {
+ title: string;
+ description?: string;
+ programmingLanguage: string;
+ visibility: ProjectVisibility;
+ owner?: Types.ObjectId | null;
+ collaborators: ICollaborator[];
+ code?: string;
+ archived: boolean;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const collaboratorSchema = new Schema(
+ {
+ userId: { type: Schema.Types.ObjectId, ref: "User", required: true },
+ role: {
+ type: String,
+ enum: ["owner", "editor", "viewer"],
+ default: "editor",
+ },
+ joinedAt: { type: Date, default: Date.now },
+ },
+ { _id: false }
+);
+
+const projectSchema = new Schema(
+ {
+ title: { type: String, required: true, trim: true, maxlength: 100 },
+ description: { type: String, trim: true, maxlength: 500 },
+ programmingLanguage: { type: String, default: "javascript" },
+ visibility: {
+ type: String,
+ enum: ["public", "private"],
+ default: "private",
+ },
+ owner: {
+ type: Schema.Types.ObjectId,
+ ref: "User",
+ required: false,
+ default: null,
+ index: true,
+ },
+ collaborators: {
+ type: [collaboratorSchema],
+ default: [],
+ },
+ code: { type: String, default: "// your code..." },
+ archived: { type: Boolean, default: false },
+ },
+ { timestamps: true }
+);
+
+projectSchema.index({ owner: 1, archived: 1 });
+projectSchema.index(
+ { title: "text", description: "text" },
+ { default_language: "none" }
+);
+
+const Project: Model =
+ mongoose.models.Project ||
+ mongoose.model("Project", projectSchema, "projects");
+
+export default Project;
diff --git a/src/lib/models/user.js b/src/lib/models/user.js
deleted file mode 100644
index 66fd14e..0000000
--- a/src/lib/models/user.js
+++ /dev/null
@@ -1,10 +0,0 @@
-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/src/lib/models/user.ts b/src/lib/models/user.ts
new file mode 100644
index 0000000..b672636
--- /dev/null
+++ b/src/lib/models/user.ts
@@ -0,0 +1,25 @@
+import mongoose, { Schema, Document, Model } from "mongoose";
+
+export interface IUserDocument extends Document {
+ username: string;
+ email: string;
+ password: string;
+ avatar?: string;
+ createdAt: Date;
+ updatedAt: Date;
+}
+
+const userSchema = new Schema(
+ {
+ username: { type: String, required: true },
+ email: { type: String, required: true, unique: true },
+ password: { type: String, required: true },
+ avatar: { type: String },
+ },
+ { timestamps: true }
+);
+
+const User: Model =
+ mongoose.models.User || mongoose.model("User", userSchema, "users");
+
+export default User;
diff --git a/src/lib/socket.js b/src/lib/socket.js
index 935e511..0ea4a01 100644
--- a/src/lib/socket.js
+++ b/src/lib/socket.js
@@ -20,6 +20,10 @@ export function setupSocketIO(server) {
socketId: socket.id,
username: username || "Unknown Client",
});
+ socket.in(roomId).emit("user joined", {
+ username: username || "Unknown Client",
+ clients: roomsAndClientsmap[roomId].clients,
+ });
} else {
roomsAndClientsmap[roomId] = {
code: "// your code...",
@@ -40,13 +44,20 @@ export function setupSocketIO(server) {
socket.in(roomId).emit("code change", { newCode });
});
+ socket.on("chat", ({ roomId, username, message }) => {
+ io.in(roomId).emit("chat", { username, message });
+ });
+
socket.on("leave", (roomId) => {
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.in(roomId).emit("disconnected", {
+ username: client?.username,
+ clients,
+ });
}
socket.leave();
});
diff --git a/src/services/toast.js b/src/services/toast.js
index be9737a..587f055 100644
--- a/src/services/toast.js
+++ b/src/services/toast.js
@@ -4,11 +4,6 @@ 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/src/types/global.d.ts b/src/types/global.d.ts
new file mode 100644
index 0000000..85bcdaf
--- /dev/null
+++ b/src/types/global.d.ts
@@ -0,0 +1,16 @@
+declare module "*.module.scss" {
+ const classes: Record;
+ export default classes;
+}
+
+declare module "*.scss" {
+ const content: string;
+ export default content;
+}
+
+declare module "*.css" {
+ const content: string;
+ export default content;
+}
+
+
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..ec5bbed
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,44 @@
+export interface IUser {
+ _id?: string;
+ username: string;
+ email: string;
+ password: string;
+ avatar?: string;
+ createdAt?: Date;
+ updatedAt?: Date;
+}
+
+export type ProjectVisibility = "public" | "private";
+export type CollaboratorRole = "owner" | "editor" | "viewer";
+
+export interface ICollaborator {
+ userId: string;
+ role: CollaboratorRole;
+ joinedAt: Date;
+}
+
+export interface IProject {
+ _id?: string;
+ title: string;
+ description?: string;
+ programmingLanguage: string;
+ visibility: ProjectVisibility;
+ owner: string;
+ collaborators: ICollaborator[];
+ code?: string;
+ archived: boolean;
+ createdAt?: Date;
+ updatedAt?: Date;
+}
+
+export type ProjectFormData = Pick<
+ IProject,
+ "title" | "description" | "programmingLanguage" | "visibility"
+>;
+
+export interface ApiResponse {
+ success: boolean;
+ data?: T;
+ error?: string;
+ message?: string;
+}
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..c3e73ae
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,27 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": false,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "preserve",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./*"]
+ }
+ },
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "exclude": ["node_modules"]
+}