├── .env
├── code.png
├── src
├── utls
│ └── currencyFomrmatter.js
├── index.css
├── components
│ ├── Footer.js
│ ├── Navbar.js
│ ├── ProjectDetails.js
│ └── ProjectForm.js
├── hooks
│ ├── useAuthContext.js
│ ├── useProjectsContext.js
│ ├── useLogout.js
│ ├── useLogin.js
│ └── useSignup.js
├── index.js
├── context
│ ├── AuthContext.js
│ └── ProjectContext.js
├── App.js
└── pages
│ ├── Home.js
│ ├── Login.js
│ └── Signup.js
├── tailwind.config.js
├── .gitignore
├── public
└── index.html
├── package.json
└── README.md
/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_BASE_URL=https://proxima-csb4.onrender.com
--------------------------------------------------------------------------------
/code.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sadrilamin1999/proxima-client/HEAD/code.png
--------------------------------------------------------------------------------
/src/utls/currencyFomrmatter.js:
--------------------------------------------------------------------------------
1 | export const currencyFormatter = (amount) => {
2 | return amount?.toLocaleString("en-US", {
3 | style: "currency",
4 | currency: "USD",
5 | });
6 | };
7 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap");
2 | @tailwind base;
3 | @tailwind components;
4 | @tailwind utilities;
5 |
6 | /* font-family: 'Inter', sans-serif; */
7 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('tailwindcss').Config} */
2 | module.exports = {
3 | content: ["./src/**/*.{js,jsx,ts,tsx}"],
4 | theme: {
5 | extend: {
6 | fontFamily: {
7 | sans: ["Inter, sans-serif"],
8 | },
9 | },
10 | },
11 | plugins: [],
12 | };
13 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | const Footer = () => {
2 | return (
3 |
4 |
5 |
©{new Date().getFullYear()} Proxima, All Rights Reserved
6 |
7 |
8 | );
9 | };
10 |
11 | export default Footer;
12 |
--------------------------------------------------------------------------------
/src/hooks/useAuthContext.js:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import { AuthContext } from "../context/AuthContext";
3 |
4 | export const useAuthContext = () => {
5 | const context = useContext(AuthContext);
6 |
7 | if (!context) {
8 | throw new Error(
9 | "You must call useAuthContext inside a AuthContextProvider"
10 | );
11 | }
12 |
13 | return context;
14 | };
15 |
--------------------------------------------------------------------------------
/src/hooks/useProjectsContext.js:
--------------------------------------------------------------------------------
1 | import { useContext } from "react";
2 | import { ProjectContext } from "../context/ProjectContext";
3 |
4 | export const useProjectsContext = () => {
5 | const context = useContext(ProjectContext);
6 |
7 | if (!context) {
8 | throw new Error(
9 | "You must call useProjectsContext inside a ProjectContextProvider"
10 | );
11 | }
12 |
13 | return context;
14 | };
15 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 | proxima
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/src/hooks/useLogout.js:
--------------------------------------------------------------------------------
1 | import { useAuthContext } from "./useAuthContext";
2 | import { useProjectsContext } from "./useProjectsContext";
3 |
4 | export const useLogout = () => {
5 | const { dispatch: logoutDispatch } = useAuthContext();
6 | const { dispatch: projectsDispatch } = useProjectsContext();
7 |
8 | const logout = () => {
9 | // clear ls
10 | localStorage.removeItem("user");
11 |
12 | // dispatch logout
13 | logoutDispatch({ type: "LOGOUT" });
14 | projectsDispatch({ type: "SET_PROJECTS", payload: [] });
15 | };
16 |
17 | return { logout };
18 | };
19 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import "./index.css";
4 | import App from "./App";
5 | import { BrowserRouter } from "react-router-dom";
6 | import { ProjectContextProvider } from "./context/ProjectContext";
7 | import { AuthContextProvider } from "./context/AuthContext";
8 |
9 | const root = ReactDOM.createRoot(document.getElementById("root"));
10 | root.render(
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | );
21 |
--------------------------------------------------------------------------------
/src/context/AuthContext.js:
--------------------------------------------------------------------------------
1 | import { createContext, useReducer } from "react";
2 |
3 | const initialState = {
4 | user: localStorage.getItem("user")
5 | ? JSON.parse(localStorage.getItem("user"))
6 | : null,
7 | };
8 |
9 | export const authReducer = (state, action) => {
10 | switch (action.type) {
11 | case "LOGIN":
12 | return {
13 | ...state,
14 | user: action.payload,
15 | };
16 | case "LOGOUT":
17 | return {
18 | ...state,
19 | user: null,
20 | };
21 | default:
22 | return state;
23 | }
24 | };
25 |
26 | export const AuthContext = createContext();
27 |
28 | export const AuthContextProvider = ({ children }) => {
29 | const [state, dispatch] = useReducer(authReducer, initialState);
30 |
31 | return (
32 |
33 | {children}
34 |
35 | );
36 | };
37 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { Routes, Route, Navigate } from "react-router-dom";
2 | import Navbar from "./components/Navbar";
3 | import Footer from "./components/Footer";
4 | import Home from "./pages/Home";
5 | import Login from "./pages/Login";
6 | import Signup from "./pages/Signup";
7 | import { useAuthContext } from "./hooks/useAuthContext";
8 |
9 | function App() {
10 | const { user } = useAuthContext();
11 | return (
12 |
13 |
14 |
15 | : } />
16 | : }
19 | />
20 | : }
23 | />
24 | } />
25 |
26 |
27 |
28 | );
29 | }
30 |
31 | export default App;
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.5",
7 | "@testing-library/react": "^13.4.0",
8 | "@testing-library/user-event": "^13.5.0",
9 | "moment": "^2.29.4",
10 | "react": "^18.2.0",
11 | "react-dom": "^18.2.0",
12 | "react-router-dom": "^6.8.1",
13 | "react-scripts": "5.0.1",
14 | "web-vitals": "^2.1.4"
15 | },
16 | "scripts": {
17 | "start": "react-scripts start",
18 | "build": "react-scripts build",
19 | "test": "react-scripts test",
20 | "eject": "react-scripts eject"
21 | },
22 | "eslintConfig": {
23 | "extends": [
24 | "react-app",
25 | "react-app/jest"
26 | ]
27 | },
28 | "browserslist": {
29 | "production": [
30 | ">0.2%",
31 | "not dead",
32 | "not op_mini all"
33 | ],
34 | "development": [
35 | "last 1 chrome version",
36 | "last 1 firefox version",
37 | "last 1 safari version"
38 | ]
39 | },
40 | "devDependencies": {
41 | "tailwindcss": "^3.2.7"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/hooks/useLogin.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { useAuthContext } from "./useAuthContext";
3 |
4 | export const useLogin = () => {
5 | const [error, setError] = useState(null);
6 | const [loading, setLoading] = useState(false);
7 |
8 | const { dispatch } = useAuthContext();
9 |
10 | const login = async (email, password) => {
11 | setLoading(true);
12 | setError(null);
13 |
14 | const res = await fetch(
15 | `${process.env.REACT_APP_BASE_URL}/api/user/login`,
16 | {
17 | method: "POST",
18 | headers: {
19 | "Content-Type": "application/json",
20 | },
21 | body: JSON.stringify({ email, password }),
22 | }
23 | );
24 |
25 | const json = await res.json();
26 | console.log(json);
27 | // res.ok === false
28 | if (!res.ok) {
29 | setLoading(false);
30 | setError(json.error);
31 | }
32 |
33 | // res.ok === true
34 | if (res.ok) {
35 | // update auth context
36 | dispatch({ type: "LOGIN", payload: json });
37 | // save user to local storage
38 | localStorage.setItem("user", JSON.stringify(json));
39 |
40 | setLoading(false);
41 | }
42 | };
43 |
44 | return { login, error, loading };
45 | };
46 |
--------------------------------------------------------------------------------
/src/hooks/useSignup.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { useAuthContext } from "./useAuthContext";
3 |
4 | export const useSignup = () => {
5 | const [error, setError] = useState(null);
6 | const [loading, setLoading] = useState(false);
7 |
8 | const { dispatch } = useAuthContext();
9 |
10 | const signup = async (email, password) => {
11 | setLoading(true);
12 | setError(null);
13 |
14 | const res = await fetch(
15 | `${process.env.REACT_APP_BASE_URL}/api/user/signup`,
16 | {
17 | method: "POST",
18 | headers: {
19 | "Content-Type": "application/json",
20 | },
21 | body: JSON.stringify({ email, password }),
22 | }
23 | );
24 |
25 | const json = await res.json();
26 | console.log(json);
27 | // res.ok === false
28 | if (!res.ok) {
29 | setLoading(false);
30 | setError(json.error);
31 | }
32 |
33 | // res.ok === true
34 | if (res.ok) {
35 | // update auth context
36 | dispatch({ type: "LOGIN", payload: json });
37 | // save user to local storage
38 | localStorage.setItem("user", JSON.stringify(json));
39 |
40 | setLoading(false);
41 | }
42 | };
43 |
44 | return { signup, error, loading };
45 | };
46 |
--------------------------------------------------------------------------------
/src/context/ProjectContext.js:
--------------------------------------------------------------------------------
1 | import { createContext, useReducer } from "react";
2 |
3 | const initialState = {
4 | projects: [],
5 | };
6 |
7 | export const projectReducer = (state, action) => {
8 | switch (action.type) {
9 | case "SET_PROJECTS":
10 | return {
11 | projects: action.payload,
12 | };
13 | case "CREATE_PROJECTS":
14 | return {
15 | projects: [action.payload, ...state.projects],
16 | };
17 | case "DELETE_PROJECT":
18 | return {
19 | ...state,
20 | projects: state.projects.filter(
21 | (project) => project._id !== action.payload._id
22 | ),
23 | };
24 | case "UPDATE_PROJECT":
25 | const [existingProject] = state.projects.filter(
26 | (project) => project._id === action.payload._id
27 | );
28 | return {
29 | ...state,
30 | projects: [
31 | action.payload,
32 | ...state.projects.filter(
33 | (project) => project._id !== existingProject._id
34 | ),
35 | ],
36 | };
37 | default:
38 | return state;
39 | }
40 | };
41 | export const ProjectContext = createContext();
42 |
43 | export const ProjectContextProvider = ({ children }) => {
44 | const [state, dispatch] = useReducer(projectReducer, initialState);
45 |
46 | return (
47 |
48 | {children}
49 |
50 | );
51 | };
52 |
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from "react";
2 | import ProjectDetails from "../components/ProjectDetails";
3 | import ProjectForm from "../components/ProjectForm";
4 | import { useProjectsContext } from "../hooks/useProjectsContext";
5 | import { useAuthContext } from "../hooks/useAuthContext";
6 |
7 | const Home = () => {
8 | const { projects, dispatch } = useProjectsContext();
9 | const { user } = useAuthContext();
10 | useEffect(() => {
11 | const getAllProjects = async () => {
12 | const res = await fetch(
13 | `${process.env.REACT_APP_BASE_URL}/api/projects`,
14 | {
15 | headers: {
16 | Authorization: `Bearer ${user.token}`,
17 | },
18 | }
19 | );
20 | const json = await res.json();
21 |
22 | if (res.ok) {
23 | dispatch({ type: "SET_PROJECTS", payload: json });
24 | }
25 | };
26 | if (user) {
27 | getAllProjects();
28 | }
29 | }, [dispatch, user]);
30 | return (
31 |
32 |
33 |
34 | {projects.length < 1 ? "No projects" : "All Projects"}
35 |
36 |
37 | {projects &&
38 | projects.map((project) => (
39 |
40 | ))}
41 |
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default Home;
49 |
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import { Link } from "react-router-dom";
2 | import { useAuthContext } from "../hooks/useAuthContext";
3 | import { useLogout } from "../hooks/useLogout";
4 |
5 | const Navbar = () => {
6 | const { user } = useAuthContext();
7 |
8 | const { logout } = useLogout();
9 |
10 | const handleLogout = () => {
11 | logout();
12 | };
13 | return (
14 |
15 |
16 |
20 | Proxima
21 |
22 |
46 |
47 |
48 | );
49 | };
50 |
51 | export default Navbar;
52 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Proxima - Project Management Web Application
2 | Proxima is a web application built for project managers to efficiently plan, execute and monitor projects. It provides a user-friendly interface that simplifies project management tasks such as task assignment, progress tracking, and deadline setting. This application is built using React, Node.js, Express, MongoDB, and Tailwind CSS.
3 |
4 | ## Features
5 | - Effortlessly manage projects: Create, update, and delete, projects quickly and easily using the user-friendly interface.
6 | = Robust security features: The app features highly secure JWT authentication and frontend route protection, ensuring that your data is always safe and secure.
7 | = User-specific project views: Users can only see the projects they have created, ensuring that project information is kept private and secure.
8 | = Intuitive and streamlined UI: The app’s sleek and intuitive user interface makes managing projects a breeze.
9 |
10 |
11 | ## Tools
12 | - React
13 | - Node.js
14 | - Express
15 | - MongoDB
16 | - Tailwind CSS
17 | - Moment.js
18 | - Mongoose
19 | - JSON Web Token
20 | - CORS
21 | - Bcrypt
22 | - Validator
23 |
24 | ## Installation
25 | 1. Clone the repository: git clone https://github.com/yourusername/proxima.git
26 | 2. Navigate to the project directory: `cd proxima-client`
27 | 3. Install dependencies:`npm install`
28 | 4. Start the application:`npm start`
29 |
30 | ## Conclusion
31 | Proxima is an efficient and user-friendly project management web application. Its features such as user authentication, task management, and progress tracking make it an essential tool for project managers. The application is built using modern web technologies such as React, Node.js, Express, and MongoDB. With its intuitive user interface and features, it provides an optimal user experience for project management.
32 |
--------------------------------------------------------------------------------
/src/pages/Login.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { useLogin } from "../hooks/useLogin.js";
3 |
4 | const Login = () => {
5 | const [email, setEmail] = useState("");
6 | const [password, setPassword] = useState("");
7 |
8 | const { login, error, loading } = useLogin();
9 |
10 | const handleLogin = async (e) => {
11 | e.preventDefault();
12 |
13 | // login user
14 | await login(email, password);
15 | };
16 | return (
17 |
71 | );
72 | };
73 |
74 | export default Login;
75 |
--------------------------------------------------------------------------------
/src/pages/Signup.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { useSignup } from "../hooks/useSignup";
3 |
4 | const Signup = () => {
5 | const [email, setEmail] = useState("");
6 | const [password, setPassword] = useState("");
7 |
8 | const { signup, error, loading } = useSignup();
9 |
10 | const handleSignup = async (e) => {
11 | e.preventDefault();
12 |
13 | // signup user
14 | await signup(email, password);
15 | };
16 | return (
17 |
70 | );
71 | };
72 |
73 | export default Signup;
74 |
--------------------------------------------------------------------------------
/src/components/ProjectDetails.js:
--------------------------------------------------------------------------------
1 | import { currencyFormatter } from "../utls/currencyFomrmatter";
2 | import { useProjectsContext } from "../hooks/useProjectsContext";
3 | import { useAuthContext } from "../hooks/useAuthContext";
4 | import moment from "moment";
5 | import { useState } from "react";
6 | import ProjectForm from "./ProjectForm";
7 |
8 | const ProjectDetails = ({ project }) => {
9 | const [isModalOpen, setIsModalOpen] = useState(false);
10 | const [isOverlayOpen, setIsOverlayOpen] = useState(false);
11 | const { dispatch } = useProjectsContext();
12 | const { user } = useAuthContext();
13 |
14 | const handleDelete = async () => {
15 | if (!user) {
16 | return;
17 | }
18 | const res = await fetch(
19 | `${process.env.REACT_APP_BASE_URL}/api/projects/${project._id}`,
20 | {
21 | method: "DELETE",
22 | headers: {
23 | Authorization: `Bearer ${user.token}`,
24 | },
25 | }
26 | );
27 | const json = await res.json();
28 |
29 | if (res.ok) {
30 | dispatch({ type: "DELETE_PROJECT", payload: json });
31 | }
32 | };
33 | const handleUpdate = async () => {
34 | setIsModalOpen(true);
35 | setIsOverlayOpen(true);
36 | };
37 |
38 | const handleOverlay = () => {
39 | setIsModalOpen(false);
40 | setIsOverlayOpen(false);
41 | };
42 | return (
43 |
44 |
45 | ID: {project._id}
46 |
47 | {project.title}
48 |
49 |
50 | {project.tech}
51 |
52 |
53 |
54 |
55 | Budget: {currencyFormatter(project.budget)}
56 |
57 | Added: {moment(project.createdAt).format("DD-MMM hh:mm A")}
58 |
59 |
60 | Updated: {moment(project.updatedAt).format("DD-MMM hh:mm A")}
61 |
62 |
63 |
64 | Manager: {project.manager}
65 | Developers: {project.dev}
66 |
67 | Duration:{" "}
68 | {`${project.duration} week${project.duration === 1 ? "" : "s"}`}
69 |
70 |
71 |
72 |
73 |
79 |
85 |
86 | {/* overlay */}
87 |
93 | {/* modal */}
94 |
99 |
100 | Update project
101 |
102 |
107 |
108 |
109 | );
110 | };
111 |
112 | export default ProjectDetails;
113 |
--------------------------------------------------------------------------------
/src/components/ProjectForm.js:
--------------------------------------------------------------------------------
1 | import { useState } from "react";
2 | import { useProjectsContext } from "../hooks/useProjectsContext";
3 | import { useAuthContext } from "../hooks/useAuthContext";
4 |
5 | const ProjectForm = ({ project, setIsModalOpen, setIsOverlayOpen }) => {
6 | const [title, setTitle] = useState(project ? project.title : "");
7 | const [tech, setTech] = useState(project ? project.tech : "");
8 | const [budget, setBudget] = useState(project ? project.budget : "");
9 | const [duration, setDuration] = useState(project ? project.duration : "");
10 | const [manager, setManager] = useState(project ? project.manager : "");
11 | const [dev, setDev] = useState(project ? project.dev : "");
12 | const [error, setError] = useState(null);
13 | const [emptyFields, setEmptyfields] = useState([]);
14 |
15 | const { dispatch } = useProjectsContext();
16 | const { user } = useAuthContext();
17 |
18 | const handleSubmit = async (e) => {
19 | e.preventDefault();
20 |
21 | if (!user) {
22 | setError("You must be logged in!");
23 | return;
24 | }
25 |
26 | // data
27 | const projectObj = { title, tech, budget, duration, manager, dev };
28 |
29 | // if there is no project
30 | if (!project) {
31 | // post request
32 | const res = await fetch(
33 | `${process.env.REACT_APP_BASE_URL}/api/projects`,
34 | {
35 | method: "POST",
36 | headers: {
37 | "Content-Type": "application/json",
38 | Authorization: `Bearer ${user.token}`,
39 | },
40 | body: JSON.stringify(projectObj),
41 | }
42 | );
43 |
44 | const json = await res.json();
45 |
46 | // ste error
47 | if (!res.ok) {
48 | setError(json.error);
49 | setEmptyfields(json.emptyFields);
50 | }
51 |
52 | // reset
53 | if (res.ok) {
54 | setTitle("");
55 | setTech("");
56 | setBudget("");
57 | setDuration("");
58 | setManager("");
59 | setDev("");
60 | setError(null);
61 | setEmptyfields([]);
62 | dispatch({ type: "CREATE_PROJECTS", payload: json });
63 | }
64 | return;
65 | }
66 |
67 | // there is a project, send patch request
68 | if (project) {
69 | //send patch
70 | const res = await fetch(
71 | `${process.env.REACT_APP_BASE_URL}/api/projects/${project._id}`,
72 | {
73 | method: "PATCH",
74 | headers: {
75 | "Content-Type": "application/json",
76 | Authorization: `Bearer ${user.token}`,
77 | },
78 | body: JSON.stringify(projectObj),
79 | }
80 | );
81 |
82 | const json = await res.json();
83 | // !res.ok
84 | if (!res.ok) {
85 | setError(json.error);
86 | setEmptyfields(json.emptyFields);
87 | }
88 | // res.ok
89 | if (res.ok) {
90 | setError(null);
91 | setEmptyfields([]);
92 |
93 | // dispatch
94 | dispatch({ type: "UPDATE_PROJECT", payload: json });
95 |
96 | // close overlay modal
97 | setIsModalOpen(false);
98 | setIsOverlayOpen(false);
99 | }
100 | return;
101 | }
102 | };
103 | return (
104 |
246 | );
247 | };
248 |
249 | export default ProjectForm;
250 |
--------------------------------------------------------------------------------