├── CNAME
├── client
├── src
│ ├── services
│ │ ├── login
│ │ │ ├── signout.js
│ │ │ ├── forgotpassword.js
│ │ │ ├── signin.js
│ │ │ ├── resetpassword.js
│ │ │ └── signup.js
│ │ ├── notifications
│ │ │ └── index.js
│ │ ├── employees
│ │ │ ├── allEmployees.js
│ │ │ └── employee-details.js
│ │ ├── departments
│ │ │ └── departments.js
│ │ ├── projects
│ │ │ └── projects.js
│ │ └── tasks
│ │ │ ├── tasks.js
│ │ │ └── taskBoards.js
│ ├── font
│ │ ├── font-Bold.ttf
│ │ ├── Nunito-Bold.ttf
│ │ ├── font-Light.ttf
│ │ ├── font-Medium.ttf
│ │ ├── Nunito-Medium.ttf
│ │ ├── Nunito-Regular.ttf
│ │ └── font-Regular.ttf
│ ├── utils
│ │ ├── check-all.js
│ │ ├── email-validator.js
│ │ ├── isHeaderHidden.js
│ │ ├── localStorage.js
│ │ ├── fetchUrl.js
│ │ ├── date-handler.js
│ │ └── checkLogin.js
│ ├── containers
│ │ ├── departments
│ │ │ ├── create-depart.scss
│ │ │ ├── index.js
│ │ │ ├── create-depart-form.js
│ │ │ ├── main-section.js
│ │ │ └── styles.scss
│ │ ├── reports
│ │ │ └── index.js
│ │ ├── settings
│ │ │ └── index.js
│ │ ├── sign-up
│ │ │ ├── top-section.js
│ │ │ ├── pre-signup
│ │ │ │ ├── index.js
│ │ │ │ └── form-section.js
│ │ │ ├── OTP-verification
│ │ │ │ ├── index.js
│ │ │ │ └── form-section.js
│ │ │ └── styles.scss
│ │ ├── time-sheets
│ │ │ └── index.js
│ │ ├── sign-in
│ │ │ ├── top-section.js
│ │ │ ├── styles.scss
│ │ │ ├── top-section.scss
│ │ │ ├── index.js
│ │ │ ├── form-section.scss
│ │ │ └── form-section.js
│ │ ├── resetPassword
│ │ │ ├── styles.scss
│ │ │ ├── top-sections.js
│ │ │ ├── index.js
│ │ │ ├── form-section.scss
│ │ │ ├── top-section.scss
│ │ │ └── form-section.js
│ │ ├── employees
│ │ │ ├── styles.scss
│ │ │ ├── main-section.js
│ │ │ ├── employeeDetails.js
│ │ │ ├── index.js
│ │ │ └── employee-details.scss
│ │ ├── profile
│ │ │ ├── index.js
│ │ │ ├── main-section.js
│ │ │ └── style.scss
│ │ ├── tasks-board
│ │ │ ├── top-section.js
│ │ │ ├── empty-board.js
│ │ │ ├── subtask.js
│ │ │ ├── Task.js
│ │ │ ├── column.js
│ │ │ └── index.js
│ │ ├── tasks
│ │ │ ├── index.js
│ │ │ ├── board-card.js
│ │ │ ├── welcome-page.js
│ │ │ └── styles.scss
│ │ ├── projects
│ │ │ ├── index.js
│ │ │ ├── style.scss
│ │ │ └── main-section.js
│ │ ├── app
│ │ │ ├── index.js
│ │ │ └── all-routes.js
│ │ └── dashboard
│ │ │ ├── index.js
│ │ │ ├── top-section.js
│ │ │ └── main-section.js
│ ├── components
│ │ ├── loader
│ │ │ ├── styles.scss
│ │ │ └── index.js
│ │ ├── meta-tags
│ │ │ └── index.js
│ │ ├── dropdown
│ │ │ ├── styles.scss
│ │ │ └── index.js
│ │ ├── button
│ │ │ ├── index.js
│ │ │ └── style.scss
│ │ ├── card
│ │ │ ├── index.js
│ │ │ └── styles.scss
│ │ ├── modal
│ │ │ ├── index.js
│ │ │ └── style.scss
│ │ ├── input
│ │ │ ├── index.js
│ │ │ └── styles.scss
│ │ ├── ellipsis-menu
│ │ │ ├── styles.scss
│ │ │ └── ElipsisMenu.js
│ │ ├── header
│ │ │ ├── styles.scss
│ │ │ └── index.js
│ │ ├── otp
│ │ │ ├── styles.scss
│ │ │ └── index.js
│ │ ├── name
│ │ │ ├── styles.scss
│ │ │ └── index.js
│ │ ├── email
│ │ │ ├── styles.scss
│ │ │ └── index.js
│ │ ├── password
│ │ │ ├── styles.scss
│ │ │ └── index.js
│ │ ├── delete-modal
│ │ │ ├── DeleteModal.js
│ │ │ └── styles.scss
│ │ ├── create-board-modal
│ │ │ └── styles.scss
│ │ ├── navbar
│ │ │ └── style.scss
│ │ └── table
│ │ │ └── styles.scss
│ ├── index.js
│ ├── redux
│ │ ├── store.js
│ │ └── reducers
│ │ │ ├── employee.js
│ │ │ ├── other.js
│ │ │ └── board.js
│ ├── data
│ │ ├── projectsData.js
│ │ ├── signup-data
│ │ │ ├── otp-verification.json
│ │ │ └── signup.json
│ │ ├── employeesData.js
│ │ ├── profileData.js
│ │ ├── signin.json
│ │ ├── tasksData.js
│ │ ├── newTaskForm.js
│ │ ├── departmentsData.js
│ │ ├── navbarData.js
│ │ ├── resetPasswordData.json
│ │ ├── taskDetails.js
│ │ ├── dashboardData.js
│ │ └── employeeDetailsData.json
│ └── index.css
├── public
│ ├── logo.png
│ ├── com_logo.png
│ ├── assests
│ │ ├── project.jpg
│ │ └── kanban-background.jpg
│ └── index.html
├── Documentation.md
├── .gitignore
├── package.json
└── README.md
├── website
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── logo192.png
│ ├── logo512.png
│ ├── manifest.json
│ └── index.html
├── src
│ ├── setupTests.js
│ ├── App.test.js
│ ├── index.css
│ ├── reportWebVitals.js
│ ├── index.js
│ ├── App.js
│ ├── App.css
│ └── logo.svg
├── .gitignore
├── package.json
└── README.md
├── server
├── server.js
├── Middilewares
│ ├── checkOTP.js
│ ├── checkLogin.js
│ └── jwt_auth.js
├── db
│ ├── connect.js
│ ├── departmentModel.js
│ ├── reportModel.js
│ ├── projectModel.js
│ ├── employeeModel.js
│ ├── taskBoardModel.js
│ └── taskModel.js
├── utils
│ ├── sendOtp.js
│ ├── apiFeatures.js
│ └── replaceIdKey.js
├── Routers
│ ├── reportRoutes.js
│ ├── projectRoutes.js
│ ├── departmentRoutes.js
│ ├── taskBoardRoutes.js
│ ├── taskRoutes.js
│ └── employeeRoutes.js
├── html-template
│ └── send-otp-template.js
├── package.json
├── send-in-blue
│ └── index.js
├── app.js
├── Scripts
│ └── index.js
├── .gitignore
└── Controllers
│ ├── reportController.js
│ ├── department.js
│ ├── project.js
│ └── taskBoard.js
├── .github
└── workflows
│ └── client.yml
└── README.md
/CNAME:
--------------------------------------------------------------------------------
1 | tote-web.com
--------------------------------------------------------------------------------
/client/src/services/login/signout.js:
--------------------------------------------------------------------------------
1 | export const signout = ()=>{
2 | window.localStorage.clear();
3 | }
--------------------------------------------------------------------------------
/website/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/public/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/public/logo.png
--------------------------------------------------------------------------------
/client/public/com_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/public/com_logo.png
--------------------------------------------------------------------------------
/website/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/website/public/favicon.ico
--------------------------------------------------------------------------------
/website/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/website/public/logo192.png
--------------------------------------------------------------------------------
/website/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/website/public/logo512.png
--------------------------------------------------------------------------------
/client/src/font/font-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/font-Bold.ttf
--------------------------------------------------------------------------------
/client/src/font/Nunito-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/Nunito-Bold.ttf
--------------------------------------------------------------------------------
/client/src/font/font-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/font-Light.ttf
--------------------------------------------------------------------------------
/client/src/font/font-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/font-Medium.ttf
--------------------------------------------------------------------------------
/client/public/assests/project.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/public/assests/project.jpg
--------------------------------------------------------------------------------
/client/src/font/Nunito-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/Nunito-Medium.ttf
--------------------------------------------------------------------------------
/client/src/font/Nunito-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/Nunito-Regular.ttf
--------------------------------------------------------------------------------
/client/src/font/font-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/src/font/font-Regular.ttf
--------------------------------------------------------------------------------
/client/src/utils/check-all.js:
--------------------------------------------------------------------------------
1 | export const checkAllTrue = (list) => {
2 | return list.reduce((acc, item) => acc && item, true);
3 | };
--------------------------------------------------------------------------------
/client/public/assests/kanban-background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/React-developer-want/tote_web/HEAD/client/public/assests/kanban-background.jpg
--------------------------------------------------------------------------------
/client/src/containers/departments/create-depart.scss:
--------------------------------------------------------------------------------
1 | .create-department-formSection form{
2 | display: flex;
3 | flex-direction: column;
4 | gap: 20px;
5 | }
--------------------------------------------------------------------------------
/client/src/components/loader/styles.scss:
--------------------------------------------------------------------------------
1 | .loader-component{
2 | height: 80vh;
3 | display: flex;
4 | justify-content: center;
5 | align-items: center;
6 | }
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const app = require('./app');
2 | require('./db/connect');
3 | const port = process.env.PORT || 5000;
4 |
5 | app.listen(port, ()=>{
6 | console.log("server is running on port : ", port);
7 | })
--------------------------------------------------------------------------------
/client/src/containers/reports/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Reports = () => {
4 | return (
5 |
6 | comming soon
7 |
8 | )
9 | }
10 |
11 | export default Reports
12 |
--------------------------------------------------------------------------------
/client/src/components/meta-tags/index.js:
--------------------------------------------------------------------------------
1 | import { Helmet } from 'react-helmet';
2 |
3 | const MetaTags = (props) => {
4 | return
5 | {props.title}
6 |
7 | };
8 |
9 | export default MetaTags;
--------------------------------------------------------------------------------
/client/src/containers/settings/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const Settings = () => {
4 | return (
5 |
6 | comming soon
7 |
8 | )
9 | }
10 |
11 | export default Settings
12 |
--------------------------------------------------------------------------------
/client/src/containers/sign-up/top-section.js:
--------------------------------------------------------------------------------
1 | const TopSection = (props) => {
2 | return
5 | };
6 |
7 | export default TopSection;
--------------------------------------------------------------------------------
/client/src/containers/time-sheets/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const TimeSheets = () => {
4 | return (
5 |
6 | comming soon
7 |
8 | )
9 | }
10 |
11 | export default TimeSheets
12 |
--------------------------------------------------------------------------------
/client/src/containers/sign-in/top-section.js:
--------------------------------------------------------------------------------
1 | import './top-section.scss';
2 |
3 | const TopSection = (props) => {
4 | return
7 | };
8 |
9 | export default TopSection;
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import './index.css';
4 | import App from './containers/app';
5 |
6 | const root = ReactDOM.createRoot(document.getElementById('root'));
7 | root.render( );
8 |
--------------------------------------------------------------------------------
/client/src/containers/sign-in/styles.scss:
--------------------------------------------------------------------------------
1 | .login-page {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 100vh;
6 | }
7 |
8 | @media only screen and (max-width: 600px) {
9 | .login-page {
10 | background: #f7f7f7;
11 | }
12 | }
--------------------------------------------------------------------------------
/website/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/website/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render( );
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/client/src/containers/resetPassword/styles.scss:
--------------------------------------------------------------------------------
1 | .resetPassword-page {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 100vh;
6 | }
7 |
8 | @media only screen and (max-width: 600px) {
9 | .resetPassword-page {
10 | background: #f7f7f7;
11 | }
12 | }
--------------------------------------------------------------------------------
/client/src/utils/email-validator.js:
--------------------------------------------------------------------------------
1 | export const validateEmail = (email) => {
2 | return String(email)
3 | .toLowerCase()
4 | .match(
5 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
6 | );
7 | };
--------------------------------------------------------------------------------
/client/src/components/loader/index.js:
--------------------------------------------------------------------------------
1 | import Loaders from 'react-js-loader';
2 | import './styles.scss';
3 |
4 | const Loader = ()=>{
5 | return
6 |
7 |
8 | }
9 |
10 | export default Loader;
--------------------------------------------------------------------------------
/client/src/containers/resetPassword/top-sections.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './top-section.scss';
3 |
4 | const TopSection = (props) => {
5 | return
8 | };
9 |
10 | export default TopSection
11 |
--------------------------------------------------------------------------------
/client/src/utils/isHeaderHidden.js:
--------------------------------------------------------------------------------
1 | export const isHeaderHidden = (location) =>{
2 | const authRoutes = ['login', 'signup', 'reset-password', 'otp-verification'];
3 | const isValid = authRoutes.find((route)=> (location.split('/')[1] === route));
4 | if(isValid){
5 | return true;
6 | }
7 | return false;
8 | }
--------------------------------------------------------------------------------
/server/Middilewares/checkOTP.js:
--------------------------------------------------------------------------------
1 | const checkOtpData = (req, res, next)=>{
2 | const {email} = req.body;
3 | if(!email){
4 | res.status(400).json({
5 | status: 'failed',
6 | message: '\email\ is required'
7 | })
8 | }else{
9 | next();
10 | }
11 | }
12 |
13 | module.exports = checkOtpData;
--------------------------------------------------------------------------------
/server/db/connect.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const db = process.env.DB_CLOUD_LINK.replace(
4 | '',
5 | process.env.DB_PASSWORD
6 | );
7 |
8 | mongoose.connect(db).then(()=>{
9 | console.log("DB succesfully connected!");
10 | }).catch((error)=>{
11 | console.log("Error in db", error.message);
12 | })
--------------------------------------------------------------------------------
/client/src/utils/localStorage.js:
--------------------------------------------------------------------------------
1 | export const setLocalStorageKey = (key, value)=>{
2 | localStorage.setItem(key, value);
3 | return true;
4 | }
5 |
6 | export const getLocalStorageKey = (key) =>{
7 | return localStorage.getItem(key);
8 | }
9 |
10 | export const removeLocalStorageKey = (key) =>{
11 | localStorage.removeItem(key);
12 | return true;
13 | }
--------------------------------------------------------------------------------
/client/src/utils/fetchUrl.js:
--------------------------------------------------------------------------------
1 | export const fetchUrl = async (url, options = {}) => {
2 | try {
3 | const response = await fetch(url, options);
4 | const data = await response.json();
5 | return data;
6 | } catch (error) {
7 | console.log('API failed', url);
8 | console.log('API failed', error);
9 | return { error };
10 | }
11 | };
--------------------------------------------------------------------------------
/server/db/departmentModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const department = new mongoose.Schema({
4 | title: {
5 | type: String,
6 | required: [true, "A department should have a title."]
7 | },
8 | url: {
9 | type: String,
10 | unique: true
11 | }
12 | });
13 |
14 | module.exports = mongoose.model('departments', department);
--------------------------------------------------------------------------------
/client/src/components/dropdown/styles.scss:
--------------------------------------------------------------------------------
1 | .dropdown-component .label {
2 | color: #000;
3 | font-weight: bold;
4 | margin-bottom: 4px;
5 | }
6 |
7 | .dropdown-component .dropdown-wrapper select{
8 | width: 100%;
9 | height: auto;
10 | font-size: 16px;
11 | color: #4e4949;
12 | padding: 8px;
13 | border-radius: 5px;
14 | text-transform: capitalize;
15 | }
16 |
--------------------------------------------------------------------------------
/client/src/containers/employees/styles.scss:
--------------------------------------------------------------------------------
1 | .employees-page{
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | .employees-container{
6 | width: 100%;
7 | margin: 3rem 1rem;
8 | }
9 | }
10 |
11 | @media screen and (max-width: 700px) {
12 | .employees-page{
13 | .employees-container{
14 | margin: 3rem 0;
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/website/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/website/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/client/src/redux/store.js:
--------------------------------------------------------------------------------
1 | import { configureStore } from '@reduxjs/toolkit';
2 | import { navbarReducer } from './reducers/other';
3 | import { employeeReducer } from './reducers/employee';
4 | import { boardReducer } from './reducers/board';
5 |
6 | const store = configureStore({
7 | reducer : {
8 | navbar: navbarReducer,
9 | employee: employeeReducer,
10 | boards: boardReducer
11 | }
12 | });
13 |
14 | export default store;
--------------------------------------------------------------------------------
/website/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/client/src/redux/reducers/employee.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const employeeSlice = createSlice({
4 | name: "employee",
5 | initialState: {
6 | loggedInEmployee: {},
7 | },
8 | reducers: {
9 | setLoggedInEmployee: (state, action) => {
10 | state.loggedInEmployee = action.payload;
11 | }
12 | }
13 | })
14 |
15 | export const employeeReducer = employeeSlice.reducer;
16 | export const employeeActions = employeeSlice.actions;
--------------------------------------------------------------------------------
/client/src/components/button/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.scss';
3 |
4 | const Button = (props) => {
5 | return (
6 |
7 |
12 | {props.text}
13 |
14 |
15 | )
16 | }
17 |
18 | export default Button
19 |
--------------------------------------------------------------------------------
/client/src/components/card/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './styles.scss';
3 |
4 | const Card = (props) => {
5 | return (
6 |
7 |
8 |
{props.title}
9 |
10 | {props.children}
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default Card
18 |
--------------------------------------------------------------------------------
/server/utils/sendOtp.js:
--------------------------------------------------------------------------------
1 | const nodeMailer = require('nodemailer');
2 | require('dotenv').config();
3 |
4 | const transporter = nodeMailer.createTransport({
5 | host: process.env.SMTP_HOST,
6 | port: process.env.SMTP_PORT,
7 | auth: {
8 | user: process.env.SMTP_USER,
9 | pass: process.env.SMTP_PASS
10 | }
11 | });
12 |
13 | const generateOtp = ()=>{
14 | return Math.floor(100000 + Math.random() * 900000);
15 | }
16 |
17 | module.exports = {transporter, generateOtp};
--------------------------------------------------------------------------------
/client/src/data/projectsData.js:
--------------------------------------------------------------------------------
1 | export const mapProjectsData = (projects = []) => {
2 | return {
3 | metaData: {
4 | title: "Projects | TOTE"
5 | },
6 | mainSection: {
7 | activeProjectsList: projects?.filter((project) => project.status === 'active') || [],
8 | completedProjectsList: projects?.filter((project) => project.status === 'completed') || [],
9 | rejectedProjectsList: projects?.filter((project) => project.status === 'completed') || [],
10 | }
11 | }
12 | };
--------------------------------------------------------------------------------
/client/src/components/card/styles.scss:
--------------------------------------------------------------------------------
1 | .card-component{
2 | width: fit-content;
3 | display: flex;
4 | justify-content: center;
5 | align-items: center;
6 | .card-wrapper{
7 | width: 100%;
8 | padding: 8px 4px;
9 | background-color: #fff;
10 | -webkit-box-shadow: 1px 4px 7px 1px rgba(148,142,148,1);
11 | -moz-box-shadow: 1px 4px 7px 1px rgba(148,142,148,1);
12 | box-shadow: 1px 4px 7px 1px rgba(148,142,148,1);
13 | border-radius: 10%;
14 | }
15 | }
--------------------------------------------------------------------------------
/client/src/services/login/forgotpassword.js:
--------------------------------------------------------------------------------
1 | import { fetchUrl } from "../../utils/fetchUrl";
2 |
3 | export const forgotPassword = async (email)=>{
4 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/forgotPassword';
5 | const headers = new Headers();
6 | headers.append('Content-Type', 'application/json');
7 | const body = { email };
8 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect: 'follow' };
9 | return await fetchUrl(url, requestOptions);
10 | }
--------------------------------------------------------------------------------
/server/Middilewares/checkLogin.js:
--------------------------------------------------------------------------------
1 | const checkLoginData = (req, res, next)=>{
2 | const {email, password} = req.body;
3 | if(!email){
4 | res.status(400).json({
5 | status: 'failed',
6 | message: "\email\ is required"
7 | })
8 | }else if(!password){
9 | res.status(400).json({
10 | status: 'failed',
11 | message: '\password\ is required'
12 | })
13 | }else{
14 | next();
15 | }
16 | }
17 |
18 | module.exports = checkLoginData;
--------------------------------------------------------------------------------
/server/Routers/reportRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const verifyToken = require('../Middilewares/jwt_auth');
4 | const reportControllers = require('../Controllers/reportController');
5 |
6 | router.route('/create-report').post(verifyToken, reportControllers.createReport);
7 | router.route('/all-reports').get(verifyToken, reportControllers.getAllReports);
8 | router.route('/delete-report').delete(verifyToken, reportControllers.deleteReport);
9 |
10 | module.exports = router;
--------------------------------------------------------------------------------
/client/src/services/login/signin.js:
--------------------------------------------------------------------------------
1 | import {fetchUrl} from '../../utils/fetchUrl';
2 |
3 | export const signin = async (email, password) =>{
4 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/login';
5 | const headers = new Headers();
6 | headers.append('Content-Type', 'application/json');
7 | const body = {
8 | email,
9 | password
10 | };
11 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect: 'follow' };
12 | return await fetchUrl(url, requestOptions);
13 | }
--------------------------------------------------------------------------------
/client/src/services/login/resetpassword.js:
--------------------------------------------------------------------------------
1 | import { fetchUrl } from "../../utils/fetchUrl";
2 |
3 | export const resetPassword = async (email, otp, password) =>{
4 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/resetPassword';;
5 | const headers = new Headers();
6 | headers.append('Content-Type', 'application/json');
7 | const body = { email, otp, newPassword: password };
8 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect: 'follow' };
9 | return await fetchUrl(url, requestOptions);
10 | }
--------------------------------------------------------------------------------
/client/src/containers/employees/main-section.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useNavigate } from 'react-router-dom';
3 | import TableComponent from '../../components/table';
4 |
5 | const MainSection = (props) => {
6 | const navigate = useNavigate();
7 |
8 | const onClickRow = (row)=>{
9 | navigate(`/employees/${row.id}`);
10 | }
11 |
12 | return (
13 |
16 | )
17 | }
18 | export default MainSection;
19 |
--------------------------------------------------------------------------------
/server/html-template/send-otp-template.js:
--------------------------------------------------------------------------------
1 | module.exports.sendOTPTemplate = (otp, title) => (`
2 |
3 |
4 |
5 | TOTEWEB
6 |
7 |
8 |
9 |
${title}
10 | ${otp}
11 |
12 |
13 | `);
--------------------------------------------------------------------------------
/client/Documentation.md:
--------------------------------------------------------------------------------
1 | #employees
2 | -> list all the employees
3 | -> search an employee
4 | -> update the employee details
5 | -> delete the employee details
6 |
7 | #departments
8 | -> list all the departments
9 | -> search a department
10 | -> create new department
11 |
12 | #tasks
13 | -> a todo list
14 | -> list out upcomming , current tasks and completed tasks
15 | -> like a kanban board
16 | -> divide into three sections, upcomming, current , and completed
17 |
18 | #projects
19 | -> list all the projects which is made by me
20 | -> a project with its documentation and url
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/client/src/containers/resetPassword/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MetaTags from '../../components/meta-tags';
3 | import TopSection from './top-sections';
4 | import FormSection from './form-section';
5 |
6 | import './styles.scss';
7 |
8 | const ResetPassword = (props) => {
9 | return
10 |
11 |
12 |
13 |
14 |
15 |
16 | }
17 |
18 | export default ResetPassword
19 |
--------------------------------------------------------------------------------
/server/Routers/projectRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const projectControllers = require('../Controllers/project');
3 | const verifyToken = require('../Middilewares/jwt_auth');
4 | const router = express.Router();
5 |
6 | router.route('/create-project').post(verifyToken, projectControllers.createProject);
7 | router.route('/list-projects').get(verifyToken, projectControllers.listProjects);
8 | router.route('/project').get(verifyToken, projectControllers.getProjectDetails);
9 | router.route('/update-project').post(verifyToken, projectControllers.updateProject);
10 |
11 | module.exports = router;
--------------------------------------------------------------------------------
/website/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 reportWebVitals from './reportWebVitals';
6 |
7 | const root = ReactDOM.createRoot(document.getElementById('root'));
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/client/src/components/modal/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './style.scss';
3 |
4 | const Modal = (props) => {
5 | return (
6 |
7 |
8 |
9 |
10 | ×
11 |
12 |
13 | {props.children}
14 |
15 |
16 |
17 | )
18 | }
19 |
20 | export default Modal
21 |
--------------------------------------------------------------------------------
/website/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/server/Routers/departmentRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const verifyToken = require('../Middilewares/jwt_auth');
4 | const departControllers = require('../Controllers/department');
5 |
6 | router.route('/create-department').post(verifyToken, departControllers.createDepartment);
7 | router.route('/all-departments').get(verifyToken, departControllers.getAllDepartments);
8 | router.route('/delete-department').delete(verifyToken, departControllers.deleteDepartment);
9 | router.route('/department-count').get(verifyToken, departControllers.getDepartmentCount);
10 |
11 | module.exports = router;
--------------------------------------------------------------------------------
/website/src/App.js:
--------------------------------------------------------------------------------
1 | import logo from './logo.svg';
2 | import './App.css';
3 |
4 | function App() {
5 | return (
6 |
22 | );
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tote-backend",
3 | "version": "1.0.0",
4 | "description": "This is backend for tote application",
5 | "main": "server.js",
6 | "scripts": {
7 | "start": "nodemon server.js"
8 | },
9 | "author": "Sumit baghel",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "nodemon": "^2.0.20"
13 | },
14 | "dependencies": {
15 | "axios": "^1.4.0",
16 | "bcrypt": "^5.1.0",
17 | "cors": "^2.8.5",
18 | "dotenv": "^16.0.3",
19 | "express": "^4.18.2",
20 | "jsonwebtoken": "^9.0.0",
21 | "mongoose": "^6.8.3",
22 | "morgan": "^1.10.0",
23 | "nodemailer": "^6.9.0"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/client/src/redux/reducers/other.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 | import { isHeaderHidden } from "../../utils/isHeaderHidden";
3 |
4 | const navbarSlice = createSlice({
5 | name: "navbar",
6 | initialState: {
7 | isActive: false,
8 | isHidden: isHeaderHidden(window.location.pathname),
9 | },
10 | reducers: {
11 | setIsActive: (state, action) => {
12 | state.isActive = action.payload;
13 | },
14 | setHeaderHidden: (state, action) => {
15 | state.isHidden = action.payload;
16 | }
17 | }
18 | })
19 |
20 | export const navbarReducer = navbarSlice.reducer;
21 | export const navbarActions = navbarSlice.actions;
--------------------------------------------------------------------------------
/server/Routers/taskBoardRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const verifyToken = require('../Middilewares/jwt_auth');
4 | const boardControllers = require('../Controllers/taskBoard');
5 |
6 | router.route('/boards').post(verifyToken, boardControllers.createBoard);
7 | router.route('/boards').get(verifyToken, boardControllers.getAllBoards);
8 | router.route('/boards/:id').get(verifyToken, boardControllers.getBoardById);
9 | router.route('/boards/:id').put(verifyToken, boardControllers.updateBoardById);
10 | router.route('/boards/:id').delete(verifyToken, boardControllers.deleteBoardById);
11 |
12 | module.exports = router;
--------------------------------------------------------------------------------
/client/src/containers/sign-in/top-section.scss:
--------------------------------------------------------------------------------
1 | .login-page{
2 |
3 | .login-container {
4 | width: 700px;
5 | padding: 40px 30px;
6 | background: rgb(247 247 247);
7 | border-radius: 5px;
8 | box-shadow: 0px 2px 10px #848181;
9 | }
10 |
11 | }
12 |
13 | .login-page{
14 | .main-title{
15 | font-size: 26px;
16 | font-weight: bold;
17 | }
18 | }
19 |
20 | .login-page{
21 | .login-top {
22 | text-align: center;
23 | margin-bottom: 16px;
24 | }
25 | }
26 |
27 | @media only screen and (max-width: 700px) {
28 | .login-page {
29 | .login-container {
30 | padding: 16px;
31 | box-shadow: none;
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/client/src/containers/sign-up/pre-signup/index.js:
--------------------------------------------------------------------------------
1 | import MetaTags from '../../../components/meta-tags';
2 | import preSignupData from '../../../data/signup-data/signup.json';
3 | import TopSection from '../top-section';
4 | import FormSection from './form-section';
5 |
6 | import '../styles.scss';
7 |
8 | const Signup = () => {
9 | const { formSection, metaData, topSection } = preSignupData;
10 |
11 | return
12 |
13 |
14 |
15 |
16 |
17 |
18 | };
19 |
20 | export default Signup;
--------------------------------------------------------------------------------
/server/Routers/taskRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const verifyToken = require('../Middilewares/jwt_auth');
4 | const taskControllers = require('../Controllers/taskController');
5 |
6 | router.route('/create-task/:boardId/columns/:columnId/tasks').post(verifyToken, taskControllers.createTask);
7 | router.route('/update-task/:boardId/columns/:columnId/tasks/:taskId').post(verifyToken, taskControllers.updateTask);
8 | router.route('/update-task/:boardId/tasks/:taskId').post(verifyToken, taskControllers.dragTask);
9 | router.route('/delete-task/:boardId/tasks/:taskId').delete(verifyToken, taskControllers.deleteTask);
10 |
11 | module.exports = router;
--------------------------------------------------------------------------------
/server/db/reportModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const schema = new mongoose.Schema({
4 | title: {
5 | type: String,
6 | required: [ true, "A report must have a title." ]
7 | },
8 | description: {
9 | type: String,
10 | required: [ true, "A report must have a description." ]
11 | },
12 | sent_by: {
13 | type: mongoose.Schema.Types.ObjectId,
14 | ref: 'employees'
15 | },
16 | sent_to: {
17 | type: mongoose.Schema.Types.ObjectId,
18 | ref: 'employees'
19 | }
20 | }, {
21 | timestamps: true
22 | });
23 |
24 | const Report = mongoose.model("reports", schema);
25 |
26 | module.exports = Report;
--------------------------------------------------------------------------------
/client/src/containers/sign-up/OTP-verification/index.js:
--------------------------------------------------------------------------------
1 |
2 | import MetaTags from '../../../components/meta-tags';
3 | import otpVerificationData from '../../../data/signup-data/otp-verification.json';
4 | import TopSection from '../top-section';
5 | import FormSection from './form-section';
6 |
7 | import '../styles.scss';
8 |
9 | const OTP_VERIFICATION = () => {
10 | const { topSection, formSection, metaData } = otpVerificationData;
11 |
12 | return
13 |
14 |
15 |
16 |
17 |
18 |
19 | };
20 |
21 | export default OTP_VERIFICATION;
--------------------------------------------------------------------------------
/client/src/containers/resetPassword/form-section.scss:
--------------------------------------------------------------------------------
1 | .resetPassword-page {
2 | .form-section {
3 | margin-top: 16px;
4 | }
5 | }
6 |
7 | .resetPassword-page {
8 | .form-section {
9 | .email-component {
10 | margin-bottom: 24px;
11 | }
12 | .otp-component {
13 | margin-bottom: 24px;
14 | }
15 | .btn{
16 | margin: 24px 0px;
17 | }
18 | }
19 | }
20 |
21 | .resetPassword-page {
22 | .bottom-text {
23 | font-size: 16px;
24 | text-align: center;
25 | margin-top: 16px;
26 | font-weight: bold;
27 | }
28 | }
29 |
30 | .resetPassword-page {
31 | .bottom-text {
32 | a {
33 | text-decoration: none;
34 | margin-left: 8px;
35 | color: #086f25;
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/client/src/utils/date-handler.js:
--------------------------------------------------------------------------------
1 | export const getInputDate = (date) => {
2 | const newDate = new Date(date);
3 | let day = newDate.getDate();
4 | day = day < 10 ? '0' + day : day;
5 | let month = newDate.getMonth() + 1;
6 | month = month < 10 ? '0' + month : month;
7 | return `${newDate.getFullYear()}-${month}-${day}`;
8 | }
9 |
10 | export const getProgressPercentage = (startDate, endDate) => {
11 | const start = new Date(startDate);
12 | const end = new Date(endDate);
13 | const now = new Date();
14 | const timeDiff = now.getTime() - start.getTime();
15 | const totalTimeDiff = end.getTime() - start.getTime();
16 | const percentage = (timeDiff / totalTimeDiff) * 100;
17 | return Math.floor(percentage);
18 | }
--------------------------------------------------------------------------------
/client/src/containers/sign-in/index.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import TopSection from "./top-section";
3 | import MetaTags from "../../components/meta-tags";
4 | import FormSection from "./form-section";
5 | import Loader from '../../components/loader';
6 | import './styles.scss';
7 |
8 | const Login = (props) => {
9 | const [isLoading, setIsLoading] = useState(false);
10 |
11 | return ( isLoading ? :
12 |
13 |
14 |
15 |
16 |
17 |
18 |
)
19 | };
20 |
21 | export default Login;
--------------------------------------------------------------------------------
/server/send-in-blue/index.js:
--------------------------------------------------------------------------------
1 | const axios = require('axios');
2 |
3 | const sendMail = async (to, subject, htmlContent) => {
4 | console.log({ to, subject, htmlContent });
5 | const data = {
6 | sender: {
7 | name: 'no reply',
8 | email: process.env.EMAIL
9 | },
10 | to,
11 | subject,
12 | htmlContent
13 | };
14 |
15 | try {
16 | const response = await axios.post(process.env.SEND_IN_BLUE_URL, data, {
17 | headers: {
18 | 'Accept': 'application/json',
19 | 'API-Key': process.env.SEND_IN_BLUE_API_KEY,
20 | 'Content-Type': 'application/json'
21 | }
22 | });
23 | } catch (error) {
24 | throw new Error(error);
25 | }
26 | }
27 |
28 | module.exports = { sendMail };
--------------------------------------------------------------------------------
/website/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/client/src/containers/resetPassword/top-section.scss:
--------------------------------------------------------------------------------
1 | .resetPassword-page{
2 |
3 | .resetPassword-container {
4 | width: 700px;
5 | padding: 40px 30px;
6 | background: rgb(247 247 247);
7 | border-radius: 5px;
8 | box-shadow: 0px 2px 10px #848181;
9 | }
10 |
11 | }
12 |
13 | .resetPassword-page{
14 | .main-title{
15 | font-size: 26px;
16 | font-weight: bold;
17 | }
18 | }
19 |
20 | .resetPassword-page{
21 | .resetPassword-top {
22 | text-align: center;
23 | margin-bottom: 16px;
24 | }
25 | }
26 |
27 | @media only screen and (max-width: 700px) {
28 | .resetPassword-page {
29 | .resetPassword-container {
30 | padding: 16px;
31 | box-shadow: none;
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/client/src/data/signup-data/otp-verification.json:
--------------------------------------------------------------------------------
1 | {
2 | "metaData": {
3 | "title": "OTP | TOTE"
4 | },
5 | "topSection": {
6 | "title": "OTP Verification"
7 | },
8 | "formSection": {
9 | "inputComponents": [
10 | {
11 | "component": "otp",
12 | "details": {
13 | "label": "Please Enter The 6 Digit OTP",
14 | "placeholder": "Enter The 6 Digit OTP"
15 | }
16 | },
17 | {
18 | "component": "button",
19 | "details": {
20 | "text": "Verify OTP",
21 | "type": "submit",
22 | "button": "primary"
23 | }
24 | }
25 | ],
26 | "bottomText": "Already a member?",
27 | "linkText": "Login",
28 | "signUpLink": "/login"
29 | }
30 | }
--------------------------------------------------------------------------------
/.github/workflows/client.yml:
--------------------------------------------------------------------------------
1 | name: Client Pipeline
2 | on:
3 | push:
4 | branches:
5 | - main
6 | paths:
7 | - 'client/**' # Trigger only when code is pushed inside the 'client' folder
8 |
9 | jobs:
10 | build:
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout repository
15 | uses: actions/checkout@v2
16 |
17 | - name: Set up Node.js
18 | uses: actions/setup-node@v2
19 | with:
20 | node-version: '14'
21 |
22 | - name: Install dependencies
23 | run: cd client && npm install
24 |
25 | - name: Build client
26 | run: |
27 | cd client
28 | npm run build
29 | env:
30 | REACT_APP_BASE_URI: ${{ secrets.REACT_APP_BASE_URI }}
31 |
--------------------------------------------------------------------------------
/client/src/containers/employees/employeeDetails.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import MetaTags from '../../components/meta-tags';
3 | import FormSection from './formSection';
4 | import './employee-details.scss';
5 |
6 | const TopSection = (props) => {
7 | return
10 | }
11 |
12 | const EmployeeDetails = (props) => {
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | )
22 | }
23 |
24 | export default EmployeeDetails
25 |
--------------------------------------------------------------------------------
/server/db/projectModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const projectSchema = new mongoose.Schema({
4 | name: {
5 | type: String,
6 | required: [ true, 'A project must have a name.' ]
7 | },
8 | status: {
9 | type: String,
10 | enum: ['active', 'completed', 'rejected'],
11 | default: 'active'
12 | },
13 | start_date: {
14 | type: Date,
15 | required: [ true, 'A project must have a start_date.' ]
16 | },
17 | due_date: {
18 | type: Date,
19 | required: [ true, 'A project must have a due_date.' ]
20 | },
21 | manager_name: {
22 | type: String,
23 | Default: 'manager'
24 | },
25 | team_members: {
26 | type: [String]
27 | }
28 | });
29 |
30 | module.exports = mongoose.model('projects', projectSchema);
--------------------------------------------------------------------------------
/client/src/services/notifications/index.js:
--------------------------------------------------------------------------------
1 | import { toast } from 'react-toastify';
2 |
3 | const toastProperty = {
4 | position: "top-right",
5 | autoClose: 3000,
6 | hideProgressBar: true,
7 | closeOnClick: true,
8 | pauseOnHover: true,
9 | draggable: true,
10 | progress: undefined,
11 | theme: "colored",
12 | }
13 |
14 | export const sendSuccessNotification = (message) =>{
15 | toast.success(message,toastProperty);
16 | }
17 |
18 | export const sendErrorNotification = (message) => {
19 | toast.error(message, toastProperty);
20 | }
21 |
22 | export const sendWarningNotification = (message) => {
23 | toast.warn(message, toastProperty);
24 | }
25 |
26 | export const sendInfoNotification = (message) => {
27 | toast.info(message, toastProperty);
28 | }
--------------------------------------------------------------------------------
/client/src/components/input/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import './styles.scss';
3 |
4 | const SimpleInput = (props) => {
5 |
6 | const onChangeInput = (event) => {
7 | const {value} = event.target;
8 | props.onChange(value);
9 | }
10 |
11 | return
12 |
13 |
14 | {props.label} {props.required && * }
15 |
16 |
22 |
23 |
24 | }
25 |
26 | export default SimpleInput;
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | React App
11 |
12 |
13 |
14 | You need to enable JavaScript to run this app.
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/client/src/containers/profile/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useSelector } from 'react-redux';
3 | import MetaTags from '../../components/meta-tags';
4 | import { mapProfileData } from '../../data/profileData';
5 | import MainSection from './main-section';
6 | import './style.scss';
7 |
8 | const Profile = () => {
9 | const employee = useSelector(state => state.employee.loggedInEmployee);
10 | const profileData = mapProfileData(employee);
11 |
12 | return (
13 |
23 | )
24 | }
25 |
26 | export default Profile
27 |
--------------------------------------------------------------------------------
/server/db/employeeModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const employeeSchema = new mongoose.Schema({
4 | name : String,
5 | email : {
6 | type : String,
7 | required : [true, "An user must have an email."],
8 | unique : true
9 | },
10 | password : {
11 | type: String
12 | },
13 | otp: {
14 | type: String
15 | },
16 | phone:{
17 | type: String
18 | },
19 | address:{
20 | type: String
21 | },
22 | role:{
23 | type: String,
24 | default: 'agent'
25 | },
26 | isVerified: {
27 | type: Boolean,
28 | default: false
29 | },
30 | verification_otp: {
31 | type: String,
32 | }
33 | });
34 |
35 | module.exports = mongoose.model('employees', employeeSchema);
--------------------------------------------------------------------------------
/client/src/components/ellipsis-menu/styles.scss:
--------------------------------------------------------------------------------
1 | .task-elipsis-menu {
2 | position: absolute;
3 | top: 1.5rem;
4 | right: 1rem;
5 | box-shadow: 1px 0px 3px;
6 | z-index: 50;
7 | .elipsis-content {
8 | display: flex;
9 | justify-content: flex-end;
10 | align-items: center;
11 | .details{
12 | font-weight: 500;
13 | font-size: 0.875rem;
14 | line-height: 1.25rem;
15 | padding-top: 1.25rem;
16 | padding-bottom: 1.25rem;
17 | padding-right: 3rem;
18 | padding-left: 1rem;
19 | background-color: var(--white);
20 | width: 10rem;
21 | height: auto;
22 | z-index: 50;
23 | .edit-type{
24 | cursor: pointer;
25 | }
26 | .delete-type {
27 | cursor: pointer;
28 | color: var(--red);
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/server/db/taskBoardModel.js:
--------------------------------------------------------------------------------
1 | // schema.js
2 |
3 | const mongoose = require('mongoose');
4 |
5 | // Define the subtask schema
6 | const subtaskSchema = new mongoose.Schema({
7 | title: String,
8 | isCompleted: Boolean,
9 | });
10 |
11 | // Define the task schema
12 | const taskSchema = new mongoose.Schema({
13 | title: String,
14 | description: String,
15 | status: String,
16 | subtasks: [subtaskSchema],
17 | });
18 |
19 | // Define the column schema
20 | const columnSchema = new mongoose.Schema({
21 | name: String,
22 | tasks: [taskSchema],
23 | });
24 |
25 | // Define the main board schema
26 | const boardSchema = new mongoose.Schema({
27 | name: String,
28 | columns: [columnSchema],
29 | });
30 |
31 | // Create the Board model
32 | const Board = mongoose.model('Boards', boardSchema);
33 |
34 | module.exports = Board;
--------------------------------------------------------------------------------
/client/src/data/employeesData.js:
--------------------------------------------------------------------------------
1 |
2 | export const mapEmployeesData = (data=[])=>{
3 | return {
4 | metaData: {
5 | title: "Employees | TOTE"
6 | },
7 | mainSection:{
8 |
9 | employeesTable: {
10 | isFilters: true,
11 | isPagination: true,
12 | rowLabels: ['Full Name', 'Email id', 'Phone Number', 'Address', 'Role', 'Action'],
13 | rows: data?.map((item) => ({
14 | action: 'Update',
15 | id: item?._id,
16 | cells: [
17 | { value: item?.name ?? '-' },
18 | { value: item?.email ?? '-' },
19 | { value: item?.phone ?? '-' },
20 | { value: item?.address ?? '-' },
21 | { value: item?.role ?? '-' },
22 | ],
23 | })),
24 | },
25 | }
26 | }
27 | }
--------------------------------------------------------------------------------
/server/Middilewares/jwt_auth.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 | require('dotenv').config();
3 |
4 | const verifyToken = (req, res, next) =>{
5 | const token = req.headers.token;
6 |
7 | if(token === null){
8 |
9 | return res.status(403).json({
10 | status : "TokenNotAvailable",
11 | message : "Token is required please provide it!"
12 | })
13 | }
14 |
15 | try{
16 |
17 | const decode = jwt.verify(token, process.env.ACCESS_TOKEN);
18 |
19 | req.employee = decode;
20 |
21 | }catch(error){
22 | return res.status(401).json({
23 | status : "unauthorized",
24 | message : error.message,
25 | expiredAt: error.expiredAt
26 | })
27 | }
28 | return next();
29 | }
30 |
31 | module.exports = verifyToken;
--------------------------------------------------------------------------------
/client/src/data/profileData.js:
--------------------------------------------------------------------------------
1 | export const mapProfileData = (data = {}) => {
2 | return {
3 | metaData: {
4 | title: "Profile | TOTE"
5 | },
6 | mainSection: {
7 | userDetails: [
8 | {
9 | field: "full name", value: data?.name || ''
10 | },
11 | {
12 | field: "email id", value: data?.email || ''
13 | },
14 | {
15 | field: "phone number", value: data?.phone || ''
16 | },
17 | {
18 | field: "address", value: data?.address || ''
19 | },
20 | {
21 | field: "organization role", value: data?.role || ''
22 | }
23 | ]
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/client/src/components/button/style.scss:
--------------------------------------------------------------------------------
1 | .btn{
2 | button{
3 | cursor: pointer;
4 | width: 100%;
5 | height: 100%;
6 | font-family: sans-serif;
7 | font-weight: 500;
8 | font-size: 16px;
9 | letter-spacing: 1px;
10 | padding: 0.8rem 0;
11 | border: none;
12 | border-radius: 5px;
13 | text-transform: capitalize;
14 | background-color: #efe7e7;
15 | }
16 | &.primary{
17 | button{
18 | background-color: var(--blue);
19 | color: #fff;
20 | }
21 | }
22 | &.success{
23 | button{
24 | background-color: #FF8B13;
25 | }
26 | }
27 | &.danger{
28 | button{
29 | color: #fff;
30 | background-color: #dc3545;
31 | border-color: #dc3545;
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/client/src/containers/profile/main-section.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const CustomInput = (props) => (
4 |
5 |
{props.field}
6 |
{props.value}
7 |
8 | )
9 |
10 | const Details = (props) => (
11 |
12 | {props.details.map((item, index)=>(
13 |
17 | ))}
18 |
19 | )
20 |
21 | const MainSection = (props) => {
22 | return (
23 |
31 | )
32 | }
33 |
34 | export default MainSection
--------------------------------------------------------------------------------
/client/src/containers/sign-in/form-section.scss:
--------------------------------------------------------------------------------
1 | .login-page {
2 | .form-section {
3 | margin-top: 16px;
4 | }
5 | }
6 |
7 | .login-page {
8 | .form-section {
9 | .email-component {
10 | margin-bottom: 24px;
11 | }
12 | .btn{
13 | margin: 24px 0px;
14 | }
15 | }
16 | }
17 |
18 | .login-page {
19 | .signup-link {
20 | display: block;
21 | text-align: center;
22 | text-decoration: none;
23 | font-size: 16px;
24 | font-weight: bold;
25 | color: #086f25;
26 | margin-top: 16px;
27 | }
28 | }
29 |
30 | .login-page {
31 | .bottom-text {
32 | font-size: 16px;
33 | text-align: center;
34 | margin-top: 16px;
35 | font-weight: bold;
36 | }
37 | }
38 |
39 | .login-page {
40 | .bottom-text {
41 | a {
42 | text-decoration: none;
43 | margin-left: 8px;
44 | color: #086f25;
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/server/utils/apiFeatures.js:
--------------------------------------------------------------------------------
1 | class ApiFeatures{
2 | constructor(query, queryStr){
3 | this.query = query;
4 | this.queryStr = queryStr;
5 | }
6 |
7 | limitFields(){
8 | if(this.queryStr.fields){
9 | const fields = this.queryStr.fields.split(',').join(' ');
10 | this.query = this.query.select(fields);
11 | }else{
12 | this.query = this.query.select('-password -__v -otp');
13 | }
14 | return this;
15 | }
16 |
17 | pagination(){
18 | const page = this.queryStr.page * 1 || 1;
19 | const limit = this.queryStr.limit * 1 || 10;
20 | const skip = (page - 1) * limit; // page = 1 limit = 10, [skip = (0 * 10) => 0]
21 |
22 | this.query = this.query.skip(skip).limit(limit);
23 |
24 | return this;
25 | }
26 | }
27 |
28 | module.exports = ApiFeatures;
--------------------------------------------------------------------------------
/server/db/taskModel.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 |
3 | const schema = new mongoose.Schema({
4 | title: {
5 | type: String,
6 | required: true
7 | },
8 | start_date: {
9 | type: Date,
10 | default: Date.now
11 | },
12 | due_date: {
13 | type: Date,
14 | required: true
15 | },
16 | status: {
17 | type: String,
18 | enum: ['up next', 'in progress', 'done', 'backlog', 'on hold', 'questions'],
19 | default: 'up next'
20 | },
21 | assigned_by: {
22 | type: mongoose.Schema.Types.ObjectId,
23 | ref: 'employees'
24 | },
25 | assigned_to: {
26 | type: mongoose.Schema.Types.ObjectId,
27 | ref: 'employees'
28 | }
29 | }, {
30 | timestamps: true
31 | });
32 |
33 | const Task = mongoose.model("tasks", schema);
34 |
35 | module.exports = Task;
--------------------------------------------------------------------------------
/client/src/components/dropdown/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import './styles.scss';
3 |
4 | const Dropdown = ({ label, value, items, onChange, defaultValue }) => {
5 |
6 | const handleChange = (e) =>{
7 | const {value} = e.target;
8 | onChange(value, e.target.selectedIndex);
9 | }
10 | return (
11 |
12 |
{label}
13 |
14 |
15 | {!value ? {defaultValue || 'Select an option -'} : null}
16 | {items.map((item, index)=>(
17 | {item}
18 | ))}
19 |
20 |
21 |
22 | )
23 | }
24 |
25 | export default Dropdown;
26 |
--------------------------------------------------------------------------------
/client/src/components/ellipsis-menu/ElipsisMenu.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import './styles.scss';
3 |
4 | const ElipsisMenu = ({ type, setOpenEditModal, setOpenDeleteModal }) => {
5 | return (
6 |
13 |
14 |
15 |
{
17 | setOpenEditModal();
18 | }}
19 | className="edit-type"
20 | >
21 | Edit {type}
22 |
23 |
24 |
setOpenDeleteModal()}
26 | className="delete-type"
27 | >
28 | Delete {type}
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | export default ElipsisMenu;
37 |
--------------------------------------------------------------------------------
/client/src/components/input/styles.scss:
--------------------------------------------------------------------------------
1 | .simpleInput-component {
2 | .simpleInput-wrapper {
3 | display: flex;
4 | border: 2px solid #B7B78A;
5 | border-radius: 10px;
6 | height: 50px;
7 | position: relative;
8 | padding: 0 0 0 16px;
9 |
10 | .text {
11 | color: #000;
12 | font-weight: 600;
13 | position: absolute;
14 | top: -10px;
15 | background-color: #fff;
16 | padding: 0px 5px;
17 | text-transform: capitalize;
18 | }
19 |
20 | .simpleInput-input {
21 | display: inline-block;
22 | height: 100%;
23 | width: 100%;
24 | border: none;
25 | color: rgb(32, 33, 33);
26 | background-color: transparent;
27 | }
28 |
29 | .simpleInput-input:focus {
30 | outline: none;
31 | }
32 |
33 | }
34 | }
--------------------------------------------------------------------------------
/client/src/utils/checkLogin.js:
--------------------------------------------------------------------------------
1 | import { sendErrorNotification } from '../services/notifications';
2 | import { getLocalStorageKey } from './localStorage';
3 | import { signout } from '../services/login/signout';
4 |
5 | export const checkLoginStatus = (location, navigate) =>{
6 | location = location.pathname;
7 | const now = Date.now();
8 | const token = getLocalStorageKey('token');
9 | const email = getLocalStorageKey('email');
10 | const expiry = getLocalStorageKey('expiry') * 1000;
11 |
12 | const authRoutes = ['login', 'signup', 'reset-password', 'otp-verification'];
13 | const isValid = authRoutes.find((route)=> (location.split('/')[1] === route));
14 |
15 | if(!isValid){
16 | if(!(token || email) || (expiry < now)){
17 | signout();
18 | navigate('/login');
19 | sendErrorNotification('Access has expired please login again.');
20 | return;
21 | }
22 | }
23 | }
--------------------------------------------------------------------------------
/website/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "website",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.5",
7 | "@testing-library/react": "^13.4.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "react": "^18.2.0",
10 | "react-dom": "^18.2.0",
11 | "react-scripts": "5.0.1",
12 | "web-vitals": "^2.1.4"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject"
19 | },
20 | "eslintConfig": {
21 | "extends": [
22 | "react-app",
23 | "react-app/jest"
24 | ]
25 | },
26 | "browserslist": {
27 | "production": [
28 | ">0.2%",
29 | "not dead",
30 | "not op_mini all"
31 | ],
32 | "development": [
33 | "last 1 chrome version",
34 | "last 1 firefox version",
35 | "last 1 safari version"
36 | ]
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cors = require('cors');
3 | const morgan = require('morgan');
4 | require('dotenv').config();
5 | const employeeRoutes = require('./Routers/employeeRoutes');
6 | const departRoutes = require('./Routers/departmentRoutes');
7 | const taskRoutes = require('./Routers/taskRoutes');
8 | const reportRoutes = require('./Routers/reportRoutes');
9 | const projectRoutes = require('./Routers/projectRoutes');
10 | const taskBoardsRoutes = require('./Routers/taskBoardRoutes');
11 | const app = express();
12 |
13 | // middilewares
14 | app.use(express.json());
15 | app.use(cors());
16 | app.use(morgan("dev"));
17 |
18 | // routers
19 | app.use('/api/v1/employees', employeeRoutes);
20 | app.use('/api/v1/departments', departRoutes);
21 | app.use('/api/v1/tasks', taskRoutes);
22 | app.use('/api/v1/reports', reportRoutes);
23 | app.use('/api/v1/projects', projectRoutes);
24 | app.use('/api/v1/task-board', taskBoardsRoutes);
25 |
26 |
27 | module.exports = app;
--------------------------------------------------------------------------------
/client/src/containers/tasks-board/top-section.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 | import AddEditTaskModal from './AddEditTaskModal';
4 |
5 | const TopSection = ({ name, id }) => {
6 | const [isAddTaskModalOpen, setIsAddTaskModalOpen] = useState(false);
7 | const navigate = useNavigate();
8 |
9 | return (
10 |
11 | {isAddTaskModalOpen && (
12 |
17 | )}
18 |
19 |
navigate('/tasks/')}> ←
20 |
{name || 'name'}
21 |
22 |
setIsAddTaskModalOpen(true)}>
23 | + Add New Task
24 |
25 |
26 | )
27 | }
28 |
29 | export default TopSection;
30 |
--------------------------------------------------------------------------------
/server/utils/replaceIdKey.js:
--------------------------------------------------------------------------------
1 | function replaceIdWithKey(obj) {
2 | const stack = [{ data: obj }];
3 | while (stack.length > 0) {
4 | const currentItem = stack.pop();
5 | const { data } = currentItem;
6 |
7 | if (data instanceof Array) {
8 | currentItem.replaced = data.map((item) => {
9 | if (item instanceof Object) {
10 | const newObj = {};
11 | Object.keys(item).forEach((key) => {
12 | const newKey = key === '_id' ? 'id' : key;
13 | newObj[newKey] = item[key];
14 | });
15 | return newObj;
16 | }
17 | return item;
18 | });
19 | } else if (data instanceof Object) {
20 | Object.keys(data).forEach((key) => {
21 | if (data[key] instanceof Array || data[key] instanceof Object) {
22 | stack.push({ data: data[key] });
23 | }
24 | });
25 |
26 | data.id = data._id;
27 | delete data._id;
28 | }
29 | }
30 | return obj;
31 | }
32 |
33 | module.exports = { replaceIdWithKey };
34 |
--------------------------------------------------------------------------------
/client/src/data/signin.json:
--------------------------------------------------------------------------------
1 | {
2 | "metaData": {
3 | "title": "Login | TOTE"
4 | },
5 | "topSection": {
6 | "title": "Sign In"
7 | },
8 | "formSection": {
9 | "inputComponents": [
10 | {
11 | "component": "email",
12 | "details": {
13 | "label": "Email Address",
14 | "placeholder": "E.g. xyz@gmail.com"
15 | }
16 | },
17 | {
18 | "component": "password",
19 | "details": {
20 | "label": "Password",
21 | "placeholder": "********"
22 | }
23 | },
24 | {
25 | "component": "button",
26 | "details": {
27 | "text": "Login",
28 | "type": "submit",
29 | "button": "primary"
30 | }
31 | },
32 | {
33 | "component": "link",
34 | "details": {
35 | "text": "Forgot Password?",
36 | "to": "/reset-password"
37 | }
38 | }
39 | ],
40 | "bottomText": "Not a member?",
41 | "linkText": "Create Account",
42 | "signUpLink": "/signup"
43 | }
44 | }
--------------------------------------------------------------------------------
/client/src/components/header/styles.scss:
--------------------------------------------------------------------------------
1 | .header {
2 | display: flex;
3 | justify-content: space-between;
4 | align-items: center;
5 | .ham-icon{
6 | display: inline-block;
7 | cursor: pointer;
8 | width: 32px;
9 | height: 32px;
10 | margin: 10px;
11 | svg{
12 | fill: var(--black1);
13 | }
14 | }
15 | .profile-section {
16 | background: var(--blue);
17 | padding: 20px;
18 | border-radius: 0 0 0 20px;
19 | width: max-content;
20 | height: auto;
21 | display: flex;
22 | justify-content: flex-end;
23 | gap: 5px;
24 | align-items: center;
25 | cursor: pointer;
26 | .employee-icon {
27 | display: flex;
28 | justify-content: center;
29 | align-items: center;
30 | padding: 5px;
31 | background: var(--yellow);
32 | border-radius: 50%;
33 | svg {
34 | width: 20px;
35 | fill: var(--gray);
36 | }
37 | }
38 |
39 | span{
40 | font-size: 16px;
41 | color: #fff;
42 | text-transform: capitalize;
43 | }
44 | }
45 | }
--------------------------------------------------------------------------------
/client/src/containers/tasks/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import WelcomePage from "./welcome-page";
3 | import './styles.scss';
4 | import { useDispatch } from "react-redux";
5 | import { useEffect, useState } from "react";
6 | import { getAllBoards } from "../../services/tasks/taskBoards";
7 | import { boardActions } from "../../redux/reducers/board";
8 | import Loader from "../../components/loader";
9 |
10 | const Task = () => {
11 | const [isLoading, setIsLoading] = useState(true);
12 | const dispatch = useDispatch();
13 |
14 | const fetchBoards = async () => {
15 | const result = await getAllBoards();
16 | if(result.status === 'success'){
17 | const boards = result.response;
18 | dispatch(boardActions.setBoardsData(boards));
19 | }
20 | setIsLoading(false);
21 | }
22 |
23 | useEffect(()=>{
24 | fetchBoards();
25 | },[]);
26 |
27 | return ( isLoading ? :
28 |
29 |
30 |
31 | )
32 | }
33 |
34 | export default Task;
--------------------------------------------------------------------------------
/client/src/data/signup-data/signup.json:
--------------------------------------------------------------------------------
1 | {
2 | "metaData": {
3 | "title": "SignUp | TOTE"
4 | },
5 | "topSection": {
6 | "title": "Sign Up"
7 | },
8 | "formSection": {
9 | "inputComponents": [
10 | {
11 | "component": "name",
12 | "details": {
13 | "label": "Full Name",
14 | "placeholder": "E.g. John Doe"
15 | }
16 | },
17 | {
18 | "component": "email",
19 | "details": {
20 | "label": "Email Address",
21 | "placeholder": "E.g. xyz@gmail.com"
22 | }
23 | },
24 | {
25 | "component": "password",
26 | "details": {
27 | "label": "Password",
28 | "placeholder": "********"
29 | }
30 | },
31 | {
32 | "component": "button",
33 | "details": {
34 | "text": "Send OTP",
35 | "type": "submit",
36 | "button": "primary"
37 | }
38 | }
39 | ],
40 | "bottomText": "Already a member?",
41 | "linkText": "Login",
42 | "signUpLink": "/login"
43 | }
44 | }
--------------------------------------------------------------------------------
/client/src/components/otp/styles.scss:
--------------------------------------------------------------------------------
1 | .otp-component {
2 | .otp-wrapper {
3 | display: flex;
4 | border: 2px solid #B7B78A;
5 | border-radius: 10px;
6 | height: 50px;
7 | position: relative;
8 | padding: 0 0 0 16px;
9 | .text{
10 | color: #000;
11 | font-weight: 600;
12 | position: absolute;
13 | top: -10px;
14 | background-color: #fff;
15 | padding: 0px 5px;
16 | }
17 | .otp-input{
18 | display: inline-block;
19 | height: 100%;
20 | width: 100%;
21 | border: none;
22 | color: rgb(32, 33, 33);
23 | background-color: transparent;
24 | }
25 | .otp-input:focus{
26 | outline: none;
27 | }
28 |
29 | }
30 | .error-icon {
31 | padding-right: 8px;
32 | display: flex;
33 | justify-content: center;
34 | align-items: center;
35 | opacity: 0;
36 | svg{
37 | fill: #e85347;
38 | }
39 | }
40 | &.error {
41 | .error-icon {
42 | opacity: 1;
43 | }
44 | .otp-wrapper{
45 | border-color: #e85347;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/client/src/components/name/styles.scss:
--------------------------------------------------------------------------------
1 | .name-component {
2 | .name-wrapper {
3 | display: flex;
4 | border: 2px solid #B7B78A;
5 | border-radius: 10px;
6 | height: 50px;
7 | position: relative;
8 | padding: 0 0 0 16px;
9 | .text{
10 | color: #000;
11 | font-weight: 600;
12 | position: absolute;
13 | top: -10px;
14 | background-color: #fff;
15 | padding: 0px 5px;
16 | }
17 | .name-input{
18 | display: inline-block;
19 | height: 100%;
20 | width: 100%;
21 | border: none;
22 | color: rgb(32, 33, 33);
23 | background-color: transparent;
24 | }
25 | .name-input:focus{
26 | outline: none;
27 | }
28 |
29 | }
30 | .error-icon {
31 | padding-right: 8px;
32 | display: flex;
33 | justify-content: center;
34 | align-items: center;
35 | opacity: 0;
36 | svg{
37 | fill: #e85347;
38 | }
39 | }
40 | &.error {
41 | .error-icon {
42 | opacity: 1;
43 | }
44 | .name-wrapper{
45 | border-color: #e85347;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/client/src/components/email/styles.scss:
--------------------------------------------------------------------------------
1 | .email-component {
2 | .email-wrapper {
3 | display: flex;
4 | border: 2px solid #B7B78A;
5 | border-radius: 10px;
6 | height: 50px;
7 | position: relative;
8 | padding: 0 0 0 16px;
9 | .text{
10 | color: #000;
11 | font-weight: 600;
12 | position: absolute;
13 | top: -10px;
14 | background-color: #fff;
15 | padding: 0px 5px;
16 | }
17 | .email-input{
18 | display: inline-block;
19 | height: 100%;
20 | width: 100%;
21 | border: none;
22 | color: rgb(32, 33, 33);
23 | background-color: transparent;
24 | }
25 | .email-input:focus{
26 | outline: none;
27 | }
28 |
29 | }
30 | .error-icon {
31 | padding-right: 8px;
32 | display: flex;
33 | justify-content: center;
34 | align-items: center;
35 | opacity: 0;
36 | svg{
37 | fill: #e85347;
38 | }
39 | }
40 | &.error {
41 | .error-icon {
42 | opacity: 1;
43 | }
44 | .email-wrapper{
45 | border-color: #e85347;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/client/src/services/employees/allEmployees.js:
--------------------------------------------------------------------------------
1 | import { fetchUrl } from "../../utils/fetchUrl";
2 | import { getLocalStorageKey } from "../../utils/localStorage";
3 |
4 | export const getAllEmployees = async ()=>{
5 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/allEmployees';
6 | const token = getLocalStorageKey('token');
7 |
8 | const headers = new Headers();
9 | headers.append('Content-Type','application/json');
10 | headers.append('token',token);
11 |
12 | const requestOptions = {method:'GET', headers, redirect: 'follow'};
13 |
14 | return await fetchUrl(url, requestOptions);
15 | }
16 |
17 | export const getEmployeesCount = async ()=>{
18 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/employees-count';
19 | const token = getLocalStorageKey('token');
20 |
21 | const headers = new Headers();
22 | headers.append('Content-Type','application/json');
23 | headers.append('token',token);
24 |
25 | const requestOptions = {method:'GET', headers, redirect: 'follow'};
26 |
27 | return await fetchUrl(url, requestOptions);
28 | }
--------------------------------------------------------------------------------
/client/src/containers/tasks-board/empty-board.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import CreateBoardModal from "../../components/create-board-modal/create-board-modal";
3 |
4 |
5 | const EmptyBoard = ({ type, id, refetchData }) => {
6 | const [isBoardModalOpen, setIsBoardModalOpen] = useState(false);
7 |
8 | return (
9 |
10 |
11 | {type === "edit"
12 | ? "This board is empty. Create a new column to get started."
13 | : "There are no boards available. Create a new board to get started"}
14 |
15 | {
17 | setIsBoardModalOpen(true);
18 | }}
19 | >
20 | {type === "edit" ? "+ Add New Column" : "+ Add New Board"}
21 |
22 | {isBoardModalOpen && (
23 |
29 | )}
30 |
31 | );
32 | }
33 |
34 | export default EmptyBoard;
35 |
--------------------------------------------------------------------------------
/client/src/containers/tasks-board/subtask.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useDispatch, useSelector } from "react-redux";
3 | import { boardActions } from "../../redux/reducers/board";
4 |
5 | const Subtask = ({ id, taskId, colId, setIsChanged }) => {
6 | const dispatch = useDispatch();
7 | const board = useSelector((state) => state.boards.selectedBoard);
8 | const col = board.columns.find((col) => col._id === colId);
9 | const task = col.tasks.find((task) => task._id === taskId);
10 | const subtask = task.subtasks.find((subtask) => subtask._id === id);
11 | const checked = subtask.isCompleted;
12 |
13 | const onChange = (e) => {
14 | dispatch(
15 | boardActions.setSubtaskCompleted({ id, taskId, colId })
16 | );
17 | setIsChanged(true);
18 | };
19 |
20 | return (
21 |
22 |
27 |
28 | {subtask.title}
29 |
30 |
31 | );
32 | }
33 |
34 | export default Subtask;
35 |
--------------------------------------------------------------------------------
/server/Scripts/index.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const departmentsData = require('./departments.json');
3 |
4 | const departSchema = new mongoose.Schema({
5 | title: {
6 | type: String,
7 | required: [true, "A department should have a title."]
8 | },
9 | url: {
10 | type: String
11 | }
12 | });
13 |
14 | const Department = mongoose.model('departments', departSchema);
15 |
16 | const createDeparments = async () => {
17 | const response = await Department.insertMany(departmentsData);
18 | console.log("result", response);
19 | }
20 |
21 | const getAllDepartments = async () => {
22 | const response = await Department.find();
23 | console.log("length", response);
24 | }
25 |
26 | const db = '';
27 | const password = '';
28 | const dblink = db.replace(
29 | '',
30 | password
31 | );
32 |
33 | mongoose.connect(dblink).then(()=>{
34 | console.log("DB succesfully connected!");
35 | createDeparments().then(()=>{
36 | console.log("data inserted succesfully");
37 | })
38 | }).catch((error)=>{
39 | console.log("Error in db", error.message);
40 | })
--------------------------------------------------------------------------------
/client/src/components/name/index.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { ExclamationMark } from '../icons';
3 |
4 | import './styles.scss';
5 |
6 | const Name = (props) => {
7 | const { onChange, isValid, ...rest } = props;
8 | const [isError, setIsError] = useState(false);
9 |
10 | const onChangeName = ({ target }) => {
11 | const isValid = target.value !== '';
12 | setIsError(!isValid);
13 | props.onChange(target.value, !!isValid);
14 | };
15 |
16 | const onBlurName = ({target}) => {
17 | const isValid = target.value !== '';
18 | setIsError(!isValid);
19 | }
20 |
21 | const onFocusName = () => {
22 | setIsError(false);
23 | }
24 |
25 | return
26 |
27 |
28 | {props.label}
29 |
30 |
31 |
32 |
33 |
34 | };
35 |
36 | export default Name;
--------------------------------------------------------------------------------
/client/src/components/otp/index.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { ExclamationMark } from '../icons';
3 |
4 | import './styles.scss';
5 |
6 | const Otp = (props) => {
7 | const { onChange, isValid, ...rest } = props;
8 | const [isError, setIsError] = useState(false);
9 |
10 | const onChangeName = ({ target }) => {
11 | const isValid = target.value !== '';
12 | setIsError(!isValid);
13 | props.onChange(target.value, !!isValid);
14 | };
15 |
16 | const onBlurName = ({target}) => {
17 | const isValid = target.value !== '';
18 | setIsError(!isValid);
19 | }
20 |
21 | const onFocusName = () => {
22 | setIsError(false);
23 | }
24 |
25 | return
26 |
27 |
28 | {props.label}
29 |
30 |
31 |
32 |
33 |
34 | };
35 |
36 | export default Otp;
--------------------------------------------------------------------------------
/client/src/containers/projects/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react'
2 | import MetaTags from '../../components/meta-tags';
3 | import './style.scss';
4 | import { mapProjectsData } from '../../data/projectsData';
5 | import MainSection from './main-section';
6 | import Loader from '../../components/loader';
7 | import { getListProjects } from '../../services/projects/projects';
8 |
9 | const Projects = () => {
10 | const [{ mainSection, metaData }, setProjectsData] = useState(mapProjectsData());
11 | const [isLoading, setLoading] = useState(false);
12 |
13 | const fetchProjectsData = async () => {
14 | setLoading(true);
15 | const result = await getListProjects();
16 | if (result.status === 'success') {
17 | setProjectsData(mapProjectsData(result.response));
18 | }
19 | setLoading(false);
20 | }
21 |
22 | useEffect(()=> {
23 | fetchProjectsData();
24 | }, []);
25 |
26 | return ( isLoading ? :
27 |
33 | )
34 | };
35 |
36 | export default Projects;
37 |
--------------------------------------------------------------------------------
/client/src/containers/app/index.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter, Route, Routes } from 'react-router-dom';
2 | import { Provider, useSelector } from 'react-redux';
3 | import { allRoutes } from './all-routes';
4 | import Navbar from '../../components/navbar';
5 | import { ToastContainer } from 'react-toastify';
6 | import 'react-toastify/dist/ReactToastify.css';
7 | import store from '../../redux/store';
8 | import Header from '../../components/header';
9 |
10 | const App = () => {
11 | const routes = allRoutes;
12 | const { isActive, isHidden } = useSelector(state => state.navbar);
13 |
14 | return (
15 |
16 |
17 |
18 |
19 |
20 |
21 | {routes.map(route => {
22 | return
23 | })}
24 |
25 |
26 |
27 | )
28 | };
29 |
30 | const AppWithStore = () => (
31 |
32 |
33 |
34 | )
35 |
36 | export default AppWithStore;
37 |
--------------------------------------------------------------------------------
/client/src/components/header/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 | import { useDispatch, useSelector } from 'react-redux';
4 | import { navbarActions } from '../../redux/reducers/other';
5 | import { HamburgerIcon, UserIcon } from '../icons';
6 | import './styles.scss';
7 |
8 | const Header = () => {
9 | const { isActive, isHidden } = useSelector(state => state.navbar);
10 | const employee = useSelector(state => state.employee.loggedInEmployee);
11 | const dispatch = useDispatch();
12 | const navigate = useNavigate();
13 |
14 | const handleNav = () => {
15 | dispatch(navbarActions.setIsActive(!isActive));
16 | }
17 |
18 | const handleClick = () => {
19 | navigate('/profile');
20 | };
21 |
22 | return ( !isHidden ?
23 |
24 |
25 |
26 |
27 |
28 |
29 |
Hi {employee?.name?.split(' ')[0] || '😎' }
30 |
31 |
: null
32 | )
33 | }
34 |
35 | export default Header;
36 |
--------------------------------------------------------------------------------
/client/src/components/email/index.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { validateEmail } from '../../utils/email-validator';
3 | import { ExclamationMark } from '../icons';
4 | import './styles.scss';
5 |
6 | const Email = (props) => {
7 | const { onChange, isValid, ...rest } = props;
8 | const [isError, setIsError] = useState(false);
9 |
10 | const onChangeEmail = ({ target }) => {
11 | const isValid = validateEmail(target.value);
12 | setIsError(!isValid);
13 | props.onChange(target.value?.toLowerCase(), !!isValid);
14 | };
15 |
16 | const onBlurEmail = ({target}) => {
17 | const isValid = validateEmail(target.value);
18 | setIsError(!isValid);
19 | }
20 |
21 | const onFocusEmail = () => {
22 | setIsError(false);
23 | }
24 |
25 | return
26 |
27 |
28 | {props.label}
29 |
30 |
31 |
32 |
33 |
34 | };
35 |
36 | export default Email;
--------------------------------------------------------------------------------
/client/src/components/modal/style.scss:
--------------------------------------------------------------------------------
1 | .modal{
2 | display: block;
3 | position: fixed;
4 | z-index: 1;
5 | padding-top: 100px;
6 | left: 0;
7 | top: 0;
8 | width: 100%;
9 | height: 100%;
10 | overflow: auto;
11 | background-color: rgba(16, 16, 16, 0.4);
12 | .modal-container{
13 | background-color: #fefefe;
14 | margin: auto;
15 | padding: 20px;
16 | border: 1px solid #888;
17 | width: 80%;
18 | .modal-header{
19 | display: flex;
20 | justify-content: space-between;
21 | align-items: center;
22 | border-bottom: 2px solid #6c6b6b;
23 | margin-bottom: 20px;
24 | header{
25 | font-size: larger;
26 | font-weight: bold;
27 | text-transform: capitalize;
28 | }
29 | .close{
30 | color: #aaaaaa;
31 | font-size: 28px;
32 | font-weight: bold;
33 | &:hover, &:focus{
34 | color: #000;
35 | text-decoration: none;
36 | cursor: pointer;
37 | }
38 | }
39 | }
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/client/src/data/tasksData.js:
--------------------------------------------------------------------------------
1 | export const mapTasksData = (tasks = []) => {
2 | return {
3 | metaData: {
4 | title: "Tasks | TOTE"
5 | },
6 | backgroundImage: {
7 | path: '/assests/kanban-background.jpg'
8 | },
9 | mainSection: {
10 | allTasksDetails: [
11 | {
12 | title: 'backlog',
13 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'backlog') || [],
14 | },
15 | {
16 | title: 'up next',
17 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'up next') || [],
18 | },
19 | {
20 | title: 'in progress',
21 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'in progress') || [],
22 | },
23 | {
24 | title: 'on hold',
25 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'on hold') || [],
26 | },
27 | {
28 | title: 'completed',
29 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'done') || [],
30 | },
31 | {
32 | title: 'questions',
33 | data: tasks?.filter((task)=> task.status.toLowerCase() === 'questions') || [],
34 | },
35 | ]
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/client/.gitignore:
--------------------------------------------------------------------------------
1 | # These are some examples of commonly ignored file patterns.
2 | # You should customize this list as applicable to your project.
3 | # Learn more about .gitignore:
4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore
5 |
6 | # Node artifact files
7 | node_modules/
8 | dist/
9 |
10 | # Compiled Java class files
11 | *.class
12 |
13 | # Compiled Python bytecode
14 | *.py[cod]
15 |
16 | # Log files
17 | *.log
18 |
19 | # Package files
20 | *.jar
21 |
22 | # Maven
23 | target/
24 | dist/
25 |
26 | # JetBrains IDE
27 | .idea/
28 |
29 | # Unit test reports
30 | TEST*.xml
31 |
32 | # Generated by MacOS
33 | .DS_Store
34 |
35 | # Generated by Windows
36 | Thumbs.db
37 |
38 | # Applications
39 | *.app
40 | *.exe
41 | *.war
42 |
43 | # Large media files
44 | *.mp4
45 | *.tiff
46 | *.avi
47 | *.flv
48 | *.mov
49 | *.wmv
50 |
51 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
52 |
53 | # dependencies
54 | /node_modules
55 | /.pnp
56 | .pnp.js
57 |
58 | # testing
59 | /coverage
60 |
61 | # production
62 | /build
63 |
64 | # misc
65 | .DS_Store
66 | .env
67 | .env.local
68 | .env.development.local
69 | .env.test.local
70 | .env.production.local
71 |
72 | npm-debug.log*
73 | yarn-debug.log*
74 | yarn-error.log*
75 |
--------------------------------------------------------------------------------
/server/.gitignore:
--------------------------------------------------------------------------------
1 | # These are some examples of commonly ignored file patterns.
2 | # You should customize this list as applicable to your project.
3 | # Learn more about .gitignore:
4 | # https://www.atlassian.com/git/tutorials/saving-changes/gitignore
5 |
6 | # Node artifact files
7 | node_modules/
8 | dist/
9 |
10 | # Compiled Java class files
11 | *.class
12 |
13 | # Compiled Python bytecode
14 | *.py[cod]
15 |
16 | # Log files
17 | *.log
18 |
19 | # Package files
20 | *.jar
21 |
22 | # Maven
23 | target/
24 | dist/
25 |
26 | # JetBrains IDE
27 | .idea/
28 |
29 | # Unit test reports
30 | TEST*.xml
31 |
32 | # Generated by MacOS
33 | .DS_Store
34 |
35 | # Generated by Windows
36 | Thumbs.db
37 |
38 | # Applications
39 | *.app
40 | *.exe
41 | *.war
42 |
43 | # Large media files
44 | *.mp4
45 | *.tiff
46 | *.avi
47 | *.flv
48 | *.mov
49 | *.wmv
50 |
51 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
52 |
53 | # dependencies
54 | /node_modules
55 | /.pnp
56 | .pnp.js
57 |
58 | # testing
59 | /coverage
60 |
61 | # production
62 | /build
63 |
64 | # misc
65 | .DS_Store
66 | .env
67 | .env.local
68 | .env.development.local
69 | .env.test.local
70 | .env.production.local
71 |
72 | npm-debug.log*
73 | yarn-debug.log*
74 | yarn-error.log*
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Tote Web
3 |
4 | A MERN stack application to manage the employees of a particular organization. This is my side project and i want this project to look good in UI. Currently, my focus is to make it a MVP in tems of functionalities. If you are eager to work on this admin panel.
5 |
6 |
7 |
8 |
9 | ## Installation Guide
10 |
11 | Clone or fork the repository and run the provided commands in console of the tote_web folder. To run this project, you will need to do following steps :
12 |
13 | ##### To run client side
14 |
15 | ```http
16 | cd ../client
17 | npm Install
18 | create a .env file and add given variables
19 | npm run start / npm start
20 | ```
21 | ### Client env variables
22 |
23 | `REACT_APP_BASE_URI=http://localhost:5000`
24 |
25 | #### To run server side
26 |
27 | ```http
28 | cd ../server
29 | npm install
30 | create a .env file and given variables
31 | npm run dev
32 | ```
33 | ### Server env variables
34 |
35 |
36 | `ACCESS_TOKEN=[jwt_access_secret]`
37 |
38 | `PORT=5000`
39 |
40 | `DB_USERNAME=[mongodb_user_name]`
41 |
42 | `DB_PASSWORD=[mongodb_cluster_access_password]`
43 |
44 | `DB_CLOUD_LINK=[mongodb_cluster_url]`
45 |
46 | `SMTP_HOST=[smptp_host_uri]`
47 |
48 | `SMTP_PORT=[smtp_port]`
49 |
50 | `SMTP_USER=[smtp_user_key]`
51 |
52 | `SMTP_PASS=[smtp_user_password_key]`
53 |
54 |
--------------------------------------------------------------------------------
/client/src/services/login/signup.js:
--------------------------------------------------------------------------------
1 | import { fetchUrl } from "../../utils/fetchUrl";
2 |
3 | export const signup = async (id, otp) =>{
4 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/signup';
5 | const headers = new Headers();
6 | headers.append('Content-Type', 'application/json');
7 | const body = { id, otp };
8 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect:'follow' };
9 | return await fetchUrl(url, requestOptions);
10 | }
11 |
12 | export const preSignup = async (name, email, password) =>{
13 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/signup/send-otp';
14 | const headers = new Headers();
15 | headers.append('Content-Type', 'application/json');
16 | const body = { name, email, password };
17 | const requestOptions = { method: 'POST', headers, body: JSON.stringify(body), redirect:'follow' };
18 | return await fetchUrl(url, requestOptions);
19 | }
20 |
21 | export const preSignupResendOTP = async (id) =>{
22 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/signup/resend-otp/';
23 | const headers = new Headers();
24 | headers.append('Content-Type', 'application/json');
25 |
26 | const requestOptions = { method: 'GET', headers, redirect:'follow' };
27 | return await fetchUrl(url + id, requestOptions);
28 | }
--------------------------------------------------------------------------------
/server/Routers/employeeRoutes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const employeeControllers = require('../Controllers/employeeControllers');
3 | const verifyToken = require('../Middilewares/jwt_auth');
4 | const checkLoginData = require('../Middilewares/checkLogin');
5 | const checkOtpData = require('../Middilewares/checkOTP');
6 | const router = express.Router();
7 |
8 | router.route('/login').post(checkLoginData, employeeControllers.login);
9 |
10 | router.route('/signup/send-otp').post(employeeControllers.preSignup);
11 |
12 | router.route('/signup/resend-otp/:id').get(employeeControllers.preSignupResendOTP);
13 |
14 | router.route('/signup').post(employeeControllers.signup);
15 |
16 | router.route('/forgotPassword').post(checkOtpData, employeeControllers.resetPasswordSendOTP);
17 |
18 | router.route('/resetPassword').post(employeeControllers.resetPassword);
19 |
20 | router.route('/employeeDetails').get(verifyToken, employeeControllers.employeeDetails);
21 |
22 | router.route('/updateEmployee').post(verifyToken, employeeControllers.updateEmployee);
23 |
24 | router.route('/deleteEmployee').delete(verifyToken, employeeControllers.deleteEmployee);
25 |
26 | router.route('/allEmployees').get(verifyToken, employeeControllers.getAllEmployees);
27 |
28 | router.route('/employees-count').get(verifyToken, employeeControllers.getEmployeesCount);
29 |
30 | module.exports = router;
--------------------------------------------------------------------------------
/client/src/components/password/styles.scss:
--------------------------------------------------------------------------------
1 | .password-component {
2 | .password-wrapper {
3 | display: flex;
4 | border: 2px solid #B7B78A;
5 | border-radius: 10px;
6 | height: 50px;
7 | position: relative;
8 | padding: 0 0 0 16px;
9 | .text{
10 | color: #000;
11 | font-weight: 600;
12 | position: absolute;
13 | top: -10px;
14 | background-color: #fff;
15 | padding: 0px 5px;
16 | }
17 | .password-input{
18 | display: inline-block;
19 | height: 100%;
20 | width: 100%;
21 | border: none;
22 | color: rgb(32, 33, 33);
23 | background-color: transparent;
24 | }
25 | .password-input:focus{
26 | outline: none;
27 | }
28 | .show-password{
29 | cursor: pointer;
30 | background-color: transparent;
31 | color: #086b25;
32 | display: flex;
33 | justify-content: center;
34 | align-items: center;
35 | padding: 4px 10px;
36 | }
37 | }
38 | .error-icon {
39 | padding-right: 8px;
40 | display: none;
41 | opacity: 0;
42 | svg{
43 | fill: #e85347;
44 | }
45 | }
46 | &.error {
47 | .error-icon {
48 | display: flex;
49 | justify-content: center;
50 | align-items: center;
51 | opacity: 1;
52 | }
53 | .password-wrapper{
54 | border-color: #e85347;
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "homepage": "http://React-developer-want.github.io/tote_web",
6 | "dependencies": {
7 | "@reduxjs/toolkit": "^1.9.5",
8 | "@testing-library/jest-dom": "^5.16.5",
9 | "@testing-library/react": "^13.4.0",
10 | "@testing-library/user-event": "^13.5.0",
11 | "lodash": "^4.17.21",
12 | "react": "^18.2.0",
13 | "react-dom": "^18.2.0",
14 | "react-helmet": "^6.1.0",
15 | "react-js-loader": "^0.1.0",
16 | "react-redux": "^8.1.1",
17 | "react-router-dom": "^6.6.2",
18 | "react-scripts": "5.0.1",
19 | "react-toastify": "^9.1.1",
20 | "sass": "^1.57.1",
21 | "uuid": "^9.0.0",
22 | "web-vitals": "^2.1.4"
23 | },
24 | "scripts": {
25 | "start": "react-scripts start",
26 | "predeploy": "npm run build",
27 | "deploy": "gh-pages -d build",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test",
30 | "eject": "react-scripts eject"
31 | },
32 | "eslintConfig": {
33 | "extends": [
34 | "react-app",
35 | "react-app/jest"
36 | ]
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/client/src/components/password/index.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { OpenEyeIcon, CloseEyeIcon, ExclamationMark } from '../icons';
3 | import './styles.scss';
4 |
5 | const Password = (props) => {
6 | const [isError, setIsError] = useState(false);
7 | const [showPwd,setShowPwd] = useState(false);
8 | const { onChange, ...rest } = props;
9 |
10 | const onChangePassword = ({ target }) => {
11 | const isValid = target.value !== '';
12 | props.onChange(target.value, isValid);
13 | }
14 |
15 | const onBlurPassword = ({target}) => {
16 | const isValid = target.value !== '';
17 | setIsError(!isValid);
18 | }
19 |
20 | const onFocusPassword = () => {
21 | setIsError(false);
22 | }
23 |
24 | const handleShowPwd = () => {
25 | setShowPwd((prev)=> !prev);
26 | }
27 |
28 | return
29 |
30 |
{props.label}
31 |
32 |
33 |
34 | {showPwd ? : }
35 |
36 |
37 |
38 | };
39 |
40 | export default Password;
--------------------------------------------------------------------------------
/client/src/data/newTaskForm.js:
--------------------------------------------------------------------------------
1 | import { getInputDate } from "../utils/date-handler";
2 |
3 | export const mapNewTaskData = (allEmployees, status) => ({
4 | allFields: [
5 | {
6 | label: 'Title',
7 | value: '',
8 | type: 'text',
9 | details: {
10 | placeholder: 'Title'
11 | }
12 | },
13 | {
14 | label: 'Start Date',
15 | value: getInputDate((new Date()).toISOString()),
16 | type: 'date',
17 | details: {
18 | className: 'date'
19 | }
20 | },
21 | {
22 | label: 'Due Date',
23 | value: '',
24 | type: 'date',
25 | details: {
26 | className: 'date'
27 | }
28 | },
29 | {
30 | label: 'Status',
31 | type: 'dropdown',
32 | value: status || '',
33 | details: {
34 | className: "dropdown",
35 | list: [
36 | { value: 'completed', label: 'Completed' }, { value: 'in progress', label: 'In progress' }, { value: 'backlog', label: 'Backlog' }, { value: 'up next', label: 'Up Next' }, { value: 'question', label: 'question' }, { value: 'on hold', label: 'On Hold' }
37 | ],
38 | }
39 | },
40 | {
41 | label: 'Assigned to',
42 | value: '',
43 | type: 'dropdown',
44 | details: {
45 | className: 'dropdown',
46 | list: allEmployees.map((employee)=> ({
47 | value: employee._id, label: employee.name
48 | }))
49 | }
50 | }
51 | ]
52 | })
--------------------------------------------------------------------------------
/client/src/containers/dashboard/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 | import MetaTags from '../../components/meta-tags';
4 | import { mapDashboardData } from '../../data/dashboardData.js';
5 | import MainSection from './main-section.js';
6 | import TopSection from './top-section.js';
7 | import './styles.scss';
8 | import Loader from '../../components/loader';
9 | import { getAllEmployees } from '../../services/employees/allEmployees';
10 |
11 | const Dashboard = () => {
12 | const [dashboardData, setDashboardData] = useState(mapDashboardData());
13 | const [isLoading, setLoading] = useState(false);
14 | const navigate = useNavigate();
15 |
16 | const syncDashboard = async () => {
17 | setLoading(true);
18 | const response = await getAllEmployees();
19 | setDashboardData(mapDashboardData({}, response.body.data));
20 | setLoading(false);
21 | };
22 |
23 | useEffect(()=> {
24 | syncDashboard();
25 | }, []);
26 |
27 | return ( isLoading ? :
28 |
29 |
30 |
33 |
36 |
39 |
40 |
41 | )
42 | }
43 |
44 | export default Dashboard
45 |
--------------------------------------------------------------------------------
/client/src/containers/tasks-board/Task.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { useSelector } from "react-redux";
3 | import TaskModal from "./task-modal";
4 |
5 | const Task = ({ colId, taskId }) => {
6 | const [isTaskModalOpen, setIsTaskModalOpen] = useState(false);
7 | const board = useSelector((state) => state.boards.selectedBoard);
8 | const columns = board.columns;
9 | const col = columns.find((col) => col._id === colId);
10 | const task = col.tasks.find((task) => task._id === taskId);
11 |
12 | let completed = 0;
13 | let subtasks = task.subtasks;
14 | subtasks.forEach((subtask) => {
15 | if (subtask.isCompleted) {
16 | completed++;
17 | }
18 | });
19 |
20 | const handleOnDrag = (e) => {
21 | e.dataTransfer.setData(
22 | "text",
23 | JSON.stringify({ taskId, prevColId: colId })
24 | );
25 | };
26 |
27 | return (
28 |
29 |
setIsTaskModalOpen(true)}
31 | draggable
32 | onDragStart={handleOnDrag}
33 | className="task"
34 | >
35 |
{task.title}
36 |
37 | {completed} of {subtasks.length} completed tasks
38 |
39 |
40 | {isTaskModalOpen && (
41 |
46 | )}
47 |
48 | );
49 | }
50 |
51 | export default Task;
52 |
--------------------------------------------------------------------------------
/client/src/containers/employees/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import { useEffect, useState } from 'react';
3 | import { useNavigate } from 'react-router-dom';
4 | import Loader from '../../components/loader';
5 | import MetaTags from '../../components/meta-tags';
6 | import { mapEmployeesData } from '../../data/employeesData';
7 | import { sendErrorNotification } from '../../services/notifications';
8 | import { getAllEmployees } from '../../services/employees/allEmployees';
9 | import MainSection from './main-section';
10 | import './styles.scss';
11 |
12 | const Employees = () => {
13 | const navigate = useNavigate();
14 | const [{metaData, mainSection}, setEmployeesData] = useState(mapEmployeesData([]));
15 | const [isLoading, setIsLoading] = useState(false);
16 |
17 | useEffect( ()=>{
18 | const fetchEmployees = async ()=>{
19 | setIsLoading(true);
20 | const response = await getAllEmployees();
21 | if(response.status === 'TokenExpiredError'){
22 | navigate('/login');
23 | sendErrorNotification('Session expired login again!')
24 | }
25 | setEmployeesData(mapEmployeesData(response.body.data));
26 | setIsLoading(false);
27 | }
28 | fetchEmployees();
29 | },[])
30 |
31 | return ( isLoading ? :
32 |
38 | )
39 | }
40 |
41 | export default Employees
42 |
--------------------------------------------------------------------------------
/client/src/containers/dashboard/top-section.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const TopSection = () => {
4 | return (
5 |
6 |
7 |
8 |
9 |
1,504
10 |
Daily Views
11 |
12 |
13 |
14 |
15 |
16 |
17 |
26 |
27 |
28 |
29 |
284
30 |
Comments
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
$7,842
40 |
Earning
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | )
49 | }
50 |
51 | export default TopSection;
52 |
--------------------------------------------------------------------------------
/client/src/data/departmentsData.js:
--------------------------------------------------------------------------------
1 | export const mapDepartmentsData = (data = []) => {
2 | return {
3 | metaData: {
4 | title: "Departments | TOTE"
5 | },
6 | mainSection: {
7 | filter: {
8 | search: {
9 | placeholder: "Search department"
10 | },
11 | button: {
12 | text: "Create Department"
13 | }
14 | },
15 | createDepartForm: [
16 | {
17 | component: "name",
18 | details: {
19 | label: "Department name",
20 | type: "text",
21 | placeholder: "Enter department name",
22 | required: true
23 | }
24 | },
25 | {
26 | component: "url",
27 | details: {
28 | label: "Website url",
29 | type: "url",
30 | placeholder: "Enter website url",
31 | required: true
32 | }
33 | },
34 | {
35 | component: "button",
36 | details: {
37 | text: "create department",
38 | type: "submit",
39 | button: "primary"
40 | }
41 | }
42 | ],
43 | departmentsCards : data || []
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/client/src/data/navbarData.js:
--------------------------------------------------------------------------------
1 | export const mapNavbarData = (data)=>{
2 | return {
3 | header : 'Tote web',
4 | navbarItems : [
5 | {
6 | to: '/',
7 | paths: [''],
8 | title: 'Dashboard',
9 | icon: 'home_icon',
10 | id: 'navitem-1'
11 | },
12 | {
13 | to: '/employees',
14 | paths: ['employees'],
15 | title: 'Employees',
16 | icon: 'employees_icon',
17 | id: 'navitem-2'
18 | },
19 | {
20 | to: '/departments',
21 | paths: ['departments'],
22 | title: 'Departments',
23 | icon: 'departments_icon',
24 | id: 'navitem-3'
25 | },
26 | {
27 | to: '/projects',
28 | paths: ['projects'],
29 | title: 'Projects',
30 | icon: 'projects_icon',
31 | id: 'navitem-4'
32 | },
33 | {
34 | to: '/tasks',
35 | paths: ['tasks'],
36 | title: 'Tasks Manager',
37 | icon: 'tasks_icon',
38 | id: 'navitem-5'
39 | },
40 | {
41 | to: '/profile',
42 | paths: ['profile'],
43 | title: 'Profile',
44 | icon: 'profile_icon',
45 | id: 'navitem-8'
46 | },
47 | ],
48 | employeeName: data?.name ?? '😎'
49 | }
50 | }
--------------------------------------------------------------------------------
/client/src/containers/employees/employee-details.scss:
--------------------------------------------------------------------------------
1 | .employeeDetails-page{
2 | display: flex;
3 | justify-content: center;
4 | align-items: center;
5 | .employeeDetails-container{
6 | width: 70%;
7 | margin: 3rem 1rem;
8 | .employeeDetails-topSection{
9 | width: 100%;
10 | margin-bottom: 20px;
11 | .main-title{
12 | font-size: 26px;
13 | font-weight: bold;
14 | }
15 | }
16 | .formSection{
17 | display: flex;
18 | justify-content: center;
19 | align-items: center;
20 | form{
21 | width: 100%;
22 | .simpleInput-component{
23 | margin-bottom: 20px;
24 | }
25 | .dropdown-component{
26 | margin-bottom: 20px;
27 | display: flex;
28 | gap: 20px;
29 | align-items: center;
30 | padding: 8px;
31 |
32 | }
33 | .buttons{
34 | display: flex;
35 | justify-content: center;
36 | width: 100%;
37 | gap: 10px;
38 | .btn{
39 | width: 40%;
40 | }
41 | }
42 | }
43 | }
44 | }
45 | }
46 |
47 | @media screen and (max-width: 700px) {
48 | .employeeDetails-page{
49 | .employeeDetails-container{
50 | width: 100%;
51 | margin: 3rem 0;
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/client/src/services/departments/departments.js:
--------------------------------------------------------------------------------
1 | import { fetchUrl } from "../../utils/fetchUrl";
2 | import { getLocalStorageKey } from "../../utils/localStorage"
3 |
4 | export const getAllDepartments = async () => {
5 | const token = getLocalStorageKey('token');
6 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/departments/all-departments' ;
7 |
8 | const headers = new Headers();
9 | headers.append('Content-Type','application/json');
10 | headers.append('token',token);
11 |
12 | const requestOptions = {method: 'GET', headers, redirect: 'follow'};
13 | return await fetchUrl(url, requestOptions);
14 | }
15 |
16 | export const getDepartmentsCount = async () => {
17 | const token = getLocalStorageKey('token');
18 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/departments/department-count';
19 |
20 | const headers = new Headers();
21 | headers.append('Content-Type','application/json');
22 | headers.append('token',token);
23 |
24 | const requestOptions = {method: 'GET', headers, redirect: 'follow'};
25 | return await fetchUrl(url, requestOptions);
26 | }
27 |
28 | export const createDepartment = async (details) => {
29 | const token = getLocalStorageKey('token');
30 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/departments/create-department';
31 |
32 | const headers = new Headers();
33 | headers.append('Content-Type', 'application/json');
34 | headers.append('token', token);
35 |
36 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(details), redirect: 'follow'};
37 | return await fetchUrl(url, requestOptions);
38 | }
--------------------------------------------------------------------------------
/client/src/containers/projects/style.scss:
--------------------------------------------------------------------------------
1 | .projects-page{
2 | .projects-container{
3 | padding: 3rem 1rem;
4 | .projects-main-section .project-card{
5 | margin-bottom: 4rem;
6 | .title{
7 | font-size: 2rem;
8 | font-weight: bold;
9 | margin-bottom: 1rem;
10 | border-bottom: 1px solid gray;
11 | }
12 | img{
13 | width: 100%;
14 | height: 12rem;
15 | margin-bottom: .5rem;
16 | }
17 | .grid{
18 | display: flex;
19 | justify-content: flex-start;
20 | gap: 1rem;
21 | flex-wrap: wrap;
22 | }
23 | .container{
24 | cursor: pointer;
25 | width: 20.5rem;
26 | padding: 0.25rem;
27 | box-shadow: 0px 5px 5px rgb(219, 214, 214);
28 | &:hover{
29 | box-shadow: 0px 5px 5px rgb(171, 161, 161);
30 | }
31 | .name{
32 | text-align: left;
33 | font-size: 1.4rem;
34 | font-weight: bold;
35 | text-transform: capitalize;
36 | }
37 | .flag{
38 | display: inline-block;
39 | color: #608906;
40 | font-weight: bolder;
41 | text-transform: uppercase;
42 | }
43 | .entity-pairs{
44 | display: flex;
45 | justify-content: space-between;
46 | margin-bottom: .5rem;
47 | }
48 | }
49 | .no-data{
50 | height: 4rem;
51 | display: flex;
52 | justify-content: center;
53 | align-items: center;
54 | font-size: 2rem;
55 | font-weight: lighter;
56 | color: gray;
57 | }
58 | }
59 | }
60 | }
--------------------------------------------------------------------------------
/client/src/data/resetPasswordData.json:
--------------------------------------------------------------------------------
1 | {
2 | "metaData": {
3 | "title": "Reset password | TOTE"
4 | },
5 | "topSection": {
6 | "title": "Reset Password"
7 | },
8 | "formSection": {
9 | "inputComponents": [
10 | {
11 | "component": "email",
12 | "details": {
13 | "label": "Email",
14 | "placeholder": "E.g. xyz@gmail.com"
15 | }
16 | },
17 | {
18 | "component": "button",
19 | "details": {
20 | "text": "Send OTP",
21 | "type": "submit",
22 | "button": "primary"
23 | }
24 | }
25 | ],
26 | "otpComponents": [
27 | {
28 | "component": "otp",
29 | "details": {
30 | "label": "Please Enter The 6 Digit OTP",
31 | "placeholder": "Enter The 6 Digit OTP"
32 | }
33 | },
34 | {
35 | "component": "password",
36 | "details": {
37 | "label": "New password",
38 | "placeholder": "********"
39 | }
40 | },
41 | {
42 | "component": "button",
43 | "details": {
44 | "text": "Submit",
45 | "type": "submit",
46 | "button": "success"
47 | }
48 | }
49 | ],
50 | "linkText": "Return to Login",
51 | "signInLink": "/login"
52 | }
53 | }
--------------------------------------------------------------------------------
/client/src/containers/projects/main-section.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const EntityPairs = (props) => (
4 |
5 | {props.title}
6 | {props.value}
7 |
8 | );
9 |
10 | const ProjectComponent = (props) => (
11 |
12 |
{props?.title}
13 | {props.list.length > 0 ?
14 | {props?.list?.map((item, index)=> (
15 |
16 |
17 |
18 |
{item?.name}
19 |
20 |
21 |
22 |
{item?.status}
23 |
24 | ))}
25 |
: (
26 |
27 | No Data to Show
28 |
29 | )}
30 |
31 | );
32 |
33 | const MainSection = (props) => {
34 | const { activeProjectsList, completedProjectsList, rejectedProjectsList } = props;
35 |
36 | return (
37 |
42 | )
43 | }
44 |
45 | export default MainSection;
46 |
--------------------------------------------------------------------------------
/client/src/containers/departments/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import React, { useEffect, useState } from 'react';
3 | import { useNavigate } from 'react-router-dom';
4 | import { getAllDepartments } from '../../services/departments/departments';
5 | import MetaTags from '../../components/meta-tags';
6 | import { sendErrorNotification } from '../../services/notifications';
7 | import { mapDepartmentsData } from '../../data/departmentsData';
8 | import MainSection from './main-section';
9 | import './styles.scss';
10 | import Loader from '../../components/loader';
11 |
12 | const Departments = () => {
13 | const navigate = useNavigate();
14 | const [isLoading, setIsLoading] = useState(false);
15 | const [departmentData, setDepartmentData] = useState(mapDepartmentsData([]));
16 |
17 | const fetchData = async () => {
18 | setIsLoading(true);
19 | const result = await getAllDepartments();
20 | if(result.status === 'TokenExpiredError'){
21 | navigate('/login');
22 | sendErrorNotification('Session expired login again!')
23 | }
24 | setDepartmentData(mapDepartmentsData(result?.response));
25 | setIsLoading(false);
26 | }
27 |
28 | useEffect(()=> {
29 | fetchData();
30 | },[]);
31 |
32 | return ( isLoading ? :
33 |
44 | )
45 | }
46 |
47 | export default Departments
48 |
49 | // https://up.gov.in/en/page/departments
--------------------------------------------------------------------------------
/client/src/components/delete-modal/DeleteModal.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import './styles.scss';
3 |
4 | const DeleteModal = ({ type, title, onDeleteBtnClick, setIsDeleteModalOpen, isDeleteLoading }) => {
5 |
6 | const onClose = (e) => {
7 | if (e.target !== e.currentTarget) {
8 | return;
9 | }
10 | setIsDeleteModalOpen(false);
11 | }
12 |
13 | return (
14 | // Modal Container
15 |
19 | {/* Delete Modal */}
20 |
21 |
22 |
23 | Delete this {type}?
24 |
25 | {type === "task" ? (
26 |
27 | Are you sure you want to delete the "{title}" task and its subtasks?
28 | This action cannot be reversed.
29 |
30 | ) : (
31 |
32 | Are you sure you want to delete the "{title}" board? This action
33 | will remove all columns and tasks and cannot be reversed.
34 |
35 | )}
36 |
37 |
38 |
43 | {isDeleteLoading ? '...Loading' : 'Delete'}
44 |
45 | {
47 | setIsDeleteModalOpen(false)
48 | }}
49 | className="cancel-btn"
50 | >
51 | Cancel
52 |
53 |
54 |
55 |
56 | );
57 | }
58 |
59 | export default DeleteModal;
60 |
--------------------------------------------------------------------------------
/client/src/data/taskDetails.js:
--------------------------------------------------------------------------------
1 | import { getInputDate } from "../utils/date-handler";
2 |
3 | export const mapTaskDetails = (allEmployees = [], details = {}) => {
4 | return {
5 | initialState: details ?? {},
6 | allFields: [
7 | {
8 | label: 'Title',
9 | value: details?.title || '',
10 | type: 'text',
11 | details: {
12 | placeholder: 'Title'
13 | }
14 | },
15 | {
16 | label: 'Start Date',
17 | value: getInputDate(details?.start_date),
18 | type: 'date',
19 | details: {
20 | className: 'date'
21 | }
22 | },
23 | {
24 | label: 'Due Date',
25 | value: getInputDate(details?.due_date),
26 | type: 'date',
27 | details: {
28 | className: 'date'
29 | }
30 | },
31 | {
32 | label: 'Status',
33 | type: 'dropdown',
34 | value: details?.status.toLowerCase(),
35 | details: {
36 | className: "dropdown",
37 | list: [
38 | { value: 'completed', label: 'Completed' }, { value: 'in progress', label: 'In progress' }, { value: 'backlog', label: 'Backlog' }, { value: 'up next', label: 'Up Next' }, { value: 'question', label: 'question' }, { value: 'on hold', label: 'On Hold' }
39 | ],
40 | }
41 | },
42 | {
43 | label: 'Assigned to',
44 | value: allEmployees.find((employee)=> employee.name === details?.assigned_to?.name),
45 | type: 'dropdown',
46 | details: {
47 | className: 'dropdown',
48 | list: allEmployees.map((employee)=> ({
49 | value: employee._id, label: employee.name
50 | }))
51 | }
52 | }
53 | ]
54 | }
55 | };
--------------------------------------------------------------------------------
/client/src/containers/profile/style.scss:
--------------------------------------------------------------------------------
1 | .profile-page{
2 | .profile-container{
3 | padding: 3rem 1rem;
4 | .profile-main-section {
5 | display: flex;
6 | justify-content: center;
7 | align-items: center;
8 | flex-direction: column;
9 | .user-icon {
10 | display: flex;
11 | justify-content: center;
12 | align-items: center;
13 | padding: 50px;
14 | background: var(--yellow);
15 | border-radius: 50%;
16 | margin-bottom: 20px;
17 | ion-icon {
18 | width: 100px;
19 | height: 100px;
20 | }
21 | }
22 | .profile-details{
23 | display: flex;
24 | flex-direction: column;
25 | gap: 20px;
26 | min-width: 40rem;
27 | padding: 20px;
28 | border: 2px solid var(--blue);
29 | border-radius: 20px;
30 | box-shadow: 0 5px 5px var(--blue);
31 | .custom-input{
32 | display: flex;
33 | justify-content: space-between;
34 | align-items: center;
35 | text-transform: capitalize;
36 | .field{
37 | font-size: larger;
38 | font-weight: bolder;
39 | width: 50%;
40 | }
41 | .value{
42 | width: 50%;
43 | border-bottom: 1px solid rgb(96, 95, 95);
44 | border-style: none none dotted none;
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/server/Controllers/reportController.js:
--------------------------------------------------------------------------------
1 | const Report = require("../db/reportModel");
2 | // const Report = require("../db/reportModel");
3 |
4 | exports.createReport = async (req, res) => {
5 | const { employee_id } = req.employee;
6 | // here sent to must be an Object
7 | const { title, description, sent_to } = req.body;
8 | try {
9 | const existingReport = await Report.find({ title: title });
10 | if (existingReport.length !== 0) {
11 | return res.status(409).json({
12 | status: "failed",
13 | message: "the report already exists.",
14 | });
15 | }
16 | const report = await Report.create({
17 | title,
18 | description,
19 | sent_to,
20 | sent_by: employee_id,
21 | });
22 |
23 | res.status(201).json({
24 | status: "success",
25 | message: "successfully created the report."
26 | });
27 | } catch (error) {
28 | res.status(403).json({
29 | status: "failed",
30 | message: error.message,
31 | });
32 | }
33 | };
34 |
35 | exports.getAllReports = async (req, res) => {
36 | try {
37 | const reports = await Report.find()
38 | .populate("sent_to", "name")
39 | .populate("sent_by", "name");
40 |
41 | res.status(200).json({
42 | status: "success",
43 | response: reports,
44 | });
45 | } catch (error) {
46 | res.status(404).json({
47 | status: "failed",
48 | message: error.message,
49 | });
50 | }
51 | };
52 |
53 | exports.deleteReport = async (req, res) => {
54 | const { id } = req.query;
55 | try {
56 | await Report.findByIdAndDelete(id);
57 |
58 | res.status(202).json({
59 | status: "success",
60 | message: "successfully deleted report.",
61 | });
62 | } catch (error) {
63 | res.status(404).json({
64 | status: "failed",
65 | message: error.message,
66 | });
67 | }
68 | };
69 |
--------------------------------------------------------------------------------
/client/src/containers/sign-up/styles.scss:
--------------------------------------------------------------------------------
1 | .signup-page {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 100vh;
6 | .signup-container {
7 | width: 700px;
8 | padding: 40px 30px;
9 | background: rgb(247 247 247);
10 | border-radius: 5px;
11 | box-shadow: 0px 2px 10px #848181;
12 | .signup-top {
13 | text-align: center;
14 | margin-bottom: 16px;
15 | .main-title{
16 | font-size: 26px;
17 | font-weight: bold;
18 | }
19 | }
20 | .form-section {
21 | margin-top: 16px;
22 | .email-component {
23 | margin-bottom: 24px;
24 | }
25 | .name-component{
26 | margin-bottom: 24px;
27 | }
28 | .btn{
29 | margin: 24px 0px;
30 | }
31 | .signup-link {
32 | display: block;
33 | text-align: center;
34 | text-decoration: none;
35 | font-size: 16px;
36 | font-weight: bold;
37 | color: #086f25;
38 | margin-top: 16px;
39 | }
40 | .resent-otp {
41 | text-align: center;
42 | font-size: 1rem;
43 | font-weight: 600;
44 | .link {
45 | color: var(--blue);
46 | cursor: pointer;
47 | }
48 | .text {
49 | color: #999;
50 | font-weight: 500;
51 | }
52 | }
53 | .bottom-text {
54 | font-size: 16px;
55 | text-align: center;
56 | margin-top: 16px;
57 | font-weight: bold;
58 | a {
59 | text-decoration: none;
60 | margin-left: 8px;
61 | color: #086f25;
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
68 | @media only screen and (max-width: 600px) {
69 | .signup-page {
70 | background: #f7f7f7;
71 | .signup-container {
72 | padding: 16px;
73 | box-shadow: none;
74 | }
75 | }
76 | }
--------------------------------------------------------------------------------
/website/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 | You need to enable JavaScript to run this app.
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/client/src/components/delete-modal/styles.scss:
--------------------------------------------------------------------------------
1 | .delete-modal {
2 | background-color: #00000080;
3 | position: fixed;
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | right: 0;
8 | top: 0;
9 | bottom: 0;
10 | left: 0;
11 | -ms-overflow-style: none;
12 | scrollbar-width: none;
13 | padding: 1rem 0.5rem;
14 | overflow: scroll;
15 | z-index: 50;
16 | .content {
17 | -ms-overflow-style: none;
18 | scrollbar-width: none;
19 | overflow-y: scroll;
20 | max-height: 95vh;
21 | background-color: var(--white);
22 | color: var(--black1);
23 | font-weight: bold;
24 | box-shadow: 0 4px 4px #364e7e1a;
25 | max-width: 600px;
26 | margin: auto;
27 | width: 100%;
28 | padding: 2rem;
29 | border-radius: 0.75rem;
30 | .title {
31 | font-weight: 700;
32 | color: var(--red);
33 | font-size: 1.25rem;
34 | line-height: 1.75rem;
35 | }
36 | .description {
37 | font-size: 0.75rem;
38 | line-height: 1rem;
39 | font-weight: 600;
40 | color: gray;
41 | padding-top: 1.5rem;
42 | letter-spacing: 0.025rem;
43 | }
44 | .btns {
45 | display: flex;
46 | justify-content: center;
47 | align-items: center;
48 | width: 100%;
49 | margin-top: 1rem;
50 | gap: 1rem;
51 | .delete-btn {
52 | color: var(--white);
53 | background-color: var(--red);
54 | }
55 | .cancel-btn {
56 | color: #635fc7;
57 | background-color: #635fc71a;
58 | }
59 | .delete-btn, .cancel-btn {
60 | font-size: 16px;
61 | border: none;
62 | width: 100%;
63 | cursor: pointer;
64 | border-radius: 9999px;
65 | padding-top: 0.5rem;
66 | padding-bottom: 0.5rem;
67 | &:hover {
68 | opacity: 0.75;
69 | }
70 | }
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/client/src/containers/app/all-routes.js:
--------------------------------------------------------------------------------
1 | import Login from '../sign-in';
2 | import Dashboard from '../dashboard';
3 | import Signup from '../sign-up/pre-signup';
4 | import loginData from '../../data/signin.json';
5 | import resetPasswordData from '../../data/resetPasswordData.json';
6 | import Ledger from '../tasks';
7 | import Departments from '../departments';
8 | import Employees from '../employees';
9 | import Projects from '../projects';
10 | import ResetPassword from '../resetPassword';
11 | import {
12 | Navigate
13 | } from "react-router-dom";
14 | import EmployeeDetails from '../employees/employeeDetails';
15 | import employeeDetailsData from '../../data/employeeDetailsData.json';
16 | import Profile from '../profile';
17 | import TasksBoard from '../tasks-board';
18 | import OTP_VERIFICATION from '../sign-up/OTP-verification';
19 |
20 | export const allRoutes = [
21 | {
22 | path: '/login',
23 | element:
24 | },
25 | {
26 | path: '/signup',
27 | element:
28 | },
29 | {
30 | path: '/otp-verification/:id',
31 | element:
32 | },
33 | {
34 | path: '/reset-password',
35 | element:
36 | },
37 | {
38 | path: '/',
39 | element:
40 | },
41 | {
42 | path: '/tasks',
43 | element:
44 | },
45 | {
46 | path: '/tasks/board/:id',
47 | element:
48 | },
49 | {
50 | path: '/departments',
51 | element:
52 | },
53 | {
54 | path: '/employees',
55 | element:
56 | },
57 | {
58 | path: '/employees/:id',
59 | element:
60 | },
61 | {
62 | path: '/projects',
63 | element:
64 | },
65 | {
66 | path: '/profile',
67 | element:
68 | },
69 | {
70 | path: '/*',
71 | element:
72 | }
73 | ];
--------------------------------------------------------------------------------
/client/src/data/dashboardData.js:
--------------------------------------------------------------------------------
1 |
2 | export const mapDashboardData = (allCounts = {}, employees = []) => {
3 |
4 | return {
5 | metaData: {
6 | title: "Dashboard | TOTE"
7 | },
8 | topSection: {
9 | allEntities: [
10 | {
11 | title: 'Total departments', count: allCounts?.departments || 0, link: '/departments'
12 | },
13 | {
14 | title: 'Total employees', count: allCounts?.employees || 0, link: '/employees'
15 | },
16 | {
17 | title: 'Total projects', count: allCounts?.projects || 0, link: '/projects'
18 | },
19 | ],
20 | },
21 | mainSection:{
22 | recentOrders : [
23 | {
24 | name: "Star Refrigerator", price: "$ 1200", payment: "Paid", status: "Delivered", statusStyle: "delivered",
25 | },
26 | {
27 | name: "Dell Laptop", price: "$ 110", payment: "Due", status: "Pending", statusStyle: "pending",
28 | },
29 | {
30 | name: "Apple Watch", price: "$ 1200", payment: "Paid", status: "Return", statusStyle: "return",
31 | },
32 | {
33 | name: "Addidas Shoes", price: "$ 620", payment: "Due", status: "In Progress", statusStyle: "inProgress",
34 | },
35 | {
36 | name: "Start Refrigerator", price: "$ 1200", payment: "Paid", status: "Delivered", statusStyle: "delivered",
37 | },
38 | {
39 | name: "Dell Laptop", price: "$ 110", payment: "Due", status: "Pending", statusStyle: "pending",
40 | },
41 | {
42 | name: "Apple Watch", price: "$ 1200", payment: "Paid", status: "Return", statusStyle: "return",
43 | },
44 | {
45 | name: "Addidas Shoes", price: "$ 620", payment: "Due", status: "In Progress", statusStyle: "inProgress",
46 | },
47 | ],
48 | recentCustomers: employees?.map((employee)=> ({
49 | id: employee?._id,
50 | name: employee?.name,
51 | email: employee?.email
52 | })) || [],
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/client/src/containers/tasks/board-card.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { useDispatch } from "react-redux";
3 | import { boardActions } from "../../redux/reducers/board";
4 | import DeleteModal from "../../components/delete-modal/DeleteModal";
5 | import { deleteBoardById } from "../../services/tasks/taskBoards";
6 | import { sendErrorNotification, sendSuccessNotification } from "../../services/notifications";
7 |
8 | const BoardCard = ({ id, name, onClickEdit, onSelectBoard }) => {
9 | const [isTooltipActive, setIsTooltipActive] = useState(false);
10 | const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
11 | const [isDeleteLoading, setIsDeleteLoading] = useState(false);
12 | const dispatch = useDispatch();
13 |
14 | const handleEdit = () => {
15 | setIsTooltipActive(false);
16 | onClickEdit(id);
17 | }
18 |
19 | const handleDelete = async () => {
20 | setIsDeleteLoading(true);
21 | const result = await deleteBoardById(id);
22 | if(result.status === 'success') {
23 | dispatch(boardActions.deleteBoard({id}));
24 | sendSuccessNotification(result.message);
25 | }else{
26 | sendErrorNotification(result.message);
27 | }
28 | setIsDeleteLoading(false);
29 | setIsDeleteModalOpen(false);
30 | }
31 |
32 | return (
33 |
34 | {isDeleteModalOpen ?
: null}
35 |
onSelectBoard(id)}>
36 |
{name}
37 |
38 |
39 | {isTooltipActive ?
40 | Edit board
41 | setIsDeleteModalOpen(true)}>Delete board
42 |
: null}
43 |
setIsTooltipActive(prev => !prev)}>
44 |
45 |
46 | )
47 | };
48 |
49 | export default BoardCard;
--------------------------------------------------------------------------------
/client/src/services/projects/projects.js:
--------------------------------------------------------------------------------
1 | import { fetchUrl } from '../../utils/fetchUrl';
2 | import { getLocalStorageKey } from '../../utils/localStorage';
3 |
4 | export const createProject = async (details) => {
5 | const token = getLocalStorageKey('token');
6 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/projects/create-project';
7 |
8 | const headers = new Headers();
9 | headers.append('Content-Type','application/json');
10 | headers.append('token',token);
11 |
12 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(details), redirect: 'follow'};
13 | return await fetchUrl(url, requestOptions);
14 | };
15 |
16 | export const getProjectDetails = async (id) => {
17 | const token = getLocalStorageKey('token');
18 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/projects/project';
19 |
20 | const headers = new Headers();
21 | headers.append('Content-Type','application/json');
22 | headers.append('token',token);
23 |
24 | const query = `?id=${id}`;
25 |
26 | const requestOptions = {method: 'GET', headers, redirect: 'follow'};
27 | return await fetchUrl(url+query, requestOptions);
28 | };
29 |
30 | export const getListProjects = async () => {
31 | const token = getLocalStorageKey('token');
32 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/projects/list-projects';
33 |
34 | const headers = new Headers();
35 | headers.append('Content-Type','application/json');
36 | headers.append('token',token);
37 |
38 | const requestOptions = {method: 'GET', headers, redirect: 'follow'};
39 | return await fetchUrl(url, requestOptions);
40 | };
41 |
42 | export const updateProject = async (id, details) => {
43 | const token = getLocalStorageKey('token');
44 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/projects/update-project';
45 |
46 | const headers = new Headers();
47 | headers.append('Content-Type','application/json');
48 | headers.append('token',token);
49 |
50 | const query = `?id=${id}`;
51 |
52 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(details), redirect: 'follow'};
53 | return await fetchUrl(url+query, requestOptions);
54 | };
--------------------------------------------------------------------------------
/server/Controllers/department.js:
--------------------------------------------------------------------------------
1 | const Department = require('../db/departmentModel');
2 |
3 | exports.createDepartment = async (req, res) => {
4 | const departObj = req.body;
5 | try{
6 | const existingDepartment = await Department.find({title: departObj.title});
7 | if(existingDepartment.length !== 0){
8 | return res.status(409).json({
9 | status: 'failed',
10 | message: 'the department already exists.'
11 | })
12 | }
13 | await Department.create(departObj);
14 |
15 | res.status(201).json({
16 | status: 'success',
17 | message: "successfully created the department."
18 | })
19 | }catch(error){
20 | res.status(403).json({
21 | status: "failed",
22 | message: error.message
23 | })
24 | }
25 | }
26 |
27 | exports.getAllDepartments = async (req, res) => {
28 | try{
29 | const departments = await Department.find();
30 |
31 | res.status(200).json({
32 | status: 'success',
33 | response: departments
34 | })
35 | }catch(error){
36 | res.status(404).json({
37 | status: 'failed',
38 | message: error.message
39 | })
40 | }
41 | }
42 |
43 | exports.deleteDepartment = async (req, res) => {
44 | const {id} = req.query;
45 | try{
46 | await Department.findByIdAndDelete(id);
47 |
48 | res.status(202).json({
49 | status: "success",
50 | message: "successfully deleted department."
51 | })
52 | }catch(error){
53 | res.status(404).json({
54 | status: 'failed',
55 | message: error.message
56 | })
57 | }
58 | }
59 |
60 | exports.getDepartmentCount = async (_, res) => {
61 | try{
62 | const count = await Department.countDocuments();
63 |
64 | res.status(202).json({
65 | status: 'success',
66 | response: count
67 | })
68 | }catch(error){
69 | res.status(500).json({
70 | status: 'failed',
71 | message: error.message
72 | })
73 | }
74 | };
--------------------------------------------------------------------------------
/client/src/containers/tasks/welcome-page.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import BoardCard from './board-card';
3 | import { useDispatch, useSelector } from 'react-redux';
4 | import { boardActions } from '../../redux/reducers/board';
5 | import CreateBoardModal from '../../components/create-board-modal/create-board-modal';
6 | import { useNavigate } from 'react-router-dom';
7 |
8 | const WelcomePage = ({ fetchBoards }) => {
9 | const { boards } = useSelector(state => state.boards);
10 | const [isBoardModalOpen, setIsBoardModalOpen] = useState({ type: 'add', status: false });
11 | const dispatch = useDispatch();
12 | const navigate = useNavigate();
13 |
14 | const onClickEdit = (id) => {
15 | dispatch(boardActions.setSelectedBoard(boards.find(board => board._id === id)));
16 | setIsBoardModalOpen({ type: 'edit', status: true, id });
17 | }
18 |
19 | const onSelectBoard = (id) => {
20 | dispatch(boardActions.setSelectedBoard({id}));
21 | navigate(`/tasks/board/${id}`);
22 | }
23 |
24 | return (
25 |
26 | {isBoardModalOpen.status ?
setIsBoardModalOpen(prev => ({ ...prev, status}))}
28 | type={isBoardModalOpen.type}
29 | refetchData={fetchBoards}
30 | /> : null}
31 | {boards.length !== 0 ? (
32 | <>
33 |
34 | {boards.map((board, index)=> {
35 | return
42 | })}
43 |
44 | setIsBoardModalOpen({ type: 'add', status: true })}>
45 | + Create new board
46 |
47 | >
48 | ) :
49 | setIsBoardModalOpen({ type: 'add', status: true })}>
50 | Click to create new
51 |
}
52 |
53 | )
54 | }
55 |
56 | export default WelcomePage
57 |
--------------------------------------------------------------------------------
/client/src/containers/departments/create-depart-form.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react'
2 | import Button from '../../components/button'
3 | import SimpleInput from '../../components/input'
4 | import { createDepartment } from '../../services/departments/departments';
5 | import { sendErrorNotification, sendSuccessNotification } from '../../services/notifications';
6 | import { checkAllTrue } from '../../utils/check-all';
7 | import './create-depart.scss';
8 |
9 | const CreateDepartForm = (props) => {
10 | const [[name, isNameValid], setName] = useState(['', false]);
11 | const [[url, isUrlValid], setURL] = useState(['', false]);
12 |
13 | const handleSubmit = async (e)=> {
14 | e.preventDefault();
15 | if(!checkAllTrue([isNameValid, isUrlValid])){
16 | sendErrorNotification("Please fill the details");
17 | return;
18 | }
19 | const details = {title: name, url};
20 | const response = await createDepartment(details);
21 | if(response.status === 'success'){
22 | sendSuccessNotification("Successfully created a new department");
23 | props.closeModal();
24 | props.fetchData();
25 | }else{
26 | sendErrorNotification(response.message);
27 | }
28 | }
29 |
30 | const formComponents = {
31 | name: (key, props) => setName([value, !!value])}
34 | />,
35 | url: (key, props) => setURL([value, !!value])}
38 | />,
39 | button: (key, props) =>
42 | }
43 | return (
44 |
45 |
51 |
52 | )
53 | }
54 |
55 | export default CreateDepartForm
56 |
--------------------------------------------------------------------------------
/client/src/services/employees/employee-details.js:
--------------------------------------------------------------------------------
1 | import { fetchUrl } from "../../utils/fetchUrl";
2 | import { getLocalStorageKey } from "../../utils/localStorage";
3 |
4 | export const getEmployeeDetails = async ()=>{
5 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/employeeDetails';
6 | const token = getLocalStorageKey('token');
7 | const headers = new Headers();
8 | headers.append('Content-Type', 'application/json');
9 | headers.append('token', token);
10 | const requestOptions = {method: 'GET', headers, redirect: 'follow'};
11 | return await fetchUrl(url, requestOptions);
12 | }
13 |
14 | export const getEmployeeDetailsById = async (id)=>{
15 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/employeeDetails';
16 | const token = getLocalStorageKey('token');
17 | const headers = new Headers();
18 | headers.append('Content-Type', 'application/json');
19 | headers.append('token', token);
20 | const query = '?id='+id;
21 | const requestOptions = {method: 'GET', headers, redirect: 'follow'};
22 | return await fetchUrl(url+query, requestOptions);
23 | }
24 |
25 | export const updateEmployeeDetails = async (id, details) => {
26 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/updateEmployee';
27 | const token = getLocalStorageKey('token');
28 | const headers = new Headers();
29 | headers.append('Content-Type', 'application/json');
30 | headers.append('token', token);
31 | const body = {
32 | ...details
33 | }
34 | const query = '?id='+id;
35 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(body), redirect: 'follow'};
36 | return await fetchUrl(url + query, requestOptions);
37 | }
38 |
39 | export const deleteEmployee = async (id) => {
40 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/employees/deleteEmployee';
41 | const token = getLocalStorageKey('token');
42 | const headers = new Headers();
43 | headers.append('Content-Type', 'application/json');
44 | headers.append('token', token);
45 | const query = '?id='+id;
46 | const requestOptions = {method: 'DELETE', headers, redirect: 'follow'};
47 | return await fetchUrl(url + query, requestOptions);
48 | }
--------------------------------------------------------------------------------
/client/src/containers/dashboard/main-section.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 |
4 | const RecentOrders = (props) => (
5 |
6 |
7 |
Recent Orders
8 | View All
9 |
10 |
11 |
12 |
13 | Name
14 | Price
15 | Payment
16 | Status
17 |
18 |
19 |
20 | {props.orders.map((order, index) => (
21 |
22 | {order.name}
23 | {order.price}
24 | {order.payment}
25 | {order.status}
26 |
27 | ))}
28 |
29 |
30 |
31 | );
32 |
33 | const RecentCustomers = (props) => (
34 |
35 |
36 |
Recent Customers
37 |
38 |
39 | {props.customers.map((customer, index) => (
40 | props.onClickRow(customer.id)}>
41 |
42 |
43 |
44 |
45 | {customer.name} {customer.email}
46 |
47 |
48 | ))}
49 |
50 |
51 | );
52 |
53 | const MainSection = (props) => {
54 | const navigate = useNavigate();
55 |
56 | const onClickRow = (id)=>{
57 | navigate(`/employees/${id}`);
58 | }
59 |
60 | return (
61 |
67 | )
68 | }
69 |
70 | export default MainSection
71 |
--------------------------------------------------------------------------------
/client/src/containers/tasks-board/column.js:
--------------------------------------------------------------------------------
1 | import { shuffle } from "lodash";
2 | import React, { useEffect, useState } from "react";
3 | import { useDispatch, useSelector } from "react-redux";
4 | import Task from "./Task";
5 | import { boardActions } from "../../redux/reducers/board";
6 | import { dragTaskToNewCol } from "../../services/tasks/tasks";
7 | import { sendErrorNotification, sendSuccessNotification } from "../../services/notifications";
8 |
9 | const colors = [
10 | "bg-red-500",
11 | "bg-orange-500",
12 | "bg-blue-500",
13 | "bg-purple-500",
14 | "bg-green-500",
15 | "bg-indigo-500",
16 | "bg-yellow-500",
17 | "bg-pink-500",
18 | "bg-sky-500",
19 | ];
20 |
21 | const Column = ({ colId }) => {
22 |
23 | const dispatch = useDispatch();
24 | const [color, setColor] = useState(null)
25 | const board = useSelector((state) => state.boards.selectedBoard);
26 | const col = board.columns.find((col) => col._id === colId);
27 | useEffect(() => {
28 | setColor(shuffle(colors).pop())
29 | }, [dispatch]);
30 |
31 | const updateDraggedTask = async (boardId, taskId, { prevColId, colId }) => {
32 | const result = await dragTaskToNewCol(boardId, taskId, { prevColId, colId });
33 | if(result.status === 'success') {
34 | dispatch(boardActions.dragTask({ colId, prevColId, taskId, task: result.response }));
35 | sendSuccessNotification(result.message);
36 | }else{
37 | sendErrorNotification(result.message);
38 | }
39 | }
40 |
41 | const handleOnDrop = async (e) => {
42 | const { prevColId, taskId } = JSON.parse(e.dataTransfer.getData("text"));
43 | if (colId === prevColId) return;
44 | await updateDraggedTask(board._id, taskId, { prevColId, colId });
45 | };
46 |
47 | const handleOnDragOver = (e) => {
48 | e.preventDefault();
49 | };
50 |
51 | return (
52 |
57 |
58 |
59 | {col.name} ({col.tasks.length})
60 |
61 |
62 | {col.tasks.map((task, index) => (
63 |
64 | ))}
65 |
66 | );
67 | }
68 |
69 | export default Column;
70 |
--------------------------------------------------------------------------------
/client/src/containers/tasks-board/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-hooks/exhaustive-deps */
2 | import React, { useEffect, useState } from 'react'
3 | import TopSection from './top-section';
4 | import './styles.scss';
5 | import { useParams } from 'react-router-dom';
6 | import { useDispatch, useSelector } from 'react-redux';
7 | import Column from './column';
8 | import EmptyBoard from './empty-board';
9 | import CreateBoardModal from '../../components/create-board-modal/create-board-modal';
10 | import { getBoardById } from '../../services/tasks/taskBoards';
11 | import { boardActions } from '../../redux/reducers/board';
12 | import Loader from '../../components/loader';
13 |
14 | const TasksBoard = () => {
15 | const [isBoardModalOpen, setIsBoardModalOpen] = useState(false);
16 | const [isLoading, setIsLoading] = useState(true);
17 | const dispatch = useDispatch();
18 | const { id } = useParams();
19 | const board = useSelector((state) => state.boards.selectedBoard);
20 |
21 | const fetchBoardDetails = async () => {
22 | const result = await getBoardById(id);
23 | if(result.status === 'success'){
24 | dispatch(boardActions.setSelectedBoard(result.response));
25 | }
26 | setIsLoading(false);
27 | };
28 |
29 | useEffect(()=> {
30 | fetchBoardDetails();
31 | }, [id]);
32 |
33 | return ( isLoading ? :
34 |
35 |
36 |
37 | {board?.columns.length > 0 ? (
38 | <>
39 | {board.columns.map((col, index) => (
40 |
41 | ))}
42 |
{
44 | setIsBoardModalOpen(true);
45 | }}
46 | className="new-column-btn"
47 | >
48 | + New Column
49 |
50 | >
51 | ) : (
52 | <>
53 |
54 | >
55 | )}
56 | {isBoardModalOpen && (
57 |
63 | )}
64 |
65 |
66 | )
67 | }
68 |
69 | export default TasksBoard;
--------------------------------------------------------------------------------
/client/src/services/tasks/tasks.js:
--------------------------------------------------------------------------------
1 | import { getLocalStorageKey } from '../../utils/localStorage';
2 | import { fetchUrl } from '../../utils/fetchUrl';
3 |
4 | export const createTask = async (boardId, columnId, task) => {
5 | const token = getLocalStorageKey('token');
6 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/tasks/create-task';
7 |
8 | const headers = new Headers();
9 | headers.append('Content-Type','application/json');
10 | headers.append('token',token);
11 | const params = `/${boardId}/columns/${columnId}/tasks`;
12 |
13 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(task), redirect: 'follow'};
14 | return await fetchUrl(url + params, requestOptions);
15 | };
16 |
17 | export const updateTask = async (boardId, taskId, columnId, task) => {
18 | const token = getLocalStorageKey('token');
19 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/tasks/update-task';
20 |
21 | const headers = new Headers();
22 | headers.append('Content-Type','application/json');
23 | headers.append('token',token);
24 | const params = `/${boardId}/columns/${columnId}/tasks/${taskId}`;
25 |
26 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(task), redirect: 'follow'};
27 | return await fetchUrl(url + params, requestOptions);
28 | };
29 |
30 | export const dragTaskToNewCol = async (boardId, taskId, { prevColId, colId }) => {
31 | const token = getLocalStorageKey('token');
32 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/tasks/update-task';
33 |
34 | const headers = new Headers();
35 | headers.append('Content-Type','application/json');
36 | headers.append('token',token);
37 | const params = `/${boardId}/tasks/${taskId}`;
38 | const details = { prevColId, colId };
39 |
40 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(details), redirect: 'follow'};
41 | return await fetchUrl(url + params, requestOptions);
42 | };
43 |
44 | export const deleteTask = async (boardId, taskId) => {
45 | const token = getLocalStorageKey('token');
46 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/tasks/delete-task';
47 |
48 | const headers = new Headers();
49 | headers.append('Content-Type','application/json');
50 | headers.append('token',token);
51 | const params = `/${boardId}/tasks/${taskId}`;
52 |
53 | const requestOptions = {method: 'delete', headers, redirect: 'follow'};
54 | return await fetchUrl(url + params, requestOptions);
55 | };
--------------------------------------------------------------------------------
/client/src/data/employeeDetailsData.json:
--------------------------------------------------------------------------------
1 | {
2 | "metaData": {
3 | "title": "Employees Details | TOTE"
4 | },
5 | "topSection": {
6 | "title": "Employees Details"
7 | },
8 | "formSection": {
9 | "inputComponents": [
10 | {
11 | "component": "name",
12 | "details": {
13 | "label": "full name",
14 | "type": "text",
15 | "placeholder": "full name"
16 | }
17 | },
18 | {
19 | "component": "email",
20 | "details": {
21 | "label": "email id",
22 | "type": "email",
23 | "placeholder": "email id"
24 | }
25 | },
26 | {
27 | "component": "phone",
28 | "details": {
29 | "label": "Phone",
30 | "type": "tel",
31 | "placeholder": "phone number"
32 | }
33 | },
34 | {
35 | "component": "address",
36 | "details": {
37 | "label": "Address",
38 | "type": "text",
39 | "placeholder": "address"
40 | }
41 | },
42 | {
43 | "component": "dropdown",
44 | "details": {
45 | "label": "Role",
46 | "items": [
47 | "agent",
48 | "admin"
49 | ]
50 | }
51 | }
52 | ],
53 | "inputButtons": [
54 | {
55 | "component": "button",
56 | "details": {
57 | "text": "Update",
58 | "type": "submit",
59 | "button": "primary"
60 | }
61 | },
62 | {
63 | "component": "cancelButton",
64 | "details": {
65 | "text": "Cancel",
66 | "type": "button",
67 | "button": "success"
68 | }
69 | },
70 | {
71 | "component": "deleteButton",
72 | "details": {
73 | "text": "Delete",
74 | "type": "button",
75 | "button": "danger"
76 | }
77 | }
78 | ]
79 | }
80 | }
--------------------------------------------------------------------------------
/client/src/containers/departments/main-section.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { WebIcon } from '../../components/icons';
3 | import Button from '../../components/button';
4 | import Modal from '../../components/modal';
5 | import CreateDepartForm from './create-depart-form';
6 |
7 | const CustomCard = (props) => (
8 | props.onClick(props.url)}>
9 |
10 |
11 | {props.title}
12 | {props.url}
13 |
14 |
15 | )
16 |
17 | const Filter = (props) => (
18 |
19 |
20 | props.setSearchText(event.target.value)}
23 | placeholder={props.search.placeholder}
24 | />
25 |
26 |
27 |
32 |
33 | )
34 |
35 | const MainSection = (props) => {
36 | const [searchText, setSearchText] = useState('');
37 | const [isDepartModal, setDepartModal] = useState(false);
38 | const filteredData = searchText.length ? props.departmentsCards
39 | .filter((item)=> (item.title.toLowerCase().includes(searchText.toLowerCase())))
40 | : props.departmentsCards;
41 |
42 | const handleClick = (url) => {
43 | window.open(url, '_blank', 'noopener,noreferrer');
44 | }
45 |
46 | return (
47 |
48 |
setDepartModal(true)}
53 | />
54 |
55 | {isDepartModal && setDepartModal(false)}
59 | >
60 |
65 | }
66 |
67 |
68 | {filteredData.map((item, index)=>(
69 |
74 | ))}
75 |
76 |
77 | )
78 | }
79 |
80 | export default MainSection
--------------------------------------------------------------------------------
/website/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/client/src/components/create-board-modal/styles.scss:
--------------------------------------------------------------------------------
1 | .create-board-modal {
2 | position: fixed;
3 | right: 0;
4 | top: 0;
5 | padding: 1rem 0.5rem;
6 | -ms-overflow-style: none;
7 | scrollbar-width: none;
8 | z-index: 50;
9 | left: 0;
10 | bottom: 0;
11 | display: flex;
12 | justify-content: center;
13 | align-items: center;
14 | background-color: #00000080;
15 | .content{
16 | -ms-overflow-style: none;
17 | scrollbar-width: none;
18 | overflow-y: scroll;
19 | max-height: 95vh;
20 | background-color: var(--white);
21 | color: var(--black1);
22 | font-weight: bold;
23 | box-shadow: 0 4px 4px #364e7e1a;
24 | max-width: 600px;
25 | margin: auto;
26 | width: 100%;
27 | padding: 2rem;
28 | border-radius: 0.75rem;
29 | .title {
30 | font-size: 1.125rem;
31 | line-height: 1.75rem;
32 | }
33 | .board-name {
34 | margin-top: 2rem;
35 | display: flex;
36 | flex-direction: column;
37 | label {
38 | font-size: 0.875rem;
39 | line-height: 1.25rem;
40 | }
41 | input {
42 | outline-width: 1px;
43 | font-size: 0.875rem;
44 | line-height: 1.25rem;
45 | padding: 0.5rem 1rem;
46 | background-color: transparent;
47 | border-width: 0.5px;
48 | border-radius: 0.375rem;
49 | }
50 | }
51 | .board-columns {
52 | margin-top: 2rem;
53 | display: flex;
54 | flex-direction: column;
55 | label {
56 | font-size: 0.875rem;
57 | line-height: 1.25rem;
58 | }
59 | .column {
60 | width: 100%;
61 | display: flex;
62 | align-items: center;
63 | margin-bottom: 1rem;
64 | input {
65 | outline-width: 1px;
66 | font-size: 0.875rem;
67 | line-height: 1.25rem;
68 | padding: 0.5rem 1rem;
69 | background-color: transparent;
70 | border-radius: 0.375rem;
71 | flex-grow: 1;
72 | }
73 | span {
74 | margin-left: 1rem;
75 | cursor: pointer;
76 | font-size: 1.4rem;
77 | }
78 | }
79 | }
80 | .btns {
81 | .btn{
82 | width: 100%;
83 | display: flex;
84 | align-items: center;
85 | justify-content: center;
86 | padding: 0.5rem 0;
87 | border-radius: 9999px;
88 | cursor: pointer;
89 | color: var(--white);
90 | background-color: #635fc7;
91 | font-size: 16px;
92 | }
93 | .save-btn {
94 | margin-top: 2rem;
95 | }
96 | }
97 | }
98 | }
--------------------------------------------------------------------------------
/client/src/services/tasks/taskBoards.js:
--------------------------------------------------------------------------------
1 | import { getLocalStorageKey } from '../../utils/localStorage';
2 | import { fetchUrl } from '../../utils/fetchUrl';
3 |
4 | export const getAllBoards = async () => {
5 | const token = getLocalStorageKey('token');
6 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/task-board/boards';
7 |
8 | const headers = new Headers();
9 | headers.append('Content-Type','application/json');
10 | headers.append('token',token);
11 |
12 | const requestOptions = {method: 'GET', headers, redirect: 'follow'};
13 | return await fetchUrl(url, requestOptions);
14 | };
15 |
16 | export const createBoard = async (details) => {
17 | const token = getLocalStorageKey('token');
18 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/task-board/boards';
19 |
20 | const headers = new Headers();
21 | headers.append('Content-Type','application/json');
22 | headers.append('token',token);
23 |
24 | const requestOptions = {method: 'POST', headers, body: JSON.stringify(details), redirect: 'follow'};
25 | return await fetchUrl(url, requestOptions);
26 | };
27 |
28 | export const getBoardById = async (id) => {
29 | const token = getLocalStorageKey('token');
30 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/task-board/boards';
31 |
32 | const headers = new Headers();
33 | headers.append('Content-Type','application/json');
34 | headers.append('token',token);
35 |
36 | const params = '/' + id;
37 |
38 | const requestOptions = {method: 'GET', headers, redirect: 'follow'};
39 | return await fetchUrl(url + params, requestOptions);
40 | };
41 |
42 | export const deleteBoardById = async (id) => {
43 | const token = getLocalStorageKey('token');
44 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/task-board/boards';
45 |
46 | const headers = new Headers();
47 | headers.append('Content-Type','application/json');
48 | headers.append('token',token);
49 |
50 | const params = '/' + id;
51 |
52 | const requestOptions = {method: 'DELETE', headers, redirect: 'follow'};
53 | return await fetchUrl(url + params, requestOptions);
54 | };
55 |
56 | export const updateBoardById = async (id, details) => {
57 | const token = getLocalStorageKey('token');
58 | const url = process.env.REACT_APP_BASE_URI + '/api/v1/task-board/boards';
59 |
60 | const headers = new Headers();
61 | headers.append('Content-Type','application/json');
62 | headers.append('token',token);
63 |
64 | const params = '/' + id;
65 |
66 | const requestOptions = {method: 'PUT', headers, body: JSON.stringify(details), redirect: 'follow'};
67 | return await fetchUrl(url + params, requestOptions);
68 | };
--------------------------------------------------------------------------------
/client/src/containers/sign-up/pre-signup/form-section.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import { preSignup } from '../../../services/login/signup';
4 | import { checkAllTrue } from '../../../utils/check-all';
5 | import { sendErrorNotification, sendSuccessNotification } from '../../../services/notifications';
6 | import Email from '../../../components/email';
7 | import Password from '../../../components/password';
8 | import Name from '../../../components/name';
9 | import Button from '../../../components/button';
10 |
11 | const FormSection = (props) => {
12 | const navigate = useNavigate();
13 | const [[name, isNameValid], setName] = useState(['', false]);
14 | const [[email, isEmailValid], setEmail] = useState(['', false]);
15 | const [[password, isPasswordValid], setPassword] = useState(['', false]);
16 |
17 | const onSubmitHandle = async (event) => {
18 | event.preventDefault();
19 | if (!checkAllTrue([isEmailValid, isPasswordValid, isNameValid])) {
20 | sendErrorNotification('Form Incomplete - Please fill details below.');
21 | return;
22 | }
23 | const result = await preSignup(name, email, password);
24 | if(result.status === 'success') {
25 | sendSuccessNotification(result.message);
26 | const { id } = result.response;
27 | navigate(`/otp-verification/${id}`);
28 | }else{
29 | sendErrorNotification(result.message);
30 | }
31 |
32 | };
33 |
34 | const formComponents = {
35 | email: (key, props) => setEmail([value, isValid]) }} />,
36 | password: (key, props) => setPassword([value, isValid]) }} />,
37 | name: (key, props) => setName([value, isValid]) }} />,
38 | button: (key, props) => ,
39 | link: (key, props) => {props.text}
40 | };
41 |
42 | return
43 |
49 |
50 | {props.bottomText}
51 | {props.linkText}
52 |
53 |
54 | };
55 |
56 | export default FormSection;
--------------------------------------------------------------------------------
/client/src/containers/departments/styles.scss:
--------------------------------------------------------------------------------
1 | .departments-page{
2 | width: 100%;
3 | overflow: hidden;
4 | .departments-container{
5 | padding: 20px;
6 | .main-section{
7 | width: 100%;
8 | .filter{
9 | display: flex;
10 | justify-content: space-between;
11 | .btn button{
12 | padding: 0px 8px;
13 | }
14 | .search {
15 | width: 20rem;
16 | input{
17 | width: 100%;
18 | border: 2px solid #dbdfea;
19 | border-radius: 5px;
20 | height: 28px;
21 | font-size: 16px;
22 | padding: 16px 0px 16px 16px;
23 | &:focus{
24 | -webkit-box-shadow: 1px 1px 12px 4px rgba(252,36,252,0.78);
25 | -moz-box-shadow: 1px 1px 12px 4px rgba(252,36,252,0.78);
26 | box-shadow: 1px 1px 12px 4px rgba(252,36,252,0.78);
27 | outline: none;
28 | }
29 | }
30 | }
31 | }
32 | .cards-container{
33 | max-width: 1200px;
34 | margin: 40px auto;
35 | display: flex;
36 | justify-content: center;
37 | flex-wrap: wrap;
38 | gap: 1rem;
39 | .custom-card{
40 | cursor: pointer;
41 | text-decoration: none;
42 | display: flex;
43 | align-content: center;
44 | width: 20rem;
45 | padding: 1rem;
46 | box-shadow: 0 1px 5px #d9cfcf;
47 | &:hover {
48 | box-shadow: 0 5px 5px #d9cfcf;
49 | }
50 | .content{
51 | display: flex;
52 | flex-direction: column;
53 | height: 100%;
54 | justify-content: center;
55 | strong{
56 | font-size: larger;
57 | color: #000;
58 | text-transform: capitalize;
59 | }
60 | }
61 | }
62 | }
63 | .create-depart.modal .modal-container {
64 | max-width: 30rem;
65 | }
66 | }
67 | }
68 | }
69 |
70 | @media only screen and (max-width: 900px) {
71 | .main-section .search input{
72 | width: 100%;
73 | }
74 | }
--------------------------------------------------------------------------------
/client/src/index.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: "Roboto";
3 | src: url("./font/font-Regular.ttf") format("truetype");
4 | font-weight: normal;
5 | font-style: normal;
6 | }
7 |
8 | @font-face {
9 | font-family: "Roboto";
10 | src: url("./font/font-Medium.ttf") format("truetype");
11 | font-weight: 500;
12 | font-style: normal;
13 | }
14 |
15 | @font-face {
16 | font-family: "Roboto";
17 | src: url("./font/font-Bold.ttf") format("truetype");
18 | font-weight: bold;
19 | font-style: normal;
20 | }
21 |
22 | @font-face {
23 | font-family: "Roboto";
24 | src: url("./font/font-Light.ttf") format("truetype");
25 | font-weight: lighter;
26 | font-style: normal;
27 | }
28 |
29 | @font-face {
30 | font-family: "Nunito";
31 | src: url("./font/Nunito-Regular.ttf") format("truetype");
32 | font-weight: normal;
33 | font-style: normal;
34 | }
35 |
36 | @font-face {
37 | font-family: "Nunito";
38 | src: url("./font/Nunito-Medium.ttf") format("truetype");
39 | font-weight: 500;
40 | font-style: normal;
41 | }
42 |
43 | @font-face {
44 | font-family: "Nunito";
45 | src: url("./font/Nunito-Bold.ttf") format("truetype");
46 | font-weight: bold;
47 | font-style: normal;
48 | }
49 |
50 | * {
51 | box-sizing: border-box;
52 | scroll-behavior: smooth;
53 | scrollbar-color: #145996 #afaeae;
54 | scrollbar-width: 10px;
55 | scrollbar-width: thin;
56 | margin: 0;
57 | padding: 0;
58 | }
59 |
60 | :root {
61 | --blue: #2a2185;
62 | --white: #fff;
63 | --gray: #f5f5f5;
64 | --black1: #222;
65 | --black2: #999;
66 | --yellow: #e0e01c;
67 | --gray: #653d3d;
68 | --red: #890909;
69 | --shadow-color: #364e7e1a;
70 | }
71 |
72 | #root {
73 | position: relative;
74 | }
75 |
76 | body {
77 | margin: 0;
78 | font-family: "Nunito", sans-serif;
79 | -webkit-font-smoothing: antialiased;
80 | -moz-osx-font-smoothing: grayscale;
81 | color: #364a63;
82 | font-size: 14px;
83 | }
84 |
85 | input:focus-visible {
86 | outline: none;
87 | }
88 |
89 | input::placeholder,
90 | textarea::placeholder {
91 | opacity: 1;
92 | color: #9ca3af;
93 | }
94 |
95 | .text-primary {
96 | color: #A31ACB;
97 | }
98 |
99 | .main-container {
100 | width: calc(100% - 200px);
101 | position: absolute;
102 | top: 0;
103 | left: 200px;
104 | transition: all 0.6s;
105 | }
106 |
107 | .main-container.hidden {
108 | width: 100%;
109 | left: 0;
110 | transition: all 0.3s;
111 | }
112 |
113 | .main-container.active {
114 | width: calc(100% - 80px);
115 | left: 80px;
116 | }
117 |
118 | @media only screen and (max-width: 600px) {
119 | .rnc__notification {
120 | width: 95vw !important;
121 | }
122 | }
--------------------------------------------------------------------------------
/client/src/containers/tasks/styles.scss:
--------------------------------------------------------------------------------
1 | .task-page {
2 | .tasks-welcome-page{
3 | display: flex;
4 | justify-content: center;
5 | flex-direction: column;
6 | width: 100%;
7 | padding: 40px 0px;
8 | .boards-container {
9 | display: flex;
10 | align-items: center;
11 | gap: 40px;
12 | min-height: 66vh;
13 | overflow: auto;
14 | padding: 40px;
15 | scroll-behavior: smooth;
16 | width: 100%;
17 | flex-wrap: wrap;
18 | justify-content: center;
19 | .board-detail-container {
20 | box-shadow: 1.5px 1px 4px;
21 | border-radius: 10px;
22 | display: flex;
23 | justify-content: space-between;
24 | .board-card {
25 | cursor: pointer;
26 | display: flex;
27 | align-items: center;
28 | gap: 10px;
29 | padding: 20px 0 20px 20px;
30 | width: 16rem;
31 | .name {
32 | color: var(--blue);
33 | font-size: 20px;
34 | font-weight: bold;
35 | text-overflow: ellipsis;
36 | overflow: hidden;
37 | white-space: nowrap;
38 | }
39 | }
40 | .actions {
41 | position: relative;
42 | display: flex;
43 | justify-content: center;
44 | align-items: center;
45 | margin-right: 8px;
46 | .dots::after {
47 | cursor: pointer;
48 | content: "⠇";
49 | font-size: 26px;
50 | color: blue;
51 | }
52 | .tooltip {
53 | width: max-content;
54 | position: absolute;
55 | top: 100%;
56 | right: 7%;
57 | padding: 4px 15px;
58 | display: flex;
59 | justify-content: flex-start;
60 | align-items: flex-start;
61 | flex-direction: column;
62 | box-shadow: 1.5px 1.5px 4px;
63 | background: white;
64 | z-index: 1;
65 | border-radius: 5px;
66 | gap: 4px;
67 | font-size: 16px;
68 | .edit, .delete {
69 | cursor: pointer;
70 | }
71 | .delete {
72 | color: var(--red);
73 | }
74 | }
75 | }
76 | }
77 | }
78 | .create-btn {
79 | cursor: pointer;
80 | margin: 20px auto;
81 | padding: 10px;
82 | background-color: var(--blue);
83 | color: var(--white);
84 | font-size: 20px;
85 | border-radius: 10px;
86 | }
87 | .empty-board {
88 | cursor: pointer;
89 | height: 50vh;
90 | display: flex;
91 | justify-content: center;
92 | align-items: center;
93 | font-size: 46px;
94 | background-color: #d5bfbf;
95 | margin: 0.5rem;
96 | border-radius: 0.75rem;
97 | }
98 | }
99 | }
--------------------------------------------------------------------------------
/server/Controllers/project.js:
--------------------------------------------------------------------------------
1 | const Project = require('../db/projectModel');
2 |
3 | const createProject = async (req, res) => {
4 | const body = req.body;
5 | try {
6 | const existingProject = await Project.find({ name: body?.name });
7 | if(existingProject.length > 0) {
8 | return res.status(409).json({
9 | status: 'failed',
10 | message: 'the project already exists.'
11 | })
12 | }
13 | const projectData = {
14 | name: body?.name,
15 | start_date: new Date(body?.startDate).toISOString(),
16 | due_date: new Date(body?.dueDate).toISOString(),
17 | status: body?.status,
18 | manager_name: body?.managerName,
19 | team_members: body?.teamMembers
20 | };
21 | await Project.create(projectData);
22 |
23 | res.status(200).json({
24 | status: 'success',
25 | message: "successfully created the project."
26 | })
27 | } catch (error) {
28 | res.status(403).json({
29 | status: 'failed',
30 | message: error.message
31 | })
32 | }
33 | };
34 |
35 | const listProjects = async (req, res) => {
36 | try {
37 | const projects = await Project.find().select('-__v');
38 |
39 | res.status(200).json({
40 | status: 'success',
41 | response: projects
42 | });
43 | } catch (error) {
44 | res.status(404).json({
45 | status: 'failed',
46 | message: error.message
47 | })
48 | }
49 | };
50 |
51 | const getProjectDetails = async (req, res) => {
52 | const { id } = req.query;
53 | try {
54 | const projectDetails = await Project.findById(id).select('-__v');
55 |
56 | res.status(200).json({
57 | status: 'success',
58 | response: projectDetails
59 | })
60 | } catch (error) {
61 | res.status(404).json({
62 | status: 'failed',
63 | message: error.message
64 | })
65 | }
66 | };
67 |
68 | const updateProject = async (req, res) => {
69 | const { id } = req.query;
70 | const { body } = req;
71 | try {
72 | const projectData = await Project.findById(id);
73 | if(!projectData){
74 | throw new Error('there is no project with this id.');
75 | }
76 | const updatedData = {
77 | name: body?.name || projectData.name,
78 | due_date: (body?.dueDate && new Date(body?.dueDate).toISOString()) || projectData.due_date,
79 | status: body?.status || projectData.status,
80 | manager_name: body?.managerName || projectData.manager_name,
81 | team_members: body?.teamMembers || projectData.team_members
82 | };
83 | await Project.findByIdAndUpdate(id, updatedData);
84 |
85 | res.status(200).json({
86 | status: 'success',
87 | message: 'successfully updated the project.'
88 | })
89 | } catch (error) {
90 | res.status(404).json({
91 | status: 'failed',
92 | message: error.message
93 | })
94 | }
95 | };
96 |
97 | module.exports = { createProject, listProjects, getProjectDetails, updateProject };
--------------------------------------------------------------------------------
/client/src/containers/sign-in/form-section.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import Button from '../../components/button';
4 | import Email from '../../components/email';
5 | import Password from '../../components/password';
6 | import { signin } from '../../services/login/signin';
7 | import { sendErrorNotification, sendInfoNotification, sendSuccessNotification } from '../../services/notifications';
8 | import { checkAllTrue } from '../../utils/check-all';
9 | import { setLocalStorageKey } from '../../utils/localStorage';
10 |
11 | import './form-section.scss';
12 |
13 | const handleLocalStorge = (tokenDetails, id, email) => {
14 | setLocalStorageKey('token', tokenDetails.token);
15 | setLocalStorageKey('expiry', tokenDetails.expiry);
16 | setLocalStorageKey('id', id);
17 | setLocalStorageKey('email', email);
18 | }
19 |
20 | const FormSection = (props) => {
21 | const navigate = useNavigate();
22 | const [[email, isEmailValid], setEmail] = useState(['', false]);
23 | const [[password, isPasswordValid], setPassword] = useState(['', false]);
24 |
25 | const onSubmitHandle = async (event) => {
26 | event.preventDefault();
27 | props.setIsLoading(true);
28 | if (!checkAllTrue([isEmailValid, isPasswordValid])) {
29 | sendInfoNotification('Form Incomplete: Please fill details below.');
30 | return;
31 | }
32 | try{
33 | const response = await signin(email, password);
34 | if(response.status === 'success'){
35 | handleLocalStorge(response.tokenDetails, response.body.employee._id, email);
36 | sendSuccessNotification('Welcome '+ response.body.employee.name);
37 | navigate('/');
38 | }else{
39 | sendErrorNotification(response.message);
40 | }
41 | }catch(error){
42 | sendErrorNotification(error.message);
43 | }
44 | props.setIsLoading(false);
45 | };
46 |
47 | const formComponents = {
48 | email: (key, props) => setEmail([value, isValid]) }} />,
49 | password: (key, props) => setPassword([value, isValid]) }} />,
50 | button: (key, props) => ,
51 | link: (key, props) => {props.text}
52 | };
53 |
54 | return (
55 |
56 |
62 |
63 | {props.bottomText}
64 | {props.linkText}
65 |
66 |
)
67 | };
68 |
69 | export default FormSection;
--------------------------------------------------------------------------------
/client/src/containers/sign-up/OTP-verification/form-section.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react';
2 | import { Link, useNavigate, useParams } from 'react-router-dom';
3 | import Otp from '../../../components/otp';
4 | import Button from '../../../components/button';
5 | import { preSignupResendOTP, signup } from '../../../services/login/signup';
6 | import { sendErrorNotification, sendSuccessNotification } from '../../../services/notifications';
7 |
8 | const FormSection = (props) => {
9 | const navigate = useNavigate();
10 | const { id } = useParams();
11 | const [[otp, isValidOTP], setOtp] = useState(['', false]);
12 | const [isResendOTP, setIsResendOTP] = useState(true);
13 | const [timeRemaining, setTimeRemaining] = useState(10);
14 |
15 | const onSubmitHandle = async (event) => {
16 | event.preventDefault();
17 | if(!isValidOTP){
18 | sendErrorNotification('Please provide a valid OTP');
19 | return;
20 | }
21 | const result = await signup(id, otp);
22 | if(result.status === 'success') {
23 | sendSuccessNotification('You have successfully created an account.');
24 | navigate('/login');
25 | }else{
26 | sendErrorNotification(result.message);
27 | }
28 | };
29 |
30 | const handleResentOTP = async () => {
31 | const result = await preSignupResendOTP(id);
32 | if(result.status === 'success'){
33 | sendSuccessNotification(result.message);
34 | setIsResendOTP(true);
35 | setTimeRemaining(10);
36 | }else{
37 | sendErrorNotification(result.message);
38 | }
39 | };
40 |
41 | useEffect(() => {
42 | const timerInterval = setInterval(() => {
43 | setTimeRemaining((prevTime) => prevTime - 1);
44 | }, 1000);
45 |
46 | if (timeRemaining <= 0) {
47 | clearInterval(timerInterval);
48 | }
49 |
50 | return () => clearInterval(timerInterval);
51 | }, [timeRemaining]);
52 |
53 | useEffect(()=> {
54 | if(!isResendOTP) return;
55 |
56 | setTimeout(() => {
57 | setIsResendOTP(false);
58 | }, 10*1000);
59 | },[isResendOTP])
60 |
61 | const formComponents = {
62 | otp: (key, props) => setOtp([value, isValid])}} />,
63 | button: (key, props) => ,
64 | };
65 |
66 | return
67 |
73 |
74 | {!isResendOTP ?
75 | Resend OTP
76 | : Resend OTP in {timeRemaining}s }
77 |
78 |
79 | {props.bottomText}
80 | {props.linkText}
81 |
82 |
83 | };
84 |
85 | export default FormSection;
--------------------------------------------------------------------------------
/client/src/components/navbar/style.scss:
--------------------------------------------------------------------------------
1 | .navigation {
2 | position: fixed;
3 | top: 0px;
4 | left: 0px;
5 | width: 200px;
6 | height: 100%;
7 | background: var(--blue);
8 | border-left: 10px solid var(--blue);
9 | transition: 0.5s;
10 | overflow: hidden;
11 | }
12 | .navigation.active {
13 | width: 80px;
14 | }
15 |
16 | .navigation ul {
17 | position: absolute;
18 | top: 0;
19 | left: 0;
20 | width: 100%;
21 | padding-top: 20px;
22 | }
23 |
24 | .navigation ul li {
25 | position: relative;
26 | width: 100%;
27 | list-style: none;
28 | border-top-left-radius: 30px;
29 | border-bottom-left-radius: 30px;
30 | }
31 |
32 | .navigation ul li:hover,
33 | .navigation ul li.hovered {
34 | background-color: var(--white);
35 | }
36 |
37 | .navigation ul li:nth-child(1) {
38 | margin-bottom: 40px;
39 | pointer-events: none;
40 | transition: 0.5ms;
41 | }
42 |
43 | .navigation ul li a {
44 | position: relative;
45 | display: block;
46 | width: 100%;
47 | display: flex;
48 | text-decoration: none;
49 | color: var(--white);
50 | }
51 | .navigation ul li:hover a,
52 | .navigation ul li.hovered a {
53 | color: var(--blue);
54 | }
55 |
56 | .navigation ul li a .icon {
57 | position: relative;
58 | display: block;
59 | min-width: 60px;
60 | height: 60px;
61 | line-height: 75px;
62 | text-align: center;
63 | img {
64 | width: 100%;
65 | height: 100%;
66 | border-radius: 20%;
67 | transition: 0.5s;
68 | }
69 | }
70 | .navigation ul li a .icon ion-icon {
71 | font-size: 1.75rem;
72 | }
73 |
74 | .navigation ul li a .title {
75 | position: relative;
76 | display: block;
77 | padding: 0 10px;
78 | height: 60px;
79 | line-height: 60px;
80 | text-align: start;
81 | white-space: nowrap;
82 | transition: 0.5ms;
83 | &.brand {
84 | font-size: 20px;
85 | font-weight: 700;
86 | span {
87 | color: var(--yellow);
88 | }
89 | }
90 | }
91 |
92 | /* --------- curve outside ---------- */
93 | .navigation ul li:hover a::before,
94 | .navigation ul li.hovered a::before {
95 | content: "";
96 | position: absolute;
97 | right: 0;
98 | top: -50px;
99 | width: 50px;
100 | height: 50px;
101 | background-color: transparent;
102 | border-radius: 50%;
103 | box-shadow: 35px 35px 0 10px var(--white);
104 | pointer-events: none;
105 | }
106 | .navigation ul li:hover a::after,
107 | .navigation ul li.hovered a::after {
108 | content: "";
109 | position: absolute;
110 | right: 0;
111 | bottom: -50px;
112 | width: 50px;
113 | height: 50px;
114 | background-color: transparent;
115 | border-radius: 50%;
116 | box-shadow: 35px -35px 0 10px var(--white);
117 | pointer-events: none;
118 | }
119 |
120 | // .navbar{
121 | // width: calc(100% - 300px);
122 | // position: absolute;
123 | // left: 300px;
124 | // top: 0px;
125 | // transition: 0.5s;
126 | // min-height: 100vh;
127 | // &.active {
128 | // width: calc(100% - 80px);
129 | // left: 80px;
130 | // }
131 | // }
--------------------------------------------------------------------------------
/server/Controllers/taskBoard.js:
--------------------------------------------------------------------------------
1 | const Board = require('../db/taskBoardModel');
2 | const Employee = require('../db/employeeModel');
3 |
4 | // Create a new board
5 | const createBoard = async (req, res) => {
6 | try {
7 | const board = await Board.create(req.body);
8 | res.status(200).json({
9 | status: 'success',
10 | message: "successfully created the board.",
11 | response : board
12 | });
13 | } catch (error) {
14 | res.status(403).json({
15 | status: 'failed',
16 | message: error.message
17 | });
18 | }
19 | };
20 |
21 | // Get all boards
22 | const getAllBoards = async (req, res) => {
23 | const { fields } = req.query;
24 | try {
25 | const modifiedFields = fields?.split(',')?.join(' ');
26 | const boards = await Board.find().select(modifiedFields);
27 |
28 | res.status(200).json({
29 | status: 'success',
30 | response: boards
31 | });
32 | } catch (error) {
33 | res.status(404).json({
34 | status: 'failed',
35 | message: error.message
36 | })
37 | }
38 | };
39 |
40 | // Get a single board by ID
41 | const getBoardById = async (req, res) => {
42 | res.set('Cache-Control', 'no-cache, no-store');
43 | try {
44 | const board = await Board.findById(req.params.id);
45 | if (!board) {
46 | throw new Error('Board not found');
47 | }
48 | res.status(200).json({
49 | status: 'success',
50 | response: board
51 | });
52 | } catch (error) {
53 | res.status(404).json({
54 | status: 'failed',
55 | message: error.message
56 | });
57 | }
58 | };
59 |
60 | // Update a board by ID
61 | const updateBoardById = async (req, res) => {
62 | try {
63 | const updatedBoard = await Board.findByIdAndUpdate(req.params.id, req.body, { new: true });
64 | if (!updatedBoard) {
65 | throw new Error('Board not found');
66 | }
67 | res.status(200).json({
68 | status: 'success',
69 | message: 'successfully updated the board.'
70 | })
71 | } catch (error) {
72 | res.status(404).json({
73 | status: 'failed',
74 | message: error.message
75 | })
76 | }
77 | };
78 |
79 | // Delete a board by ID
80 | const deleteBoardById = async (req, res) => {
81 | const { employee_id } = req.employee;
82 | try {
83 | const employee = await Employee.findById(employee_id);
84 | if(employee.role !== 'admin') throw new Error('You are not allowed to delete.');
85 | const deletedBoard = await Board.findByIdAndRemove(req.params.id);
86 | if (!deletedBoard) {
87 | throw new Error('Board not found');
88 | }
89 | res.status(200).json({
90 | status: 'success',
91 | message: 'Board deleted successfully'
92 | })
93 | } catch (error) {
94 | res.status(404).json({
95 | status: 'failed',
96 | message: error.message
97 | });
98 | }
99 | };
100 |
101 | module.exports = {
102 | createBoard,
103 | getAllBoards,
104 | getBoardById,
105 | updateBoardById,
106 | deleteBoardById,
107 | };
108 |
--------------------------------------------------------------------------------
/client/src/components/table/styles.scss:
--------------------------------------------------------------------------------
1 | .table-component table {
2 | width: 100%;
3 | border-collapse: collapse;
4 | font-family: "Roboto";
5 | color: #526484;
6 | }
7 |
8 | .table-component tr {
9 | font-weight: 500;
10 | }
11 |
12 | .table-component{
13 | th, td {
14 | border: 2px solid #dbdfea;
15 | padding: 12px 10px;
16 | vertical-align: top;
17 | text-align: center;
18 | }
19 | }
20 |
21 | .table-component th {
22 | border: 2px solid #eae8e8;
23 | background: #f1f0f0;
24 | color: #000;
25 | vertical-align: top;
26 | position: relative;
27 | font-weight: bold;
28 | font-size: 16px;
29 | text-transform: capitalize;
30 | letter-spacing: 0.5px;
31 | cursor: pointer;
32 | z-index: -1;
33 | }
34 |
35 | .table-component td {
36 | text-transform: lowercase;
37 | vertical-align: middle;
38 | .btn button{
39 | font-size: 14px;
40 | letter-spacing: 0;
41 | padding: 5px;
42 | }
43 | }
44 |
45 | .table-component th svg {
46 | width: 12px;
47 | position: absolute;
48 | right: 8px;
49 | top: 50%;
50 | transition-duration: 400ms;
51 | }
52 |
53 | .table-component th svg.ascending {
54 | transform: translateY(-50%) rotate(90deg);
55 | }
56 |
57 | .table-component th svg.descending {
58 | transform: translateY(-50%) rotate(-90deg);
59 | }
60 |
61 | .table-component .pagination {
62 | text-align: center;
63 | display: flex;
64 | align-items: center;
65 | justify-content: center;
66 | margin-top: 32px;
67 | }
68 |
69 | .table-component .page-count {
70 | width: 24px;
71 | height: 24px;
72 | display: flex;
73 | align-items: center;
74 | justify-content: center;
75 | background: #f5f6fa;
76 | margin-right: 4px;
77 | border-radius: 5px;
78 | cursor: pointer;
79 | }
80 |
81 | .table-component .page-count.active {
82 | color: #fff;
83 | background: #1f3466;
84 | }
85 |
86 | .table-component .filters {
87 | display: flex;
88 | align-items: center;
89 | justify-content: space-between;
90 | margin-bottom: 16px;
91 | .entries span{
92 | text-transform: capitalize;
93 | font-size: 16px;
94 | }
95 |
96 | }
97 |
98 | .table-component .filters input {
99 | min-width: 600px;
100 | border: 2px solid #dbdfea;
101 | border-radius: 5px;
102 | height: 28px;
103 | font-size: 16px;
104 | margin-right: 8px;
105 | padding: 16px 0px 16px 16px;
106 | }
107 |
108 | .table-component .filters input:focus {
109 | -webkit-box-shadow: 1px 1px 12px 4px rgba(252,36,252,0.78);
110 | -moz-box-shadow: 1px 1px 12px 4px rgba(252,36,252,0.78);
111 | box-shadow: 1px 1px 12px 4px rgba(252,36,252,0.78);
112 | outline: none;
113 | }
114 |
115 | .table-component .filters select {
116 | border-radius: 5px;
117 | padding: 5px;
118 | height: 28px;
119 | border: 1px solid #dbdfea;
120 | &:focus{
121 | outline: none;
122 | }
123 | }
124 |
125 | @media only screen and (max-width: 600px) {
126 |
127 | .table-component .filters {
128 | display: block;
129 | }
130 |
131 | .table-component .filters input {
132 | min-width: unset;
133 | width: 100%;
134 | margin-bottom: 8px;
135 | }
136 |
137 | .table-wrapper {
138 | overflow-x: scroll;
139 | }
140 |
141 | .table-component table {
142 | width: 800px;
143 | }
144 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/website/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/containers/resetPassword/form-section.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { Link, useNavigate } from 'react-router-dom';
3 | import Button from '../../components/button';
4 | import Email from '../../components/email';
5 | import Otp from '../../components/otp';
6 | import Password from '../../components/password';
7 | import { forgotPassword } from '../../services/login/forgotpassword';
8 | import { resetPassword } from '../../services/login/resetpassword';
9 | import { sendErrorNotification, sendInfoNotification, sendSuccessNotification } from '../../services/notifications';
10 | import { checkAllTrue } from '../../utils/check-all';
11 |
12 | import './form-section.scss';
13 |
14 | const FormSection = (props) => {
15 | const navigate = useNavigate();
16 | const [[email, isEmailValid], setEmail] = useState(['', false]);
17 | const [[password, isPasswordValid], setPassword] = useState(['', false]);
18 | const [[otp, isOtpValid], setOtp] = useState(['', false]);
19 | const [isOtpScreenActive, setOtpScreenActive] = useState(false);
20 |
21 | const onSubmitHandle = async (event) => {
22 | event.preventDefault();
23 | if(!isOtpScreenActive){
24 | if (!checkAllTrue([isEmailValid])) {
25 | sendErrorNotification('Please fill details below.');
26 | return;
27 | }
28 | try{
29 | sendInfoNotification("We are sending OTP on your email!");
30 | const response = await forgotPassword(email);
31 | if(response.status === 'success'){
32 | sendSuccessNotification("OTP has been successfully sent!");
33 | setOtpScreenActive(true);
34 | }else{
35 | sendErrorNotification(response.message);
36 | }
37 | }catch(error){
38 | sendErrorNotification(error.message);
39 | }
40 |
41 | }else{
42 | if(!checkAllTrue([isPasswordValid, isOtpValid])){
43 | sendErrorNotification('Please fill details below.');
44 | return;
45 | }
46 | try{
47 | sendInfoNotification("We are validating your OTP!")
48 | const response = await resetPassword(email, otp, password);
49 | if(response.status === 'success'){
50 | sendSuccessNotification('Password has been successfully changed!');
51 | navigate('/login');
52 | }else{
53 | sendErrorNotification(response.message);
54 | }
55 | }catch(error){
56 | sendErrorNotification(error.message);
57 | }
58 | }
59 | };
60 |
61 | const formComponents = {
62 | otp: (key, props) => setOtp([value, isValid])}} />,
63 | email: (key, props) => setEmail([value, isValid]) }} />,
64 | password: (key, props) => setPassword([value, isValid]) }} />,
65 | button: (key, props) => ,
66 | link: (key, props) => {props.text}
67 | };
68 |
69 | return
70 |
76 |
77 | {props.linkText}
78 |
79 |
80 | };
81 |
82 | export default FormSection;
--------------------------------------------------------------------------------
/client/src/redux/reducers/board.js:
--------------------------------------------------------------------------------
1 | import { createSlice } from "@reduxjs/toolkit";
2 |
3 | const boardSlice = createSlice({
4 | name: "board",
5 | initialState: {
6 | boards: [],
7 | selectedBoard: {}
8 | },
9 | reducers: {
10 | setBoardsData: (state, action) => {
11 | state.boards = action.payload;
12 | },
13 | setSelectedBoard: (state, action) => {
14 | state.selectedBoard = action.payload;
15 | },
16 | addBoard: (state, action) => {
17 | const { board } = action.payload;
18 | state.boards.push(board);
19 | },
20 | editBoard: (state, action) => {
21 | const { name, newColumns } = action.payload;
22 | const board = state.selectedBoard;
23 | board.name = name;
24 | board.columns = newColumns;
25 | },
26 | deleteBoard: (state, action) => {
27 | const board = state.selectedBoard;
28 | state.boards.splice(state.boards.indexOf(board), 1);
29 | },
30 | setBoardActive: (state, action) => {
31 | state.boards.map((board, index) => {
32 | index === action.payload.index
33 | ? (board.isActive = true)
34 | : (board.isActive = false);
35 | return board;
36 | });
37 | },
38 | addTask: (state, action) => {
39 | const { newTask, columnId } =
40 | action.payload;
41 | const board = state.selectedBoard;
42 | const column = board.columns.find((col) => col._id === columnId);
43 | column.tasks.push(newTask);
44 | },
45 | editTask: (state, action) => {
46 | const { newTask, columnId } = action.payload;
47 | const board = state.selectedBoard;
48 | const newColumn = board.columns.find((col) => col._id === columnId);
49 | const taskIndex = newColumn.tasks.indexOf(newTask);
50 | if(taskIndex !== -1) {
51 | const task = newColumn[taskIndex];
52 | task.name = newTask.name;
53 | task.description = newTask.description;
54 | task.subtasks = newTask.subtasks;
55 | task.status = newTask.status;
56 | }else{
57 | // Find the task and its current column ID
58 | let task;
59 | let columnIndex;
60 | for (let i = 0; i < board.columns.length; i++) {
61 | task = board.columns[i].tasks.find(task => task._id === newTask._id);
62 | if (task) {
63 | columnIndex = i;
64 | break;
65 | }
66 | }
67 | board.columns[columnIndex].tasks = board.columns[columnIndex].tasks.filter(task => task._id !== newTask._id);
68 | newColumn.tasks.push(newTask);
69 | }
70 | },
71 | dragTask: (state, action) => {
72 | const { colId, prevColId, taskId, task } = action.payload;
73 | if(colId === prevColId) return;
74 | const board = state.selectedBoard;
75 | const prevCol = board.columns.find((col) => col._id === prevColId);
76 | prevCol.tasks = prevCol.tasks.filter(task => task._id !== taskId);
77 | board.columns.find((col) => col._id === colId).tasks.push(task);
78 | },
79 | setSubtaskCompleted: (state, action) => {
80 | const { colId, taskId, id } = action.payload;
81 | const board = state.selectedBoard;
82 | const col = board.columns.find((col) => col._id === colId);
83 | const task = col.tasks.find((task) => task._id === taskId);
84 | const subtask = task.subtasks.find((subtask) => subtask._id === id);
85 | subtask.isCompleted = !subtask.isCompleted;
86 | },
87 | setTaskStatus: (state, action) => {
88 | const { colIndex, newColIndex, status, taskIndex } = action.payload;
89 | const board = state.selectedBoard;
90 | const columns = board.columns;
91 | const col = columns.find((col, i) => i === colIndex);
92 | if (colIndex === newColIndex) return;
93 | const task = col.tasks.find((task, i) => i === taskIndex);
94 | task.status = status;
95 | col.tasks = col.tasks.filter((task, i) => i !== taskIndex);
96 | const newCol = columns.find((col, i) => i === newColIndex);
97 | newCol.tasks.push(task);
98 | },
99 | deleteTask: (state, action) => {
100 | const {colId, taskId} = action.payload;
101 | const board = state.selectedBoard;
102 | const col = board.columns.find((col) => col._id === colId);
103 | col.tasks = col.tasks.filter((task) => task._id !== taskId);
104 | },
105 | }
106 | })
107 |
108 | export const boardReducer = boardSlice.reducer;
109 | export const boardActions = boardSlice.actions;
--------------------------------------------------------------------------------