├── server
├── procfile
├── error.js
├── middleware
│ ├── auth.js
│ └── verifyToken.js
├── models
│ ├── Notifications.js
│ ├── Tasks.js
│ ├── Works.js
│ ├── User.js
│ ├── Teams.js
│ └── Project.js
├── package.json
├── routes
│ ├── auth.js
│ ├── teams.js
│ ├── user.js
│ └── project.js
├── index.js
└── controllers
│ ├── works.js
│ └── user.js
├── client
├── src
│ ├── data
│ │ ├── types.js
│ │ └── data.js
│ ├── Images
│ │ ├── Header.png
│ │ ├── AddProject.gif
│ │ ├── google.svg
│ │ └── Logo.svg
│ ├── pages
│ │ ├── Home
│ │ │ ├── components
│ │ │ │ ├── About.jsx
│ │ │ │ ├── HeroBgAnimation
│ │ │ │ │ └── HeroBgAnimationStyle.js
│ │ │ │ ├── Faq.jsx
│ │ │ │ ├── Testimonials.jsx
│ │ │ │ ├── TeamMember.jsx
│ │ │ │ ├── Navbar.jsx
│ │ │ │ ├── Team.jsx
│ │ │ │ ├── Hero.jsx
│ │ │ │ ├── Footer.jsx
│ │ │ │ ├── Features.jsx
│ │ │ │ └── Benifits.jsx
│ │ │ └── Home.jsx
│ │ ├── Community.jsx
│ │ ├── Chats.jsx
│ │ └── Projects.jsx
│ ├── index.css
│ ├── redux
│ │ ├── snackbarSlice.js
│ │ ├── store.js
│ │ └── userSlice.js
│ ├── index.js
│ ├── components
│ │ ├── ToastMessage.jsx
│ │ ├── ToolsCard.jsx
│ │ ├── DropWrapper.jsx
│ │ ├── IdeaCard.jsx
│ │ ├── ProjectInvite.jsx
│ │ ├── MemberCard.jsx
│ │ ├── TeamInvite.jsx
│ │ ├── ImageSelector.jsx
│ │ ├── NotificationDialog.jsx
│ │ ├── AccountDialog.jsx
│ │ ├── ProjectStatCard.jsx
│ │ ├── TaskCard.jsx
│ │ ├── Card.jsx
│ │ ├── DeletePopup.jsx
│ │ ├── ChatContact.jsx
│ │ ├── WorkCards.jsx
│ │ ├── Menu.jsx
│ │ ├── ChatContainer.jsx
│ │ ├── Navbar.jsx
│ │ ├── InviteMembers.jsx
│ │ └── WorkDetails.jsx
│ ├── utils
│ │ └── Theme.js
│ ├── logo.svg
│ ├── App.js
│ └── api
│ │ └── index.js
├── netlify.toml
├── public
│ ├── favicon.ico
│ ├── index.html
│ └── Logo.svg
├── package.json
└── README.md
├── .gitignore
├── README.md
└── LICENSE
/server/procfile:
--------------------------------------------------------------------------------
1 | web: npm start
--------------------------------------------------------------------------------
/client/src/data/types.js:
--------------------------------------------------------------------------------
1 | const ITEM_TYPE = "ITEM";
2 |
3 | export default ITEM_TYPE;
--------------------------------------------------------------------------------
/client/netlify.toml:
--------------------------------------------------------------------------------
1 | [[redirects]]
2 | from = "/*"
3 | to = "/index.html"
4 | status = 200
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rishavchanda/Project-Management-App/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/src/Images/Header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rishavchanda/Project-Management-App/HEAD/client/src/Images/Header.png
--------------------------------------------------------------------------------
/client/src/Images/AddProject.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rishavchanda/Project-Management-App/HEAD/client/src/Images/AddProject.gif
--------------------------------------------------------------------------------
/client/src/pages/Home/components/About.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const About = () => {
4 | return (
5 |
About
6 | )
7 | }
8 |
9 | export default About
--------------------------------------------------------------------------------
/server/error.js:
--------------------------------------------------------------------------------
1 | export const createError = (status, message)=>{
2 | const err = new Error()
3 | err.status= status
4 | err.message= message
5 | return err
6 | }
--------------------------------------------------------------------------------
/client/src/pages/Community.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Community = () => {
4 | return (
5 | Comming Soon...
6 | )
7 | }
8 |
9 | export default Community
--------------------------------------------------------------------------------
/client/src/pages/Home/components/HeroBgAnimation/HeroBgAnimationStyle.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 |
4 | export const Div = styled.div`
5 | width: 500px;
6 | height: 500px;
7 | `
--------------------------------------------------------------------------------
/server/middleware/auth.js:
--------------------------------------------------------------------------------
1 |
2 | export function localVariables(req, res, next) {
3 | res.app.locals = {
4 | OTP: null,
5 | resetSession: false,
6 | CODE: null
7 | };
8 | next();
9 | }
--------------------------------------------------------------------------------
/server/models/Notifications.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const Notifications = new mongoose.Schema({
4 | message: {
5 | type: String,
6 | required: true,
7 | },
8 | link: {
9 | type: String,
10 | },
11 | type: { type: String, required: true },
12 | },
13 | { timestamps: true }
14 | );
15 |
16 | export default mongoose.model("Notifications", Notifications);
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | server/node_modules
5 | client/node_modules
6 | client/.env
7 | server/.env
8 | /.pnp
9 | .pnp.js
10 |
11 | # testing
12 | /coverage
13 |
14 | # production
15 | /build
16 |
17 | # misc
18 | .env
19 | .DS_Store
20 | .env.local
21 | .env.development.local
22 | .env.test.local
23 | .env.production.local
24 |
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 |
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | @import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@100;300;400;500;600&display=swap');
2 | html{
3 | scroll-behavior: smooth;
4 | }
5 | body {
6 | margin: 0;
7 | padding: 0;
8 | font-family: 'Montserrat', sans-serif;
9 | -webkit-font-smoothing: antialiased;
10 | -moz-osx-font-smoothing: grayscale;
11 |
12 | }
13 |
14 | /* width */
15 | ::-webkit-scrollbar {
16 | width: 2px;
17 | }
18 | /* Track */
19 | ::-webkit-scrollbar-track {
20 |
21 | }
22 |
23 | /* Handle */
24 | ::-webkit-scrollbar-thumb {
25 | background: #888;
26 | border-radius: 6px;
27 | height: 50px;
28 | }
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "type": "module",
7 | "scripts": {
8 | "start": "node index.js",
9 | "dev": "nodemon index.js"
10 | },
11 | "author": "rishav",
12 | "license": "ISC",
13 | "dependencies": {
14 | "bcrypt": "^5.1.0",
15 | "cookie-parser": "^1.4.6",
16 | "cors": "^2.8.5",
17 | "dotenv": "^16.0.3",
18 | "express": "^4.18.2",
19 | "jsonwebtoken": "^8.5.1",
20 | "mongoose": "^6.7.0",
21 | "morgan": "^1.10.0",
22 | "nodemailer": "^6.8.0",
23 | "nodemon": "^2.0.20",
24 | "otp-generator": "^4.0.1"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vexa Project
2 | Welcome to Vexa - a project management app designed to help you manage your projects more efficiently.
3 |
4 | ## Features
5 | - Project management
6 | - Team collaboration
7 | - Community building
8 | - Time tracking
9 |
10 | ## Benefits
11 | - Increased productivity
12 | - Improved communication
13 | - Better project outcomes
14 | - Increased collaboration
15 | - Networking opportunities
16 |
17 |
18 | ## Technologies Used
19 | - React Js
20 | - NodeJs
21 | - Express Js
22 | - Mongo Db
23 | - Styled Components
24 | - Material-UI
25 | - Nodemailer
26 |
27 | Installation
28 |
29 | 
30 |
--------------------------------------------------------------------------------
/client/src/redux/snackbarSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const initialState = {
4 | open: false,
5 | message: "",
6 | severity: "success",
7 | };
8 |
9 | const snackbar = createSlice({
10 | name: 'snackbar',
11 | initialState,
12 | reducers: {
13 | openSnackbar: (state, action) => {
14 | state.open = true;
15 | state.message = action.payload.message;
16 | state.severity = action.payload.severity;
17 | },
18 | closeSnackbar: (state) => {
19 | state.open = false;
20 | }
21 | }
22 | });
23 |
24 | export const { openSnackbar, closeSnackbar } = snackbar.actions;
25 |
26 | export default snackbar.reducer;
27 |
--------------------------------------------------------------------------------
/server/middleware/verifyToken.js:
--------------------------------------------------------------------------------
1 | import jwt from "jsonwebtoken";
2 | import { createError } from "../error.js";
3 |
4 | export const verifyToken = async (req, res, next) => {
5 | try {
6 | if (!req.headers.authorization) return next(createError(401, "You are not authenticated!"));
7 | // Get the token from the header
8 | const token = req.headers.authorization.split(" ")[1];
9 | // Check if token exists
10 | if (!token) return next(createError(401, "You are not authenticated!"));
11 |
12 | const decode = await jwt.verify(token, process.env.JWT);
13 | req.user = decode;
14 | next();
15 | } catch (error) {
16 | console.log(error)
17 | res.status(402).json({ error: error.message })
18 | }
19 | };
--------------------------------------------------------------------------------
/server/models/Tasks.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const TasksSchema = new mongoose.Schema({
4 | projectId: {
5 | type: mongoose.Schema.Types.ObjectId,
6 | ref: "Projects",
7 | required: true,
8 | unique: false,
9 | },
10 | workId: { type: String, unique: false },
11 | task: { type: String, required: true },
12 | start_date: { type: String, required: true, default: "" },
13 | end_date: { type: String, required: true, default: "" },
14 | members: {
15 | type: [mongoose.Schema.Types.ObjectId],
16 | ref: "User",
17 | default: [],
18 | },
19 | status: { type: String, default: "Working" },
20 | },
21 | { timestamps: true }
22 | );
23 |
24 | export default mongoose.model("Tasks", TasksSchema);
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import { store, persistor } from './redux/store';
6 | import { Provider } from 'react-redux';
7 | import { PersistGate } from 'redux-persist/integration/react';
8 | import { CookiesProvider } from "react-cookie";
9 | import { GoogleOAuthProvider } from '@react-oauth/google';
10 |
11 | const root = ReactDOM.createRoot(document.getElementById('root'));
12 | root.render(
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 |
24 |
--------------------------------------------------------------------------------
/client/src/components/ToastMessage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import Alert from "@mui/material/Alert";
3 | import Snackbar from "@mui/material/Snackbar";
4 | import { useState } from "react";
5 | import { closeSnackbar } from "../redux/snackbarSlice";
6 | import { useSelector } from "react-redux";
7 | import { useDispatch } from "react-redux";
8 |
9 | const ToastMessage = ({
10 | message,
11 | severity,
12 | open,
13 | }) => {
14 | const dispatch = useDispatch();
15 | return (
16 | dispatch(closeSnackbar())}
20 | >
21 | dispatch(closeSnackbar())}
23 | severity={severity}
24 | sx={{ width: "100%" }}
25 | >
26 | {message}
27 |
28 |
29 | );
30 | };
31 |
32 | export default ToastMessage;
33 |
--------------------------------------------------------------------------------
/client/src/components/ToolsCard.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from "styled-components";
3 |
4 |
5 | const Container = styled.div`
6 | padding: 8px 16px;
7 | font-weight: 500;
8 | cursor: pointer;
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | gap: 6px;
13 | border: 1.2px solid ${({ theme }) => theme.soft2 + "99"};
14 | color: ${({ theme }) => theme.soft2+"99"};
15 | border-radius: 12px
16 | `;
17 |
18 | const Img = styled.img`
19 | width: 16px;
20 | height: 16px;
21 | `;
22 |
23 | const Text = styled.div`
24 | font-size: 12px;
25 | font-weight: 500;
26 | color: ${({ theme }) => theme.soft2};
27 | `;
28 |
29 | const ToolsCard = ({tool}) => {
30 | const openWebsite = () => {
31 | window.open(tool.link, "_blank");
32 | }
33 | return (
34 |
35 |
36 | {tool.name}
37 |
38 | )
39 | }
40 |
41 | export default ToolsCard
--------------------------------------------------------------------------------
/server/routes/auth.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { signup,signin, logout, googleAuthSignIn, generateOTP, verifyOTP, createResetSession,findUserByEmail, resetPassword } from "../controllers/auth.js";
3 | import { verifyToken } from "../middleware/verifyToken.js";
4 | import { localVariables } from "../middleware/auth.js";
5 |
6 | const router = express.Router();
7 |
8 | //create a user
9 | router.post("/signup", signup);
10 | //signin
11 | router.post("/signin", signin);
12 | //logout
13 | router.post("/logout", logout);
14 | //google signin
15 | router.post("/google", googleAuthSignIn);
16 | //find user by email
17 | router.get("/findbyemail", findUserByEmail);
18 | //generate opt
19 | router.get("/generateotp",localVariables, generateOTP);
20 | //verify opt
21 | router.get("/verifyotp", verifyOTP);
22 | //create reset session
23 | router.get("/createResetSession", createResetSession);
24 | //forget password
25 | router.put("/forgetpassword", resetPassword);
26 |
27 |
28 |
29 |
30 | export default router;
--------------------------------------------------------------------------------
/client/src/components/DropWrapper.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDrop } from "react-dnd";
3 | import ITEM_TYPE from "../data/types";
4 | import { statuses } from "../data/data";
5 |
6 | const DropWrapper = ({ onDrop, children, status }) => {
7 | const [{ isOver }, drop] = useDrop({
8 | accept: ITEM_TYPE,
9 | canDrop: (item, monitor) => {
10 | const itemIndex = statuses.findIndex(si => si.status === item.status);
11 | const statusIndex = statuses.findIndex(si => si.status === status);
12 | return [itemIndex + 1, itemIndex - 1, itemIndex].includes(statusIndex);
13 | },
14 | drop: (item, monitor) => {
15 | onDrop(item, monitor, status);
16 | },
17 | collect: monitor => ({
18 | isOver: monitor.isOver()
19 | })
20 | });
21 |
22 | return (
23 |
24 | {React.cloneElement(children, { isOver })}
25 |
26 | )
27 | };
28 |
29 | export default DropWrapper;
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore, combineReducers } from "@reduxjs/toolkit";
2 | import userReducer from "./userSlice";
3 | import snackbarReducer from "./snackbarSlice";
4 | import {
5 | persistStore,
6 | persistReducer,
7 | FLUSH,
8 | REHYDRATE,
9 | PAUSE,
10 | PERSIST,
11 | PURGE,
12 | REGISTER,
13 | } from "redux-persist";
14 | import storage from "redux-persist/lib/storage";
15 | import { PersistGate } from "redux-persist/integration/react";
16 |
17 | const persistConfig = {
18 | key: "root",
19 | version: 1,
20 | storage,
21 | };
22 |
23 | const rootReducer = combineReducers({ user: userReducer, snackbar: snackbarReducer });
24 |
25 | const persistedReducer = persistReducer(persistConfig, rootReducer);
26 |
27 | export const store = configureStore({
28 | reducer: persistedReducer,
29 | middleware: (getDefaultMiddleware) =>
30 | getDefaultMiddleware({
31 | serializableCheck: {
32 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
33 | },
34 | }),
35 | });
36 |
37 | export const persistor = persistStore(store)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Rishav Chanda
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 |
--------------------------------------------------------------------------------
/client/src/components/IdeaCard.jsx:
--------------------------------------------------------------------------------
1 | import { Delete } from "@mui/icons-material";
2 | import { IconButton } from "@mui/material";
3 | import React from "react";
4 | import styled from "styled-components";
5 |
6 | const Container = styled.div`
7 | display: flex;
8 | align-items: center;
9 | border-radius: 10px;
10 | gap: 12px;
11 | justify-content: space-between;
12 | `;
13 |
14 | const Span = styled.div`
15 | font-size: 13px;
16 | font-weight: 600;
17 | margin-top: 1px;
18 | color: ${({ theme }) => theme.textSoft + "99"};
19 | `;
20 |
21 | const Text = styled.div`
22 | display: flex;
23 | gap: 8px;
24 | font-size: 13px;
25 | color: ${({ theme }) => theme.textSoft + "99"};
26 | `;
27 |
28 | const IcoButton = styled(IconButton)`
29 | color: ${({ theme }) => theme.textSoft + "99"} !important;
30 | `;
31 |
32 | const IdeaCard = ({ idea, no }) => {
33 | return (
34 |
35 |
36 | {no + 1})
37 | {idea}
38 |
39 |
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default IdeaCard;
47 |
--------------------------------------------------------------------------------
/client/src/Images/google.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/server/models/Works.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const WorksSchema = new mongoose.Schema({
4 | projectId: {
5 | type: mongoose.Schema.Types.ObjectId,
6 | ref: "Projects",
7 | required: true,
8 | unique: false,
9 | },
10 | creatorId: {
11 | type: mongoose.Schema.Types.ObjectId,
12 | ref: "User",
13 | required: true,
14 | unique: false,
15 | },
16 | title: {
17 | type: String,
18 | required: true,
19 | unique: false,
20 | },
21 | desc: {
22 | type: String,
23 | required: true,
24 | unique: false,
25 | },
26 | priority: {
27 | type: String,
28 | required: true,
29 | default: "Low",
30 | },
31 | tags: {
32 | type: [String],
33 | default: [],
34 | },
35 | status: {
36 | type: String,
37 | required: true,
38 | default: "Working",
39 | },
40 | tasks: {
41 | type: [mongoose.Schema.Types.ObjectId],
42 | ref: "Tasks",
43 | default: [],
44 | }
45 | },
46 | { timestamps: true }
47 | );
48 |
49 | export default mongoose.model("Works", WorksSchema);
--------------------------------------------------------------------------------
/server/routes/teams.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { addTeam, getTeam, deleteTeam, updateTeam, addTeamProject, inviteTeamMember, verifyInvitationTeam, getTeamMembers, removeMember, updateMembers } from "../controllers/teams.js";
3 | import { verifyToken } from "../middleware/verifyToken.js";
4 | import { localVariables } from "../middleware/auth.js";
5 |
6 | const router = express.Router();
7 |
8 | //create a Team
9 | router.post("/",verifyToken, addTeam);
10 | //get all Teams
11 | router.get("/:id",verifyToken, getTeam)
12 | //delete a Team
13 | router.delete("/:id", verifyToken, deleteTeam)
14 | //update a Team
15 | router.patch("/:id", verifyToken, updateTeam)
16 | //update a team member
17 | router.patch("/member/:id", verifyToken, updateMembers)
18 | //remove a team member
19 | router.patch("/member/remove/:id", verifyToken, removeMember)
20 | //add a team project
21 | router.post("/addProject/:id", verifyToken, addTeamProject)
22 | //invite a team member
23 | router.post("/invite/:id", verifyToken,localVariables, inviteTeamMember)
24 | //verify a invite
25 | router.get("/invite/:code",verifyInvitationTeam)
26 | //get team members
27 | router.get("/members/:id", verifyToken, getTeamMembers)
28 |
29 |
30 | export default router;
--------------------------------------------------------------------------------
/server/routes/user.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import {
3 | update,
4 | deleteUser,
5 | getUser,
6 | subscribe,
7 | unsubscribe,
8 | getUserProjects,
9 | getUserTeams,
10 | findUser,
11 | findUserByEmail,
12 | getNotifications,
13 | getWorks,
14 | getTasks
15 | } from "../controllers/user.js";
16 | import { verifyToken } from "../middleware/verifyToken.js";
17 |
18 | const router = express.Router();
19 |
20 | //update user
21 | router.put("/:id", verifyToken, update);
22 |
23 | //delete user
24 | router.delete("/:id", verifyToken, deleteUser);
25 |
26 | //get a user
27 | router.get("/find/:id",verifyToken, findUser);
28 | router.get("/find",verifyToken, getUser);
29 |
30 | //get user projects
31 | router.get("/projects", verifyToken, getUserProjects);
32 |
33 | //get user teams
34 | router.get("/teams", verifyToken, getUserTeams);
35 |
36 | //search a user
37 | router.get("/search/:email",verifyToken, findUserByEmail);
38 |
39 | //get notifications of a user
40 | router.get("/notifications", verifyToken, getNotifications);
41 |
42 | //get works of a user
43 | router.get("/works", verifyToken, getWorks);
44 |
45 | //get tasks of a user
46 | router.get("/tasks", verifyToken, getTasks);
47 |
48 |
49 | export default router;
--------------------------------------------------------------------------------
/client/src/utils/Theme.js:
--------------------------------------------------------------------------------
1 | export const darkTheme = {
2 | bg:"#15171E",
3 | bgLighter:"#1C1E27",
4 | itemHover: "#3A3C45",
5 | itemText:"#DADEDF",
6 | primary: "#854CE6",
7 | bgDark:"#15171E",
8 | text:"#F2F3F4",
9 | textSoft:"#C1C7C9",
10 | soft:"#373D3F",
11 | soft2:"#8C979A",
12 | card: "#1C1E27",
13 | lightAdd:"99",
14 | yellow: "#fccf03",
15 | green: "#03fc45",
16 | pink: "#fc036b",
17 | recieve_message: "#1a1826",
18 | chat_background: "#100f17",
19 | send_message: "#4f34ad",
20 | contact_background: "#151a21",
21 |
22 | /*Colors
23 | Swatch 1
24 | #030405
25 | Swatch 2
26 | #585759
27 | Swatch 3
28 | #03840c
29 | Swatch 4
30 | #acb0b3
31 | Swatch 5
32 | #185c1a
33 | Swatch 6
34 | #40b957*/
35 | }
36 |
37 | export const lightTheme = {
38 | bg:"#F8F9FD",
39 | bgLighter:"#FFFFFF",
40 | itemHover: "#F7F5F4",
41 | itemText:"#555F61",
42 | primary: "#854CE6",
43 | bgDark:"white",
44 | text:"#2F2F2F",
45 | textSoft:"#606060",
46 | soft:"#C4C4C4",
47 | soft2:"#757575",
48 | card: "#FFFFFF",
49 | lightAdd:"",
50 | recieve_message: "#ffffff",
51 | chat_background: "#f5f5fc",
52 | send_message: "#4f34ad",
53 | send_message_text_color: "#ffffff",
54 | contact_background: "#ffffff",
55 |
56 | }
--------------------------------------------------------------------------------
/server/routes/project.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import { addProject, deleteProject, getProject, updateProject,removeMember, inviteProjectMember, verifyInvitation, getProjectMembers, addWork, getWorks, updateMembers } from "../controllers/project.js";
3 | import { verifyToken } from "../middleware/verifyToken.js";
4 | import { localVariables } from "../middleware/auth.js";
5 |
6 | const router = express.Router();
7 |
8 | //create a project
9 | router.post("/", verifyToken, addProject);
10 | //get all projects
11 | router.get("/:id", verifyToken, getProject)
12 | //delete a project
13 | router.delete("/:id", verifyToken, deleteProject)
14 | //update a project
15 | router.patch("/:id", verifyToken, updateProject)
16 | //update a project member
17 | router.patch("/member/:id", verifyToken, updateMembers)
18 | //remove a project member
19 | router.patch("/member/remove/:id", verifyToken, removeMember)
20 |
21 | //invite a project
22 | router.post("/invite/:id", verifyToken, localVariables, inviteProjectMember)
23 | //verify a invite
24 | router.get("/invite/:code", verifyInvitation)
25 | //get members
26 | router.get("/members/:id",verifyToken, getProjectMembers)
27 |
28 | //works
29 | // add works to a project
30 | router.post("/works/:id", verifyToken, addWork)
31 | //get all works of a project
32 | router.get("/works/:id", verifyToken, getWorks)
33 |
34 |
35 | export default router;
--------------------------------------------------------------------------------
/client/src/redux/userSlice.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { useNavigate } from 'react-router-dom'
3 |
4 | const initialState = {
5 | currentUser: null,
6 | loading: false,
7 | error: false,
8 | };
9 |
10 | export const userSlice = createSlice({
11 | name: "user",
12 | initialState,
13 | reducers: {
14 | loginStart: (state) => {
15 | state.loading = true;
16 | },
17 | loginSuccess: (state, action) => {
18 | state.loading = false;
19 | state.currentUser = action.payload.user;
20 | localStorage.setItem('token', action.payload.token);
21 | },
22 | loginFailure: (state) => {
23 | state.loading = false;
24 | state.error = true;
25 | },
26 | logout: (state) => {
27 | state.currentUser = null;
28 | state.loading = false;
29 | state.error = false;
30 | localStorage.removeItem('token');
31 | },
32 | verified: (state, action) => {
33 | if(state.currentUser){
34 | state.currentUser.verified = action.payload;
35 | }
36 | },
37 | subscription: (state, action) => {
38 | if (state.currentUser.subscribedUsers.includes(action.payload)) {
39 | state.currentUser.subscribedUsers.splice(
40 | state.currentUser.subscribedUsers.findIndex(
41 | (channelId) => channelId === action.payload
42 | ),
43 | 1
44 | );
45 | } else {
46 | state.currentUser.subscribedUsers.push(action.payload);
47 | }
48 | },
49 | },
50 | });
51 |
52 | export const { loginStart, loginSuccess, loginFailure, logout, subscription,verified } =
53 | userSlice.actions;
54 |
55 | export default userSlice.reducer;
--------------------------------------------------------------------------------
/client/src/pages/Home/components/Faq.jsx:
--------------------------------------------------------------------------------
1 | import styled from "styled-components";
2 |
3 | const FaqContainer = styled.section`
4 | background-color: #060B27;
5 | color: #fff;
6 | padding: 80px 0;
7 | `;
8 |
9 | const FaqTitle = styled.h2`
10 | text-align: center;
11 | font-size: 36px;
12 | margin-bottom: 40px;
13 | `;
14 |
15 | const FaqList = styled.ul`
16 | list-style: none;
17 | margin: 0;
18 | padding: 0;
19 | `;
20 |
21 | const FaqItem = styled.li`
22 | margin-bottom: 20px;
23 | `;
24 |
25 | const FaqQuestion = styled.h4`
26 | font-size: 24px;
27 | margin-bottom: 10px;
28 | `;
29 |
30 | const FaqAnswer = styled.p`
31 | font-size: 18px;
32 | `;
33 |
34 | const Faq = () => {
35 | const faqData = [
36 | {
37 | question: "Is my data secure?",
38 | answer:
39 | "Yes, we take security very seriously and use the latest encryption technology to protect your data.",
40 | },
41 | {
42 | question: "How much does the app cost?",
43 | answer:
44 | "Our app has a variety of pricing plans to fit any budget, starting at just $9.99/month.",
45 | },
46 | {
47 | question: "What kind of support is available?",
48 | answer:
49 | "We offer 24/7 support via email and live chat to ensure you get the help you need.",
50 | },
51 | ];
52 |
53 | return (
54 |
55 | Frequently Asked Questions
56 |
57 | {faqData.map((faq, index) => (
58 |
59 | {faq.question}
60 | {faq.answer}
61 |
62 | ))}
63 |
64 |
65 | );
66 | };
67 |
68 | export default Faq;
69 |
--------------------------------------------------------------------------------
/server/models/User.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import jwt from "jsonwebtoken";
3 |
4 | const UserSchema = new mongoose.Schema({
5 | name: {
6 | type: String,
7 | required: true,
8 | unique: false,
9 | },
10 | email: {
11 | type: String,
12 | required: true,
13 | unique: true,
14 | },
15 | password: {
16 | type: String,
17 | default: "",
18 | },
19 | img: {
20 | type: String,
21 | default: "",
22 | },
23 | googleSignIn:{
24 | type: Boolean,
25 | required: true,
26 | default: false,
27 | },
28 | projects: {
29 | type: [mongoose.Schema.Types.ObjectId],
30 | ref: "Project",
31 | default: [],
32 | },
33 | teams: {
34 | type: [mongoose.Schema.Types.ObjectId],
35 | ref: "Teams",
36 | default: [],
37 | },
38 | notifications: {
39 | type: [mongoose.Schema.Types.ObjectId],
40 | ref: "Notifications",
41 | default: [],
42 | },
43 | works: {
44 | type: [mongoose.Schema.Types.ObjectId],
45 | ref: "Works",
46 | default: [],
47 | },
48 | tasks: {
49 | type: [mongoose.Schema.Types.ObjectId],
50 | ref: "Tasks",
51 | default: [],
52 | }
53 | },
54 | { timestamps: true }
55 | );
56 |
57 | UserSchema.methods.generateVerificationToken = function () {
58 | const user = this;
59 | const verificationToken = jwt.sign(
60 | { ID: user._id },
61 | process.env.USER_VERIFICATION_TOKEN_SECRET,
62 | { expiresIn: "7d" }
63 | );
64 | return verificationToken;
65 | };
66 |
67 | export default mongoose.model("User", UserSchema);
--------------------------------------------------------------------------------
/server/models/Teams.js:
--------------------------------------------------------------------------------
1 | import mongoose, { Mongoose } from "mongoose";
2 |
3 | const TeamSchema = new mongoose.Schema({
4 | name: {
5 | type: String,
6 | required: true,
7 | unique: false,
8 | },
9 | desc: {
10 | type: String,
11 | required: true,
12 | unique: false,
13 | },
14 | img: {
15 | type: String,
16 | default: "",
17 | unique: false,
18 | },
19 | tools: {
20 | type: [{
21 | _id: false,
22 | link: {
23 | type: String,
24 | required: true,
25 | },
26 | name: {
27 | type: String,
28 | required: true,
29 | },
30 | icon: {
31 | type: String,
32 | require: true,
33 | }
34 | }],
35 | default: [],
36 | },
37 | members: {
38 | type: [{
39 | _id: false,
40 | id: {
41 | type: mongoose.Schema.Types.ObjectId,
42 | ref: "User",
43 | required: true,
44 | },
45 | role: {
46 | type: String,
47 | required: true,
48 | },
49 | access: {
50 | type: String,
51 | require: true,
52 | default: "View Only",
53 | unique: false,
54 | }
55 | }],
56 | required: true,
57 | default: [],
58 | },
59 | projects: {
60 | type: [mongoose.Schema.Types.ObjectId],
61 | default: [],
62 | ref: "Project",
63 | unique: true
64 | }
65 | },
66 | { timestamps: true }
67 | );
68 |
69 | export default mongoose.model("Teams", TeamSchema);
--------------------------------------------------------------------------------
/client/src/pages/Home/components/Testimonials.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | const TestimonialsContainer = styled.div`
5 | background-color: #060B27;
6 | padding: 50px 0;
7 | `;
8 |
9 | const TestimonialCard = styled.div`
10 | background-color: #1C1E27;
11 | border-radius: 20px;
12 | box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.2);
13 | padding: 30px;
14 | text-align: center;
15 | margin: 0 20px;
16 | width: 300px;
17 | `;
18 |
19 | const TestimonialText = styled.p`
20 | font-size: 18px;
21 | line-height: 1.5;
22 | color: #fff;
23 | `;
24 |
25 | const TestimonialAuthor = styled.p`
26 | font-size: 16px;
27 | font-weight: bold;
28 | margin-top: 20px;
29 | color: #854CE6;
30 | `;
31 |
32 | const Testimonials = () => {
33 | const testimonialsData = [{ id: 1, text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed auctor justo quis felis malesuada, a feugiat elit tristique.', author: 'John Doe', jobTitle: 'CEO' }, { id: 2, text: 'Donec laoreet elit in malesuada tempus. Aliquam pretium blandit commodo. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.', author: 'Jane Smith', jobTitle: 'Marketing Manager' }, { id: 3, text: 'Fusce eget turpis eget nulla fringilla auctor. Praesent eget mi nec quam dictum consequat nec eu nibh.', author: 'David Lee', jobTitle: 'Software Engineer' }];
34 |
35 | return (
36 |
37 | Testimonials
38 |
39 | {testimonialsData.map(testimonial => (
40 |
41 | "{testimonial.text}"
42 | {testimonial.author}, {testimonial.jobTitle}
43 |
44 | ))}
45 |
46 |
47 | );
48 | };
49 |
50 | export default Testimonials;
51 |
--------------------------------------------------------------------------------
/server/models/Project.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | const ProjectSchema = new mongoose.Schema({
4 | title: {
5 | type: String,
6 | required: true,
7 | unique: false,
8 | },
9 | desc: {
10 | type: String,
11 | required: true,
12 | unique: false,
13 | },
14 | img: {
15 | type: String,
16 | default: "",
17 | unique: false,
18 | },
19 | tags: {
20 | type: [String],
21 | default: [],
22 | },
23 | status: {
24 | type: String,
25 | required: true,
26 | default: "Working",
27 | },
28 | works: {
29 | type: [String],
30 | default: []
31 | },
32 | tools: {
33 | type: [{
34 | _id: false,
35 | link: {
36 | type: String,
37 | required: true,
38 | },
39 | name: {
40 | type: String,
41 | required: true,
42 | },
43 | icon: {
44 | type: String,
45 | require: true,
46 | }
47 | }],
48 | default: [],
49 | },
50 | members: {
51 | type: [{
52 | _id: false,
53 | id: {
54 | type: mongoose.Schema.Types.ObjectId,
55 | ref: "User",
56 | required: true,
57 | },
58 | role: {
59 | type: String,
60 | required: true,
61 | },
62 | access: {
63 | type: String,
64 | require: true,
65 | default: "View Only",
66 | unique: false,
67 | }
68 | }],
69 | required: true,
70 | default: [],
71 | },
72 | },
73 | { timestamps: true }
74 | );
75 |
76 | export default mongoose.model("Project", ProjectSchema);
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | import express from 'express';
2 | import dotenv from 'dotenv';
3 | import mongoose from 'mongoose';
4 | import authRoutes from './routes/auth.js';
5 | import userRoutes from './routes/user.js';
6 | import projectRoutes from './routes/project.js';
7 | import teamRoutes from './routes/teams.js';
8 | import cookieParser from "cookie-parser";
9 | import cors from 'cors';
10 | import morgan from 'morgan';
11 | const app = express();
12 | dotenv.config();
13 |
14 | /** Middlewares */
15 | app.use(express.json());
16 | const corsConfig = {
17 | credentials: true,
18 | origin: true,
19 | };
20 | app.use(cors(corsConfig));
21 | app.use(morgan('tiny'));
22 | app.disable('x-powered-by');
23 |
24 | const port = process.env.PORT || 8700;
25 |
26 | const connect = () => {
27 | mongoose.set('strictQuery', true);
28 | mongoose.connect(process.env.MONGO_URL).then(() => {
29 | console.log('MongoDB connected');
30 | }).catch((err) => {
31 | console.log(err);
32 | });
33 | };
34 |
35 |
36 | app.use(express.json())
37 | // app.enable('trust proxy'); // optional, not needed for secure cookies
38 | // app.use(express.session({
39 | // secret : '123456',
40 | // key : 'sid',
41 | // proxy : true, // add this when behind a reverse proxy, if you need secure cookies
42 | // cookie : {
43 | // secure : true,
44 | // maxAge: 5184000000 // 2 months
45 | // }
46 | // }));
47 | app.use("/api/auth", authRoutes)
48 | app.use("/api/users", userRoutes)
49 | app.use("/api/project", projectRoutes)
50 | app.use("/api/team", teamRoutes)
51 | app.use((err, req, res, next)=>{
52 | const status = err.status || 500;
53 | const message = err.message || "Something went wrong";
54 | return res.status(status).json({
55 | success: false,
56 | status,
57 | message
58 | })
59 | })
60 |
61 | app.listen(port,()=>{
62 | console.log("Connected")
63 | connect();
64 | })
65 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.10.4",
7 | "@emotion/styled": "^11.10.4",
8 | "@fortawesome/fontawesome-free": "^6.4.0",
9 | "@mui/icons-material": "^5.10.6",
10 | "@mui/material": "^5.10.8",
11 | "@mui/x-date-pickers": "^5.0.13",
12 | "@mui/x-date-pickers-pro": "^5.0.13",
13 | "@react-oauth/google": "^0.9.0",
14 | "@reduxjs/toolkit": "^1.9.0",
15 | "@testing-library/jest-dom": "^5.16.5",
16 | "@testing-library/react": "^13.4.0",
17 | "@testing-library/user-event": "^13.5.0",
18 | "axios": "^1.1.3",
19 | "dotenv": "^10.0.0",
20 | "firebase": "^9.13.0",
21 | "gh-pages": "^4.0.0",
22 | "jwt-decode": "^3.1.2",
23 | "react": "^18.2.0",
24 | "react-cookie": "^4.1.1",
25 | "react-dnd": "^16.0.1",
26 | "react-dnd-html5-backend": "^16.0.1",
27 | "react-dom": "^18.2.0",
28 | "react-file-image-to-base64": "^1.0.1",
29 | "react-otp-input": "^3.0.0",
30 | "react-redux": "^8.0.5",
31 | "react-responsive-masonry": "^2.1.7",
32 | "react-router-dom": "^6.4.2",
33 | "react-scripts": "5.0.1",
34 | "redux": "^4.2.0",
35 | "redux-persist": "^6.0.0",
36 | "styled-components": "^5.3.6",
37 | "timeago.js": "^4.0.2",
38 | "validator": "^13.7.0",
39 | "web-vitals": "^2.1.4"
40 | },
41 | "scripts": {
42 | "predeploy": "npm run build",
43 | "deploy": "gh-pages -d build",
44 | "start": "react-scripts start",
45 | "build": "react-scripts build",
46 | "test": "react-scripts test",
47 | "eject": "react-scripts eject"
48 | },
49 | "eslintConfig": {
50 | "extends": [
51 | "react-app",
52 | "react-app/jest"
53 | ]
54 | },
55 | "browserslist": {
56 | "production": [
57 | ">0.2%",
58 | "not dead",
59 | "not op_mini all"
60 | ],
61 | "development": [
62 | "last 1 chrome version",
63 | "last 1 firefox version",
64 | "last 1 safari version"
65 | ]
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/client/src/components/ProjectInvite.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useDispatch } from 'react-redux';
3 | import {
4 | useParams,
5 | useLocation
6 | } from "react-router-dom";
7 | import { useNavigate } from 'react-router-dom'
8 | import { useSelector } from "react-redux";
9 | import { verifyProjectInvite } from '../api';
10 | import { openSnackbar } from "../redux/snackbarSlice";
11 | import styled from 'styled-components';
12 | import { CircularProgress } from '@mui/material';
13 |
14 | const Joining = styled.div`
15 | display: flex;
16 | justify-content: center;
17 | align-items: center;
18 | height: 100vh;
19 | font-size: 2rem;
20 | `;
21 |
22 | const ProjectInvite = () => {
23 | const navigate = useNavigate();
24 | const dispatch = useDispatch();
25 | function useQuery() {
26 | const { search } = useLocation();
27 |
28 | return React.useMemo(() => new URLSearchParams(search), [search]);
29 | }
30 | let query = useQuery();
31 |
32 | const { code } = useParams();
33 | const projectid = query.get("projectid");
34 | const userid = query.get("userid");
35 | const access = query.get("access");
36 | const role = query.get("role");
37 |
38 |
39 | const { currentUser } = useSelector((state) => state.user);
40 | useEffect(() => {
41 | verifyProjectInvite(code, projectid, userid, access, role).then((res) => {
42 | console.log(res);
43 | if (res.status === 200) {
44 | dispatch(openSnackbar({ message: res.data.Message, type: "success" }));
45 | //navigate to project page
46 | if (currentUser)
47 | navigate(`/projects/${projectid}`);
48 | else
49 | navigate(`/`);
50 |
51 | }
52 | else {
53 | navigate(`/`);
54 | }
55 |
56 | }
57 | ).catch((err) => {
58 | console.log(err);
59 | navigate(`/`);
60 | }
61 | )
62 | }, [projectid, userid, access, role]);
63 | return (
64 |
65 |
66 |
67 | )
68 | }
69 |
70 | export default ProjectInvite
--------------------------------------------------------------------------------
/client/src/components/MemberCard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { tagColors } from "../data/data";
4 | import { Avatar } from "@mui/material";
5 |
6 | const Container = styled.div`
7 | padding: 6px 4px;
8 | text-align: left;
9 | margin: 1px 0px;
10 | font-weight: 500;
11 | cursor: pointer;
12 | display: flex;
13 | align-items: center;
14 | justify-content: space-between;
15 | `;
16 |
17 | const Wrapper = styled.div`
18 | display: flex;
19 | align-items: center;
20 | gap: 12px;
21 | `;
22 |
23 | const Details = styled.div`
24 | gap: 4px;
25 | `;
26 |
27 | const Name = styled.div`
28 | font-size: 13px;
29 | font-weight: 500;
30 | color: ${({ theme }) => theme.textSoft};
31 | `;
32 |
33 | const EmailId = styled.div`
34 | font-size: 10px;
35 | font-weight: 400;
36 | color: ${({ theme }) => theme.textSoft + "99"};
37 | `;
38 |
39 | const Role = styled.div`
40 | font-size: 10px;
41 | font-weight: 500;
42 | padding: 4px 8px;
43 | border-radius: 12px;
44 | color: ${({ tagColor, theme }) => tagColor + theme.lightAdd};
45 | background-color: ${({ tagColor, theme }) => tagColor + "10"};
46 | `;
47 |
48 | const Access = styled.div`
49 | font-size: 10px;
50 | font-weight: 500;
51 | color: ${({ theme }) => theme.soft2};
52 | padding: 4px 8px;
53 | border-radius: 12px;
54 | background-color: ${({ theme }) => theme.soft2 + "33"};
55 | `;
56 |
57 | const MemberCard = ({ member }) => {
58 | return (
59 |
60 |
61 | {member.id.name.charAt(0)}
62 |
63 | {member.id.name}
64 | {member.id.email}
65 |
66 |
69 | {member.role}
70 |
71 |
72 | {member.access}
73 |
74 | );
75 | };
76 |
77 | export default MemberCard;
78 |
--------------------------------------------------------------------------------
/client/src/pages/Home/components/TeamMember.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | const TeamMemberContainer = styled.div`
5 | width: 350px;
6 | display: flex;
7 | flex-direction: column;
8 | background-color: hsl(250, 24%, 9%);
9 | box-shadow: RGB(252, 3, 111, 0.15) 0px 4px 24px;
10 | border: 1px solid RGB(252, 3, 111,30%);
11 | padding: 24px 24px;
12 | border-radius: 12px;
13 | gap: 8px;
14 | cursor: pointer;
15 | @media (max-width: 925px) {
16 | width: 300px;
17 | }
18 | `;
19 |
20 | const Header = styled.div`
21 | display: flex;
22 | flex-direction: row;
23 | align-items: center;
24 | gap: 12px;
25 | `;
26 |
27 | const TeamMemberData = styled.div`
28 | display: flex;
29 | flex-direction: column;
30 | align-items: start;
31 | justify-content: start;
32 | gap: 4px;
33 | `;
34 |
35 | const TeamMemberPhoto = styled.img`
36 | width: 60px;
37 | height: 60px;
38 | border-radius: 50%;
39 | border: 2px solid #282D45;
40 | object-fit: cover;
41 | `;
42 |
43 | const TeamMemberName = styled.div`
44 | font-size: 18px;
45 | font-weight: bold;
46 | color: ${({ theme }) => theme.text};
47 | `;
48 |
49 | const TeamMemberTitle = styled.div`
50 | font-size: 14px;
51 | font-weight: 500;
52 | color: ${({ theme }) => theme.soft2 + '99'};
53 | `;
54 |
55 | const TeamMemberBio = styled.p`
56 | font-size: 16px;
57 | line-height: 1.5;
58 | color: ${({ theme }) => theme.soft2};
59 | `;
60 |
61 | const TeamMember = ({ photo, name, title, bio }) => {
62 | return (
63 |
64 |
65 |
66 |
67 | {name}
68 | {title}
69 |
70 |
71 | {bio}
72 |
73 | );
74 | };
75 |
76 | export default TeamMember;
77 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 |
28 |
29 | VEXA
30 |
31 |
32 |
33 |
34 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/client/src/components/TeamInvite.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import { useDispatch } from 'react-redux';
3 | import {
4 | useParams,
5 | useLocation
6 | } from "react-router-dom";
7 | import { useNavigate } from 'react-router-dom'
8 | import { useSelector } from "react-redux";
9 | import { verifyTeamInvite } from '../api';
10 | import { openSnackbar } from "../redux/snackbarSlice";
11 | import styled from 'styled-components';
12 | import { CircularProgress } from '@mui/material';
13 |
14 | const Joining = styled.div`
15 | display: flex;
16 | justify-content: center;
17 | align-items: center;
18 | height: 100vh;
19 | font-size: 2rem;
20 | `;
21 |
22 | const TeamInvite = () => {
23 |
24 | const dispatch = useDispatch();
25 | const navigate = useNavigate();
26 | function useQuery() {
27 | const { search } = useLocation();
28 |
29 | return React.useMemo(() => new URLSearchParams(search), [search]);
30 | }
31 |
32 | let query = useQuery();
33 |
34 | const { code } = useParams();
35 | const teamid = query.get("teamid");
36 | const userid = query.get("userid");
37 | const access = query.get("access");
38 | const role = query.get("role");
39 |
40 | const { currentUser } = useSelector((state) => state.user);
41 |
42 | useEffect(() => {
43 | verifyTeamInvite(code, teamid, userid, access, role).then((res) => {
44 | console.log(res);
45 | if (res.status === 200) {
46 | dispatch(openSnackbar({ message: res.data.Message, type: "success" }));
47 | //navigate to project page
48 | if (currentUser)
49 | navigate(`/teams/${teamid}`);
50 | else
51 | navigate(`/`);
52 | }
53 | else {
54 | navigate(`/`);
55 | }
56 | }
57 | ).catch((err) => {
58 | console.log(err);
59 | navigate(`/`);
60 | }
61 | )
62 | }, [teamid, userid, access, role]);
63 |
64 | return (
65 |
66 |
67 |
68 | )
69 | }
70 |
71 | export default TeamInvite
--------------------------------------------------------------------------------
/client/src/pages/Home/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import AccountCircleOutlinedIcon from "@mui/icons-material/AccountCircleOutlined";
4 |
5 | const Container = styled.div`
6 | width: 90%;
7 | max-width: 1320px;
8 | height: 60px;
9 | margin: 12px 14px;
10 | display: flex;
11 | align-items: center;
12 | justify-content: space-between;
13 | @media (max-width: 768px) {
14 | padding: 0px 20px !important;
15 | }
16 | `;
17 | const Logo = styled.h1`
18 | font-weight: 600;
19 | font-size: 20px;
20 | color: ${({ theme }) => theme.primary};
21 | `;
22 |
23 | const Menu = styled.ul`
24 | display: flex;
25 | align-items: center;
26 | gap: 20px;
27 | list-style: none;
28 | @media (max-width: 768px) {
29 | display: none;
30 | }
31 | `;
32 |
33 | const MenuItem = styled.a`
34 | font-size: 16px;
35 | text-decoration: none;
36 | font-weight: 500;
37 | color: ${({ theme }) => theme.text};
38 | cursor: pointer;
39 | transition: all 0.3s ease;
40 | &:hover {
41 | color: ${({ theme }) => theme.primary};
42 | }
43 | `;
44 |
45 |
46 |
47 | const Button = styled.button`
48 | padding: 5px 18px;
49 | background-color: transparent;
50 | border: 1px solid ${({ theme }) => theme.primary};
51 | color: ${({ theme }) => theme.primary};
52 | border-radius: 3px;
53 | font-weight: 500;
54 | cursor: pointer;
55 | display: flex;
56 | align-items: center;
57 | gap: 5px;
58 | font-size: 15px;
59 | border-radius: 100px;
60 | transition: all 0.3s ease;
61 | &:hover {
62 | background-color: ${({ theme }) => theme.primary};
63 | color: ${({ theme }) => theme.text};
64 | }
65 | `;
66 | const Navbar = ({ setSignInOpen }) => {
67 |
68 | return (
69 |
70 | VEXA
71 |
77 |
80 |
81 |
82 | )
83 | }
84 |
85 | export default Navbar
--------------------------------------------------------------------------------
/client/src/components/ImageSelector.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components';
3 | import { useState, useEffect } from 'react';
4 | import ReactImageFileToBase64 from "react-file-image-to-base64";
5 | import CloudUploadIcon from '@mui/icons-material/CloudUpload';
6 |
7 | const Container = styled.div`
8 | height: 120px;
9 | display: flex;
10 | flex-direction: column;
11 | justify-content: center;
12 | gap: 6px;
13 | align-items: center;
14 | border: 2px dashed ${({ theme }) => theme.soft2+ "80"}};
15 | border-radius: 12px;
16 | color: ${({ theme }) => theme.soft2+ "80"};
17 | margin: 30px 20px 0px 20px;
18 | `;
19 |
20 | const Typo = styled.div`
21 | font-size: 14px;
22 | font-weight: 600;
23 | `;
24 |
25 | const TextBtn = styled.div`
26 | font-size: 14px;
27 | font-weight: 600;
28 | color: ${({ theme }) => theme.primary};
29 | cursor: pointer;
30 | `;
31 |
32 | const Img = styled.img`
33 | height: 120px !important;
34 | width: 100%;
35 | object-fit: cover;
36 | border-radius: 12px;
37 | `;
38 |
39 | const ImageSelector = ({ inputs, setInputs }) => {
40 | const handleOnCompleted = files => {
41 | console.log(files[0].base64_file);
42 |
43 | setInputs((prev) => {
44 | return { ...prev, img: files[0].base64_file };
45 | });
46 | console.log(inputs);
47 | };
48 |
49 | const CustomisedButton = ({ triggerInput }) => {
50 | return (
51 |
52 | Browse Image
53 |
54 | );
55 | };
56 | return (
57 |
58 | {inputs.img!=="" ?
: <>
59 |
60 | Drag & Drop Image here
61 |
62 | or
63 |
68 |
69 | >}
70 |
71 | )
72 | }
73 |
74 | export default ImageSelector
--------------------------------------------------------------------------------
/client/src/pages/Home/components/Team.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import TeamMember from "./TeamMember";
4 |
5 | const TeamWrapper = styled.div`padding: 40px 0;
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | padding: 40px 0px 100px 0px;
10 | background-image: linear-gradient(90deg, rgba(0,70,209,0.03) 0%, rgba(57,14,61,0.1) 100%);
11 | clip-path: polygon(0 0, 100% 0, 100% 100%,50% 95%, 0 100%);
12 | `;
13 |
14 | const Title = styled.div`
15 | font-size: 52px;
16 | font-weight: 800;
17 | text-align: center;
18 | @media (max-width: 768px) {
19 | margin-top: 12px;
20 | font-size: 36px;
21 | }
22 | color: ${({ theme }) => theme.text};
23 | `;
24 |
25 | const Description = styled.p`
26 | font-size: 20px;
27 | line-height: 1.5;
28 | font-weight:600px;
29 | width: 100%;
30 | max-width: 700px;
31 | text-align: center;
32 | color: hsl(246, 6%, 65%);
33 | margin-bottom: 80px;
34 | @media (max-width: 768px) {
35 | width: 90%;
36 | font-size: 16px;
37 | margin-bottom: 60px;
38 | }
39 | `;
40 |
41 |
42 | const TeamContainer = styled.div`
43 | display: flex;
44 | justify-content: center;
45 | flex-wrap: wrap;
46 | width: 100%;
47 | gap: 20px;
48 | max-width: 1200px;
49 | @media (max-width: 768px) {
50 | flex-direction: column;
51 | align-items: center;
52 | }
53 | `;
54 |
55 | const Team = () => {
56 | const member = [{
57 | photo: "https://avatars.githubusercontent.com/u/64485885?v=4",
58 | name: "Rishav Chanda",
59 | title: "Full-Stack Developer",
60 | bio: "I have expertise in full-stack web development, Android app development, and MERN stack development. I am knowledgeable in various programming languages, frameworks, and technologies and strive to create high-quality, user-friendly applications."
61 | }]
62 | return (
63 |
64 | Meet the crew
65 | We're a small, remote team working on interesting problems at the edge of compute.
66 |
67 | {member.map((member, index) => (
68 |
69 | ))}
70 |
71 |
72 |
73 | );
74 | };
75 |
76 | export default Team;
77 |
--------------------------------------------------------------------------------
/client/src/pages/Home/components/Hero.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import DemoImage from "../../../Images/AddProject.gif"
4 | import HeaderImage from "../../../Images/Header.png"
5 |
6 | const Container = styled.div`
7 | height: 80vh;
8 | margin: 6px 14px;
9 | max-width: 1320px;
10 | display: flex;
11 | justify-content: space-between;
12 | align-items: center;
13 | gap: 80px;
14 | padding: 20px;
15 | @media (max-width: 768px) {
16 | flex-direction: column;
17 | gap: 20px;
18 | padding: 20px 12px 30px 12px!important;
19 | height: 70vh;
20 | }
21 | `
22 | const Left = styled.div`
23 | flex: 1;
24 | display: flex;
25 | flex-direction: column;
26 | gap: 12px;
27 | `
28 |
29 | const TitleTag = styled.div`
30 | font-size: 58px;
31 | @media (max-width: 768px) {
32 | font-size: 40px;
33 | }
34 | font-weight: 800;
35 | color: ${({ theme }) => theme.text};
36 | `
37 | const DescriptiveTag = styled.p`
38 | font-size: 17px;
39 | @media (max-width: 768px) {
40 | font-size: 15px;
41 | }
42 | font-weight: 300;
43 | margin-bottom: 32px;
44 | line-height: 1.5;
45 | color: ${({ theme }) => theme.soft2};
46 | `
47 | const Button = styled.button`
48 | width: 50%;
49 | padding: 16px 20px;
50 | font-size: 20px;
51 | font-weight: 600;
52 | background: linear-gradient(76.35deg, #801AE6 15.89%, #A21AE6 89.75%);
53 | color: ${({ theme }) => theme.text};
54 | border: none;
55 | border-radius: 10px;
56 | cursor: pointer;
57 | @media (max-width: 1250px) {
58 | width: 80%;
59 | }
60 | @media (max-width: 600px) {
61 | width: 100%;
62 | font-size: 16px;
63 | }
64 | `
65 | const Image = styled.img`
66 | width: 500px;
67 | height: 500px;
68 | flex: 0.8;
69 | display: flex;
70 | object-fit: scale-down;
71 | border-radius: 10px;
72 | @media (max-width: 1000px) {
73 | display: none;
74 | }
75 | `
76 |
77 | const Hero = ({ setSignInOpen }) => {
78 | return (
79 |
80 |
81 | Power Your Projects with Our App.
82 | Take control of your projects and stay on top of your goals with our intuitive project management app. Say goodbye to chaos and hello to streamlined efficiency. Try it now and experience the difference.
83 |
84 |
85 |
86 |
87 | )
88 | }
89 |
90 | export default Hero
--------------------------------------------------------------------------------
/client/src/components/NotificationDialog.jsx:
--------------------------------------------------------------------------------
1 | import { Avatar, Popover } from "@mui/material";
2 | import React from "react";
3 | import styled from "styled-components";
4 | import { useDispatch } from "react-redux";
5 | import { logout } from "../redux/userSlice";
6 |
7 | const Wrapper = styled.div`
8 | width: 100%;
9 | min-width: 300px;
10 | max-width: 400px;
11 | height: 500px;
12 | display: flex;
13 | flex-direction: column;
14 | position: relative;
15 | padding: 6px 2px;
16 | background-color: ${({ theme }) => theme.card};
17 | `;
18 |
19 | const Heading = styled.div`
20 | font-size: 22px;
21 | font-weight: 500;
22 | color: ${({ theme }) => theme.text};
23 | margin: 4px 0px 12px 12px;
24 | `;
25 |
26 | const Item = styled.div`
27 | display: flex;
28 | gap: 10px;
29 | padding: 4px 12px 0px 12px;
30 | `;
31 |
32 | const Details = styled.div`
33 | width: 100%;
34 | display: flex;
35 | flex-direction: column;
36 | gap: 4px;
37 | padding: 0px 0px 0px 0px;
38 | `;
39 |
40 | const Title = styled.div`
41 | font-size: 14px;
42 | font-weight: 500;
43 | color: ${({ theme }) => theme.textSoft};
44 | `;
45 |
46 | const Desc = styled.div`
47 | font-size: 12px;
48 | font-weight: 400;
49 | color: ${({ theme }) => theme.textSoft + "99"};
50 | `;
51 |
52 | const Hr = styled.hr`
53 | background-color: ${({ theme }) => theme.soft + "99"};
54 | border: none;
55 | width: 100%;
56 | height: 1px;
57 | margin-top: 4px;
58 | `;
59 |
60 | const NotificationDialog = ({
61 | open,
62 | id,
63 | anchorEl,
64 | handleClose,
65 | currentUser,
66 | notification,
67 | }) => {
68 | return (
69 |
80 |
81 | Notifications
82 |
83 | {notification.map((item) => (
84 | -
85 |
89 | {currentUser.name.charAt(0)}
90 |
91 |
92 | {item.type} invitation
93 | {item.message}
94 |
95 |
96 |
97 | ))}
98 |
99 |
100 |
101 | );
102 | };
103 |
104 | export default NotificationDialog;
105 |
--------------------------------------------------------------------------------
/client/src/pages/Home/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import FacebookIcon from '@mui/icons-material/Facebook';
3 | import TwitterIcon from '@mui/icons-material/Twitter';
4 | import LinkedInIcon from '@mui/icons-material/LinkedIn';
5 | import InstagramIcon from '@mui/icons-material/Instagram';
6 |
7 | const FooterContainer = styled.footer`
8 | width: 100%;
9 | max-width: 1200px;
10 | display: flex;
11 | flex-direction: column;
12 | gap: 14px;
13 | align-items: center;
14 | padding: 1rem;
15 | color: ${({ theme }) => theme.text};
16 | `;
17 |
18 | const Logo = styled.h1`
19 | font-weight: 600;
20 | font-size: 20px;
21 | color: ${({ theme }) => theme.primary};
22 | `;
23 |
24 | const Nav = styled.nav`
25 | width: 100%;
26 | max-width: 800px;
27 | margin-top: 0.5rem;
28 | display: flex;
29 | flex-direction: row;
30 | gap: 2rem;
31 | justify-content: center;
32 | @media (max-width: 768px) {
33 | flex-wrap: wrap;
34 | gap: 1rem;
35 | justify-content: center;
36 | text-align: center;
37 | font-size: 12px;
38 | }
39 | `;
40 |
41 | const NavLink = styled.a`
42 | color: ${({ theme }) => theme.text};
43 | text-decoration: none;
44 | font-size: 1.2rem;
45 | transition: color 0.2s ease-in-out;
46 | &:hover {
47 | color: ${({ theme }) => theme.primary};
48 | }
49 | `;
50 |
51 | const SocialMediaIcons = styled.div`
52 | display: flex;
53 | margin-top: 1rem;
54 | `;
55 |
56 | const SocialMediaIcon = styled.a`
57 | display: inline-block;
58 | margin: 0 1rem;
59 | font-size: 1.5rem;
60 | color: ${({ theme }) => theme.text};
61 | transition: color 0.2s ease-in-out;
62 | &:hover {
63 | color: ${({ theme }) => theme.primary};
64 | }
65 | `;
66 |
67 | const Copyright = styled.p`
68 | margin-top: 1.5rem;
69 | font-size: 0.9rem;
70 | color: ${({ theme }) => theme.soft2};
71 | text-align: center;
72 | `;
73 |
74 | function Footer() {
75 | return (
76 |
77 | VEXA
78 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 | © 2023 Vexa. All rights reserved.
92 |
93 |
94 | );
95 | }
96 |
97 | export default Footer;
--------------------------------------------------------------------------------
/client/src/pages/Chats.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react'
2 | import ChatContainer from '../components/ChatContainer'
3 | import ChatContact from '../components/ChatContact'
4 | import styled from 'styled-components'
5 |
6 | const Container = styled.div`
7 | display: flex;
8 | flex-direction: row;
9 | height: 100%;
10 | width: 100%;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | height: 100%;
15 | background-color: ${({ theme }) => theme.bg};
16 | `
17 |
18 | const Wrapper = styled.div`
19 | display: flex;
20 | flex-direction: row;
21 | height: 85vh;
22 | width: 100%;
23 | display: flex;
24 | justify-content: center;
25 | align-items: center;
26 | margin: 12px 0px;
27 | @media (max-width: 800px) {
28 | height: 82vh;
29 | border-radius: 0px;
30 | height: 87vh;
31 | }
32 | `
33 |
34 | const ChatsContact = styled.div`
35 | margin: 12px 0px;
36 | display: flex;
37 | flex-direction: column;
38 | width: 100%;
39 | max-width: 360px;
40 | height: 100%;
41 | background-color: ${({ theme }) => theme.card};
42 | border-right: 1px solid ${({ theme }) => theme.soft};
43 | @media (max-width: 800px) {border-right: 1px solid ${({ theme }) => theme.soft};
44 | border-right: none;
45 | border-radius: 0px 0px 0px 0px;
46 | }
47 | border-radius: 10px 0px 0px 10px;
48 | `
49 |
50 | const ChatsContainer = styled.div`
51 |
52 | margin: 12px 0px;
53 | display: flex;
54 | max-width: 800px;
55 | width: 100%;
56 | height: 100%;
57 | flex-direction: column;
58 | background-color: ${({ theme }) => theme.card};
59 | border-radius: 0px 10px 10px 0px;
60 | `
61 |
62 | const Chats = () => {
63 | //get the window size and hide the chat container for mobile and dislay it for desktop
64 | const [width, setWidth] = React.useState(window.innerWidth)
65 | const breakpoint = 768
66 |
67 | useEffect(() => {
68 | const handleWindowResize = () => setWidth(window.innerWidth)
69 | window.addEventListener("resize", handleWindowResize)
70 | return () => window.removeEventListener("resize", handleWindowResize)
71 | }, [])
72 |
73 | const [showChat, setShowChat] = React.useState(false)
74 |
75 | return (
76 |
77 |
78 | {width < breakpoint ?
79 | (showChat ?
80 |
81 | :
82 | )
83 | : (
84 | <>
85 |
86 |
87 |
88 |
89 |
90 |
91 | >
92 | )}
93 |
94 |
95 | )
96 | }
97 |
98 | export default Chats
--------------------------------------------------------------------------------
/client/src/pages/Home/Home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import styled from 'styled-components'
3 | import Navbar from './components/Navbar'
4 | import Hero from './components/Hero'
5 | import Footer from './components/Footer'
6 | import Features from './components/Features'
7 | import Testimonials from './components/Testimonials'
8 | import Team from './components/Team'
9 | import Benefits from './components/Benifits'
10 | import About from './components/About'
11 | import SignUp from '../../components/SignUp'
12 | import SignIn from '../../components/SignIn'
13 | import Faq from './components/Faq'
14 |
15 | const Body = styled.div`
16 | background: #13111C;
17 | display: flex;
18 | justify-content: center;
19 | overflow-x: hidden;
20 | `
21 |
22 | const Container = styled.div`
23 | width: 100%;
24 | background-Image: linear-gradient(38.73deg, rgba(204, 0, 187, 0.25) 0%, rgba(201, 32, 184, 0) 50%), linear-gradient(141.27deg, rgba(0, 70, 209, 0) 50%, rgba(0, 70, 209, 0.25) 100%);
25 | display: flex;
26 | flex-direction: column;
27 | align-items: center;
28 | justify-content: center;
29 | `
30 |
31 | const Top = styled.div`
32 | width: 100%;
33 | display: flex;
34 | padding-bottom: 50px;
35 | flex-direction: column;
36 | align-items: center;
37 | background: linear-gradient(38.73deg, rgba(204, 0, 187, 0.15) 0%, rgba(201, 32, 184, 0) 50%), linear-gradient(141.27deg, rgba(0, 70, 209, 0) 50%, rgba(0, 70, 209, 0.15) 100%);
38 | clip-path: polygon(0 0, 100% 0, 100% 100%,50% 95%, 0 100%);
39 | @media (max-width: 768px) {
40 | clip-path: polygon(0 0, 100% 0, 100% 100%,50% 98%, 0 100%);
41 | padding-bottom: 0px;
42 | }
43 | `;
44 | const Content = styled.div`
45 | width: 100%;
46 | height: 100%;
47 | background: #13111C;
48 | display: flex;
49 | flex-direction: column;
50 | `
51 |
52 | const Home = () => {
53 | const [SignInOpen, setSignInOpen] = React.useState(false);
54 | const [SignUpOpen, setSignUpOpen] = React.useState(false);
55 |
56 | return (
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | {/* */}
66 |
67 | {/* */}
68 |
69 |
70 |
71 |
72 |
73 | {SignUpOpen && (
74 |
75 | )}
76 | {SignInOpen && (
77 |
78 | )}
79 |
80 |
81 | )
82 | }
83 |
84 | export default Home
--------------------------------------------------------------------------------
/client/src/components/AccountDialog.jsx:
--------------------------------------------------------------------------------
1 | import { Avatar, Popover } from "@mui/material";
2 | import React from "react";
3 | import styled from "styled-components";
4 | import { useDispatch } from "react-redux";
5 | import { logout } from "../redux/userSlice";
6 | import {useNavigate} from 'react-router-dom';
7 |
8 | const Wrapper = styled.div`
9 | min-width: 200px;
10 | display: flex;
11 | flex-direction: column;
12 | position: relative;
13 | gap: 12px;
14 | padding: 6px 2px;
15 | background-color: ${({ theme }) => theme.card};
16 | `;
17 |
18 | const Account = styled.div`
19 | display: flex;
20 | align-items: center;
21 | gap: 16px;
22 | padding: 16px 16px 6px 16px;
23 | `;
24 |
25 | const Details = styled.div`
26 | display: flex;
27 | flex-direction: column;
28 | gap: 4px;
29 | padding: 0px 16px 0px 0px;
30 | `;
31 |
32 | const Name = styled.div`
33 | font-size: 17px;
34 | font-weight: 600;
35 | color: ${({ theme }) => theme.textSoft};
36 | `;
37 |
38 | const Email = styled.div`
39 | font-size: 12px;
40 | font-weight: 400;
41 | color: ${({ theme }) => theme.textSoft + "99"};
42 | `;
43 |
44 | const Hr = styled.hr`
45 | background-color: ${({ theme }) => theme.soft + "99"};
46 | border: none;
47 | width: 100%;
48 | height: 1px;
49 | margin: 0;
50 | `;
51 |
52 | const Logout = styled.div`
53 | padding: 0px 0px 12px 0px;
54 | display: flex;
55 | align-items: center;
56 | justify-content: center;;
57 | font-size: 16px;
58 | font-weight: 500;
59 | color: ${({ theme }) => theme.textSoft};
60 | cursor: pointer;
61 | `;
62 |
63 | const OutlinedBox = styled.div`
64 | border-radius: 6px;
65 | padding: 4px 16px;
66 | border: 1px solid ${({ theme }) => theme.soft2+"99"};
67 | color: ${({ theme }) => theme.soft2+"99"};
68 | font-size: 12px;
69 | &:hover {
70 | background-color: ${({ theme }) => theme.soft2 + "33"};
71 | }
72 | `;
73 |
74 | const AccountDialog = ({ open, id, anchorEl, handleClose, currentUser }) => {
75 | const dispatch = useDispatch()
76 | const navigate = useNavigate()
77 | const logoutUser = () => {
78 | dispatch(logout())
79 | navigate('/');
80 | }
81 | return (
82 |
93 |
94 |
95 | {currentUser.name.charAt(0)}
99 |
100 | {currentUser.name}
101 | {currentUser.email}
102 |
103 |
104 |
105 |
106 | Logout
107 |
108 |
109 |
110 | );
111 | };
112 |
113 | export default AccountDialog;
114 |
--------------------------------------------------------------------------------
/client/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
37 |
38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
39 |
40 | You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/client/src/components/ProjectStatCard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import LinearProgress from "@mui/material/LinearProgress";
4 |
5 | const Container = styled.div`
6 | padding: 12px 16px;
7 | border-radius: 12px;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: space-between;
11 | gap: 6px;
12 | background-color: ${({ theme }) => theme.card};
13 | &:hover {
14 | box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.1);
15 | }
16 | `;
17 |
18 | const Middle = styled.div`
19 | display: flex;
20 | flex-direction: column;
21 | gap: 6px;
22 | `;
23 |
24 | const Flex = styled.div`
25 | display: flex;
26 | flex-direction: row;
27 | justify-content: space-between;
28 | gap: 30px;
29 | `;
30 |
31 | const Title = styled.div`
32 | font-size: 14px;
33 | font-weight: 500;
34 | color: ${({ theme }) => theme.textSoft};
35 | `;
36 |
37 | const Desc = styled.div`
38 | font-size: 11px;
39 | font-weight: 500;
40 | color: ${({ theme }) => theme.textSoft + "99"};
41 | `;
42 |
43 | const Tags = styled.div`
44 | display: flex;
45 | flex-wrap: wrap;
46 | flex-direction: row;
47 | gap: 4px;
48 | margin-top: 4px;
49 | `;
50 |
51 | const Tag = styled.div`
52 | padding: 4px 10px;
53 | border-radius: 8px;
54 | color: ${({ tagColor, theme }) => tagColor + theme.lightAdd};
55 | background-color: ${({ tagColor, theme }) => tagColor + "10"};
56 | font-size: 10px;
57 | font-weight: 500;
58 | `;
59 |
60 | const TimeLeft = styled.div`
61 | font-size: 10px;
62 | font-weight: 500;
63 | color: #20B2AA;
64 | padding: 4px 8px;
65 | border-radius: 12px;
66 | background-color: #20B2AA10};
67 | width: fit-content;
68 | height: fit-content;
69 | `;
70 |
71 | const Progress = styled.div`
72 | margin: 4px 0px 4px 0px;
73 | `;
74 |
75 | const Text = styled.div`
76 | display: flex;
77 | align-items: center;
78 | font-size: 11px;
79 | font-weight: 400;
80 | color: ${({ theme }) => theme.soft2};
81 | margin: 0px 0px 6px 0px;
82 | line-height: 1.5;
83 | overflow: hidden;
84 | gap: 8px;
85 | `;
86 |
87 | const Span = styled.span`
88 | font-size: 12px;
89 | font-weight: 600;
90 | color: ${({ theme }) => theme.soft2};
91 | line-height: 1.5;
92 | `;
93 |
94 | const AvatarGroup = styled.div`
95 | display: flex;
96 | align-items: center;
97 | `;
98 |
99 | const Avatar = styled.img`
100 | width: 28px;
101 | height: 28px;
102 | border-radius: 50%;
103 | margin-right: -12px;
104 | border: 3px solid ${({ theme }) => theme.bgLighter};
105 | `;
106 |
107 | const Hr = styled.hr`
108 | margin: 2px 0px;
109 | border: 0.5px solid ${({ theme }) => theme.soft + "99"};
110 | `;
111 |
112 | const ProjectStatCard = () => {
113 | return (
114 |
115 | Project Card
116 | Lorem ipsum dolor sit amet consectetur adipisicing eli jbj
117 |
118 | React
119 | Tag 2
120 |
121 |
132 |
133 |
134 |
135 |
136 |
137 |
138 | 12 days left
139 |
140 |
141 | );
142 | };
143 |
144 | export default ProjectStatCard;
145 |
--------------------------------------------------------------------------------
/client/src/components/TaskCard.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import { Avatar } from "@mui/material";
4 |
5 | const Card = styled.div`
6 | display: flex;
7 | justify-content: space-between;
8 | align-items: center;
9 | padding: 6px 10px;
10 | gap: 8px;
11 | border-left: 1.8px solid ${({ theme }) => theme.soft + "99"};
12 | border-right: 1.8px solid ${({ theme }) => theme.soft + "99"};
13 | border-bottom: 1.8px solid ${({ theme }) => theme.soft + "99"};
14 | background-color: ${({ theme }) => theme.card};
15 | color: ${({ theme }) => theme.text};
16 | cursor: pointer;
17 | &:hover {
18 | transition: all 0.2s ease-in-out;
19 | background-color: ${({ theme }) => theme.bgDark + "40"};
20 | }
21 |
22 | ${({ completed, theme }) =>
23 | completed === "Completed" &&
24 | `
25 | background-color: ${theme.soft + "30"};
26 | `}
27 | `;
28 |
29 | const No = styled.div`
30 | width: 4%;
31 | font-size: 12px;
32 | text-overflow: ellipsis;
33 | font-weight: 500;
34 | color: ${({ theme }) => theme.soft2};
35 | display: -webkit-box;
36 | -webkit-line-clamp: 5; /* number of lines to show */
37 | line-clamp: 5;
38 | -webkit-box-orient: vertical;
39 |
40 | ${({ completed, theme }) =>
41 | completed === "Completed" &&
42 | `
43 | text-decoration: line-through;
44 | `}
45 | `;
46 |
47 | const Task = styled.div`
48 | width: 50%;
49 | font-size: 12px;
50 | font-weight: 500;
51 | color: ${({ theme }) => theme.soft2};
52 | display: -webkit-box;
53 | -webkit-line-clamp: 5; /* number of lines to show */
54 | line-clamp: 5;
55 | -webkit-box-orient: vertical;
56 | padding: 6px;
57 |
58 | ${({ completed, theme }) =>
59 | completed === "Completed" &&
60 | `
61 | text-decoration: line-through;
62 | `}
63 | `;
64 |
65 | const Date = styled.div`
66 | font-size: 12px;
67 | font-weight: 500;
68 | text-align: center;
69 | text-overflow: ellipsis;
70 | width: 14%;
71 | color: ${({ theme }) => theme.soft2};
72 | ${({ enddate, theme }) =>
73 | enddate &&
74 | `
75 | color: ${theme.pink};
76 | `}
77 | display: -webkit-box;
78 | -webkit-line-clamp: 5; /* number of lines to show */
79 | line-clamp: 5;
80 | -webkit-box-orient: vertical;
81 |
82 | ${({ completed, theme }) =>
83 | completed === "Completed" &&
84 | `
85 | text-decoration: line-through;
86 | `}
87 | `;
88 |
89 | const Status = styled.div`
90 | font-size: 12px;
91 | font-weight: 500;
92 | text-align: center;
93 | width: 10%;
94 | color: ${({ theme }) => theme.yellow};
95 | padding: 4px 8px;
96 | background: ${({ theme }) => theme.yellow + "10"};
97 | border-radius: 8px;
98 |
99 | ${({ completed, theme }) =>
100 | completed === "Completed" &&
101 | `
102 | color: ${theme.green};
103 | background: ${theme.green + "10"};
104 | `}
105 | `;
106 |
107 | const Members = styled.div`
108 | display: flex;
109 | flex-direction: row;
110 | align-items: center;
111 | `;
112 |
113 | const TaskCard = ({item,index,members}) => {
114 | return (
115 |
116 | {index + 1}.
117 | {item.task}
118 |
119 | {item.start_date.split("-").reverse().join("-")}
120 |
121 |
122 | {item.end_date.split("-").reverse().join("-")}
123 |
124 | {item.status}
125 |
131 | {item.members.slice(0, 5).map((member) => (
132 |
141 | {member.name.charAt(0)}
142 |
143 | ))}
144 |
145 | {item.members.length > 5 && (
146 |
154 | +{members.length - 9}
155 |
156 | )}
157 |
158 |
159 | );
160 | };
161 |
162 | export default TaskCard;
163 |
--------------------------------------------------------------------------------
/client/public/Logo.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/client/src/logo.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/client/src/Images/Logo.svg:
--------------------------------------------------------------------------------
1 |
30 |
--------------------------------------------------------------------------------
/client/src/pages/Projects.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React from "react";
3 | import { useState, useEffect } from "react";
4 | import styled from "styled-components";
5 | import Item from "../components/Card";
6 | import { statuses, data, tagColors } from "../data/data";
7 | import { useDispatch } from "react-redux";
8 | import { openSnackbar } from "../redux/snackbarSlice";
9 | import { useSelector } from "react-redux";
10 | import Skeleton from "@mui/material/Skeleton";
11 | import { useCookies } from "react-cookie";
12 | import { getProjects } from "../api/index";
13 | import AddNewProject from "../components/AddNewProject";
14 | import { CircularProgress } from "@mui/material";
15 |
16 | const Container = styled.div`
17 | width: 100%;
18 | `;
19 |
20 | const Column = styled.div`
21 | display: flex;
22 | flex-direction: row;
23 | @media screen and (max-width: 480px) {
24 | flex-direction: column;
25 | }
26 | justify-content: space-between;
27 | margin: 12px 0px;
28 | `;
29 | const ItemWrapper = styled.div`
30 | width: 100%;
31 | height: 100%;
32 | @media screen and (max-width: 480px) {
33 | width: 97%;
34 | }
35 | padding: 4px;
36 | text-align: left;
37 | margin: 2px;
38 | font-size: 16px;
39 | font-weight: 500;
40 | color: ${({ theme }) => theme.text};
41 | `;
42 |
43 | const Span = styled.span`
44 | color: ${({ theme }) => theme.soft2};
45 | font-weight: 400;
46 | margin-left: 8px;
47 | `;
48 |
49 | const Wrapper = styled.div`
50 | padding: 12px 6px;
51 | `;
52 |
53 | const OutlinedBox = styled.div`
54 | min-height: 44px;
55 | border-radius: 8px;
56 | border: 1px solid ${({ theme }) => theme.soft2};
57 | color: ${({ theme }) => theme.soft2};
58 | ${({ googleButton, theme }) =>
59 | googleButton &&
60 | `
61 | user-select: none;
62 | gap: 16px;`}
63 | ${({ button, theme }) =>
64 | button &&
65 | `
66 | user-select: none;
67 | border: none;
68 | font-weight: 600;
69 | font-size: 16px;
70 | background: ${theme.card}; `}
71 | ${({ activeButton, theme }) =>
72 | activeButton &&
73 | `
74 | user-select: none;
75 | border: none;
76 | background: ${theme.primary};
77 | color: white;`}
78 | margin-top: 8px;
79 | font-weight: 600;
80 | font-size: 16px;
81 | display: flex;
82 | justify-content: center;
83 | align-items: center;
84 | padding: 0px 14px;
85 | &:hover {
86 | transition: all 0.6s ease-in-out;
87 | background: ${({ theme }) => theme.soft};
88 | color: white;
89 | }
90 | `;
91 |
92 | const Projects = ({newProject,setNewProject}) => {
93 | const dispatch = useDispatch();
94 | const [data, setData] = useState([]);
95 | const [loading, setLoading] = useState(true);
96 | const { currentUser } = useSelector((state) => state.user);
97 |
98 |
99 | const token = localStorage.getItem("token");
100 | console.log(token)
101 | const getprojects = async () => {
102 | await getProjects(token)
103 | .then((res) => {
104 | setData(res.data);
105 | setLoading(false);
106 | })
107 | .catch((err) => {
108 | setLoading(false);
109 | dispatch(
110 | openSnackbar({
111 | message: err.response.data.message,
112 | severity: "error",
113 | })
114 | );
115 | });
116 | };
117 |
118 | useEffect(() => {
119 | getprojects();
120 | window.scrollTo(0, 0);
121 | }, [newProject, currentUser]);
122 |
123 |
124 | return (
125 |
126 | {loading ? (
127 |
128 |
129 |
130 | ) : (
131 |
132 | {statuses.map((s, index) => {
133 | return (
134 |
135 | {s.icon} {s.status}
136 |
137 | ({data.filter((item) => item.status == s.status).length})
138 |
139 |
140 | {s.status === "Working" && (
141 | setNewProject(true)}>
142 | New Project
143 |
144 | )}
145 | {data
146 | .filter((item) => item.status == s.status)
147 | .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
148 | .map((item, idx) => (
149 |
156 | ))}
157 |
158 |
159 | );
160 | })}
161 |
162 | )}
163 |
164 | );
165 | };
166 |
167 | export default Projects;
168 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import logo from './logo.svg';
2 | import { ThemeProvider } from "styled-components";
3 | import { useState } from "react";
4 | import { darkTheme, lightTheme } from "./utils/Theme";
5 | import {
6 | BrowserRouter,
7 | Routes,
8 | Route,
9 | } from "react-router-dom"
10 | import Menu from './components/Menu';
11 | import Navbar from './components/Navbar';
12 | import styled from 'styled-components';
13 | import Dashboard from './pages/Dashboard';
14 | import Works from './pages/Works';
15 | import Projects from './pages/Projects';
16 | import { DndProvider } from "react-dnd";
17 | import { HTML5Backend } from "react-dnd-html5-backend";
18 | import ProjectDetails from './pages/ProjectDetails';
19 | import Teams from './pages/Teams';
20 | import ToastMessage from './components/ToastMessage';
21 | import Community from './pages/Community';
22 | import { useSelector } from "react-redux";
23 | import AddNewTeam from './components/AddNewTeam';
24 | import { useEffect } from 'react';
25 | import { getUsers } from './api';
26 | import { useDispatch } from 'react-redux';
27 | import Home from './pages/Home/Home';
28 | import Chats from './pages/Chats';
29 | import ProjectInvite from './components/ProjectInvite';
30 | import TeamInvite from './components/TeamInvite';
31 | import AddNewProject from './components/AddNewProject';
32 |
33 | const Container = styled.div`
34 | height: 100vh;
35 | display: flex;
36 | background-color: ${({ theme }) => theme.bg};
37 | color: ${({ theme }) => theme.text};
38 | overflow-x: hidden;
39 | `;
40 |
41 | const Main = styled.div`
42 | flex: 7;
43 | `;
44 | const Wrapper = styled.div`
45 | padding: 0% 1%;
46 | overflow-y: scroll !important;
47 | `;
48 |
49 | function App() {
50 | const [darkMode, setDarkMode] = useState(true);
51 | const [menuOpen, setMenuOpen] = useState(true);
52 | const [newTeam, setNewTeam] = useState(false);
53 | const [newProject, setNewProject] = useState(false);
54 | const { open, message, severity } = useSelector((state) => state.snackbar);
55 | const [loading, setLoading] = useState(false);
56 |
57 |
58 | const { currentUser } = useSelector(state => state.user);
59 |
60 |
61 | //set the menuOpen state to false if the screen size is less than 768px
62 | useEffect(() => {
63 | const resize = () => {
64 | if (window.innerWidth < 1110) {
65 | setMenuOpen(false);
66 | } else {
67 | setMenuOpen(true);
68 | }
69 | }
70 | resize();
71 | window.addEventListener("resize", resize);
72 | return () => window.removeEventListener("resize", resize);
73 | }, []);
74 |
75 | return (
76 |
77 |
78 |
79 |
80 | {currentUser ?
81 |
82 | {loading ? Loading...
: <>
83 | {menuOpen && }
84 |
85 |
86 |
87 | {newTeam && }
88 | {newProject && }
89 |
90 |
91 | } />
92 | } />
93 |
94 | } />
95 |
96 |
97 | } />
98 |
99 |
100 | } />
101 |
102 |
103 | } />
104 |
105 |
106 | } />
107 | } />
108 | } />
109 | Not Found} />
110 |
111 |
112 |
113 |
114 | >}
115 |
116 | :
118 |
119 |
120 |
121 | } />
123 |
124 | } />
125 |
126 |
127 | } />
128 |
129 |
130 |
131 | }
132 | {open && }
133 |
134 |
135 |
136 |
137 | );
138 | }
139 |
140 | export default App;
141 |
--------------------------------------------------------------------------------
/client/src/api/index.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import jwt_decode from 'jwt-decode';
3 | //https://vexa-server.herokuapp.com/api
4 | //http://localhost:8800/api/
5 | //https://dull-blue-dolphin-tutu.cyclic.app
6 | //https://project-management-app-production-3d51.up.railway.app/api/
7 | //http://localhost:8700/api/
8 | //https://vexa.onrender.com/
9 | const API = axios.create({ baseURL: `https://vexa.onrender.com/api/` });
10 |
11 |
12 |
13 | //auth
14 | export const signIn = async ({ email, password }) => await API.post('/auth/signin', { email, password });
15 | export const signUp = async ({
16 | name,
17 | email,
18 | password,
19 | }) => await API.post('/auth/signup', {
20 | name,
21 | email,
22 | password,
23 | });
24 | export const googleSignIn = async ({
25 | name,
26 | email,
27 | img,
28 | }) => await API.post('/auth/google', {
29 | name,
30 | email,
31 | img,
32 | },{ withCredentials: true });
33 | export const findUserByEmail = async (email) => await API.get(`/auth/findbyemail?email=${email}`);
34 | export const generateOtp = async (email,name,reason) => await API.get(`/auth/generateotp?email=${email}&name=${name}&reason=${reason}`);
35 | export const verifyOtp = async (otp) => await API.get(`/auth/verifyotp?code=${otp}`);
36 | export const resetPassword = async (email,password) => await API.put(`/auth/forgetpassword`,{email,password});
37 |
38 | //user api
39 | export const getUsers = async (token) => await API.get('/users/find', { headers: { "Authorization" : `Bearer ${token}` }},{
40 | withCredentials: true
41 | });
42 | export const searchUsers = async (search,token) => await API.get(`users/search/${search}`,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
43 | export const notifications = async (token) => await API.get('/users/notifications',{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
44 | export const getProjects = async (token) => await API.get(`/users/projects`, { headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
45 | export const userWorks = async (token) => await API.get('/users/works',{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
46 | export const userTasks = async (token) => await API.get('/users/tasks',{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
47 |
48 | //projects api
49 | export const createProject = async (project,token) => await API.post('project/', project,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
50 | export const getProjectDetails = async (id,token) => await API.get(`/project/${id}`,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
51 | export const inviteProjectMembers = async (id, members,token) => await API.post(`/project/invite/${id}`, members,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
52 | export const addWorks = async (id, works,token) => await API.post(`/project/works/${id}`, works,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
53 | export const getWorks = async (id,token) => await API.get(`/project/works/${id}`,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
54 | export const verifyProjectInvite = async (code,projectid,userid,access,role) => await API.get(`/project/invite/${code}?projectid=${projectid}&userid=${userid}&access=${access}&role=${role}`,{ withCredentials: true });
55 | export const updateProject = async (id, project,token) => await API.patch(`/project/${id}`, project,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
56 | export const deleteProject = async (id,token) => await API.delete(`/project/${id}`,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
57 | export const updateMembers = async (id, members,token) => await API.patch(`/project/member/${id}`, members,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
58 | export const removeMembers = async (id, members,token) => await API.patch(`/project/member/remove/${id}`, members,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
59 |
60 |
61 | //teams api
62 | export const createTeam = async (team,token) => await API.post('team/', team,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
63 | export const getTeams = async (id,token) => await API.get(`/team/${id}`,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
64 | export const inviteTeamMembers = async (id, members,token) => await API.post(`/team/invite/${id}`, members,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
65 | export const addTeamProject = async (id, project,token) => await API.post(`/team/addProject/${id}`, project,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
66 | export const verifyTeamInvite = async (code,teamid,userid,access,role) => await API.get(`/team/invite/${code}?teamid=${teamid}&userid=${userid}&access=${access}&role=${role}`,{ withCredentials: true });
67 | export const updateTeam = async (id, team,token) => await API.patch(`/team/${id}`, team,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
68 | export const deleteTeam = async (id,token) => await API.delete(`/team/${id}`,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
69 | export const updateTeamMembers = async (id, members,token) => await API.patch(`/team/member/${id}`, members,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
70 | export const removeTeamMembers = async (id, members,token) => await API.patch(`/team/member/remove/${id}`, members,{ headers: { "Authorization" : `Bearer ${token}` }},{ withCredentials: true });
71 |
--------------------------------------------------------------------------------
/client/src/components/Card.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Fragment, useState, useRef } from "react";
3 | import styled from "styled-components";
4 | import { MoreHoriz, TimelapseRounded } from "@mui/icons-material";
5 | import { LinearProgress } from "@mui/material";
6 | import { useDrag, useDrop } from "react-dnd";
7 | import ITEM_TYPE from "../data/types";
8 | import { tagColors } from "../data/data";
9 | import { Link } from "react-router-dom";
10 | import axios from "axios";
11 | import Avatar from "@mui/material/Avatar";
12 | import {format} from 'timeago.js';
13 |
14 | const Container = styled.div`
15 | padding: 14px 14px;
16 | text-align: left;
17 | margin: 12px 0px 8px 0px;
18 | font-size: 14px;
19 | font-weight: 500;
20 | border-radius: 10px;
21 | background-color: ${({ theme }) => theme.card};
22 | color: ${({ theme }) => theme.text};
23 | cursor: pointer;
24 | box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.09);
25 | &:hover {
26 | transition: all 0.6s ease-in-out;
27 | box-shadow: 0 0 18px 0 rgba(0, 0, 0, 0.5);
28 | }
29 | `;
30 |
31 | const Image = styled.img`
32 | height: 120px;
33 | width: 100%;
34 | object-fit: cover;
35 | border-radius: 10px;
36 | margin-top: 1px;
37 | margin-bottom: 8px;
38 | `;
39 |
40 | const Top = styled.div`
41 | display: flex;
42 | justify-content: space-between;
43 | align-items: center;
44 | `;
45 |
46 | const Title = styled.div`
47 | font-size: 15px;
48 | font-weight: 500;
49 | color: ${({ theme }) => theme.textSoft};
50 | margin-top: 6px;
51 | flex: 7;
52 | line-height: 1.5;
53 | overflow: hidden;
54 | text-overflow: ellipsis;
55 | display: -webkit-box;
56 | -webkit-line-clamp: 2; /* number of lines to show */
57 | line-clamp: 2;
58 | -webkit-box-orient: vertical;
59 | `;
60 |
61 | const Desc = styled.div`
62 | font-size: 12px;
63 | font-weight: 400;
64 | color: ${({ theme }) => theme.soft2};
65 | margin-top: 8px;
66 | line-height: 1.5;
67 | overflow: hidden;
68 | text-overflow: ellipsis;
69 | display: -webkit-box;
70 | -webkit-line-clamp: 5; /* number of lines to show */
71 | line-clamp: 5;
72 | -webkit-box-orient: vertical;
73 | `;
74 |
75 |
76 | const Tags = styled.div`
77 | display: flex;
78 | flex-wrap: wrap;
79 | flex-direction: row;
80 | gap: 6px;
81 | margin-top: 8px;
82 | `;
83 |
84 | const Tag = styled.div`
85 | padding: 4px 10px;
86 | border-radius: 8px;
87 | color: ${({ tagColor,theme }) => tagColor + theme.lightAdd};
88 | background-color: ${({ tagColor, theme }) => tagColor + "10"};
89 | font-size: 10px;
90 | font-weight: 500;
91 | `;
92 |
93 | const Bottom = styled.div`
94 | display: flex;
95 | justify-content: space-between;
96 | align-items: center;
97 | margin: 10px 0px;
98 | `;
99 |
100 | const Time = styled.div`
101 | display: flex;
102 | align-items: center;
103 | gap: 8px;
104 | font-size: 12px;
105 | font-weight: 500;
106 | color: ${({ theme }) => theme.soft2 + "99"};
107 | `;
108 |
109 | const AvatarGroup = styled.div`
110 | display: flex;
111 | align-items: center;
112 | margin-right: 12px;
113 | `;
114 |
115 |
116 | const Card = ({ tagColor, item, index, status }) => {
117 | const ref = useRef(null);
118 |
119 | /*const [, drop] = useDrop({
120 | accept: ITEM_TYPE,
121 | hover(item, monitor) {
122 | if (!ref.current) {
123 | return
124 | }
125 | const dragIndex = item.index;
126 | const hoverIndex = index;
127 |
128 | if (dragIndex === hoverIndex) {
129 | return
130 | }
131 |
132 | const hoveredRect = ref.current.getBoundingClientRect();
133 | const hoverMiddleY = (hoveredRect.bottom - hoveredRect.top) / 2;
134 | const mousePosition = monitor.getClientOffset();
135 | const hoverClientY = mousePosition.y - hoveredRect.top;
136 |
137 | if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
138 | return;
139 | }
140 |
141 | if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
142 | return;
143 | }
144 | moveItem(dragIndex, hoverIndex);
145 | item.index = hoverIndex;
146 | },
147 | });
148 |
149 | const [{ isDragging }, drag] = useDrag({
150 | item: { type: ITEM_TYPE, ...item, index },
151 | collect: monitor => ({
152 | isDragging: monitor.isDragging()
153 | })
154 | });*/
155 |
156 | //drag(drop(ref));
157 |
158 | return (
159 |
160 |
161 |
162 | {item.img && }
163 |
164 | {item.title}
165 |
166 | {item.desc}
167 |
168 | {item.tags.map((tag) => (
169 |
174 | {tag}
175 |
176 | ))}
177 |
178 |
179 |
182 |
183 | {item.members.map((member) => (
184 | {member.id.name.charAt(0)}
185 | ))}
186 |
187 |
188 |
189 |
190 |
191 | );
192 | };
193 |
194 | export default Card;
195 |
--------------------------------------------------------------------------------
/client/src/components/DeletePopup.jsx:
--------------------------------------------------------------------------------
1 | import { CloseRounded } from '@mui/icons-material';
2 | import { CircularProgress, Modal } from '@mui/material';
3 | import React, { useEffect } from 'react'
4 | import { useNavigate } from 'react-router-dom';
5 | import styled from 'styled-components'
6 | import { deleteProject, deleteTeam } from '../api';
7 | import { useDispatch } from 'react-redux';
8 | import { openSnackbar } from '../redux/snackbarSlice';
9 |
10 |
11 | const Container = styled.div`
12 | width: 100%;
13 | height: 100%;
14 | position: absolute;
15 | top: 0;
16 | left: 0;
17 | background-color: #000000a7;
18 | display: flex;
19 | justify-content: center;
20 | `;
21 |
22 | const Wrapper = styled.div`
23 | width: 430px;
24 | height: min-content;
25 | border-radius: 16px;
26 | background-color: ${({ theme }) => theme.bgLighter};
27 | color: ${({ theme }) => theme.text};
28 | padding: 10px;
29 | display: flex;
30 | margin-top: 80px;
31 | flex-direction: column;
32 | position: relative;
33 | @media (max-width: 768px) {
34 | width: 100%;
35 | }
36 | `;
37 |
38 | const Heading = styled.div`
39 | font-size: 20px;
40 | font-weight: 500;
41 | color: ${({ theme }) => theme.text};
42 | margin: 12px 12px 0 12px;
43 | `;
44 |
45 | const Text = styled.div`
46 | font-size: 14px;
47 | font-weight: 400;
48 | color: ${({ theme }) => theme.soft2};
49 | margin: 12px ;
50 | line-height: 1.5;
51 | `;
52 |
53 | const Input = styled.input`
54 | border: none;
55 | font-size: 14px;
56 | padding: 14px 20px;
57 | margin: 12px;
58 | border-radius: 12px;
59 | color: ${({ theme }) => theme.textSoft};
60 | background-color: ${({ theme }) => theme.bgDark};
61 | &:focus {
62 | outline: 1px solid ${({ theme }) => theme.primary};
63 | }
64 | `;
65 |
66 | const Button = styled.button`
67 | border: none;
68 | font-size: 14px;
69 | padding: 14px 20px;
70 | margin: 0px 12px 12px 12px;
71 | font-size: 14px;
72 | border-radius: 12px;
73 | font-weight: 600;
74 | color: ${({ theme }) => theme.text};
75 | background: ${({ theme }) => theme.primary};
76 | cursor: pointer;
77 | ${({ disabled, theme }) =>
78 | disabled &&
79 | `
80 | background: ${theme.soft};
81 | color: ${theme.soft2};
82 | cursor: not-allowed;
83 | `
84 | }
85 | `;
86 |
87 |
88 | const DeletePopup = ({ openDelete, setOpenDelete }) => {
89 |
90 | const [name, setName] = React.useState('');
91 | const [loading, setLoading] = React.useState(false);
92 | const [disabled, setDisabled] = React.useState(true);
93 | const navigate = useNavigate();
94 | const dispatch = useDispatch();
95 |
96 | useEffect(() => {
97 | if (name === openDelete.name) {
98 | setDisabled(false);
99 | } else {
100 | setDisabled(true);
101 | }
102 | }, [name, openDelete.name]);
103 |
104 | const handleDelete = () => {
105 | setLoading(true);
106 | setDisabled(true);
107 | if (openDelete.type === "Project") {
108 | DeleteProject();
109 | } else if (openDelete.type === "Team") {
110 | DeleteTeam();
111 | } else if (openDelete.type === "Work") {
112 | deleteWork();
113 | }
114 |
115 | }
116 |
117 | const DeleteProject = async () => {
118 | await deleteProject(openDelete.id, openDelete.token)
119 | .then((res) => {
120 | console.log(res);
121 | dispatch(openSnackbar
122 | ({
123 | message: "Project deleted successfully",
124 | type: "success",
125 | }));
126 |
127 | handleDeleteSuccess("/projects");
128 | })
129 | .catch((err) => {
130 | dispatch(openSnackbar
131 | ({
132 | message: err.message,
133 | type: "error",
134 | }));
135 | })
136 | }
137 |
138 | const DeleteTeam = async () => {
139 | await deleteTeam(openDelete.id, openDelete.token)
140 | .then((res) => {
141 | console.log(res);
142 | dispatch(openSnackbar
143 | ({
144 | message: "Team deleted successfully",
145 | type: "success",
146 | }));
147 |
148 | handleDeleteSuccess("/");
149 | }
150 | ).catch((err) => {
151 | dispatch(openSnackbar
152 | ({
153 | message: err.message,
154 | type: "error",
155 | }));
156 | }
157 | )
158 | }
159 |
160 | const deleteWork = () => {
161 | }
162 |
163 |
164 | const handleDeleteSuccess = (link) => {
165 | setLoading(false);
166 | setOpenDelete({ ...openDelete, state: false });
167 | navigate(`${link}`);
168 | }
169 |
170 |
171 |
172 | return (
173 | setOpenDelete({ ...openDelete, state: false })}>
174 |
175 |
176 | setOpenDelete({ ...openDelete, state: false })}
185 | />
186 | Delete {openDelete.type}
187 | Are you sure you want to delete this {openDelete.type} {openDelete.name}.
This will permanently delete {openDelete.name} {openDelete.type}'s comments, tools, tasks, workflow runs, and remove all collaborator associations.
188 | setName(e.target.value)} />
189 |
193 |
194 |
195 |
196 | )
197 | }
198 |
199 | export default DeletePopup
--------------------------------------------------------------------------------
/client/src/pages/Home/components/Features.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import HeroBgAnimation from '../components/HeroBgAnimation'
3 | import Groups3Icon from '@mui/icons-material/Groups3';
4 | import TimelineIcon from '@mui/icons-material/Timeline';
5 | import ElectricBoltIcon from '@mui/icons-material/ElectricBolt';
6 | import PublicIcon from '@mui/icons-material/Public';
7 |
8 | const FeaturesWrapper = styled.section`
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | background-color: #13111C;
13 | padding-bottom: 200px;
14 | margin-top: -80px;
15 | background: linear-gradient(343.07deg, rgba(23, 92, 230, 0.02) 2.71%, rgba(23, 92, 230, 0.0) 64.83%);
16 | clip-path: polygon(0 0, 100% 0, 100% 100%,50% 95%, 0 100%);
17 | @media (max-width: 768px) {
18 | padding-bottom: 100px;
19 | margin-top: -40px;
20 | clip-path: polygon(0 0, 100% 0, 100% 100%,50% 98%, 0 100%);
21 | }
22 | `;
23 |
24 | const Number = styled.div`
25 | width: 70px;
26 | height: 70px;
27 | font-size: 36px;
28 | font-weight: 800;
29 | color: #306EE8;
30 | text-align: center;
31 | display: flex;
32 | justify-content: center;
33 | align-items: center;
34 | border-radius: 50%;
35 | border: 6px solid #306EE8;
36 | background-color: #306EE816;
37 | margin-bottom: 20px;
38 | @media (max-width: 768px) {
39 |
40 | width: 50px;
41 | height: 50px;
42 | font-size: 32px;
43 | }
44 | `;
45 |
46 | const FeaturesTitle = styled.div`
47 | font-size: 52px;
48 | text-align: center;
49 | font-weight: 800;
50 | margin-top: 20px;
51 | color: #306EE8;
52 | @media (max-width: 768px) {
53 | margin-top: 12px;
54 | font-size: 36px;
55 | }
56 | `;
57 |
58 |
59 | const FeatureDescription = styled.p`
60 | font-size: 20px;
61 | line-height: 1.5;
62 | font-weight:600px;
63 | width: 100%;
64 | max-width: 700px;
65 | text-align: center;
66 | color: hsl(246, 6%, 65%);
67 | margin-bottom: 80px;
68 | @media (max-width: 768px) {
69 | width: 100%;
70 | font-size: 16px;
71 | margin-bottom: 60px;
72 | }
73 | `;
74 |
75 | const Content = styled.div`
76 | position: relative;
77 | `;
78 |
79 | const FeaturesContainer = styled.div`
80 | position: relative;
81 | z-index: 1;
82 | grid-template-columns: repeat(2, 1fr);
83 | display: grid;
84 | grid-column-gap: 60px;
85 | grid-row-gap: 60px;
86 | @media (max-width: 768px) {
87 | grid-template-columns: repeat(1, 1fr);
88 | grid-column-gap: 30px;
89 | grid-row-gap: 30px;
90 |
91 | }
92 | `;
93 |
94 | const FeatureCard = styled.div`
95 | width: 350px;
96 | height: 190px;
97 | position: relative;
98 | background-color: hsl(250, 24%, 9%);
99 | border: 0.1px solid #306EE8;
100 | border-radius: 16px;
101 | padding: 24px 42px;
102 | box-shadow: rgba(23, 92, 230, 0.15) 0px 4px 24px;
103 | transition: transform 0.2s ease-in-out;
104 | display: flex;
105 | &:hover {
106 | transform: translateY(-10px);
107 | }
108 | @media (max-width: 925px) {
109 | width: 300px;
110 | }
111 |
112 | @media (max-width: 728px)
113 | {
114 | padding: 20px 20px;
115 | }
116 |
117 | `;
118 |
119 | const FeatureIcon = styled.div`
120 | width: 80px;
121 | height: 80px;
122 | color: #306EE8;
123 | position: absolute;
124 | bottom: 0px;
125 | right: 0px;
126 | flex-shrink: 0;
127 | border-top-right-radius: 40%;
128 | border-top-left-radius: 60%;
129 | border-bottom-left-radius: 40%;
130 | border-bottom-right-radius: 16px;
131 | border: 2px solid hsl(220, 80%, 75%,30%);
132 | display: flex;
133 | justify-content: center;
134 | align-items: center;
135 | @media (max-width: 925px) {
136 | width: 80px;
137 | height: 80px;
138 | }
139 | `;
140 |
141 | const FeatureTitle = styled.div`
142 | font-size: 20px;
143 | color: hsl(220, 80%, 75%);
144 | margin-bottom: 10px;
145 | margin-top: 16px;
146 | font-weight: 600;
147 | `;
148 |
149 | const FeatureCardDescription = styled.div`
150 | font-size: 16px;
151 | line-height: 1.5;
152 | color: hsl(246, 6%, 65%);
153 | `;
154 |
155 | const BgImage = styled.div`
156 | position: absolute;
157 | top: 50%;
158 | left: 50%;
159 | transform: translate(-50%, -50%);
160 | @media (max-width: 768px) {
161 | display: none;
162 | }
163 | `;
164 |
165 | const featuresData = [{ icon: , title: 'Project Management', description: 'Effortlessly manage your personal projects and assign tasks to team members while keeping track of progress.', },
166 | { icon: , title: 'Team Collaboration', description: 'Collaborate with your team members in real-time, assign tasks, and keep track of your team’s progress.', },
167 | { icon: , title: 'Community Building', description: 'Connect with members of similar interests, build communities, and grow your network.', },
168 | { icon: , title: 'Time Tracking', description: 'Track your time and improve your productivity by setting goals and keeping track of your progress.', },];
169 |
170 | const Features = () => {
171 | return (
172 |
173 | 1
174 | Key Features
175 | Discover how our app simplifies project management and makes collaboration effortless.
176 |
177 |
178 | {featuresData.map((feature, index) => (
179 |
180 |
181 | {feature.title}
182 | {feature.description}
183 |
184 |
185 | {feature.icon}
186 |
187 |
188 | ))}
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | );
197 | };
198 |
199 | export default Features;
--------------------------------------------------------------------------------
/client/src/pages/Home/components/Benifits.jsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import TrendingUpIcon from '@mui/icons-material/TrendingUp';
3 | import ForumIcon from '@mui/icons-material/Forum';
4 | import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
5 | import HeroBgAnimation from '../components/HeroBgAnimation'
6 | import Diversity3Icon from '@mui/icons-material/Diversity3';
7 |
8 | const FeaturesWrapper = styled.section`
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | background-color: #181622;
13 | padding-bottom: 150px;
14 | margin-top: -90px;
15 | @media (max-width: 768px) {
16 | padding-bottom: 100px;
17 | margin-top: -50px;
18 | }
19 | background: linear-gradient(343.07deg, rgba(132, 59, 206, 0.06) 5.71%, rgba(132, 59, 206, 0) 64.83%);
20 | `;
21 |
22 |
23 | const Number = styled.div`
24 | width: 70px;
25 | height: 70px;
26 | font-size: 36px;
27 | font-weight: 800;
28 | color: #854CE6;
29 | text-align: center;
30 | display: flex;
31 | justify-content: center;
32 | align-items: center;
33 | border-radius: 50%;
34 | background-color: #854CE616;
35 | border: 6px solid #854CE6;
36 | margin-bottom: 20px;
37 | @media (max-width: 768px) {
38 |
39 | width: 50px;
40 | height: 50px;
41 | font-size: 32px;
42 | }
43 | `;
44 |
45 |
46 | const FeaturesTitle = styled.div`
47 | font-size: 52px;
48 | text-align: center;
49 | font-weight: 800;
50 | margin-top: 20px;
51 | color: #854CE6;
52 | @media (max-width: 768px) {
53 | margin-top: 12px;
54 | font-size: 36px;
55 | }
56 | `;
57 |
58 |
59 | const FeatureDescription = styled.p`
60 | font-size: 20px;
61 | line-height: 1.5;
62 | font-weight:600px;
63 | width: 100%;
64 | max-width: 700px;
65 | text-align: center;
66 | color: hsl(246, 6%, 65%);
67 | margin-bottom: 80px;
68 | @media (max-width: 768px) {
69 | width: 90%;
70 | font-size: 16px;
71 | margin-bottom: 60px;
72 | }
73 | `;
74 |
75 | const Content = styled.div`
76 | position: relative;
77 | `;
78 |
79 |
80 | const FeaturesContainer = styled.div`
81 | position: relative;
82 | z-index: 1;
83 | grid-template-columns: repeat(2, 1fr);
84 | display: grid;
85 | grid-column-gap: 60px;
86 | grid-row-gap: 60px;
87 | @media (max-width: 768px) {
88 | grid-template-columns: repeat(1, 1fr);
89 | grid-column-gap: 30px;
90 | grid-row-gap: 30px;
91 |
92 | }
93 | `;
94 |
95 | const FeatureCard = styled.div`
96 | width: 350px;
97 | height: 190px;
98 | position: relative;
99 | background-color: hsl(250, 24%, 9%);
100 | border: 0.1px solid #854CE6;
101 | border-radius: 16px;
102 | padding: 24px 42px;
103 | box-shadow: rgba(23, 92, 230, 0.15) 0px 4px 24px;
104 | transition: transform 0.2s ease-in-out;
105 | display: flex;
106 | &:hover {
107 | transform: translateY(-10px);
108 | }
109 | @media (max-width: 925px) {
110 | width: 300px;
111 | }
112 |
113 | @media (max-width: 728px)
114 | {
115 | padding: 20px 20px;
116 | }
117 |
118 | `;
119 |
120 | const FeatureIcon = styled.div`
121 | width: 80px;
122 | height: 80px;
123 | color: #854CE6;
124 | position: absolute;
125 | bottom: 0px;
126 | right: 0px;
127 | flex-shrink: 0;
128 | border-top-right-radius: 40%;
129 | border-top-left-radius: 60%;
130 | border-bottom-left-radius: 40%;
131 | border-bottom-right-radius: 16px;
132 | border: 2px solid hsl(220, 80%, 75%,30%);
133 | display: flex;
134 | justify-content: center;
135 | align-items: center;
136 | @media (max-width: 925px) {
137 | width: 80px;
138 | height: 80px;
139 | }
140 | `;
141 |
142 | const FeatureTitle = styled.div`
143 | font-size: 20px;
144 | color: #854CE6;
145 | margin-bottom: 10px;
146 | margin-top: 16px;
147 | font-weight: 600;
148 | `;
149 |
150 | const FeatureCardDescription = styled.div`
151 | font-size: 16px;
152 | line-height: 1.5;
153 | color: hsl(246, 6%, 65%);
154 | `;
155 |
156 |
157 | const BgImage = styled.div`
158 | position: absolute;
159 | top: 50%;
160 | left: 50%;
161 | transform: translate(-50%, -50%);
162 | @media (max-width: 768px) {
163 | display: none;
164 | }
165 | `;
166 |
167 | const featuresData = [{ icon: , title: 'Increased Productivity', description: 'Effortlessly manage your personal projects and assign tasks to team members while keeping track of progress.', },
168 | { icon: , title: 'Improved Communication', description: 'Keep everyone on the same page and reduce misunderstandings with clear communication.', },
169 | { icon: , title: 'Better Project Outcomes', description: 'Make informed decisions and track progress to ensure successful project outcomes.', },
170 | {icon: , title: 'Networking Opportunities', description: 'Connect and collaborate with other developers and professionals in your industry to expand your network and build valuable relationships.'}];
171 |
172 | const Benefits = () => {
173 |
174 | return (
175 |
176 | 2
177 | Benefits
178 | Discover the many benefits of using our app to manage your personal and team projects.
179 |
180 | {/* */}
181 |
182 | {featuresData.map((feature, index) => (
183 |
184 |
185 | {feature.title}
186 | {feature.description}
187 |
188 |
189 | {feature.icon}
190 |
191 |
192 | ))}
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 | );
201 | };
202 |
203 | export default Benefits;
204 |
--------------------------------------------------------------------------------
/client/src/components/ChatContact.jsx:
--------------------------------------------------------------------------------
1 | import { Search } from '@mui/icons-material'
2 | import { Avatar } from '@mui/material'
3 | import React from 'react'
4 | import styled from 'styled-components'
5 |
6 | const Continer = styled.div`
7 | display: flex;
8 | flex-direction: column;
9 | width: 100%;
10 | height: 100%;
11 | background-color: ${({ theme }) => theme.card};
12 | `
13 |
14 | const TopBar = styled.div`
15 | height: 70px;
16 | border-bottom: 1px solid ${({ theme }) => theme.soft};
17 | display: flex;
18 | align-items: center;
19 | padding: 0 16px;
20 | @media (max-width: 800px) {
21 | height: 60px;
22 | }
23 | `
24 | const Contacts = styled.div`
25 | flex: 1;
26 | overflow-y: scroll;
27 | background-color: ${({ theme }) => theme.contact_background};
28 | @media (max-width: 800px) {
29 | padding: 20px 0;
30 | }
31 | border-bottom-left-radius: 10px;
32 | `
33 |
34 |
35 | const Profile = styled.div`
36 | display: flex;
37 | flex-direction: column;
38 | margin-left: 16px;
39 | gap: 4px;
40 | `
41 | const Name = styled.span`
42 | font-weight: 500;
43 | font-size: 16px;
44 | color: ${({ theme }) => theme.text};
45 | `
46 |
47 | const SearchBar = styled.div`
48 | display: flex;
49 | align-items: center;
50 | padding: 0 16px;
51 | height: 56px;
52 | border-bottom: 1px solid ${({ theme }) => theme.soft};
53 | color: ${({ theme }) => theme.soft2};
54 | @media (max-width: 800px) {
55 | height: 46px;
56 | }
57 | `
58 | const SearchInput = styled.input`
59 | border: none;
60 | outline: none;
61 | background-color: transparent;
62 | font-size: 16px;
63 | color: ${({ theme }) => theme.text};
64 | margin-left: 16px;
65 | flex: 1;
66 | &::placeholder {
67 | color: ${({ theme }) => theme.soft2};
68 | }
69 |
70 | `
71 |
72 | const ContactCard = styled.div`
73 | display: flex;
74 | align-items: center;
75 | padding: 14px 12px;
76 | cursor: pointer;
77 | border-bottom: 1px solid ${({ theme }) => theme.soft};
78 | &:hover {
79 | background-color: ${({ theme }) => theme.soft};
80 | }
81 | `
82 |
83 | const Message = styled.span`
84 | font-size: 14px;
85 | color: ${({ theme }) => theme.soft2};
86 | `
87 | const Time = styled.span`
88 | font-size: 12px;
89 | color: ${({ theme }) => theme.soft2};
90 | margin-left: auto;
91 | `
92 |
93 |
94 | const ChatContact = ({ showChat, setShowChat }) => {
95 | return (
96 |
97 |
98 |
99 |
100 | Messaging
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 | setShowChat(true)}>
109 |
110 |
111 | John Doe
112 | Test message this is
113 |
114 |
115 |
116 |
117 |
118 |
119 | John Doe
120 | Test message this is
121 |
122 |
123 |
124 |
125 |
126 |
127 | John Doe
128 | Test message this is
129 |
130 |
131 |
132 |
133 |
134 |
135 | John Doe
136 | Test message this is
137 |
138 |
139 |
140 |
141 |
142 |
143 | John Doe
144 | Test message this is
145 |
146 |
147 |
148 |
149 |
150 |
151 | John Doe
152 | Test message this is
153 |
154 |
155 |
156 |
157 |
158 |
159 | John Doe
160 | Test message this is
161 |
162 |
163 |
164 |
165 |
166 |
167 | John Doe
168 | Test message this is
169 |
170 |
171 |
172 |
173 |
174 |
175 | John Doe
176 | Test message this is
177 |
178 |
179 |
180 |
181 |
182 | )
183 | }
184 |
185 | export default ChatContact
--------------------------------------------------------------------------------
/client/src/data/data.js:
--------------------------------------------------------------------------------
1 | const data = [
2 | {
3 | id: 1,
4 | title: "Dashboard Design for Task Manager App",
5 | desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
6 | tags: ["React", "Node", "MongoDB"],
7 | time: "2 days ago",
8 | status: "Working",
9 | image: "https://cdn.dribbble.com/userupload/3800319/file/original-54a969b772fca3ef0ec9b3b39f0f1b48.jpg?compress=1&resize=450x338&vertical=top",
10 | members: [
11 | { name: "John", image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTUB8kqGZ74kvQczb_fL00a6LecB331zRp5SQ&usqp=CAU" },
12 | { name: "John", image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTal_yVhjVwOho5Ck1i0mqlutKZPxcOsRfIBg&usqp=CAU" },
13 | { name: "John", image: "https://source.unsplash.com/random" },
14 | ],
15 | },
16 | {
17 | id: 2,
18 | title: "Project 2",
19 | desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit.Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
20 | tags: ["React", "Node", "MongoDB"],
21 | time: "2 days ago",
22 | status: "In Progress",
23 | image: "",
24 | members: [
25 | { name: "John", image: "https://source.unsplash.com/random" },
26 | { name: "John", image: "https://source.unsplash.com/random" },
27 | { name: "John", image: "https://source.unsplash.com/random" },
28 | ],
29 | },
30 | {
31 | id: 3,
32 | title: "Project 1",
33 | desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
34 | tags: ["Android", "MERN", "MongoDB"],
35 | time: "2 days ago",
36 | status: "In Progress",
37 | image: "",
38 | members: [
39 | { name: "John", image: "https://source.unsplash.com/random" },
40 | { name: "John", image: "https://source.unsplash.com/random" },
41 | { name: "John", image: "https://source.unsplash.com/random" },
42 | ],
43 | },
44 | {
45 | id: 4,
46 | title: "Project 1",
47 | desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
48 | tags: ["React", "Node", "MongoDB"],
49 | time: "2 days ago",
50 | status: "Completed",
51 | image: "",
52 | members: [
53 | { name: "John", image: "https://source.unsplash.com/random" },
54 | { name: "John", image: "https://source.unsplash.com/random" },
55 | { name: "John", image: "https://source.unsplash.com/random" },
56 | ],
57 | },
58 | {
59 | id: 5,
60 | title: "Project 2",
61 | desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
62 | tags: ["React", "Node", "MongoDB"],
63 | time: "2 days ago",
64 | status: "Completed",
65 | image: "https://cdn.dribbble.com/userupload/3801107/file/original-1a14267f7088e99ef74ced21616a4137.png?compress=1&resize=1504x1128",
66 | members: [
67 | { name: "John", image: "https://source.unsplash.com/random" },
68 | { name: "John", image: "https://source.unsplash.com/random" },
69 | { name: "John", image: "https://source.unsplash.com/random" },
70 | ],
71 | },
72 | {
73 | id: 6,
74 | title: "Project 3",
75 | desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
76 | tags: ["React", "Node", "MongoDB"],
77 | time: "2 days ago",
78 | status: "Completed",
79 | image: "",
80 | members: [
81 | { name: "John", image: "https://source.unsplash.com/random" },
82 | { name: "John", image: "https://source.unsplash.com/random" },
83 | { name: "John", image: "https://source.unsplash.com/random" },
84 | ],
85 | },
86 | ];
87 | const statuses = [
88 | {
89 | status: "Working",
90 | icon: "⭕️",
91 | color: "#EB5A46",
92 | },
93 | {
94 | status: "In Progress",
95 | icon: "🔆️",
96 | color: "#00C2E0",
97 | },
98 | {
99 | status: "Completed",
100 | icon: "📝",
101 | color: "#C377E0",
102 | },
103 | ];
104 |
105 | const tagColors = [
106 | "#FF69B4",
107 | "#9932CC",
108 | "#FFA07A",
109 | "#FFD700",
110 | "#90EE90",
111 | "#20B2AA",
112 | "#AFEEEE",
113 | "#FFF0F5",
114 | "#B5E4CA",
115 | "#CABDFF",
116 | "#B1E5FC",
117 | "#FFBC99",
118 |
119 | ]
120 |
121 | const tools = [
122 | {
123 | name: "Slack",
124 | icon: "https://cdn.worldvectorlogo.com/logos/slack-1.svg",
125 | },
126 | {
127 | name: "Figma",
128 | icon: "https://cdn.worldvectorlogo.com/logos/figma-1.svg",
129 | },
130 | {
131 | name: "Adobe XD",
132 | icon: "https://cdn.worldvectorlogo.com/logos/adobe-xd.svg",
133 | },
134 | {
135 | name: "Github",
136 | icon: "https://cdn.worldvectorlogo.com/logos/github-icon.svg",
137 | },
138 | {
139 | name: "GitLab",
140 | icon: "https://cdn-icons-png.flaticon.com/512/5968/5968853.png",
141 | },
142 | {
143 | name: "Bitbucket",
144 | icon: "https://cdn.worldvectorlogo.com/logos/bitbucket.svg",
145 | },
146 | ]
147 |
148 | const members = [
149 | {
150 | name: "John Abharim",
151 | email: "john123@gmail.com",
152 | role: "Manager",
153 | access: "Owner",
154 | image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTUB8kqGZ74kvQczb_fL00a6LecB331zRp5SQ&usqp=CAU",
155 | },
156 | {
157 | name: "John Abharim",
158 | email: "john123@gmail.com",
159 | role: "Developer",
160 | access: "Editor",
161 | image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTUB8kqGZ74kvQczb_fL00a6LecB331zRp5SQ&usqp=CAU",
162 | },
163 | {
164 | name: "John Abharim",
165 | email: "john123@gmail.com",
166 | role: "Designer",
167 | access: "View Only",
168 | image: "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTUB8kqGZ74kvQczb_fL00a6LecB331zRp5SQ&usqp=CAU",
169 | },
170 | ]
171 |
172 | const ideas = [
173 | "Lorem ipsum dolor sit amet, adipiscing elit. ",
174 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
175 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
176 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. ",
177 | ]
178 |
179 |
180 | export { statuses, data , tagColors, tools, members, ideas };
181 |
--------------------------------------------------------------------------------
/client/src/components/WorkCards.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Fragment, useState, useRef } from "react";
3 | import styled from "styled-components";
4 | import {
5 | ImportantDevices,
6 | MoreVert,
7 | TimelapseRounded,
8 | StarsRounded
9 | } from "@mui/icons-material";
10 | import LinearProgress, {
11 | linearProgressClasses,
12 | } from "@mui/material/LinearProgress";
13 | import { format } from "timeago.js";
14 | import { useDrag, useDrop } from "react-dnd";
15 | import ITEM_TYPE from "../data/types";
16 | import { tagColors } from "../data/data";
17 | import { Link } from "react-router-dom";
18 | import { color } from "@mui/system";
19 | import { Avatar, IconButton } from "@mui/material";
20 | import WorkDetails from "./WorkDetails";
21 |
22 | const Container = styled.div`
23 | padding: 14px;
24 | text-align: left;
25 | margin: 2px 0px;
26 | font-size: 16px;
27 | font-weight: 500;
28 | border-radius: 10px;
29 | background-color: ${({ theme }) => theme.card};
30 | color: ${({ theme }) => theme.text};
31 | cursor: pointer;
32 | box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.09);
33 | &:hover {
34 | transition: all 0.6s ease-in-out;
35 | box-shadow: 0 0 18px 0 rgba(0, 0, 0, 0.5);
36 | }
37 | `;
38 |
39 | const Top = styled.div`
40 | display: flex;
41 | justify-content: space-between;
42 | align-items: center;
43 | `;
44 |
45 | const Title = styled.div`
46 | font-size: 16px;
47 | font-weight: 500;
48 | color: ${({ theme }) => theme.textSoft};
49 | margin-top: 6px;
50 | flex: 7;
51 | line-height: 1.5;
52 | overflow: hidden;
53 | text-overflow: ellipsis;
54 | display: -webkit-box;
55 | -webkit-line-clamp: 2; /* number of lines to show */
56 | line-clamp: 2;
57 | -webkit-box-orient: vertical;
58 | `;
59 |
60 | const Desc = styled.div`
61 | font-size: 14px;
62 | font-weight: 400;
63 | color: ${({ theme }) => theme.soft2};
64 | margin-top: 4px;
65 | line-height: 1.5;
66 | overflow: hidden;
67 | text-overflow: ellipsis;
68 | display: -webkit-box;
69 | -webkit-line-clamp: 5; /* number of lines to show */
70 | line-clamp: 5;
71 | -webkit-box-orient: vertical;
72 | `;
73 |
74 | const Progress = styled.div`
75 | position: relative;
76 | `;
77 |
78 | const Text = styled.div`
79 | display: flex;
80 | justify-content: space-between;
81 | align-items: center;
82 | font-size: 12px;
83 | font-weight: 400;
84 | color: ${({ theme }) => theme.soft2};
85 | margin: 14px 0px 10px 0px;
86 | line-height: 1.5;
87 | overflow: hidden;
88 | `;
89 |
90 | const Tags = styled.div`
91 | display: flex;
92 | flex-wrap: wrap;
93 | flex-direction: row;
94 | gap: 4px;
95 | margin-top: 8px;
96 | `;
97 |
98 | const Tag = styled.div`
99 | padding: 4px 10px;
100 | border-radius: 8px;
101 | color: ${({ tagColor, theme }) => tagColor + theme.lightAdd};
102 | background-color: ${({ tagColor, theme }) => tagColor + "10"};
103 | font-size: 10px;
104 | font-weight: 500;
105 | `;
106 |
107 | const Span = styled.span`
108 | font-size: 12px;
109 | font-weight: 600;
110 | color: ${({ theme }) => theme.soft2};
111 | line-height: 1.5;
112 | `;
113 |
114 | const Bottom = styled.div`
115 | display: flex;
116 | justify-content: space-between;
117 | align-items: center;
118 | margin: 20px 0px 14px 0px;
119 | `;
120 |
121 | const Time = styled.div`
122 | display: flex;
123 | align-items: center;
124 | gap: 8px;
125 | font-size: 12px;
126 | font-weight: 500;
127 | color: ${({ theme }) => theme.soft2 + "99"};
128 | `;
129 |
130 | const AvatarGroup = styled.div`
131 | display: flex;
132 | align-items: center;
133 | margin-right: 12px;
134 | `;
135 | const IcoBtn = styled(IconButton)`
136 | color: ${({ theme }) => theme.textSoft} !important;
137 | `;
138 |
139 | const Card = ({ status, work }) => {
140 | const [color, setColor] = useState("primary");
141 | const [task, setTask] = useState(work.tasks);
142 | const [tag, setTag] = useState(work.tags);
143 | const [completed, setCompleted] = useState(0);
144 | const [progress, setProgress] = useState(0);
145 | const [members, setMembers] = useState([]);
146 | const [openWork, setOpenWork] = useState(false);
147 |
148 | useEffect(() => {
149 | if (status === "Completed") {
150 | setColor("success");
151 | }
152 | }, [status]);
153 |
154 | //check the no of tasks completed in the work and set the progress
155 | useEffect(() => {
156 | let count = 0;
157 | let Members = [];
158 | task.forEach((item) => {
159 | if (item.status === "Completed") {
160 | count++;
161 | }
162 | console.log(item);
163 | if (item.members.length > 0) {
164 | item.members.forEach((items) => {
165 | let isPresent = Members.some((member) => member._id === items._id);
166 | if (!isPresent) {
167 | Members.push(items);
168 | }
169 | });
170 | }
171 | });
172 | setCompleted(count);
173 | setProgress(completed);
174 | setMembers(Members);
175 | }, [task]);
176 |
177 | //get the members of the work from all the tasks and add it in members array withb the image and name
178 |
179 | return (
180 |
181 |
182 | {work.title}
183 | {work.priority === "Low" &&
184 | }
185 |
186 |
187 |
188 |
189 | {work.desc}
190 |
191 | {tag.map((tag) => (
192 |
195 | {tag}
196 |
197 | ))}
198 |
199 |
213 |
214 |
218 |
219 | {members.slice(0, 2).map((member) => (
220 |
229 | {member.name.charAt(0)}
230 |
231 | ))}
232 | {members.length > 2 && (
233 |
241 | +{members.length - 2}
242 |
243 | )}
244 |
245 |
246 |
247 | );
248 | };
249 |
250 | export default Card;
251 |
--------------------------------------------------------------------------------
/client/src/components/Menu.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useState } from "react";
3 | import styled from "styled-components";
4 | import SettingsBrightnessOutlinedIcon from "@mui/icons-material/SettingsBrightnessOutlined";
5 | import { Link } from "react-router-dom";
6 | import {
7 | Add,
8 | Dashboard,
9 | CloseRounded,
10 | Groups2Rounded,
11 | HubRounded,
12 | Logout,
13 | StreamRounded,
14 | WorkspacesRounded,
15 | Public,
16 | AccountTreeRounded,
17 | DashboardRounded,
18 | AddTaskRounded,
19 | } from "@mui/icons-material";
20 | import { tagColors } from "../data/data";
21 | import LogoIcon from "../Images/Logo.svg";
22 | import { useDispatch } from "react-redux";
23 | import { logout } from "../redux/userSlice";
24 | import { openSnackbar } from "../redux/snackbarSlice";
25 | import axios from "axios";
26 | import { useSelector } from "react-redux";
27 | import { getUsers, notifications } from "../api/index";
28 | import { useNavigate } from 'react-router-dom';
29 | import { Avatar, CircularProgress } from "@mui/material";
30 | import Skeleton from "@mui/material/Skeleton";
31 |
32 | const Container = styled.div`
33 | flex: 1.3;
34 | background-color: ${({ theme }) => theme.bgLighter};
35 | height: 100vh;
36 | border-top-right-radius: 14px;
37 | border-bottom-right-radius: 14px;
38 | color: ${({ theme }) => theme.text};
39 | font-size: 14px;
40 | position: sticky;
41 | top: 0;
42 | box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.04);
43 | transition: 0.3s ease-in-out;
44 | @media (max-width: 1100px) {
45 | position: fixed;
46 | z-index: 100;
47 | width: 100%;
48 | max-width: 250px;
49 | left: ${({ setMenuOpen }) => (setMenuOpen ? "0" : "-100%")};
50 | transition: 0.3s ease-in-out;
51 | }
52 | `;
53 | const ContainerWrapper = styled.div`
54 | height: 90%;
55 | overflow-y: scroll !important;
56 | margin-top: 0px;
57 | `;
58 | const Space = styled.div`
59 | height: 50px;
60 | `;
61 | const Flex = styled.div`
62 | display: flex;
63 | justify-content: space-between;
64 | align-items: center;
65 | padding: 24px 24px;
66 | `;
67 |
68 | const Logo = styled.div`
69 | color: ${({ theme }) => theme.primary};
70 | display: flex;
71 | align-items: center;
72 | gap: 16px;
73 | font-weight: bold;
74 | font-size: 26px;
75 | `;
76 |
77 | const Close = styled.div`
78 | display: none;
79 | @media (max-width: 1100px) {
80 | display: block;
81 | }
82 | `;
83 |
84 | const Image = styled.img`
85 | height: 32px;
86 | `;
87 |
88 | const Item = styled.div`
89 | display: flex;
90 | color: ${({ theme }) => theme.itemText};
91 | align-items: center;
92 | gap: 20px;
93 | cursor: pointer;
94 | padding: 7.5px 26px;
95 | &:hover {
96 | background-color: ${({ theme }) => theme.itemHover};
97 | }
98 | `;
99 |
100 | const Hr = styled.hr`
101 | margin: 15px 15px 15px 0px;
102 | border: 0.5px solid ${({ theme }) => theme.soft};
103 | `;
104 |
105 | const Title = styled.h2`
106 | font-size: 15px;
107 | font-weight: 500;
108 | color: ${({ theme }) => theme.textSoft + "99"};
109 | margin-bottom: 4px;
110 | padding: 0px 26px;
111 | display: flex;
112 | align-items: center;
113 | gap: 12px;
114 | `;
115 |
116 | const TeamIcon = styled(WorkspacesRounded)`
117 | color: ${({ tagColor }) => tagColor};
118 | font-size: 18px;
119 | margin-left: 2px;
120 | `;
121 |
122 | const Menu = ({ darkMode, setDarkMode, setMenuOpen, setNewTeam }) => {
123 | const [teamsLoading, setTeamsLoading] = useState(true);
124 | const token = localStorage.getItem("token");
125 | const dispatch = useDispatch();
126 | const navigate = useNavigate();
127 | const logoutUser = () => {
128 | dispatch(logout());
129 | navigate(`/`);
130 | };
131 |
132 | const [team, setTeams] = useState([]);
133 | const { currentUser } = useSelector(state => state.user);
134 |
135 | const getteams = async () => {
136 | setTeamsLoading(true);
137 | await getUsers(token)
138 | .then((res) => {
139 | setTeams(res.data.teams);
140 | setTeamsLoading(false);
141 | })
142 | .catch((err) => {
143 | dispatch(openSnackbar({ message: err.message, type: "error" }));
144 | if (err.response.status === 401 || err.response.status === 402) logoutUser();
145 | });
146 | };
147 |
148 |
149 | useEffect(() => {
150 | getteams();
151 | }, [currentUser]);
152 |
153 | return (
154 |
155 |
156 |
157 |
158 |
159 | VEXA
160 |
161 |
162 |
163 | setMenuOpen(false)} />
164 |
165 |
166 |
167 |
168 | -
169 |
170 | Dashboard
171 |
172 |
173 |
177 | -
178 |
179 | Projects
180 |
181 |
182 |
186 | -
187 |
188 | Your Works
189 |
190 |
191 |
195 | -
196 |
197 | Community
198 |
199 |
200 |
201 |
202 | Teams
203 |
204 | {teamsLoading ? (
205 |
206 |
207 |
208 | ) : (<>
209 | {team.map((team, i) => (
210 |
214 | -
215 | {team.img !== "" ?
216 | {team.name[0]} :
217 | }
218 | {team.name}
219 |
220 |
221 | ))}
222 | >
223 | )}
224 | - setNewTeam(true)}>
225 |
226 | New Team
227 |
228 |
229 | - setDarkMode(!darkMode)}>
230 |
231 | {darkMode ? "Light" : "Dark"} Mode
232 |
233 | - logoutUser()}>
234 |
235 | Logout
236 |
237 |
238 |
239 |
240 | );
241 | };
242 |
243 | export default Menu;
244 |
--------------------------------------------------------------------------------
/server/controllers/works.js:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 | import User from "../models/User.js";
3 | import bcrypt from "bcrypt";
4 | import { createError } from "../error.js";
5 | import jwt from "jsonwebtoken";
6 | import nodemailer from "nodemailer";
7 | import dotenv from 'dotenv';
8 | import Works from "../models/Works.js";
9 |
10 |
11 | export const addWork = async (req, res, next) => {
12 | const user = await User.findById(req.user.id);
13 | if (!user) {
14 | return next(createError(404, "User not found"));
15 | }
16 |
17 | const newWork = new Project({ members: [{ id: user.id, role: "d", access: "Owner" }], ...req.body });
18 | try {
19 | const saveProject = await (await newWork.save());
20 | User.findByIdAndUpdate(user.id, { $push: { projects: saveProject._id } }, { new: true }, (err, doc) => {
21 | if (err) {
22 | next(err);
23 | }
24 | });
25 | res.status(200).json(saveProject);
26 | } catch (err) {
27 | next(err);
28 | }
29 | };
30 |
31 |
32 |
33 | export const deleteProject = async (req, res, next) => {
34 | try {
35 | const project = await Project.findById(req.params.id);
36 | if (!project) return next(createError(404, "Project not found!"));
37 | for (let i = 0; i < project.members.length; i++) {
38 | if (project.members[i].id === req.user.id) {
39 | if (project.members[i].access === "Owner") {
40 | await project.delete();
41 | User.findByIdAndUpdate(req.user.id, { $pull: { projects: req.params.id } }, { new: true }, (err, doc) => {
42 | if (err) {
43 | next(err);
44 | }
45 | });
46 | res.status(200).json("Project has been deleted...");
47 | } else {
48 | return next(createError(403, "You are not allowed to delete this project!"));
49 | }
50 | }
51 | }
52 | } catch (err) {
53 | next(err);
54 | }
55 | };
56 |
57 | export const getProject = async (req, res, next) => {
58 | try {
59 | const project = await Project.findById(req.params.id);
60 | const members = []
61 | var verified = false
62 | await Promise.all(
63 | project.members.map(async (Member) => {
64 | console.log(Member.id)
65 | if (Member.id === req.user.id) {
66 | verified = true
67 | }
68 | await User.findById(Member.id).then((member) => {
69 | console.log(member)
70 | members.push({ id: member.id, role: Member.role, access: Member.access, name: member.name, img: member.img, email: member.email });
71 | })
72 | })
73 | )
74 | .then(() => {
75 | if (verified) {
76 | return res.status(200).json({ project, members });
77 | } else {
78 | return next(createError(403, "You are not allowed to view this project!"));
79 | }
80 | });
81 |
82 | } catch (err) {
83 | next(err);
84 | }
85 | };
86 |
87 |
88 | export const updateProject = async (req, res, next) => {
89 | try {
90 | const project = await Project.findById(req.params.id);
91 | if (!project) return next(createError(404, "project not found!"));
92 | for (let i = 0; i < project.members.length; i++) {
93 | if (project.members[i].id === req.user.id) {
94 | if (project.members[i].access === "Owner" || project.members[i].access === "Admin" || project.members[i].access === "Editor") {
95 | const updatedproject = await Project.findByIdAndUpdate(
96 | req.params.id,
97 | {
98 | $set: req.body,
99 | },
100 | { new: true }
101 | );
102 | res.status(200).json(updatedproject);
103 | } else {
104 | return next(createError(403, "You are not allowed to update this project!"));
105 | }
106 | } else {
107 | return next(createError(403, "You can update only if you are a member of this project!"));
108 | }
109 | }
110 | } catch (err) {
111 | next(err);
112 | }
113 | };
114 |
115 |
116 |
117 | dotenv.config();
118 |
119 | const transporter = nodemailer.createTransport({
120 | service: "gmail",
121 | auth: {
122 | user: process.env.EMAIL_USERNAME,
123 | pass: process.env.EMAIL_PASSWORD
124 | },
125 | port: 465,
126 | host: 'smtp.gmail.com'
127 | });
128 |
129 | export const inviteProjectMember = async (req, res, next) => {
130 | //send mail using nodemailer
131 | const user = await User.findById(req.user.id);
132 | if (!user) {
133 | return next(createError(404, "User not found"));
134 | }
135 |
136 | const project = await Project.findById(req.params.id);
137 | if (!project) return next(createError(404, "Project not found!"));
138 | for (let i = 0; i < project.members.length; i++) {
139 | if (project.members[i].id === req.user.id) {
140 | if (project.members[i].access === "Owner" || project.members[i].access === "Admin" || project.members[i].access === "Editor") {
141 |
142 | const mailOptions = {
143 | from: process.env.EMAIL,
144 | to: req.body.email,
145 | subject: "Invitation to join project",
146 | text: `Hi ${req.body.name}, you have been invited to join project ${project.title} by ${user.name}. Please click on the link to join the project. http://localhost:8080/api/project/invite/${req.params.id}/${req.body.id}`,
147 | };
148 | transporter.sendMail(mailOptions, (err, data) => {
149 | if (err) {
150 | return next(err);
151 | } else {
152 | res.status(200).json({ message: "Email sent successfully" });
153 | }
154 | });
155 | } else {
156 | return next(createError(403, "You are not allowed to invite members to this project!"));
157 | }
158 | }
159 | }
160 | };
161 |
162 | //verify invitation and add to project member
163 | export const verifyInvitation = async (req, res, next) => {
164 | try {
165 | const project = await Project.findById(req.params.projectId);
166 | if (!project) return next(createError(404, "Project not found!"));
167 | const user = await User.findById(req.params.userId);
168 | if (!user) {
169 | return next(createError(404, "User not found"));
170 | }
171 | for (let i = 0; i < project.members.length; i++) {
172 | if (project.members[i].id === user.id) {
173 | return next(createError(403, "You are already a member of this project!"));
174 | }
175 | }
176 | const newMember = { id: user.id, img: user.img, name: user.name, email: user.email, role: "d", access: "View Only" };
177 | const updatedProject = await Project.findByIdAndUpdate(
178 | req.params.projectId,
179 | {
180 | $push: { members: newMember },
181 | },
182 | { new: true }
183 | );
184 | User.findByIdAndUpdate(user.id, { $push: { projects: updatedProject._id } }, { new: true }, (err, doc) => {
185 | if (err) {
186 | next(err);
187 | }
188 | });
189 | res.status(200).json(updatedProject);
190 | } catch (err) {
191 | next(err);
192 | }
193 | };
194 |
195 |
196 | export const getProjectMembers = async (req, res, next) => {
197 | try {
198 | const project = await Project.findById(req.params.id);
199 | if (!project) return next(createError(404, "Project not found!"));
200 | res.status(200).json(project.members);
201 | } catch (err) {
202 | next(err);
203 | }
204 | }
--------------------------------------------------------------------------------
/client/src/components/ChatContainer.jsx:
--------------------------------------------------------------------------------
1 | import { ArrowBack, AttachFile, DoneAll, Send, Telegram } from '@mui/icons-material'
2 | import { Avatar, IconButton } from '@mui/material'
3 | import React, { useRef, useEffect } from 'react'
4 | import styled from 'styled-components'
5 |
6 | const Container = styled.div`
7 | display: flex;
8 | width: 100%;
9 | height: 100%;
10 | flex-direction: column;
11 | background-color: ${({ theme }) => theme.card};
12 | `
13 |
14 | const TopBar = styled.div`
15 | height: 70px;
16 | border-bottom: 1px solid ${({ theme }) => theme.soft};
17 | display: flex;
18 | align-items: center;
19 | padding: 0px 16px;
20 | @media (max-width: 800px) {
21 | height: 60px;
22 | padding: 0px 16px 0px 6px;
23 | }
24 | `
25 |
26 | const Chat = styled.div`
27 | flex: 1;
28 | overflow-y: scroll;
29 | padding: 20px 6px;
30 | background-color: ${({ theme }) => theme.chat_background};
31 | @media (max-width: 800px) {
32 | padding: 20px 0;
33 | }
34 |
35 | `
36 |
37 | const RecievedMessage = styled.p`
38 | margin: 16px 16px 0 16px;
39 | padding: 12px 16px;
40 | background-color: ${({ theme }) => theme.recieve_message};
41 | border-radius: 12px;
42 | color: ${({ theme }) => theme.text};
43 | font-size: 14px;
44 | max-width: 70%;
45 | width: fit-content;
46 | box-shadow: 0 0 6px rgba(0,0,0,0.2);
47 | position: relative;
48 | &:after {
49 | content: '';
50 | position: absolute;
51 | visibility: visible;
52 | top: 0px;
53 | left: -10px;
54 | border: 10px solid transparent;
55 | border-top: 10px solid ${({ theme }) => theme.recieve_message};
56 | clear: both;
57 | }
58 | `
59 |
60 | const SentMessage = styled.p`
61 | margin: 16px 16px 0 auto;
62 | padding: 12px 16px;
63 | background-color: ${({ theme }) => theme.send_message};
64 | border-radius: 12px 0px 12px 12px;
65 | color: ${({ theme }) => theme.send_message_text_color};
66 | font-size: 14px;
67 | max-width: 70%;
68 | width: fit-content;
69 | box-shadow: 0 0 6px rgba(0,0,0,0.4);
70 | position: relative;
71 | &:after {
72 | content: '';
73 | position: absolute;
74 | visibility: visible;
75 | top: 0px;
76 | right: -10px;
77 | border: 10px solid transparent;
78 | border-top: 10px solid ${({ theme }) => theme.send_message};
79 | clear: both;
80 | }
81 | `
82 |
83 | const Time = styled.span`
84 | font-size: 12px;
85 | padding: 10px 16px;
86 | color: ${({ theme }) => theme.soft2};
87 | margin: 0 16px;
88 | display: flex;
89 | align-items: center;
90 | justify-content: flex-end;
91 | gap: 10px;
92 | ${({ message }) => message === 'recieved' && `
93 | justify-content: flex-start;
94 | `}
95 | `
96 |
97 | const SendMessage = styled.div`
98 | min-height: 70px;
99 | border-top: 1px solid ${({ theme }) => theme.soft};
100 | display: flex;
101 | align-items: center;
102 | padding: 0 16px;
103 | gap: 10px;
104 | @media (max-width: 800px) {
105 | position: fixed;
106 | background-color: ${({ theme }) => theme.card};
107 | bottom: 0;
108 | left: 0;
109 | right: 0;
110 | z-index: 100;
111 | gap: 6px;
112 | padding: 0 2px;
113 | }
114 | `
115 |
116 | const Profile = styled.div`
117 | display: flex;
118 | flex-direction: column;
119 | margin-left: 16px;
120 | gap: 4px;
121 | `
122 | const Name = styled.span`
123 | font-weight: 600;
124 | font-size: 16px;
125 | color: ${({ theme }) => theme.text};
126 | `
127 | const Status = styled.span`
128 | font-size: 12px;
129 | color: ${({ theme }) => theme.text};
130 | `
131 |
132 | const MessageBox = styled.div`
133 | display: flex;
134 | align-items: center;
135 | justify-content: space-between;
136 | width: 100%;
137 | background-color: ${({ theme }) => theme.bg};
138 | border-radius: 12px;
139 | padding: 16px 8px;
140 | `
141 |
142 | const Message = styled.input`
143 | border: none;
144 | flex: 1;
145 | height: 100%;
146 | width: 100%;
147 | background-color: transparent;
148 | color: ${({ theme }) => theme.text};
149 | font-size: 16px;
150 | padding: 0 16px;
151 | &:focus {
152 | outline: none;
153 | }
154 | `;
155 |
156 |
157 | const ChatContainer = ({showChat,setShowChat}) => {
158 |
159 | //get the window size and hide the chat container for mobile and dislay it for desktop
160 | const [width, setWidth] = React.useState(window.innerWidth)
161 | const breakpoint = 768
162 |
163 | useEffect(() => {
164 | const handleWindowResize = () => setWidth(window.innerWidth)
165 | window.addEventListener("resize", handleWindowResize)
166 | return () => window.removeEventListener("resize", handleWindowResize)
167 | }, [])
168 |
169 | const messagesEndRef = useRef(null)
170 | const scrollToBottom = () => {
171 | messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
172 | }
173 | useEffect(scrollToBottom);
174 |
175 | return (
176 |
177 |
178 | {width < breakpoint &&
179 | setShowChat(false)}>
180 |
181 | }
182 |
183 |
184 | John Doe
185 | Online
186 |
187 |
188 |
189 | hola fghtdfhhhhhhhhhhhhhhhh trw twr twrtrw44t rwerewty rewyetryetyetyetryery ertyetyertyertyetry e5ty5et444444444444y 5y54ey5yy y53y5e4ye45
190 |
191 | hola fghtdfhhhhhhhhhhhhhhhh trw twr twrtrw44t rwerewty rewyetryetyetyetryery ertyetyertyertyetry e5ty5et444444444444y 5y54ey5yy y53y5e4ye45
192 |
193 | hola fghtdfhhhhhhhhhhhhhhhh trw twr twrtrw44t rwerewty rewyetryetyetyetryery ertyetyertyertyetry e5ty5et444444444444y 5y54ey5yy y53y5e4ye45
194 |
195 | hola fghtdfhhhhhhhhhhhhhhhh trw twr twrtrw44t rwerewty rewyetryetyetyetryery ertyetyertyertyetry e5ty5et444444444444y 5y54ey5yy y53y5e4ye45
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 | )
213 | }
214 |
215 | export default ChatContainer
--------------------------------------------------------------------------------
/server/controllers/user.js:
--------------------------------------------------------------------------------
1 | import { createError } from "../error.js";
2 | import User from "../models/User.js";
3 | import Project from "../models/Project.js";
4 | import Teams from "../models/Teams.js";
5 | import Notifications from "../models/Notifications.js";
6 |
7 | export const update = async (req, res, next) => {
8 | if (req.params.id === req.user.id) {
9 | try {
10 | const updatedUser = await User.findByIdAndUpdate(
11 | req.params.id,
12 | {
13 | $set: req.body,
14 | },
15 | { new: true }
16 | );
17 | res.status(200).json(updatedUser);
18 | } catch (err) {
19 | next(err);
20 | }
21 | } else {
22 | return next(createError(403, "You can update only your account!"));
23 | }
24 | }
25 |
26 | export const deleteUser = async (req, res, next) => {
27 | if (req.params.id === req.user.id) {
28 | try {
29 | await User.findByIdAndDelete(req.params.id);
30 | res.status(200).json("User has been deleted.");
31 | } catch (err) {
32 | next(err);
33 | }
34 | } else {
35 | return next(createError(403, "You can delete only your account!"));
36 | }
37 | }
38 |
39 | export const findUser = async (req, res, next) => {
40 | try {
41 | const user = await User.findById(req.params.id);
42 | res.status(200).json(user);
43 | } catch (err) {
44 | next(err);
45 | }
46 | }
47 |
48 | export const getUser = async (req, res, next) => {
49 | //if the req.user id is not present then it will give a message of user not authenticated
50 |
51 | try {
52 | const user = await User.findById(req.user.id).populate("notifications").populate({
53 | path: "teams",
54 | populate: {
55 | path: "members.id",
56 | select: "_id name email",
57 | }
58 | }).populate("projects").populate("works").populate("tasks");
59 | //extract the notification from the user and send it to the client
60 | console.log(user)
61 | res.status(200).json(user);
62 | } catch (err) {
63 | console.log(req.user)
64 | next(err);
65 | }
66 | }
67 |
68 | // get notifications of the user
69 |
70 | export const getNotifications = async (req, res, next) => {
71 | try {
72 | const user = await User.findById(req.user.id);
73 | //extract the notification from the user and send it to the client
74 | const notifications = user.notifications;
75 | const notificationArray = [];
76 | for (let i = 0; i < notifications.length; i++) {
77 | const notification = await Notifications.findById(notifications[i]);
78 | notificationArray.push(notification);
79 | }
80 | res.status(200).json(notificationArray);
81 | } catch (err) {
82 | next(err);
83 | }
84 | }
85 |
86 |
87 |
88 | //fetch all the works of the user
89 | export const getWorks = async (req, res, next) => {
90 | try {
91 | const user = await User.findById(req.user.id).populate(
92 | {
93 | path: "works",
94 | populate: {
95 | path: "tasks",
96 | populate: {
97 | path: "members",
98 | select: "name img",
99 | },
100 | }
101 | }).populate({
102 | path: "works",
103 | populate: {
104 | path: "creatorId",
105 | select: "name img",
106 | }
107 | })
108 | .sort({ updatedAt: -1 });;
109 | if (!user) return next(createError(404, "User not found!"));
110 | //store all the works of the user in an array and send it to the client
111 | const works = [];
112 | await Promise.all(
113 | user.works.map(async (work) => {
114 | works.push(work);
115 | })
116 | ).then(() => {
117 | res.status(200).json(works);
118 | });
119 | } catch (err) {
120 | next(err);
121 | }
122 | };
123 |
124 | //get all the tasks of the user
125 | export const getTasks = async (req, res, next) => {
126 | try {
127 | const user = await User.findById(req.user.id).populate({
128 | path: "tasks",
129 | populate: {
130 | path: "members",
131 | select: "name img",
132 | }
133 | }).sort({ end_date: 1 });
134 | if (!user) return next(createError(404, "User not found!"));
135 | //store all the tasks of the user in an array and send it to the client
136 | const tasks = [];
137 | await Promise.all(
138 | user.tasks.map(async (task) => {
139 | tasks.push(task);
140 | })
141 | ).then(() => {
142 | res.status(200).json(tasks);
143 | });
144 | } catch (err) {
145 | next(err);
146 | }
147 | };
148 |
149 |
150 |
151 | export const subscribe = async (req, res, next) => {
152 | try {
153 | await User.findByIdAndUpdate(req.user.id, {
154 | $push: { subscribedUsers: req.params.id },
155 | });
156 | await User.findByIdAndUpdate(req.params.id, {
157 | $inc: { subscribers: 1 },
158 | });
159 | res.status(200).json("Subscription successfull.")
160 | } catch (err) {
161 | next(err);
162 | }
163 | }
164 |
165 | export const unsubscribe = async (req, res, next) => {
166 | try {
167 | try {
168 | await User.findByIdAndUpdate(req.user.id, {
169 | $pull: { subscribedUsers: req.params.id },
170 | });
171 | await User.findByIdAndUpdate(req.params.id, {
172 | $inc: { subscribers: -1 },
173 | });
174 | res.status(200).json("Unsubscription successfull.")
175 | } catch (err) {
176 | next(err);
177 | }
178 | } catch (err) {
179 | next(err);
180 | }
181 | }
182 |
183 | //find project id from user and get it from projects collection and send it to client
184 | export const getUserProjects = async (req, res, next) => {
185 | try {
186 | const user = await User.findById(req.user.id).populate("projects")
187 | const projects = []
188 | await Promise.all(user.projects.map(async (project) => {
189 | await Project.findById(project).populate("members.id", "_id name email img").then((project) => {
190 | projects.push(project)
191 | }).catch((err) => {
192 | next(err)
193 | })
194 | })).then(() => {
195 | res.status(200).json(projects)
196 | }).catch((err) => {
197 | next(err)
198 | })
199 | } catch (err) {
200 | next(err);
201 | }
202 | }
203 |
204 | //find team id from user and get it from teams collection and send it to client
205 | export const getUserTeams = async (req, res, next) => {
206 | try {
207 | const user = await User.findById(req.user.id).populate("teams")
208 | const teams = []
209 | await Promise.all(user.teams.map(async (team) => {
210 | await Teams.findById(team.id).then((team) => {
211 | teams.push(team)
212 | }).catch((err) => {
213 | next(err)
214 | })
215 | })).then(() => {
216 | res.status(200).json(teams)
217 | }).catch((err) => {
218 | next(err)
219 | })
220 | } catch (err) {
221 | next(err);
222 | }
223 | }
224 |
225 | //find user from email and send it to client
226 | export const findUserByEmail = async (req, res, next) => {
227 | const email = req.params.email;
228 | const users = [];
229 | try {
230 | await User.findOne({ email: { $regex: email, $options: "i" } }).then((user) => {
231 | if(user!=null)
232 | {
233 | users.push(user);
234 | res.status(200).json(users);
235 | }else{
236 | res.status(201).json({message:"No user found"});
237 | }
238 | }).catch((err) => {
239 | next(err)
240 | })
241 | } catch (err) {
242 | next(err);
243 | }
244 | }
--------------------------------------------------------------------------------
/client/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import styled from "styled-components";
3 | import SearchIcon from "@mui/icons-material/Search";
4 | import { useSelector } from "react-redux";
5 | import AccountCircleOutlinedIcon from "@mui/icons-material/AccountCircleOutlined";
6 | import SignUp from "./SignUp";
7 | import SignIn from "./SignIn";
8 | import MenuIcon from "@mui/icons-material/Menu";
9 | import { IconButton } from "@mui/material";
10 | import { Forum, NotificationsRounded } from "@mui/icons-material";
11 | import Badge from "@mui/material/Badge";
12 | import { useDispatch } from "react-redux";
13 | import Avatar from "@mui/material/Avatar";
14 | import AccountDialog from "./AccountDialog";
15 | import NotificationDialog from "./NotificationDialog";
16 | import { getUsers, notifications } from "../api/index";
17 | import { openSnackbar } from "../redux/snackbarSlice";
18 | import { logout } from "../redux/userSlice";
19 | import { useNavigate } from "react-router-dom";
20 |
21 | const Container = styled.div`
22 | position: sticky;
23 | top: 0;
24 | height: 56px;
25 | margin: 6px 6px 0px 6px;
26 | border-radius: 12px;
27 | z-index: 99;
28 | box-shadow: 0 0 16px 0 rgba(0, 0, 0, 0.06);
29 | background-color: ${({ theme }) => theme.bgLighter};
30 | @media screen and (max-width: 480px) {
31 | margin: 0px 0px 0px 0px;
32 | height: 60px;
33 | }
34 | `;
35 | const Wrapper = styled.div`
36 | height: 100%;
37 | display: flex;
38 | align-items: center;
39 | justify-content: flex-end;
40 | padding: 0px 14px;
41 | @media screen and (max-width: 480px) {
42 | padding: 0px 4px;
43 | }
44 | position: relative;
45 | `;
46 |
47 | const IcoButton = styled(IconButton)`
48 | color: ${({ theme }) => theme.textSoft} !important;
49 | `;
50 |
51 | const Search = styled.div`
52 | width: 40%;
53 | @media screen and (max-width: 480px) {
54 | width: 50%;
55 | }
56 | left: 0px;
57 | right: 0px;
58 | margin: auto;
59 | display: flex;
60 | align-items: center;
61 | justify-content: space-between;
62 | border-radius: 100px;
63 | color: ${({ theme }) => theme.textSoft};
64 | background-color: ${({ theme }) => theme.bgDark};
65 | `;
66 | const Input = styled.input`
67 | width: 100%;
68 | border: none;
69 | font-size: 16px;
70 | padding: 10px 20px;
71 | border-radius: 100px;
72 | background-color: transparent;
73 | outline: none;
74 | color: ${({ theme }) => theme.textSoft};
75 | `;
76 |
77 | const Button = styled.button`
78 | padding: 5px 18px;
79 | background-color: transparent;
80 | border: 1px solid ${({ theme }) => theme.primary};
81 | color: ${({ theme }) => theme.primary};
82 | border-radius: 3px;
83 | font-weight: 500;
84 | cursor: pointer;
85 | display: flex;
86 | align-items: center;
87 | gap: 5px;
88 | font-size: 15px;
89 | border-radius: 100px;
90 | transition: all 0.3s ease;
91 | &:hover {
92 | background-color: ${({ theme }) => theme.primary};
93 | color: ${({ theme }) => theme.text};
94 | }
95 | `;
96 |
97 | const User = styled.div`
98 | display: flex;
99 | align-items: center;
100 | gap: 12px;
101 | font-weight: 500;
102 | font-size: 18px;
103 | padding: 0px 8px;
104 | color: ${({ theme }) => theme.text};
105 | @media (max-width: 800px) {
106 | gap: 2px;
107 | }
108 | `;
109 |
110 |
111 |
112 | const Navbar = ({ menuOpen, setMenuOpen }) => {
113 | const [SignUpOpen, setSignUpOpen] = useState(false);
114 | const [SignInOpen, setSignInOpen] = useState(false);
115 | const [verifyEmail, setVerifyEmail] = useState(false);
116 | const dispatch = useDispatch();
117 | const { currentUser } = useSelector((state) => state.user);
118 | const [users, setUsers] = useState([]);
119 | const token = localStorage.getItem("token");
120 | const navigate = useNavigate();
121 |
122 | const [notification, setNotification] = useState([]);
123 | useEffect(() => {
124 | getUsers(token).then((res) => {
125 | setUsers(res.data);
126 | }).catch((err) => {
127 | if (err.response.status === 401) {
128 | dispatch(logout())
129 | }
130 | });
131 | }, [dispatch]);
132 |
133 |
134 | const getNotifications = async () => {
135 | try {
136 | notifications(token).then((res) => {
137 | setNotification(res.data);
138 | console.log(notification);
139 | });
140 | } catch (error) {
141 | console.log(error);
142 | }
143 | };
144 |
145 | useEffect(() => {
146 | getNotifications();
147 | }, []);
148 |
149 | useEffect(() => {
150 | if (!currentUser && !SignUpOpen) {
151 | setSignInOpen(true);
152 | setSignUpOpen(false);
153 | } else if (!currentUser && SignUpOpen) {
154 | setSignInOpen(false);
155 | setSignUpOpen(true);
156 | }
157 | console.log(currentUser);
158 | if (currentUser && !currentUser.verified) {
159 | setVerifyEmail(true);
160 | } else {
161 | setVerifyEmail(false);
162 | }
163 | }, [currentUser, SignInOpen, SignUpOpen, setVerifyEmail, users]);
164 |
165 | //Open the account dialog
166 | const [anchorEl, setAnchorEl] = useState(null);
167 | const open = Boolean(anchorEl);
168 | const id = open ? "simple-popover" : undefined;
169 | const handleClick = (event) => {
170 | setAnchorEl(event.currentTarget);
171 | };
172 |
173 | const handleClose = () => {
174 | setAnchorEl(null);
175 | };
176 |
177 | //Open the notification dialog
178 | const [anchorEl2, setAnchorEl2] = useState(null);
179 | const open2 = Boolean(anchorEl2);
180 | const id2 = open2 ? "simple-popover" : undefined;
181 | const notificationClick = (event) => {
182 | setAnchorEl2(event.currentTarget);
183 | };
184 |
185 | const notificationClose = () => {
186 | setAnchorEl2(null);
187 | };
188 |
189 | return (
190 | <>
191 |
192 |
193 | setMenuOpen(!menuOpen)}>
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 | {currentUser ? (
202 | <>
203 | navigate('/chats')}>
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
224 |
229 | {currentUser.name.charAt(0)}
230 |
231 |
232 |
233 | >
234 | ) : (
235 |
238 | )}
239 |
240 |
241 |
242 | {currentUser && (
243 |
250 | )}
251 | {currentUser && (
252 |
260 | )}
261 | >
262 | );
263 | };
264 |
265 | export default Navbar;
266 |
--------------------------------------------------------------------------------
/client/src/components/InviteMembers.jsx:
--------------------------------------------------------------------------------
1 | import { CloseRounded, SearchOutlined, SendRounded } from "@mui/icons-material";
2 | import { Modal } from "@mui/material";
3 | import React, { useState } from "react";
4 | import styled from "styled-components";
5 | import { Avatar } from "@mui/material";
6 | import { useSelector } from "react-redux";
7 | import { inviteTeamMembers, inviteProjectMembers, searchUsers } from "../api/index";
8 | import { openSnackbar } from "../redux/snackbarSlice";
9 | import { useDispatch } from "react-redux";
10 | import CircularProgress from "@mui/material/CircularProgress";
11 |
12 | const Container = styled.div`
13 | width: 100%;
14 | height: 100%;
15 | position: absolute;
16 | top: 0;
17 | left: 0;
18 | background-color: #000000a7;
19 | display: flex;
20 | justify-content: center;
21 | `;
22 |
23 | const Wrapper = styled.div`
24 | width: 430px;
25 | height: min-content;
26 | border-radius: 16px;
27 | background-color: ${({ theme }) => theme.bgLighter};
28 | color: ${({ theme }) => theme.text};
29 | padding: 10px;
30 | display: flex;
31 | margin-top: 80px;
32 | flex-direction: column;
33 | position: relative;
34 | @media (max-width: 768px) {
35 | width: 100%;
36 | }
37 | `;
38 |
39 | const Title = styled.div`
40 | font-size: 16px;
41 | font-weight: 500;
42 | color: ${({ theme }) => theme.text};
43 | margin: 12px;
44 | `;
45 |
46 | const Search = styled.div`
47 | margin: 6px 6px;
48 | display: flex;
49 | align-items: center;
50 | justify-content: space-between;
51 | border-radius: 12px;
52 | color: ${({ theme }) => theme.textSoft};
53 | background-color: ${({ theme }) => theme.bgDark};
54 | `;
55 |
56 | const Input = styled.input`
57 | width: 100%;
58 | border: none;
59 | font-size: 14px;
60 | padding: 10px 20px;
61 | border-radius: 100px;
62 | background-color: transparent;
63 | outline: none;
64 | color: ${({ theme }) => theme.textSoft};
65 | `;
66 |
67 | const UsersList = styled.div`
68 | padding: 18px 8px;
69 | display: flex;
70 | margin-bottom: 12px;
71 | flex-direction: column;
72 | gap: 12px;
73 | border-radius: 12px;
74 | color: ${({ theme }) => theme.textSoft};
75 | `;
76 |
77 | const MemberCard = styled.div`
78 | display: flex;
79 | align-items: center;
80 | gap: 6px;
81 | justify-content: space-between;
82 | @media (max-width: 768px) {
83 | gap: 6px;
84 | }
85 | `;
86 | const UserData = styled.div`
87 | display: flex;
88 | align-items: center;
89 | gap: 12px;
90 | `;
91 |
92 | const Details = styled.div`
93 | gap: 4px;
94 | `;
95 |
96 | const Name = styled.div`
97 | font-size: 13px;
98 | font-weight: 500;
99 | max-width: 100px;
100 | word-wrap: break-word;
101 | color: ${({ theme }) => theme.textSoft};
102 | `;
103 |
104 | const EmailId = styled.div`
105 | font-size: 10px;
106 | font-weight: 400;
107 | max-width: 100px;
108 | word-wrap: break-word;
109 | color: ${({ theme }) => theme.textSoft + "99"};
110 | `;
111 |
112 | const Flex = styled.div`
113 | display: flex;
114 | flex-direction: row;
115 | gap: 2px;
116 | @media (max-width: 768px) {
117 | display: flex;
118 | flex-direction: column;
119 | align-items: center;
120 | }
121 | `;
122 |
123 | const Access = styled.div`
124 | padding: 6px 10px;
125 | border-radius: 12px;
126 | display: flex;
127 | align-items: center;
128 | justify-content: center;
129 | background-color: ${({ theme }) => theme.bgDark};
130 | `;
131 |
132 | const Select = styled.select`
133 | border: none;
134 | font-size: 12px;
135 | background-color: transparent;
136 | outline: none;
137 | color: ${({ theme }) => theme.text};
138 | background-color: ${({ theme }) => theme.bgDark};
139 | `;
140 |
141 | const Role = styled.div`
142 | background-color: ${({ theme }) => theme.bgDark};
143 | border-radius: 12px;
144 | `;
145 |
146 | const InviteButton = styled.button`
147 | padding: 6px 8px;
148 | background-color: transparent;
149 | border: 1px solid ${({ theme }) => theme.primary};
150 | color: ${({ theme }) => theme.primary};
151 | border-radius: 1px;
152 | font-weight: 500;
153 | cursor: pointer;
154 | display: flex;
155 | align-items: center;
156 | gap: 5px;
157 | font-size: 11px;
158 | border-radius: 10px;
159 | transition: all 0.3s ease;
160 | &:hover {
161 | background-color: ${({ theme }) => theme.primary};
162 | color: ${({ theme }) => theme.text};
163 | }
164 | `;
165 |
166 | const InviteMembers = ({ setInvitePopup, id, teamInvite }) => {
167 | const [search, setSearch] = React.useState("");
168 | const [users, setUsers] = React.useState([]);
169 | const [message, setMessage] = React.useState("");
170 | const { currentUser } = useSelector((state) => state.user);
171 | const token = localStorage.getItem("token");
172 |
173 | const [role, setRole] = React.useState("Member");
174 | const [access, setAccess] = React.useState("View Only");
175 | const [Loading, setLoading] = React.useState(false);
176 |
177 | const handleSearch = async (e) => {
178 | setSearch(e.target.value);
179 | searchUsers(e.target.value, token)
180 | .then((res) => {
181 | if (res.status === 200) {
182 | setUsers(res.data);
183 | setMessage("");
184 | }
185 | else {
186 | setUsers([]);
187 | setMessage(res.status);
188 | }
189 | })
190 | .catch((err) => {
191 | setUsers([]);
192 | setMessage(err.message);
193 | });
194 | };
195 |
196 | const handleInvite = async (user) => {
197 | setLoading(true);
198 | const User = {
199 | id: user._id,
200 | name: user.name,
201 | email: user.email,
202 | role: role,
203 | access: access,
204 | };
205 | console.log(User);
206 | if (teamInvite) {
207 | inviteTeamMembers(id, User, token)
208 | .then((res) => {
209 | console.log(res);
210 | if (res.status === 200)
211 | dispatch(openSnackbar({ message: `Invitation sent to ${user.name}`, type: "success" }));
212 | setLoading(false);
213 | })
214 | .catch((err) => {
215 | dispatch(openSnackbar({ message: err.message, type: "error" }));
216 | setLoading(false);
217 | console.log(err);
218 | });
219 | } else {
220 | console.log("project");
221 | inviteProjectMembers(id, User, token)
222 | .then((res) => {
223 | if (res.status === 200)
224 | dispatch(openSnackbar({ message: `Invitation sent to ${user.name}`, type: "success" }));
225 | setLoading(false);
226 | })
227 | .catch((err) => {
228 | console.log(err);
229 | dispatch(openSnackbar({ message: err.message, type: "error" }));
230 | setLoading(false);
231 | });
232 | }
233 | };
234 |
235 | const dispatch = useDispatch();
236 |
237 | return (
238 | setInvitePopup(false)}>
239 |
240 |
241 | setInvitePopup(false)}
250 | />
251 | Invite Members
252 |
253 | handleSearch(e)}
256 | />
257 |
261 |
262 | {message && {message}
}
263 |
264 | {users.map((user) => (
265 |
266 |
267 |
268 | {user.name[0]}
269 |
270 |
271 | {user.name}
272 | {user.email}
273 |
274 |
275 |
276 |
277 |
284 |
285 |
286 | setRole(e.target.value)} />
287 |
288 |
289 |
290 | handleInvite(user)}>
291 | {Loading ? (
292 |
293 | ) : (<>
294 |
295 | Invite>
296 | )}
297 |
298 |
299 | ))}
300 |
301 |
302 |
303 |
304 |
305 | );
306 | };
307 |
308 | export default InviteMembers;
309 |
--------------------------------------------------------------------------------
/client/src/components/WorkDetails.jsx:
--------------------------------------------------------------------------------
1 | import { IconButton, Modal, Snackbar } from "@mui/material";
2 | import React, { useState, useEffect } from "react";
3 | import CircularProgress from "@mui/material/CircularProgress";
4 | import styled from "styled-components";
5 | import {
6 | CloseRounded,
7 | } from "@mui/icons-material";
8 | import { Avatar } from "@mui/material";
9 | import { useDispatch } from "react-redux";
10 | import { tagColors } from "../data/data";
11 | import TaskCard from "./TaskCard";
12 |
13 | const Container = styled.div`
14 | width: 100%;
15 | height: 100%;
16 | overflow-y: scroll !important;
17 | position: absolute;
18 | top: 0;
19 | left: 0;
20 | background-color: #000000a7;
21 | display: flex;
22 | justify-content: center;
23 | align-item: center;
24 | `;
25 |
26 | const Wrapper = styled.div`
27 | width: 100%;
28 | height: min-content;
29 | margin: 2%;
30 | max-width: 800px;
31 | border-radius: 8px;
32 | background-color: ${({ theme }) => theme.bgLighter};
33 | color: ${({ theme }) => theme.text};
34 | display: flex;
35 | flex-direction: column;
36 | position: relative;
37 | `;
38 |
39 | const FlexDisplay = styled.div`
40 | display: flex;
41 | justify-content: space-between;
42 | align-items: center;
43 | `;
44 |
45 | const Top = styled.div`
46 | padding: 6px 16px 0px 16px;
47 | `;
48 |
49 | const Title = styled.div`
50 | font-size: 20px;
51 | @media screen and (max-width: 480px) {
52 | font-size: 20px;
53 | }
54 | font-weight: 500;
55 | color: ${({ theme }) => theme.text};
56 | flex: 7;
57 | line-height: 1.5;
58 | overflow: hidden;
59 | text-overflow: ellipsis;
60 | display: -webkit-box;
61 | -webkit-line-clamp: 2; /* number of lines to show */
62 | line-clamp: 2;
63 | -webkit-box-orient: vertical;
64 | `;
65 |
66 | const Desc = styled.div`
67 | font-size: 13px;
68 | font-weight: 400;
69 | color: ${({ theme }) => theme.soft2};
70 | flex: 7;
71 | line-height: 1.5;
72 | overflow: hidden;
73 | text-overflow: ellipsis;
74 | display: -webkit-box;m
75 | -webkit-line-clamp: 2; /* number of lines to show */
76 | line-clamp: 2;
77 | -webkit-box-orient: vertical;
78 | `;
79 |
80 | const Tags = styled.div`
81 | display: flex;
82 | flex-wrap: wrap;
83 | flex-direction: row;
84 | gap: 4px;
85 | margin-top: 8px;
86 | `;
87 |
88 | const Tag = styled.div`
89 | padding: 3px 8px;
90 | border-radius: 8px;
91 | color: ${({ tagColor, theme }) => tagColor + theme.lightAdd};
92 | background-color: ${({ tagColor, theme }) => tagColor + "10"};
93 | font-size: 12px;
94 | font-weight: 500;
95 | `;
96 |
97 | const Members = styled.div`
98 | display: flex;
99 | flex-direction: row;
100 | align-items: center;
101 | `;
102 |
103 | const Hr = styled.hr`
104 | margin: 2px 0px;
105 | border: 0.5px solid ${({ theme }) => theme.soft + "99"};
106 | `;
107 |
108 | const Bottom = styled.div`
109 | display: flex;
110 | justify-content: space-between;
111 | align-items: center;
112 | margin: 20px 0px 14px 0px;
113 | `;
114 |
115 | const Table = styled.div`
116 | width: 100%;
117 | display: flex;
118 | flex-direction: column;
119 | padding: 6px 10px;
120 | `;
121 |
122 | const TableHeader = styled.div`
123 | display: flex;
124 | justify-content: space-between;
125 | align-items: center;
126 | padding: 12px 10px;
127 | gap: 8px;
128 | border-radius: 8px 8px 0px 0px;
129 | border: 1.8px solid ${({ theme }) => theme.soft + "99"};
130 | background-color: ${({ theme }) => theme.card};
131 | color: ${({ theme }) => theme.text};
132 | cursor: pointer;
133 | background-color: ${({ theme }) => theme.bgDark};
134 | `;
135 |
136 |
137 | const No = styled.div`
138 | width: 4%;
139 | font-size: 12px;
140 | text-overflow: ellipsis;
141 | font-weight: 500;
142 | color: ${({ theme }) => theme.soft2};
143 | display: -webkit-box;
144 | -webkit-line-clamp: 5; /* number of lines to show */
145 | line-clamp: 5;
146 | -webkit-box-orient: vertical;
147 |
148 | ${({ completed, theme }) =>
149 | completed === "Completed" &&
150 | `
151 | text-decoration: line-through;
152 | `}
153 | `;
154 |
155 | const Task = styled.div`
156 | width: 50%;
157 | font-size: 12px;
158 | font-weight: 500;
159 | color: ${({ theme }) => theme.soft2};
160 | display: -webkit-box;
161 | -webkit-line-clamp: 5; /* number of lines to show */
162 | line-clamp: 5;
163 | -webkit-box-orient: vertical;
164 | padding: 6px;
165 |
166 | ${({ completed, theme }) =>
167 | completed === "Completed" &&
168 | `
169 | text-decoration: line-through;
170 | `}
171 | `;
172 |
173 | const Date = styled.div`
174 | font-size: 12px;
175 | font-weight: 500;
176 | text-align: center;
177 | text-overflow: ellipsis;
178 | width: 14%;
179 | color: ${({ theme }) => theme.soft2};
180 | ${({ enddate, theme }) =>
181 | enddate &&
182 | `
183 | color: ${theme.pink};
184 | `}
185 | display: -webkit-box;
186 | -webkit-line-clamp: 5; /* number of lines to show */
187 | line-clamp: 5;
188 | -webkit-box-orient: vertical;
189 |
190 | ${({ completed, theme }) =>
191 | completed === "Completed" &&
192 | `
193 | text-decoration: line-through;
194 | `}
195 | `;
196 |
197 |
198 | const WorkDetails = ({ setOpenWork, work }) => {
199 | const dispatch = useDispatch();
200 | const [task, setTask] = useState(work.tasks);
201 | const [tag, setTag] = useState(work.tags);
202 | const [completed, setCompleted] = useState(0);
203 | const [progress, setProgress] = useState(0);
204 | const [members, setMembers] = useState([]);
205 |
206 | useEffect(() => {
207 | let count = 0;
208 | let Members = [];
209 | task.forEach((item) => {
210 | if (item.status === "Completed") {
211 | count++;
212 | }
213 |
214 | if (item.members.length > 0) {
215 | item.members.forEach((items) => {
216 | //check if the same member is already present in the array
217 | let isPresent = Members.some((member) => member._id === items._id);
218 | if (!isPresent)
219 | {
220 | Members.push(items);
221 | }
222 |
223 | });
224 | }
225 | });
226 | setCompleted(count);
227 | setProgress((completed / task.length) * 100);
228 | setMembers(Members);
229 | }, [work]);
230 |
231 | return (
232 | setOpenWork(false)}>
233 |
234 |
235 |
236 |
237 | {work.title}
238 | setOpenWork(false)}
244 | >
245 |
246 |
247 |
248 |
249 | {work.desc}
250 |
251 |
252 | {work.tags.map((tag) => (
253 |
258 | {tag}
259 |
260 | ))}
261 |
262 |
267 | {members.slice(0, 10).map((member) => (
268 |
277 | {member.name.charAt(0)}
278 |
279 | ))}
280 | {members.length > 9 && (
281 |
289 | +{members.length - 9}
290 |
291 | )}
292 |
293 |
294 |
295 |
296 |
297 |
298 | No
299 |
302 | Tasks
303 |
304 |
305 | Start Date
306 |
307 |
308 | Deadline
309 |
310 |
311 | Status
312 |
313 |
321 | Members
322 |
323 |
324 | {task.map((item, index) => (
325 |
326 | ))}
327 |
328 |
329 |
330 |
331 |
332 | );
333 | };
334 |
335 | export default WorkDetails;
336 |
--------------------------------------------------------------------------------