├── .env.example ├── .gitignore ├── .vscode └── launch.json ├── README.md ├── client ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── Methodize-meta.jpg │ ├── favicon.ico │ └── index.html └── src │ ├── App.js │ ├── App.test.js │ ├── assets │ ├── Add.js │ ├── Home.js │ ├── Logo.js │ ├── Methodize_lp-circles-bg.png │ ├── Product-screenshot.png │ ├── alert.js │ ├── comments.js │ ├── logo.png │ ├── message.png │ ├── pin.js │ ├── project.js │ ├── search.js │ ├── tasks.js │ └── team.svg │ ├── components │ ├── AuthRoutes.js │ ├── Forms │ │ ├── AddMemberForm.js │ │ ├── AddTaskForm.js │ │ ├── AddTaskProjectForm.js │ │ ├── LoginForm.js │ │ ├── ProjectForm.js │ │ ├── TaskListForm.js │ │ └── TeamForm.js │ ├── LandingPage │ │ ├── LandingPage.js │ │ ├── LandingRoutes.js │ │ ├── LoginPage.js │ │ ├── Onboard.js │ │ └── RegisterPage.js │ ├── Loader.js │ ├── NavigationBar │ │ ├── LeftNavBar.js │ │ ├── Searchbar.js │ │ ├── TopNavBar.js │ │ ├── TopNavBarHome.js │ │ ├── TopNavBarTask.js │ │ └── UserAvatar.js │ ├── Pages │ │ ├── Home.js │ │ ├── NewProject.js │ │ ├── NewTasks.js │ │ ├── Project.js │ │ ├── Projects.js │ │ ├── Tasks.js │ │ └── Team.js │ ├── PopOutMenu │ │ ├── AddProjectPopOut.js │ │ ├── AddTaskPopOutProjectPage.js │ │ ├── AddTaskPopOutTaskPage.js │ │ ├── AddTasklistPopOut.js │ │ ├── PopOutTaskDetails.js │ │ └── PopOutTaskDetailsHome.js │ ├── Routes.js │ ├── projects │ │ ├── NewProjectTile.js │ │ ├── ProjectItemHome.js │ │ └── ProjectTile.js │ ├── tasks │ │ ├── ColumnTaskItem.js │ │ ├── ColumnTasklist.js │ │ ├── TaskDetailsForm.js │ │ ├── TaskItemHome.js │ │ ├── TaskItemProject.js │ │ ├── TaskItemTask.js │ │ ├── TaskListItem.js │ │ └── TaskSection.js │ └── teams │ │ ├── NewTeamMemberIcon.js │ │ └── TeamMemberIcon.js │ ├── config │ ├── apiServer.js │ └── index.js │ ├── context │ ├── AuthContext.js │ ├── createDataContext.js │ ├── reducer │ │ ├── ProjectReducer.js │ │ ├── TaskReducer.js │ │ ├── TasklistReducer.js │ │ ├── TeamReducer.js │ │ └── UserReducer.js │ └── store │ │ ├── ProjectStore.js │ │ ├── TaskStore.js │ │ ├── TasklistStore.js │ │ ├── TeamStore.js │ │ └── UserStore.js │ ├── css │ ├── Forms.css │ ├── Home.css │ ├── LandingPage.css │ ├── LoginPage.css │ ├── Modal.css │ ├── Navbar.css │ ├── PopOutForms.css │ ├── Project.css │ ├── Task.css │ ├── TaskList.css │ └── TeamPage.css │ ├── index.css │ ├── index.js │ └── setupTests.js └── server ├── .sequelizerc ├── README.md ├── app.js ├── bin └── www ├── config ├── database.js └── index.js ├── db ├── migrations │ ├── 20201103190955-create-user.js │ ├── 20201103193359-create-team.js │ ├── 20201103193644-create-user-team.js │ ├── 20201103193900-create-project.js │ ├── 20201103193957-create-task-list.js │ ├── 20201103194246-create-task.js │ ├── 20201103194324-create-comment.js │ └── 20201202005720-create-user-project.js ├── models │ ├── comment.js │ ├── index.js │ ├── project.js │ ├── task.js │ ├── tasklist.js │ ├── team.js │ ├── user.js │ ├── userproject.js │ └── userteam.js ├── routes │ ├── comments.js │ ├── projects.js │ ├── tasklists.js │ ├── tasks.js │ ├── teams.js │ ├── users.js │ ├── userteams.js │ └── utilities │ │ ├── auth.js │ │ └── utils.js └── seeders │ └── 20201104032635-test-data.js ├── package-lock.json └── package.json /.env.example: -------------------------------------------------------------------------------- 1 | PORT= 2 | DB_USERNAME= 3 | DB_PASSWORD= 4 | DB_DATABASE= 5 | DB_HOST= 6 | JWT_SECRET= 7 | JWT_EXPIRES_IN= -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "pwa-chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:8080", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Methodize 2 | 3 | 4 | 5 |

home

6 | 7 | ## Overview 8 | 9 | Methodize is a project management tool. Methodize allows users to keep track of teams, projects, and tasks. Methodize was developed using NodeJS, Express, and React. 10 | 11 | ## Live Link 12 | 13 | [Methodize](https://methodize-app.herokuapp.com) 14 | 15 | ## Technologies 16 | 17 | ### Backend: 18 | 19 | | Technology | Use | 20 | | ------------ | ---------------------------------- | 21 | | PostgreSQL | SQL database | 22 | | SequelizeORM | Object Relation Mapping (ORM) | 23 | | Express.js | Server framework/API endpoints git | 24 | | Node.js | Runtime environment | 25 | 26 | ### Frontend 27 | 28 | | Technology | Use | 29 | | --------------- | --------------------------------------------------- | 30 | | React.js | Javascript Library for UI | 31 | | React Hook Form | Easy-to-use forms with validation | 32 | | React Hooks | Reusuable stateful logic with functional components | 33 | | Axios | Promised-based HTTP client | 34 | | Material UI | React UI Framework | 35 | 36 | - BcryptJS 37 | - Heroku 38 | 39 | ## Technical Challenges 40 | 41 | ## Features 42 | 43 | 1. Users can create Teams and add users to teams 44 | 45 | 2. Users can create multiple projects within teams depending on scope 46 | 47 | 3. Users can create and assign tasks to members of teams 48 | 49 | 4. Users can check off completed tasks via tasklists 50 | 51 | ## Future Features 52 | 53 | - Search functionality 54 | - Removal of team members 55 | -------------------------------------------------------------------------------- /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 the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will 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/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.0", 7 | "@testing-library/jest-dom": "^5.11.5", 8 | "@testing-library/react": "^11.1.1", 9 | "@testing-library/user-event": "^12.2.0", 10 | "axios": "^0.21.1", 11 | "moment": "^2.29.1", 12 | "react": "^17.0.1", 13 | "react-beautiful-dnd": "^13.0.0", 14 | "react-datepicker": "^3.3.0", 15 | "react-dom": "^17.0.1", 16 | "react-hook-form": "^6.11.0", 17 | "react-icons": "^3.11.0", 18 | "react-router-dom": "^5.2.0", 19 | "react-scripts": "4.0.0", 20 | "web-vitals": "^0.2.4" 21 | }, 22 | "scripts": { 23 | "start": "node_modules/.bin/react-scripts start", 24 | "build": "node_modules/.bin/react-scripts build", 25 | "test": "react-scripts test", 26 | "eject": "react-scripts eject" 27 | }, 28 | "eslintConfig": { 29 | "extends": [ 30 | "react-app", 31 | "react-app/jest" 32 | ] 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /client/public/Methodize-meta.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctran01/Methodize/199ea64775fa75981a1c3411d71dc7a0749bc57a/client/public/Methodize-meta.jpg -------------------------------------------------------------------------------- /client/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctran01/Methodize/199ea64775fa75981a1c3411d71dc7a0749bc57a/client/public/favicon.ico -------------------------------------------------------------------------------- /client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 25 | Methodize 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /client/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import Routes from "./components/Routes"; 3 | import AuthContext from "./context/AuthContext"; 4 | import UserStore from "./context/store/UserStore"; 5 | import TeamStore from "./context/store/TeamStore"; 6 | import TaskStore from "./context/store/TaskStore"; 7 | import ProjectStore from "./context/store/ProjectStore"; 8 | import TasklistStore from "./context/store/TasklistStore"; 9 | import "./css/Home.css"; 10 | 11 | const App = () => { 12 | const [auth, setAuth] = useState(localStorage.getItem("token") || ""); 13 | const [userId, setUserId] = useState(localStorage.getItem("userId") || null); 14 | const [email, setEmail] = useState(localStorage.getItem("email") || null); 15 | const [user, setUser] = useState(localStorage.getItem("user") || null); 16 | 17 | const [sidebar, setSidebar] = useState(true); 18 | const showSidebar = () => setSidebar(!sidebar); 19 | const logout = () => { 20 | localStorage.removeItem("token"); 21 | localStorage.removeItem("email"); 22 | localStorage.removeItem("userId"); 23 | setAuth(null); 24 | setEmail(null); 25 | setUserId(null); 26 | }; 27 | const context = { 28 | auth, 29 | setAuth, 30 | userId, 31 | setUserId, 32 | email, 33 | setEmail, 34 | user, 35 | setUser, 36 | sidebar, 37 | setSidebar, 38 | showSidebar, 39 | logout, 40 | }; 41 | 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | }; 58 | 59 | export default App; 60 | -------------------------------------------------------------------------------- /client/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/assets/Add.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Add = () => { 4 | return ( 5 | 12 | Buton add 13 | 20 | 21 | 22 | 27 | 32 | 33 | 34 | 35 | 36 | ); 37 | }; 38 | 39 | export default Add; 40 | -------------------------------------------------------------------------------- /client/src/assets/Home.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Home = () => { 4 | return ( 5 | 12 | Group 6 13 | 20 | 25 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | ); 40 | }; 41 | 42 | export default Home; 43 | -------------------------------------------------------------------------------- /client/src/assets/Logo.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Logo = () => { 4 | return ( 5 | 12 | Group 9 13 | 14 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 34 | 42 | 43 | 44 | Methodize. 45 | 46 | 47 | 48 | 55 | 62 | 63 | 64 | 65 | 66 | ); 67 | }; 68 | 69 | export default Logo; 70 | -------------------------------------------------------------------------------- /client/src/assets/Methodize_lp-circles-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctran01/Methodize/199ea64775fa75981a1c3411d71dc7a0749bc57a/client/src/assets/Methodize_lp-circles-bg.png -------------------------------------------------------------------------------- /client/src/assets/Product-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctran01/Methodize/199ea64775fa75981a1c3411d71dc7a0749bc57a/client/src/assets/Product-screenshot.png -------------------------------------------------------------------------------- /client/src/assets/alert.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const alert = () => { 4 | return ( 5 | 12 | Group 40 13 | 20 | 21 | 22 | 23 | 28 | 32 | 36 | 37 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default alert; 55 | -------------------------------------------------------------------------------- /client/src/assets/comments.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const comments = () => { 4 | return ( 5 | 12 | Group 5 13 | 22 | 27 | 28 | 29 | 30 | 34 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export default comments; 48 | -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctran01/Methodize/199ea64775fa75981a1c3411d71dc7a0749bc57a/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/assets/message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ctran01/Methodize/199ea64775fa75981a1c3411d71dc7a0749bc57a/client/src/assets/message.png -------------------------------------------------------------------------------- /client/src/assets/pin.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const pin = () => { 4 | return ( 5 | 12 | Group 5 13 | 20 | 25 | 26 | 27 | 28 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | ); 43 | }; 44 | 45 | export default pin; 46 | -------------------------------------------------------------------------------- /client/src/assets/project.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const project = () => { 4 | return ( 5 | 12 | project 13 | 22 | 27 | 28 | 29 | 30 | 37 | 44 | 51 | 55 | 59 | 63 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | ); 74 | }; 75 | 76 | export default project; 77 | -------------------------------------------------------------------------------- /client/src/assets/search.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const search = () => { 4 | return ( 5 | 12 | search 13 | 21 | 26 | 27 | 28 | 29 | 33 | 37 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | ); 52 | }; 53 | 54 | export default search; 55 | -------------------------------------------------------------------------------- /client/src/assets/tasks.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const tasks = () => { 4 | return ( 5 | 12 | verified 13 | 20 | 26 | 27 | 28 | 29 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | ); 44 | }; 45 | 46 | export default tasks; 47 | -------------------------------------------------------------------------------- /client/src/components/AuthRoutes.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext, useEffect } from "react"; 2 | import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom"; 3 | import HomePage from "./Pages/Home"; 4 | import TasksPage from "./Pages/Tasks"; 5 | import ProjectPage from "./Pages/Project"; 6 | import ProjectsPage from "./Pages/Projects"; 7 | import NewProjectPage from "./Pages/NewProject"; 8 | import TeamPage from "./Pages/Team"; 9 | import NewTasksPage from "./Pages/NewTasks"; 10 | import "../css/Navbar.css"; 11 | import LeftNavBar from "./NavigationBar/LeftNavBar"; 12 | 13 | import { Context as UserContext } from "../context/store/UserStore"; 14 | import { Context as TaskContext } from "../context/store/TaskStore"; 15 | import { Context as ProjectContext } from "../context/store/ProjectStore"; 16 | import { Context as TeamContext } from "../context/store/TeamStore"; 17 | 18 | import apiServer from "../config/apiServer"; 19 | 20 | const AuthRoutes = () => { 21 | const [sidebar, setSidebar] = useState(true); 22 | const showSidebar = () => setSidebar(!sidebar); 23 | const [taskState, taskdispatch] = useContext(TaskContext); 24 | const [userState, userdispatch] = useContext(UserContext); 25 | const [projectState, projectdispatch] = useContext(ProjectContext); 26 | const [teamState, teamdispatch] = useContext(TeamContext); 27 | 28 | //Maybe grab all information here and state goes down to child components? 29 | const getUserInfo = async () => { 30 | const id = localStorage.getItem("userId"); 31 | const res = await apiServer.get(`/user/${id}`); 32 | await userdispatch({ type: "get_user_info", payload: res.data }); 33 | }; 34 | 35 | const getUserTasks = async () => { 36 | const id = localStorage.getItem("userId"); 37 | const res = await apiServer.get(`/task/user/${id}`); 38 | await taskdispatch({ type: "get_user_tasks", payload: res.data }); 39 | }; 40 | 41 | const getUserTeams = async () => { 42 | const id = localStorage.getItem("userId"); 43 | const res = await apiServer.get(`/team/user/${id}`); 44 | await teamdispatch({ type: "get_user_teams", payload: res.data }); 45 | }; 46 | 47 | const getUserProjects = async () => { 48 | const id = localStorage.getItem("userId"); 49 | const res = await apiServer.get(`/project/user/${id}`); 50 | await projectdispatch({ 51 | type: "get_user_projects", 52 | payload: res.data, 53 | }); 54 | }; 55 | 56 | useEffect(() => { 57 | getUserInfo(); 58 | getUserTasks(); 59 | getUserTeams(); 60 | getUserProjects(); 61 | // eslint-disable-next-line react-hooks/exhaustive-deps 62 | }, []); 63 | 64 | return ( 65 |
66 | 67 | 68 |
75 | 76 | 77 | 78 | 79 | 80 | {/* */} 84 | } 87 | /> 88 | 89 | { 92 | return ; 93 | }} 94 | /> 95 | 96 |
97 |
98 |
99 | ); 100 | }; 101 | 102 | export default AuthRoutes; 103 | -------------------------------------------------------------------------------- /client/src/components/Forms/AddMemberForm.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "../../css/Task.css"; 3 | import Button from "@material-ui/core/Button"; 4 | import { Modal } from "@material-ui/core"; 5 | import { useForm } from "react-hook-form"; 6 | import apiServer from "../../config/apiServer"; 7 | import Loader from "../Loader"; 8 | 9 | const AddMemberForm = ({ teamId, clickClose, open, setTeamUsers }) => { 10 | const { register, handleSubmit, errors } = useForm(); 11 | const [users, setUsers] = useState(); 12 | const [error, setError] = useState(); 13 | const [loading, setLoading] = useState(true); 14 | 15 | const onSubmit = async ({ userId }) => { 16 | try { 17 | await apiServer.post(`/team/${teamId}/user/${userId}`); 18 | const res = await apiServer.get(`/team/${teamId}`); 19 | setTeamUsers(res.data.Users); 20 | 21 | clickClose(); 22 | } catch (err) { 23 | setError("User already on team"); 24 | } 25 | 26 | // const res = await apiServer.get(`/project/${projectId}/tasklists`); 27 | }; 28 | 29 | const getAllUsers = async () => { 30 | const res = await apiServer.get("/users"); 31 | setUsers(res.data); 32 | setLoading(false); 33 | }; 34 | useEffect(() => { 35 | getAllUsers(); 36 | }, []); 37 | 38 | if (loading) { 39 | return ; 40 | } 41 | 42 | const renderedUsers = users.map((user, i) => { 43 | return ( 44 | 47 | ); 48 | }); 49 | return ( 50 |
51 | 52 |
53 |
58 |

Add a member to the team!

59 |
60 |
61 | 77 |
78 |
79 |
80 |
81 | 82 |
83 | 90 | 97 |
98 |
99 |
100 |
101 |
102 | ); 103 | }; 104 | 105 | export default AddMemberForm; 106 | -------------------------------------------------------------------------------- /client/src/components/Forms/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { useForm } from "react-hook-form"; 3 | import AuthContext from "../../context/AuthContext"; 4 | import "../../css/LoginPage.css"; 5 | import apiServer from "../../config/apiServer"; 6 | const LoginForm = () => { 7 | const { register, handleSubmit, errors } = useForm(); 8 | 9 | const [errorMessage, setErrorMessage] = useState(""); 10 | const { setAuth, setEmail, setUserId, setUser } = useContext(AuthContext); 11 | const [formEmail, setFormEmail] = useState(""); 12 | const [password, setPassword] = useState(""); 13 | const [loading, setLoading] = useState(false); 14 | const [demoLoading, setDemoLoading] = useState(false); 15 | const onSubmit = async ({ email, password }) => { 16 | // if (!email && !password) { 17 | // email = "demo@email.com"; 18 | // password = "password"; 19 | // setFormEmail(email); 20 | // setPassword(password); 21 | // } 22 | setLoading(true); 23 | try { 24 | const res = await apiServer.post("/login", { email, password }); 25 | 26 | localStorage.setItem("email", res.data.email); 27 | localStorage.setItem("userId", res.data.id); 28 | localStorage.setItem("token", res.data.token); 29 | setErrorMessage(""); 30 | setAuth(res.data.token); 31 | // setUserId(res.data.id); 32 | // setEmail(res.data.email); 33 | // setUser(res.data); 34 | } catch (err) { 35 | setLoading(false); 36 | setErrorMessage("The provided credentials were invalid"); 37 | } 38 | }; 39 | 40 | const handleEmailChange = (e) => { 41 | setFormEmail(e.target.value); 42 | }; 43 | const handlePasswordChange = (e) => { 44 | setPassword(e.target.value); 45 | }; 46 | const demoLogin = async (e) => { 47 | e.preventDefault(); 48 | setErrorMessage(""); 49 | setDemoLoading(true); 50 | const email = "demo@email.com"; 51 | const password = "password"; 52 | try { 53 | const res = await apiServer.post("/login", { email, password }); 54 | 55 | localStorage.setItem("email", res.data.email); 56 | localStorage.setItem("userId", res.data.id); 57 | localStorage.setItem("token", res.data.token); 58 | setErrorMessage(""); 59 | setAuth(res.data.token); 60 | setUserId(res.data.id); 61 | setEmail(res.data.email); 62 | setUser(res.data); 63 | } catch (err) { 64 | setLoading(false); 65 | console.log(err.status); 66 | setErrorMessage("Something went wrong"); 67 | } 68 | }; 69 | 70 | return ( 71 |
72 |
73 | 74 | 81 | {errors.email?.type === "required" && ( 82 |

83 | Please enter an email address 84 |

85 | )} 86 |
87 |
88 | 89 | 96 | {errors.password?.type === "required" && ( 97 |

Please enter a password

98 | )} 99 |
100 | 101 | {errorMessage ? ( 102 |

{errorMessage}

103 | ) : null} 104 | 107 |
108 | ); 109 | }; 110 | 111 | export default LoginForm; 112 | -------------------------------------------------------------------------------- /client/src/components/Forms/ProjectForm.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { Modal } from "@material-ui/core"; 3 | import Button from "@material-ui/core/Button"; 4 | import { useForm } from "react-hook-form"; 5 | import apiServer from "../../config/apiServer"; 6 | import { Context as TeamContext } from "../../context/store/TeamStore"; 7 | import { Context as ProjectContext } from "../../context/store/ProjectStore"; 8 | import "../../css/Forms.css"; 9 | const ProjectForm = ({ 10 | handleNewClose, 11 | clickClose, 12 | open, 13 | setTeamProjects, 14 | showSideProjectForm, 15 | }) => { 16 | const { register, handleSubmit, errors, clearErrors } = useForm(); 17 | const [projectName, setProjectName] = useState(); 18 | const [teamState, teamdispatch] = useContext(TeamContext); 19 | const [projectState, projectdispatch] = useContext(ProjectContext); 20 | 21 | const userId = localStorage.getItem("userId"); 22 | 23 | const handleNameChange = (e) => { 24 | setProjectName(e.target.value); 25 | }; 26 | const handleUserKeyPress = (e) => { 27 | if (e.key === "Enter" && !e.shiftKey) { 28 | // e.preventDefault(); 29 | handleSubmit(onSubmit)(); 30 | } 31 | }; 32 | const onSubmit = async ({ name, teamId }) => { 33 | await apiServer.post(`/team/${teamId}/project/`, { 34 | name, 35 | userId, 36 | }); 37 | 38 | //REFER TO THIS WHEN CHECKING FOR RERENDERING 39 | const res = await apiServer.get(`/project/user/${userId}`); 40 | await projectdispatch({ type: "get_user_projects", payload: res.data }); 41 | const projectResponse = await apiServer.get(`/team/${teamId}`); 42 | // NOTE: One way this could work is if we recreate form for just team page add project form button 43 | // Will not work with top nav bar form 44 | // setTeamProjects(projectResponse.data.Projects); 45 | await teamdispatch({ 46 | type: `get_team_projects${teamId}`, 47 | payload: projectResponse.data, 48 | }); 49 | if (setTeamProjects) { 50 | const teamResponse = await apiServer.get(`/team/${teamId}`); 51 | setTeamProjects(teamResponse.data.Projects); 52 | } 53 | // window.location.reload(); 54 | 55 | // clickClose(); 56 | showSideProjectForm(); 57 | }; 58 | 59 | const clearError = () => { 60 | var teamSelect = document.getElementById("team-select"); 61 | clearErrors(teamSelect.name); 62 | }; 63 | const renderedTeams = teamState.teams.map((team, i) => { 64 | return ( 65 | 68 | ); 69 | }); 70 | 71 | return ( 72 | <> 73 | {/* 74 |
*/} 75 |
76 | {/*

Add a Project

*/} 77 |
78 |
79 |
80 | 81 |
82 |
83 | 93 | {errors.name?.type === "required" && ( 94 |

Please fill out project name

95 | )} 96 |
97 |
98 | 99 |
100 |
101 | 109 | {errors.teamId?.type === "required" && ( 110 |

Please choose a team

111 | )} 112 |
113 |
114 |
115 | 116 |
117 | {/* marginLeft: "400px" */} 118 | 125 | 134 |
135 |
136 | 137 | // 138 | //
139 | ); 140 | }; 141 | 142 | export default ProjectForm; 143 | -------------------------------------------------------------------------------- /client/src/components/Forms/TaskListForm.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import "../../css/Task.css"; 3 | import Button from "@material-ui/core/Button"; 4 | import { Modal } from "@material-ui/core"; 5 | import { useForm } from "react-hook-form"; 6 | import apiServer from "../../config/apiServer"; 7 | import { Context as TasklistContext } from "../../context/store/TasklistStore"; 8 | import { useParams } from "react-router-dom"; 9 | 10 | const TaskListForm = ({ setTasklists, showSideTasklistForm }) => { 11 | const { register, handleSubmit, errors } = useForm(); 12 | const [tasklistName, setTasklistName] = useState(); 13 | const { projectId } = useParams(); 14 | const handleNameChange = (e) => { 15 | setTasklistName(e.target.value); 16 | }; 17 | 18 | const onSubmit = async ({ name }) => { 19 | const userId = localStorage.getItem("userId"); 20 | await apiServer.post(`/project/${projectId}/tasklist`, { name, userId }); 21 | 22 | const res = await apiServer.get(`/project/${projectId}/tasklists`); 23 | setTasklists(res.data); 24 | // tasklistdispatch({ type: "update_project_tasklists", payload: res.data }); 25 | showSideTasklistForm(); 26 | }; 27 | 28 | const handleUserKeyPress = (e) => { 29 | if (e.key === "Enter" && !e.shiftKey) { 30 | // e.preventDefault(); 31 | handleSubmit(onSubmit)(); 32 | } 33 | }; 34 | 35 | return ( 36 |
41 | {/*

Add a Tasklist

*/} 42 |
43 |
44 |
45 | 46 |
47 |
48 | 57 | {errors.name?.type === "required" && ( 58 |

Please enter a column name

59 | )} 60 |
61 |
62 |
63 | 64 |
65 | 68 | 77 |
78 |
79 | ); 80 | }; 81 | 82 | export default TaskListForm; 83 | -------------------------------------------------------------------------------- /client/src/components/Forms/TeamForm.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import { Modal } from "@material-ui/core"; 3 | import Button from "@material-ui/core/Button"; 4 | import { useForm } from "react-hook-form"; 5 | import apiServer from "../../config/apiServer"; 6 | import { Context as TeamContext } from "../../context/store/TeamStore"; 7 | import "../../css/Forms.css"; 8 | const TeamForm = ({ handleNewClose, clickClose, open }) => { 9 | const { register, handleSubmit, errors } = useForm(); 10 | const [teamState, teamdispatch] = useContext(TeamContext); 11 | const userId = localStorage.getItem("userId"); 12 | 13 | const onSubmit = async ({ name, description }) => { 14 | await apiServer.post(`/team/user/${userId}`, { 15 | name, 16 | description, 17 | }); 18 | 19 | const res = await apiServer.get(`/team/user/${userId}`); 20 | await teamdispatch({ type: "update_user_teams", payload: res.data }); 21 | clickClose(); 22 | }; 23 | 24 | return ( 25 |
26 | 27 |
28 |
29 |

Create a Team

30 |
31 |
32 | 45 |
46 |
47 | {/*
*/} 51 |
52 |
53 | 60 |
61 |
62 | 69 | 76 |
77 |
78 |
79 |
80 |
81 | ); 82 | }; 83 | 84 | export default TeamForm; 85 | -------------------------------------------------------------------------------- /client/src/components/LandingPage/LandingPage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "../../css/LandingPage.css"; 3 | import picture from "../../assets/Product-screenshot.png"; 4 | import circles from "../../assets/Methodize_lp-circles-bg.png"; 5 | import { BsCardChecklist } from "react-icons/bs"; 6 | import { AiOutlineTeam } from "react-icons/ai"; 7 | import { MdAssignment } from "react-icons/md"; 8 | import { Button } from "@material-ui/core"; 9 | import Logo from "../../assets/Logo"; 10 | 11 | const LandingPage = () => { 12 | return ( 13 |
29 |
42 |
43 |
44 |
45 | 46 | 47 | 48 |
49 |
50 | 55 | 56 | 63 |
64 |
65 |
66 |
67 |

75 | The easiest way to manage team, projects, and tasks 76 |

77 |

78 | Why use Methodize? Methodize gives you everything you need to stay 79 | in sync, hit deadlines, and reach your goals 80 |

81 | 86 |
87 |
88 | landing 89 |
90 | {/*
91 |
92 |
93 | 96 |

97 | Establish Teams with other colleagues and work together to 98 | accomplish tasks. 99 |

100 |
101 |
102 | 105 |

106 | Create multiple projects within a team categorize tasks based 107 | on different types of projects. 108 |

109 |
110 |
111 | 114 |

115 | Keep track of tasks via tasklists in individual projects and 116 | check them off when they are completed. 117 |

118 |
119 |
120 | 127 |
*/} 128 |
129 |
130 |
131 | ); 132 | }; 133 | 134 | export default LandingPage; 135 | -------------------------------------------------------------------------------- /client/src/components/LandingPage/LandingRoutes.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter, Route, Switch, Redirect } from "react-router-dom"; 3 | import LoginPage from "./LoginPage"; 4 | import LandingPage from "./LandingPage"; 5 | import RegisterPage from "./RegisterPage"; 6 | import Onboard from "./Onboard"; 7 | const LandingRoutes = () => { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | { 18 | return ; 19 | }} 20 | /> 21 | 22 | 23 | ); 24 | }; 25 | 26 | export default LandingRoutes; 27 | -------------------------------------------------------------------------------- /client/src/components/LandingPage/LoginPage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import logo from "../../assets/logo.png"; 4 | import "../../css/LoginPage.css"; 5 | import LoginForm from "../Forms/LoginForm"; 6 | import { MdKeyboardBackspace } from "react-icons/md"; 7 | const LoginPage = () => { 8 | return ( 9 |
10 |
11 | 12 | logo 13 | 14 |

22 | Welcome back!{" "} 23 |

24 |
25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 |

back to home page

33 |
34 |
35 |
36 |
37 | 38 | 39 |
40 | Not a user?{" "} 41 | 42 | Click here to sign up 43 | 44 |
45 |
46 | ); 47 | }; 48 | 49 | export default LoginPage; 50 | -------------------------------------------------------------------------------- /client/src/components/LandingPage/Onboard.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import AuthContext from "../../context/AuthContext"; 3 | import apiServer from "../../config/apiServer"; 4 | import { useForm } from "react-hook-form"; 5 | import "../../css/LoginPage.css"; 6 | 7 | const Onboard = (props) => { 8 | const { register, handleSubmit, errors } = useForm(); 9 | const { setAuth } = useContext(AuthContext); 10 | 11 | const [errorMessage, setErrorMessage] = useState(""); 12 | const onboard = async ({ teamName }) => { 13 | const email = localStorage.getItem("email"); 14 | if (teamName) { 15 | try { 16 | const res = await apiServer.put("/register/onboard", { 17 | email, 18 | teamName, 19 | }); 20 | //sets initial token 21 | localStorage.setItem("token", res.data.token); 22 | setErrorMessage(""); 23 | //for Refresh 24 | setAuth(res.data.token); 25 | } catch (err) { 26 | console.log(err.status); 27 | setErrorMessage("Something went wrong"); 28 | } 29 | } 30 | }; 31 | 32 | const onSkip = () => { 33 | //sets initial token 34 | localStorage.setItem("token", localStorage.getItem("onboard")); 35 | //for component to refresh to redirect webpage 36 | setAuth(localStorage.getItem("onboard")); 37 | localStorage.removeItem("onboard"); 38 | }; 39 | return ( 40 |
41 |
49 |
50 |

58 | What team will you be working on? 59 |

60 |
61 |
62 |
63 | 64 | 65 | {errors.teamName?.type === "minLengh" && ( 66 |

67 | Team name must be greater than 1 character 68 |

69 | )} 70 |
71 |
78 | {/* */} 92 | 100 |
101 | {errorMessage ? ( 102 |

{errorMessage}

103 | ) : null} 104 |
105 |
106 |
107 | ); 108 | }; 109 | 110 | export default Onboard; 111 | -------------------------------------------------------------------------------- /client/src/components/LandingPage/RegisterPage.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { useForm } from "react-hook-form"; 3 | import AuthContext from "../../context/AuthContext"; 4 | import logo from "../../assets/logo.png"; 5 | import "../../css/LoginPage.css"; 6 | import apiServer from "../../config/apiServer"; 7 | import { MdKeyboardBackspace } from "react-icons/md"; 8 | const RegisterPage = () => { 9 | const { register, handleSubmit, errors } = useForm(); 10 | const { setAuth, setEmail, setUserId, setUser } = useContext(AuthContext); 11 | const [errorMessage, setErrorMessage] = useState(""); 12 | const [loading, setLoading] = useState(false); 13 | const onSubmit = async ({ name, email, password }) => { 14 | setLoading(true); 15 | try { 16 | const res = await apiServer.post("/register", { name, email, password }); 17 | localStorage.setItem("onboard", res.data.token); 18 | localStorage.setItem("email", res.data.email); 19 | localStorage.setItem("userId", res.data.id); 20 | window.location.href = "/register/onboard"; 21 | setErrorMessage(""); 22 | setUser(res.data); 23 | setAuth(res.data.token); 24 | setEmail(res.data.email); 25 | setUserId(res.data.id); 26 | } catch (err) { 27 | setLoading(false); 28 | console.log(err.status); 29 | setErrorMessage("Something went wrong with registering"); 30 | } 31 | }; 32 | 33 | return ( 34 |
35 |
36 | 37 | logo 38 | 39 |

47 | Welcome to Methodize!{" "} 48 |

49 |

57 | First things first, let's set up your account... 58 |

59 |
60 |
61 | 62 |
63 |
64 | 65 |
66 |
67 |

back to home page

68 |
69 |
70 |
71 |
72 |
73 |
74 | 75 | 80 | {errors.name?.type === "required" && ( 81 |

82 | Please enter your full name 83 |

84 | )} 85 |
86 |
87 | 88 | 93 | {errors.email?.type === "required" && ( 94 |

95 | Please enter an email address 96 |

97 | )} 98 |
99 | 100 |
101 | 102 | 107 | {errors.password?.type === "required" && ( 108 |

109 | Please enter a password 110 |

111 | )} 112 |
113 | 114 | {errorMessage ? ( 115 |

{errorMessage}

116 | ) : null} 117 |
118 |
119 | Already a user?{" "} 120 | 121 | Click here to login 122 | 123 |
124 |
125 | ); 126 | }; 127 | 128 | export default RegisterPage; 129 | -------------------------------------------------------------------------------- /client/src/components/Loader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const Loader = () => { 4 | return
; 5 | }; 6 | 7 | export default Loader; 8 | -------------------------------------------------------------------------------- /client/src/components/NavigationBar/Searchbar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const SearchBar = () => { 4 | return
SearchBar
; 5 | }; 6 | 7 | export default SearchBar; 8 | -------------------------------------------------------------------------------- /client/src/components/NavigationBar/TopNavBar.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import AuthContext from "../../context/AuthContext"; 3 | import "../../css/Navbar.css"; 4 | import { GrAddCircle } from "react-icons/gr"; 5 | import UserAvatar from "./UserAvatar"; 6 | import { Menu, MenuItem } from "@material-ui/core"; 7 | import ProjectForm from "../Forms/ProjectForm"; 8 | import TaskForm from "../Forms/AddTaskForm"; 9 | import Search from "../../assets/search"; 10 | import messageIcon from "../../assets/message.png"; 11 | import Alert from "../../assets/alert"; 12 | import { Context as UserContext } from "../../context/store/UserStore"; 13 | 14 | const TopNavBar = ({ name, setTeamProjects, setTasklists, sidebar }) => { 15 | const { logout } = useContext(AuthContext); 16 | 17 | const [anchorEl, setAnchorEl] = useState(null); 18 | const [anchorEle, setAnchorEle] = useState(null); 19 | const [openProject, setOpenProject] = useState(false); 20 | const [openTask, setOpenTask] = useState(false); 21 | const [userState, userdispatch] = useContext(UserContext); 22 | 23 | const clickOpenTask = () => { 24 | setOpenTask(true); 25 | handleNewClose(); 26 | }; 27 | 28 | const clickCloseTask = () => { 29 | setOpenTask(false); 30 | }; 31 | 32 | const clickOpenProject = () => { 33 | setOpenProject(true); 34 | handleNewClose(); 35 | }; 36 | const clickCloseProject = () => { 37 | setOpenProject(false); 38 | }; 39 | 40 | const handleNewClick = (event) => { 41 | setAnchorEl(event.currentTarget); 42 | }; 43 | const handleNewClose = () => { 44 | setAnchorEl(null); 45 | }; 46 | 47 | const handleProfClick = (event) => { 48 | setAnchorEle(event.currentTarget); 49 | }; 50 | const handleProfClose = () => { 51 | setAnchorEle(null); 52 | }; 53 | 54 | return ( 55 | //
60 |
61 |
62 |

{name}

63 |
64 |
65 |
66 | {/*
67 | 68 |
*/} 69 | {/*
70 | 71 | 78 | Add Task 79 | 85 | Add Project 86 | 92 | 93 |
*/} 94 |
98 |
99 | 100 |
101 |
102 | 103 |
104 | 105 |
106 | logo 107 |
108 |
109 | 110 |
116 |
117 | 118 |
119 |
{userState.user.name}
120 |
124 | 125 |
126 |
127 | 128 | 135 | Logout 136 | 137 |
138 |
139 | ); 140 | }; 141 | 142 | export default TopNavBar; 143 | -------------------------------------------------------------------------------- /client/src/components/NavigationBar/TopNavBarHome.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import AuthContext from "../../context/AuthContext"; 3 | import "../../css/Navbar.css"; 4 | import { GrAddCircle } from "react-icons/gr"; 5 | import UserAvatar from "./UserAvatar"; 6 | import { Menu, MenuItem } from "@material-ui/core"; 7 | import Search from "../../assets/search"; 8 | import messageIcon from "../../assets/message.png"; 9 | import Alert from "../../assets/alert"; 10 | import ProjectForm from "../Forms/ProjectForm"; 11 | import TaskForm from "../Forms/AddTaskForm"; 12 | import apiServer from "../../config/apiServer"; 13 | import { Context as UserContext } from "../../context/store/UserStore"; 14 | 15 | const TopNavBarHome = () => { 16 | const { logout } = useContext(AuthContext); 17 | const [userState, userdispatch] = useContext(UserContext); 18 | 19 | const [anchorEl, setAnchorEl] = useState(null); 20 | const [anchorEle, setAnchorEle] = useState(null); 21 | const [openProject, setOpenProject] = useState(false); 22 | const [openTask, setOpenTask] = useState(false); 23 | const userId = localStorage.getItem("userId"); 24 | 25 | // useEffect(()=>{ 26 | // (async()=>{ 27 | // const user = await apiServer.get("/user") 28 | // })(); 29 | // },[]) 30 | 31 | const clickOpenTask = () => { 32 | setOpenTask(true); 33 | handleNewClose(); 34 | }; 35 | 36 | const clickCloseTask = () => { 37 | setOpenTask(false); 38 | }; 39 | 40 | const clickOpenProject = () => { 41 | setOpenProject(true); 42 | handleNewClose(); 43 | }; 44 | const clickCloseProject = () => { 45 | setOpenProject(false); 46 | }; 47 | 48 | const handleNewClick = (event) => { 49 | setAnchorEl(event.currentTarget); 50 | }; 51 | const handleNewClose = () => { 52 | setAnchorEl(null); 53 | }; 54 | 55 | const handleProfClick = (event) => { 56 | setAnchorEle(event.currentTarget); 57 | }; 58 | const handleProfClose = () => { 59 | setAnchorEle(null); 60 | }; 61 | return ( 62 |
63 |
67 |
68 |
69 | {/*
70 | 71 |
*/} 72 | {/*
73 | 74 | 81 | Add Task 82 | 87 | Add Project 88 | 93 | 94 |
*/} 95 |
99 |
100 | 101 |
102 |
103 | 104 |
105 | 106 |
107 | logo 108 |
109 |
110 | 111 |
117 |
118 | 119 |
120 |
121 |

{userState.user.name}

122 |
123 |
127 | 128 |
129 |
130 | 131 | 138 | Logout 139 | 140 |
141 |
142 | ); 143 | }; 144 | 145 | export default TopNavBarHome; 146 | -------------------------------------------------------------------------------- /client/src/components/NavigationBar/TopNavBarTask.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import AuthContext from "../../context/AuthContext"; 3 | import { Context as UserContext } from "../../context/store/UserStore"; 4 | import { Context as TaskContext } from "../../context/store/TaskStore"; 5 | import "../../css/Navbar.css"; 6 | import { GrAddCircle } from "react-icons/gr"; 7 | import UserAvatar from "./UserAvatar"; 8 | import { Menu, MenuItem } from "@material-ui/core"; 9 | import ProjectForm from "../Forms/ProjectForm"; 10 | import TaskForm from "../Forms/AddTaskForm"; 11 | import Search from "../../assets/search"; 12 | import messageIcon from "../../assets/message.png"; 13 | import Alert from "../../assets/alert"; 14 | 15 | const TopNavBarTask = () => { 16 | const { logout } = useContext(AuthContext); 17 | const [userState, userdispatch] = useContext(UserContext); 18 | const { name } = userState.user; 19 | const [taskState, taskdispatch] = useContext(TaskContext); 20 | const numTask = taskState.tasks.filter((task) => task.completed === true); 21 | 22 | const [anchorEl, setAnchorEl] = useState(null); 23 | const [anchorEle, setAnchorEle] = useState(null); 24 | const [openProject, setOpenProject] = useState(false); 25 | const [openTask, setOpenTask] = useState(false); 26 | 27 | const clickOpenTask = () => { 28 | setOpenTask(true); 29 | handleNewClose(); 30 | }; 31 | 32 | const clickCloseTask = () => { 33 | setOpenTask(false); 34 | }; 35 | 36 | const clickOpenProject = () => { 37 | setOpenProject(true); 38 | handleNewClose(); 39 | }; 40 | const clickCloseProject = () => { 41 | setOpenProject(false); 42 | }; 43 | 44 | const handleNewClick = (event) => { 45 | setAnchorEl(event.currentTarget); 46 | }; 47 | const handleNewClose = () => { 48 | setAnchorEl(null); 49 | }; 50 | 51 | const handleProfClick = (event) => { 52 | setAnchorEle(event.currentTarget); 53 | }; 54 | const handleProfClose = () => { 55 | setAnchorEle(null); 56 | }; 57 | 58 | return ( 59 |
60 |
64 |
{name}'s Tasks
65 |

{numTask.length} completed tasks

66 |
67 |
68 |
69 | {/*
70 | 71 |
*/} 72 | {/*
73 | 74 | 81 | Add Task 82 | 87 | Add Project 88 | 93 | 94 | 95 |
*/} 96 |
100 |
101 | 102 |
103 |
104 | 105 |
106 | 107 |
108 | logo 109 |
110 |
111 | 112 |
118 |
119 | 120 |
121 |
{userState.user.name}
122 |
126 | 127 |
128 |
129 | 130 | 137 | Logout 138 | 139 |
140 |
141 | ); 142 | }; 143 | 144 | export default TopNavBarTask; 145 | -------------------------------------------------------------------------------- /client/src/components/NavigationBar/UserAvatar.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import "../../css/Navbar.css"; 3 | import apiServer from "../../config/apiServer"; 4 | 5 | const UserAvatar = ({ id }) => { 6 | const [user, setUser] = useState(); 7 | const [loading, setLoading] = useState(true); 8 | 9 | const getUser = async () => { 10 | const res = await apiServer.get(`/user/${id}`); 11 | setUser(res.data); 12 | setLoading(false); 13 | }; 14 | 15 | useEffect(() => { 16 | getUser(); 17 | // eslint-disable-next-line react-hooks/exhaustive-deps 18 | }, []); 19 | 20 | if (loading) { 21 | return
Loading..
; 22 | } 23 | return ( 24 |
25 | {(user.name[0] + user.name[1]).toUpperCase()} 26 |
27 | ); 28 | }; 29 | 30 | export default UserAvatar; 31 | -------------------------------------------------------------------------------- /client/src/components/Pages/NewTasks.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import TopNavBarTask from "../NavigationBar/TopNavBarTask"; 3 | import { Context as TaskContext } from "../../context/store/TaskStore"; 4 | import apiServer from "../../config/apiServer"; 5 | import TaskSection from "../tasks/TaskSection"; 6 | import PopOutTaskDetails from "../PopOutMenu/PopOutTaskDetails"; 7 | import TaskItemTask from "../tasks/TaskItemTask"; 8 | import Add from "../../assets/Add"; 9 | import AddTaskPopOutTaskPage from "../PopOutMenu/AddTaskPopOutTaskPage"; 10 | 11 | const NewTasks = () => { 12 | const [taskState, taskdispatch] = useContext(TaskContext); 13 | const [loading, setLoading] = useState(true); 14 | const [initialLoad, setInitialLoad] = useState(true); 15 | const [open, setOpen] = useState(false); 16 | const [sideTaskForm, setSideTaskForm] = useState(false); 17 | 18 | const [sideTaskDetails, setSideTaskDetails] = useState(false); 19 | 20 | const showSideTaskForm = () => { 21 | setSideTaskDetails(false); 22 | setSideTaskForm(!sideTaskForm); 23 | }; 24 | const showSideTaskDetails = () => { 25 | setSideTaskForm(false); 26 | setSideTaskDetails(!sideTaskDetails); 27 | }; 28 | 29 | const getUserTasks = async () => { 30 | const id = localStorage.getItem("userId"); 31 | const res = await apiServer.get(`/task/user/${id}`); 32 | await taskdispatch({ type: "get_user_tasks", payload: res.data }); 33 | // setTasks(res.data); 34 | setLoading(false); 35 | }; 36 | 37 | const sortedTasks = taskState.tasks.sort(function (a, b) { 38 | return new Date(a.due_date) - new Date(b.due_date); 39 | }); 40 | 41 | const renderedTasks = sortedTasks.map((task, i) => { 42 | return ( 43 | 50 | ); 51 | }); 52 | const openModal = () => { 53 | setOpen(true); 54 | }; 55 | 56 | const closeModal = () => { 57 | setOpen(false); 58 | }; 59 | 60 | useEffect(() => { 61 | getUserTasks(); 62 | // eslint-disable-next-line react-hooks/exhaustive-deps 63 | }, []); 64 | 65 | return ( 66 | <> 67 | 68 |
69 | {/*
*/} 70 |
71 |
78 |
82 |
83 | 84 |
85 |
86 |

87 | Add Task 88 |

89 |
90 |
91 | {renderedTasks} 92 | {/* */} 93 |
94 | {sideTaskDetails && taskState.selectedTask ? ( 95 | 99 | ) : null} 100 | {sideTaskForm ? ( 101 | 105 | ) : null} 106 |
107 |
108 | 109 | ); 110 | }; 111 | 112 | export default NewTasks; 113 | -------------------------------------------------------------------------------- /client/src/components/Pages/Projects.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TopNavBar from "../NavigationBar/TopNavBar"; 3 | const Projects = () => { 4 | return ( 5 | <> 6 | 7 |
Projects
8 | 9 | ); 10 | }; 11 | 12 | export default Projects; 13 | -------------------------------------------------------------------------------- /client/src/components/Pages/Tasks.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from "react"; 2 | import { Modal } from "@material-ui/core"; 3 | import TopNavBarTask from "../NavigationBar/TopNavBarTask"; 4 | import "../../css/Task.css"; 5 | import { Context as TaskContext } from "../../context/store/TaskStore"; 6 | import apiServer from "../../config/apiServer"; 7 | import TaskSection from "../tasks/TaskSection"; 8 | import moment from "moment"; 9 | import TaskForm from "../Forms/AddTaskForm"; 10 | 11 | const TasksPage = () => { 12 | const [taskState, taskdispatch] = useContext(TaskContext); 13 | const [loading, setLoading] = useState(true); 14 | const [open, setOpen] = useState(false); 15 | 16 | const getUserTasks = async () => { 17 | const id = localStorage.getItem("userId"); 18 | const res = await apiServer.get(`/task/user/${id}`); 19 | await taskdispatch({ type: "get_user_tasks", payload: res.data }); 20 | // setTasks(res.data); 21 | setLoading(false); 22 | }; 23 | const openModal = () => { 24 | setOpen(true); 25 | }; 26 | 27 | const closeModal = () => { 28 | setOpen(false); 29 | }; 30 | 31 | useEffect(() => { 32 | getUserTasks(); 33 | // eslint-disable-next-line react-hooks/exhaustive-deps 34 | }, []); 35 | 36 | if (loading) { 37 | return
Loading...
; 38 | } 39 | 40 | const modalBody = ( 41 |
42 | 43 |
44 | ); 45 | const sortedTasks = taskState.tasks.sort(function (a, b) { 46 | return new Date(a.due_date) - new Date(b.due_date); 47 | }); 48 | 49 | const recentlyAdded = taskState.tasks.filter((task) => { 50 | const date = new Date(task.createdAt); 51 | const createdDate = moment(date); 52 | const todaysDate = moment(new Date()); 53 | const previousDate = moment(new Date()).subtract(1, "week"); 54 | return createdDate.isBetween(previousDate, todaysDate); //created date is between previous week and today 55 | }); 56 | 57 | const todaysTasks = sortedTasks.filter((task) => { 58 | const date = new Date(task.due_date); 59 | const dueDate = moment(date).format("M D YYYY"); 60 | const todaysDate = moment(new Date()).format("M D YYYY"); 61 | return dueDate === todaysDate; //due date is today 62 | }); 63 | 64 | const upcomingTasks = sortedTasks.filter((task) => { 65 | const date = new Date(task.due_date); 66 | const dueDate = moment(date); 67 | const todaysDate = moment(new Date()); 68 | const upcomingDate = moment(new Date()).add(1, "month"); 69 | return dueDate.isBetween(todaysDate, upcomingDate); //due date is between today and a month 70 | }); 71 | 72 | const laterTasks = sortedTasks.filter((task) => { 73 | const date = new Date(task.due_date); 74 | const dueDate = moment(date); 75 | const laterDate = moment(new Date()).add(1, "month"); 76 | return dueDate.isAfter(laterDate); //due date is after 1 month 77 | }); 78 | 79 | return ( 80 | <> 81 | 82 |
83 |
84 |
85 | 88 |
89 |
92 | Due Date 93 |
94 |
95 |
96 | 97 | 98 | 99 | 100 |
101 |
102 | 103 | {modalBody} 104 | 105 | 106 | ); 107 | }; 108 | 109 | export default TasksPage; 110 | -------------------------------------------------------------------------------- /client/src/components/PopOutMenu/AddProjectPopOut.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import "../../css/PopOutForms.css"; 4 | import { RiCloseLine } from "react-icons/ri"; 5 | import ProjectForm from "../Forms/ProjectForm"; 6 | const AddProjectPopOut = ({ showSideProjectForm, title, setTeamProjects }) => { 7 | return ( 8 | <> 9 |
13 |
22 |
23 |
24 |
25 |

{title}

26 |
27 |
28 | 36 |
37 |
38 | 42 |
43 |
44 |
45 | 46 | ); 47 | }; 48 | 49 | export default AddProjectPopOut; 50 | -------------------------------------------------------------------------------- /client/src/components/PopOutMenu/AddTaskPopOutProjectPage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AddTaskProjectForm from "../Forms/AddTaskProjectForm"; 3 | import "../../css/PopOutForms.css"; 4 | import { RiCloseLine } from "react-icons/ri"; 5 | const AddTaskPopOutProjectPage = ({ 6 | showSideTaskForm, 7 | title, 8 | setTasklists, 9 | }) => { 10 | return ( 11 | <> 12 |
13 |
22 |
23 |
24 |
25 |

{title}

26 |
27 |
28 | 36 |
37 |
38 | 42 |
43 |
44 |
45 | 46 | ); 47 | }; 48 | 49 | export default AddTaskPopOutProjectPage; 50 | -------------------------------------------------------------------------------- /client/src/components/PopOutMenu/AddTaskPopOutTaskPage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AddTaskForm from "../Forms/AddTaskForm"; 3 | import "../../css/PopOutForms.css"; 4 | import { RiCloseLine } from "react-icons/ri"; 5 | const AddTaskPopOutTaskPage = ({ showSideTaskForm, title }) => { 6 | return ( 7 | <> 8 |
9 |
18 |
19 |
20 |
21 |

{title}

22 |
23 |
24 | 32 |
33 |
34 | 35 |
36 |
37 |
38 | 39 | ); 40 | }; 41 | 42 | export default AddTaskPopOutTaskPage; 43 | -------------------------------------------------------------------------------- /client/src/components/PopOutMenu/AddTasklistPopOut.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TasklistForm from "../Forms/TaskListForm"; 3 | import "../../css/PopOutForms.css"; 4 | import { RiCloseLine } from "react-icons/ri"; 5 | const AddTasklistPopOut = ({ showSideTasklistForm, title, setTasklists }) => { 6 | return ( 7 | <> 8 |
9 |
18 |
19 |
20 |
21 |

{title}

22 |
23 |
24 | 32 |
33 |
34 | 38 |
39 |
40 |
41 | 42 | ); 43 | }; 44 | 45 | export default AddTasklistPopOut; 46 | -------------------------------------------------------------------------------- /client/src/components/Routes.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from "react"; 2 | import AuthContext from "../context/AuthContext"; 3 | import LandingRoutes from "./LandingPage/LandingRoutes"; 4 | import AuthRoutes from "./AuthRoutes"; 5 | 6 | const Routes = () => { 7 | const { auth } = useContext(AuthContext); 8 | 9 | //If there is Auth(ternary statement) load separate auth component that includes Login, signup, landing. 10 | // OR have auth ternary statement render in each route 11 | return <>{auth ? : }; 12 | }; 13 | 14 | export default Routes; 15 | -------------------------------------------------------------------------------- /client/src/components/projects/NewProjectTile.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Modal } from "@material-ui/core"; 3 | import { FiPlus } from "react-icons/fi"; 4 | import "../../css/Project.css"; 5 | import ProjectForm from "../Forms/ProjectForm"; 6 | const NewProjectTile = ({ showSideProjectForm }) => { 7 | const [open, setOpen] = useState(false); 8 | // const openModal = () => { 9 | // setOpen(true); 10 | // }; 11 | 12 | // const closeModal = () => { 13 | // setOpen(false); 14 | // }; 15 | // const modalBody = ( 16 | //
17 | // 18 | //
19 | // ); 20 | return ( 21 |
22 |
23 |
24 | 25 |
26 |
27 |
New Project
28 |
29 | ); 30 | }; 31 | 32 | export default NewProjectTile; 33 | -------------------------------------------------------------------------------- /client/src/components/projects/ProjectItemHome.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import "../../css/Project.css"; 4 | import { AiOutlineEllipsis } from "react-icons/ai"; 5 | import { Menu, MenuItem } from "@material-ui/core"; 6 | 7 | const ProjectItemHome = ({ project }) => { 8 | const [open, setOpen] = useState(false); 9 | const [anchorEl, setAnchorEl] = useState(null); 10 | const openModal = () => { 11 | setOpen(true); 12 | }; 13 | 14 | const closeModal = () => { 15 | setOpen(false); 16 | }; 17 | 18 | const handleMenuClick = (event) => { 19 | setAnchorEl(event.currentTarget); 20 | }; 21 | const handleMenuClose = () => { 22 | setAnchorEl(null); 23 | }; 24 | 25 | //import component as body such as forms, details, etc 26 | // const body = ( 27 | //
28 | // {/* 29 | // */} 32 | // 33 | //
34 | // ); 35 | 36 | return ( 37 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |
48 |

49 | {project.name} 50 |

51 |
52 |
53 |
58 | 59 |
60 | 67 | Delete 68 | 69 |
70 |
71 | 72 | ); 73 | }; 74 | 75 | export default ProjectItemHome; 76 | -------------------------------------------------------------------------------- /client/src/components/projects/ProjectTile.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { Link } from "react-router-dom"; 3 | import apiServer from "../../config/apiServer"; 4 | import "../../css/Project.css"; 5 | import { AiOutlineProject } from "react-icons/ai"; 6 | import Loader from "../Loader"; 7 | 8 | const ProjectTile = ({ project, teamId, id }) => { 9 | const [loading, setLoading] = useState(true); 10 | const [team, setTeam] = useState(); 11 | const getTeam = async () => { 12 | const res = await apiServer.get(`/project/${project.id}/team`); 13 | setTeam(res.data); 14 | setLoading(false); 15 | }; 16 | 17 | useEffect(() => { 18 | (async () => { 19 | const res = await apiServer.get(`/project/${project.id}/team`); 20 | setTeam(res.data); 21 | setLoading(false); 22 | })(); 23 | }, []); 24 | 25 | if (loading) { 26 | return ; 27 | } 28 | const team_id = teamId || team.id; 29 | return ( 30 | 34 |
35 |
36 |
37 | 38 |
39 |
40 |
{project.name}
41 |
42 | 43 | ); 44 | }; 45 | 46 | export default ProjectTile; 47 | -------------------------------------------------------------------------------- /client/src/components/tasks/ColumnTaskItem.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd"; 3 | import TaskDetailsForm from "../tasks/TaskDetailsForm"; 4 | import { Modal, responsiveFontSizes } from "@material-ui/core"; 5 | import Pin from "../../assets/pin"; 6 | import Comments from "../../assets/comments"; 7 | import UserAvatar from "../NavigationBar/UserAvatar"; 8 | import moment from "moment"; 9 | import { Context as TaskContext } from "../../context/store/TaskStore"; 10 | import apiServer from "../../config/apiServer"; 11 | 12 | const ColumnTaskItem = ({ 13 | task, 14 | index, 15 | showSideTaskDetails, 16 | sideTaskDetails, 17 | }) => { 18 | const [taskState, taskdispatch] = useContext(TaskContext); 19 | 20 | const date = moment( 21 | task.due_date.substring(0, 10).replace("-", ""), 22 | "YYYYMMDD" 23 | ); 24 | 25 | const setTaskPopOut = async () => { 26 | if (sideTaskDetails === false) { 27 | showSideTaskDetails(); 28 | //--- 29 | taskdispatch({ type: "get_selected_task", payload: null }); 30 | const res = await apiServer.get(`/task/${task.id}`); 31 | await taskdispatch({ type: "get_selected_task", payload: res.data }); 32 | } else { 33 | taskdispatch({ type: "get_selected_task", payload: null }); 34 | const res = await apiServer.get(`/task/${task.id}`); 35 | await taskdispatch({ type: "get_selected_task", payload: res.data }); 36 | } 37 | }; 38 | 39 | return ( 40 |
41 | 47 | {(provided, snapshot) => ( 48 |
56 |
57 |
{task.name}
58 |
59 |
60 | {" "} 61 |

8

62 |
63 |
64 | {" "} 65 |

9

66 |
67 |
68 |
69 |
70 |
71 |
72 | {(task.User.name[0] + task.User.name[1]).toUpperCase()} 73 |
74 |
75 |
76 |

{date.format("MMM D")}

77 |
78 |
79 |
80 | )} 81 |
82 |
83 | ); 84 | }; 85 | 86 | export default ColumnTaskItem; 87 | -------------------------------------------------------------------------------- /client/src/components/tasks/TaskItemHome.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import moment from "moment"; 3 | import { Modal } from "@material-ui/core"; 4 | import "../../css/Modal.css"; 5 | import TaskDetailsForm from "./TaskDetailsForm"; 6 | import { 7 | RiCheckboxBlankCircleLine, 8 | RiCheckboxCircleLine, 9 | } from "react-icons/ri"; 10 | import { AiOutlineEllipsis } from "react-icons/ai"; 11 | import { Menu, MenuItem } from "@material-ui/core"; 12 | import { Context as TaskContext } from "../../context/store/TaskStore"; 13 | import apiServer from "../../config/apiServer"; 14 | 15 | //Task item list for home and task page 16 | 17 | const TaskItemHome = ({ task, showSideTaskDetails, sideTaskDetails }) => { 18 | const date = moment( 19 | task.due_date.substring(0, 10).replace("-", ""), 20 | "YYYYMMDD" 21 | ); 22 | 23 | const [taskState, taskdispatch] = useContext(TaskContext); 24 | const [anchorEl, setAnchorEl] = useState(null); 25 | const [open, setOpen] = useState(false); 26 | const openModal = () => { 27 | setOpen(true); 28 | }; 29 | 30 | const closeModal = () => { 31 | setOpen(false); 32 | }; 33 | 34 | const handleMenuClick = (event) => { 35 | setAnchorEl(event.currentTarget); 36 | }; 37 | const handleMenuClose = () => { 38 | setAnchorEl(null); 39 | }; 40 | 41 | const setTaskPopOut = async () => { 42 | if (sideTaskDetails === false) { 43 | showSideTaskDetails(); 44 | //--- 45 | taskdispatch({ type: "get_selected_task", payload: null }); 46 | const res = await apiServer.get(`/task/${task.id}`); 47 | await taskdispatch({ type: "get_selected_task", payload: res.data }); 48 | // setInitialLoad(false); 49 | console.log("if popout"); 50 | } else { 51 | console.log("else popout"); 52 | taskdispatch({ type: "get_selected_task", payload: null }); 53 | const res = await apiServer.get(`/task/${task.id}`); 54 | await taskdispatch({ type: "get_selected_task", payload: res.data }); 55 | // setInitialLoad(false); 56 | } 57 | }; 58 | 59 | const handleTaskDelete = async (e) => { 60 | // console.log(task.id); 61 | handleMenuClose(); 62 | await apiServer.delete(`/task/${task.id}`); 63 | const id = localStorage.getItem("userId"); 64 | const res = await apiServer.get(`/task/user/${id}`); 65 | await taskdispatch({ type: "get_user_tasks", payload: res.data }); 66 | }; 67 | //import component as body such as forms, details, etc 68 | const body = ( 69 |
70 | {/* 71 | */} 74 | 75 |
76 | ); 77 | return ( 78 | <> 79 |
80 |
81 |
82 |
83 | {/* {task.completed ? ( 84 | 87 | ) : ( 88 | 89 | )} */} 90 | 91 |
92 |
93 |

100 | {task.name} 101 |

102 |

103 | {date.format("MMM D")} 104 |

105 |
106 |
107 |
112 | 113 |
114 | 121 | Delete 122 | 123 |
124 |
125 | {/* 126 | {body} 127 | */} 128 | {/* */} 129 | 130 | ); 131 | }; 132 | 133 | export default TaskItemHome; 134 | -------------------------------------------------------------------------------- /client/src/components/tasks/TaskItemProject.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { Modal } from "@material-ui/core"; 3 | import { Draggable } from "react-beautiful-dnd"; 4 | import TaskDetailsForm from "../tasks/TaskDetailsForm"; 5 | import "../../css/Modal.css"; 6 | 7 | //Project page task item for the task list 8 | 9 | const TaskItemProject = ({ task, index, setTasklistTasks }) => { 10 | const [openTaskDetailForm, setOpenTaskDetailForm] = useState(false); 11 | // console.log(task); 12 | const openTaskDetailFormModal = () => { 13 | setOpenTaskDetailForm(true); 14 | }; 15 | 16 | const closeTaskDetailFormModal = () => { 17 | setOpenTaskDetailForm(false); 18 | }; 19 | const taskDetailModalBody = ( 20 |
21 | 27 |
28 | ); 29 | return ( 30 |
31 | 37 | {(provided, snapshot) => ( 38 |
45 | {task.name} 46 |
47 | )} 48 |
49 |
50 | 51 | {taskDetailModalBody} 52 | 53 |
54 |
55 | ); 56 | }; 57 | 58 | export default TaskItemProject; 59 | -------------------------------------------------------------------------------- /client/src/components/tasks/TaskItemTask.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from "react"; 2 | import moment from "moment"; 3 | import { Modal } from "@material-ui/core"; 4 | import "../../css/Modal.css"; 5 | import TaskDetailsForm from "../tasks/TaskDetailsForm"; 6 | import { 7 | RiCheckboxBlankCircleLine, 8 | RiCheckboxCircleLine, 9 | } from "react-icons/ri"; 10 | import { Context as TaskContext } from "../../context/store/TaskStore"; 11 | import apiServer from "../../config/apiServer"; 12 | 13 | //Task item list for home and task page 14 | 15 | const TaskItemTask = ({ 16 | task, 17 | showSideTaskDetails, 18 | sideTaskDetails, 19 | setInitialLoad, 20 | }) => { 21 | const [taskState, taskdispatch] = useContext(TaskContext); 22 | const [open, setOpen] = useState(false); 23 | 24 | const date = moment( 25 | task.due_date.substring(0, 10).replace("-", ""), 26 | "YYYYMMDD" 27 | ); 28 | const openModal = () => { 29 | setOpen(true); 30 | }; 31 | 32 | const closeModal = () => { 33 | setOpen(false); 34 | }; 35 | 36 | const setTaskPopOut = async () => { 37 | if (sideTaskDetails === false) { 38 | showSideTaskDetails(); 39 | //--- 40 | taskdispatch({ type: "get_selected_task", payload: null }); 41 | const res = await apiServer.get(`/task/${task.id}`); 42 | await taskdispatch({ type: "get_selected_task", payload: res.data }); 43 | setInitialLoad(false); 44 | console.log("if popout"); 45 | } else { 46 | console.log("else popout"); 47 | taskdispatch({ type: "get_selected_task", payload: null }); 48 | const res = await apiServer.get(`/task/${task.id}`); 49 | await taskdispatch({ type: "get_selected_task", payload: res.data }); 50 | setInitialLoad(false); 51 | } 52 | }; 53 | 54 | //import component as body such as forms, details, etc 55 | const body = ( 56 |
57 | {/* 58 | */} 61 | 62 |
63 | ); 64 | return ( 65 | <> 66 |
  • 67 |
    68 | {task.completed ? ( 69 | 72 | ) : ( 73 | 74 | )} 75 |

    84 | {task.name} 85 |

    86 |
    87 |
    88 |
    91 |

    102 | {task.Project.name} 103 |

    104 |
    105 |
    112 |

    120 | {date.format("MMM D YYYY")} 121 |

    122 |
    123 |
    124 |
  • 125 | {/* 126 | {body} 127 | */} 128 | 129 | ); 130 | }; 131 | 132 | export default TaskItemTask; 133 | -------------------------------------------------------------------------------- /client/src/components/tasks/TaskListItem.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import TaskItemProject from "./TaskItemProject"; 3 | import apiServer from "../../config/apiServer"; 4 | import "../../css/TaskList.css"; 5 | import Loader from "../Loader"; 6 | import { Modal } from "@material-ui/core"; 7 | import { Draggable, Droppable } from "react-beautiful-dnd"; 8 | import AddTaskProjectForm from "../Forms/AddTaskProjectForm"; 9 | 10 | //Project page task list 11 | const TaskListItem = ({ index, tasklist, tasks, setTasks }) => { 12 | const [tasklistTasks, setTasklistTasks] = useState(); 13 | const [loading, setLoading] = useState(true); 14 | const [openTaskProjectForm, setOpenTaskProjectForm] = useState(false); 15 | 16 | // const getTasks = async () => { 17 | // try { 18 | // const res = await apiServer.get(`/tasklist/${tasklist.id}/tasks`); 19 | // setTasks(res.data); 20 | // setLoading(false); 21 | // } catch (err) { 22 | // console.log(err); 23 | // } 24 | // }; 25 | 26 | const openTaskProjectFormModal = () => { 27 | setOpenTaskProjectForm(true); 28 | }; 29 | 30 | const closeTaskProjectFormModal = () => { 31 | setOpenTaskProjectForm(false); 32 | }; 33 | 34 | const updateTasks = async () => { 35 | //returns individual tasklist tasks 36 | const res = await apiServer.get(`/tasklist/${tasklist.id}/tasks`); 37 | setTasklistTasks(res.data); 38 | setLoading(false); 39 | }; 40 | useEffect(() => { 41 | updateTasks(); 42 | // eslint-disable-next-line react-hooks/exhaustive-deps 43 | }, [setTasklistTasks]); 44 | 45 | if (loading) { 46 | return ; 47 | } 48 | 49 | const renderedTasks = tasklistTasks.map((task, i) => { 50 | return ( 51 | 58 | ); 59 | }); 60 | 61 | const taskProjectFormModal = ( 62 |
    63 | 71 |
    72 | ); 73 | 74 | return ( 75 |
    76 | 82 | {(provided) => ( 83 |
    89 |
    {tasklist.name}
    90 |
    91 | 92 | {(provided) => ( 93 |
    98 | {renderedTasks} 99 | {provided.placeholder} 100 |
    101 | )} 102 |
    103 | 104 |
    108 | + Add task 109 |
    110 |
    111 | )} 112 |
    113 |
    114 | 115 | {taskProjectFormModal} 116 | 117 |
    118 |
    119 | ); 120 | }; 121 | 122 | export default TaskListItem; 123 | -------------------------------------------------------------------------------- /client/src/components/tasks/TaskSection.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from "react"; 2 | import { BiRightArrow } from "react-icons/bi"; 3 | import { Context as TaskContext } from "../../context/store/TaskStore"; 4 | import "../../css/Task.css"; 5 | import TaskItemTask from "./TaskItemTask"; 6 | const TaskSection = ({ title, tasks }) => { 7 | const [open, setOpen] = useState(true); 8 | const [taskState] = useContext(TaskContext); 9 | 10 | const toggle = () => { 11 | setOpen(!open); 12 | let arrow = document.getElementById(`task-collapse-${title}`); 13 | arrow.classList.toggle("open-arrow-collapse"); 14 | arrow.classList.toggle("open-arrow"); 15 | }; 16 | const taskList = tasks.map((task, i) => { 17 | return ; 18 | }); 19 | return ( 20 |
    21 |
    22 |
    23 | 24 |
    25 |

    {title}

    26 |
    27 | {open &&
    {taskList}
    } 28 |
    29 | ); 30 | }; 31 | 32 | export default TaskSection; 33 | -------------------------------------------------------------------------------- /client/src/components/teams/NewTeamMemberIcon.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import "../../css/Navbar.css"; 3 | import { FiPlus } from "react-icons/fi"; 4 | import { Modal } from "@material-ui/core"; 5 | import AddMemberForm from "../Forms/AddMemberForm"; 6 | 7 | const NewTeamMemberIcon = ({ setTeamUsers, teamId }) => { 8 | const [open, setOpen] = useState(false); 9 | const openModal = () => { 10 | setOpen(true); 11 | }; 12 | 13 | const closeModal = () => { 14 | setOpen(false); 15 | }; 16 | const modalBody = ( 17 |
    18 | 24 |
    25 | ); 26 | return ( 27 |
    28 |
    33 |
    34 |
    35 | 36 |
    37 |
    38 |
    39 |
    Add Member
    40 |
    41 |
    42 | 43 | {modalBody} 44 | 45 |
    46 | ); 47 | }; 48 | 49 | export default NewTeamMemberIcon; 50 | -------------------------------------------------------------------------------- /client/src/components/teams/TeamMemberIcon.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import UserAvatar from "../NavigationBar/UserAvatar"; 3 | 4 | const TeamMemberIcon = ({ user }) => { 5 | return ( 6 |
    7 |
    8 | 9 |
    10 |
    11 |
    {user.name}
    12 |
    {user.email}
    13 |
    14 |
    15 | ); 16 | }; 17 | 18 | export default TeamMemberIcon; 19 | -------------------------------------------------------------------------------- /client/src/config/apiServer.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { backendUrl } from "./index"; 3 | 4 | const instance = axios.create({ 5 | // baseURL: backendUrl, 6 | baseURL: "https://methodic-backend.herokuapp.com/", 7 | }); 8 | 9 | instance.interceptors.request.use( 10 | (config) => { 11 | const token = localStorage.getItem("token"); 12 | if (token) { 13 | config.headers.Authorization = `Bearer ${token}`; 14 | } 15 | return config; 16 | }, 17 | 18 | //Error 19 | (err) => { 20 | return err.message; 21 | } 22 | ); 23 | 24 | export default instance; 25 | -------------------------------------------------------------------------------- /client/src/config/index.js: -------------------------------------------------------------------------------- 1 | export const environment = process.env.NODE_ENV || "development"; 2 | export const backendUrl = 3 | // "https://methodic-backend.herokuapp.com/"; 4 | process.env.NODE_ENV === "production" 5 | ? `https://methodic-backend.herokuapp.com/` 6 | : "http://localhost:8080/"; 7 | -------------------------------------------------------------------------------- /client/src/context/AuthContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from "react"; 2 | const AuthContext = createContext(); 3 | export default AuthContext; 4 | -------------------------------------------------------------------------------- /client/src/context/createDataContext.js: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from "react"; 2 | 3 | export default (reducer, actions, initalState) => { 4 | const Context = React.createContext(); 5 | 6 | const Provider = ({ children }) => { 7 | const [state, dispatch] = useReducer(reducer, initalState); 8 | //actions == {addBlogPost: (dispatch)=>{ return ()=>{} } } 9 | const boundActions = {}; 10 | for (let key in actions) { 11 | boundActions[key] = actions[key](dispatch); 12 | } 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | }; 19 | return { Context, Provider }; 20 | }; 21 | -------------------------------------------------------------------------------- /client/src/context/reducer/ProjectReducer.js: -------------------------------------------------------------------------------- 1 | const Reducer = (state, action) => { 2 | switch (action.type) { 3 | case "get_user_projects": 4 | return { ...state, projects: action.payload }; 5 | case "update_user_projects": 6 | return { ...state, projects: action.payload }; 7 | case "get_project": 8 | return { ...state, userProject: action.payload }; 9 | case "update_project": 10 | return { ...state, userProject: action.payload }; 11 | 12 | default: 13 | return state; 14 | } 15 | }; 16 | 17 | export default Reducer; 18 | -------------------------------------------------------------------------------- /client/src/context/reducer/TaskReducer.js: -------------------------------------------------------------------------------- 1 | const Reducer = (state, action) => { 2 | switch (action.type) { 3 | case "get_user_tasks": 4 | return { ...state, tasks: action.payload }; 5 | case "update_task": 6 | return { ...state, tasks: action.payload }; 7 | case "get_selected_task": 8 | return { ...state, selectedTask: action.payload }; 9 | default: 10 | return state; 11 | } 12 | }; 13 | 14 | export default Reducer; 15 | -------------------------------------------------------------------------------- /client/src/context/reducer/TasklistReducer.js: -------------------------------------------------------------------------------- 1 | const Reducer = (state, action) => { 2 | switch (action.type) { 3 | case "get_project_tasklists": 4 | return { ...state, tasklists: action.payload }; 5 | case "update_project_tasklists": 6 | return { ...state, tasklists: action.payload }; 7 | case "get_selected_tasklist": 8 | return { ...state, selectedTasklist: action.payload }; 9 | default: 10 | return state; 11 | } 12 | }; 13 | 14 | export default Reducer; 15 | -------------------------------------------------------------------------------- /client/src/context/reducer/TeamReducer.js: -------------------------------------------------------------------------------- 1 | const Reducer = (state, action) => { 2 | switch (action.type) { 3 | case "get_user_teams": 4 | return { 5 | ...state, 6 | teams: action.payload, 7 | }; 8 | case "update_user_teams": 9 | return { ...state, teams: action.payload }; 10 | 11 | default: 12 | return state; 13 | } 14 | }; 15 | 16 | export default Reducer; 17 | -------------------------------------------------------------------------------- /client/src/context/reducer/UserReducer.js: -------------------------------------------------------------------------------- 1 | const Reducer = (state, action) => { 2 | switch (action.type) { 3 | case "get_user_info": 4 | return { user: action.payload }; 5 | 6 | default: 7 | return state; 8 | } 9 | }; 10 | 11 | export default Reducer; 12 | -------------------------------------------------------------------------------- /client/src/context/store/ProjectStore.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useReducer } from "react"; 2 | import Reducer from "../reducer/ProjectReducer"; 3 | 4 | const initialState = { 5 | projects: [], 6 | }; 7 | 8 | const ProjectStore = ({ children }) => { 9 | const [projectState, projectdispatch] = useReducer(Reducer, initialState); 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export const Context = createContext(initialState); 18 | export default ProjectStore; 19 | -------------------------------------------------------------------------------- /client/src/context/store/TaskStore.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useReducer } from "react"; 2 | import Reducer from "../reducer/TaskReducer"; 3 | 4 | const initialState = { 5 | tasks: [], 6 | }; 7 | 8 | const TaskStore = ({ children }) => { 9 | const [taskState, taskdispatch] = useReducer(Reducer, initialState); 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export const Context = createContext(initialState); 18 | export default TaskStore; 19 | -------------------------------------------------------------------------------- /client/src/context/store/TasklistStore.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useReducer } from "react"; 2 | import Reducer from "../reducer/TasklistReducer"; 3 | 4 | const initialState = { 5 | tasklists: [], 6 | }; 7 | 8 | const TasklistStore = ({ children }) => { 9 | const [tasklistState, tasklistdispatch] = useReducer(Reducer, initialState); 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export const Context = createContext(initialState); 18 | export default TasklistStore; 19 | -------------------------------------------------------------------------------- /client/src/context/store/TeamStore.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useReducer } from "react"; 2 | import Reducer from "../reducer/TeamReducer"; 3 | 4 | const initialState = { 5 | teams: [], 6 | }; 7 | 8 | const TeamStore = ({ children }) => { 9 | const [teamState, teamdispatch] = useReducer(Reducer, initialState); 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export const Context = createContext(initialState); 18 | export default TeamStore; 19 | -------------------------------------------------------------------------------- /client/src/context/store/UserStore.js: -------------------------------------------------------------------------------- 1 | import React, { createContext, useReducer } from "react"; 2 | import Reducer from "../reducer/UserReducer"; 3 | 4 | const initialState = { 5 | user: [], 6 | }; 7 | 8 | const UserStore = ({ children }) => { 9 | const [userState, userdispatch] = useReducer(Reducer, initialState); 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | }; 16 | 17 | export const Context = createContext(initialState); 18 | export default UserStore; 19 | -------------------------------------------------------------------------------- /client/src/css/Forms.css: -------------------------------------------------------------------------------- 1 | .form-container { 2 | width: 100%; 3 | } 4 | 5 | .form-header { 6 | margin: 0; 7 | } 8 | 9 | .textarea { 10 | box-sizing: border-box; 11 | resize: none; 12 | white-space: pre-wrap; 13 | word-wrap: break-word; 14 | overflow: hidden; 15 | outline: none; 16 | } 17 | .form-top-container { 18 | display: flex; 19 | justify-content: space-between; 20 | align-items: center; 21 | /* flex-direction: column; */ 22 | } 23 | 24 | .form-section { 25 | flex-direction: column; 26 | margin-bottom: 48px; 27 | margin-top: 32px; 28 | display: flex; 29 | flex: 1 1 auto; 30 | min-width: 1px; 31 | margin-right: 10px; 32 | } 33 | .input-section { 34 | margin: 10px; 35 | } 36 | .label-container { 37 | box-sizing: border-box; 38 | cursor: default; 39 | display: inline-flex; 40 | flex: 0 0 auto; 41 | margin-bottom: 8px; 42 | } 43 | 44 | .input-container { 45 | display: flex; 46 | flex: 1 1 auto; 47 | flex-direction: column; 48 | min-width: 1px; 49 | } 50 | 51 | .form-input { 52 | /* padding: 10px 5px; */ 53 | /* width: 100%; */ 54 | /* border: none; 55 | outline: none; 56 | text-overflow: ellipsis; */ 57 | background-color: #f6f8f9; 58 | border-color: #cbd4db; 59 | border-radius: 0; 60 | border-style: solid; 61 | border-width: 0 0 1px; 62 | font-size: 16px; 63 | outline: none; 64 | padding-left: 4px; 65 | height: 36px; 66 | padding-bottom: 4px; 67 | padding-top: 4px; 68 | } 69 | 70 | .form-input:hover, 71 | .form-input:focus { 72 | border: 1px solid lightgray; 73 | } 74 | .name-textarea { 75 | line-height: 32px; 76 | height: 42px; 77 | font-weight: 500; 78 | font-size: 24px; 79 | width: 100%; 80 | padding: 4px 0px 0px 0px; 81 | outline: none; 82 | border-radius: 5px; 83 | border: 1px solid #c5cad1; 84 | } 85 | 86 | .form-label { 87 | /* display: flex; 88 | flex-direction: column; */ 89 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, 90 | "Helvetica Neue", Helvetica, Arial, sans-serif; 91 | font-size: 12px; 92 | line-height: 16px; 93 | color: #6f7782; 94 | display: inline-block; 95 | font-weight: 500; 96 | margin-right: 8px; 97 | } 98 | 99 | .error-message { 100 | color: red; 101 | margin: 1px; 102 | font-size: 13px; 103 | width: 200px; 104 | } 105 | 106 | .form-button-container { 107 | display: flex; 108 | justify-content: flex-end; 109 | } 110 | 111 | .cancel-button { 112 | align-items: center; 113 | border: 1px solid; 114 | border-radius: 6px; 115 | box-sizing: border-box; 116 | display: inline-flex; 117 | flex-shrink: 0; 118 | justify-content: center; 119 | overflow: hidden; 120 | transition-duration: 0.2s; 121 | -webkit-user-select: none; 122 | -moz-user-select: none; 123 | -ms-user-select: none; 124 | user-select: none; 125 | font-size: 13px; 126 | height: 36px; 127 | line-height: 36px; 128 | padding: 0 12px; 129 | background: #f6f8f9; 130 | border-color: #e8ecee; 131 | color: #9ca6af; 132 | fill: #9ca6af; 133 | margin-right: 15px; 134 | cursor: pointer; 135 | } 136 | .submit-button.disabled { 137 | align-items: center; 138 | border: 1px solid; 139 | border-radius: 6px; 140 | box-sizing: border-box; 141 | display: inline-flex; 142 | flex-shrink: 0; 143 | justify-content: center; 144 | overflow: hidden; 145 | transition-duration: 0.2s; 146 | /* transition-property: background,border,box-shadow,color,fill; */ 147 | -webkit-user-select: none; 148 | -moz-user-select: none; 149 | -ms-user-select: none; 150 | user-select: none; 151 | font-size: 13px; 152 | height: 36px; 153 | line-height: 36px; 154 | padding: 0 12px; 155 | background: #f6f8f9; 156 | border-color: #e8ecee; 157 | color: #9ca6af; 158 | fill: #9ca6af; 159 | } 160 | .submit-button.enabled { 161 | align-items: center; 162 | border: 1px solid; 163 | border-radius: 6px; 164 | box-sizing: border-box; 165 | display: inline-flex; 166 | flex-shrink: 0; 167 | justify-content: center; 168 | overflow: hidden; 169 | transition-duration: 0.2s; 170 | /* transition-property: background,border,box-shadow,color,fill; */ 171 | -webkit-user-select: none; 172 | -moz-user-select: none; 173 | -ms-user-select: none; 174 | user-select: none; 175 | font-size: 13px; 176 | height: 36px; 177 | line-height: 36px; 178 | padding: 0 12px; 179 | background: #14aaf5; 180 | border-color: #14aaf5; 181 | color: #fff; 182 | fill: #fff; 183 | cursor: pointer; 184 | } 185 | -------------------------------------------------------------------------------- /client/src/css/Home.css: -------------------------------------------------------------------------------- 1 | .home-welcome-header { 2 | display: flex; 3 | /* align-items: center; */ 4 | /* margin-left: 269px; */ 5 | margin-top: 10px; 6 | flex-direction: column; 7 | align-self: center; 8 | width: 77%; 9 | } 10 | 11 | .home-welcome-message { 12 | color: #151b26; 13 | font-weight: 500; 14 | font-size: 24px; 15 | margin: 0; 16 | display: flex; 17 | align-self: center; 18 | } 19 | .home-tasks-container { 20 | width: 25%; 21 | height: 100%; 22 | /* margin: 32px auto; */ 23 | margin-top: 32px; 24 | transition-duration: 1s; 25 | flex-grow: 1; 26 | } 27 | .home-tasks-container--small { 28 | width: 65%; 29 | height: 100%; 30 | /* margin: 32px auto; */ 31 | margin-top: 32px; 32 | transition-duration: 1s; 33 | flex-grow: 1; 34 | } 35 | .home-tasks-container--smaller { 36 | width: revert; 37 | height: 100%; 38 | /* margin: 32px auto; */ 39 | margin-top: 32px; 40 | transition-duration: 1s; 41 | } 42 | 43 | .home-projects-container { 44 | width: 30%; 45 | /* height: 175px; */ 46 | height: 60%; 47 | /* margin: 32px auto; */ 48 | /* margin: 32px; */ 49 | margin-top: 32px; 50 | transition-duration: 1s; 51 | flex-grow: 1; 52 | } 53 | 54 | .home-projects-container--small { 55 | width: 30%; 56 | /* height: 175px; */ 57 | height: 60%; 58 | /* margin: 32px auto; */ 59 | /* margin: 32px; */ 60 | margin-top: 32px; 61 | transition-duration: 1s; 62 | flex-grow: 1; 63 | } 64 | .home-projects-container--smaller { 65 | width: 30%; 66 | /* height: 175px; */ 67 | height: 60%; 68 | /* margin: 32px auto; */ 69 | margin-left: 87px; 70 | margin-top: 32px; 71 | transition-duration: 1s; 72 | } 73 | 74 | .home-main-container { 75 | display: flex; 76 | flex-direction: column; 77 | width: 100%; 78 | /* align-items: center; */ 79 | } 80 | .home-main-content-container { 81 | display: flex; 82 | flex-direction: row; 83 | height: 100%; 84 | width: 100%; 85 | align-self: center; 86 | } 87 | 88 | .home-tasks-header, 89 | .home-projects-header { 90 | display: flex; 91 | justify-content: space-between; 92 | align-items: center; 93 | border-bottom: 2px solid #e8ecee; 94 | height: 32px; 95 | margin-left: 24px; 96 | margin-right: 24px; 97 | padding: 8px 0; 98 | margin-bottom: 9px; 99 | } 100 | 101 | .home-projects--list { 102 | display: flex; 103 | flex-flow: row wrap; 104 | padding-top: 20px; 105 | margin: 0px 25px; 106 | width: 100%; 107 | } 108 | 109 | .home-tasks--list { 110 | /* border: 2px solid #e8ecee; */ 111 | border-radius: 5px; 112 | /* padding-top: 20px; */ 113 | /* overflow: hidden; */ 114 | margin: 0px 25px; 115 | display: flex; 116 | flex-direction: column; 117 | flex-wrap: wrap; 118 | height: 530px; 119 | padding-top: 39px; 120 | width: 100%; 121 | } 122 | 123 | .home-container { 124 | /* margin: 20px 120px; */ 125 | height: 100%; 126 | /* overflow-y: scroll; */ 127 | /* width: 900px; */ 128 | display: flex; 129 | /* margin: 0 auto; */ 130 | justify-content: center; 131 | overflow-y: scroll; 132 | } 133 | .home-container::-webkit-scrollbar { 134 | display: none; 135 | } 136 | 137 | /* Home page New project/task item */ 138 | .new-home-item-container { 139 | display: flex; 140 | border: dashed #cbd4db; 141 | background: transparent; 142 | font-size: 13px; 143 | width: 300px; 144 | height: 75px; 145 | padding: 5px; 146 | margin-bottom: 15px; 147 | /* box-shadow: 2px 2px 10px 2px rgba(0,0,0,0.3); */ 148 | border-radius: 10px; 149 | cursor: pointer; 150 | align-items: center; 151 | } 152 | .new-home-item-container:hover { 153 | box-shadow: 2px 2px 10px 2px rgba(0, 0, 0, 0.3); 154 | background-color: rgba(133, 140, 155, 0.08); 155 | } 156 | .new-home-icon-container { 157 | border-radius: 20px; 158 | height: 100%; 159 | opacity: 0.5; 160 | align-items: center; 161 | background: transparent; 162 | display: flex; 163 | justify-content: center; 164 | } 165 | .new-home-item-name { 166 | padding-left: 25px; 167 | font-weight: 700; 168 | opacity: 0.5; 169 | } 170 | .new-home-item-container:hover .new-home-item-name { 171 | opacity: 1; 172 | } 173 | 174 | .new-home-item-container:hover .new-home-icon-container { 175 | opacity: 1; 176 | } 177 | 178 | .new-home-item-icon { 179 | color: blue; 180 | } 181 | 182 | @media only screen and (max-width: 1240px) { 183 | .home-projects-container--small { 184 | overflow: auto; 185 | } 186 | .home-tasks-container--small { 187 | overflow: auto; 188 | } 189 | 190 | .home-tasks--list { 191 | flex-wrap: nowrap; 192 | } 193 | .home-tasks-container--smaller { 194 | width: auto; 195 | } 196 | } 197 | @media only screen and (max-width: 1400px) { 198 | .home-projects-container--smaller { 199 | margin-left: revert; 200 | overflow: scroll; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /client/src/css/LandingPage.css: -------------------------------------------------------------------------------- 1 | .landing-container { 2 | display: flex; 3 | flex-flow: column; 4 | width: 100%; 5 | } 6 | .landing-nav-container { 7 | display: flex; 8 | flex-direction: row; 9 | align-items: center; 10 | 11 | justify-content: space-between; 12 | margin: 10px 15px 0px; 13 | } 14 | .landing-nav-sessions { 15 | display: flex; 16 | justify-content: space-between; 17 | margin-right: 50px; 18 | } 19 | .landing-nav-register--button { 20 | margin: 0 7px; 21 | background-color: #0093ff; 22 | color: white; 23 | padding: 3px 30px; 24 | outline: none; 25 | text-decoration: none; 26 | border: 2px solid transparent; 27 | cursor: pointer; 28 | border-radius: 2px; 29 | font-weight: 500; 30 | font-size: 1.17em; 31 | } 32 | .landing-nav-register--button:hover { 33 | border: 2px solid #0093ff; 34 | margin: 0 7px; 35 | background: none; 36 | } 37 | .landing-nav-login--button { 38 | margin: 0 7px; 39 | color: white; 40 | padding: 3px 43px; 41 | background: none; 42 | outline: none; 43 | text-decoration: none; 44 | border: 2px solid transparent; 45 | cursor: pointer; 46 | border-radius: 4px; 47 | font-weight: 500; 48 | font-size: 1.17em; 49 | } 50 | .landing-nav-login--button:hover { 51 | border: 2px solid #0093ff; 52 | background-color: #0093ff; 53 | margin: 0 7px; 54 | } 55 | .landing-main { 56 | display: flex; 57 | justify-content: center; 58 | flex-direction: column; 59 | align-items: center; 60 | } 61 | .landing-message { 62 | /* max-width: 800px; */ 63 | width: 60%; 64 | } 65 | .landing-message-button--div { 66 | display: flex; 67 | margin-top: 25px; 68 | /* justify-content: center; */ 69 | } 70 | 71 | .landing-message--button { 72 | border-radius: 4px; 73 | cursor: pointer; 74 | text-align: center; 75 | padding: 10px 20px; 76 | background-color: #0093ff; 77 | color: white; 78 | outline: none; 79 | border: none; 80 | 81 | font-size: 1.17em; 82 | } 83 | 84 | .landing-picture { 85 | display: block; 86 | margin: 50px auto 75px auto; 87 | /* width: 80%; */ 88 | max-width: 900px; 89 | box-shadow: 2px 2px 10px rgba(0, 0, 0, 0.2); 90 | } 91 | .landing-main-bottom { 92 | display: flex; 93 | flex-direction: column; 94 | align-items: center; 95 | } 96 | .landing-main-bottom-icons-container { 97 | display: flex; 98 | justify-content: space-between; 99 | align-items: center; 100 | /* width: 600px; */ 101 | width: 50%; 102 | margin: 0 auto 50px auto; 103 | } 104 | .icon-container { 105 | display: flex; 106 | flex-direction: column; 107 | align-items: center; 108 | /* width: 33%; */ 109 | } 110 | 111 | .icon-container p { 112 | line-height: 1.4; 113 | text-align: center; 114 | color: #606060; 115 | font-size: 1.1em; 116 | } 117 | /* //color: "rgb(59, 182, 170)" */ 118 | 119 | .guest-login-button { 120 | margin: 50px 0; 121 | } 122 | -------------------------------------------------------------------------------- /client/src/css/LoginPage.css: -------------------------------------------------------------------------------- 1 | .login-page-container, 2 | .register-page-container, 3 | .onboard-page-container { 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | } 8 | 9 | .login-page-header, 10 | .register-page-header, 11 | .onboard-page-header { 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | } 16 | 17 | .login-page--form, 18 | .register-page--form, 19 | .onboard-page--form { 20 | width: 100%; 21 | display: flex; 22 | flex-direction: column; 23 | padding: 20px; 24 | max-width: 310px; 25 | border: 1px solid lightgrey; 26 | border-radius: 5px; 27 | } 28 | 29 | .login-page--form div, 30 | .register-page--form div, 31 | .onboard-page--form div { 32 | display: flex; 33 | flex-direction: column; 34 | padding: 5px 0px; 35 | } 36 | .login-page--form label, 37 | .register-page--form label, 38 | .onboard-page--form label { 39 | padding-bottom: 5px; 40 | font-weight: 500; 41 | } 42 | .login-page--form input, 43 | .register-page--form input, 44 | .onboard-page--form input { 45 | height: 30px; 46 | border-radius: 5px; 47 | border: 1px solid lightgrey; 48 | } 49 | .login-page--form button, 50 | .register-page--form button, 51 | .onboard-page--form button { 52 | height: 30px; 53 | margin: 10px 0px; 54 | border-radius: 5px; 55 | border: none; 56 | background-color: #0093ff; 57 | outline: none; 58 | cursor: pointer; 59 | color: white; 60 | } 61 | .login-page--form button:hover, 62 | .register-page--form button:hover, 63 | .onboard-page--form button:hover { 64 | background-color: #0053ff; 65 | } 66 | 67 | .register-container, 68 | .login-container { 69 | padding-top: 10px; 70 | font-size: 15px; 71 | } 72 | 73 | .onboard-page-button-container { 74 | display: flex; 75 | flex-direction: row; 76 | } 77 | -------------------------------------------------------------------------------- /client/src/css/Modal.css: -------------------------------------------------------------------------------- 1 | .modal-container { 2 | position: absolute; 3 | 4 | background-color: white; 5 | box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 6 | 0px 5px 8px 0px rgba(0, 0, 0, 0.14), 0px 1px 14px 0px rgba(0, 0, 0, 0.12); 7 | padding: 16px 32px 24px; 8 | top: 50%; 9 | left: 50%; 10 | transform: translate(-50%, -50%); 11 | outline: none; 12 | border-radius: 4px; 13 | } 14 | 15 | .tasklist-modal-container { 16 | position: absolute; 17 | 18 | background-color: white; 19 | box-shadow: 0px 3px 5px -1px rgba(0, 0, 0, 0.2), 20 | 0px 5px 8px 0px rgba(0, 0, 0, 0.14), 0px 1px 14px 0px rgba(0, 0, 0, 0.12); 21 | padding: 16px 32px 24px; 22 | top: 50%; 23 | left: 50%; 24 | transform: translate(-50%, -50%); 25 | outline: none; 26 | border-radius: 4px; 27 | } 28 | /* [role="presentation"] { 29 | background-color: white !important; 30 | } */ 31 | -------------------------------------------------------------------------------- /client/src/css/PopOutForms.css: -------------------------------------------------------------------------------- 1 | .popout-form { 2 | background-color: white; 3 | width: 60%; 4 | height: 100%; 5 | display: flex; 6 | box-shadow: 0 1px 4px 0 rgba(21, 27, 38, 0.08); 7 | border: 2px solid #e8ecee; 8 | padding: 10px; 9 | margin: 10px; 10 | } 11 | 12 | .popout-form-container { 13 | width: 100%; 14 | height: 100%; 15 | display: flex; 16 | flex-direction: column; 17 | flex: 1 1 auto; 18 | min-height: 1px; 19 | } 20 | 21 | .popout-form-top { 22 | display: flex; 23 | justify-content: space-between; 24 | border-bottom: 1px solid lightgray; 25 | padding-bottom: 10px; 26 | } 27 | 28 | .popout-form-close-icon { 29 | } 30 | -------------------------------------------------------------------------------- /client/src/css/Project.css: -------------------------------------------------------------------------------- 1 | .project-tile-container { 2 | border-radius: 30px; 3 | box-sizing: border-box; 4 | height: 170px; 5 | min-width: 152px; 6 | padding: 16px 0; 7 | width: 152px; 8 | align-items: center; 9 | /* background: rgba(246, 248, 249, 0); */ 10 | display: flex; 11 | flex-flow: column; 12 | position: relative; 13 | transition-duration: 0.4s; 14 | cursor: pointer; 15 | } 16 | .project-tile-icon-1 { 17 | background: lightblue; 18 | } 19 | .project-tile-icon-2 { 20 | background: yellowgreen; 21 | } 22 | .project-tile-icon-3 { 23 | background: red; 24 | } 25 | .project-tile-icon-4 { 26 | background: salmon; 27 | } 28 | .project-tile-icon-5 { 29 | background: seagreen; 30 | } 31 | .project-tile-icon-6 { 32 | background: darkcyan; 33 | } 34 | .project-tile-icon-7 { 35 | background: slateblue; 36 | } 37 | .project-tile-icon-8 { 38 | background: skyblue; 39 | } 40 | .project-tile-icon-9 { 41 | background: lightgoldenrodyellow; 42 | } 43 | .project-tile-icon-10 { 44 | background: lawngreen; 45 | } 46 | .project-tile-icon-11 { 47 | background: lightblue; 48 | } 49 | .project-tile-icon-12 { 50 | background: yellowgreen; 51 | } 52 | .project-tile-icon-13 { 53 | background: red; 54 | } 55 | .project-tile-icon-14 { 56 | background: salmon; 57 | } 58 | .project-tile-icon-15 { 59 | background: seagreen; 60 | } 61 | .project-tile-icon-16 { 62 | background: darkcyan; 63 | } 64 | .project-tile-icon-17 { 65 | background: slateblue; 66 | } 67 | .project-tile-icon-18 { 68 | background: skyblue; 69 | } 70 | .project-tile-icon-19 { 71 | background: lightgoldenrodyellow; 72 | } 73 | .project-tile-icon-20 { 74 | background: lawngreen; 75 | } 76 | .project-tile-icon-21 { 77 | background: lightblue; 78 | } 79 | .project-tile-icon-22 { 80 | background: yellowgreen; 81 | } 82 | .project-tile-icon-23 { 83 | background: red; 84 | } 85 | .project-tile-icon-24 { 86 | background: salmon; 87 | } 88 | .project-tile-icon-25 { 89 | background: seagreen; 90 | } 91 | .project-tile-icon-26 { 92 | background: darkcyan; 93 | } 94 | .project-tile-icon-27 { 95 | background: slateblue; 96 | } 97 | .project-tile-icon-28 { 98 | background: skyblue; 99 | } 100 | .project-tile-icon-29 { 101 | background: lightgoldenrodyellow; 102 | } 103 | .project-tile-icon-30 { 104 | background: lawngreen; 105 | } 106 | .project-tile-icon-31 { 107 | background: lightblue; 108 | } 109 | .project-tile-icon-32 { 110 | background: yellowgreen; 111 | } 112 | .project-tile-icon-33 { 113 | background: red; 114 | } 115 | .project-tile-icon-34 { 116 | background: salmon; 117 | } 118 | .project-tile-icon-35 { 119 | background: seagreen; 120 | } 121 | .project-tile-icon-36 { 122 | background: darkcyan; 123 | } 124 | .project-tile-icon-37 { 125 | background: slateblue; 126 | } 127 | .project-tile-icon-38 { 128 | background: skyblue; 129 | } 130 | .project-tile-icon-39 { 131 | background: lightgoldenrodyellow; 132 | } 133 | .project-tile-icon-40 { 134 | background: lawngreen; 135 | } 136 | .project-tile-container:hover { 137 | padding: 0px 0px; 138 | } 139 | 140 | .project-tile-box { 141 | height: 100px; 142 | width: 100px; 143 | } 144 | 145 | .project-tile--link { 146 | text-decoration: none; 147 | height: 25%; 148 | } 149 | 150 | .project-tile--link:visited { 151 | color: inherit; 152 | } 153 | 154 | .project-tile-icon { 155 | border-radius: 20px; 156 | height: 100%; 157 | width: 100%; 158 | box-shadow: inset 0 -2px rgba(0, 0, 0, 0.05); 159 | align-items: center; 160 | 161 | display: flex; 162 | fill: #fff; 163 | flex-direction: column; 164 | justify-content: center; 165 | flex-flow: column; 166 | position: relative; 167 | } 168 | 169 | .new-project-tile-icon-container { 170 | border-radius: 20px; 171 | height: 100%; 172 | width: 100%; 173 | border: dashed #cbd4db; 174 | align-items: center; 175 | background: transparent; 176 | display: flex; 177 | flex-direction: column; 178 | justify-content: center; 179 | } 180 | 181 | .project-tile-container:hover .new-project-tile-icon-container { 182 | border: dashed #91979c; 183 | } 184 | 185 | .new-project-tile-icon { 186 | font-size: 40px; 187 | color: #cbd4db; 188 | } 189 | 190 | .project-tile-container:hover .new-project-tile-icon { 191 | color: #91979c; 192 | } 193 | 194 | .project-tile-name { 195 | padding-top: 10px; 196 | color: black; 197 | font-size: 15px; 198 | } 199 | 200 | .project-home-item { 201 | /* display: flex; 202 | justify-content: space-between; */ 203 | border-bottom: 1px solid #e8ecee; 204 | background-color: white; 205 | /* font-size: 13px; */ 206 | width: 300px; 207 | height: 75px; 208 | padding: 5px; 209 | margin-bottom: 15px; 210 | box-shadow: 2px 2px 6px 3px rgba(0, 0, 0, 0.3); 211 | border-radius: 10px; 212 | cursor: pointer; 213 | align-items: center; 214 | -webkit-user-select: none; 215 | -moz-user-select: none; 216 | -ms-user-select: none; 217 | user-select: none; 218 | } 219 | .project-home-item:hover { 220 | background-color: rgba(133, 140, 155, 0.08); 221 | } 222 | 223 | .project-home-item-name-container { 224 | display: flex; 225 | flex-direction: column; 226 | width: 150px; 227 | text-overflow: ellipsis; 228 | margin-left: 25px; 229 | } 230 | 231 | .project-home-item-inner-container { 232 | display: flex; 233 | align-items: center; 234 | justify-content: space-around; 235 | height: 100%; 236 | } 237 | 238 | .project-home-item-inner-left { 239 | display: flex; 240 | justify-content: space-evenly; 241 | align-items: center; 242 | height: 100%; 243 | width: 500px; 244 | } 245 | 246 | .project-page-container { 247 | display: flex; 248 | padding: 20px 15px 15px 15px; 249 | align-items: center; 250 | box-sizing: border-box; 251 | height: 100%; 252 | } 253 | .project-page-main-content { 254 | display: contents; 255 | } 256 | 257 | .project-container { 258 | display: flex; 259 | /* padding: 15px; */ 260 | /* margin: 30px 15px; */ 261 | height: 100%; 262 | width: 100%; 263 | overflow: scroll; 264 | } 265 | -------------------------------------------------------------------------------- /client/src/css/TaskList.css: -------------------------------------------------------------------------------- 1 | .tasklist-container { 2 | display: flex; 3 | flex-direction: column; 4 | width: 300px; 5 | /* width: 270px; */ 6 | margin: 0px 10px; 7 | padding: 0px 10px; 8 | align-items: center; 9 | /* background-color: #ebecf0; */ 10 | background-color: white; 11 | border-radius: 10px; 12 | height: fit-content; 13 | } 14 | 15 | .tasklist-header { 16 | display: flex; 17 | margin: 10px 0px; 18 | width: inherit; 19 | } 20 | .tasklist-title { 21 | display: flex; 22 | font-size: 16px; 23 | font-weight: 500; 24 | width: inherit; 25 | align-content: center; 26 | justify-content: center; 27 | } 28 | .tasklist-title__textarea { 29 | resize: none; 30 | font-size: 16px; 31 | line-height: 16px; 32 | height: 18px; 33 | outline: none; 34 | border: 1px solid lightgray; 35 | } 36 | 37 | .tasklist-more-menu { 38 | display: flex; 39 | cursor: pointer; 40 | } 41 | .tasklist-task--list { 42 | width: 290px; 43 | min-height: 25px; 44 | /* width: 260px; */ 45 | display: flex; 46 | flex-direction: column; 47 | } 48 | 49 | .tasklist-new-task--button { 50 | height: 30px; 51 | display: flex; 52 | align-items: center; 53 | justify-content: center; 54 | background: white; 55 | padding: 15px; 56 | font-size: 15px; 57 | margin: 10px 0; 58 | box-shadow: 0 1px 3px 0 rgba(21, 27, 38, 0.15); 59 | box-sizing: border-box; 60 | cursor: pointer; 61 | border-radius: 4px; 62 | transition: top ease 0.2s; 63 | } 64 | 65 | .tasklist-new-task--button:hover { 66 | background: #e8ecee; 67 | } 68 | 69 | .tasklist-new-tasklist--button { 70 | display: flex; 71 | width: 50px; 72 | margin: 0px 10px; 73 | background-color: white; 74 | border-radius: 10px; 75 | justify-content: center; 76 | align-items: flex-start; 77 | height: 100%; 78 | cursor: pointer; 79 | } 80 | 81 | .tasklist-new-tasklist--button:hover { 82 | background: #dcdde0; 83 | } 84 | -------------------------------------------------------------------------------- /client/src/css/TeamPage.css: -------------------------------------------------------------------------------- 1 | .team-page-container { 2 | display: flex; 3 | flex: 1 1 auto; 4 | flex-direction: row; 5 | min-height: 1px; 6 | position: relative; 7 | } 8 | 9 | .team-page-content-container { 10 | display: flex; 11 | flex-direction: row; 12 | justify-content: center; 13 | margin: 0 auto; 14 | } 15 | 16 | .team-page-content-left { 17 | width: 258px; 18 | margin-left: 0; 19 | margin-right: 0; 20 | } 21 | 22 | .team-content-left-description-container { 23 | margin: 32px auto; 24 | } 25 | .team-content-left-description-header { 26 | align-items: center; 27 | border-bottom: 2px solid #e8ecee; 28 | display: flex; 29 | height: 32px; 30 | margin-left: 24px; 31 | margin-right: 24px; 32 | padding: 8px 0; 33 | position: relative; 34 | } 35 | .team-content-left-description-form { 36 | padding: 4px; 37 | margin: 16px 20px; 38 | font-size: 13px; 39 | font-weight: 400; 40 | line-height: 20px; 41 | border-width: 1px; 42 | box-sizing: border-box; 43 | border-radius: 6px; 44 | } 45 | 46 | .edit-description { 47 | border-color: transparent; 48 | overflow-wrap: break-word; 49 | white-space: pre-wrap; 50 | outline: none; 51 | background-color: transparent; 52 | min-width: 194px; 53 | min-height: 83px; 54 | resize: none; 55 | font-family: Arial, Helvetica, sans-serif; 56 | color: #838485; 57 | } 58 | .description:focus { 59 | border-color: #cbd4db; 60 | } 61 | .team-content-left-members-container { 62 | margin: 32px auto; 63 | margin-top: 0; 64 | } 65 | .team-content-left-members-header { 66 | align-items: center; 67 | border-bottom: 2px solid #e8ecee; 68 | display: flex; 69 | height: 32px; 70 | margin-left: 24px; 71 | margin-right: 24px; 72 | padding: 8px 0; 73 | position: relative; 74 | justify-content: space-between; 75 | } 76 | .team-content-left-members--list { 77 | margin: 5px 24px; 78 | } 79 | 80 | .team-page-content-right { 81 | margin-left: 0; 82 | margin-right: 0; 83 | margin-top: 32px; 84 | width: 672px; 85 | height: 96%; 86 | } 87 | .team-content-right-header { 88 | align-items: center; 89 | border-bottom: 2px solid #e8ecee; 90 | display: flex; 91 | height: 32px; 92 | margin-left: 24px; 93 | margin-right: 24px; 94 | padding: 8px 0; 95 | position: relative; 96 | } 97 | 98 | .team-content-right-projects--list { 99 | display: flex; 100 | flex-flow: row wrap; 101 | padding-top: 16px; 102 | /* height: 100%; */ 103 | width: 100%; 104 | overflow-y: scroll; 105 | } 106 | .team-content-right-projects--list::-webkit-scrollbar { 107 | display: none; 108 | } 109 | .team-content-title { 110 | box-sizing: border-box; 111 | color: #151b26; 112 | display: flex; 113 | font-size: 16px; 114 | font-weight: 500; 115 | line-height: 20px; 116 | } 117 | 118 | /* Team Member Icon */ 119 | 120 | .team-member-container { 121 | display: flex; 122 | padding: 8px 0; 123 | } 124 | 125 | .team-member-icon { 126 | } 127 | 128 | .team-member-name-container { 129 | display: flex; 130 | flex-direction: column; 131 | justify-content: center; 132 | margin-left: 16px; 133 | font-size: 13px; 134 | } 135 | .team-member-name { 136 | } 137 | .team-member-email { 138 | } 139 | 140 | .team-member-container:hover .new-user-avatar { 141 | border: dashed #91979c; 142 | } 143 | 144 | .team-member-container:hover .new-user-avatar--icon { 145 | color: #91979c; 146 | } 147 | 148 | .new-team-member-name { 149 | color: #9ca6af; 150 | } 151 | .team-member-container:hover .new-team-member-name { 152 | color: #5b5e61; 153 | } 154 | -------------------------------------------------------------------------------- /client/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/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | 6 | ReactDOM.render( 7 | 8 | 9 | , 10 | document.getElementById("root") 11 | ); 12 | 13 | //"start": "node --max_old_space_size=6144 node_modules/.bin/react-scripts start", 14 | // "build": "node --max_old_space_size=6144 node_modules/.bin/react-scripts build", 15 | -------------------------------------------------------------------------------- /client/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 | -------------------------------------------------------------------------------- /server/.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | 'config': path.resolve('config', 'database.js'), 5 | 'models-path': path.resolve('db', 'models'), 6 | 'seeders-path': path.resolve('db', 'seeders'), 7 | 'migrations-path': path.resolve('db', 'migrations') 8 | }; -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | npx dotenv sequelize db:migrate:undo:all 2 | npx dotenv sequelize db:migrate 3 | npx dotenv sequelize db:seed:all 4 | npm start 5 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const morgan = require("morgan"); 3 | const bodyParser = require("body-parser"); 4 | const { environment } = require("./config"); 5 | const cors = require("cors"); 6 | const userRouter = require("./routes/users"); 7 | const taskRouter = require("./routes/tasks"); 8 | const projectRouter = require("./routes/projects"); 9 | const teamRouter = require("./routes/teams"); 10 | const tasklistRouter = require("./routes/tasklists"); 11 | const commentRouter = require("./routes/comments"); 12 | const userteamRouter = require("./routes/userteams"); 13 | const app = express(); 14 | 15 | app.use(bodyParser.json()); 16 | 17 | // Same as bodyParser but built in 18 | // app.use(express.json()) 19 | // app.use(express.urlencoded({extended:true})) 20 | 21 | app.use(morgan("dev")); 22 | app.use(cors({ origin: true })); 23 | 24 | app.use(userRouter); 25 | app.use("./db/routes/task", taskRouter); 26 | app.use("./db/routes/project", projectRouter); 27 | app.use("./db/routes/team", teamRouter); 28 | app.use("./db/routes/tasklist", tasklistRouter); 29 | app.use("./db/routes/comment", commentRouter); 30 | app.use("./db/routes/userteam", userteamRouter); 31 | 32 | app.get("/", (req, res) => { 33 | res.send("

    You're Connected

    "); 34 | }); 35 | 36 | // Catch unhandled requests such as wrong HTTP Method and forward to error handler. 37 | app.use((req, res, next) => { 38 | const err = new Error("The requested resource couldn't be found."); 39 | err.status = 404; 40 | err.errors = ["Could not find string of resource"]; 41 | next(err); 42 | }); 43 | 44 | // Generic error handler. 45 | app.use((err, req, res, next) => { 46 | res.status(err.status || 500); 47 | const isProduction = environment === "production"; 48 | res.json({ 49 | title: err.title || "Server Error", 50 | message: err.message, 51 | errors: err.errors, 52 | stack: isProduction ? null : err.stack, 53 | }); 54 | }); 55 | 56 | module.exports = app; 57 | -------------------------------------------------------------------------------- /server/bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const { port } = require("../config"); 4 | require("dotenv").config(); 5 | const app = require("../app"); 6 | const db = require("../db/models"); 7 | 8 | // Check the database connection before starting the app. 9 | db.sequelize 10 | .authenticate() 11 | .then(() => { 12 | console.log("Database connection success! Sequelize is ready to use..."); 13 | 14 | // Start listening for connections. 15 | app.listen(port, () => console.log(`Listening on port ${port}...`)); 16 | }) 17 | .catch((err) => { 18 | console.log("Database connection failure."); 19 | console.error(err); 20 | }); 21 | -------------------------------------------------------------------------------- /server/config/database.js: -------------------------------------------------------------------------------- 1 | const config = require("./index"); 2 | 3 | const db = config.db; 4 | const username = db.username; 5 | const password = db.password; 6 | const database = db.database; 7 | const host = db.host; 8 | 9 | module.exports = { 10 | development: { 11 | username, 12 | password, 13 | database, 14 | host, 15 | dialect: "postgres", 16 | }, 17 | production: { 18 | dialect: "postgres", 19 | 20 | use_env_variable: "DATABASE_URL", 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /server/config/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | environment: process.env.NODE_ENV || "development", 3 | port: process.env.PORT || 8080, 4 | db: { 5 | username: process.env.DB_USERNAME, 6 | password: process.env.DB_PASSWORD, 7 | database: process.env.DB_DATABASE, 8 | host: process.env.DB_HOST, 9 | }, 10 | jwtConfig: { 11 | secret: process.env.JWT_SECRET, 12 | expiresIn: process.env.JWT_EXPIRES_IN, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /server/db/migrations/20201103190955-create-user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable("Users", { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER, 10 | }, 11 | 12 | email: { 13 | allowNull: false, 14 | type: Sequelize.STRING(50), 15 | unique: true, 16 | }, 17 | hashed_password: { 18 | allowNull: false, 19 | type: Sequelize.STRING.BINARY, 20 | }, 21 | name: { 22 | type: Sequelize.STRING(100), 23 | }, 24 | image: { 25 | type: Sequelize.STRING, 26 | }, 27 | createdAt: { 28 | allowNull: false, 29 | type: Sequelize.DATE, 30 | }, 31 | updatedAt: { 32 | allowNull: false, 33 | type: Sequelize.DATE, 34 | }, 35 | }); 36 | }, 37 | down: async (queryInterface, Sequelize) => { 38 | await queryInterface.dropTable("Users"); 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /server/db/migrations/20201103193359-create-team.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable("Teams", { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER, 10 | }, 11 | name: { 12 | type: Sequelize.STRING(50), 13 | allowNull: false, 14 | }, 15 | description: { 16 | type: Sequelize.TEXT, 17 | }, 18 | createdAt: { 19 | allowNull: false, 20 | type: Sequelize.DATE, 21 | }, 22 | updatedAt: { 23 | allowNull: false, 24 | type: Sequelize.DATE, 25 | }, 26 | }); 27 | }, 28 | down: async (queryInterface, Sequelize) => { 29 | await queryInterface.dropTable("Teams"); 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /server/db/migrations/20201103193644-create-user-team.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable("UserTeams", { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER, 10 | }, 11 | user_id: { 12 | type: Sequelize.INTEGER, 13 | allowNull: false, 14 | references: { model: "Users" }, 15 | }, 16 | team_id: { 17 | type: Sequelize.INTEGER, 18 | allowNull: false, 19 | references: { model: "Teams" }, 20 | }, 21 | createdAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE, 24 | }, 25 | updatedAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | }, 29 | }); 30 | }, 31 | down: async (queryInterface, Sequelize) => { 32 | await queryInterface.dropTable("UserTeams"); 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /server/db/migrations/20201103193900-create-project.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable("Projects", { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER, 10 | }, 11 | name: { 12 | type: Sequelize.STRING(50), 13 | allowNull: false, 14 | }, 15 | 16 | team_id: { 17 | type: Sequelize.INTEGER, 18 | allowNull: false, 19 | references: { model: "Teams" }, 20 | onDelete: "CASCADE", 21 | }, 22 | createdAt: { 23 | allowNull: false, 24 | type: Sequelize.DATE, 25 | }, 26 | updatedAt: { 27 | allowNull: false, 28 | type: Sequelize.DATE, 29 | }, 30 | }); 31 | }, 32 | down: async (queryInterface, Sequelize) => { 33 | await queryInterface.dropTable("Projects"); 34 | }, 35 | }; 36 | -------------------------------------------------------------------------------- /server/db/migrations/20201103193957-create-task-list.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable("TaskLists", { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER, 10 | }, 11 | name: { 12 | type: Sequelize.STRING(100), 13 | allowNull: false, 14 | }, 15 | project_id: { 16 | type: Sequelize.INTEGER, 17 | allowNull: false, 18 | references: { model: "Projects" }, 19 | onDelete: "CASCADE", 20 | }, 21 | column_index: { 22 | type: Sequelize.INTEGER, 23 | autoIncrement: true, 24 | }, 25 | owner_id: { 26 | type: Sequelize.INTEGER, 27 | allowNull: false, 28 | references: { model: "Users" }, 29 | }, 30 | createdAt: { 31 | allowNull: false, 32 | type: Sequelize.DATE, 33 | }, 34 | updatedAt: { 35 | allowNull: false, 36 | type: Sequelize.DATE, 37 | }, 38 | }); 39 | }, 40 | down: async (queryInterface, Sequelize) => { 41 | await queryInterface.dropTable("TaskLists"); 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /server/db/migrations/20201103194246-create-task.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable("Tasks", { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER, 10 | }, 11 | name: { 12 | type: Sequelize.STRING(100), 13 | allowNull: false, 14 | }, 15 | tasklist_id: { 16 | type: Sequelize.INTEGER, 17 | allowNull: false, 18 | references: { model: "TaskLists" }, 19 | onDelete: "CASCADE", 20 | }, 21 | assignee_id: { 22 | type: Sequelize.INTEGER, 23 | allowNull: false, 24 | references: { model: "Users" }, 25 | }, 26 | project_id: { 27 | type: Sequelize.INTEGER, 28 | references: { model: "Projects" }, 29 | }, 30 | task_index: { 31 | type: Sequelize.INTEGER, 32 | autoIncrement: true, 33 | }, 34 | description: { 35 | type: Sequelize.TEXT, 36 | }, 37 | due_date: { 38 | type: Sequelize.DATE, 39 | }, 40 | completed: { 41 | type: Sequelize.BOOLEAN, 42 | allowNull: false, 43 | }, 44 | completed_at: { 45 | type: Sequelize.DATE, 46 | }, 47 | createdAt: { 48 | allowNull: false, 49 | type: Sequelize.DATE, 50 | }, 51 | updatedAt: { 52 | allowNull: false, 53 | type: Sequelize.DATE, 54 | }, 55 | }); 56 | }, 57 | down: async (queryInterface, Sequelize) => { 58 | await queryInterface.dropTable("Tasks"); 59 | }, 60 | }; 61 | -------------------------------------------------------------------------------- /server/db/migrations/20201103194324-create-comment.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable("Comments", { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER, 10 | }, 11 | text: { 12 | type: Sequelize.TEXT, 13 | }, 14 | task_id: { 15 | type: Sequelize.INTEGER, 16 | allowNull: false, 17 | references: { model: "Tasks" }, 18 | onDelete: "CASCADE", 19 | }, 20 | user_id: { 21 | type: Sequelize.INTEGER, 22 | allowNull: false, 23 | references: { model: "Users" }, 24 | }, 25 | createdAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | }, 29 | updatedAt: { 30 | allowNull: false, 31 | type: Sequelize.DATE, 32 | }, 33 | }); 34 | }, 35 | down: async (queryInterface, Sequelize) => { 36 | await queryInterface.dropTable("Comments"); 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /server/db/migrations/20201202005720-create-user-project.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = { 3 | up: async (queryInterface, Sequelize) => { 4 | await queryInterface.createTable("UserProjects", { 5 | id: { 6 | allowNull: false, 7 | autoIncrement: true, 8 | primaryKey: true, 9 | type: Sequelize.INTEGER, 10 | }, 11 | user_id: { 12 | type: Sequelize.INTEGER, 13 | allowNull: false, 14 | references: { model: "Users" }, 15 | }, 16 | project_id: { 17 | type: Sequelize.INTEGER, 18 | allowNull: false, 19 | references: { model: "Projects" }, 20 | }, 21 | createdAt: { 22 | allowNull: false, 23 | type: Sequelize.DATE, 24 | }, 25 | updatedAt: { 26 | allowNull: false, 27 | type: Sequelize.DATE, 28 | }, 29 | }); 30 | }, 31 | down: async (queryInterface, Sequelize) => { 32 | await queryInterface.dropTable("UserProjects"); 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /server/db/models/comment.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { Model } = require("sequelize"); 3 | module.exports = (sequelize, DataTypes) => { 4 | class Comment extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | Comment.belongsTo(models.Task, { 12 | foreignKey: "task_id", 13 | }); 14 | 15 | Comment.belongsTo(models.User, { 16 | foreignKey: "user_id", 17 | }); 18 | } 19 | } 20 | Comment.init( 21 | { 22 | text: { 23 | type: DataTypes.TEXT, 24 | allowNull: false, 25 | }, 26 | task_id: { 27 | type: DataTypes.INTEGER, 28 | allowNull: false, 29 | }, 30 | user_id: { 31 | type: DataTypes.INTEGER, 32 | allowNull: false, 33 | }, 34 | }, 35 | { 36 | sequelize, 37 | modelName: "Comment", 38 | } 39 | ); 40 | return Comment; 41 | }; 42 | -------------------------------------------------------------------------------- /server/db/models/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const Sequelize = require('sequelize'); 6 | const basename = path.basename(__filename); 7 | const env = process.env.NODE_ENV || 'development'; 8 | const config = require(__dirname + '/../../config/database.js')[env]; 9 | const db = {}; 10 | 11 | let sequelize; 12 | if (config.use_env_variable) { 13 | sequelize = new Sequelize(process.env[config.use_env_variable], config); 14 | } else { 15 | sequelize = new Sequelize(config.database, config.username, config.password, config); 16 | } 17 | 18 | fs 19 | .readdirSync(__dirname) 20 | .filter(file => { 21 | return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js'); 22 | }) 23 | .forEach(file => { 24 | const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes); 25 | db[model.name] = model; 26 | }); 27 | 28 | Object.keys(db).forEach(modelName => { 29 | if (db[modelName].associate) { 30 | db[modelName].associate(db); 31 | } 32 | }); 33 | 34 | db.sequelize = sequelize; 35 | db.Sequelize = Sequelize; 36 | 37 | module.exports = db; 38 | -------------------------------------------------------------------------------- /server/db/models/project.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { Model } = require("sequelize"); 3 | module.exports = (sequelize, DataTypes) => { 4 | class Project extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | Project.hasMany(models.TaskList, { 12 | foreignKey: "project_id", 13 | }); 14 | 15 | Project.hasMany(models.Task, { 16 | foreignKey: "project_id", 17 | }); 18 | 19 | Project.belongsTo(models.Team, { 20 | foreignKey: "team_id", 21 | }); 22 | 23 | Project.belongsToMany(models.User, { 24 | foreignKey: "project_id", 25 | through: "UserProjects", 26 | otherKey: "user_id", 27 | }); 28 | } 29 | } 30 | Project.init( 31 | { 32 | name: { 33 | type: DataTypes.STRING, 34 | allowNull: false, 35 | }, 36 | team_id: { 37 | type: DataTypes.INTEGER, 38 | allowNull: false, 39 | }, 40 | }, 41 | { 42 | sequelize, 43 | modelName: "Project", 44 | } 45 | ); 46 | return Project; 47 | }; 48 | -------------------------------------------------------------------------------- /server/db/models/task.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { Model } = require("sequelize"); 3 | module.exports = (sequelize, DataTypes) => { 4 | class Task extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | Task.belongsTo(models.TaskList, { 13 | foreignKey: "tasklist_id", 14 | }); 15 | 16 | Task.belongsTo(models.User, { 17 | foreignKey: "assignee_id", 18 | }); 19 | 20 | Task.belongsTo(models.Project, { 21 | foreignKey: "project_id", 22 | }); 23 | 24 | Task.hasMany(models.Comment, { 25 | foreignKey: "task_id", 26 | onDelete: "cascade", 27 | hooks: true, 28 | }); 29 | } 30 | } 31 | Task.init( 32 | { 33 | name: { 34 | type: DataTypes.STRING, 35 | allowNull: false, 36 | }, 37 | tasklist_id: { 38 | type: DataTypes.INTEGER, 39 | allowNull: false, 40 | }, 41 | assignee_id: { 42 | type: DataTypes.INTEGER, 43 | allowNull: false, 44 | }, 45 | project_id: { 46 | type: DataTypes.INTEGER, 47 | allowNull: false, 48 | }, 49 | task_index: { 50 | type: DataTypes.INTEGER, 51 | }, 52 | description: DataTypes.TEXT, 53 | due_date: DataTypes.DATE, 54 | completed: { 55 | type: DataTypes.BOOLEAN, 56 | defaultValue: false, 57 | }, 58 | completed_at: DataTypes.DATE, 59 | }, 60 | { 61 | sequelize, 62 | modelName: "Task", 63 | } 64 | ); 65 | return Task; 66 | }; 67 | -------------------------------------------------------------------------------- /server/db/models/tasklist.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { Model } = require("sequelize"); 3 | module.exports = (sequelize, DataTypes) => { 4 | class TaskList extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | TaskList.belongsTo(models.Project, { 12 | foreignKey: "project_id", 13 | }); 14 | 15 | TaskList.belongsTo(models.User, { 16 | foreignKey: "owner_id", 17 | }); 18 | 19 | TaskList.hasMany(models.Task, { 20 | foreignKey: "tasklist_id", 21 | onDelete: "CASCADE", 22 | hooks: true, 23 | }); 24 | } 25 | } 26 | TaskList.init( 27 | { 28 | name: { 29 | type: DataTypes.STRING, 30 | allowNull: false, 31 | }, 32 | project_id: { 33 | type: DataTypes.INTEGER, 34 | allowNull: false, 35 | }, 36 | column_index: { 37 | type: DataTypes.INTEGER, 38 | }, 39 | owner_id: { 40 | type: DataTypes.INTEGER, 41 | allowNull: false, 42 | }, 43 | }, 44 | { 45 | sequelize, 46 | modelName: "TaskList", 47 | } 48 | ); 49 | return TaskList; 50 | }; 51 | -------------------------------------------------------------------------------- /server/db/models/team.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { Model } = require("sequelize"); 3 | module.exports = (sequelize, DataTypes) => { 4 | class Team extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | Team.hasMany(models.Project, { 12 | foreignKey: "team_id", 13 | }); 14 | 15 | Team.belongsToMany(models.User, { 16 | foreignKey: "team_id", 17 | through: "UserTeams", 18 | otherKey: "user_id", 19 | }); 20 | 21 | // Team.hasMany(models.UserTeam, { 22 | // foreignKey: "team_id", 23 | // }); 24 | } 25 | } 26 | Team.init( 27 | { 28 | name: { 29 | type: DataTypes.STRING, 30 | allowNull: false, 31 | }, 32 | description: DataTypes.TEXT, 33 | }, 34 | { 35 | sequelize, 36 | modelName: "Team", 37 | } 38 | ); 39 | return Team; 40 | }; 41 | -------------------------------------------------------------------------------- /server/db/models/user.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { Model } = require("sequelize"); 3 | const bcrypt = require("bcryptjs"); 4 | module.exports = (sequelize, DataTypes) => { 5 | class User extends Model { 6 | /** 7 | * Helper method for defining associations. 8 | * This method is not a part of Sequelize lifecycle. 9 | * The `models/index` file will call this method automatically. 10 | */ 11 | static associate(models) { 12 | User.hasMany(models.TaskList, { 13 | foreignKey: "owner_id", 14 | onDelete: "CASCADE", 15 | hooks: true, 16 | }); 17 | 18 | User.hasMany(models.Task, { 19 | foreignKey: "assignee_id", 20 | onDelete: "CASCADE", 21 | hooks: true, 22 | }); 23 | 24 | User.hasMany(models.Comment, { 25 | foreignKey: "user_id", 26 | onDelete: "CASCADE", 27 | hooks: true, 28 | }); 29 | 30 | User.belongsToMany(models.Team, { 31 | foreignKey: "user_id", 32 | through: "UserTeam", 33 | otherKey: "team_id", 34 | }); 35 | 36 | User.belongsToMany(models.Project, { 37 | foreignKey: "user_id", 38 | through: "UserProject", 39 | otherKey: "project_id", 40 | }); 41 | // User.hasMany(models.UserTeam, { 42 | // foreignKey: "user_id", 43 | // }); 44 | } 45 | } 46 | User.init( 47 | { 48 | name: { 49 | type: DataTypes.STRING, 50 | 51 | validate: { 52 | notEmpty: { 53 | args: true, 54 | msg: "Please provide your full name", 55 | }, 56 | }, 57 | }, 58 | email: { 59 | type: DataTypes.STRING, 60 | allowNull: false, 61 | unique: true, 62 | validate: { 63 | notEmpty: { 64 | args: true, 65 | msg: "Please provide a value for email.", 66 | }, 67 | isEmail: { 68 | args: true, 69 | msg: "Email addresss is not a valid email.", 70 | }, 71 | }, 72 | }, 73 | hashed_password: { 74 | type: DataTypes.STRING, 75 | allowNull: false, 76 | validate: { 77 | notEmpty: { 78 | args: true, 79 | msg: "Please provide a value for password.", 80 | }, 81 | len: { 82 | args: [8, 255], 83 | msg: "Length needs to be between 8 - 255 characters.", 84 | }, 85 | }, 86 | }, 87 | image: { 88 | type: DataTypes.STRING, 89 | }, 90 | }, 91 | { 92 | sequelize, 93 | modelName: "User", 94 | } 95 | ); 96 | User.prototype.validatePassword = function (password) { 97 | return bcrypt.compareSync(password, this.hashed_password.toString()); 98 | }; 99 | return User; 100 | }; 101 | -------------------------------------------------------------------------------- /server/db/models/userproject.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { Model } = require("sequelize"); 3 | module.exports = (sequelize, DataTypes) => { 4 | class UserProject extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | } 13 | } 14 | UserProject.init( 15 | { 16 | user_id: { 17 | type: DataTypes.INTEGER, 18 | allowNull: false, 19 | }, 20 | project_id: { 21 | type: DataTypes.INTEGER, 22 | allowNull: false, 23 | }, 24 | }, 25 | { 26 | sequelize, 27 | modelName: "UserProject", 28 | } 29 | ); 30 | return UserProject; 31 | }; 32 | -------------------------------------------------------------------------------- /server/db/models/userteam.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { Model } = require("sequelize"); 3 | module.exports = (sequelize, DataTypes) => { 4 | class UserTeam extends Model { 5 | /** 6 | * Helper method for defining associations. 7 | * This method is not a part of Sequelize lifecycle. 8 | * The `models/index` file will call this method automatically. 9 | */ 10 | static associate(models) { 11 | // define association here 12 | // UserTeam.belongsTo(models.Team, { 13 | // foreignKey: "team_id", 14 | // }); 15 | // UserTeam.belongsTo(models.User, { 16 | // foreignKey: "user_id", 17 | // }); 18 | } 19 | } 20 | UserTeam.init( 21 | { 22 | user_id: { 23 | type: DataTypes.INTEGER, 24 | allowNull: false, 25 | }, 26 | team_id: { 27 | type: DataTypes.INTEGER, 28 | allowNull: false, 29 | }, 30 | }, 31 | { 32 | sequelize, 33 | modelName: "UserTeam", 34 | } 35 | ); 36 | return UserTeam; 37 | }; 38 | -------------------------------------------------------------------------------- /server/db/routes/comments.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { asyncHandler } = require("./utilities/utils"); 3 | const { requireAuth } = require("./utilities/auth"); 4 | const { check, validationResult } = require("express-validator"); 5 | const { Comment } = require("../../db/models"); 6 | 7 | const router = express.Router(); 8 | 9 | //Authenticates user before being able to use API 10 | // router.use(requireAuth); 11 | 12 | //get all comments 13 | router.get( 14 | "/", 15 | asyncHandler(async (req, res, next) => { 16 | const comments = await Comment.findAll({}); 17 | 18 | res.json(comments); 19 | }) 20 | ); 21 | 22 | //Delete Comment 23 | router.delete( 24 | "/:id", 25 | asyncHandler(async (req, res, next) => { 26 | const comment_id = req.params.id; 27 | 28 | const comment = await Comment.delete({ 29 | where: { id: comment_id }, 30 | }); 31 | res.json(202); 32 | }) 33 | ); 34 | 35 | module.exports = router; 36 | -------------------------------------------------------------------------------- /server/db/routes/projects.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { asyncHandler } = require("./utilities/utils"); 3 | const { requireAuth, getUserToken } = require("./utilities/auth"); 4 | const { check, validationResult } = require("express-validator"); 5 | const { 6 | Project, 7 | User, 8 | TaskList, 9 | Team, 10 | UserProject, 11 | Task, 12 | } = require("../../db/models"); 13 | 14 | const router = express.Router(); 15 | //Authenticates user before being able to use API 16 | // router.use(requireAuth); 17 | 18 | //get all projects 19 | router.get( 20 | "/", 21 | asyncHandler(async (req, res, next) => { 22 | const projects = await Project.findAll({}); 23 | 24 | res.json(projects); 25 | }) 26 | ); 27 | 28 | //Get all projects for a user 29 | // router.get( 30 | // "/user/:id", 31 | // asyncHandler(async (req, res, next) => { 32 | // const user_id = req.params.id; 33 | // const projects = await Project.findAll({ 34 | // include: [ 35 | // { 36 | // model: User, 37 | // where: { 38 | // id: user_id, 39 | // }, 40 | // attributes: ["name"], 41 | // }, 42 | // ], 43 | // }); 44 | // res.json(projects); 45 | // // select * from Projects where user_id = id from projects join team on projects.team_id = team.id join user_team 46 | // }) 47 | // ); 48 | 49 | //get all projects for teams that a user is on 50 | 51 | router.get( 52 | "/user/:id", 53 | asyncHandler(async (req, res, next) => { 54 | const user_id = req.params.id; 55 | const projects = await Team.findAll({ 56 | include: [ 57 | { 58 | model: User, 59 | where: { 60 | id: user_id, 61 | }, 62 | attributes: ["name"], 63 | }, 64 | { model: Project }, 65 | ], 66 | }); 67 | 68 | let combinedProjects = projects.map((team) => { 69 | return team.Projects; 70 | }); 71 | //pulls all projects from teams and combines into an array 72 | let arrays = []; 73 | for (i = 0; i < combinedProjects.length; i++) { 74 | for (j = 0; j < combinedProjects[i].length; j++) { 75 | arrays.push(combinedProjects[i][j]); 76 | } 77 | } 78 | //Sorts by created date 79 | arrays.sort(function (a, b) { 80 | var keyA = new Date(a.createdAt), 81 | keyB = new Date(b.createdAt); 82 | // Compare the 2 dates 83 | if (keyA < keyB) return -1; 84 | if (keyA > keyB) return 1; 85 | return 0; 86 | }); 87 | res.json(arrays); 88 | }) 89 | ); 90 | 91 | //get all users in a project 92 | router.get( 93 | "/:id/users", 94 | asyncHandler(async (req, res, next) => { 95 | const project_id = req.params.id; 96 | 97 | const users = await User.findAll({ 98 | include: [ 99 | { 100 | model: Project, 101 | where: { id: project_id }, 102 | }, 103 | ], 104 | 105 | attributes: ["id", "name"], 106 | }); 107 | res.json(users); 108 | }) 109 | ); 110 | 111 | //get all taskslists for a project 112 | router.get( 113 | "/:id/tasklists", 114 | asyncHandler(async (req, res, next) => { 115 | const project_id = req.params.id; 116 | const tasklist = await TaskList.findAll({ 117 | where: { 118 | project_id: project_id, 119 | }, 120 | order: [["column_index", "ASC"]], 121 | include: [ 122 | { 123 | model: Task, 124 | include: [{ model: User, attributes: ["id", "name", "email"] }], 125 | }, 126 | ], 127 | }); 128 | if (!tasklist) { 129 | res.json({ message: "error" }); 130 | } 131 | res.json(tasklist); 132 | }) 133 | ); 134 | 135 | //get team project is on 136 | router.get( 137 | "/:id/team", 138 | asyncHandler(async (req, res, next) => { 139 | const project_id = req.params.id; 140 | const team = await Team.findOne({ 141 | include: [ 142 | { model: Project, where: { id: project_id } }, 143 | { model: User, attributes: ["name", "id"] }, 144 | ], 145 | }); 146 | res.json(team); 147 | }) 148 | ); 149 | //Create tasklist for project 150 | router.post( 151 | "/:id/tasklist", 152 | asyncHandler(async (req, res, next) => { 153 | const project_id = req.params.id; 154 | const { name, userId } = req.body; 155 | 156 | const tasklist = await TaskList.create({ 157 | name: name, 158 | owner_id: userId, 159 | project_id: project_id, 160 | }); 161 | res.json(tasklist).status(201); 162 | }) 163 | ); 164 | 165 | //Delete project 166 | router.delete( 167 | "/:id", 168 | asyncHandler(async (req, res, next) => { 169 | const team_id = req.params.id; 170 | const project_id = req.params.projectId; 171 | const project = await Project.delete({ 172 | where: { id: project_id }, 173 | }); 174 | res.status(202); 175 | }) 176 | ); 177 | 178 | //get everything about project 179 | 180 | router.get( 181 | "/:id", 182 | asyncHandler(async (req, res, next) => { 183 | const user_id = req.params.userId; 184 | const project_name = req.params.projectName; 185 | const project_id = req.params.id; 186 | // const project = await Project.findOne({ 187 | // include: [ 188 | // { 189 | // model: User, 190 | // where: { 191 | // id: user_id, 192 | // }, 193 | // attributes: ["name"], 194 | // }, 195 | // { model: TaskList }, 196 | // ], 197 | // where: { 198 | // name: project_name, 199 | // }, 200 | // }); 201 | 202 | const project = await Project.findOne({ 203 | include: [ 204 | { 205 | model: TaskList, 206 | }, 207 | // { model: Team }, 208 | // { model: User, attributes: ["name", "email", "id"] }, 209 | ], 210 | where: { 211 | id: project_id, 212 | }, 213 | }); 214 | 215 | res.json(project); 216 | }) 217 | ); 218 | 219 | module.exports = router; 220 | -------------------------------------------------------------------------------- /server/db/routes/tasklists.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { asyncHandler } = require("./utilities/utils"); 3 | const { requireAuth } = require("./utilities/auth"); 4 | const { check, validationResult } = require("express-validator"); 5 | const { TaskList, Task } = require("../../db/models"); 6 | const router = express.Router(); 7 | 8 | //Authenticates user before being able to use API 9 | // router.use(requireAuth); 10 | 11 | //get all tasklists 12 | router.get( 13 | "/", 14 | asyncHandler(async (req, res, next) => { 15 | const tasklists = await TaskList.findAll({}); 16 | 17 | res.json(tasklists); 18 | }) 19 | ); 20 | 21 | //get all tasks for tasklist 22 | router.get( 23 | "/:id/tasks", 24 | asyncHandler(async (req, res, next) => { 25 | const tasklist_id = req.params.id; 26 | const tasks = await Task.findAll({ 27 | where: { 28 | tasklist_id: tasklist_id, 29 | }, 30 | }); 31 | res.json(tasks); 32 | }) 33 | ); 34 | 35 | //Create task to tasklist 36 | router.post( 37 | "/:id/task", 38 | asyncHandler(async (req, res, next) => { 39 | const tasklist_id = req.params.id; 40 | const { 41 | name, 42 | projectId, 43 | assigneeId, 44 | due_date, 45 | completed, 46 | description, 47 | } = req.body; 48 | if (completed === []) { 49 | completed = false; 50 | } 51 | const task = await Task.create({ 52 | name: name, 53 | project_id: projectId, 54 | assignee_id: assigneeId, 55 | due_date: due_date, 56 | completed: completed, 57 | description: description, 58 | tasklist_id: tasklist_id, 59 | }); 60 | if (!task) { 61 | res.status(404); 62 | } else { 63 | res.json(task).status(201); 64 | } 65 | }) 66 | ); 67 | 68 | //Delete TaskList 69 | router.delete( 70 | "/:id", 71 | asyncHandler(async (req, res, next) => { 72 | const tasklist_id = req.params.id; 73 | 74 | const tasklist = await TaskList.destroy({ 75 | where: { id: tasklist_id }, 76 | }); 77 | res.json(202); 78 | }) 79 | ); 80 | 81 | //Edit Column index 82 | router.put( 83 | "/:id/columnindex", 84 | asyncHandler(async (req, res, next) => { 85 | const { newIndex } = req.body; 86 | const tasklist_id = req.params.id; 87 | const column_index = req.params.columnIndex; 88 | 89 | try { 90 | const updateIndex = await TaskList.update( 91 | { 92 | column_index: newIndex, 93 | }, 94 | { 95 | where: { 96 | id: tasklist_id, 97 | }, 98 | } 99 | ); 100 | console.log(newIndex); 101 | res.json(updateIndex); 102 | } catch (err) { 103 | res.status(401).send({ error: "Something went wrong" }); 104 | } 105 | }) 106 | ); 107 | 108 | //update tasklist name 109 | 110 | router.put( 111 | "/:id/title", 112 | asyncHandler(async (req, res, next) => { 113 | const tasklist_id = req.params.id; 114 | const { columnTitle } = req.body; 115 | const tasklist = await TaskList.update( 116 | { name: columnTitle }, 117 | { where: { id: tasklist_id } } 118 | ); 119 | res.json({ message: "updated" }); 120 | }) 121 | ); 122 | 123 | module.exports = router; 124 | -------------------------------------------------------------------------------- /server/db/routes/teams.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { asyncHandler } = require("./utilities/utils"); 3 | const { requireAuth } = require("./utilities/auth"); 4 | const { check, validationResult } = require("express-validator"); 5 | const { Team, UserTeam, User, Project, UserProject } = require("../../db/models"); 6 | 7 | const router = express.Router(); 8 | //Authenticates user before being able to use API 9 | // router.use(requireAuth); 10 | 11 | //Gets all Teams 12 | router.get( 13 | "/", 14 | asyncHandler(async (req, res, next) => { 15 | const teams = await Team.findAll({}); 16 | 17 | res.json(teams); 18 | }) 19 | ); 20 | 21 | //get all users in a Team 22 | router.get( 23 | "/:id/users", 24 | asyncHandler(async (req, res, next) => { 25 | const team_id = req.params.id; 26 | 27 | const users = await Team.findAll({ 28 | include: [ 29 | { 30 | model: User, 31 | attributes: ["id", "name", "email"], 32 | }, 33 | ], 34 | where: { id: team_id }, 35 | }); 36 | 37 | res.json(users); 38 | }) 39 | ); 40 | 41 | // router.get( 42 | // "/:id/users", 43 | // asyncHandler(async (req, res, next) => { 44 | // // const project_id = req.params.id; 45 | // const team_id = req.params.id; 46 | 47 | // const users = await User.findAll({ 48 | // include: [{ model: Team, where: { id: team_id } }], 49 | // attributes: ["id", "name"], 50 | // }); 51 | // res.json(users); 52 | // }) 53 | // ); 54 | 55 | //get all teams for a user 56 | router.get( 57 | "/user/:id", 58 | asyncHandler(async (req, res, next) => { 59 | const user_id = req.params.id; 60 | 61 | const teams = await Team.findAll({ 62 | include: [ 63 | { 64 | model: User, 65 | where: { 66 | id: user_id, 67 | }, 68 | attributes: ["name", "id"], 69 | }, 70 | ], 71 | }); 72 | 73 | res.json(teams); 74 | }) 75 | ); 76 | 77 | //get all projects for team 78 | router.get( 79 | "/:id/projects", 80 | asyncHandler(async (req, res, next) => { 81 | const team_id = req.params.id; 82 | const projects = await Project.findAll({ 83 | where: { 84 | team_id: team_id, 85 | }, 86 | }); 87 | res.json(projects); 88 | }) 89 | ); 90 | 91 | //get everything about team 92 | router.get( 93 | "/:id", 94 | asyncHandler(async (req, res, next) => { 95 | const team_id = req.params.id; 96 | const team = await Team.findOne({ 97 | include: [ 98 | { model: Project }, 99 | { model: User, attributes: ["name", "email", "id"] }, 100 | ], 101 | where: { id: team_id }, 102 | }); 103 | if (!team) { 104 | res.send({ error: "No team exists" }); 105 | } 106 | res.json(team); 107 | }) 108 | ); 109 | 110 | //Create team 111 | router.post( 112 | "/user/:userId", 113 | asyncHandler(async (req, res, next) => { 114 | const user_id = req.params.userId; 115 | const { description, name } = req.body; 116 | if (description) { 117 | const team = await Team.create({ 118 | description: description, 119 | name: name, 120 | }); 121 | //Adds user to team 122 | const userteam = await UserTeam.create({ 123 | team_id: team.id, 124 | user_id: user_id, 125 | }); 126 | res.json(team).status(201); 127 | } else if (!description) { 128 | const team = await Team.create({ 129 | name: name, 130 | }); 131 | //Adds user to team 132 | const userteam = await UserTeam.create({ 133 | team_id: team.id, 134 | user_id: user_id, 135 | }); 136 | res.json(team).status(201); 137 | } 138 | }) 139 | ); 140 | 141 | //Add other users to team 142 | router.post( 143 | "/:teamId/user/:userId", 144 | asyncHandler(async (req, res, next) => { 145 | const team_id = req.params.teamId; 146 | const user_id = req.params.userId; 147 | const userteam = await UserTeam.findOne({ 148 | where: { 149 | team_id: team_id, 150 | user_id: user_id, 151 | }, 152 | }); 153 | if (userteam) { 154 | res.status(404).send({ error: "user already exists" }); 155 | } else if (!userteam) { 156 | const newUserTeam = await UserTeam.create({ 157 | team_id: team_id, 158 | user_id: user_id, 159 | }); 160 | res.json(newUserTeam).status(201); 161 | } 162 | }) 163 | ); 164 | 165 | //Edit team description 166 | router.put( 167 | "/:teamId/description", 168 | asyncHandler(async (req, res, next) => { 169 | const team_id = req.params.teamId; 170 | const { description } = req.body; 171 | await Team.update( 172 | { 173 | description: description, 174 | }, 175 | { 176 | where: { 177 | id: team_id, 178 | }, 179 | } 180 | ); 181 | }) 182 | ); 183 | 184 | //Create Project for team 185 | router.post( 186 | "/:id/project", 187 | asyncHandler(async (req, res, next) => { 188 | //need to add owner for project 189 | const team_id = req.params.id; 190 | const { name, userId } = req.body; 191 | const project = await Project.create({ 192 | name: name, 193 | team_id: team_id, 194 | }); 195 | 196 | if (project) { 197 | const userproject = await UserProject.create({ 198 | user_id: userId, 199 | project_id: project.id, 200 | }); 201 | res.json(userproject).status(201); 202 | } else { 203 | res.status(404); 204 | } 205 | }) 206 | ); 207 | 208 | //Delete team 209 | router.delete( 210 | "/:teamId/", 211 | asyncHandler(async (req, res, next) => { 212 | const team_id = req.params.teamId; 213 | const project_id = req.params.projectId; 214 | const team = await Team.destroy({ 215 | where: { id: team_id }, 216 | }); 217 | res.status(202); 218 | }) 219 | ); 220 | 221 | module.exports = router; 222 | -------------------------------------------------------------------------------- /server/db/routes/users.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const bcrypt = require("bcryptjs"); 3 | const { asyncHandler } = require("./utilities/utils"); 4 | const { check, validationResult } = require("express-validator"); 5 | const { User, Team, UserTeam } = require("../../db/models"); 6 | const { getUserToken, requireAuth } = require("./utilities/auth"); 7 | 8 | const router = express.Router(); 9 | 10 | validateUserFields = [ 11 | check("email") 12 | .exists({ checkFalsy: true }) 13 | .withMessage("Please provide a valid email"), 14 | check("password") 15 | .exists({ checkFalsy: true }) 16 | .withMessage("Please provide a valid password"), 17 | ]; 18 | 19 | const validateTeamName = [ 20 | check("teamName") 21 | .exists({ checkFalsy: true }) 22 | .withMessage("You'll need to enter a name"), 23 | ]; 24 | 25 | const validateEmailPassword = [ 26 | check("email") 27 | .exists({ checkFalsy: true }) 28 | .withMessage("Please provide a valid email"), 29 | check("password") 30 | .exists({ checkFalsy: true }) 31 | .withMessage("Please provide a valid password"), 32 | ]; 33 | 34 | //Get user Information 35 | router.get( 36 | "/user/:id", 37 | requireAuth, 38 | asyncHandler(async (req, res, next) => { 39 | const user_id = req.params.id; 40 | const user = await User.findOne({ 41 | where: { 42 | id: user_id, 43 | }, 44 | attributes: ["name", "email"], 45 | }); 46 | 47 | res.json(user); 48 | }) 49 | ); 50 | 51 | router.get( 52 | "/users", 53 | requireAuth, 54 | asyncHandler(async (req, res, next) => { 55 | const users = await User.findAll({ 56 | attributes: ["id", "name", "email"], 57 | }); 58 | res.json(users); 59 | }) 60 | ); 61 | //Register 62 | router.post( 63 | "/register", 64 | validateUserFields, 65 | asyncHandler(async (req, res) => { 66 | const validatorErr = validationResult(req); 67 | 68 | if (!validatorErr.isEmpty()) { 69 | const errors = validatorErr.array().map((error) => error.msg); 70 | res.status(422).json(["Errors", ...errors]); 71 | return; 72 | } 73 | 74 | const { name, email, password } = req.body; 75 | 76 | const hashedPassword = await bcrypt.hash(password, 10); 77 | 78 | // try { 79 | const existingUser = await User.findOne({ 80 | where: { 81 | email: email, 82 | }, 83 | }); 84 | if (existingUser) { 85 | res.status(422).send({ Error: "User already exists" }); 86 | return; 87 | } 88 | 89 | const user = await User.create({ 90 | name: name, 91 | email: email, 92 | hashed_password: hashedPassword, 93 | createdAt: new Date(), 94 | updatedAt: new Date(), 95 | }); 96 | 97 | const token = getUserToken(user); 98 | 99 | res.status(200).json({ 100 | id: user.id, 101 | token, 102 | email: user.email, 103 | }); 104 | // } catch (err) { 105 | // res.status(422).send({ error: err.errors[0].message }); 106 | // } 107 | }) 108 | ); 109 | 110 | //Onboard information 111 | router.put( 112 | "/register/onboard", 113 | validateTeamName, 114 | asyncHandler(async (req, res, next) => { 115 | const validatorErr = validationResult(req); 116 | 117 | if (!validatorErr.isEmpty()) { 118 | const errors = validatorErr.array().map((error) => error.msg); 119 | res.json(["ERRORS", ...errors]); 120 | return; 121 | } 122 | 123 | const { email, teamName } = req.body; 124 | // try { 125 | 126 | const user = await User.findOne({ 127 | where: { 128 | email: email, 129 | }, 130 | }); 131 | const token = getUserToken(user); 132 | res.status(200).json({ 133 | token, 134 | }); 135 | 136 | //Create initial Team 137 | const team = await Team.create({ 138 | name: teamName, 139 | }); 140 | //Tie user to team 141 | const userTeam = await UserTeam.create({ 142 | user_id: user.id, 143 | team_id: team.id, 144 | }); 145 | 146 | // } catch (err) { 147 | // res.status(422).send(err.message); 148 | // } 149 | }) 150 | ); 151 | 152 | //Log in 153 | router.post( 154 | "/login", 155 | validateEmailPassword, 156 | asyncHandler(async (req, res, next) => { 157 | const validatorErr = validationResult(req); 158 | 159 | if (!validatorErr.isEmpty()) { 160 | const errors = validatorErr.array().map((error) => error.msg); 161 | res.json(["ERRORS", ...errors]); 162 | return; 163 | } 164 | const { email, password } = req.body; 165 | 166 | if (!email || !password) { 167 | return res.status(422).send({ error: "Must provide email and password" }); 168 | } 169 | const user = await User.findOne({ 170 | where: { 171 | email, 172 | }, 173 | }); 174 | 175 | if (!user || !user.validatePassword(password)) { 176 | const err = new Error("Login Failed"); 177 | err.status = 401; 178 | err.title = "Login Failed"; 179 | err.errors = ["The provided credentials were invalid"]; 180 | res.status(401).json(err); 181 | return; 182 | } else { 183 | const token = getUserToken(user); 184 | 185 | res.json({ 186 | id: user.id, 187 | token, 188 | email: user.email, 189 | }); 190 | } 191 | }) 192 | ); 193 | 194 | module.exports = router; 195 | -------------------------------------------------------------------------------- /server/db/routes/userteams.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { asyncHandler } = require("./utilities/utils"); 3 | const { requireAuth } = require("./utilities/auth"); 4 | const { check, validationResult } = require("express-validator"); 5 | const { UserTeam, Team, User } = require("../../db/models"); 6 | 7 | const router = express.Router(); 8 | 9 | //Leave Team 10 | 11 | router.delete( 12 | "/:teamId/user/:userId", 13 | asyncHandler(async (req, res, next) => { 14 | const team_id = req.params.teamId; 15 | const user_id = req.params.userId; 16 | const userteam = await UserTeam.destroy({ 17 | where: { 18 | user_id: user_id, 19 | team_id: team_id, 20 | }, 21 | }); 22 | 23 | const team = await Team.findOne({ 24 | where: { 25 | id: team_id, 26 | }, 27 | include: [{ model: User }], 28 | }); 29 | 30 | if (team.Users.length === 0) { 31 | await Team.destroy({ 32 | where: { 33 | id: team_id, 34 | }, 35 | }); 36 | } 37 | res.send({ message: "User removed from Team" }); 38 | }) 39 | ); 40 | 41 | //get all userteams 42 | router.get( 43 | "/", 44 | asyncHandler(async (req, res, next) => { 45 | const userteams = await UserTeam.findAll({}); 46 | res.json(userteams); 47 | }) 48 | ); 49 | 50 | module.exports = router; 51 | -------------------------------------------------------------------------------- /server/db/routes/utilities/auth.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const { jwtConfig } = require("../../../config"); 3 | const { User } = require("../../../db/models"); 4 | const bearerToken = require("express-bearer-token"); 5 | const { secret, expiresIn } = jwtConfig; 6 | 7 | const getUserToken = (user) => { 8 | const userDataForToken = { 9 | id: user.id, 10 | email: user.email, 11 | }; 12 | 13 | // const token = jwt.sign({ data: userDataForToken }, secret); 14 | // const token = jwt.sign({ data: userDataForToken }, process.env.JWT_SECRET); 15 | const token = jwt.sign({ data: userDataForToken }, process.env.JWT_SECRET, { 16 | expiresIn: parseInt(process.env.JWT_EXPIRES_IN, 10), 17 | }); 18 | 19 | return token; 20 | }; 21 | 22 | const restoreUser = (req, res, next) => { 23 | const { token } = req; 24 | 25 | if (!token) { 26 | return res.set("WWW-Authenticate", "Bearer").status(401).end(); 27 | } 28 | 29 | //Changed jwt token here as well 30 | return jwt.verify( 31 | token, 32 | process.env.JWT_SECRET, 33 | null, 34 | async (err, jwtPayload) => { 35 | if (err) { 36 | err.status = 401; 37 | return next(err); 38 | } 39 | 40 | const { id } = jwtPayload.data; 41 | try { 42 | req.user = await User.findByPk(id); 43 | } catch (e) { 44 | return next(e); 45 | } 46 | if (!req.user) { 47 | return res.set("WWW-Authenicate", "Bearer").status(401).end(); 48 | } 49 | return next(); 50 | } 51 | ); 52 | }; 53 | 54 | const requireAuth = [bearerToken(), restoreUser]; 55 | module.exports = { getUserToken, requireAuth }; 56 | -------------------------------------------------------------------------------- /server/db/routes/utilities/utils.js: -------------------------------------------------------------------------------- 1 | const { validationResult } = require("express-validator"); 2 | 3 | const asyncHandler = (handler) => (req, res, next) => 4 | handler(req, res, next).catch(next); 5 | 6 | // const handleValidationErrors = (req, res, next) => { 7 | // const validationErrors = validationResult(req); 8 | // console.log("ERRORS", errors.array()); 9 | // if (!validationErrors.isEmpty()) { 10 | // const errors = validationErrors.array().map((error) => error.msg); 11 | 12 | // const err = Error("Bad request."); 13 | // err.status = 400; 14 | // err.title = "Bad request."; 15 | // err.errors = errors; 16 | // return next(err); 17 | // } 18 | // next(); 19 | // }; 20 | 21 | module.exports = { asyncHandler }; 22 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "./bin/www", 6 | "scripts": { 7 | "start": "per-env", 8 | "start:development": "nodemon -r dotenv/config ./bin/www", 9 | "start:production": "./bin/www" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "bcryptjs": "^2.4.3", 16 | "cors": "^2.8.5", 17 | "dotenv": "^8.2.0", 18 | "express": "^4.17.1", 19 | "express-bearer-token": "^2.4.0", 20 | "express-validator": "^6.6.1", 21 | "jsonwebtoken": "^8.5.1", 22 | "morgan": "^1.10.0", 23 | "per-env": "^1.0.2", 24 | "pg": "^8.4.2", 25 | "sequelize": "^6.3.5" 26 | }, 27 | "devDependencies": { 28 | "dotenv-cli": "^4.0.0", 29 | "nodemon": "^2.0.6", 30 | "sequelize-cli": "^6.2.0" 31 | }, 32 | "engines": { 33 | "node":"10.x" 34 | } 35 | } 36 | --------------------------------------------------------------------------------