├── LICENSE
├── Readme.md
├── backend
├── .gitignore
├── config
│ └── jwtProvider.js
├── controllers
│ ├── analytics.js
│ ├── auth.js
│ ├── task.js
│ └── user.js
├── middlewares
│ ├── authorization.js
│ └── wrapAsync.js
├── models
│ ├── task.js
│ └── user.js
├── package-lock.json
├── package.json
├── routes
│ ├── analytics.js
│ ├── auth.js
│ ├── task.js
│ └── user.js
├── server.js
└── vercel.json
└── frontend
├── .eslintrc.cjs
├── .gitignore
├── index.html
├── package-lock.json
├── package.json
├── src
├── App.jsx
├── assets
│ ├── AuthImage.png
│ ├── checkbox_select.png
│ └── checkbox_unselect.png
├── components
│ ├── Loading.jsx
│ ├── Model.jsx
│ ├── PopUp.jsx
│ ├── PrivateRoute.jsx
│ └── TaskBox.jsx
├── css
│ ├── Analytics.css
│ ├── Dashboard.css
│ ├── Model.css
│ ├── Navbar.css
│ ├── Page_Not_Found.css
│ ├── PopUp.css
│ ├── Register_Login.css
│ └── Task.css
├── hooks
│ ├── useAddTask.js
│ ├── useAddToBoard.js
│ ├── useAllTask.js
│ ├── useAllTaskFilter.js
│ ├── useDeleteTask.js
│ ├── useGetAnalytics.js
│ ├── useGetTask.js
│ ├── useUpdateCategory.js
│ └── useUpdateTask.js
├── index.css
├── main.jsx
├── pages
│ ├── Analytics.jsx
│ ├── Backlog.jsx
│ ├── Board.jsx
│ ├── Dashboard.jsx
│ ├── Done.jsx
│ ├── InProgress.jsx
│ ├── Navbar.jsx
│ ├── PageNotFound.jsx
│ ├── Register_Login.jsx
│ ├── Settings.jsx
│ └── ToDo.jsx
├── redux
│ ├── slices
│ │ ├── authSlice.js
│ │ ├── stateSlice.js
│ │ └── taskSlice.js
│ └── store.js
└── utils
│ ├── generateDate.js
│ ├── header.js
│ └── validate.js
├── vercel.json
└── vite.config.js
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Akash Deep
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Readme.md:
--------------------------------------------------------------------------------
1 | # **Task Management Application**
2 |
3 | This is a Task Management Application built with a MERN stack. The front end is developed using React with Vite, and the back-end is built using Express.js with MongoDB for the database.
4 |
5 | ---
6 |
7 |
8 | Visitor count
9 |
10 |
11 |
12 |
13 |
14 |
15 | ## 📜 Project Overview
16 |
17 | _A task management app, where users can organize their personal and team-based tasks, track progress, and share task updates. Users can manage their task boards with features to create, edit, assign, and delete tasks for themselves and other members._
18 |
19 | Register Page
20 |
21 |
22 | 
23 |
24 |
25 | Dashboard Page
26 |
27 |
28 | 
29 |
30 |
31 | Public View
32 |
33 |
34 | 
35 |
36 |
37 |
38 | ## **Table of Contents**
39 |
40 | - [Features](#features)
41 | - [Tech Stack](#tech-stack)
42 | - [Setup Instructions](#setup-instructions)
43 | - [Scripts](#scripts)
44 | - [Live Demo](#live-demo)
45 | - [Author](#author)
46 | - [License](#license)
47 |
48 | ---
49 |
50 | ## **Features**
51 |
52 | - User authentication using JWT.
53 | - Secure password handling with bcrypt.js.
54 | - State management with Redux Toolkit.
55 | - User-friendly interface and Only public page Responsive.
56 | - Toast notifications for feedback.
57 | - RESTful APIs for seamless communication between frontend and backend.
58 |
59 | ### **Core Functionalities**
60 |
61 | - **User Authentication:**
62 |
63 | - Users can register and log in.
64 | - Only authenticated users can create and manage tasks.
65 |
66 | - **Task Management:**
67 |
68 | - Create tasks with properties like priority, optional due dates, categories, and the ability to share tasks with others (read-only public access for shared tasks).
69 | - Update tasks, including title, priority, and due dates.
70 | - Delete tasks.
71 | - Change task statuses across four categories: **Backlog**, **To-Do**, **In-Progress**, and **Done**.
72 | - Automatically highlight overdue tasks with red and completed tasks with green.
73 |
74 | - **User Management:**
75 |
76 | - Users can update their name, email, or password via the settings page.
77 | - Changes to email or password will log users out to ensure security.
78 |
79 | - **Analytics & Filtering:**
80 |
81 | - Review task analytics in a dedicated section.
82 | - Filter tasks by **Today**, **This Week**, or **This Month** (default is the current week).
83 |
84 | - **User-Friendly Interface:**
85 |
86 | - Task titles are truncated on the board for readability, with full titles accessible via tooltips.
87 | - Mandatory fields are marked with a red asterisk (\*).
88 | - Notifications and alerts are provided via toast messages.
89 |
90 | - **Collaboration:**
91 | - Add members to task boards.
92 | - Assign members to tasks during creation.
93 |
94 | ### **Additional Features**
95 |
96 | - Visual indicators for task statuses based on due dates:
97 | - **Red**: Overdue tasks in active categories.
98 | - **Green**: Tasks marked as done.
99 | - Pre-filled user information for seamless updates on the settings page.
100 |
101 | ## 🛠️ **Tech Stack**
102 |
103 | ### **Frontend**
104 |
105 | - **React**: UI library.
106 | - **React Router DOM**: For routing.
107 | - **Redux Toolkit**: State management.
108 | - **React Icons**: Icon library.
109 | - **React Toastify**: Notification handling.
110 | - **Vite**: Frontend build tool.
111 | - **Eslint**: Code quality and linting.
112 |
113 | ### **Backend**
114 |
115 | - **Express.js**: Backend framework.
116 | - **Mongoose**: MongoDB object modeling.
117 | - **JWT**: Secure token-based authentication.
118 | - **Bcrypt.js**: Password encryption.
119 | - **Dotenv**: Environment variable management.
120 | - **Cors**: Cross-origin resource sharing.
121 |
122 | ---
123 |
124 | ## **Setup Instructions**
125 |
126 | ### **Prerequisites**
127 |
128 | - Install **Node.js (18.17.1)**.
129 | - Install **npm** or **yarn**.
130 | - MongoDB database.
131 |
132 | ### **Backend Setup**
133 |
134 | 1. Navigate to the backend directory:
135 | ```bash
136 | cd backend
137 | ```
138 | 2. Install dependencies:
139 | ```bash
140 | npm install
141 | ```
142 | 3. Create a `.env` file in the backend directory and add:
143 | ```env
144 | MONGODB_URI=mongodb://127.0.0.1:27017/task_management
145 | FRONTEND_URL=http://localhost:5173
146 | PORT=9000
147 | JWT_SECRET=secret-kJKJllKKJJghLjOiUfcHGkMLgdJlLKDtrdyKLBJbRdesEkj
148 | ```
149 | 4. Start the server in development mode:
150 | ```bash
151 | npm run dev
152 | ```
153 |
154 | ### **Frontend Setup**
155 |
156 | 1. Navigate to the frontend directory:
157 | ```bash
158 | cd frontend
159 | ```
160 | 2. Install dependencies:
161 | ```bash
162 | npm install
163 | ```
164 | 3. Create a `.env` file in the frontend directory and add:
165 | ```env
166 | VITE_BACKEND_URL=http://localhost:9000
167 | VITE_FRONTEND_URL=http://localhost:5173
168 | ```
169 | 4. Start the development server:
170 | ```bash
171 | npm run dev
172 | ```
173 | 5. Open the application in your browser at `http://localhost:5173`.
174 |
175 | ---
176 |
177 | ## **Scripts**
178 |
179 | ### **Frontend**
180 |
181 | | Script | Description |
182 | | ----------------- | ------------------------------------------- |
183 | | `npm run dev` | Starts the development server. |
184 | | `npm run build` | Builds the production version of the app. |
185 | | `npm run lint` | Lints the codebase for errors and warnings. |
186 | | `npm run preview` | Previews the built application. |
187 |
188 | ### **Backend**
189 |
190 | | Script | Description |
191 | | --------------- | ---------------------------------------------------- |
192 | | `npm run start` | Starts the server in production mode. |
193 | | `npm run dev` | Starts the server in development mode using nodemon. |
194 |
195 | ---
196 |
197 | ## **Live Demo**
198 |
199 | Check out the live demo of Task Management here: [Task Management](https://task-management-org.vercel.app)
200 |
201 | ## **Author**
202 |
203 | Akash Deep \
204 | Email: contact.akashdeep023@gmail.com \
205 | LinkedIn: https://www.linkedin.com/in/akashdeep023/
206 |
207 | ## **Contributors**
208 |
209 | We'd like to acknowledge the efforts and contributions of the following individuals:
210 |
211 | - **[Akash Deep](https://github.com/akashdeep023)** - Full Stack development and Project lead.
212 | - **[Ekant Verma](https://github.com/ekantverma)** - Full Stack development.
213 | - **[Anjali Kumari](https://github.com/Anjali17aj)** - Full Stack development.
214 | - **[Shanedra Singh](https://github.com/shanedraSingh/)** - Full Stack development.
215 |
216 | ## **License**
217 |
218 | This project is licensed under the [MIT License](LICENSE).
219 |
220 | ---
221 |
222 | Thank you for checking out my Task Management project! If you have any feedback or suggestions, I would love to hear from you.
223 | Feel free to contribute, report issues, or suggest improvements! 😊
224 |
--------------------------------------------------------------------------------
/backend/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .env
3 |
--------------------------------------------------------------------------------
/backend/config/jwtProvider.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 | const JWT_SECRET = process.env.JWT_SECRET;
3 |
4 | const generateToken = (userId) => {
5 | const token = jwt.sign({ userId }, JWT_SECRET, { expiresIn: "48h" });
6 | return token;
7 | };
8 | const getUserIdFromToken = (token) => {
9 | const decodedToken = jwt.verify(token, JWT_SECRET);
10 | return decodedToken.userId;
11 | };
12 |
13 | module.exports = {
14 | generateToken,
15 | getUserIdFromToken,
16 | };
17 |
--------------------------------------------------------------------------------
/backend/controllers/analytics.js:
--------------------------------------------------------------------------------
1 | const Task = require("../models/task");
2 |
3 | const getAnalytics = async (req, res) => {
4 | let backlog = await Task.find({
5 | $or: [
6 | { $and: [{ userName: req.user._id }, { category: "backlog" }] },
7 | { $and: [{ assign: req.user.email }, { category: "backlog" }] },
8 | ],
9 | }).countDocuments();
10 | let todo = await Task.find({
11 | $or: [
12 | { $and: [{ userName: req.user._id }, { category: "to-do" }] },
13 | { $and: [{ assign: req.user.email }, { category: "to-do" }] },
14 | ],
15 | }).countDocuments();
16 | let inProgress = await Task.find({
17 | $or: [
18 | { $and: [{ userName: req.user._id }, { category: "in-progress" }] },
19 | { $and: [{ assign: req.user.email }, { category: "in-progress" }] },
20 | ],
21 | }).countDocuments();
22 | let done = await Task.find({
23 | $or: [
24 | { $and: [{ userName: req.user._id }, { category: "done" }] },
25 | { $and: [{ assign: req.user.email }, { category: "done" }] },
26 | ],
27 | }).countDocuments();
28 | let high = await Task.find({
29 | $or: [
30 | {
31 | $and: [
32 | { userName: req.user._id },
33 | { priority: "High Priority" },
34 | ],
35 | },
36 | {
37 | $and: [
38 | { assign: req.user.email },
39 | { priority: "High Priority" },
40 | ],
41 | },
42 | ],
43 | }).countDocuments();
44 | let moderate = await Task.find({
45 | $or: [
46 | {
47 | $and: [
48 | { userName: req.user._id },
49 | { priority: "Moderate Priority" },
50 | ],
51 | },
52 | {
53 | $and: [
54 | { assign: req.user.email },
55 | { priority: "Moderate Priority" },
56 | ],
57 | },
58 | ],
59 | }).countDocuments();
60 | let low = await Task.find({
61 | $or: [
62 | {
63 | $and: [
64 | { userName: req.user._id },
65 | { priority: "Low Priority" },
66 | ],
67 | },
68 | {
69 | $and: [
70 | { assign: req.user.email },
71 | { priority: "Low Priority" },
72 | ],
73 | },
74 | ],
75 | }).countDocuments();
76 | let dueDate = await Task.find({
77 | $or: [
78 | { $and: [{ userName: req.user._id }, { dueDate: { $ne: "" } }] },
79 | { $and: [{ assign: req.user.email }, { dueDate: { $ne: "" } }] },
80 | ],
81 | }).countDocuments();
82 | res.status(200).send({
83 | message: "success",
84 | data: { backlog, todo, inProgress, done, high, moderate, low, dueDate },
85 | });
86 | };
87 |
88 | module.exports = { getAnalytics };
89 |
--------------------------------------------------------------------------------
/backend/controllers/auth.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/user");
2 | const bcrypt = require("bcryptjs");
3 | const { generateToken } = require("../config/jwtProvider");
4 |
5 | const registerUser = async (req, res, next) => {
6 | let { name, email, password } = req.body;
7 | const existingUser = await User.findOne({ email: email });
8 | if (existingUser) {
9 | return res.status(400).json({ message: `User Already Exist` });
10 | }
11 | password = bcrypt.hashSync(password, 8);
12 | const userData = new User({
13 | name,
14 | email,
15 | password,
16 | });
17 | const user = await userData.save();
18 | const jwt = generateToken(user._id);
19 | res.status(200).json({
20 | message: "Registration Successfully",
21 | token: jwt,
22 | });
23 | };
24 |
25 | const loginUser = async (req, res) => {
26 | let { email, password } = req.body;
27 | let user = await User.findOne({ email: email });
28 | if (!user) {
29 | return res.status(404).json({ message: `User Not Found` });
30 | }
31 | const isPasswordValid = bcrypt.compareSync(password, user.password);
32 | if (!isPasswordValid) {
33 | return res.status(401).json({ message: "Incorrect Password" });
34 | }
35 | const jwt = generateToken(user._id);
36 | user.password = null;
37 | res.status(200).json({
38 | message: "Login Successfully",
39 | data: user,
40 | token: jwt,
41 | });
42 | };
43 |
44 | module.exports = { registerUser, loginUser };
45 |
--------------------------------------------------------------------------------
/backend/controllers/task.js:
--------------------------------------------------------------------------------
1 | const Task = require("../models/task");
2 |
3 | const getTask = async (req, res) => {
4 | const { id } = req.params;
5 | let task = await Task.findById(id);
6 | res.status(200).send({ message: "success", data: task });
7 | };
8 |
9 | const getAllTask = async (req, res) => {
10 | const daysAgo = (days) => new Date(new Date() - days * 24 * 3600000);
11 | let backlog = await Task.find({
12 | $or: [
13 | {
14 | $and: [
15 | { userName: req.user._id },
16 | { category: "backlog" },
17 | { createdAt: { $gte: daysAgo(req.query.days || 365) } },
18 | ],
19 | },
20 | {
21 | $and: [
22 | { assign: req.user.email },
23 | { category: "backlog" },
24 | { createdAt: { $gte: daysAgo(req.query.days || 365) } },
25 | ],
26 | },
27 | ],
28 | }).populate({
29 | path: "userName",
30 | select: "name",
31 | });
32 | let todo = await Task.find({
33 | $or: [
34 | {
35 | $and: [
36 | { userName: req.user._id },
37 | { category: "to-do" },
38 | { createdAt: { $gte: daysAgo(req.query.days || 365) } },
39 | ],
40 | },
41 | {
42 | $and: [
43 | { assign: req.user.email },
44 | { category: "to-do" },
45 | { createdAt: { $gte: daysAgo(req.query.days || 365) } },
46 | ],
47 | },
48 | ],
49 | }).populate({
50 | path: "userName",
51 | select: "name",
52 | });
53 | let inProgress = await Task.find({
54 | $or: [
55 | {
56 | $and: [
57 | { userName: req.user._id },
58 | { category: "in-progress" },
59 | { createdAt: { $gte: daysAgo(req.query.days || 365) } },
60 | ],
61 | },
62 | {
63 | $and: [
64 | { assign: req.user.email },
65 | { category: "in-progress" },
66 | { createdAt: { $gte: daysAgo(req.query.days || 365) } },
67 | ],
68 | },
69 | ],
70 | }).populate({
71 | path: "userName",
72 | select: "name",
73 | });
74 | let done = await Task.find({
75 | $or: [
76 | {
77 | $and: [
78 | { userName: req.user._id },
79 | { category: "done" },
80 | { createdAt: { $gte: daysAgo(req.query.days || 365) } },
81 | ],
82 | },
83 | {
84 | $and: [
85 | { assign: req.user.email },
86 | { category: "done" },
87 | { createdAt: { $gte: daysAgo(req.query.days || 365) } },
88 | ],
89 | },
90 | ],
91 | }).populate({
92 | path: "userName",
93 | select: "name",
94 | });
95 | res.status(200).send({
96 | message: "success",
97 | data: { backlog, todo, inProgress, done },
98 | });
99 | };
100 |
101 | const addTask = async (req, res) => {
102 | let { title, priority, checklist, dueDate, assign } = req.body;
103 | let userName = req.user._id;
104 | let newTask = new Task({
105 | title,
106 | priority,
107 | checklist,
108 | dueDate,
109 | userName,
110 | assign,
111 | });
112 | let createTask = await newTask.save();
113 | let task = await Task.findById(createTask._id).populate({
114 | path: "userName",
115 | select: "name",
116 | });
117 | res.status(200).send({ message: "success", data: task });
118 | };
119 |
120 | const updateTask = async (req, res) => {
121 | let { title, priority, checklist, dueDate, assign } = req.body;
122 | const { id } = req.params;
123 | let oldTask = await Task.findById(id);
124 | let updatedTask = await Task.findByIdAndUpdate(
125 | id,
126 | { title, priority, checklist, dueDate, assign },
127 | { new: true }
128 | ).populate({
129 | path: "userName",
130 | select: "name",
131 | });
132 | if (
133 | (assign == "" && oldTask.assign == req.user.email) ||
134 | (assign != "" &&
135 | assign != req.user.email &&
136 | updatedTask.userName._id.toString() != req.user._id.toString())
137 | ) {
138 | res.status(200).send({
139 | message: "success",
140 | data: updatedTask,
141 | removeAssignCategory: oldTask.category,
142 | });
143 | } else {
144 | res.status(200).send({ message: "success", data: updatedTask });
145 | }
146 | };
147 | const deleteTask = async (req, res) => {
148 | const { id } = req.params;
149 | const deleteTask = await Task.findByIdAndDelete(id);
150 | res.status(200).send({ message: "success", data: deleteTask });
151 | };
152 |
153 | const updateCategory = async (req, res) => {
154 | const { id } = req.params;
155 | const { category } = req.body;
156 | let task = await Task.findByIdAndUpdate(
157 | id,
158 | { category: category },
159 | { new: true }
160 | ).populate({
161 | path: "userName",
162 | select: "name",
163 | });
164 |
165 | res.status(200).send({
166 | message: "success",
167 | data: task,
168 | });
169 | };
170 |
171 | module.exports = {
172 | getTask,
173 | getAllTask,
174 | addTask,
175 | updateTask,
176 | deleteTask,
177 | updateCategory,
178 | };
179 |
--------------------------------------------------------------------------------
/backend/controllers/user.js:
--------------------------------------------------------------------------------
1 | const User = require("../models/user");
2 | const bcrypt = require("bcryptjs");
3 |
4 | const getAuthUser = async (req, res) => {
5 | if (!req.user) {
6 | return res.status(404).json({ message: `User Not Found` });
7 | }
8 | res.status(200).json({
9 | data: req.user,
10 | });
11 | };
12 |
13 | const updateUser = async (req, res) => {
14 | let { name, email, oldPassword, newPassword } = req.body;
15 | if (req.user.email != email) {
16 | const existingUser = await User.findOne({ email: email });
17 | if (existingUser) {
18 | return res.status(400).json({ message: `Email Already Used` });
19 | }
20 | }
21 | const user = await User.findById(req.user.id);
22 | const passwordEqual = bcrypt.compareSync(oldPassword, user.password);
23 | if (passwordEqual) {
24 | newPassword = bcrypt.hashSync(newPassword, 8);
25 | const userData = await User.findByIdAndUpdate(
26 | req.user.id,
27 | {
28 | name: name,
29 | email: email,
30 | password: newPassword,
31 | },
32 | { new: true }
33 | );
34 | userData.password = null;
35 | res.status(200).json({
36 | message: "success",
37 | data: userData,
38 | });
39 | } else {
40 | return res.status(400).json({ message: "Password is incorrect" });
41 | }
42 | };
43 |
44 | const getAllUsers = async (req, res) => {
45 | const allUsers = await User.find({ _id: { $ne: req.user._id } })
46 | .select("-password")
47 | .sort({ _id: -1 });
48 | res.status(200).send({ data: allUsers });
49 | };
50 | const updateBoard = async (req, res) => {
51 | let { email } = req.body;
52 | const userData = await User.findByIdAndUpdate(
53 | req.user.id,
54 | { $push: { board: email } },
55 | { new: true }
56 | );
57 | userData.password = null;
58 | res.status(200).json({
59 | message: "success",
60 | data: userData,
61 | email: email,
62 | });
63 | };
64 |
65 | module.exports = { getAuthUser, updateUser, getAllUsers, updateBoard };
66 |
--------------------------------------------------------------------------------
/backend/middlewares/authorization.js:
--------------------------------------------------------------------------------
1 | const { getUserIdFromToken } = require("../config/jwtProvider");
2 | const User = require("../models/user");
3 | const wrapAsync = require("./wrapAsync");
4 |
5 | const authorization = wrapAsync(async (req, res, next) => {
6 | const token = req.headers.authorization?.split(" ")[1];
7 | if (!token) {
8 | return res.status(404).send({ message: "Token not found" });
9 | }
10 | const userId = getUserIdFromToken(token);
11 | if (userId) {
12 | req.user = await User.findById(userId).select("-password");
13 | next();
14 | } else {
15 | return res.status(404).send({ message: "Something went wrong" });
16 | }
17 | });
18 |
19 | module.exports = { authorization };
20 |
--------------------------------------------------------------------------------
/backend/middlewares/wrapAsync.js:
--------------------------------------------------------------------------------
1 | module.exports = (fn) => {
2 | return (req, res, next) => {
3 | fn(req, res, next).catch(next);
4 | };
5 | };
6 |
--------------------------------------------------------------------------------
/backend/models/task.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const taskSchema = new mongoose.Schema(
4 | {
5 | title: {
6 | type: String,
7 | required: true,
8 | unique: true,
9 | trim: true,
10 | },
11 | priority: {
12 | type: String,
13 | required: true,
14 | },
15 | category: {
16 | type: String,
17 | required: true,
18 | default: "to-do",
19 | },
20 | checklist: [
21 | {
22 | name: {
23 | type: String,
24 | trim: true,
25 | required: true,
26 | },
27 | isDone: {
28 | type: Boolean,
29 | default: false,
30 | required: true,
31 | },
32 | },
33 | ],
34 | userName: {
35 | type: mongoose.Schema.ObjectId,
36 | ref: "User",
37 | required: true,
38 | },
39 | assign: String,
40 |
41 | dueDate: Date,
42 | },
43 | {
44 | timestamps: true,
45 | }
46 | );
47 |
48 | const Task = mongoose.model("Task", taskSchema);
49 | module.exports = Task;
50 |
--------------------------------------------------------------------------------
/backend/models/user.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const userSchema = new mongoose.Schema(
4 | {
5 | name: {
6 | type: String,
7 | required: true,
8 | },
9 | password: {
10 | type: String,
11 | required: true,
12 | },
13 | email: {
14 | type: String,
15 | required: true,
16 | unique: true,
17 | },
18 | board: [String],
19 | },
20 | {
21 | timestamps: true,
22 | }
23 | );
24 |
25 | const User = mongoose.model("User", userSchema);
26 | module.exports = User;
27 |
--------------------------------------------------------------------------------
/backend/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "lockfileVersion": 3,
5 | "requires": true,
6 | "packages": {
7 | "": {
8 | "name": "backend",
9 | "version": "1.0.0",
10 | "license": "ISC",
11 | "dependencies": {
12 | "bcryptjs": "^2.4.3",
13 | "cors": "^2.8.5",
14 | "dotenv": "^16.4.5",
15 | "express": "^4.19.2",
16 | "jsonwebtoken": "^9.0.2",
17 | "mongoose": "^8.4.1"
18 | },
19 | "engines": {
20 | "node": "18.17.1"
21 | }
22 | },
23 | "node_modules/@mongodb-js/saslprep": {
24 | "version": "1.1.7",
25 | "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz",
26 | "integrity": "sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==",
27 | "dependencies": {
28 | "sparse-bitfield": "^3.0.3"
29 | }
30 | },
31 | "node_modules/@types/webidl-conversions": {
32 | "version": "7.0.3",
33 | "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
34 | "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
35 | },
36 | "node_modules/@types/whatwg-url": {
37 | "version": "11.0.5",
38 | "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
39 | "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
40 | "dependencies": {
41 | "@types/webidl-conversions": "*"
42 | }
43 | },
44 | "node_modules/accepts": {
45 | "version": "1.3.8",
46 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
47 | "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
48 | "dependencies": {
49 | "mime-types": "~2.1.34",
50 | "negotiator": "0.6.3"
51 | },
52 | "engines": {
53 | "node": ">= 0.6"
54 | }
55 | },
56 | "node_modules/array-flatten": {
57 | "version": "1.1.1",
58 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
59 | "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
60 | },
61 | "node_modules/bcryptjs": {
62 | "version": "2.4.3",
63 | "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
64 | "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
65 | },
66 | "node_modules/body-parser": {
67 | "version": "1.20.2",
68 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
69 | "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
70 | "dependencies": {
71 | "bytes": "3.1.2",
72 | "content-type": "~1.0.5",
73 | "debug": "2.6.9",
74 | "depd": "2.0.0",
75 | "destroy": "1.2.0",
76 | "http-errors": "2.0.0",
77 | "iconv-lite": "0.4.24",
78 | "on-finished": "2.4.1",
79 | "qs": "6.11.0",
80 | "raw-body": "2.5.2",
81 | "type-is": "~1.6.18",
82 | "unpipe": "1.0.0"
83 | },
84 | "engines": {
85 | "node": ">= 0.8",
86 | "npm": "1.2.8000 || >= 1.4.16"
87 | }
88 | },
89 | "node_modules/bson": {
90 | "version": "6.7.0",
91 | "resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz",
92 | "integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==",
93 | "engines": {
94 | "node": ">=16.20.1"
95 | }
96 | },
97 | "node_modules/buffer-equal-constant-time": {
98 | "version": "1.0.1",
99 | "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
100 | "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
101 | },
102 | "node_modules/bytes": {
103 | "version": "3.1.2",
104 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
105 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
106 | "engines": {
107 | "node": ">= 0.8"
108 | }
109 | },
110 | "node_modules/call-bind": {
111 | "version": "1.0.7",
112 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
113 | "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
114 | "dependencies": {
115 | "es-define-property": "^1.0.0",
116 | "es-errors": "^1.3.0",
117 | "function-bind": "^1.1.2",
118 | "get-intrinsic": "^1.2.4",
119 | "set-function-length": "^1.2.1"
120 | },
121 | "engines": {
122 | "node": ">= 0.4"
123 | },
124 | "funding": {
125 | "url": "https://github.com/sponsors/ljharb"
126 | }
127 | },
128 | "node_modules/content-disposition": {
129 | "version": "0.5.4",
130 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
131 | "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
132 | "dependencies": {
133 | "safe-buffer": "5.2.1"
134 | },
135 | "engines": {
136 | "node": ">= 0.6"
137 | }
138 | },
139 | "node_modules/content-type": {
140 | "version": "1.0.5",
141 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
142 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
143 | "engines": {
144 | "node": ">= 0.6"
145 | }
146 | },
147 | "node_modules/cookie": {
148 | "version": "0.6.0",
149 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
150 | "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
151 | "engines": {
152 | "node": ">= 0.6"
153 | }
154 | },
155 | "node_modules/cookie-signature": {
156 | "version": "1.0.6",
157 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
158 | "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
159 | },
160 | "node_modules/cors": {
161 | "version": "2.8.5",
162 | "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
163 | "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
164 | "dependencies": {
165 | "object-assign": "^4",
166 | "vary": "^1"
167 | },
168 | "engines": {
169 | "node": ">= 0.10"
170 | }
171 | },
172 | "node_modules/debug": {
173 | "version": "2.6.9",
174 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
175 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
176 | "dependencies": {
177 | "ms": "2.0.0"
178 | }
179 | },
180 | "node_modules/define-data-property": {
181 | "version": "1.1.4",
182 | "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
183 | "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
184 | "dependencies": {
185 | "es-define-property": "^1.0.0",
186 | "es-errors": "^1.3.0",
187 | "gopd": "^1.0.1"
188 | },
189 | "engines": {
190 | "node": ">= 0.4"
191 | },
192 | "funding": {
193 | "url": "https://github.com/sponsors/ljharb"
194 | }
195 | },
196 | "node_modules/depd": {
197 | "version": "2.0.0",
198 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
199 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
200 | "engines": {
201 | "node": ">= 0.8"
202 | }
203 | },
204 | "node_modules/destroy": {
205 | "version": "1.2.0",
206 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
207 | "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
208 | "engines": {
209 | "node": ">= 0.8",
210 | "npm": "1.2.8000 || >= 1.4.16"
211 | }
212 | },
213 | "node_modules/dotenv": {
214 | "version": "16.4.5",
215 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
216 | "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
217 | "engines": {
218 | "node": ">=12"
219 | },
220 | "funding": {
221 | "url": "https://dotenvx.com"
222 | }
223 | },
224 | "node_modules/ecdsa-sig-formatter": {
225 | "version": "1.0.11",
226 | "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
227 | "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
228 | "dependencies": {
229 | "safe-buffer": "^5.0.1"
230 | }
231 | },
232 | "node_modules/ee-first": {
233 | "version": "1.1.1",
234 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
235 | "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
236 | },
237 | "node_modules/encodeurl": {
238 | "version": "1.0.2",
239 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
240 | "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
241 | "engines": {
242 | "node": ">= 0.8"
243 | }
244 | },
245 | "node_modules/es-define-property": {
246 | "version": "1.0.0",
247 | "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
248 | "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
249 | "dependencies": {
250 | "get-intrinsic": "^1.2.4"
251 | },
252 | "engines": {
253 | "node": ">= 0.4"
254 | }
255 | },
256 | "node_modules/es-errors": {
257 | "version": "1.3.0",
258 | "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
259 | "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
260 | "engines": {
261 | "node": ">= 0.4"
262 | }
263 | },
264 | "node_modules/escape-html": {
265 | "version": "1.0.3",
266 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
267 | "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
268 | },
269 | "node_modules/etag": {
270 | "version": "1.8.1",
271 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
272 | "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
273 | "engines": {
274 | "node": ">= 0.6"
275 | }
276 | },
277 | "node_modules/express": {
278 | "version": "4.19.2",
279 | "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
280 | "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
281 | "dependencies": {
282 | "accepts": "~1.3.8",
283 | "array-flatten": "1.1.1",
284 | "body-parser": "1.20.2",
285 | "content-disposition": "0.5.4",
286 | "content-type": "~1.0.4",
287 | "cookie": "0.6.0",
288 | "cookie-signature": "1.0.6",
289 | "debug": "2.6.9",
290 | "depd": "2.0.0",
291 | "encodeurl": "~1.0.2",
292 | "escape-html": "~1.0.3",
293 | "etag": "~1.8.1",
294 | "finalhandler": "1.2.0",
295 | "fresh": "0.5.2",
296 | "http-errors": "2.0.0",
297 | "merge-descriptors": "1.0.1",
298 | "methods": "~1.1.2",
299 | "on-finished": "2.4.1",
300 | "parseurl": "~1.3.3",
301 | "path-to-regexp": "0.1.7",
302 | "proxy-addr": "~2.0.7",
303 | "qs": "6.11.0",
304 | "range-parser": "~1.2.1",
305 | "safe-buffer": "5.2.1",
306 | "send": "0.18.0",
307 | "serve-static": "1.15.0",
308 | "setprototypeof": "1.2.0",
309 | "statuses": "2.0.1",
310 | "type-is": "~1.6.18",
311 | "utils-merge": "1.0.1",
312 | "vary": "~1.1.2"
313 | },
314 | "engines": {
315 | "node": ">= 0.10.0"
316 | }
317 | },
318 | "node_modules/finalhandler": {
319 | "version": "1.2.0",
320 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
321 | "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
322 | "dependencies": {
323 | "debug": "2.6.9",
324 | "encodeurl": "~1.0.2",
325 | "escape-html": "~1.0.3",
326 | "on-finished": "2.4.1",
327 | "parseurl": "~1.3.3",
328 | "statuses": "2.0.1",
329 | "unpipe": "~1.0.0"
330 | },
331 | "engines": {
332 | "node": ">= 0.8"
333 | }
334 | },
335 | "node_modules/forwarded": {
336 | "version": "0.2.0",
337 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
338 | "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
339 | "engines": {
340 | "node": ">= 0.6"
341 | }
342 | },
343 | "node_modules/fresh": {
344 | "version": "0.5.2",
345 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
346 | "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
347 | "engines": {
348 | "node": ">= 0.6"
349 | }
350 | },
351 | "node_modules/function-bind": {
352 | "version": "1.1.2",
353 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
354 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
355 | "funding": {
356 | "url": "https://github.com/sponsors/ljharb"
357 | }
358 | },
359 | "node_modules/get-intrinsic": {
360 | "version": "1.2.4",
361 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
362 | "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
363 | "dependencies": {
364 | "es-errors": "^1.3.0",
365 | "function-bind": "^1.1.2",
366 | "has-proto": "^1.0.1",
367 | "has-symbols": "^1.0.3",
368 | "hasown": "^2.0.0"
369 | },
370 | "engines": {
371 | "node": ">= 0.4"
372 | },
373 | "funding": {
374 | "url": "https://github.com/sponsors/ljharb"
375 | }
376 | },
377 | "node_modules/gopd": {
378 | "version": "1.0.1",
379 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
380 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
381 | "dependencies": {
382 | "get-intrinsic": "^1.1.3"
383 | },
384 | "funding": {
385 | "url": "https://github.com/sponsors/ljharb"
386 | }
387 | },
388 | "node_modules/has-property-descriptors": {
389 | "version": "1.0.2",
390 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
391 | "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
392 | "dependencies": {
393 | "es-define-property": "^1.0.0"
394 | },
395 | "funding": {
396 | "url": "https://github.com/sponsors/ljharb"
397 | }
398 | },
399 | "node_modules/has-proto": {
400 | "version": "1.0.3",
401 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
402 | "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
403 | "engines": {
404 | "node": ">= 0.4"
405 | },
406 | "funding": {
407 | "url": "https://github.com/sponsors/ljharb"
408 | }
409 | },
410 | "node_modules/has-symbols": {
411 | "version": "1.0.3",
412 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
413 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
414 | "engines": {
415 | "node": ">= 0.4"
416 | },
417 | "funding": {
418 | "url": "https://github.com/sponsors/ljharb"
419 | }
420 | },
421 | "node_modules/hasown": {
422 | "version": "2.0.2",
423 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
424 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
425 | "dependencies": {
426 | "function-bind": "^1.1.2"
427 | },
428 | "engines": {
429 | "node": ">= 0.4"
430 | }
431 | },
432 | "node_modules/http-errors": {
433 | "version": "2.0.0",
434 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
435 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
436 | "dependencies": {
437 | "depd": "2.0.0",
438 | "inherits": "2.0.4",
439 | "setprototypeof": "1.2.0",
440 | "statuses": "2.0.1",
441 | "toidentifier": "1.0.1"
442 | },
443 | "engines": {
444 | "node": ">= 0.8"
445 | }
446 | },
447 | "node_modules/iconv-lite": {
448 | "version": "0.4.24",
449 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
450 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
451 | "dependencies": {
452 | "safer-buffer": ">= 2.1.2 < 3"
453 | },
454 | "engines": {
455 | "node": ">=0.10.0"
456 | }
457 | },
458 | "node_modules/inherits": {
459 | "version": "2.0.4",
460 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
461 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
462 | },
463 | "node_modules/ipaddr.js": {
464 | "version": "1.9.1",
465 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
466 | "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
467 | "engines": {
468 | "node": ">= 0.10"
469 | }
470 | },
471 | "node_modules/jsonwebtoken": {
472 | "version": "9.0.2",
473 | "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
474 | "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
475 | "dependencies": {
476 | "jws": "^3.2.2",
477 | "lodash.includes": "^4.3.0",
478 | "lodash.isboolean": "^3.0.3",
479 | "lodash.isinteger": "^4.0.4",
480 | "lodash.isnumber": "^3.0.3",
481 | "lodash.isplainobject": "^4.0.6",
482 | "lodash.isstring": "^4.0.1",
483 | "lodash.once": "^4.0.0",
484 | "ms": "^2.1.1",
485 | "semver": "^7.5.4"
486 | },
487 | "engines": {
488 | "node": ">=12",
489 | "npm": ">=6"
490 | }
491 | },
492 | "node_modules/jsonwebtoken/node_modules/ms": {
493 | "version": "2.1.3",
494 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
495 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
496 | },
497 | "node_modules/jwa": {
498 | "version": "1.4.1",
499 | "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
500 | "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
501 | "dependencies": {
502 | "buffer-equal-constant-time": "1.0.1",
503 | "ecdsa-sig-formatter": "1.0.11",
504 | "safe-buffer": "^5.0.1"
505 | }
506 | },
507 | "node_modules/jws": {
508 | "version": "3.2.2",
509 | "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
510 | "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
511 | "dependencies": {
512 | "jwa": "^1.4.1",
513 | "safe-buffer": "^5.0.1"
514 | }
515 | },
516 | "node_modules/kareem": {
517 | "version": "2.6.3",
518 | "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
519 | "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
520 | "engines": {
521 | "node": ">=12.0.0"
522 | }
523 | },
524 | "node_modules/lodash.includes": {
525 | "version": "4.3.0",
526 | "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
527 | "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
528 | },
529 | "node_modules/lodash.isboolean": {
530 | "version": "3.0.3",
531 | "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
532 | "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
533 | },
534 | "node_modules/lodash.isinteger": {
535 | "version": "4.0.4",
536 | "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
537 | "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
538 | },
539 | "node_modules/lodash.isnumber": {
540 | "version": "3.0.3",
541 | "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
542 | "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
543 | },
544 | "node_modules/lodash.isplainobject": {
545 | "version": "4.0.6",
546 | "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
547 | "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
548 | },
549 | "node_modules/lodash.isstring": {
550 | "version": "4.0.1",
551 | "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
552 | "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
553 | },
554 | "node_modules/lodash.once": {
555 | "version": "4.1.1",
556 | "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
557 | "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
558 | },
559 | "node_modules/media-typer": {
560 | "version": "0.3.0",
561 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
562 | "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
563 | "engines": {
564 | "node": ">= 0.6"
565 | }
566 | },
567 | "node_modules/memory-pager": {
568 | "version": "1.5.0",
569 | "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
570 | "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
571 | },
572 | "node_modules/merge-descriptors": {
573 | "version": "1.0.1",
574 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
575 | "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
576 | },
577 | "node_modules/methods": {
578 | "version": "1.1.2",
579 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
580 | "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
581 | "engines": {
582 | "node": ">= 0.6"
583 | }
584 | },
585 | "node_modules/mime": {
586 | "version": "1.6.0",
587 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
588 | "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
589 | "bin": {
590 | "mime": "cli.js"
591 | },
592 | "engines": {
593 | "node": ">=4"
594 | }
595 | },
596 | "node_modules/mime-db": {
597 | "version": "1.52.0",
598 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
599 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
600 | "engines": {
601 | "node": ">= 0.6"
602 | }
603 | },
604 | "node_modules/mime-types": {
605 | "version": "2.1.35",
606 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
607 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
608 | "dependencies": {
609 | "mime-db": "1.52.0"
610 | },
611 | "engines": {
612 | "node": ">= 0.6"
613 | }
614 | },
615 | "node_modules/mongodb": {
616 | "version": "6.6.2",
617 | "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.2.tgz",
618 | "integrity": "sha512-ZF9Ugo2JCG/GfR7DEb4ypfyJJyiKbg5qBYKRintebj8+DNS33CyGMkWbrS9lara+u+h+yEOGSRiLhFO/g1s1aw==",
619 | "dependencies": {
620 | "@mongodb-js/saslprep": "^1.1.5",
621 | "bson": "^6.7.0",
622 | "mongodb-connection-string-url": "^3.0.0"
623 | },
624 | "engines": {
625 | "node": ">=16.20.1"
626 | },
627 | "peerDependencies": {
628 | "@aws-sdk/credential-providers": "^3.188.0",
629 | "@mongodb-js/zstd": "^1.1.0",
630 | "gcp-metadata": "^5.2.0",
631 | "kerberos": "^2.0.1",
632 | "mongodb-client-encryption": ">=6.0.0 <7",
633 | "snappy": "^7.2.2",
634 | "socks": "^2.7.1"
635 | },
636 | "peerDependenciesMeta": {
637 | "@aws-sdk/credential-providers": {
638 | "optional": true
639 | },
640 | "@mongodb-js/zstd": {
641 | "optional": true
642 | },
643 | "gcp-metadata": {
644 | "optional": true
645 | },
646 | "kerberos": {
647 | "optional": true
648 | },
649 | "mongodb-client-encryption": {
650 | "optional": true
651 | },
652 | "snappy": {
653 | "optional": true
654 | },
655 | "socks": {
656 | "optional": true
657 | }
658 | }
659 | },
660 | "node_modules/mongodb-connection-string-url": {
661 | "version": "3.0.1",
662 | "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
663 | "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
664 | "dependencies": {
665 | "@types/whatwg-url": "^11.0.2",
666 | "whatwg-url": "^13.0.0"
667 | }
668 | },
669 | "node_modules/mongoose": {
670 | "version": "8.4.1",
671 | "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.4.1.tgz",
672 | "integrity": "sha512-odQ2WEWGL3hb0Qex+QMN4eH6D34WdMEw7F1If2MGABApSDmG9cMmqv/G1H6WsXmuaH9mkuuadW/WbLE5+tHJwA==",
673 | "dependencies": {
674 | "bson": "^6.7.0",
675 | "kareem": "2.6.3",
676 | "mongodb": "6.6.2",
677 | "mpath": "0.9.0",
678 | "mquery": "5.0.0",
679 | "ms": "2.1.3",
680 | "sift": "17.1.3"
681 | },
682 | "engines": {
683 | "node": ">=16.20.1"
684 | },
685 | "funding": {
686 | "type": "opencollective",
687 | "url": "https://opencollective.com/mongoose"
688 | }
689 | },
690 | "node_modules/mongoose/node_modules/ms": {
691 | "version": "2.1.3",
692 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
693 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
694 | },
695 | "node_modules/mpath": {
696 | "version": "0.9.0",
697 | "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
698 | "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
699 | "engines": {
700 | "node": ">=4.0.0"
701 | }
702 | },
703 | "node_modules/mquery": {
704 | "version": "5.0.0",
705 | "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
706 | "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
707 | "dependencies": {
708 | "debug": "4.x"
709 | },
710 | "engines": {
711 | "node": ">=14.0.0"
712 | }
713 | },
714 | "node_modules/mquery/node_modules/debug": {
715 | "version": "4.3.5",
716 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
717 | "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
718 | "dependencies": {
719 | "ms": "2.1.2"
720 | },
721 | "engines": {
722 | "node": ">=6.0"
723 | },
724 | "peerDependenciesMeta": {
725 | "supports-color": {
726 | "optional": true
727 | }
728 | }
729 | },
730 | "node_modules/mquery/node_modules/ms": {
731 | "version": "2.1.2",
732 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
733 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
734 | },
735 | "node_modules/ms": {
736 | "version": "2.0.0",
737 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
738 | "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
739 | },
740 | "node_modules/negotiator": {
741 | "version": "0.6.3",
742 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
743 | "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
744 | "engines": {
745 | "node": ">= 0.6"
746 | }
747 | },
748 | "node_modules/object-assign": {
749 | "version": "4.1.1",
750 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
751 | "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
752 | "engines": {
753 | "node": ">=0.10.0"
754 | }
755 | },
756 | "node_modules/object-inspect": {
757 | "version": "1.13.1",
758 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
759 | "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
760 | "funding": {
761 | "url": "https://github.com/sponsors/ljharb"
762 | }
763 | },
764 | "node_modules/on-finished": {
765 | "version": "2.4.1",
766 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
767 | "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
768 | "dependencies": {
769 | "ee-first": "1.1.1"
770 | },
771 | "engines": {
772 | "node": ">= 0.8"
773 | }
774 | },
775 | "node_modules/parseurl": {
776 | "version": "1.3.3",
777 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
778 | "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
779 | "engines": {
780 | "node": ">= 0.8"
781 | }
782 | },
783 | "node_modules/path-to-regexp": {
784 | "version": "0.1.7",
785 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
786 | "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
787 | },
788 | "node_modules/proxy-addr": {
789 | "version": "2.0.7",
790 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
791 | "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
792 | "dependencies": {
793 | "forwarded": "0.2.0",
794 | "ipaddr.js": "1.9.1"
795 | },
796 | "engines": {
797 | "node": ">= 0.10"
798 | }
799 | },
800 | "node_modules/punycode": {
801 | "version": "2.3.1",
802 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
803 | "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
804 | "engines": {
805 | "node": ">=6"
806 | }
807 | },
808 | "node_modules/qs": {
809 | "version": "6.11.0",
810 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
811 | "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
812 | "dependencies": {
813 | "side-channel": "^1.0.4"
814 | },
815 | "engines": {
816 | "node": ">=0.6"
817 | },
818 | "funding": {
819 | "url": "https://github.com/sponsors/ljharb"
820 | }
821 | },
822 | "node_modules/range-parser": {
823 | "version": "1.2.1",
824 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
825 | "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
826 | "engines": {
827 | "node": ">= 0.6"
828 | }
829 | },
830 | "node_modules/raw-body": {
831 | "version": "2.5.2",
832 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
833 | "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
834 | "dependencies": {
835 | "bytes": "3.1.2",
836 | "http-errors": "2.0.0",
837 | "iconv-lite": "0.4.24",
838 | "unpipe": "1.0.0"
839 | },
840 | "engines": {
841 | "node": ">= 0.8"
842 | }
843 | },
844 | "node_modules/safe-buffer": {
845 | "version": "5.2.1",
846 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
847 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
848 | "funding": [
849 | {
850 | "type": "github",
851 | "url": "https://github.com/sponsors/feross"
852 | },
853 | {
854 | "type": "patreon",
855 | "url": "https://www.patreon.com/feross"
856 | },
857 | {
858 | "type": "consulting",
859 | "url": "https://feross.org/support"
860 | }
861 | ]
862 | },
863 | "node_modules/safer-buffer": {
864 | "version": "2.1.2",
865 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
866 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
867 | },
868 | "node_modules/semver": {
869 | "version": "7.6.2",
870 | "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
871 | "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
872 | "bin": {
873 | "semver": "bin/semver.js"
874 | },
875 | "engines": {
876 | "node": ">=10"
877 | }
878 | },
879 | "node_modules/send": {
880 | "version": "0.18.0",
881 | "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
882 | "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
883 | "dependencies": {
884 | "debug": "2.6.9",
885 | "depd": "2.0.0",
886 | "destroy": "1.2.0",
887 | "encodeurl": "~1.0.2",
888 | "escape-html": "~1.0.3",
889 | "etag": "~1.8.1",
890 | "fresh": "0.5.2",
891 | "http-errors": "2.0.0",
892 | "mime": "1.6.0",
893 | "ms": "2.1.3",
894 | "on-finished": "2.4.1",
895 | "range-parser": "~1.2.1",
896 | "statuses": "2.0.1"
897 | },
898 | "engines": {
899 | "node": ">= 0.8.0"
900 | }
901 | },
902 | "node_modules/send/node_modules/ms": {
903 | "version": "2.1.3",
904 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
905 | "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
906 | },
907 | "node_modules/serve-static": {
908 | "version": "1.15.0",
909 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
910 | "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
911 | "dependencies": {
912 | "encodeurl": "~1.0.2",
913 | "escape-html": "~1.0.3",
914 | "parseurl": "~1.3.3",
915 | "send": "0.18.0"
916 | },
917 | "engines": {
918 | "node": ">= 0.8.0"
919 | }
920 | },
921 | "node_modules/set-function-length": {
922 | "version": "1.2.2",
923 | "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
924 | "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
925 | "dependencies": {
926 | "define-data-property": "^1.1.4",
927 | "es-errors": "^1.3.0",
928 | "function-bind": "^1.1.2",
929 | "get-intrinsic": "^1.2.4",
930 | "gopd": "^1.0.1",
931 | "has-property-descriptors": "^1.0.2"
932 | },
933 | "engines": {
934 | "node": ">= 0.4"
935 | }
936 | },
937 | "node_modules/setprototypeof": {
938 | "version": "1.2.0",
939 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
940 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
941 | },
942 | "node_modules/side-channel": {
943 | "version": "1.0.6",
944 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
945 | "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
946 | "dependencies": {
947 | "call-bind": "^1.0.7",
948 | "es-errors": "^1.3.0",
949 | "get-intrinsic": "^1.2.4",
950 | "object-inspect": "^1.13.1"
951 | },
952 | "engines": {
953 | "node": ">= 0.4"
954 | },
955 | "funding": {
956 | "url": "https://github.com/sponsors/ljharb"
957 | }
958 | },
959 | "node_modules/sift": {
960 | "version": "17.1.3",
961 | "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
962 | "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
963 | },
964 | "node_modules/sparse-bitfield": {
965 | "version": "3.0.3",
966 | "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
967 | "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
968 | "dependencies": {
969 | "memory-pager": "^1.0.2"
970 | }
971 | },
972 | "node_modules/statuses": {
973 | "version": "2.0.1",
974 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
975 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
976 | "engines": {
977 | "node": ">= 0.8"
978 | }
979 | },
980 | "node_modules/toidentifier": {
981 | "version": "1.0.1",
982 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
983 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
984 | "engines": {
985 | "node": ">=0.6"
986 | }
987 | },
988 | "node_modules/tr46": {
989 | "version": "4.1.1",
990 | "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
991 | "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
992 | "dependencies": {
993 | "punycode": "^2.3.0"
994 | },
995 | "engines": {
996 | "node": ">=14"
997 | }
998 | },
999 | "node_modules/type-is": {
1000 | "version": "1.6.18",
1001 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1002 | "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1003 | "dependencies": {
1004 | "media-typer": "0.3.0",
1005 | "mime-types": "~2.1.24"
1006 | },
1007 | "engines": {
1008 | "node": ">= 0.6"
1009 | }
1010 | },
1011 | "node_modules/unpipe": {
1012 | "version": "1.0.0",
1013 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1014 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1015 | "engines": {
1016 | "node": ">= 0.8"
1017 | }
1018 | },
1019 | "node_modules/utils-merge": {
1020 | "version": "1.0.1",
1021 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1022 | "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1023 | "engines": {
1024 | "node": ">= 0.4.0"
1025 | }
1026 | },
1027 | "node_modules/vary": {
1028 | "version": "1.1.2",
1029 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1030 | "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1031 | "engines": {
1032 | "node": ">= 0.8"
1033 | }
1034 | },
1035 | "node_modules/webidl-conversions": {
1036 | "version": "7.0.0",
1037 | "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
1038 | "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
1039 | "engines": {
1040 | "node": ">=12"
1041 | }
1042 | },
1043 | "node_modules/whatwg-url": {
1044 | "version": "13.0.0",
1045 | "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
1046 | "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
1047 | "dependencies": {
1048 | "tr46": "^4.1.1",
1049 | "webidl-conversions": "^7.0.0"
1050 | },
1051 | "engines": {
1052 | "node": ">=16"
1053 | }
1054 | }
1055 | }
1056 | }
1057 |
--------------------------------------------------------------------------------
/backend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend",
3 | "version": "1.0.0",
4 | "description": "Task_Management",
5 | "main": "server.js",
6 | "engines": {
7 | "node": "18.17.1"
8 | },
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1",
11 | "start": "node server.js",
12 | "dev": "nodemon server.js"
13 | },
14 | "author": "",
15 | "license": "ISC",
16 | "dependencies": {
17 | "bcryptjs": "^2.4.3",
18 | "cors": "^2.8.5",
19 | "dotenv": "^16.4.5",
20 | "express": "^4.19.2",
21 | "jsonwebtoken": "^9.0.2",
22 | "mongoose": "^8.4.1"
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/backend/routes/analytics.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | const { getAnalytics } = require("../controllers/analytics");
4 | const wrapAsync = require("../middlewares/wrapAsync");
5 | const { authorization } = require("../middlewares/authorization");
6 |
7 | router.get("/", authorization, wrapAsync(getAnalytics));
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/backend/routes/auth.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | const authControllers = require("../controllers/auth");
4 | const wrapAsync = require("../middlewares/wrapAsync");
5 |
6 | router.post("/register", wrapAsync(authControllers.registerUser));
7 | router.post("/login", wrapAsync(authControllers.loginUser));
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/backend/routes/task.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | const {
4 | getTask,
5 | getAllTask,
6 | addTask,
7 | updateTask,
8 | deleteTask,
9 | updateCategory,
10 | } = require("../controllers/task");
11 | const wrapAsync = require("../middlewares/wrapAsync");
12 | const { authorization } = require("../middlewares/authorization");
13 |
14 | router.get("/all", authorization, wrapAsync(getAllTask));
15 | router.get("/:id", wrapAsync(getTask));
16 | router.post("/add", authorization, wrapAsync(addTask));
17 | router.put("/:id", authorization, wrapAsync(updateTask));
18 | router.delete("/:id", authorization, wrapAsync(deleteTask));
19 | router.put("/category/:id", authorization, wrapAsync(updateCategory));
20 |
21 | module.exports = router;
22 |
--------------------------------------------------------------------------------
/backend/routes/user.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | const userControllers = require("../controllers/user");
4 | const wrapAsync = require("../middlewares/wrapAsync");
5 | const { authorization } = require("../middlewares/authorization");
6 |
7 | router.get("/profile", authorization, wrapAsync(userControllers.getAuthUser));
8 | router.put("/update", authorization, wrapAsync(userControllers.updateUser));
9 | router.get("/users", authorization, wrapAsync(userControllers.getAllUsers));
10 | router.post("/board", authorization, wrapAsync(userControllers.updateBoard));
11 |
12 | module.exports = router;
13 |
--------------------------------------------------------------------------------
/backend/server.js:
--------------------------------------------------------------------------------
1 | const dotenv = require("dotenv");
2 | dotenv.config();
3 | const express = require("express");
4 | const cors = require("cors");
5 | const mongoose = require("mongoose");
6 |
7 | const app = express();
8 |
9 | const corsOptions = {
10 | origin: process.env.FRONTEND_URL,
11 | methods: ["GET", "POST", "DELETE", "PUT"],
12 | allowedHeaders: ["Content-Type", "Authorization"],
13 | credentials: true,
14 | };
15 |
16 | app.use(cors(corsOptions));
17 | app.use(express.json());
18 | app.use(express.urlencoded({ extended: true }));
19 | const PORT = process.env.PORT || 3000;
20 |
21 | // All routers
22 | const authRouter = require("./routes/auth");
23 | const userRouter = require("./routes/user");
24 | const taskRoute = require("./routes/task");
25 | const analyticsRoute = require("./routes/analytics");
26 |
27 | // Connect to Database
28 | main()
29 | .then(() => console.log("Database Connection established"))
30 | .catch((err) => console.log(err));
31 |
32 | async function main() {
33 | await mongoose.connect(process.env.MONGODB_URI);
34 | }
35 |
36 | // Root route
37 | app.get("/", (req, res) => {
38 | res.json({
39 | message: "Welcome to Task_Management!",
40 | frontend_url: process.env.FRONTEND_URL,
41 | });
42 | });
43 |
44 | // All routes
45 | app.use("/api/auth", authRouter);
46 | app.use("/api/user", userRouter);
47 | app.use("/api/task", taskRoute);
48 | app.use("/api/analytics", analyticsRoute);
49 |
50 | // Invaild routes
51 | app.all("*", (req, res) => {
52 | res.json({ error: "Invaild Route" });
53 | });
54 |
55 | // Error handling middleware
56 | app.use((err, req, res, next) => {
57 | const errorMessage = err.message || "Something Went Wrong!";
58 | res.status(500).json({ message: errorMessage });
59 | });
60 |
61 | // Start the server
62 | const server = app.listen(PORT, async () => {
63 | console.log(`Server listening on ${PORT}`);
64 | });
65 |
--------------------------------------------------------------------------------
/backend/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | {
5 | "src": "./server.js",
6 | "use": "@vercel/node"
7 | }
8 | ],
9 | "routes": [
10 | {
11 | "src": "/(.*)",
12 | "dest": "/server.js"
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------
/frontend/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: { browser: true, es2020: true },
4 | extends: [
5 | 'eslint:recommended',
6 | 'plugin:react/recommended',
7 | 'plugin:react/jsx-runtime',
8 | 'plugin:react-hooks/recommended',
9 | ],
10 | ignorePatterns: ['dist', '.eslintrc.cjs'],
11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
12 | settings: { react: { version: '18.2' } },
13 | plugins: ['react-refresh'],
14 | rules: {
15 | 'react/jsx-no-target-blank': 'off',
16 | 'react-refresh/only-export-components': [
17 | 'warn',
18 | { allowConstantExport: true },
19 | ],
20 | },
21 | }
22 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | .env
11 | node_modules
12 | dist
13 | dist-ssr
14 | *.local
15 |
16 | # Editor directories and files
17 | .vscode/*
18 | !.vscode/extensions.json
19 | .idea
20 | .DS_Store
21 | *.suo
22 | *.ntvs*
23 | *.njsproj
24 | *.sln
25 | *.sw?
26 |
--------------------------------------------------------------------------------
/frontend/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
13 | Task_Management
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/frontend/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "private": true,
4 | "version": "0.0.0",
5 | "type": "module",
6 | "scripts": {
7 | "dev": "vite",
8 | "build": "vite build",
9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
10 | "preview": "vite preview"
11 | },
12 | "dependencies": {
13 | "@reduxjs/toolkit": "^2.2.5",
14 | "react": "^18.2.0",
15 | "react-dom": "^18.2.0",
16 | "react-icons": "^5.2.1",
17 | "react-redux": "^9.1.2",
18 | "react-router-dom": "^6.23.1",
19 | "react-toastify": "^10.0.6"
20 | },
21 | "devDependencies": {
22 | "@types/react": "^18.2.66",
23 | "@types/react-dom": "^18.2.22",
24 | "@vitejs/plugin-react": "^4.2.1",
25 | "eslint": "^8.57.0",
26 | "eslint-plugin-react": "^7.34.1",
27 | "eslint-plugin-react-hooks": "^4.6.0",
28 | "eslint-plugin-react-refresh": "^0.4.6",
29 | "vite": "^5.2.0"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { Suspense, useEffect } from "react";
2 | import { Route, Routes, useLocation, useNavigate } from "react-router-dom";
3 | import Dashboard from "./pages/Dashboard";
4 | import PageNotFound from "./pages/PageNotFound";
5 | import Register_Login from "./pages/Register_Login";
6 | import PrivateRoute from "./components/PrivateRoute";
7 | import Loading from "./components/Loading";
8 | import { useDispatch, useSelector } from "react-redux";
9 | import { addAuth } from "./redux/slices/authSlice";
10 | import { setLoading } from "./redux/slices/stateSlice";
11 | import { TaskCardPublic } from "./components/Model";
12 | import getHeader from "./utils/header";
13 | import { toast } from "react-toastify";
14 |
15 | function App() {
16 | const dispatch = useDispatch();
17 | const navigate = useNavigate();
18 | const { pathname } = useLocation();
19 | const loading = useSelector((store) => store.state.loading);
20 | const auth = useSelector((store) => store.auth);
21 | const token = localStorage.getItem("token");
22 | const getAuthUser = () => {
23 | dispatch(setLoading(true));
24 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/user/profile`, {
25 | method: "GET",
26 | headers: getHeader(),
27 | })
28 | .then((res) => res.json())
29 | .then((json) => {
30 | dispatch(setLoading(false));
31 | if (json?.data) {
32 | dispatch(addAuth(json.data));
33 | navigate("/");
34 | } else {
35 | navigate("/login");
36 | toast.error("You are not logged in");
37 | }
38 | })
39 | .catch((err) => {
40 | toast.error("Something went wrong");
41 | dispatch(setLoading(false));
42 | });
43 | };
44 |
45 | useEffect(() => {
46 | if (token && !auth) {
47 | getAuthUser();
48 | }
49 | }, [token, pathname, auth]);
50 |
51 | return (
52 |
53 | {loading && }
54 | }>
55 |
56 |
60 |
61 |
62 | }
63 | />
64 | } />
65 | } />
66 | } />
67 |
68 |
69 |
70 | );
71 | }
72 |
73 | export default App;
74 |
--------------------------------------------------------------------------------
/frontend/src/assets/AuthImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akashdeep023/Task_Management/9c1a4a94edc6c3a5a5e1cc8edb2cf3e7d448c726/frontend/src/assets/AuthImage.png
--------------------------------------------------------------------------------
/frontend/src/assets/checkbox_select.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akashdeep023/Task_Management/9c1a4a94edc6c3a5a5e1cc8edb2cf3e7d448c726/frontend/src/assets/checkbox_select.png
--------------------------------------------------------------------------------
/frontend/src/assets/checkbox_unselect.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akashdeep023/Task_Management/9c1a4a94edc6c3a5a5e1cc8edb2cf3e7d448c726/frontend/src/assets/checkbox_unselect.png
--------------------------------------------------------------------------------
/frontend/src/components/Loading.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Loading = () => {
4 | return (
5 |
10 | );
11 | };
12 |
13 | export default Loading;
14 |
--------------------------------------------------------------------------------
/frontend/src/components/Model.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from "react";
2 | import "../css/Model.css";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import {
5 | setAddedPeopleM,
6 | setAddPeopleM,
7 | setLogoutM,
8 | setTaskDeleteM,
9 | setTaskCardM,
10 | setTaskM,
11 | setBoardEmail,
12 | setCategoryName,
13 | setUpdateCategoryM,
14 | } from "../redux/slices/stateSlice";
15 | import { AiFillDelete, AiOutlinePlus } from "react-icons/ai";
16 | import { PiCodesandboxLogoDuotone } from "react-icons/pi";
17 | import { Link, useNavigate, useParams } from "react-router-dom";
18 | import { removeAuth } from "../redux/slices/authSlice";
19 | import useAddTask from "../hooks/useAddTask";
20 | import { toast } from "react-toastify";
21 | import useDeleteTask from "../hooks/useDeleteTask";
22 | import useGetTask from "../hooks/useGetTask";
23 | import Loading from "./Loading";
24 | import { getMonthDate, simpleDate } from "../utils/generateDate";
25 | import useUpdateTask from "../hooks/useUpdateTask";
26 | import CheckBoxUnselect from "../assets/checkbox_unselect.png";
27 | import CheckBoxSelect from "../assets/checkbox_select.png";
28 | import useAddToBoard from "../hooks/useAddToBoard";
29 | import { checkValidEmail } from "../utils/validate";
30 | import useUpdateCategory from "../hooks/useUpdateCategory";
31 |
32 | export const AddPeople = () => {
33 | const dispatch = useDispatch();
34 | const [email, setEmail] = useState("");
35 | const [load, setLoad] = useState("");
36 | const handleAddToBoard = (e) => {
37 | const validError = checkValidEmail(email);
38 | if (validError) {
39 | toast.error(validError);
40 | return;
41 | }
42 | useAddToBoard(e, setLoad, dispatch, email);
43 | };
44 | return (
45 |
46 |
47 |
Add people to the board
48 |
setEmail(e.target.value)}
54 | />
55 |
56 | dispatch(setAddPeopleM(false))}
59 | >
60 | Cancel
61 |
62 | handleAddToBoard(e)}
65 | >
66 | {load ? "Loading..." : "Add Email"}
67 |
68 |
69 |
70 |
71 | );
72 | };
73 | export const AddedPeople = () => {
74 | const dispatch = useDispatch();
75 | const email = useSelector((store) => store.state.boardEmail);
76 | return (
77 |
78 |
79 |
{email} added to board
80 |
81 | {
84 | dispatch(setAddedPeopleM(false));
85 | dispatch(setBoardEmail(""));
86 | }}
87 | >
88 | Okey, got it!
89 |
90 |
91 |
92 |
93 | );
94 | };
95 |
96 | export const UpdateCategory = () => {
97 | const dispatch = useDispatch();
98 | const [load, setLoad] = useState("");
99 | const { oldCategory, newCategory } = useSelector(
100 | (store) => store.state.categoryName
101 | );
102 | const taskId = useSelector((store) => store.state.taskMId);
103 | const handleUpdateCategory = (e) => {
104 | useUpdateCategory(
105 | e,
106 | setLoad,
107 | dispatch,
108 | taskId,
109 | newCategory,
110 | oldCategory
111 | );
112 | };
113 | return (
114 |
115 |
116 |
117 | Change the status of task (
118 | {newCategory.charAt(0).toUpperCase() + newCategory.slice(1)}
119 | )
120 |
121 |
122 | handleUpdateCategory(e)}
125 | >
126 | {load ? "Loading..." : "Update status"}
127 |
128 | {
131 | dispatch(setUpdateCategoryM(false));
132 | dispatch(setCategoryName(""));
133 | }}
134 | >
135 | Cancel
136 |
137 |
138 |
139 |
140 | );
141 | };
142 |
143 | export const Logout = () => {
144 | const dispatch = useDispatch();
145 | const navigate = useNavigate();
146 | const handleLogout = () => {
147 | localStorage.removeItem("token");
148 | dispatch(removeAuth());
149 | dispatch(setLogoutM(false));
150 | navigate("/login");
151 | };
152 | return (
153 |
154 |
155 |
156 | Are you sure you want to Logout?
157 |
158 |
159 |
160 | Yes, Logout
161 |
162 | dispatch(setLogoutM(false))}
165 | >
166 | Cancel
167 |
168 |
169 |
170 |
171 | );
172 | };
173 | export const TaskDelete = () => {
174 | const dispatch = useDispatch();
175 | const id = useSelector((store) => store.state.taskMId);
176 | const [load, setLoad] = useState("");
177 | const handleTaskDelete = (e) => {
178 | useDeleteTask(e, setLoad, dispatch, id);
179 | };
180 |
181 | return (
182 |
183 |
184 |
185 | Are you sure you want to Delete?
186 |
187 |
188 | handleTaskDelete(e)}
191 | >
192 | {load ? "Loading..." : "Yes, Delete"}
193 |
194 | dispatch(setTaskDeleteM(false))}
197 | >
198 | Cancel
199 |
200 |
201 |
202 |
203 | );
204 | };
205 | export const TaskCard = () => {
206 | const task = useSelector((store) => store.state.taskM);
207 | const auth = useSelector((store) => store.auth);
208 |
209 | const dispatch = useDispatch();
210 | const [dueDate, setDueDate] = useState(task?.dueDate || "");
211 | const [title, setTitle] = useState(task?.title || "");
212 | const [priority, setPriority] = useState(task?.priority || "");
213 | const [checklist, setChecklist] = useState(task?.checklist || []);
214 | const [listBox, setListBox] = useState(task?.checklist?.length || 0);
215 | const [load, setLoad] = useState("");
216 | const [assignBox, setAssignBox] = useState(false);
217 | const [assign, setAssign] = useState(task?.assign || "");
218 |
219 | const handleAddTask = (e) => {
220 | if (title && priority && checklist.length > 0) {
221 | const listName = checklist.filter((list) => {
222 | return list.name == "";
223 | });
224 | if (listName.length == 0) {
225 | if (task == "") {
226 | useAddTask(
227 | e,
228 | setLoad,
229 | title,
230 | priority,
231 | checklist,
232 | assign,
233 | dueDate,
234 | dispatch
235 | );
236 | } else {
237 | useUpdateTask(
238 | e,
239 | setLoad,
240 | title,
241 | priority,
242 | checklist,
243 | assign,
244 | dueDate,
245 | dispatch,
246 | task._id
247 | );
248 | }
249 | } else {
250 | toast.error("Checklist is required");
251 | }
252 | } else {
253 | toast.error("All fields are required");
254 | }
255 | };
256 |
257 | const handleAddChecklist = () => {
258 | let list = checklist;
259 | list = [...list, { name: "", isDone: false }];
260 | setChecklist(list);
261 | setListBox(list.length);
262 | };
263 | const handleDeleteChecklist = (idx) => {
264 | if (listBox > 0) {
265 | let list = checklist;
266 | list = list.filter((item, i) => i != idx);
267 | setChecklist(list);
268 | setListBox(list.length);
269 | }
270 | };
271 |
272 | const handleTitle = (name) => {
273 | name = name.charAt(0).toUpperCase() + name.slice(1);
274 | setTitle(name);
275 | };
276 | return (
277 |
559 | );
560 | };
561 | export const TaskCardPublic = () => {
562 | const { id } = useParams();
563 | const [task, setTask] = useState([]);
564 | useGetTask(id, setTask);
565 |
566 | return task?.length == 0 ? (
567 |
568 | ) : (
569 |
570 |
571 |
572 |
Pro Manage
573 |
574 |
575 | {task == null ? (
576 |
577 |
Task Not Found
578 |
579 | The task you are looking for does not exist or has been
580 | deleted. Please check the URL and try again.
581 |
582 |
583 | Back to Home
584 |
585 |
586 | ) : (
587 |
588 |
589 |
590 |
594 | {task?.priority}
595 |
596 |
{task?.title}
597 |
598 | Checklist (
599 | {
600 | task?.checklist?.filter(
601 | (list) => list.isDone == true
602 | ).length
603 | }
604 | /{task?.checklist?.length})
605 |
606 |
607 | {task?.checklist?.map((list, idx) => {
608 | return (
609 |
613 |
614 | {!list.isDone ? (
615 |
619 | ) : (
620 |
624 | )}
625 |
626 |
627 | {list.name}
628 |
629 |
630 | );
631 | })}
632 |
633 |
634 |
635 | {task?.dueDate && (
636 | <>
637 |
Due Date
638 |
{getMonthDate(task?.dueDate)}
639 | >
640 | )}
641 |
642 |
643 | )}
644 |
645 | );
646 | };
647 |
--------------------------------------------------------------------------------
/frontend/src/components/PopUp.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "../css/PopUp.css";
3 | import { useDispatch } from "react-redux";
4 | import {
5 | setTaskCardM,
6 | setTaskDeleteM,
7 | setTaskFilterName,
8 | setTaskFilterP,
9 | setTaskM,
10 | } from "../redux/slices/stateSlice";
11 | import { toast } from "react-toastify";
12 | import useAllTaskFilter from "../hooks/useAllTaskFilter";
13 |
14 | export const TaskMenu = ({ setTaskMenuP, id, task }) => {
15 | const dispatch = useDispatch();
16 |
17 | const handleTaskEdit = () => {
18 | dispatch(setTaskM(task));
19 | dispatch(setTaskCardM(true));
20 | setTaskMenuP(false);
21 | };
22 |
23 | const handleTaskDelete = () => {
24 | dispatch(setTaskDeleteM(true));
25 | setTaskMenuP(false);
26 | };
27 | const handleSharelink = (id) => {
28 | if (!navigator.clipboard) {
29 | toast.error(
30 | "Your browser does not support copying text to the clipboard."
31 | );
32 | return;
33 | }
34 | navigator.clipboard.writeText(
35 | `${import.meta.env.VITE_FRONTEND_URL}/task/${id}`
36 | );
37 |
38 | setTaskMenuP(false);
39 | toast.success("Link Copied");
40 | };
41 | return (
42 |
43 | Edit
44 | handleSharelink(id)}>Share
45 |
46 | Delete
47 |
48 |
49 | );
50 | };
51 | export const TaskFilter = () => {
52 | const dispatch = useDispatch();
53 | const handleFilter = (days) => {
54 | dispatch(setTaskFilterP(false));
55 | useAllTaskFilter(dispatch, days);
56 | };
57 |
58 | return (
59 |
60 | {
62 | handleFilter(1);
63 | dispatch(setTaskFilterName("Today"));
64 | }}
65 | >
66 | Today
67 |
68 | {
70 | handleFilter(7);
71 | dispatch(setTaskFilterName("This week"));
72 | }}
73 | >
74 | This Week
75 |
76 | {
78 | handleFilter(30);
79 | dispatch(setTaskFilterName("This month"));
80 | }}
81 | >
82 | This Month
83 |
84 |
85 | );
86 | };
87 |
--------------------------------------------------------------------------------
/frontend/src/components/PrivateRoute.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import { Navigate } from "react-router-dom";
3 |
4 | const PrivateRoute = ({ children }) => {
5 | const auth = useSelector((store) => store.auth);
6 |
7 | if (!auth) {
8 | return ;
9 | }
10 |
11 | return children;
12 | };
13 |
14 | export default PrivateRoute;
15 |
--------------------------------------------------------------------------------
/frontend/src/components/TaskBox.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { IoIosArrowDown, IoIosArrowUp } from "react-icons/io";
3 | import { TbDots } from "react-icons/tb";
4 | import { TaskMenu } from "./PopUp";
5 | import { getMonthDate } from "../utils/generateDate";
6 | import { useDispatch } from "react-redux";
7 | import {
8 | setCategoryName,
9 | setTaskMId,
10 | setUpdateCategoryM,
11 | } from "../redux/slices/stateSlice";
12 | import CheckBoxUnselect from "../assets/checkbox_unselect.png";
13 | import CheckBoxSelect from "../assets/checkbox_select.png";
14 |
15 | const TaskBox = ({
16 | backlogCollapse,
17 | todoCollapse,
18 | progressCollapse,
19 | doneCollapse,
20 | task,
21 | }) => {
22 | const [collapse, setCollapse] = useState(true);
23 | const [taskMenuP, setTaskMenuP] = useState(false);
24 | const dispatch = useDispatch();
25 | const handleClickOutside = (event) => {
26 | if (taskMenuP && !event?.target.closest(".popup-box")) {
27 | setTaskMenuP(false);
28 | }
29 | };
30 |
31 | const handleUpdateCategory = (category) => {
32 | dispatch(setUpdateCategoryM(true));
33 | dispatch(
34 | setCategoryName({
35 | newCategory: category,
36 | oldCategory: task?.category,
37 | })
38 | );
39 | dispatch(setTaskMId(task?._id));
40 | };
41 |
42 | useEffect(() => {
43 | document.addEventListener("mousedown", handleClickOutside);
44 | return () => {
45 | document.removeEventListener("mousedown", handleClickOutside);
46 | };
47 | }, [taskMenuP]);
48 |
49 | useEffect(() => {
50 | setCollapse(true);
51 | }, [backlogCollapse, todoCollapse, progressCollapse, doneCollapse]);
52 | return (
53 |
54 |
55 |
56 |
61 |
{task?.priority}
62 |
63 | {task?.userName?.name?.split(" ")[0]?.slice(0, 1) +
64 | (task?.userName?.name?.split(" ")[1] == undefined
65 | ? ""
66 | : task?.userName?.name
67 | ?.split(" ")[1]
68 | ?.slice(0, 1)
69 | .toUpperCase())}
70 |
71 |
72 |
73 | {
77 | setTaskMenuP(true);
78 | dispatch(setTaskMId(task?._id));
79 | }}
80 | />
81 | {taskMenuP && (
82 |
87 | )}
88 |
89 |
90 |
{task?.title}
91 |
92 |
93 | Checklist (
94 | {
95 | task?.checklist.filter((list) => list.isDone == true)
96 | .length
97 | }
98 | /{task?.checklist.length})
99 |
100 |
setCollapse(!collapse)}>
101 | {collapse ? : }
102 |
103 |
104 |
109 | {task?.checklist.map((list, idx) => {
110 | return (
111 |
116 | {!list.isDone ? (
117 |
118 | ) : (
119 |
120 | )}
121 | {list.name}
122 |
123 | );
124 | })}
125 |
126 |
127 | {task?.dueDate ? (
128 |
133 | {getMonthDate(task?.dueDate)}
134 |
135 | ) : (
136 |
137 | )}
138 |
139 | {task?.category != "backlog" && (
140 |
handleUpdateCategory("backlog")}
143 | >
144 | BACKLOG
145 |
146 | )}
147 | {task?.category != "to-do" && (
148 |
handleUpdateCategory("to-do")}
151 | >
152 | TO-DO
153 |
154 | )}
155 | {task?.category != "in-progress" && (
156 |
handleUpdateCategory("in-progress")}
159 | >
160 | PROGRESS
161 |
162 | )}
163 | {task?.category != "done" && (
164 |
handleUpdateCategory("done")}
167 | >
168 | DONE
169 |
170 | )}
171 |
172 |
173 |
174 | );
175 | };
176 |
177 | export default TaskBox;
178 |
--------------------------------------------------------------------------------
/frontend/src/css/Analytics.css:
--------------------------------------------------------------------------------
1 | .analytics-container {
2 | display: flex;
3 | justify-content: start;
4 | align-items: center;
5 | gap: 16px;
6 | }
7 |
8 | .analytics-box {
9 | background: #f9fcff;
10 | padding: 16px;
11 | border-radius: 12px;
12 | width: 40%;
13 | min-width: 300px;
14 | }
15 |
16 | .analytics-circle {
17 | height: 14px;
18 | width: 14px;
19 | border-radius: 50%;
20 | background: #90c4cc;
21 | }
22 |
23 | .analytics-task {
24 | display: flex;
25 | justify-content: space-between;
26 | align-items: center;
27 | padding: 12px;
28 | }
29 | .analytics-task > div {
30 | display: flex;
31 | justify-content: start;
32 | align-items: center;
33 | gap: 10px;
34 | }
35 |
--------------------------------------------------------------------------------
/frontend/src/css/Dashboard.css:
--------------------------------------------------------------------------------
1 | .dashboard-container {
2 | display: flex;
3 | flex-direction: column;
4 | overflow-x: auto;
5 | height: 100vh;
6 | padding-left: 16px;
7 | gap: 16px;
8 | margin-left: 200px;
9 | width: calc(100% - 200px);
10 | min-width: 725px;
11 | }
12 |
13 | .dashboard-header {
14 | padding-right: 16px;
15 | }
16 |
17 | .dashboard-header div h2,
18 | .dashboard-header div h3,
19 | .dashboard-header div h4 {
20 | font-weight: 600 !important;
21 | }
22 |
23 | .dashboard-header > .header-user {
24 | margin: 20px 0px;
25 | }
26 |
27 | .dashboard-header div h4 {
28 | opacity: 0.7;
29 | }
30 |
31 | .dashboard-header > div {
32 | display: flex;
33 | justify-content: space-between;
34 | align-items: center;
35 | }
36 |
37 | .header-board > div {
38 | display: flex;
39 | justify-content: start;
40 | align-items: center;
41 | gap: 10px;
42 | }
43 | .add-people {
44 | cursor: pointer;
45 | margin-left: 10px;
46 | font-size: 14px;
47 | display: flex;
48 | align-items: center;
49 | justify-content: start;
50 | gap: 4px;
51 | opacity: 0.7;
52 | }
53 |
54 | .dashboard-header .header-time span {
55 | display: flex;
56 | align-items: center;
57 | justify-content: center;
58 | gap: 4px;
59 | font-size: 15px;
60 | cursor: pointer;
61 | }
62 |
63 | .dashboard-column {
64 | display: flex;
65 | overflow-x: auto;
66 | height: 100vh;
67 | padding-bottom: 16px;
68 | width: 100%;
69 | }
70 |
71 | .column {
72 | min-width: 350px;
73 | background-color: #f5f5f5;
74 | border-radius: 8px;
75 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
76 | overflow-y: hidden;
77 | height: 100%;
78 | background: #eef2f5;
79 | margin-right: 16px;
80 | }
81 |
82 | .column > h4 {
83 | padding: 16px;
84 | height: fit-content;
85 | font-weight: 500;
86 | width: 100%;
87 | display: flex;
88 | justify-content: space-between;
89 | align-items: center;
90 | }
91 | .column > h4 > span {
92 | display: flex;
93 | justify-content: space-between;
94 | align-items: center;
95 | gap: 10px;
96 | }
97 |
98 | /* Scrollbar Style */
99 | .dashboard-column::-webkit-scrollbar {
100 | height: 8px;
101 | }
102 |
103 | .dashboard-column::-webkit-scrollbar-thumb {
104 | background: #8daab9cc;
105 | cursor: grab;
106 | border-radius: 4px;
107 | }
108 |
109 | .dashboard-column::-webkit-scrollbar-thumb:active {
110 | cursor: grabbing;
111 | }
112 |
113 | .dashboard-column::-webkit-scrollbar-track {
114 | background: #d6eaf4;
115 | }
116 |
117 | .column > div::-webkit-scrollbar {
118 | width: 8px;
119 | }
120 |
121 | .column > div::-webkit-scrollbar-thumb {
122 | background: #8daab9cc;
123 | cursor: grab;
124 | border-radius: 4px;
125 | }
126 |
127 | .column > div::-webkit-scrollbar-thumb:active {
128 | cursor: grabbing;
129 | }
130 |
131 | .column > div::-webkit-scrollbar-track {
132 | background: #d6eaf4;
133 | }
134 |
--------------------------------------------------------------------------------
/frontend/src/css/Model.css:
--------------------------------------------------------------------------------
1 | .model-container {
2 | position: absolute;
3 | top: 0px;
4 | left: 0px;
5 | height: 100vh;
6 | width: 100vw;
7 | z-index: 110;
8 | display: flex;
9 | justify-content: center;
10 | align-items: center;
11 | background: #303d438c;
12 | }
13 |
14 | .model-box {
15 | background: #fff;
16 | padding: 30px 20px;
17 | border-radius: 12px;
18 | min-width: 400px;
19 | width: 35%;
20 | min-height: 250px;
21 | display: flex;
22 | flex-direction: column;
23 | align-items: start;
24 | justify-content: center;
25 | gap: 12px;
26 | border: 1px solid #efefef;
27 | box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.07);
28 | }
29 |
30 | .model-card {
31 | height: 70%;
32 | justify-content: space-between;
33 | font-size: 16px;
34 | }
35 | .assign-unselect-color {
36 | color: gray;
37 | }
38 | .model-task-null {
39 | align-items: center;
40 | }
41 | .model-task-null p {
42 | text-align: center;
43 | opacity: 0.9;
44 | font-size: 14px;
45 | }
46 | .model-card-details {
47 | height: 100%;
48 | width: 100%;
49 | }
50 | .model-assign {
51 | display: flex;
52 | align-items: center;
53 | }
54 | .model-assign span {
55 | font-size: 14px;
56 | margin-right: 10px;
57 | font-weight: 500;
58 | text-wrap: nowrap;
59 | }
60 | .model-box span {
61 | font-weight: 500;
62 | }
63 |
64 | .model-box-s {
65 | width: fit-content;
66 | }
67 | .model-box h3 {
68 | font-weight: 500;
69 | }
70 |
71 | .model-center {
72 | text-align: center;
73 | width: 100%;
74 | margin: 16px 0px;
75 | }
76 |
77 | .checklist-box {
78 | width: 100%;
79 | max-height: 26vh;
80 | overflow-y: auto;
81 | padding-right: 5px;
82 | }
83 |
84 | .checklist-box::-webkit-scrollbar,
85 | .assign-selection-box::-webkit-scrollbar {
86 | width: 8px;
87 | }
88 |
89 | .checklist-box::-webkit-scrollbar-thumb,
90 | .assign-selection-box::-webkit-scrollbar-thumb {
91 | background: #8daab9cc;
92 | cursor: grab;
93 | border-radius: 4px;
94 | }
95 |
96 | .checklist-box::-webkit-scrollbar-thumb:active,
97 | .assign-selection-box::-webkit-scrollbar-thumb:active {
98 | cursor: grabbing;
99 | }
100 |
101 | .checklist-box::-webkit-scrollbar-track,
102 | .assign-selection-box::-webkit-scrollbar-track {
103 | background: #d6eaf4;
104 | }
105 |
106 | .assign-model-box {
107 | width: 100%;
108 | position: relative;
109 | }
110 | .model-input {
111 | width: 100%;
112 | padding: 8px 10px;
113 | border: 1px solid #ccc;
114 | border-radius: 5px;
115 | outline: none;
116 | font-size: 16px;
117 | margin: 6px 0px;
118 | }
119 | .model-assign-input {
120 | cursor: pointer;
121 | }
122 | .assign-selection-box {
123 | position: absolute;
124 | width: 100%;
125 | background-color: #fff;
126 | top: 100%;
127 | left: 0px;
128 | border-radius: 5px;
129 | z-index: 99;
130 | display: flex;
131 | gap: 20px;
132 | flex-direction: column;
133 | height: 0px;
134 | overflow-y: scroll;
135 | transition: all 0.1s ease;
136 | padding: 0px 10px;
137 | }
138 | .assign-selection-box-exist {
139 | height: fit-content;
140 | max-height: 30vh;
141 | padding: 8px 10px;
142 | border: 1px solid #ccc;
143 | }
144 |
145 | .assign-selection-box > div {
146 | display: flex;
147 | justify-content: space-between;
148 | align-items: center;
149 | width: 100%;
150 | }
151 | .assign-selection-box > div > div {
152 | display: flex;
153 | align-items: center;
154 | width: calc(100% - 140px);
155 | overflow: hidden;
156 | }
157 | .assign-selection-box button {
158 | width: fit-content;
159 | padding: 4px 40px;
160 | border-radius: 10px;
161 | cursor: pointer;
162 | font-weight: 500;
163 | border: 1px solid #ccc;
164 | color: gray;
165 | }
166 | .assign-selection-box span {
167 | font-weight: 500;
168 | }
169 | .assign-circel {
170 | display: flex;
171 | justify-content: center;
172 | align-items: center;
173 | min-height: 40px;
174 | min-width: 40px;
175 | border-radius: 50%;
176 | background-color: #ffebeb;
177 | margin-right: 5px;
178 | }
179 |
180 | .checklist-input-box {
181 | position: relative;
182 | }
183 | .model-checkbox {
184 | cursor: pointer;
185 | }
186 | .model-input-btn {
187 | padding: 8px 35px;
188 | }
189 | .checklist-btn {
190 | display: flex;
191 | justify-content: center;
192 | align-items: center;
193 | position: absolute;
194 | top: 50%;
195 | transform: translateY(-50%);
196 | /* cursor: pointer; */
197 | }
198 | .checklist-btn-l {
199 | left: 8px;
200 | }
201 | .checklist-btn-r {
202 | right: 8px;
203 | color: red;
204 | }
205 | .model-btns {
206 | display: flex;
207 | gap: 12px;
208 | justify-content: space-between;
209 | width: 100%;
210 | }
211 | .model-box-s .model-btns {
212 | flex-direction: column;
213 | }
214 |
215 | .model-btns button {
216 | width: 100%;
217 | padding: 8px 10px;
218 | border-radius: 10px;
219 | cursor: pointer;
220 | font-weight: 500;
221 | }
222 |
223 | .model-cancel {
224 | border: 1px solid red;
225 | color: red;
226 | }
227 |
228 | .model-submit {
229 | background: #17a2b8;
230 | color: white;
231 | border: 1px solid #17a2b8;
232 | }
233 |
234 | .model-due-date {
235 | margin-right: 20px;
236 | border: 1px solid #ccc;
237 | text-wrap: nowrap;
238 | }
239 |
240 | .model-btns label {
241 | cursor: pointer;
242 | width: 100%;
243 | }
244 |
245 | #model-card-date {
246 | height: 0px;
247 | width: 0px;
248 | opacity: 0;
249 | }
250 | .require {
251 | color: red;
252 | font-size: 14px !important;
253 | margin-bottom: 5px;
254 | }
255 | .priority-box {
256 | display: flex;
257 | justify-content: start;
258 | align-items: center;
259 | gap: 10px;
260 | margin: 5px 0;
261 | font-size: 14px;
262 | margin: 12px 0;
263 | }
264 | .priority-box span {
265 | text-wrap: nowrap;
266 | font-size: 14px;
267 | }
268 | .priority-box label span {
269 | text-wrap: nowrap;
270 | font-size: 12px;
271 | text-transform: uppercase;
272 | }
273 | .priority-box > div {
274 | width: 100%;
275 | display: flex;
276 | align-items: center;
277 | justify-content: start;
278 | gap: 5px;
279 | }
280 | .priority-box > div label {
281 | display: flex;
282 | align-items: center;
283 | justify-content: center;
284 | gap: 5px;
285 | width: fit-content;
286 | border: 1px solid #ccc;
287 | padding: 2px 7px;
288 | border-radius: 8px;
289 | cursor: pointer;
290 | text-wrap: nowrap;
291 | background: #fff;
292 | }
293 |
294 | .priority-box input[type="radio"] {
295 | display: none;
296 | }
297 |
298 | .priority-circel {
299 | min-width: 7px;
300 | min-height: 7px;
301 | border-radius: 50%;
302 | }
303 |
304 | .selected-priority {
305 | background: #dad8d8 !important;
306 | }
307 | .checklist-head {
308 | font-size: 14px;
309 | }
310 | .checklist-add {
311 | display: flex;
312 | align-items: center;
313 | justify-content: start;
314 | gap: 5px;
315 | opacity: 0.8;
316 | cursor: pointer;
317 | margin: 16px 0;
318 | width: fit-content;
319 | }
320 |
321 | /* Model public view */
322 | .nav-logo-public {
323 | position: absolute;
324 | top: 30px;
325 | left: 20px;
326 | width: fit-content;
327 | text-decoration: none;
328 | }
329 | .model-public {
330 | background-color: #fff;
331 | }
332 | .model-due {
333 | display: flex;
334 | justify-content: start;
335 | align-items: center;
336 | gap: 16px;
337 | font-size: 14px;
338 | font-weight: 500;
339 | }
340 | .model-due > div {
341 | padding: 8px 10px;
342 | border-radius: 10px;
343 | background: #cf3636;
344 | color: white;
345 | }
346 | .priority-public span {
347 | font-size: 10px;
348 | text-transform: uppercase;
349 | }
350 | .checklist-public {
351 | margin: 10px 0 6px 0;
352 | }
353 |
354 | .checklist-box-public {
355 | max-height: 40vh;
356 | }
357 |
358 | /* Responsive */
359 | @media (max-width: 768px) {
360 | .model-box {
361 | min-width: 90%;
362 | }
363 | }
364 |
--------------------------------------------------------------------------------
/frontend/src/css/Navbar.css:
--------------------------------------------------------------------------------
1 | .navbar {
2 | width: 200px;
3 | height: 100vh;
4 | background-color: white;
5 | color: black;
6 | position: fixed;
7 | display: flex;
8 | flex-direction: column;
9 | align-items: center;
10 | padding: 25px 0;
11 | border-right: 1px solid #8daab952;
12 | z-index: 100;
13 | top: 0;
14 | left: 0;
15 | }
16 |
17 | .nav-logo {
18 | font-size: 16px;
19 | font-weight: 700;
20 | margin-bottom: 30px;
21 | color: black;
22 | letter-spacing: 0.1px;
23 | display: flex;
24 | align-items: center;
25 | justify-content: start;
26 | gap: 15px;
27 | width: 100%;
28 | padding-left: 20px;
29 | text-decoration: none;
30 | }
31 |
32 | .nav-links {
33 | display: flex;
34 | flex-direction: column;
35 | gap: 15px;
36 | width: 100%;
37 | height: 100%;
38 | justify-content: space-between;
39 | }
40 | .nav-links > div {
41 | display: flex;
42 | flex-direction: column;
43 | gap: 10px;
44 | justify-content: space-between;
45 | }
46 | .nav-logout {
47 | padding: 10px;
48 | padding-left: 20px;
49 | margin-bottom: 20px;
50 | }
51 | .nav-log {
52 | text-align: center;
53 | text-decoration: none;
54 | color: black;
55 | border-radius: 2px;
56 | font-weight: 500;
57 | font-size: 16px;
58 | display: flex;
59 | align-items: center;
60 | justify-content: start;
61 | gap: 15px;
62 | width: fit-content;
63 | color: red;
64 | cursor: pointer;
65 | }
66 | .nav-log:hover {
67 | color: rgb(123, 0, 0);
68 | }
69 |
70 | .nav-link {
71 | padding: 10px;
72 | text-align: center;
73 | text-decoration: none;
74 | color: black;
75 | border-radius: 2px;
76 | font-weight: 500;
77 | font-size: 16px;
78 | display: flex;
79 | align-items: center;
80 | justify-content: start;
81 | gap: 15px;
82 | width: 100%;
83 | padding-left: 20px;
84 | cursor: pointer;
85 | }
86 |
87 | .nav-active {
88 | background: #4392ed29;
89 | }
90 |
91 | .nav-link:hover {
92 | background: #4391ed1a;
93 | }
94 |
95 | .nav-link.active {
96 | background: #4391ed1a;
97 | }
98 |
--------------------------------------------------------------------------------
/frontend/src/css/Page_Not_Found.css:
--------------------------------------------------------------------------------
1 | .page_not_found {
2 | height: 100%;
3 | width: 100%;
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | background: #317f8b;
9 | color: #fff;
10 | gap: 10px;
11 | }
12 |
13 | .page_not_found_link {
14 | color: #fff;
15 | text-decoration: none;
16 | transition: color 0.3s ease;
17 | &:hover {
18 | color: #90b4db;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/frontend/src/css/PopUp.css:
--------------------------------------------------------------------------------
1 | .popup-box {
2 | height: fit-content;
3 | width: 120px;
4 | background: #fff;
5 | padding: 14px 20px;
6 | position: absolute;
7 | top: 90%;
8 | right: 0px;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | align-items: start;
13 | gap: 7px;
14 | z-index: 90;
15 | border-radius: 12px;
16 | font-weight: 500;
17 | box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.218);
18 | }
19 | .pupup-filter {
20 | top: 100%;
21 | }
22 |
23 | .popup-box button {
24 | background: transparent;
25 | color: black;
26 | border: none;
27 | cursor: pointer;
28 | }
29 | .popup-delete {
30 | color: red !important;
31 | }
32 |
--------------------------------------------------------------------------------
/frontend/src/css/Register_Login.css:
--------------------------------------------------------------------------------
1 | .register_login_box {
2 | height: 100%;
3 | width: 100%;
4 | display: flex;
5 | }
6 | .img_box {
7 | height: 100%;
8 | width: 60%;
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | justify-content: center;
13 | background: #17a2b8;
14 | color: #fff;
15 | letter-spacing: 0.7px;
16 | }
17 | .authimage_box {
18 | position: relative;
19 | height: 20vw;
20 | width: 20vw;
21 | margin-bottom: 5%;
22 | }
23 | .authimage_back {
24 | border-radius: 50%;
25 | background: #317f8b;
26 | width: 100%;
27 | height: 100%;
28 | position: absolute;
29 | top: 0;
30 | }
31 | .authimage {
32 | top: 10%;
33 | position: absolute;
34 | height: 100%;
35 | width: 100%;
36 | }
37 | .img_box h2,
38 | .form_box h2 {
39 | font-weight: 500;
40 | margin-bottom: 10px;
41 | }
42 | .img_box span {
43 | font-size: 0.9em;
44 | font-weight: 100;
45 | opacity: 0.9;
46 | }
47 |
48 | .form_box {
49 | height: 100%;
50 | width: 40%;
51 | display: flex;
52 | flex-direction: column;
53 | align-items: center;
54 | justify-content: center;
55 | gap: 15px;
56 | }
57 | .form_box_s {
58 | height: 100%;
59 | width: 40%;
60 | display: flex;
61 | flex-direction: column;
62 | align-items: start;
63 | justify-content: center;
64 | gap: 15px;
65 | }
66 |
67 | .form_box input[type="text"],
68 | .form_box input[type="password"],
69 | .form_box input[type="email"] {
70 | width: 100%;
71 | padding: 10px 35px;
72 | border: 1px solid #ccc;
73 | border-radius: 5px;
74 | outline: none;
75 | font-size: 16px;
76 | }
77 |
78 | .form_box .input_p_box {
79 | width: 80%;
80 | position: relative;
81 | display: flex;
82 | align-items: center;
83 | justify-content: center;
84 | }
85 | .form_box .hide_show_btn {
86 | position: absolute;
87 | top: 50%;
88 | right: 10px;
89 | transform: translateY(-50%);
90 | font-size: 14px;
91 | cursor: pointer;
92 | }
93 | .form_box .hide_show_btn_s {
94 | position: absolute;
95 | top: 50%;
96 | left: 8px;
97 | transform: translateY(-50%);
98 | font-size: 14px;
99 | cursor: pointer;
100 | }
101 |
102 | .form_box .submit_button {
103 | padding: 10px 20px;
104 | width: 80%;
105 | background: #317f8b;
106 | border: 1px solid #317f8b !important;
107 | color: #fff;
108 | border: none;
109 | border-radius: 50px;
110 | cursor: pointer;
111 | font-size: smaller;
112 | margin: 15px 0px;
113 | }
114 | .form_box .update_button {
115 | padding: 12px 20px;
116 | width: 80%;
117 | background: #17a2b8;
118 | border: 1px solid #17a2b8 !important;
119 | color: #fff;
120 | border: none;
121 | border-radius: 50px;
122 | cursor: pointer;
123 | font-size: smaller;
124 | margin: 15px 0px;
125 | }
126 | .form_box .submit_button:active,
127 | .form_box .update_button:active {
128 | background: #005f6e;
129 | border: 1px solid #005f6e !important;
130 | }
131 | .form_box .auth_change {
132 | opacity: 0.8;
133 | font-size: smaller;
134 | }
135 | .form_box .button_change {
136 | font-size: smaller;
137 | width: 80%;
138 | padding: 10px 20px;
139 | border: 1px solid #317f8b;
140 | background: transparent;
141 | color: #317f8b;
142 | cursor: pointer;
143 | border-radius: 50px;
144 | }
145 | .form_box .button_change:active {
146 | border: 1px solid #005f6e;
147 | background: #005f6e;
148 | color: white;
149 | }
150 |
151 | .form_box button:disabled {
152 | cursor: not-allowed;
153 | }
154 |
--------------------------------------------------------------------------------
/frontend/src/css/Task.css:
--------------------------------------------------------------------------------
1 | .task-container {
2 | display: flex;
3 | flex-direction: column;
4 | gap: 16px;
5 | overflow-y: auto;
6 | padding: 0px 12px;
7 | height: 95%;
8 | padding-bottom: 50px;
9 | }
10 |
11 | .task-box {
12 | background-color: white;
13 | width: 100%;
14 | padding: 16px;
15 | border-radius: 20px;
16 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
17 | }
18 |
19 | .relative {
20 | position: relative;
21 | }
22 | .priority-menu {
23 | display: flex;
24 | width: 100%;
25 | justify-content: space-between;
26 | align-items: center;
27 | margin-bottom: 10px;
28 | }
29 |
30 | .priority-menu p {
31 | text-transform: uppercase;
32 | }
33 | .priority-menu > div {
34 | display: flex;
35 | justify-content: start;
36 | align-items: center;
37 | font-size: 9px;
38 | gap: 5px;
39 | }
40 | .priority-menu .circle-box {
41 | height: 10px;
42 | width: 10px;
43 | border-radius: 50%;
44 | }
45 | .high-box {
46 | background-color: #ff2473;
47 | }
48 | .moderate-box {
49 | background-color: #18b0ff;
50 | }
51 | .low-box {
52 | background-color: #63c05b;
53 | }
54 | .priority-menu .circle-name-box {
55 | display: flex;
56 | align-items: center;
57 | justify-content: center;
58 | height: 20px;
59 | width: 20px;
60 | background-color: rgba(255, 202, 202, 0.408);
61 | border-radius: 50%;
62 | }
63 |
64 | .task-box h3 {
65 | margin: 0;
66 | width: fit-content;
67 | font-size: 18px;
68 | font-weight: 500;
69 | -webkit-line-clamp: 2;
70 | -webkit-box-orient: vertical;
71 | display: -webkit-box;
72 | overflow: hidden;
73 | }
74 |
75 | .task-box > p {
76 | margin: 8px 0 0;
77 | color: #666;
78 | }
79 |
80 | .task-checklist {
81 | display: flex;
82 | align-items: center;
83 | justify-content: space-between;
84 | margin-top: 14px;
85 | font-size: 14px;
86 | }
87 |
88 | .task-checklist > div {
89 | padding: 5px;
90 | border-radius: 3px;
91 | background: #eeecec;
92 | height: 22px;
93 | width: 22px;
94 | display: flex;
95 | align-items: center;
96 | justify-content: center;
97 | cursor: pointer;
98 | opacity: 0.7;
99 | }
100 |
101 | .task-checklist-details {
102 | display: flex;
103 | flex-direction: column;
104 | gap: 8px;
105 | font-size: 14px;
106 | margin-top: 15px;
107 | height: fit-content;
108 | transition: all 0.1s ease;
109 | }
110 | .task-checklist-details-collapse {
111 | height: 0px;
112 | overflow: hidden;
113 | margin: 0px;
114 | }
115 |
116 | .checklist-details-box {
117 | display: flex;
118 | align-items: center;
119 | justify-content: start;
120 | gap: 10px;
121 | width: 100%;
122 | border: 1px solid #66666638;
123 | border-radius: 8px;
124 | padding: 6px 8px;
125 | }
126 |
127 | .task-btns {
128 | display: flex;
129 | justify-content: space-between;
130 | align-items: center;
131 | font-size: 8px;
132 | color: #767575;
133 | font-weight: 500;
134 | margin-top: 15px;
135 | }
136 |
137 | .task-btn {
138 | background: #eeecec;
139 | border-radius: 8px;
140 | padding: 6px 8px;
141 | cursor: pointer;
142 | }
143 |
144 | .task-btns > div {
145 | display: flex;
146 | gap: 5px;
147 | }
148 |
149 | .task-btn-red {
150 | background: #cf3636;
151 | color: white;
152 | cursor: default;
153 | }
154 | .task-btn-green {
155 | background: #63c05b;
156 | color: white;
157 | cursor: default;
158 | }
159 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useAddTask.js:
--------------------------------------------------------------------------------
1 | import getHeader from "../utils/header";
2 | import { toast } from "react-toastify";
3 | import { setTaskCardM, setTaskM } from "../redux/slices/stateSlice";
4 | import { addTodoTask } from "../redux/slices/taskSlice";
5 | const useAddTask = (
6 | e,
7 | setLoad,
8 | title,
9 | priority,
10 | checklist,
11 | assign,
12 | dueDate,
13 | dispatch
14 | ) => {
15 | setLoad("Loading...");
16 | e.target.disabled = true;
17 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/task/add`, {
18 | method: "POST",
19 | headers: getHeader(),
20 | body: JSON.stringify({
21 | title: title,
22 | priority: priority,
23 | checklist: checklist,
24 | assign: assign,
25 | dueDate: dueDate,
26 | }),
27 | })
28 | .then((response) => response.json())
29 | .then((json) => {
30 | setLoad("");
31 | e.target.disabled = false;
32 | if (json?.message === "success") {
33 | toast.success("Task Added Successfully");
34 | dispatch(addTodoTask(json.data));
35 | dispatch(setTaskCardM(false));
36 | dispatch(setTaskM(""));
37 | } else {
38 | toast.error(json?.message);
39 | }
40 | })
41 | .catch((error) => {
42 | console.error("Error:", error);
43 | setLoad("");
44 | toast.error("Something went wrong");
45 | e.target.disabled = false;
46 | });
47 | };
48 |
49 | export default useAddTask;
50 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useAddToBoard.js:
--------------------------------------------------------------------------------
1 | import getHeader from "../utils/header";
2 | import { toast } from "react-toastify";
3 | import {
4 | setAddedPeopleM,
5 | setAddPeopleM,
6 | setBoardEmail,
7 | } from "../redux/slices/stateSlice";
8 | import { addAuth } from "../redux/slices/authSlice";
9 | const useAddToBoard = (e, setLoad, dispatch, email) => {
10 | setLoad("Loading...");
11 | e.target.disabled = true;
12 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/user/board`, {
13 | method: "POST",
14 | headers: getHeader(),
15 | body: JSON.stringify({
16 | email: email,
17 | }),
18 | })
19 | .then((response) => response.json())
20 | .then((json) => {
21 | setLoad("");
22 | e.target.disabled = false;
23 | if (json?.message === "success") {
24 | toast.success("Added Email to the Board");
25 | dispatch(addAuth(json.data));
26 | dispatch(setAddPeopleM(false));
27 | dispatch(setAddedPeopleM(true));
28 | dispatch(setBoardEmail(json.email));
29 | } else {
30 | toast.error(json?.message);
31 | }
32 | })
33 | .catch((error) => {
34 | console.error("Error:", error);
35 | setLoad("");
36 | toast.error("Something went wrong");
37 | e.target.disabled = false;
38 | });
39 | };
40 |
41 | export default useAddToBoard;
42 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useAllTask.js:
--------------------------------------------------------------------------------
1 | import { useDispatch } from "react-redux";
2 | import getHeader from "../utils/header";
3 | import { useEffect } from "react";
4 | import {
5 | setBacklog,
6 | setTodo,
7 | setInProgress,
8 | setDone,
9 | } from "../redux/slices/taskSlice";
10 | import { setLoading } from "../redux/slices/stateSlice";
11 | import { toast } from "react-toastify";
12 |
13 | const useAllTask = () => {
14 | const dispatch = useDispatch();
15 | const fetchData = () => {
16 | dispatch(setLoading(true));
17 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/task/all`, {
18 | method: "GET",
19 | headers: getHeader(),
20 | })
21 | .then((response) => response.json())
22 | .then((json) => {
23 | if (json?.message === "success") {
24 | dispatch(setBacklog(json.data.backlog));
25 | dispatch(setTodo(json.data.todo));
26 | dispatch(setInProgress(json.data.inProgress));
27 | dispatch(setDone(json.data.done));
28 | }
29 | dispatch(setLoading(false));
30 | })
31 | .catch((error) => {
32 | console.error("Error:", error);
33 | toast.error("Something went wrong");
34 | dispatch(setLoading(false));
35 | });
36 | };
37 |
38 | useEffect(() => {
39 | fetchData();
40 | }, []);
41 | };
42 |
43 | export default useAllTask;
44 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useAllTaskFilter.js:
--------------------------------------------------------------------------------
1 | import getHeader from "../utils/header";
2 | import {
3 | setBacklog,
4 | setTodo,
5 | setInProgress,
6 | setDone,
7 | } from "../redux/slices/taskSlice";
8 | import { setLoading } from "../redux/slices/stateSlice";
9 | import { toast } from "react-toastify";
10 |
11 | const useAllTaskFilter = (dispatch, days) => {
12 | dispatch(setLoading(true));
13 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/task/all?days=${days}`, {
14 | method: "GET",
15 | headers: getHeader(),
16 | })
17 | .then((response) => response.json())
18 | .then((json) => {
19 | if (json?.message === "success") {
20 | dispatch(setBacklog(json.data.backlog));
21 | dispatch(setTodo(json.data.todo));
22 | dispatch(setInProgress(json.data.inProgress));
23 | dispatch(setDone(json.data.done));
24 | }
25 | dispatch(setLoading(false));
26 | })
27 | .catch((error) => {
28 | console.error("Error:", error);
29 | toast.error("Something went wrong");
30 | dispatch(setLoading(false));
31 | });
32 | };
33 |
34 | export default useAllTaskFilter;
35 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useDeleteTask.js:
--------------------------------------------------------------------------------
1 | import getHeader from "../utils/header";
2 | import { toast } from "react-toastify";
3 | import { setTaskDeleteM } from "../redux/slices/stateSlice";
4 | import {
5 | deleteBacklogTask,
6 | deleteDoneTask,
7 | deleteInProgressTask,
8 | deleteTodoTask,
9 | } from "../redux/slices/taskSlice";
10 | const useDeleteTask = (e, setLoad, dispatch, id) => {
11 | setLoad("Loading...");
12 | e.target.disabled = true;
13 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/task/${id}`, {
14 | method: "DELETE",
15 | headers: getHeader(),
16 | })
17 | .then((response) => response.json())
18 | .then((json) => {
19 | setLoad("");
20 | e.target.disabled = false;
21 | if (json?.message === "success") {
22 | toast.success("Task Deleted Successfully");
23 | if (json.data.category == "to-do") {
24 | dispatch(deleteTodoTask(json.data));
25 | } else if (json.data.category == "backlog") {
26 | dispatch(deleteBacklogTask(json.data));
27 | } else if (json.data.category == "in-progress") {
28 | dispatch(deleteInProgressTask(json.data));
29 | } else {
30 | dispatch(deleteDoneTask(json.data));
31 | }
32 | dispatch(setTaskDeleteM(false));
33 | } else {
34 | toast.error(json?.message);
35 | }
36 | })
37 | .catch((error) => {
38 | console.error("Error:", error);
39 | setLoad("");
40 | toast.error("Something went wrong");
41 | e.target.disabled = false;
42 | });
43 | };
44 |
45 | export default useDeleteTask;
46 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useGetAnalytics.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { toast } from "react-toastify";
3 | import getHeader from "../utils/header";
4 | const useGetAnalytics = (setAnalytics) => {
5 | const getAnalytics = () => {
6 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/analytics`, {
7 | method: "GET",
8 | headers: getHeader(),
9 | })
10 | .then((response) => response.json())
11 | .then((json) => {
12 | if (json?.message === "success") {
13 | setAnalytics(json.data);
14 | } else {
15 | toast.error("Something went wrong");
16 | }
17 | })
18 | .catch((error) => {
19 | console.error("Error:", error);
20 | toast.error("Something went wrong");
21 | });
22 | };
23 | useEffect(() => {
24 | getAnalytics();
25 | }, []);
26 | };
27 |
28 | export default useGetAnalytics;
29 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useGetTask.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import { toast } from "react-toastify";
3 | const useGetTask = (id, setTask) => {
4 | const getTask = () => {
5 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/task/${id}`, {
6 | method: "GET",
7 | headers: { "Content-Type": "application/json" },
8 | })
9 | .then((response) => response.json())
10 | .then((json) => {
11 | if (json?.message === "success") {
12 | setTask(json.data);
13 | } else {
14 | toast.error("Something went wrong");
15 | setTask(null);
16 | }
17 | })
18 | .catch((error) => {
19 | console.error("Error:", error);
20 | toast.error("Something went wrong");
21 | });
22 | };
23 | useEffect(() => {
24 | getTask();
25 | }, []);
26 | };
27 |
28 | export default useGetTask;
29 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useUpdateCategory.js:
--------------------------------------------------------------------------------
1 | import getHeader from "../utils/header";
2 | import { toast } from "react-toastify";
3 | import {
4 | setCategoryName,
5 | setUpdateCategoryM,
6 | } from "../redux/slices/stateSlice";
7 | import {
8 | addBacklogTask,
9 | addDoneTask,
10 | addInProgressTask,
11 | addTodoTask,
12 | deleteBacklogTask,
13 | deleteDoneTask,
14 | deleteInProgressTask,
15 | deleteTodoTask,
16 | } from "../redux/slices/taskSlice";
17 | const useUpdateCategory = (
18 | e,
19 | setLoad,
20 | dispatch,
21 | taskId,
22 | newCategory,
23 | oldCategory
24 | ) => {
25 | setLoad("Loading...");
26 | e.target.disabled = true;
27 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/task/category/${taskId}`, {
28 | method: "PUT",
29 | headers: getHeader(),
30 | body: JSON.stringify({
31 | category: newCategory,
32 | }),
33 | })
34 | .then((response) => response.json())
35 | .then((json) => {
36 | setLoad("");
37 | e.target.disabled = false;
38 | if (json?.message === "success") {
39 | toast.success("Changed the status of task");
40 | dispatch(setUpdateCategoryM(false));
41 | dispatch(setCategoryName(""));
42 | if (json.data.category == "to-do") {
43 | dispatch(addTodoTask(json.data));
44 | } else if (json.data.category == "backlog") {
45 | dispatch(addBacklogTask(json.data));
46 | } else if (json.data.category == "in-progress") {
47 | dispatch(addInProgressTask(json.data));
48 | } else {
49 | dispatch(addDoneTask(json.data));
50 | }
51 | if (oldCategory == "to-do") {
52 | dispatch(deleteTodoTask(json.data));
53 | } else if (oldCategory == "backlog") {
54 | dispatch(deleteBacklogTask(json.data));
55 | } else if (oldCategory == "in-progress") {
56 | dispatch(deleteInProgressTask(json.data));
57 | } else {
58 | dispatch(deleteDoneTask(json.data));
59 | }
60 | } else {
61 | toast.error(json?.message);
62 | }
63 | })
64 | .catch((error) => {
65 | console.error("Error:", error);
66 | setLoad("");
67 | toast.error("Something went wrong");
68 | e.target.disabled = false;
69 | });
70 | };
71 |
72 | export default useUpdateCategory;
73 |
--------------------------------------------------------------------------------
/frontend/src/hooks/useUpdateTask.js:
--------------------------------------------------------------------------------
1 | import getHeader from "../utils/header";
2 | import { toast } from "react-toastify";
3 | import { setTaskCardM, setTaskM } from "../redux/slices/stateSlice";
4 | import {
5 | deleteBacklogTask,
6 | deleteDoneTask,
7 | deleteInProgressTask,
8 | deleteTodoTask,
9 | updateBacklogTask,
10 | updateDoneTask,
11 | updateInProgressTask,
12 | updateTodoTask,
13 | } from "../redux/slices/taskSlice";
14 | const useUpdateTask = (
15 | e,
16 | setLoad,
17 | title,
18 | priority,
19 | checklist,
20 | assign,
21 | dueDate,
22 | dispatch,
23 | id
24 | ) => {
25 | setLoad("Loading...");
26 | e.target.disabled = true;
27 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/task/${id}`, {
28 | method: "PUT",
29 | headers: getHeader(),
30 | body: JSON.stringify({
31 | title: title,
32 | priority: priority,
33 | checklist: checklist,
34 | assign: assign,
35 | dueDate: dueDate,
36 | }),
37 | })
38 | .then((response) => response.json())
39 | .then((json) => {
40 | setLoad("");
41 | e.target.disabled = false;
42 | if (json?.message === "success") {
43 | toast.success("Task Updated Successfully");
44 | if (json?.removeAssignCategory == "to-do") {
45 | dispatch(deleteTodoTask(json.data));
46 | } else if (json?.removeAssignCategory == "in-progress") {
47 | dispatch(deleteInProgressTask(json.data));
48 | } else if (json?.removeAssignCategory == "backlog") {
49 | dispatch(deleteBacklogTask(json.data));
50 | } else if (json?.removeAssignCategory == "done") {
51 | dispatch(deleteDoneTask(json.data));
52 | } else {
53 | if (json.data.category == "to-do") {
54 | dispatch(updateTodoTask(json.data));
55 | } else if (json.data.category == "backlog") {
56 | dispatch(updateBacklogTask(json.data));
57 | } else if (json.data.category == "in-progress") {
58 | dispatch(updateInProgressTask(json.data));
59 | } else {
60 | dispatch(updateDoneTask(json.data));
61 | }
62 | }
63 | dispatch(setTaskCardM(false));
64 | dispatch(setTaskM(""));
65 | } else {
66 | toast.error(json?.message);
67 | }
68 | })
69 | .catch((error) => {
70 | console.error("Error:", error);
71 | setLoad("");
72 | toast.error("Something went wrong");
73 | e.target.disabled = false;
74 | });
75 | };
76 |
77 | export default useUpdateTask;
78 |
--------------------------------------------------------------------------------
/frontend/src/index.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | font-family: "Poppins", sans-serif;
6 | font-style: normal;
7 | -webkit-user-select: none;
8 | -moz-user-select: none;
9 | -ms-user-select: none;
10 | user-select: none;
11 | }
12 | #root,
13 | .app-container {
14 | height: 100vh;
15 | width: 100vw;
16 | }
17 |
18 | /* Loading Style */
19 | .loading-container {
20 | position: absolute;
21 | left: 0px;
22 | top: 0px;
23 | z-index: 99;
24 | display: flex;
25 | align-items: center;
26 | justify-content: center;
27 | height: 100vh;
28 | width: 100vw;
29 | }
30 |
31 | .loading-box {
32 | transform: translateY(-200%);
33 | }
34 |
35 | .loading {
36 | height: 60px;
37 | width: 60px;
38 | border-radius: 50%;
39 | border: 10px solid #8daab9cc;
40 | border-bottom: 10px solid transparent;
41 | animation: loading 1.5s infinite linear;
42 | }
43 |
44 | @keyframes loading {
45 | to {
46 | transform: rotate(0deg);
47 | }
48 | from {
49 | transform: rotate(360deg);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/frontend/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App.jsx";
4 | import "./index.css";
5 | import { BrowserRouter } from "react-router-dom";
6 | import store from "./redux/store";
7 | import { Provider } from "react-redux";
8 | import { ToastContainer } from "react-toastify";
9 | import "react-toastify/dist/ReactToastify.css";
10 |
11 | ReactDOM.createRoot(document.getElementById("root")).render(
12 |
13 |
14 |
15 |
33 |
34 |
35 |
36 |
37 | );
38 |
--------------------------------------------------------------------------------
/frontend/src/pages/Analytics.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import "../css/Analytics.css";
3 | import useGetAnalytics from "../hooks/useGetAnalytics";
4 | import Loading from "../components/Loading";
5 | const Analytics = () => {
6 | const [analytics, setAnalytics] = useState(null);
7 | useGetAnalytics(setAnalytics);
8 |
9 | return analytics == null ? (
10 |
11 | ) : (
12 |
13 |
14 |
15 |
Analytics
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
Backlog Tasks
24 |
25 |
{analytics?.backlog}
26 |
27 |
28 |
29 |
30 |
To-do Tasks
31 |
32 |
{analytics?.todo}
33 |
34 |
35 |
36 |
37 |
In-Progress Tasks
38 |
39 |
{analytics?.inProgress}
40 |
41 |
42 |
43 |
44 |
Completed Tasks
45 |
46 |
{analytics?.done}
47 |
48 |
49 |
50 |
51 |
52 |
53 |
Low Priority
54 |
55 |
{analytics?.low}
56 |
57 |
58 |
59 |
60 |
Moderate Priority
61 |
62 |
{analytics?.moderate}
63 |
64 |
65 |
66 |
67 |
High Priority
68 |
69 |
{analytics?.high}
70 |
71 |
72 |
73 |
74 |
Due Date Tasks
75 |
76 |
{analytics?.dueDate}
77 |
78 |
79 |
80 |
81 | );
82 | };
83 |
84 | export default Analytics;
85 |
--------------------------------------------------------------------------------
/frontend/src/pages/Backlog.jsx:
--------------------------------------------------------------------------------
1 | import { TbDots } from "react-icons/tb";
2 | import TaskBox from "../components/TaskBox";
3 | import { useSelector } from "react-redux";
4 |
5 | const Backlog = ({ backlogCollapse }) => {
6 | const backlog = useSelector((store) => store.task.backlog);
7 | return (
8 |
9 | {backlog?.map((task, index) => (
10 |
15 | ))}
16 |
17 | );
18 | };
19 |
20 | export default Backlog;
21 |
--------------------------------------------------------------------------------
/frontend/src/pages/Board.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import Backlog from "./Backlog";
3 | import ToDo from "./ToDo";
4 | import InProgress from "./InProgress";
5 | import Done from "./Done";
6 | import { VscCollapseAll } from "react-icons/vsc";
7 | import { AiOutlinePlus } from "react-icons/ai";
8 | import { IoIosArrowDown } from "react-icons/io";
9 | import "../css/Task.css";
10 | import { useDispatch, useSelector } from "react-redux";
11 | import { generateDate } from "../utils/generateDate";
12 | import { LuUsers2 } from "react-icons/lu";
13 | import {
14 | setAddPeopleM,
15 | setTaskCardM,
16 | setTaskFilterP,
17 | } from "../redux/slices/stateSlice";
18 | import { TaskFilter } from "../components/PopUp";
19 | import useAllTask from "../hooks/useAllTask";
20 | const Board = () => {
21 | const [backlogCollapse, setBacklogCollapse] = useState(false);
22 | const [todoCollapse, setTodoCollapse] = useState(false);
23 | const [progressCollapse, setProgressCollapse] = useState(false);
24 | const [doneCollapse, setDoneCollapse] = useState(false);
25 | const auth = useSelector((store) => store.auth);
26 | const taskFilter = useSelector((store) => store.state.taskFilterP);
27 | const taskFilterName = useSelector((store) => store.state.taskFilterName);
28 | const dispatch = useDispatch();
29 |
30 | const handleClickOutside = (event) => {
31 | if (taskFilter && !event?.target.closest(".popup-box")) {
32 | dispatch(setTaskFilterP(false));
33 | }
34 | };
35 | useEffect(() => {
36 | document.addEventListener("mousedown", handleClickOutside);
37 | return () => {
38 | document.removeEventListener("mousedown", handleClickOutside);
39 | };
40 | }, [taskFilter]);
41 |
42 | useAllTask();
43 |
44 | return (
45 |
46 |
47 |
48 |
Welcome! {auth.name.split(" ")[0]}
49 | {generateDate()}
50 |
51 |
52 |
53 |
Board
54 | dispatch(setAddPeopleM(true))}
57 | >
58 |
59 | Add People
60 |
61 |
62 |
63 | dispatch(setTaskFilterP(true))}>
64 | {taskFilterName}
65 |
66 |
67 | {taskFilter && }
68 |
69 |
70 |
71 |
72 |
73 |
74 | Backlog {" "}
75 | setBacklogCollapse(!backlogCollapse)}
79 | />
80 |
81 |
82 |
83 |
84 |
85 | To Do
86 |
87 | dispatch(setTaskCardM(true))}
91 | />
92 | setTodoCollapse(!todoCollapse)}
96 | />
97 |
98 |
99 |
100 |
101 |
102 |
103 | In Progress {" "}
104 |
108 | setProgressCollapse(!progressCollapse)
109 | }
110 | />
111 |
112 |
113 |
114 |
115 |
116 | Done {" "}
117 | setDoneCollapse(!doneCollapse)}
121 | />
122 |
123 |
124 |
125 |
126 |
127 | );
128 | };
129 |
130 | export default Board;
131 |
--------------------------------------------------------------------------------
/frontend/src/pages/Dashboard.jsx:
--------------------------------------------------------------------------------
1 | import { lazy } from "react";
2 | import Navbar from "./Navbar";
3 | const Board = lazy(() => import("./Board.jsx"));
4 | const Analytics = lazy(() => import("./Analytics.jsx"));
5 | const Settings = lazy(() => import("./Settings.jsx"));
6 | import { useSelector } from "react-redux";
7 | import "../css/Dashboard.css";
8 | import {
9 | AddedPeople,
10 | AddPeople,
11 | Logout,
12 | TaskCard,
13 | TaskDelete,
14 | UpdateCategory,
15 | } from "../components/Model";
16 |
17 | const Dashboard = () => {
18 | const state = useSelector((store) => store.state);
19 | return (
20 | <>
21 |
22 | {state.dashboardSection == "board" && }
23 | {state.dashboardSection == "analytics" && }
24 | {state.dashboardSection == "settings" && }
25 | {state.addPeopleM && }
26 | {state.addedPeopleM && }
27 | {state.logoutM && }
28 | {state.taskDeleteM && }
29 | {state.taskCardM && }
30 | {state.updateCategoryM && }
31 | >
32 | );
33 | };
34 |
35 | export default Dashboard;
36 |
--------------------------------------------------------------------------------
/frontend/src/pages/Done.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import TaskBox from "../components/TaskBox";
3 |
4 | const Done = ({ doneCollapse }) => {
5 | const done = useSelector((store) => store.task.done);
6 | return (
7 |
8 | {done?.map((task, index) => (
9 |
10 | ))}
11 |
12 | );
13 | };
14 |
15 | export default Done;
16 |
--------------------------------------------------------------------------------
/frontend/src/pages/InProgress.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import TaskBox from "../components/TaskBox";
3 |
4 | const InProgress = ({ progressCollapse }) => {
5 | const inProgress = useSelector((store) => store.task.inProgress);
6 | return (
7 |
8 | {inProgress?.map((task, index) => (
9 |
14 | ))}
15 |
16 | );
17 | };
18 |
19 | export default InProgress;
20 |
--------------------------------------------------------------------------------
/frontend/src/pages/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import { PiCodesandboxLogoDuotone } from "react-icons/pi";
2 | import { MdOutlineSpaceDashboard } from "react-icons/md";
3 | import { GoDatabase } from "react-icons/go";
4 | import { IoLogOutOutline, IoSettingsOutline } from "react-icons/io5";
5 | import "../css/Navbar.css";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import { setLogoutM, updateDashboardSection } from "../redux/slices/stateSlice";
8 | import { Link } from "react-router-dom";
9 |
10 | const Navbar = () => {
11 | const dispatch = useDispatch();
12 | const dashboardSection = useSelector(
13 | (store) => store.state.dashboardSection
14 | );
15 |
16 | return (
17 |
18 |
19 |
20 |
Pro Manage
21 |
22 |
23 |
24 |
26 | dispatch(updateDashboardSection("board"))
27 | }
28 | className={`nav-link ${
29 | dashboardSection == "board" && "nav-active"
30 | }`}
31 | >
32 |
33 | Board
34 |
35 |
37 | dispatch(updateDashboardSection("analytics"))
38 | }
39 | className={`nav-link ${
40 | dashboardSection == "analytics" && "nav-active"
41 | }`}
42 | >
43 |
44 | Analytics
45 |
46 |
48 | dispatch(updateDashboardSection("settings"))
49 | }
50 | className={`nav-link ${
51 | dashboardSection == "settings" && "nav-active"
52 | }`}
53 | >
54 |
55 | Settings
56 |
57 |
58 |
59 | dispatch(setLogoutM(true))}
62 | >
63 |
64 | Log out
65 |
66 |
67 |
68 |
69 | );
70 | };
71 |
72 | export default Navbar;
73 |
--------------------------------------------------------------------------------
/frontend/src/pages/PageNotFound.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "../css/Page_Not_Found.css";
3 | import { Link } from "react-router-dom";
4 | const PageNotFound = () => {
5 | return (
6 |
7 |
Page Not Found
8 | 404
9 |
10 | Back
11 |
12 |
13 | );
14 | };
15 |
16 | export default PageNotFound;
17 |
--------------------------------------------------------------------------------
/frontend/src/pages/Register_Login.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useNavigate } from "react-router-dom";
3 | import { toast } from "react-toastify";
4 | import { checkValidSignInFrom, checkValidSignUpFrom } from "../utils/validate";
5 | import { PiEye, PiEyeClosedLight } from "react-icons/pi";
6 | import { CiLock, CiMail, CiUser } from "react-icons/ci";
7 | import { useDispatch } from "react-redux";
8 | import { addAuth } from "../redux/slices/authSlice";
9 | import AuthImage from "../assets/AuthImage.png";
10 | import "../css/Register_Login.css";
11 |
12 | const Register_Login = () => {
13 | const [isRegister, setIsRegister] = useState(false);
14 | const [name, setName] = useState("");
15 | const [email, setEmail] = useState("");
16 | const [password, setPassword] = useState("");
17 | const [cpassword, setCPassword] = useState("");
18 | const [load, setLoad] = useState("");
19 | const [isShow, setIsShow] = useState(false);
20 | const [isShowC, setIsShowC] = useState(false);
21 | const navigate = useNavigate();
22 | const dispatch = useDispatch();
23 |
24 | // Login
25 | const logInUser = (e) => {
26 | toast.loading("Wait until you SignIn");
27 | e.target.disabled = true;
28 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/auth/login`, {
29 | method: "POST",
30 | headers: {
31 | "Content-Type": "application/json",
32 | },
33 | body: JSON.stringify({
34 | email: email,
35 | password: password,
36 | }),
37 | })
38 | .then((response) => response.json())
39 | .then((json) => {
40 | setLoad("");
41 | e.target.disabled = false;
42 | toast.dismiss();
43 | if (json.token) {
44 | localStorage.setItem("token", json.token);
45 | dispatch(addAuth(json.data));
46 | navigate("/");
47 | toast.success(json?.message);
48 | } else {
49 | toast.error(json?.message);
50 | }
51 | })
52 | .catch((error) => {
53 | console.error("Error:", error);
54 | setLoad("");
55 | toast.dismiss();
56 | toast.error("Something went wrong");
57 | e.target.disabled = false;
58 | });
59 | };
60 | const handleLogin = (e) => {
61 | if (email && password) {
62 | const validError = checkValidSignInFrom(email, password);
63 | if (validError) {
64 | toast.error(validError);
65 | return;
66 | }
67 | setLoad("Loading...");
68 | logInUser(e);
69 | } else {
70 | toast.error("All fields are required");
71 | }
72 | };
73 |
74 | // Signup
75 | const registerUser = (e) => {
76 | toast.loading("Wait until you SignUp");
77 | e.target.disabled = true;
78 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/auth/register`, {
79 | method: "POST",
80 | headers: {
81 | "Content-Type": "application/json",
82 | },
83 | body: JSON.stringify({
84 | name: name,
85 | email: email,
86 | password: password,
87 | }),
88 | })
89 | .then((response) => response.json())
90 | .then((json) => {
91 | setLoad("");
92 | e.target.disabled = false;
93 | toast.dismiss();
94 | if (json.token) {
95 | setIsRegister(false);
96 | setPassword("");
97 | setCPassword("");
98 | toast.success(json?.message);
99 | } else {
100 | toast.error(json?.message);
101 | }
102 | })
103 | .catch((error) => {
104 | console.error("Error:", error);
105 | setLoad("");
106 | toast.dismiss();
107 | toast.error("Something went wrong");
108 | e.target.disabled = false;
109 | });
110 | };
111 | const handleRegister = (e) => {
112 | if (name && email && password) {
113 | const validError = checkValidSignUpFrom(name, email, password);
114 | if (validError) {
115 | toast.error(validError);
116 | return;
117 | }
118 | if (password != cpassword) {
119 | toast.error("Passwords don't match");
120 | return;
121 | }
122 | setLoad("Loading...");
123 | registerUser(e);
124 | } else {
125 | toast.error("All fields are required");
126 | }
127 | };
128 | const handleName = (name) => {
129 | name = name.charAt(0).toUpperCase() + name.slice(1);
130 | setName(name);
131 | };
132 |
133 | const handlePassword = (password) => {
134 | password = password.trim();
135 | setPassword(password);
136 | };
137 | return (
138 |
139 |
140 |
141 |
142 |
147 |
148 |
Welcome aboard my friend
149 |
just a couple of clicks and we start
150 |
151 |
*/}
254 |
255 |
256 | );
257 | };
258 |
259 | export default Register_Login;
260 |
--------------------------------------------------------------------------------
/frontend/src/pages/Settings.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { toast } from "react-toastify";
3 | import { checkValidSignUpFrom } from "../utils/validate";
4 | import { PiEye, PiEyeClosedLight } from "react-icons/pi";
5 | import { CiLock, CiMail, CiUser } from "react-icons/ci";
6 | import { useDispatch, useSelector } from "react-redux";
7 | import { useNavigate } from "react-router-dom";
8 | import { removeAuth } from "../redux/slices/authSlice";
9 | import getHeader from "../utils/header";
10 |
11 | const Settings = () => {
12 | const auth = useSelector((store) => store.auth);
13 | const [name, setName] = useState(auth.name);
14 | const [email, setEmail] = useState(auth.email);
15 | const [oldPassword, setOldPassword] = useState("");
16 | const [newPassword, setNewPassword] = useState("");
17 | const [load, setLoad] = useState("");
18 | const [isShowO, setIsShowO] = useState(false);
19 | const [isShowN, setIsShowN] = useState(false);
20 | const dashboardSection = useSelector(
21 | (store) => store.state.dashboardSection
22 | );
23 | const dispatch = useDispatch();
24 | const navigate = useNavigate();
25 |
26 | const handleName = (name) => {
27 | name = name.charAt(0).toUpperCase() + name.slice(1);
28 | setName(name);
29 | };
30 |
31 | const handlePasswordOld = (oldPassword) => {
32 | oldPassword = oldPassword.trim();
33 | setOldPassword(oldPassword);
34 | };
35 | const handlePasswordNew = (newPassword) => {
36 | newPassword = newPassword.trim();
37 | setNewPassword(newPassword);
38 | };
39 | // Update
40 | const updateUser = (e) => {
41 | toast.loading("Wait until you SignUp");
42 | e.target.disabled = true;
43 | fetch(`${import.meta.env.VITE_BACKEND_URL}/api/user/update`, {
44 | method: "PUT",
45 | headers: getHeader(),
46 | body: JSON.stringify({
47 | name: name,
48 | email: email,
49 | oldPassword: oldPassword,
50 | newPassword: newPassword,
51 | }),
52 | })
53 | .then((response) => response.json())
54 | .then((json) => {
55 | setLoad("");
56 | e.target.disabled = false;
57 | toast.dismiss();
58 | if (json?.message === "success") {
59 | toast.success("Update Successfully");
60 | localStorage.removeItem("token");
61 | dispatch(removeAuth());
62 | navigate("/login");
63 | } else {
64 | toast.error(json?.message);
65 | }
66 | })
67 | .catch((error) => {
68 | console.error("Error:", error);
69 | setLoad("");
70 | toast.dismiss();
71 | toast.error("Something went wrong");
72 | e.target.disabled = false;
73 | });
74 | };
75 |
76 | const handleUpdate = (e) => {
77 | if (name && email && newPassword && oldPassword) {
78 | const validError = checkValidSignUpFrom(name, email, newPassword);
79 | if (validError) {
80 | toast.error(validError);
81 | return;
82 | }
83 | setLoad("Loading...");
84 | updateUser(e);
85 | } else {
86 | toast.error("All fields are required");
87 | }
88 | };
89 | return (
90 |
91 |
92 |
93 |
Settings
94 |
95 |
96 |
183 |
184 | );
185 | };
186 |
187 | export default Settings;
188 |
--------------------------------------------------------------------------------
/frontend/src/pages/ToDo.jsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from "react-redux";
2 | import TaskBox from "../components/TaskBox";
3 |
4 | const ToDo = ({ todoCollapse }) => {
5 | const todo = useSelector((store) => store.task.todo);
6 | return (
7 |
8 | {todo?.map((task, index) => (
9 |
10 | ))}
11 |
12 | );
13 | };
14 |
15 | export default ToDo;
16 |
--------------------------------------------------------------------------------
/frontend/src/redux/slices/authSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const authSlice = createSlice({
4 | name: "auth",
5 | initialState: null,
6 | reducers: {
7 | addAuth: (state, action) => {
8 | return action.payload;
9 | },
10 | removeAuth: (state) => {
11 | return null;
12 | },
13 | },
14 | });
15 | export const { addAuth, removeAuth } = authSlice.actions;
16 | export default authSlice.reducer;
17 |
--------------------------------------------------------------------------------
/frontend/src/redux/slices/stateSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const stateSlice = createSlice({
4 | name: "state",
5 | initialState: {
6 | dashboardSection: "board",
7 | loading: false,
8 | addPeopleM: false,
9 | addedPeopleM: false,
10 | logoutM: false,
11 | taskDeleteM: false,
12 | taskCardM: false,
13 | taskFilterP: false,
14 | taskFilterName: "This month",
15 | taskMId: "",
16 | taskM: "",
17 | boardEmail: "",
18 | updateCategoryM: false,
19 | categoryName: "",
20 | },
21 | reducers: {
22 | updateDashboardSection: (state, action) => {
23 | state.dashboardSection = action.payload;
24 | },
25 | setLoading: (state, action) => {
26 | state.loading = action.payload;
27 | },
28 | setAddPeopleM: (state, action) => {
29 | state.addPeopleM = action.payload;
30 | },
31 | setAddedPeopleM: (state, action) => {
32 | state.addedPeopleM = action.payload;
33 | },
34 | setLogoutM: (state, action) => {
35 | state.logoutM = action.payload;
36 | },
37 | setTaskDeleteM: (state, action) => {
38 | state.taskDeleteM = action.payload;
39 | },
40 | setTaskCardM: (state, action) => {
41 | state.taskCardM = action.payload;
42 | },
43 | setTaskFilterP: (state, action) => {
44 | state.taskFilterP = action.payload;
45 | },
46 | setTaskFilterName: (state, action) => {
47 | state.taskFilterName = action.payload;
48 | },
49 | setTaskMId: (state, action) => {
50 | state.taskMId = action.payload;
51 | },
52 | setTaskM: (state, action) => {
53 | state.taskM = action.payload;
54 | },
55 | setBoardEmail: (state, action) => {
56 | state.boardEmail = action.payload;
57 | },
58 | setUpdateCategoryM: (state, action) => {
59 | state.updateCategoryM = action.payload;
60 | },
61 | setCategoryName: (state, action) => {
62 | state.categoryName = action.payload;
63 | },
64 | },
65 | });
66 | export const {
67 | updateDashboardSection,
68 | setLoading,
69 | setAddPeopleM,
70 | setAddedPeopleM,
71 | setLogoutM,
72 | setTaskDeleteM,
73 | setTaskCardM,
74 | setTaskFilterP,
75 | setTaskFilterName,
76 | setTaskMId,
77 | setTaskM,
78 | setBoardEmail,
79 | setUpdateCategoryM,
80 | setCategoryName,
81 | } = stateSlice.actions;
82 | export default stateSlice.reducer;
83 |
--------------------------------------------------------------------------------
/frontend/src/redux/slices/taskSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const taskSlice = createSlice({
4 | name: "task",
5 | initialState: {
6 | backlog: [],
7 | todo: [],
8 | inProgress: [],
9 | done: [],
10 | },
11 | reducers: {
12 | setBacklog: (state, action) => {
13 | state.backlog = action.payload;
14 | },
15 | setTodo: (state, action) => {
16 | state.todo = action.payload;
17 | },
18 | setInProgress: (state, action) => {
19 | state.inProgress = action.payload;
20 | },
21 | setDone: (state, action) => {
22 | state.done = action.payload;
23 | },
24 | addBacklogTask: (state, action) => {
25 | state.backlog = [...state.backlog, action.payload];
26 | },
27 | addTodoTask: (state, action) => {
28 | state.todo = [...state.todo, action.payload];
29 | },
30 | addInProgressTask: (state, action) => {
31 | state.inProgress = [...state.inProgress, action.payload];
32 | },
33 | addDoneTask: (state, action) => {
34 | state.done = [...state.done, action.payload];
35 | },
36 | updateBacklogTask: (state, action) => {
37 | state.backlog = state.backlog.map((t) =>
38 | t._id == action.payload._id ? action.payload : t
39 | );
40 | },
41 | updateTodoTask: (state, action) => {
42 | state.todo = state.todo.map((t) =>
43 | t._id == action.payload._id ? action.payload : t
44 | );
45 | },
46 | updateInProgressTask: (state, action) => {
47 | state.inProgress = state.inProgress.map((t) =>
48 | t._id == action.payload._id ? action.payload : t
49 | );
50 | },
51 | updateDoneTask: (state, action) => {
52 | state.done = state.done.map((t) =>
53 | t._id == action.payload._id ? action.payload : t
54 | );
55 | },
56 | deleteBacklogTask: (state, action) => {
57 | state.backlog = state.backlog.filter(
58 | (t) => t._id != action.payload._id
59 | );
60 | },
61 | deleteTodoTask: (state, action) => {
62 | state.todo = state.todo.filter((t) => t._id != action.payload._id);
63 | },
64 | deleteInProgressTask: (state, action) => {
65 | state.inProgress = state.inProgress.filter(
66 | (t) => t._id != action.payload._id
67 | );
68 | },
69 | deleteDoneTask: (state, action) => {
70 | state.done = state.done.filter((t) => t._id != action.payload._id);
71 | },
72 | },
73 | });
74 | export const {
75 | setBacklog,
76 | setTodo,
77 | setInProgress,
78 | setDone,
79 | addBacklogTask,
80 | addTodoTask,
81 | addInProgressTask,
82 | addDoneTask,
83 | updateBacklogTask,
84 | updateTodoTask,
85 | updateInProgressTask,
86 | updateDoneTask,
87 | deleteBacklogTask,
88 | deleteTodoTask,
89 | deleteInProgressTask,
90 | deleteDoneTask,
91 | } = taskSlice.actions;
92 | export default taskSlice.reducer;
93 |
--------------------------------------------------------------------------------
/frontend/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from "@reduxjs/toolkit";
2 | import authSlice from "./slices/authSlice";
3 | import stateSlice from "./slices/stateSlice";
4 | import taskSlice from "./slices/taskSlice";
5 |
6 | const store = configureStore({
7 | reducer: {
8 | auth: authSlice,
9 | state: stateSlice,
10 | task: taskSlice,
11 | },
12 | });
13 |
14 | export default store;
15 |
--------------------------------------------------------------------------------
/frontend/src/utils/generateDate.js:
--------------------------------------------------------------------------------
1 | export const generateDate = () => {
2 | return new Date(Date.now())
3 | .toDateString()
4 | .split(" ")
5 | .slice(2, 4)
6 | .join("th -, ")
7 | .replace("-", new Date(Date.now()).toDateString().split(" ")[1]);
8 | };
9 |
10 | export const getMonthDate = (date) => {
11 | return new Date(date).toDateString().split(" ").splice(1, 2).join(" ");
12 | };
13 |
14 | // simple date formate MM/DD/YYYY
15 | export const simpleDate = (date) => {
16 | return new Date(date)
17 | .toLocaleDateString()
18 | .split("/")
19 | .splice(1)
20 | .join("/" + new Date(date).getDate() + "/");
21 | };
22 |
--------------------------------------------------------------------------------
/frontend/src/utils/header.js:
--------------------------------------------------------------------------------
1 | const getHeader = () => {
2 | const token = localStorage.getItem("token");
3 | return {
4 | "Content-Type": "application/json",
5 | Authorization: `Bearer ${token}`,
6 | };
7 | };
8 |
9 | export default getHeader;
10 |
--------------------------------------------------------------------------------
/frontend/src/utils/validate.js:
--------------------------------------------------------------------------------
1 | export const checkValidSignInFrom = (email, password) => {
2 | const isEmailValid = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/.test(
3 | email
4 | );
5 | const isPasswordValid =
6 | /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[^((0-9)|(a-z)|(A-Z)|\s)]).{8,}$/.test(
7 | password
8 | );
9 | if (!isEmailValid) return "Invalid email format";
10 | if (!isPasswordValid) return "Invalid password";
11 | return null;
12 | };
13 | export const checkValidSignUpFrom = (name, email, password) => {
14 | const isName = /\b([A-ZÀ-ÿ][-,a-z. ']+[ ]*)+/.test(name);
15 | const isEmailValid = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/.test(
16 | email
17 | );
18 | if (!isName) return "Invalid name format";
19 | if (!isEmailValid) return "Invalid email format";
20 | if (password.length < 8) return "Min 8 characters";
21 | if (!/[a-z]/.test(password)) return "Needs 1 lowercase letter";
22 | if (!/[A-Z]/.test(password)) return "Needs 1 uppercase lette";
23 | if (!/\d/.test(password)) return "Needs 1 number";
24 | if (!/[^((0-9)|(a-z)|(A-Z)|\s)]/.test(password))
25 | return "Needs 1 special char";
26 | return null;
27 | };
28 |
29 | export const checkValidEmail = (email) => {
30 | const isEmailValid = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/.test(
31 | email
32 | );
33 | if (!isEmailValid) return "Invalid email format";
34 | return null;
35 | };
36 |
--------------------------------------------------------------------------------
/frontend/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "rewrites": [{ "source": "/(.*)", "destination": "/" }]
3 | }
4 |
--------------------------------------------------------------------------------
/frontend/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vite'
2 | import react from '@vitejs/plugin-react'
3 |
4 | // https://vitejs.dev/config/
5 | export default defineConfig({
6 | plugins: [react()],
7 | })
8 |
--------------------------------------------------------------------------------