From f3858b049a07b9ea4eee370824f3279b5b094028 Mon Sep 17 00:00:00 2001 From: shreesriv12 Date: Mon, 3 Feb 2025 01:50:02 +0530 Subject: [PATCH 01/10] fix:landing page --- frontend/pages/Home.jsx | 16 ++++++---------- frontend/pages/Layout1.jsx | 2 +- frontend/pages/Signup.jsx | 2 +- frontend/pages/styles.css | 2 +- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/frontend/pages/Home.jsx b/frontend/pages/Home.jsx index f866ef7..9731b45 100644 --- a/frontend/pages/Home.jsx +++ b/frontend/pages/Home.jsx @@ -57,20 +57,16 @@ const Home = () => { {/* Navigation Menu */} @@ -113,7 +109,7 @@ const Home = () => { )} {/* Main Container */} -
+
+
{ placeholder="Name" className="bg-[#c3bef0] w-72 h-10 p-3 rounded-lg mb-2" value={name} - onChange={(e)=>setEmail(e.target.value)} + onChange={(e)=>setName(e.target.value)} /> Date: Mon, 3 Feb 2025 11:42:47 +0530 Subject: [PATCH 02/10] added image upload and authcontext --- frontend/pages/Login.jsx | 101 ------------------- frontend/pages/Signup.jsx | 102 -------------------- frontend/src/App.jsx | 6 +- frontend/{ => src}/components/Modal.jsx | 0 frontend/src/context/AuthContext.jsx | 41 ++++++++ frontend/{ => src}/context/ModalContext.jsx | 0 frontend/{ => src}/context/UserContext.jsx | 0 frontend/{ => src}/hooks/useActiveForm.js | 0 frontend/{ => src}/hooks/useScrollTo.js | 0 frontend/src/main.jsx | 8 +- frontend/{ => src}/pages/ContactUs.jsx | 0 frontend/{ => src}/pages/Home.jsx | 0 frontend/{ => src}/pages/Layout1.jsx | 0 frontend/src/pages/Login.jsx | 59 +++++++++++ frontend/{ => src}/pages/Photos.jsx | 0 frontend/src/pages/Signup.jsx | 92 ++++++++++++++++++ frontend/{ => src}/pages/styles.css | 0 frontend/src/utils/api.js | 19 ++++ 18 files changed, 219 insertions(+), 209 deletions(-) delete mode 100644 frontend/pages/Login.jsx delete mode 100644 frontend/pages/Signup.jsx rename frontend/{ => src}/components/Modal.jsx (100%) create mode 100644 frontend/src/context/AuthContext.jsx rename frontend/{ => src}/context/ModalContext.jsx (100%) rename frontend/{ => src}/context/UserContext.jsx (100%) rename frontend/{ => src}/hooks/useActiveForm.js (100%) rename frontend/{ => src}/hooks/useScrollTo.js (100%) rename frontend/{ => src}/pages/ContactUs.jsx (100%) rename frontend/{ => src}/pages/Home.jsx (100%) rename frontend/{ => src}/pages/Layout1.jsx (100%) create mode 100644 frontend/src/pages/Login.jsx rename frontend/{ => src}/pages/Photos.jsx (100%) create mode 100644 frontend/src/pages/Signup.jsx rename frontend/{ => src}/pages/styles.css (100%) create mode 100644 frontend/src/utils/api.js diff --git a/frontend/pages/Login.jsx b/frontend/pages/Login.jsx deleted file mode 100644 index 21d995c..0000000 --- a/frontend/pages/Login.jsx +++ /dev/null @@ -1,101 +0,0 @@ -import React, { useState, useContext } from 'react'; -import { useNavigate } from 'react-router-dom'; -import UserContext from '../context/UserContext'; -import axios from 'axios' - -const Login = () => { - // State variables for email and password - const [email, setEmail] = useState(''); - const [password, setPassword] = useState(''); - - // Hook for navigation - const navigate = useNavigate(); - - // Accessing setUser function from UserContext - const { setUser } = useContext(UserContext); - - // Handler for form submission - const submitHandler = async (e) => { - e.preventDefault(); // Prevent default form submission behavior - - // User data to be sent to the server - const userData = { - email: email, - password: password, - }; - - try { - // Sending a POST request to the login endpoint - const response = await axios.post(`${import.meta.env.VITE_BASE_URL}/login`, userData); - - // If login is successful - if (response.status === 200) { - const data = response.data; - - // Setting the user data in context - setUser(data.user); - - // Storing the token and user data in local storage - localStorage.setItem('token', data.token); - localStorage.setItem('user', JSON.stringify(data.user)); - - // Navigating to the main page - navigate('/main-page'); - } - } catch (error) { - // Logging the error to the console - console.error('Error during login:', error); - - // Handling different types of errors - if (error.response) { - console.error('Error response:', error.response); - const errorData = error.response.data; - if (errorData) { - const message = errorData.message || 'Something went wrong on the server.'; - alert(`Error: ${message}`); - } else { - alert('Something went wrong on the server.'); - } - } else if (error.request) { - alert('Network error. Please check your internet connection.'); - } else if (error.message) { - alert(error.message); - } else { - alert('An error occurred while setting up the request.'); - } - } - - // Clearing the form fields - setEmail(''); - setPassword(''); - }; - - return ( -
-

Sign In

- or use your email & password - setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> - -
- ); -}; - -export default Login; \ No newline at end of file diff --git a/frontend/pages/Signup.jsx b/frontend/pages/Signup.jsx deleted file mode 100644 index 22bac86..0000000 --- a/frontend/pages/Signup.jsx +++ /dev/null @@ -1,102 +0,0 @@ -import React, { useContext, useState } from 'react'; -import { Link,useNavigate } from 'react-router-dom'; -import axios from 'axios'; -import UserContext from '../context/UserContext'; - - -const Signup = () => { - const[email,setEmail]=useState(''); - const [password,setPassword]=useState(''); - const[name,setName]=useState(''); - const[username,setUsername]=useState(''); - - - const navigate=useNavigate(); - - const{user,setUser}=useContext(UserContext); - - const submitHandler = async (e) => { - e.preventDefault(); - const newUser = { - name: name, - username:username, - email:email, - password:password, - }; - - try{ - const response=await axios.post(`${import.meta.env.VITE_BASE_URL}/signup`) - if (response.status === 201) { - const data = response.data; - setUser(data.user); - localStorage.setItem('token',data.token) - navigate('/main-page'); - } - }catch(error){ - // Enhanced error handling - console.error('Error during registration:', error); // Log entire error object - - if (error.response) { - // If error.response exists, we can get details from the server's response - console.error('Error response:', error.response); // Log the entire error response object - - const errorData = error.response.data; - if (errorData) { - const message = errorData.message || 'Something went wrong on the server.'; - alert(`Error: ${message}`); - } else { - alert('Something went wrong on the server.'); - } - } else if (error.request) { - alert('Network error. Please check your internet connection.'); - } else if (error.message) { - alert(error.message); - } else { - alert('An error occurred while setting up the request.'); - } - - setEmail(''); - setName(''); - setPassword(''); - setUsername(''); - - } -}; - return ( -
- {/* Sign Up Form */} -

Create Account

- setName(e.target.value)} - /> - setUsername(e.target.value)} - /> - setEmail(e.target.value)} - /> - setPassword(e.target.value)} - /> - -
- ); -}; - -export default Signup; \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index e3e6a60..184d5fe 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -2,9 +2,9 @@ import React from 'react' import { BrowserRouter as Router,Route,Routes } from 'react-router-dom' import { useState } from 'react' import './App.css' -import Home from '../pages/Home' -import Login from '../pages/Login' -import Signup from '../pages/Signup' +import Home from './pages/Home' +import Login from './pages/Login' +import Signup from './pages/Signup' function App() { return ( diff --git a/frontend/components/Modal.jsx b/frontend/src/components/Modal.jsx similarity index 100% rename from frontend/components/Modal.jsx rename to frontend/src/components/Modal.jsx diff --git a/frontend/src/context/AuthContext.jsx b/frontend/src/context/AuthContext.jsx new file mode 100644 index 0000000..e285d0d --- /dev/null +++ b/frontend/src/context/AuthContext.jsx @@ -0,0 +1,41 @@ +import { createContext, useContext, useEffect, useReducer } from "react"; +import { getCurrentUser, logout } from "../utils/api"; + +const AuthContext = createContext(); + +const authReducer = (state, action) => { + switch (action.type) { + case "LOGIN": + return { ...state, user: action.payload, isAuthenticated: true }; + case "LOGOUT": + return { ...state, user: null, isAuthenticated: false }; + default: + return state; + } +}; + +export const AuthProvider = ({ children }) => { + const [state, dispatch] = useReducer(authReducer, { + user: null, + isAuthenticated: false, + }); + + useEffect(() => { + getCurrentUser() + .then(({ data }) => dispatch({ type: "LOGIN", payload: data })) + .catch(() => dispatch({ type: "LOGOUT" })); + }, []); + + const handleLogout = async () => { + await logout(); + dispatch({ type: "LOGOUT" }); + }; + + return ( + + {children} + + ); +}; + +export const useAuth = () => useContext(AuthContext); diff --git a/frontend/context/ModalContext.jsx b/frontend/src/context/ModalContext.jsx similarity index 100% rename from frontend/context/ModalContext.jsx rename to frontend/src/context/ModalContext.jsx diff --git a/frontend/context/UserContext.jsx b/frontend/src/context/UserContext.jsx similarity index 100% rename from frontend/context/UserContext.jsx rename to frontend/src/context/UserContext.jsx diff --git a/frontend/hooks/useActiveForm.js b/frontend/src/hooks/useActiveForm.js similarity index 100% rename from frontend/hooks/useActiveForm.js rename to frontend/src/hooks/useActiveForm.js diff --git a/frontend/hooks/useScrollTo.js b/frontend/src/hooks/useScrollTo.js similarity index 100% rename from frontend/hooks/useScrollTo.js rename to frontend/src/hooks/useScrollTo.js diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index c4cf566..7fcfada 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -2,18 +2,20 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' import App from './App.jsx' -import ModalProvider from '../context/ModalContext.jsx' -import { UserProvider } from '../context/UserContext.jsx' +import ModalProvider from './context/ModalContext.jsx' +import { UserProvider } from './context/UserContext.jsx' import { BrowserRouter } from 'react-router-dom' - +import { AuthProvider } from './context/AuthContext.jsx' createRoot(document.getElementById('root')).render( + + , ) diff --git a/frontend/pages/ContactUs.jsx b/frontend/src/pages/ContactUs.jsx similarity index 100% rename from frontend/pages/ContactUs.jsx rename to frontend/src/pages/ContactUs.jsx diff --git a/frontend/pages/Home.jsx b/frontend/src/pages/Home.jsx similarity index 100% rename from frontend/pages/Home.jsx rename to frontend/src/pages/Home.jsx diff --git a/frontend/pages/Layout1.jsx b/frontend/src/pages/Layout1.jsx similarity index 100% rename from frontend/pages/Layout1.jsx rename to frontend/src/pages/Layout1.jsx diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx new file mode 100644 index 0000000..79f10a3 --- /dev/null +++ b/frontend/src/pages/Login.jsx @@ -0,0 +1,59 @@ +import React, { useState, useContext } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; +import { login } from '../utils/api'; + +const Login = () => { + + const [credentials, setCredentials] = useState({ email: "", password: "" }); + const { dispatch } = useAuth(); + const navigate = useNavigate(); + + const handleChange = (e) => { + const { name, value } = e.target; + setCredentials((prev) => ({ ...prev, [name]: value })); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + try { + const { data } = await login(credentials); + localStorage.setItem("token", data.accessToken); + dispatch({ type: "LOGIN", payload: data.user }); + navigate("/dashboard"); + } catch (error) { + console.error(error.response?.data?.message || "Login failed"); + } + }; + + return ( +
+

Sign In

+ or use your email & password + + + +
+ ); +}; + +export default Login; \ No newline at end of file diff --git a/frontend/pages/Photos.jsx b/frontend/src/pages/Photos.jsx similarity index 100% rename from frontend/pages/Photos.jsx rename to frontend/src/pages/Photos.jsx diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx new file mode 100644 index 0000000..38c69af --- /dev/null +++ b/frontend/src/pages/Signup.jsx @@ -0,0 +1,92 @@ +import React, { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { register } from '../utils/api'; + +const Signup = () => { + const [formData, setFormData] = useState({ + fullName: "", + username: "", + email: "", + password: "", + avatar: null, + }); + const [error, setError] = useState(""); // State for displaying error message + const navigate = useNavigate(); + + const handleChange = (e) => { + const { name, value } = e.target; + if (name === "avatar") { + setFormData((prev) => ({ ...prev, avatar: e.target.files[0] })); + } else { + setFormData((prev) => ({ ...prev, [name]: value })); + } + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + const data = new FormData(); + Object.keys(formData).forEach((key) => data.append(key, formData[key])); + + try { + await register(data); + navigate("/login"); + } catch (error) { + setError(error.response?.data?.message || "Registration failed"); + } + }; + + return ( +
+
+

Create Account

+ + + + + + + {error &&

{error}

} +
+
+ ); +}; + +export default Signup; diff --git a/frontend/pages/styles.css b/frontend/src/pages/styles.css similarity index 100% rename from frontend/pages/styles.css rename to frontend/src/pages/styles.css diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js new file mode 100644 index 0000000..c7e3f0e --- /dev/null +++ b/frontend/src/utils/api.js @@ -0,0 +1,19 @@ +import axios from "axios"; + +const API = axios.create({ + baseURL: "http://localhost:8000/api", // Change this based on your backend URL + withCredentials: true, // Required for cookies (refresh tokens) +}); + +// Add JWT to headers if available +API.interceptors.request.use((config) => { + const token = localStorage.getItem("token"); + if (token) config.headers.Authorization = `Bearer ${token}`; + return config; +}); + +export const register = (formData) => API.post("/register", formData); +export const login = (credentials) => API.post("/login", credentials); +export const logout = () => API.post("/logout"); +export const refreshToken = () => API.post("/refresh-token"); +export const getCurrentUser = () => API.get("/current-user"); From f5767a62e0164565a41c69a02d464a03079fbcf9 Mon Sep 17 00:00:00 2001 From: shreesriv12 Date: Mon, 3 Feb 2025 15:45:46 +0530 Subject: [PATCH 03/10] fix --- frontend/src/utils/api.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 3ed813f..813d8d4 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -12,8 +12,45 @@ API.interceptors.request.use((config) => { return config; }); + +/* ---------- Authentication API Calls---------- */ export const register = (formData) => API.post("/register", formData); export const login = (credentials) => API.post("/login", credentials); export const logout = () => API.post("/logout"); export const refreshToken = () => API.post("/refresh-token"); export const getCurrentUser = () => API.get("/current-user"); + +/*-----------Tasks assignment calls-----------*/ +// Create a new task +export const createTask = (taskData) => API.post("/tasks", taskData); + +// Get all tasks for the current user +export const getUserTasks = () => API.get("/tasks"); + +// Update a task +export const updateTask = (taskId, taskData) => + API.put(`/tasks/${taskId}`, taskData); + +// Delete a task +export const deleteTask = (taskId) => API.delete(`/tasks/${taskId}`); + + + + + +/* ---------- Assignment API Calls ----------*/ + + +export const createAssignment = (formData) => + API.post("/assignments", formData, { headers: { "Content-Type": "multipart/form-data" } }); + +// Get all assignments for the current user +export const getUserAssignments = () => API.get("/assignments"); + +// Update an assignment +export const updateAssignment = (assignmentId, formData) => + API.put(`/assignments/${assignmentId}`, formData, { headers: { "Content-Type": "multipart/form-data" } }); + +// Delete an assignment +export const deleteAssignment = (assignmentId) => API.delete(`/assignments/${assignmentId}`); + From 33e8622e36b2120afba3ca86c744c5de23f4898c Mon Sep 17 00:00:00 2001 From: shreesriv12 Date: Tue, 4 Feb 2025 18:28:56 +0530 Subject: [PATCH 04/10] feat: dashboard theme switcher and sidebar added --- frontend/package-lock.json | 154 ++++++++++++++++++++-- frontend/package.json | 3 +- frontend/src/App.jsx | 2 + frontend/src/components/Header.jsx | 44 +++++++ frontend/src/components/Sidebar.jsx | 90 +++++++++++++ frontend/src/components/TaskManager.jsx | 91 +++++++++++++ frontend/src/components/ThemeSwitcher.jsx | 75 +++++++++++ frontend/src/context/TaskContext.jsx | 103 +++++++++++++++ frontend/src/context/ThemeContext.jsx | 44 +++++++ frontend/src/main.jsx | 4 + frontend/src/pages/Dashboard.jsx | 26 ++++ frontend/src/pages/Home.jsx | 6 +- frontend/src/pages/theme.css | 0 frontend/src/utils/apitask.js | 33 +++++ frontend/src/utils/assignmentapi.js | 32 +++++ frontend/tailwind.config.js | 5 +- 16 files changed, 695 insertions(+), 17 deletions(-) create mode 100644 frontend/src/components/Header.jsx create mode 100644 frontend/src/components/Sidebar.jsx create mode 100644 frontend/src/components/TaskManager.jsx create mode 100644 frontend/src/components/ThemeSwitcher.jsx create mode 100644 frontend/src/context/TaskContext.jsx create mode 100644 frontend/src/context/ThemeContext.jsx create mode 100644 frontend/src/pages/Dashboard.jsx create mode 100644 frontend/src/pages/theme.css create mode 100644 frontend/src/utils/apitask.js create mode 100644 frontend/src/utils/assignmentapi.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 03478c5..e90d46a 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -20,7 +20,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.4.0", - "react-router-dom": "^7.1.4", + "react-router-dom": "^7.1.5", + "styled-components": "^6.1.14", "tailwindcss": "^4.0.1" }, "devDependencies": { @@ -333,6 +334,27 @@ "node": ">=6.9.0" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "license": "MIT", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", @@ -1623,6 +1645,12 @@ "@types/react": "^18.0.0" } }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", + "license": "MIT" + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", @@ -2006,6 +2034,15 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001696", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz", @@ -2127,6 +2164,15 @@ "node": ">= 8" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, "node_modules/css-selector-tokenizer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz", @@ -2138,6 +2184,17 @@ "fastparse": "^1.1.2" } }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "license": "MIT", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -2155,7 +2212,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/culori": { @@ -4492,6 +4548,12 @@ "postcss": "^8.4.21" } }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -4580,9 +4642,9 @@ } }, "node_modules/react-router": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.4.tgz", - "integrity": "sha512-aJWVrKoLI0nIK1lfbTU3d5al1ZEUiwtSus/xjYL8K5sv2hyPesiOIojHM7QnaNLVtroOB1McZsWk37fMQVoc6A==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.5.tgz", + "integrity": "sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==", "license": "MIT", "dependencies": { "@types/cookie": "^0.6.0", @@ -4604,12 +4666,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.4.tgz", - "integrity": "sha512-p474cAeRKfPNp+9QtpdVEa025iWLIIIBhYCnjsSwFmZH3c5DBHOc7vB7zmL6lL747o0ArfrLblNTebtL6lt0lA==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.5.tgz", + "integrity": "sha512-/4f9+up0Qv92D3bB8iN5P1s3oHAepSGa9h5k6tpTFlixTTskJZwKGhJ6vRJ277tLD1zuaZTt95hyGWV1Z37csQ==", "license": "MIT", "dependencies": { - "react-router": "7.1.4" + "react-router": "7.1.5" }, "engines": { "node": ">=20.0.0" @@ -4858,6 +4920,12 @@ "node": ">= 0.4" } }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5077,6 +5145,74 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/styled-components": { + "version": "6.1.14", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.14.tgz", + "integrity": "sha512-KtfwhU5jw7UoxdM0g6XU9VZQFV4do+KrM8idiVCH5h4v49W+3p3yMe0icYwJgZQZepa5DbH04Qv8P0/RdcLcgg==", + "license": "MIT", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/styled-components/node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "license": "0BSD" + }, + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index bd83cf5..2719d48 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,7 +22,8 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-icons": "^5.4.0", - "react-router-dom": "^7.1.4", + "react-router-dom": "^7.1.5", + "styled-components": "^6.1.14", "tailwindcss": "^4.0.1" }, "devDependencies": { diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2f2d178..55acbd0 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -5,6 +5,7 @@ import './App.css' import Home from './pages/Home' import Login from './pages/Login' import Signup from './pages/Signup' +import Dashboard from './pages/Dashboard' function App() { return ( @@ -13,6 +14,7 @@ function App() { }/> } /> } /> + } /> ) diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx new file mode 100644 index 0000000..8ac7df2 --- /dev/null +++ b/frontend/src/components/Header.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { HiOutlineMenuAlt2 } from "react-icons/hi"; +import { MdSpaceDashboard } from "react-icons/md"; +import ThemeSwitcher from './ThemeSwitcher'; +import { useTheme } from "../context/ThemeContext"; // Import theme context + +const Header = () => { + const { theme } = useTheme(); // Get the current theme + + return ( + + ); +} + +export default Header; diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx new file mode 100644 index 0000000..71f1cf1 --- /dev/null +++ b/frontend/src/components/Sidebar.jsx @@ -0,0 +1,90 @@ +import React from "react"; +import { useTheme } from "../context/ThemeContext"; // Import Theme Context +import { FaChartBar, FaCalendarAlt, FaFacebookMessenger, FaUsersCog } from "react-icons/fa"; +import { IoIosLogOut } from "react-icons/io"; +import { useState } from "react"; + +const Sidebar = () => { + const { theme } = useTheme(); // Get theme state + const [isSidebarOpen, setIsSidebarOpen] = useState(false); + + const toggleSidebar = () => { + setIsSidebarOpen(!isSidebarOpen); + }; + + return ( +
+ {/* Sidebar toggle button */} + + + {/* Sidebar */} + +
+ ); +}; + +export default Sidebar; diff --git a/frontend/src/components/TaskManager.jsx b/frontend/src/components/TaskManager.jsx new file mode 100644 index 0000000..35df2d9 --- /dev/null +++ b/frontend/src/components/TaskManager.jsx @@ -0,0 +1,91 @@ +import React from "react"; +import { useTasks } from "../context/TaskContext"; + +export default function TaskManager() { + const { + tasks, + newTask, + setNewTask, + editingTask, + setEditingTask, + isLoading, + handleCreate, + handleUpdate, + handleDelete, + error, + } = useTasks(); + + return ( + +
+

Task Manager

+ {error &&

{error}

} +
+ setNewTask({ ...newTask, title: e.target.value })} + className="w-full p-2 border rounded" + /> + setNewTask({ ...newTask, due_date: e.target.value })} + className="w-full p-2 border rounded" + /> + + +
+ {isLoading &&

Loading...

} +
    + {tasks.map((task) => ( +
  • + {editingTask && editingTask._id === task._id ? ( + setEditingTask({ ...editingTask, title: e.target.value })} + className="p-1 border rounded" + /> + ) : ( + {task.title} + )} +
    + {editingTask && editingTask._id === task._id ? ( + + ) : ( + + )} + +
    +
  • + + ))} +
+
+ + ); +} diff --git a/frontend/src/components/ThemeSwitcher.jsx b/frontend/src/components/ThemeSwitcher.jsx new file mode 100644 index 0000000..19a54a6 --- /dev/null +++ b/frontend/src/components/ThemeSwitcher.jsx @@ -0,0 +1,75 @@ +import React from "react"; +import { useTheme } from "../context/ThemeContext"; // Import the custom hook +import { FaMoon, FaSun } from "react-icons/fa"; + +const ThemeSwitcher = () => { + const { theme, toggleTheme } = useTheme(); + + return ( + <> + + + + + ); +}; + +export default ThemeSwitcher; diff --git a/frontend/src/context/TaskContext.jsx b/frontend/src/context/TaskContext.jsx new file mode 100644 index 0000000..6647bc8 --- /dev/null +++ b/frontend/src/context/TaskContext.jsx @@ -0,0 +1,103 @@ +import React, { createContext, useState, useContext, useEffect } from "react"; +import { createTask, getUserTasks, updateTask, deleteTask } from "../utils/apitask"; // Import API functions + +// Create Context +const TaskContext = createContext(); + +// Custom hook to use the TaskContext +export const useTasks = () => { + return useContext(TaskContext); +}; + +// TaskProvider Component +export const TaskProvider = ({ children }) => { + const [tasks, setTasks] = useState([]); + const [newTask, setNewTask] = useState({ title: "", description: "", due_date: "" }); + const [editingTask, setEditingTask] = useState(null); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + // Fetch tasks from the API + const fetchTasks = async () => { + setIsLoading(true); + try { + const response = await getUserTasks(); + setTasks(response.data.data); + } catch (err) { + setError("Error fetching tasks"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Add a new task + const handleCreate = async () => { + if (!newTask.title || !newTask.due_date) return; + setIsLoading(true); + try { + await createTask(newTask); + setNewTask({ title: "", description: "", due_date: "" }); + fetchTasks(); // Re-fetch tasks after adding + } catch (err) { + setError("Error creating task"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Update an existing task + const handleUpdate = async (taskId) => { + if (!editingTask) return; + setIsLoading(true); + try { + await updateTask(taskId, editingTask); + setEditingTask(null); + fetchTasks(); // Re-fetch tasks after updating + } catch (err) { + setError("Error updating task"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Delete a task + const handleDelete = async (taskId) => { + setIsLoading(true); + try { + await deleteTask(taskId); + fetchTasks(); // Re-fetch tasks after deleting + } catch (err) { + setError("Error deleting task"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Initialize by fetching tasks + useEffect(() => { + fetchTasks(); + }, []); + + return ( + + {children} + + ); +}; diff --git a/frontend/src/context/ThemeContext.jsx b/frontend/src/context/ThemeContext.jsx new file mode 100644 index 0000000..b7636b7 --- /dev/null +++ b/frontend/src/context/ThemeContext.jsx @@ -0,0 +1,44 @@ + +import { createContext, useContext, useEffect, useState } from "react"; + + +// Create a context +const ThemeContext = createContext(); + +// Custom hook to use the theme context +export const useTheme = () => useContext(ThemeContext); + +// Theme provider component +export function ThemeProvider({ children }) { + const storedTheme = localStorage.getItem("theme") || "light"; + const [theme, setTheme] = useState(storedTheme); + + useEffect(() => { + document.documentElement.setAttribute("data-theme", theme); + localStorage.setItem("theme", theme); + }, [theme]); + + // Toggle theme function + const toggleTheme = () => { + setTheme(theme === "light" ? "dark" : "light"); + }; + + return ( + + {children} + + ); +} + +// Dark Mode Page Component +export default function DarkModePage() { + const { theme, toggleTheme } = useTheme(); + + return ( +
+ +
+ ); +} diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 7fcfada..638862c 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -6,9 +6,12 @@ import ModalProvider from './context/ModalContext.jsx' import { UserProvider } from './context/UserContext.jsx' import { BrowserRouter } from 'react-router-dom' import { AuthProvider } from './context/AuthContext.jsx' +import { TaskProvider } from './context/TaskContext.jsx' + createRoot(document.getElementById('root')).render( + @@ -16,6 +19,7 @@ createRoot(document.getElementById('root')).render( + , ) diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx new file mode 100644 index 0000000..c901ae4 --- /dev/null +++ b/frontend/src/pages/Dashboard.jsx @@ -0,0 +1,26 @@ +import React from 'react' +import './theme.css' +import { IoIosLogOut } from "react-icons/io"; +import { FaChartBar, FaCalendarAlt, FaFacebookMessenger, FaUsersCog } from "react-icons/fa"; +import ThemeSwitcher from '../components/ThemeSwitcher'; +import { ThemeProvider } from '../context/ThemeContext' +import Header from '../components/Header'; +import Sidebar from '../components/Sidebar'; + +const Dashboard = () => { + + + + return ( + + +
+
+ +
+
+ + ) +} + +export default Dashboard diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index d5fc9a2..598c87e 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -60,11 +60,7 @@ const Home = () => {
  • scrollTo('top')} className="text-lg font-extrabold cursor-pointer "> Home
  • -
  • - Home - -
  • +
  • Get Started diff --git a/frontend/src/pages/theme.css b/frontend/src/pages/theme.css new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/utils/apitask.js b/frontend/src/utils/apitask.js new file mode 100644 index 0000000..ff1dd61 --- /dev/null +++ b/frontend/src/utils/apitask.js @@ -0,0 +1,33 @@ +import axios from "axios"; + +const API = axios.create({ + baseURL: "http://localhost:8000/tasks", + withCredentials: true, // Required for cookies (refresh tokens) +}); + +API.interceptors.request.use((config) => { + const token = localStorage.getItem("token"); + if (token) { + console.log("Adding Authorization header:", token); // Log token for debugging + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }); + + +/* ----------- Task API Calls ----------- */ + +// Create a new task +export const createTask = (taskData) => API.post("/", taskData); + +// Get all tasks for the current user +export const getUserTasks = () => API.get("/getTasks"); + +// Get a specific task by ID +export const getTaskById = (taskId) => API.get(`/${taskId}`); + +// Update a task +export const updateTask = (taskId, taskData) => API.patch(`/${taskId}`, taskData); + +// Delete a task +export const deleteTask = (taskId) => API.delete(`/${taskId}`); diff --git a/frontend/src/utils/assignmentapi.js b/frontend/src/utils/assignmentapi.js new file mode 100644 index 0000000..c0106e8 --- /dev/null +++ b/frontend/src/utils/assignmentapi.js @@ -0,0 +1,32 @@ +import axios from "axios"; + +const API = axios.create({ + baseURL: "http://localhost:8000/assignments", // Update with your actual backend URL + withCredentials: true, // Required for cookies (refresh tokens) +}); + +// Add JWT to headers if available +API.interceptors.request.use((config) => { + const token = localStorage.getItem("token"); + if (token) config.headers.Authorization = `Bearer ${token}`; + return config; +}); + +/* ----------- Assignment API Calls ----------- */ + +// Create a new assignment +export const createAssignment = (formData) => + API.post("/", formData, { headers: { "Content-Type": "multipart/form-data" } }); + +// Get all assignments for the current user +export const getUserAssignments = () => API.get("/"); + +// Get a specific assignment by ID +export const getAssignmentById = (assignmentId) => API.get(`/${assignmentId}`); + +// Update an assignment +export const updateAssignment = (assignmentId, formData) => + API.put(`/${assignmentId}`, formData, { headers: { "Content-Type": "multipart/form-data" } }); + +// Delete an assignment +export const deleteAssignment = (assignmentId) => API.delete(`/${assignmentId}`); diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index 1e13fd8..cef429f 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1,7 +1,7 @@ +// tailwind.config.js import daisyui from 'daisyui'; - export default { content: [ "./src/**/*.{html,js,ts,jsx,tsx}", @@ -9,5 +9,6 @@ export default { theme: { extend: {}, }, - plugins: [daisyui], + plugins: [daisyui], // DaisyUI plugin added here + darkMode: 'class', // Enable dark mode using class (this makes it toggleable) }; From 22718d3b1edb4dfc84e7e08cfc645c9912a57cc5 Mon Sep 17 00:00:00 2001 From: Venu4i Date: Tue, 4 Feb 2025 18:47:33 +0530 Subject: [PATCH 05/10] feat : calender --- frontend/pages/Calender.jsx | 78 +++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 frontend/pages/Calender.jsx diff --git a/frontend/pages/Calender.jsx b/frontend/pages/Calender.jsx new file mode 100644 index 0000000..7bc88bc --- /dev/null +++ b/frontend/pages/Calender.jsx @@ -0,0 +1,78 @@ +import React from "react"; +import clsx from "clsx"; +import { + eachDayOfInterval, + endOfMonth, + format, + getDay, + isToday, + startOfMonth, +} from "date-fns"; +import { useMemo } from "react"; + +const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +const EventCalendar = ({ events }) => { + const currentDate = new Date(); + const firstDayOfMonth = startOfMonth(currentDate); + const lastDayOfMonth = endOfMonth(currentDate); + + const daysInMonth = eachDayOfInterval({ + start: firstDayOfMonth, + end: lastDayOfMonth, + }); + + const startingDayIndex = getDay(firstDayOfMonth); + + const eventsByDate = useMemo(() => { + return events.reduce((acc, event) => { + const dateKey = format(event.date, "yyyy-MM-dd"); + if (!acc[dateKey]) { + acc[dateKey] = []; + } + acc[dateKey].push(event); + return acc; + }, {}); + }, [events]); + + return ( +
    +
    +

    {format(currentDate, "MMMM yyyy")}

    +
    +
    + {WEEKDAYS.map((day) => ( +
    + {day} +
    + ))} + {Array.from({ length: startingDayIndex }).map((_, index) => ( +
    + ))} + {daysInMonth.map((day, index) => { + const dateKey = format(day, "yyyy-MM-dd"); + const todaysEvents = eventsByDate[dateKey] || []; + return ( +
    + {/* other dys */} + {format(day, "d")} + {todaysEvents.map((event) => ( +
    + {event.title} +
    + ))} +
    + ); + })} +
    +
    + ); +}; + +export default EventCalendar; From 59009f980a54ab456892f70aac5d9ee9b7be4b6f Mon Sep 17 00:00:00 2001 From: Venu4i Date: Tue, 4 Feb 2025 19:10:51 +0530 Subject: [PATCH 06/10] feat: Calender for dashboard --- frontend/pages/Calender.jsx | 81 +++++--------------------------- frontend/pages/EventCalender.jsx | 78 ++++++++++++++++++++++++++++++ frontend/pages/Layout3.jsx | 21 +++++++++ 3 files changed, 111 insertions(+), 69 deletions(-) create mode 100644 frontend/pages/EventCalender.jsx create mode 100644 frontend/pages/Layout3.jsx diff --git a/frontend/pages/Calender.jsx b/frontend/pages/Calender.jsx index 7bc88bc..9ef40e5 100644 --- a/frontend/pages/Calender.jsx +++ b/frontend/pages/Calender.jsx @@ -1,78 +1,21 @@ import React from "react"; -import clsx from "clsx"; -import { - eachDayOfInterval, - endOfMonth, - format, - getDay, - isToday, - startOfMonth, -} from "date-fns"; -import { useMemo } from "react"; +import { addDays, subDays } from "date-fns"; +import EventCalendar from "./EventCalender"; -const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; - -const EventCalendar = ({ events }) => { - const currentDate = new Date(); - const firstDayOfMonth = startOfMonth(currentDate); - const lastDayOfMonth = endOfMonth(currentDate); - - const daysInMonth = eachDayOfInterval({ - start: firstDayOfMonth, - end: lastDayOfMonth, - }); - - const startingDayIndex = getDay(firstDayOfMonth); - - const eventsByDate = useMemo(() => { - return events.reduce((acc, event) => { - const dateKey = format(event.date, "yyyy-MM-dd"); - if (!acc[dateKey]) { - acc[dateKey] = []; - } - acc[dateKey].push(event); - return acc; - }, {}); - }, [events]); +const Calender =()=> { + const events = [ + { date: new Date("2025-02-10"), title: "Assgn" }, ]; return ( -
    -
    -

    {format(currentDate, "MMMM yyyy")}

    -
    -
    - {WEEKDAYS.map((day) => ( -
    - {day} -
    - ))} - {Array.from({ length: startingDayIndex }).map((_, index) => ( -
    - ))} - {daysInMonth.map((day, index) => { - const dateKey = format(day, "yyyy-MM-dd"); - const todaysEvents = eventsByDate[dateKey] || []; - return ( -
    - {/* other dys */} - {format(day, "d")} - {todaysEvents.map((event) => ( -
    - {event.title} -
    - ))} -
    - ); - })} +
    +
    +

    + My Event Calendar +

    +
    ); }; -export default EventCalendar; +export default Calender; diff --git a/frontend/pages/EventCalender.jsx b/frontend/pages/EventCalender.jsx new file mode 100644 index 0000000..7bc88bc --- /dev/null +++ b/frontend/pages/EventCalender.jsx @@ -0,0 +1,78 @@ +import React from "react"; +import clsx from "clsx"; +import { + eachDayOfInterval, + endOfMonth, + format, + getDay, + isToday, + startOfMonth, +} from "date-fns"; +import { useMemo } from "react"; + +const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +const EventCalendar = ({ events }) => { + const currentDate = new Date(); + const firstDayOfMonth = startOfMonth(currentDate); + const lastDayOfMonth = endOfMonth(currentDate); + + const daysInMonth = eachDayOfInterval({ + start: firstDayOfMonth, + end: lastDayOfMonth, + }); + + const startingDayIndex = getDay(firstDayOfMonth); + + const eventsByDate = useMemo(() => { + return events.reduce((acc, event) => { + const dateKey = format(event.date, "yyyy-MM-dd"); + if (!acc[dateKey]) { + acc[dateKey] = []; + } + acc[dateKey].push(event); + return acc; + }, {}); + }, [events]); + + return ( +
    +
    +

    {format(currentDate, "MMMM yyyy")}

    +
    +
    + {WEEKDAYS.map((day) => ( +
    + {day} +
    + ))} + {Array.from({ length: startingDayIndex }).map((_, index) => ( +
    + ))} + {daysInMonth.map((day, index) => { + const dateKey = format(day, "yyyy-MM-dd"); + const todaysEvents = eventsByDate[dateKey] || []; + return ( +
    + {/* other dys */} + {format(day, "d")} + {todaysEvents.map((event) => ( +
    + {event.title} +
    + ))} +
    + ); + })} +
    +
    + ); +}; + +export default EventCalendar; diff --git a/frontend/pages/Layout3.jsx b/frontend/pages/Layout3.jsx new file mode 100644 index 0000000..f4fbdb9 --- /dev/null +++ b/frontend/pages/Layout3.jsx @@ -0,0 +1,21 @@ +import React from "react"; +import { addDays, subDays } from "date-fns"; +import EventCalendar from "../src/calender/calender"; + +const Events = () => { + const events = [ + { date: new Date("2025-02-10"), title: "Assgn" }, ]; + + return ( +
    +
    +

    + My Event Calendar +

    + +
    +
    + ); +}; + +export default Events; From a620d6d35f4b61a89ec7c50bf8b16bdd4d77afe7 Mon Sep 17 00:00:00 2001 From: shreesriv12 Date: Tue, 4 Feb 2025 20:06:23 +0530 Subject: [PATCH 07/10] added zenquote api --- frontend/package-lock.json | 9 +---- frontend/package.json | 2 +- frontend/src/components/QuotesDisplay.jsx | 43 +++++++++++++++++++++++ frontend/src/components/Sidebar.jsx | 27 +++++++------- frontend/src/pages/Dashboard.jsx | 22 +++++++++++- 5 files changed, 80 insertions(+), 23 deletions(-) create mode 100644 frontend/src/components/QuotesDisplay.jsx diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e90d46a..f6cbce6 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -16,6 +16,7 @@ "@tailwindcss/vite": "^4.0.1", "axios": "^1.7.9", "cors": "^2.8.5", + "daisyui": "^4.12.23", "motion": "^12.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -29,7 +30,6 @@ "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@vitejs/plugin-react": "^4.3.4", - "daisyui": "^4.12.23", "eslint": "^9.17.0", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", @@ -2028,7 +2028,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 6" @@ -2177,7 +2176,6 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.8.0.tgz", "integrity": "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==", - "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -2199,7 +2197,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", - "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -2218,7 +2215,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/culori/-/culori-3.3.0.tgz", "integrity": "sha512-pHJg+jbuFsCjz9iclQBqyL3B2HLCBF71BwVNujUYEvCeQMvV97R59MNK3R2+jgJ3a1fcZgI9B3vYgz8lzr/BFQ==", - "dev": true, "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -2228,7 +2224,6 @@ "version": "4.12.23", "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.23.tgz", "integrity": "sha512-EM38duvxutJ5PD65lO/AFMpcw+9qEy6XAZrTpzp7WyaPeO/l+F/Qiq0ECHHmFNcFXh5aVoALY4MGrrxtCiaQCQ==", - "dev": true, "license": "MIT", "dependencies": { "css-selector-tokenizer": "^0.8", @@ -2897,7 +2892,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", "integrity": "sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==", - "dev": true, "license": "MIT" }, "node_modules/file-entry-cache": { @@ -4532,7 +4526,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", - "dev": true, "license": "MIT", "dependencies": { "camelcase-css": "^2.0.1" diff --git a/frontend/package.json b/frontend/package.json index 2719d48..b5bd493 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "@tailwindcss/vite": "^4.0.1", "axios": "^1.7.9", "cors": "^2.8.5", + "daisyui": "^4.12.23", "motion": "^12.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -31,7 +32,6 @@ "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", "@vitejs/plugin-react": "^4.3.4", - "daisyui": "^4.12.23", "eslint": "^9.17.0", "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", diff --git a/frontend/src/components/QuotesDisplay.jsx b/frontend/src/components/QuotesDisplay.jsx new file mode 100644 index 0000000..1d9bb86 --- /dev/null +++ b/frontend/src/components/QuotesDisplay.jsx @@ -0,0 +1,43 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import { useTheme } from "../context/ThemeContext"; +const QuoteDisplay = () => { + const { theme } = useTheme(); // Get theme state + const [quote, setQuote] = useState(""); + const [author, setAuthor] = useState(""); + const [loading, setLoading] = useState(true); + + // Fetch a random quote from ZenQuote API + const fetchQuote = async () => { + try { + const response = await axios.get("http://localhost:8000/quote"); + setQuote(response.data[0].q); + setAuthor(response.data[0].a); + setLoading(false); + } catch (error) { + console.error("Error fetching quote:", error); + setLoading(false); + } + }; + + useEffect(() => { + fetchQuote(); // Fetch the quote when the component mounts + }, []); + + return ( +
    + {loading ? ( +

    Loading quote...

    + ) : ( +
    +
    + {quote} +
    +

    - {author}

    +
    + )} +
    + ); +}; + +export default QuoteDisplay; diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index 71f1cf1..d2a6256 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -3,6 +3,7 @@ import { useTheme } from "../context/ThemeContext"; // Import Theme Context import { FaChartBar, FaCalendarAlt, FaFacebookMessenger, FaUsersCog } from "react-icons/fa"; import { IoIosLogOut } from "react-icons/io"; import { useState } from "react"; +import { Link } from "react-router-dom"; const Sidebar = () => { const { theme } = useTheme(); // Get theme state @@ -32,43 +33,43 @@ const Sidebar = () => {
    • - Dashboard - +
    • - HeatMap - +
    • - Chat - +
    • - - Users - + Calender +
    • diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index c901ae4..b718a82 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -6,7 +6,8 @@ import ThemeSwitcher from '../components/ThemeSwitcher'; import { ThemeProvider } from '../context/ThemeContext' import Header from '../components/Header'; import Sidebar from '../components/Sidebar'; - +import QuoteDisplay from '../components/QuotesDisplay'; +import TaskManager from '../components/Taskmanager'; const Dashboard = () => { @@ -17,6 +18,25 @@ const Dashboard = () => {
      +
      + +
      +
      +
      + Shoes +
      +
      +

      Shoes!

      +

      If a dog chews shoes whose shoes does he choose?

      +
      + +
      +
      +
      + +
      From 6b95fd3d900b5def6f0a2a801ed3ed308eed5e76 Mon Sep 17 00:00:00 2001 From: shreesriv12 Date: Thu, 6 Feb 2025 20:39:41 +0530 Subject: [PATCH 08/10] feat:calender feature implemented --- frontend/package-lock.json | 221 +++++++++++++++++- frontend/package.json | 3 + frontend/src/App.jsx | 9 + .../src/components/AssignmentsManager.jsx | 94 ++++++++ frontend/src/components/Calender.jsx | 35 +++ frontend/src/components/EventCalender.jsx | 77 ++++++ frontend/src/components/Header.jsx | 9 +- frontend/src/components/QuotesDisplay.jsx | 6 +- frontend/src/components/Sidebar.jsx | 38 ++- frontend/src/components/TaskManager.jsx | 175 ++++++++------ frontend/src/context/AssignmentContext.jsx | 104 +++++++++ frontend/src/context/TaskContext.jsx | 65 +++--- frontend/src/main.jsx | 3 + frontend/src/pages/Chats.jsx | 24 ++ frontend/src/pages/Dashboard.jsx | 18 +- frontend/src/pages/SignOut.jsx | 0 frontend/src/utils/api.js | 36 +-- frontend/src/utils/apitask.js | 2 +- frontend/src/utils/assignmentapi.js | 25 +- frontend/src/utils/messageapi.js | 40 ++++ 20 files changed, 802 insertions(+), 182 deletions(-) create mode 100644 frontend/src/components/AssignmentsManager.jsx create mode 100644 frontend/src/components/Calender.jsx create mode 100644 frontend/src/components/EventCalender.jsx create mode 100644 frontend/src/context/AssignmentContext.jsx create mode 100644 frontend/src/pages/Chats.jsx create mode 100644 frontend/src/pages/SignOut.jsx create mode 100644 frontend/src/utils/messageapi.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f6cbce6..7c75b30 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,8 +15,11 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@tailwindcss/vite": "^4.0.1", "axios": "^1.7.9", + "clsx": "^2.1.1", "cors": "^2.8.5", "daisyui": "^4.12.23", + "date-fns": "^4.1.0", + "mongoose": "^8.9.7", "motion": "^12.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1082,6 +1085,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.32.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz", @@ -1651,6 +1663,21 @@ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", "license": "MIT" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", @@ -1964,6 +1991,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bson": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.2.tgz", + "integrity": "sha512-5afhLTjqDSA3akH56E+/2J6kTDuSIlBxyXPdQslj9hcIgOUE378xdOfZvC/9q3LifJNI6KR/juZ+d0NRNYBwXg==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2080,6 +2116,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2293,11 +2338,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3843,6 +3897,15 @@ "node": ">=4.0" } }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4150,6 +4213,12 @@ "node": ">= 0.4" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -4184,6 +4253,84 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", + "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.9.7", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.7.tgz", + "integrity": "sha512-mvNXmU0V8qZzMR/qoK2mjT4Ti2ALdtfS0teK+twxhlGkwzOD76V02/zWajTu2MJ7QyEmZe9OWvnJsIY0iAuX3Q==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.1", + "kareem": "2.6.3", + "mongodb": "~6.12.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, "node_modules/motion": { "version": "12.0.6", "resolved": "https://registry.npmjs.org/motion/-/motion-12.0.6.tgz", @@ -4225,11 +4372,31 @@ "integrity": "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==", "license": "MIT" }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -4578,7 +4745,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5018,6 +5184,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5027,6 +5199,15 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -5247,6 +5428,18 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -5490,6 +5683,28 @@ } } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index b5bd493..6fd1fd4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,8 +17,11 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@tailwindcss/vite": "^4.0.1", "axios": "^1.7.9", + "clsx": "^2.1.1", "cors": "^2.8.5", "daisyui": "^4.12.23", + "date-fns": "^4.1.0", + "mongoose": "^8.9.7", "motion": "^12.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 55acbd0..dbb43d8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,6 +6,10 @@ import Home from './pages/Home' import Login from './pages/Login' import Signup from './pages/Signup' import Dashboard from './pages/Dashboard' +import TaskManager from './components/Taskmanager' +import AssignmentsManager from './components/AssignmentsManager' +import Chats from './pages/Chats' +import Calender from './components/Calender' function App() { return ( @@ -15,6 +19,11 @@ function App() { } /> } /> } /> + } /> + }/> + }/> + }/>; + ) diff --git a/frontend/src/components/AssignmentsManager.jsx b/frontend/src/components/AssignmentsManager.jsx new file mode 100644 index 0000000..3b6d6cd --- /dev/null +++ b/frontend/src/components/AssignmentsManager.jsx @@ -0,0 +1,94 @@ +import React, { useState, useEffect } from "react"; +import { useAssignments } from "../context/AssignmentContext"; +import Sidebar from "./Sidebar"; +import Header from "./Header"; +import { ThemeProvider } from "../context/ThemeContext"; + +export default function AssignmentsPage() { + + const { assignments, fetchAssignments, addAssignment, removeAssignment, isLoading, error } = useAssignments(); + const [assignmentData, setAssignmentData] = useState({ title: "", description: "", due_date: "", docs: null }); + const [editingId, setEditingId] = useState(null); + + useEffect(() => { + fetchAssignments(); + }, []); + + const handleFileChange = (e) => { + setAssignmentData({ ...assignmentData, docs: e.target.files }); + }; + + const handleEdit = (assignment) => { + setEditingId(assignment._id); + setAssignmentData({ title: assignment.title, description: assignment.description, due_date: assignment.due_date || "", docs: null }); + }; + + const handleCancelEdit = () => { + setEditingId(null); + setAssignmentData({ title: "", description: "", due_date: "", docs: null }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + const formData = new FormData(); + formData.append("title", assignmentData.title); + formData.append("description", assignmentData.description); + formData.append("due_date", assignmentData.due_date); + if (assignmentData.docs) { + Array.from(assignmentData.docs).forEach((file) => formData.append("docs", file)); + } + if (editingId) { + await addAssignment(editingId, formData); + } else { + await addAssignment(formData); + } + handleCancelEdit(); + fetchAssignments(); + }; + + return ( + +
      + +
      + + +
      +
      +

      {editingId ? "Edit Assignment" : "Assignments"}

      + {error &&

      {error}

      } +
      + + setAssignmentData({ ...assignmentData, title: e.target.value })} className="input input-bordered w-full" /> + + setAssignmentData({ ...assignmentData, due_date: e.target.value })} className="input input-bordered w-full" /> + + + + +
      + + {editingId && } +
      +
      +
      +
      + {isLoading &&

      Loading assignments...

      } +
      + {assignments.map((assignment) => ( +
      +
      +

      {assignment.title}

      +

      {assignment.description}

      +

      Due: {assignment.due_date || "No date set"}

      +
      + + +
      +
      +
      + ))} +
      +
      + ); +} \ No newline at end of file diff --git a/frontend/src/components/Calender.jsx b/frontend/src/components/Calender.jsx new file mode 100644 index 0000000..76cf272 --- /dev/null +++ b/frontend/src/components/Calender.jsx @@ -0,0 +1,35 @@ +import React from "react"; +import { useTasks } from "../context/TaskContext"; +import EventCalendar from "./EventCalender"; +import { ThemeProvider } from "../context/ThemeContext"; +import Sidebar from "./Sidebar"; +import Header from "./Header"; +import { useTheme } from "../context/ThemeContext"; + +const Calendar = () => { + const { tasks } = useTasks(); + const theme=useTheme(); + const events = tasks + .filter((task) => task.due_date) // Only tasks with due dates + .map((task) => ({ + date: new Date(task.due_date), + title: task.title, + })); + + return ( + +
      + +
      +
      +

      + My Event Calendar +

      + +
      +
      + + ); +}; + +export default Calendar; diff --git a/frontend/src/components/EventCalender.jsx b/frontend/src/components/EventCalender.jsx new file mode 100644 index 0000000..a582f3c --- /dev/null +++ b/frontend/src/components/EventCalender.jsx @@ -0,0 +1,77 @@ +import React from "react"; +import clsx from "clsx"; +import { + eachDayOfInterval, + endOfMonth, + format, + getDay, + isToday, + startOfMonth, +} from "date-fns"; +import { useMemo } from "react"; + +const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +const EventCalendar = ({ events = [] }) => { + const currentDate = new Date(); + const firstDayOfMonth = startOfMonth(currentDate); + const lastDayOfMonth = endOfMonth(currentDate); + + const daysInMonth = eachDayOfInterval({ + start: firstDayOfMonth, + end: lastDayOfMonth, + }); + + const startingDayIndex = getDay(firstDayOfMonth); + + const eventsByDate = useMemo(() => { + return events.reduce((acc, event) => { + const dateKey = format(event.date, "yyyy-MM-dd"); + if (!acc[dateKey]) { + acc[dateKey] = []; + } + acc[dateKey].push(event); + return acc; + }, {}); + }, [events]); + + return ( +
      +
      +

      {format(currentDate, "MMMM yyyy")}

      +
      +
      + {WEEKDAYS.map((day) => ( +
      + {day} +
      + ))} + {Array.from({ length: startingDayIndex }).map((_, index) => ( +
      + ))} + {daysInMonth.map((day, index) => { + const dateKey = format(day, "yyyy-MM-dd"); + const todaysEvents = eventsByDate[dateKey] || []; + return ( +
      + {format(day, "d")} + {todaysEvents.map((event) => ( +
      + {event.title} +
      + ))} +
      + ); + })} +
      +
      + ); +}; + +export default EventCalendar; diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 8ac7df2..796d2b9 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -18,12 +18,12 @@ const Header = () => { diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index d2a6256..a1b4179 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -4,6 +4,21 @@ import { FaChartBar, FaCalendarAlt, FaFacebookMessenger, FaUsersCog } from "reac import { IoIosLogOut } from "react-icons/io"; import { useState } from "react"; import { Link } from "react-router-dom"; +import { AssignmentProvider } from "../context/AssignmentContext"; +import { logout } from "../utils/api"; + + +const handleLogout = async () => { + try { + await logout(); // This will call the logout API on your backend + localStorage.removeItem("token"); // Optionally remove the access token from local storage + window.location.href = "/"; // Redirect to the login page + } catch (error) { + console.error("Error logging out:", error); + alert("Error logging out. Please try again."); + } +}; + const Sidebar = () => { const { theme } = useTheme(); // Get theme state @@ -13,7 +28,10 @@ const Sidebar = () => { setIsSidebarOpen(!isSidebarOpen); }; + + return ( +
      {/* Sidebar toggle button */}
    • +
    • + + + Assignments + + +
    • @@ -54,7 +82,7 @@ const Sidebar = () => {
    • @@ -64,11 +92,11 @@ const Sidebar = () => {
    • - Calender + Progress
    • @@ -78,7 +106,7 @@ const Sidebar = () => { className={`${theme === "dark" ? "text-white hover:bg-gray-700" : "text-black hover:bg-gray-100 "}`} > - Sign Out +
    diff --git a/frontend/src/components/TaskManager.jsx b/frontend/src/components/TaskManager.jsx index 35df2d9..4c96fb9 100644 --- a/frontend/src/components/TaskManager.jsx +++ b/frontend/src/components/TaskManager.jsx @@ -1,91 +1,124 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; import { useTasks } from "../context/TaskContext"; +import { useTheme } from "../context/ThemeContext"; export default function TaskManager() { + const { theme } = useTheme(); const { tasks, - newTask, - setNewTask, - editingTask, - setEditingTask, - isLoading, + fetchTasks, handleCreate, handleUpdate, handleDelete, + isLoading, error, } = useTasks(); + const [taskData, setTaskData] = useState({ title: "", description: "", due_date: "" }); + const [editingId, setEditingId] = useState(null); + + useEffect(() => { + fetchTasks(); + }, []); + + const handleEdit = (task) => { + setEditingId(task._id); + setTaskData({ title: task.title, description: task.description, due_date: task.due_date || "" }); + }; + + const handleCancelEdit = () => { + setEditingId(null); + setTaskData({ title: "", description: "", due_date: "" }); + }; + + const handleSubmit = async () => { + if (editingId) { + await handleUpdate(editingId, taskData); + } else { + await handleCreate(taskData); + } + handleCancelEdit(); + fetchTasks(); + }; + return ( - -
    -

    Task Manager

    - {error &&

    {error}

    } -
    - setNewTask({ ...newTask, title: e.target.value })} - className="w-full p-2 border rounded" - /> - setNewTask({ ...newTask, due_date: e.target.value })} - className="w-full p-2 border rounded" - /> - - -
    - {isLoading &&

    Loading...

    } -
      - {tasks.map((task) => ( -
    • - {editingTask && editingTask._id === task._id ? ( - setEditingTask({ ...editingTask, title: e.target.value })} - className="p-1 border rounded" - /> - ) : ( - {task.title} - )} -
      - {editingTask && editingTask._id === task._id ? ( - - ) : ( - - )} - + {editingId && }
      -
    • +
    +
    +
    + + {/* Loading State */} + {isLoading &&

    Loading tasks...

    } + + {/* Task List */} +
    + {tasks.map((task) => ( +
    +
    +

    {task.title}

    +

    {task.description}

    +

    Due: {task.due_date || "No date set"}

    + + {/* Task Actions */} +
    +
    + +
    + +
    + +
    + +
    +
    +
    ))} - +
    + +
    - ); } diff --git a/frontend/src/context/AssignmentContext.jsx b/frontend/src/context/AssignmentContext.jsx new file mode 100644 index 0000000..a49c724 --- /dev/null +++ b/frontend/src/context/AssignmentContext.jsx @@ -0,0 +1,104 @@ +import React, { createContext, useState, useContext, useEffect } from "react"; +import { + createAssignment, + getUserAssignments, + updateAssignment, + deleteAssignment +} from "../utils/assignmentapi"; // Import API functions + +// Create Context +const AssignmentContext = createContext(); + +// Custom hook to use AssignmentContext +export const useAssignments = () => { + return useContext(AssignmentContext); +}; + +// Function to validate MongoDB ObjectId +const isValidObjectId = (id) => /^[a-f\d]{24}$/i.test(id); + +// AssignmentProvider Component +export const AssignmentProvider = ({ children }) => { + const [assignments, setAssignments] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + // Fetch assignments from API + const fetchAssignments = async () => { + setIsLoading(true); + try { + const response = await getUserAssignments(); + setAssignments(response.data.data); // Assuming response.data.data holds the assignments array + } catch (err) { + setError(err.response?.data?.message || "Error fetching assignments"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Add a new assignment + const handleCreate = async (formData) => { + setIsLoading(true); + try { + await createAssignment(formData); + fetchAssignments(); // Refresh assignment list + } catch (err) { + setError(err.response?.data?.message || "Error creating assignment"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Update an existing assignment + const handleUpdate = async (assignmentId, formData) => { + if (!isValidObjectId(assignmentId)) { + setError("Invalid Assignment ID"); + return; + } + + setIsLoading(true); + try { + await updateAssignment(assignmentId, formData); + fetchAssignments(); // Refresh assignment list + } catch (err) { + setError(err.response?.data?.message || "Error updating assignment"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Delete an assignment + const handleDelete = async (assignmentId) => { + if (!isValidObjectId(assignmentId)) { + setError("Invalid Assignment ID"); + return; + } + + setIsLoading(true); + try { + await deleteAssignment(assignmentId); + fetchAssignments(); // Refresh assignment list + } catch (err) { + setError(err.response?.data?.message || "Error deleting assignment"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Load assignments when component mounts + useEffect(() => { + fetchAssignments(); + }, []); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/frontend/src/context/TaskContext.jsx b/frontend/src/context/TaskContext.jsx index 6647bc8..74c9503 100644 --- a/frontend/src/context/TaskContext.jsx +++ b/frontend/src/context/TaskContext.jsx @@ -4,27 +4,29 @@ import { createTask, getUserTasks, updateTask, deleteTask } from "../utils/apita // Create Context const TaskContext = createContext(); -// Custom hook to use the TaskContext +// Custom hook to use TaskContext export const useTasks = () => { return useContext(TaskContext); }; +// Function to validate MongoDB ObjectId +const isValidObjectId = (id) => /^[a-f\d]{24}$/i.test(id); + + // TaskProvider Component export const TaskProvider = ({ children }) => { const [tasks, setTasks] = useState([]); - const [newTask, setNewTask] = useState({ title: "", description: "", due_date: "" }); - const [editingTask, setEditingTask] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - // Fetch tasks from the API + // Fetch tasks from API const fetchTasks = async () => { setIsLoading(true); try { const response = await getUserTasks(); - setTasks(response.data.data); + setTasks(response.data.data); // Assuming response.data.data holds the tasks array } catch (err) { - setError("Error fetching tasks"); + setError(err.response?.data?.message || "Error fetching tasks"); console.error(err); } finally { setIsLoading(false); @@ -32,15 +34,13 @@ export const TaskProvider = ({ children }) => { }; // Add a new task - const handleCreate = async () => { - if (!newTask.title || !newTask.due_date) return; + const handleCreate = async (taskData) => { setIsLoading(true); try { - await createTask(newTask); - setNewTask({ title: "", description: "", due_date: "" }); - fetchTasks(); // Re-fetch tasks after adding + await createTask(taskData); + fetchTasks(); // Refresh task list } catch (err) { - setError("Error creating task"); + setError(err.response?.data?.message || "Error creating task"); console.error(err); } finally { setIsLoading(false); @@ -48,15 +48,18 @@ export const TaskProvider = ({ children }) => { }; // Update an existing task - const handleUpdate = async (taskId) => { - if (!editingTask) return; + const handleUpdate = async (taskId, taskData) => { + if (!isValidObjectId(taskId)) { + setError("Invalid Task ID"); + return; + } + setIsLoading(true); try { - await updateTask(taskId, editingTask); - setEditingTask(null); - fetchTasks(); // Re-fetch tasks after updating + await updateTask(taskId, taskData); + fetchTasks(); // Refresh task list } catch (err) { - setError("Error updating task"); + setError(err.response?.data?.message || "Error updating task"); console.error(err); } finally { setIsLoading(false); @@ -65,38 +68,30 @@ export const TaskProvider = ({ children }) => { // Delete a task const handleDelete = async (taskId) => { + if (!isValidObjectId(taskId)) { + setError("Invalid Task ID"); + return; + } + setIsLoading(true); try { await deleteTask(taskId); - fetchTasks(); // Re-fetch tasks after deleting + fetchTasks(); // Refresh task list } catch (err) { - setError("Error deleting task"); + setError(err.response?.data?.message || "Error deleting task"); console.error(err); } finally { setIsLoading(false); } }; - // Initialize by fetching tasks + // Load tasks when component mounts useEffect(() => { fetchTasks(); }, []); return ( - + {children} ); diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 638862c..56ed2a9 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -7,10 +7,12 @@ import { UserProvider } from './context/UserContext.jsx' import { BrowserRouter } from 'react-router-dom' import { AuthProvider } from './context/AuthContext.jsx' import { TaskProvider } from './context/TaskContext.jsx' +import { AssignmentProvider } from './context/AssignmentContext.jsx' createRoot(document.getElementById('root')).render( + @@ -20,6 +22,7 @@ createRoot(document.getElementById('root')).render( + , ) diff --git a/frontend/src/pages/Chats.jsx b/frontend/src/pages/Chats.jsx new file mode 100644 index 0000000..4fae5a8 --- /dev/null +++ b/frontend/src/pages/Chats.jsx @@ -0,0 +1,24 @@ +import React from 'react' +import { ThemeProvider } from '../context/ThemeContext' +import Sidebar from '../components/Sidebar' +import Header from '../components/Header'; +import ThemeSwitcher from '../components/ThemeSwitcher'; +import { getUsers } from '../utils/messageapi'; +import { useState,useEffect } from 'react'; + + + + + + +const Chats = () => { + return ( + +
    + + + + ) +}; + +export default Chats diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index b718a82..fabb78c 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -8,6 +8,7 @@ import Header from '../components/Header'; import Sidebar from '../components/Sidebar'; import QuoteDisplay from '../components/QuotesDisplay'; import TaskManager from '../components/Taskmanager'; +import { TaskProvider } from '../context/TaskContext'; const Dashboard = () => { @@ -21,22 +22,9 @@ const Dashboard = () => {
    -
    -
    - Shoes -
    -
    -

    Shoes!

    -

    If a dog chews shoes whose shoes does he choose?

    -
    - -
    -
    -
    + - +
    diff --git a/frontend/src/pages/SignOut.jsx b/frontend/src/pages/SignOut.jsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 44de5c1..0555216 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -5,7 +5,7 @@ const API = axios.create({ withCredentials: true, // Required for cookies (refresh tokens) }); -// Add JWT to headers if available +// Add JWT to headers API.interceptors.request.use((config) => { const token = localStorage.getItem("token"); if (token) config.headers.Authorization = `Bearer ${token}`; @@ -20,37 +20,3 @@ export const logout = () => API.post("/logout"); export const refreshToken = () => API.post("/refresh-token"); export const getCurrentUser = () => API.get("/current-user"); -// /*-----------Tasks assignment calls-----------*/ -// // Create a new task -// export const createTask = (taskData) => API.post("/tasks", taskData); - -// // Get all tasks for the current user -// export const getUserTasks = () => API.get("/tasks"); - -// // Update a task -// export const updateTask = (taskId, taskData) => -// API.put(`/tasks/${taskId}`, taskData); - -// // Delete a task -// export const deleteTask = (taskId) => API.delete(`/tasks/${taskId}`); - - - - - -// /* ---------- Assignment API Calls ----------*/ - - -// export const createAssignment = (formData) => -// API.post("/assignments", formData, { headers: { "Content-Type": "multipart/form-data" } }); - -// // Get all assignments for the current user -// export const getUserAssignments = () => API.get("/assignments"); - -// // Update an assignment -// export const updateAssignment = (assignmentId, formData) => -// API.put(`/assignments/${assignmentId}`, formData, { headers: { "Content-Type": "multipart/form-data" } }); - -// // Delete an assignment -// export const deleteAssignment = (assignmentId) => API.delete(`/assignments/${assignmentId}`); - diff --git a/frontend/src/utils/apitask.js b/frontend/src/utils/apitask.js index ff1dd61..ca79ac4 100644 --- a/frontend/src/utils/apitask.js +++ b/frontend/src/utils/apitask.js @@ -15,7 +15,7 @@ API.interceptors.request.use((config) => { }); -/* ----------- Task API Calls ----------- */ + // Create a new task export const createTask = (taskData) => API.post("/", taskData); diff --git a/frontend/src/utils/assignmentapi.js b/frontend/src/utils/assignmentapi.js index c0106e8..7538c52 100644 --- a/frontend/src/utils/assignmentapi.js +++ b/frontend/src/utils/assignmentapi.js @@ -1,32 +1,33 @@ import axios from "axios"; const API = axios.create({ - baseURL: "http://localhost:8000/assignments", // Update with your actual backend URL - withCredentials: true, // Required for cookies (refresh tokens) + baseURL: "http://localhost:8000/assignments", // Backend URL + withCredentials: true, // Required for authentication (cookies, sessions, etc.) }); // Add JWT to headers if available API.interceptors.request.use((config) => { const token = localStorage.getItem("token"); - if (token) config.headers.Authorization = `Bearer ${token}`; + if (token) { + console.log("Adding Authorization header:", token); // Log token for debugging + config.headers.Authorization = `Bearer ${token}`; + } return config; }); -/* ----------- Assignment API Calls ----------- */ +// ---------- Assignment API Calls ---------- -// Create a new assignment +// Create a new assignment (supports multiple files) export const createAssignment = (formData) => API.post("/", formData, { headers: { "Content-Type": "multipart/form-data" } }); -// Get all assignments for the current user -export const getUserAssignments = () => API.get("/"); +// Fetch all assignments of the current user +export const getUserAssignments = () => API.get("/getAssignments"); -// Get a specific assignment by ID -export const getAssignmentById = (assignmentId) => API.get(`/${assignmentId}`); - -// Update an assignment +// Update an existing assignment export const updateAssignment = (assignmentId, formData) => - API.put(`/${assignmentId}`, formData, { headers: { "Content-Type": "multipart/form-data" } }); + API.patch(`/${assignmentId}`, formData, { headers: { "Content-Type": "multipart/form-data" } }); // Delete an assignment export const deleteAssignment = (assignmentId) => API.delete(`/${assignmentId}`); + diff --git a/frontend/src/utils/messageapi.js b/frontend/src/utils/messageapi.js new file mode 100644 index 0000000..92c8421 --- /dev/null +++ b/frontend/src/utils/messageapi.js @@ -0,0 +1,40 @@ +// api.js +import axios from "axios"; + +const API_URL = "http://localhost:5000"; // Adjust the URL as per your backend + +// Axios instance with JWT token header +const axiosInstance = axios.create({ + baseURL: API_URL, +}); + +axiosInstance.interceptors.request.use((config) => { + const token = localStorage.getItem("token"); // Assuming token is stored in localStorage + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +// Get all users +export const getUsers = async () => { + const response = await axiosInstance.get("/users"); + return response.data.data; +}; + +// Get messages for a specific user +export const getMessages = async (userId) => { + const response = await axiosInstance.get(`/messages/${userId}`); + return response.data.data; +}; + +// Send a message +export const sendMessage = async (receiverId, text, file) => { + const formData = new FormData(); + formData.append("text", text); + if (file) { + formData.append("img", file); + } + const response = await axiosInstance.post(`/send/${receiverId}`, formData); + return response.data.data; +}; From f8a8ff83afcf9b783bb3816f6318da0bdad56676 Mon Sep 17 00:00:00 2001 From: shreesriv12 Date: Thu, 6 Feb 2025 20:55:55 +0530 Subject: [PATCH 09/10] feat:calender feature implemented --- backend/package-lock.json | 79 +++++++ backend/package.json | 1 + backend/src/app.js | 19 +- .../src/controllers/assignment.controllers.js | 7 +- backend/src/controllers/message.controller.js | 93 ++++++++ backend/src/controllers/task.controller.js | 97 ++++++++ backend/src/models/message.models.js | 27 +++ backend/src/routes/assignment.routes.js | 2 +- backend/src/routes/message.routes.js | 17 ++ backend/src/routes/task.routes.js | 21 ++ backend/src/routes/user.routes.js | 8 +- backend/yarn.lock | 47 +++- frontend/package-lock.json | 221 +++++++++++++++++- frontend/package.json | 3 + frontend/src/App.jsx | 9 + .../src/components/AssignmentsManager.jsx | 94 ++++++++ frontend/src/components/Calender.jsx | 35 +++ frontend/src/components/EventCalender.jsx | 77 ++++++ frontend/src/components/Header.jsx | 9 +- frontend/src/components/QuotesDisplay.jsx | 6 +- frontend/src/components/Sidebar.jsx | 38 ++- frontend/src/components/TaskManager.jsx | 175 ++++++++------ frontend/src/context/AssignmentContext.jsx | 104 +++++++++ frontend/src/context/TaskContext.jsx | 65 +++--- frontend/src/main.jsx | 3 + frontend/src/pages/Chats.jsx | 24 ++ frontend/src/pages/Dashboard.jsx | 18 +- frontend/src/pages/SignOut.jsx | 0 frontend/src/utils/api.js | 36 +-- frontend/src/utils/apitask.js | 2 +- frontend/src/utils/assignmentapi.js | 25 +- frontend/src/utils/messageapi.js | 40 ++++ 32 files changed, 1213 insertions(+), 189 deletions(-) create mode 100644 backend/src/controllers/message.controller.js create mode 100644 backend/src/controllers/task.controller.js create mode 100644 backend/src/models/message.models.js create mode 100644 backend/src/routes/message.routes.js create mode 100644 backend/src/routes/task.routes.js create mode 100644 frontend/src/components/AssignmentsManager.jsx create mode 100644 frontend/src/components/Calender.jsx create mode 100644 frontend/src/components/EventCalender.jsx create mode 100644 frontend/src/context/AssignmentContext.jsx create mode 100644 frontend/src/pages/Chats.jsx create mode 100644 frontend/src/pages/SignOut.jsx create mode 100644 frontend/src/utils/messageapi.js diff --git a/backend/package-lock.json b/backend/package-lock.json index cc1bced..248e71f 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "axios": "^1.7.9", "bcrypt": "^5.1.1", "cloudinary": "^2.5.1", "cookie-parser": "^1.4.7", @@ -193,6 +194,23 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -399,6 +417,18 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -503,6 +533,15 @@ "ms": "2.0.0" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -724,6 +763,40 @@ "node": ">= 0.8" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1840,6 +1913,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/backend/package.json b/backend/package.json index 9c3117e..efc6859 100644 --- a/backend/package.json +++ b/backend/package.json @@ -10,6 +10,7 @@ }, "type": "module", "dependencies": { + "axios": "^1.7.9", "bcrypt": "^5.1.1", "cloudinary": "^2.5.1", "cookie-parser": "^1.4.7", diff --git a/backend/src/app.js b/backend/src/app.js index 5291369..efce5f9 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -1,7 +1,7 @@ import express from "express"; import cors from "cors"; import cookieParser from "cookie-parser"; - +import axios from "axios"; const app = express(); app.use( @@ -20,10 +20,25 @@ app.use(express.static("public")); // Cookie Parser configuration for tokens app.use(cookieParser()); -// TODO: Routes will go here +// TODO: Routes will go here ✔ import userRouter from "./routes/user.routes.js"; +import assignmentRouter from "./routes/assignment.routes.js"; +import taskRouter from "./routes/task.routes.js"; // Routes Declaration app.use("/users", userRouter); +app.use("/assignments", assignmentRouter); +app.use("/tasks", taskRouter); + +app.get('/quote', async (req, res) => { + try { + // Ensure you are hitting the correct API URL + const response = await axios.get("https://zenquotes.io/api/random"); + res.json(response.data); + } catch (error) { + console.error("Error fetching quote from ZenQuotes API:", error); + res.status(500).json({ error: "Failed to fetch quote from ZenQuotes" }); + } +}); export { app }; diff --git a/backend/src/controllers/assignment.controllers.js b/backend/src/controllers/assignment.controllers.js index de9761f..39493d4 100644 --- a/backend/src/controllers/assignment.controllers.js +++ b/backend/src/controllers/assignment.controllers.js @@ -3,7 +3,8 @@ import { ApiError } from "../utils/ApiError.js"; import { ApiResponse } from "../utils/ApiResponse.js"; import { asyncHandler } from "../utils/asyncHandler.js"; import { uploadToCloud } from "../utils/cloudinary.js"; - +import mongoose from "mongoose"; +import cloudinary from "cloudinary"; const createAssignment = asyncHandler(async (req, res) => { const { title, description, due_date } = req.body; @@ -31,6 +32,8 @@ const createAssignment = asyncHandler(async (req, res) => { user: req.user._id, }); + + const assignmentFromDB = await Assignment.findById(assignment._id); if (!assignmentFromDB) { @@ -63,7 +66,7 @@ const updateAssignment = asyncHandler(async (req, res) => { const { title, description, due_date } = req.body; // Validate ObjectId - if (!isValidObjectId(assignmentId)) { + if (!mongoose.Types.ObjectId.isValid(assignmentId)) { throw new ApiError(400, "Invalid Assignment ID"); } diff --git a/backend/src/controllers/message.controller.js b/backend/src/controllers/message.controller.js new file mode 100644 index 0000000..55d6702 --- /dev/null +++ b/backend/src/controllers/message.controller.js @@ -0,0 +1,93 @@ +import { User } from "../models/user.models.js"; +import Message from "../models/message.models.js"; +import { ApiError } from "../utils/ApiError.js"; +import { ApiResponse } from "../utils/ApiResponse.js"; +import { asyncHandler } from "../utils/asyncHandler.js"; +import { uploadToCloud } from "../utils/cloudinary.js"; + +const getUsers = asyncHandler(async (req, res) => { + try { + const loggedUserId = req.user._id; + + // Get all users except the logged in user + const filteredUsers = await User.find({ + _id: { $ne: loggedUserId }, + }).select("-password"); + + if (!filteredUsers) { + return res.status(404).json(new ApiResponse(404, null, "No users found")); + } + + return res + .status(200) + .json(new ApiResponse(200, filteredUsers, "Users fetched")); + } catch (error) { + console.error("Error in getUsersForSidebar: ", error.message); + throw new ApiError(500, "Something went wrong while fetching users"); + } +}); + +const getMessages = asyncHandler(async (req, res) => { + try { + const loggedUserId = req.user._id; + const receiverId = req.params.id; + + // Fetching messages + const messages = await Message.find({ + $or: [ + { sender: loggedUserId, receiver: receiverId }, + { sender: receiverId, receiver: loggedUserId }, + ], + }).sort({ createdAt: 1 }); + + if (!messages) { + return res + .status(404) + .json(new ApiResponse(404, null, "No messages found")); + } + + return res + .status(200) + .json(new ApiResponse(200, messages, "Messages fetched")); + } catch (error) { + console.error("Error in getMessages: ", error.message); + throw new ApiError(500, "Something went wrong while fetching messages"); + } +}); + +const sendMessage = asyncHandler(async (req, res) => { + try { + const loggedUserId = req.user._id; + const receiverId = req.params.id; + const { text } = req.body; + + let doc = null; + const file = req.file; + if (file) { + const localPath = file.path; + doc = await uploadToCloud(localPath); + + if (!doc) { + throw new ApiError(400, "File couldn't be saved"); + } + } + + const message = await Message.create({ + sender: loggedUserId, + receiver: receiverId, + text, + doc: doc ? doc.url : null, + }); + + if (!message) { + throw new ApiError(400, "Message couldn't be saved"); + } + + return res.status(201).json(new ApiResponse(201, message, "Message sent")); + } catch (error) { + console.error("Error in sendMessage: ", error.message); + throw new ApiError(500, "Something went wrong while sending message"); + } +}); + +export { getUsers, getMessages, sendMessage }; \ No newline at end of file diff --git a/backend/src/controllers/task.controller.js b/backend/src/controllers/task.controller.js new file mode 100644 index 0000000..d61d0ea --- /dev/null +++ b/backend/src/controllers/task.controller.js @@ -0,0 +1,97 @@ +import mongoose from "mongoose"; +import { Task } from "../models/task.models.js"; +import { ApiError } from "../utils/ApiError.js"; +import { ApiResponse } from "../utils/ApiResponse.js"; +import { asyncHandler } from "../utils/asyncHandler.js"; + +const createTask = asyncHandler(async (req, res) => { + const { title, description, due_date } = req.body; + + if ([title, due_date].some((field) => field?.trim() === "")) { + throw new ApiError(400, "Some fields are required fields"); + } + + const task = await Task.create({ + title, + description, + due_date, + user: req.user._id, + }); + + const taskFromDB = await Task.findById(task._id); + + if (!taskFromDB) { + throw new ApiError(500, "Something went wrong while creating Task"); + } + + return res + .status(201) + .json(new ApiResponse(200, taskFromDB, "Task Ban Gaya 🤡!")); +}); + +const getUserTasks = asyncHandler(async (req, res) => { + const userId = req.user.id; + + const tasks = await Task.find({ user: userId }).sort({ + createdAt: -1, + }); + + if (!tasks.length) { + throw new ApiError(404, "No tasks found for this user..."); + } + + return res.status(200).json(new ApiResponse(200, tasks, "Tasks Fetched")); +}); + +const updateTask = asyncHandler(async (req, res) => { + const { taskId } = req.params; + const { title, description, due_date } = req.body; + + // Validate ObjectId + if (!mongoose.Types.ObjectId.isValid(taskId)) { + throw new ApiError(400, "Invalid Task ID"); + } + + // Better approach (apparently😭) + const task = await Task.findOne({ + _id: taskId, + user: req.user._id, + }); + // Validation Again 💀 + if (!task) { + throw new ApiError(404, "Task not found or unauthorized access"); + } + + task.title = title || task.title; + task.description = description || task.description; + task.due_date = due_date || task.due_date; + + await task.save(); + + return res + .status(200) + .json(new ApiResponse(200, task, "Task Updated Successfully")); +}); + +const deleteTask = asyncHandler(async (req, res) => { + const { taskId } = req.params; + + if (!mongoose.Types.ObjectId.isValid(taskId)) { + throw new ApiError(400, "Invalid Task ID."); + } + + const task = await Task.findOne({ + _id: taskId, + user: req.user._id, + }); + // Validation Again 💀 + if (!task) { + throw new ApiError(404, "Task not found or unauthorized access"); + } + + await Task.findByIdAndDelete(taskId); + + return res.status(200).json(new ApiResponse(200, {}, "Khatam tata bye bye")); +}); + +export { createTask, getUserTasks, updateTask, deleteTask }; diff --git a/backend/src/models/message.models.js b/backend/src/models/message.models.js new file mode 100644 index 0000000..9267d91 --- /dev/null +++ b/backend/src/models/message.models.js @@ -0,0 +1,27 @@ +import mongoose, { Schema } from "mongoose"; + +const messageSchema = new Schema( + { + senderId: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + required: true, + }, + receiverId: { + type: mongoose.Schema.Types.ObjectId, + ref: "User", + required: true, + }, + text: { + type: String, + }, + image: { + type: String, //URL 😏 + }, + }, + { timestamps: true } +); + +const Message = mongoose.model("Message", messageSchema); + +export default Message; \ No newline at end of file diff --git a/backend/src/routes/assignment.routes.js b/backend/src/routes/assignment.routes.js index 03d9da7..2c1db11 100644 --- a/backend/src/routes/assignment.routes.js +++ b/backend/src/routes/assignment.routes.js @@ -1,5 +1,5 @@ import { Router } from "express"; -import { verifyJWT } from "../middlewares/auth.middleware"; +import {verifyJWT} from "../middlewares/auth.middleware.js"; import { upload } from "../middlewares/multer.middleware.js"; import { createAssignment, diff --git a/backend/src/routes/message.routes.js b/backend/src/routes/message.routes.js new file mode 100644 index 0000000..04612ca --- /dev/null +++ b/backend/src/routes/message.routes.js @@ -0,0 +1,17 @@ +import { Router } from "express"; +import { verifyJWT } from "../middlewares/auth.middleware.js"; +import { + getUsers, + getMessages, + sendMessage, +} from "../controllers/message.controller.js"; +import { upload } from "../middlewares/multer.middleware.js"; + +const router = Router(); + +router.get("/users", verifyJWT, getUsers); +router.get("/:id", verifyJWT, getMessages); + +router.post("/send/:id", verifyJWT, upload.single("img"), sendMessage); + +export default router; \ No newline at end of file diff --git a/backend/src/routes/task.routes.js b/backend/src/routes/task.routes.js new file mode 100644 index 0000000..e6961ca --- /dev/null +++ b/backend/src/routes/task.routes.js @@ -0,0 +1,21 @@ +import { Router } from "express"; +import { verifyJWT } from "../middlewares/auth.middleware.js"; +import { + createTask, + getUserTasks, + updateTask, + deleteTask, +} from "../controllers/task.controller.js"; +const router = Router(); + +router.use(verifyJWT); + +router.route("/").post(createTask); + +// Fetch all tasks of current user +router.route("/getTasks").get(getUserTasks); + +// Task update and delete routes +router.route("/:taskId").patch(updateTask).delete(deleteTask); + +export default router; diff --git a/backend/src/routes/user.routes.js b/backend/src/routes/user.routes.js index 8bbfd20..6f6cf10 100644 --- a/backend/src/routes/user.routes.js +++ b/backend/src/routes/user.routes.js @@ -1,5 +1,11 @@ import { Router } from "express"; -import { register, logout, login, refreshAccessToken, getCurrentUser } from "../controllers/user.controllers.js"; +import { + register, + logout, + login, + refreshAccessToken, + getCurrentUser, +} from "../controllers/user.controllers.js"; import { upload } from "../middlewares/multer.middleware.js"; import { verifyJWT } from "../middlewares/auth.middleware.js"; diff --git a/backend/yarn.lock b/backend/yarn.lock index 18d99a3..cc939cb 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -92,6 +92,20 @@ array-flatten@1.1.1: resolved "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz" integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + +axios@^1.7.9: + version "1.7.9" + resolved "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz" + integrity sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz" @@ -219,6 +233,13 @@ color-support@^1.1.2: resolved "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -315,6 +336,11 @@ debug@4.x: dependencies: ms "^2.1.3" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + delegates@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz" @@ -460,6 +486,20 @@ finalhandler@1.3.1: statuses "2.0.1" unpipe "~1.0.0" +follow-redirects@^1.15.6: + version "1.15.9" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz" + integrity sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ== + +form-data@^4.0.0: + version "4.0.1" + resolved "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz" + integrity sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + forwarded@0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz" @@ -770,7 +810,7 @@ mime-db@1.52.0: resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -1032,6 +1072,11 @@ proxy-addr@~2.0.7: forwarded "0.2.0" ipaddr.js "1.9.1" +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + pstree.remy@^1.1.8: version "1.1.8" resolved "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz" diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f6cbce6..7c75b30 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -15,8 +15,11 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@tailwindcss/vite": "^4.0.1", "axios": "^1.7.9", + "clsx": "^2.1.1", "cors": "^2.8.5", "daisyui": "^4.12.23", + "date-fns": "^4.1.0", + "mongoose": "^8.9.7", "motion": "^12.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -1082,6 +1085,15 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz", + "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.32.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.32.1.tgz", @@ -1651,6 +1663,21 @@ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==", "license": "MIT" }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", @@ -1964,6 +1991,15 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bson": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.2.tgz", + "integrity": "sha512-5afhLTjqDSA3akH56E+/2J6kTDuSIlBxyXPdQslj9hcIgOUE378xdOfZvC/9q3LifJNI6KR/juZ+d0NRNYBwXg==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -2080,6 +2116,15 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2293,11 +2338,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3843,6 +3897,15 @@ "node": ">=4.0" } }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4150,6 +4213,12 @@ "node": ">= 0.4" } }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -4184,6 +4253,84 @@ "node": "*" } }, + "node_modules/mongodb": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz", + "integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.9.7", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.7.tgz", + "integrity": "sha512-mvNXmU0V8qZzMR/qoK2mjT4Ti2ALdtfS0teK+twxhlGkwzOD76V02/zWajTu2MJ7QyEmZe9OWvnJsIY0iAuX3Q==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.1", + "kareem": "2.6.3", + "mongodb": "~6.12.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, "node_modules/motion": { "version": "12.0.6", "resolved": "https://registry.npmjs.org/motion/-/motion-12.0.6.tgz", @@ -4225,11 +4372,31 @@ "integrity": "sha512-MNFiBKbbqnmvOjkPyOKgHUp3Q6oiokLkI1bEwm5QA28cxMZrv0CbbBGDNmhF6DIXsi1pCQBSs0dX8xjeER1tmA==", "license": "MIT" }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -4578,7 +4745,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5018,6 +5184,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5027,6 +5199,15 @@ "node": ">=0.10.0" } }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -5247,6 +5428,18 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -5490,6 +5683,28 @@ } } }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz", + "integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index b5bd493..6fd1fd4 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,8 +17,11 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@tailwindcss/vite": "^4.0.1", "axios": "^1.7.9", + "clsx": "^2.1.1", "cors": "^2.8.5", "daisyui": "^4.12.23", + "date-fns": "^4.1.0", + "mongoose": "^8.9.7", "motion": "^12.0.6", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 55acbd0..dbb43d8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -6,6 +6,10 @@ import Home from './pages/Home' import Login from './pages/Login' import Signup from './pages/Signup' import Dashboard from './pages/Dashboard' +import TaskManager from './components/Taskmanager' +import AssignmentsManager from './components/AssignmentsManager' +import Chats from './pages/Chats' +import Calender from './components/Calender' function App() { return ( @@ -15,6 +19,11 @@ function App() { } /> } /> } /> + } /> + }/> + }/> + }/>; + ) diff --git a/frontend/src/components/AssignmentsManager.jsx b/frontend/src/components/AssignmentsManager.jsx new file mode 100644 index 0000000..3b6d6cd --- /dev/null +++ b/frontend/src/components/AssignmentsManager.jsx @@ -0,0 +1,94 @@ +import React, { useState, useEffect } from "react"; +import { useAssignments } from "../context/AssignmentContext"; +import Sidebar from "./Sidebar"; +import Header from "./Header"; +import { ThemeProvider } from "../context/ThemeContext"; + +export default function AssignmentsPage() { + + const { assignments, fetchAssignments, addAssignment, removeAssignment, isLoading, error } = useAssignments(); + const [assignmentData, setAssignmentData] = useState({ title: "", description: "", due_date: "", docs: null }); + const [editingId, setEditingId] = useState(null); + + useEffect(() => { + fetchAssignments(); + }, []); + + const handleFileChange = (e) => { + setAssignmentData({ ...assignmentData, docs: e.target.files }); + }; + + const handleEdit = (assignment) => { + setEditingId(assignment._id); + setAssignmentData({ title: assignment.title, description: assignment.description, due_date: assignment.due_date || "", docs: null }); + }; + + const handleCancelEdit = () => { + setEditingId(null); + setAssignmentData({ title: "", description: "", due_date: "", docs: null }); + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + const formData = new FormData(); + formData.append("title", assignmentData.title); + formData.append("description", assignmentData.description); + formData.append("due_date", assignmentData.due_date); + if (assignmentData.docs) { + Array.from(assignmentData.docs).forEach((file) => formData.append("docs", file)); + } + if (editingId) { + await addAssignment(editingId, formData); + } else { + await addAssignment(formData); + } + handleCancelEdit(); + fetchAssignments(); + }; + + return ( + +
    + +
    + + +
    +
    +

    {editingId ? "Edit Assignment" : "Assignments"}

    + {error &&

    {error}

    } +
    + + setAssignmentData({ ...assignmentData, title: e.target.value })} className="input input-bordered w-full" /> + + setAssignmentData({ ...assignmentData, due_date: e.target.value })} className="input input-bordered w-full" /> + + + + +
    + + {editingId && } +
    +
    +
    +
    + {isLoading &&

    Loading assignments...

    } +
    + {assignments.map((assignment) => ( +
    +
    +

    {assignment.title}

    +

    {assignment.description}

    +

    Due: {assignment.due_date || "No date set"}

    +
    + + +
    +
    +
    + ))} +
    +
    + ); +} \ No newline at end of file diff --git a/frontend/src/components/Calender.jsx b/frontend/src/components/Calender.jsx new file mode 100644 index 0000000..76cf272 --- /dev/null +++ b/frontend/src/components/Calender.jsx @@ -0,0 +1,35 @@ +import React from "react"; +import { useTasks } from "../context/TaskContext"; +import EventCalendar from "./EventCalender"; +import { ThemeProvider } from "../context/ThemeContext"; +import Sidebar from "./Sidebar"; +import Header from "./Header"; +import { useTheme } from "../context/ThemeContext"; + +const Calendar = () => { + const { tasks } = useTasks(); + const theme=useTheme(); + const events = tasks + .filter((task) => task.due_date) // Only tasks with due dates + .map((task) => ({ + date: new Date(task.due_date), + title: task.title, + })); + + return ( + +
    + +
    +
    +

    + My Event Calendar +

    + +
    +
    + + ); +}; + +export default Calendar; diff --git a/frontend/src/components/EventCalender.jsx b/frontend/src/components/EventCalender.jsx new file mode 100644 index 0000000..a582f3c --- /dev/null +++ b/frontend/src/components/EventCalender.jsx @@ -0,0 +1,77 @@ +import React from "react"; +import clsx from "clsx"; +import { + eachDayOfInterval, + endOfMonth, + format, + getDay, + isToday, + startOfMonth, +} from "date-fns"; +import { useMemo } from "react"; + +const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + +const EventCalendar = ({ events = [] }) => { + const currentDate = new Date(); + const firstDayOfMonth = startOfMonth(currentDate); + const lastDayOfMonth = endOfMonth(currentDate); + + const daysInMonth = eachDayOfInterval({ + start: firstDayOfMonth, + end: lastDayOfMonth, + }); + + const startingDayIndex = getDay(firstDayOfMonth); + + const eventsByDate = useMemo(() => { + return events.reduce((acc, event) => { + const dateKey = format(event.date, "yyyy-MM-dd"); + if (!acc[dateKey]) { + acc[dateKey] = []; + } + acc[dateKey].push(event); + return acc; + }, {}); + }, [events]); + + return ( +
    +
    +

    {format(currentDate, "MMMM yyyy")}

    +
    +
    + {WEEKDAYS.map((day) => ( +
    + {day} +
    + ))} + {Array.from({ length: startingDayIndex }).map((_, index) => ( +
    + ))} + {daysInMonth.map((day, index) => { + const dateKey = format(day, "yyyy-MM-dd"); + const todaysEvents = eventsByDate[dateKey] || []; + return ( +
    + {format(day, "d")} + {todaysEvents.map((event) => ( +
    + {event.title} +
    + ))} +
    + ); + })} +
    +
    + ); +}; + +export default EventCalendar; diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index 8ac7df2..796d2b9 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -18,12 +18,12 @@ const Header = () => { diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index d2a6256..a1b4179 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -4,6 +4,21 @@ import { FaChartBar, FaCalendarAlt, FaFacebookMessenger, FaUsersCog } from "reac import { IoIosLogOut } from "react-icons/io"; import { useState } from "react"; import { Link } from "react-router-dom"; +import { AssignmentProvider } from "../context/AssignmentContext"; +import { logout } from "../utils/api"; + + +const handleLogout = async () => { + try { + await logout(); // This will call the logout API on your backend + localStorage.removeItem("token"); // Optionally remove the access token from local storage + window.location.href = "/"; // Redirect to the login page + } catch (error) { + console.error("Error logging out:", error); + alert("Error logging out. Please try again."); + } +}; + const Sidebar = () => { const { theme } = useTheme(); // Get theme state @@ -13,7 +28,10 @@ const Sidebar = () => { setIsSidebarOpen(!isSidebarOpen); }; + + return ( +
    {/* Sidebar toggle button */}
  • +
  • + + + Assignments + + +
  • @@ -54,7 +82,7 @@ const Sidebar = () => {
  • @@ -64,11 +92,11 @@ const Sidebar = () => {
  • - Calender + Progress
  • @@ -78,7 +106,7 @@ const Sidebar = () => { className={`${theme === "dark" ? "text-white hover:bg-gray-700" : "text-black hover:bg-gray-100 "}`} > - Sign Out +
    diff --git a/frontend/src/components/TaskManager.jsx b/frontend/src/components/TaskManager.jsx index 35df2d9..4c96fb9 100644 --- a/frontend/src/components/TaskManager.jsx +++ b/frontend/src/components/TaskManager.jsx @@ -1,91 +1,124 @@ -import React from "react"; +import React, { useState, useEffect } from "react"; +import { Link } from "react-router-dom"; import { useTasks } from "../context/TaskContext"; +import { useTheme } from "../context/ThemeContext"; export default function TaskManager() { + const { theme } = useTheme(); const { tasks, - newTask, - setNewTask, - editingTask, - setEditingTask, - isLoading, + fetchTasks, handleCreate, handleUpdate, handleDelete, + isLoading, error, } = useTasks(); + const [taskData, setTaskData] = useState({ title: "", description: "", due_date: "" }); + const [editingId, setEditingId] = useState(null); + + useEffect(() => { + fetchTasks(); + }, []); + + const handleEdit = (task) => { + setEditingId(task._id); + setTaskData({ title: task.title, description: task.description, due_date: task.due_date || "" }); + }; + + const handleCancelEdit = () => { + setEditingId(null); + setTaskData({ title: "", description: "", due_date: "" }); + }; + + const handleSubmit = async () => { + if (editingId) { + await handleUpdate(editingId, taskData); + } else { + await handleCreate(taskData); + } + handleCancelEdit(); + fetchTasks(); + }; + return ( - -
    -

    Task Manager

    - {error &&

    {error}

    } -
    - setNewTask({ ...newTask, title: e.target.value })} - className="w-full p-2 border rounded" - /> - setNewTask({ ...newTask, due_date: e.target.value })} - className="w-full p-2 border rounded" - /> - - -
    - {isLoading &&

    Loading...

    } -
      - {tasks.map((task) => ( -
    • - {editingTask && editingTask._id === task._id ? ( - setEditingTask({ ...editingTask, title: e.target.value })} - className="p-1 border rounded" - /> - ) : ( - {task.title} - )} -
      - {editingTask && editingTask._id === task._id ? ( - - ) : ( - - )} - + {editingId && }
      -
    • +
    +
    +
    + + {/* Loading State */} + {isLoading &&

    Loading tasks...

    } + + {/* Task List */} +
    + {tasks.map((task) => ( +
    +
    +

    {task.title}

    +

    {task.description}

    +

    Due: {task.due_date || "No date set"}

    + + {/* Task Actions */} +
    +
    + +
    + +
    + +
    + +
    +
    +
    ))} - +
    + +
    - ); } diff --git a/frontend/src/context/AssignmentContext.jsx b/frontend/src/context/AssignmentContext.jsx new file mode 100644 index 0000000..a49c724 --- /dev/null +++ b/frontend/src/context/AssignmentContext.jsx @@ -0,0 +1,104 @@ +import React, { createContext, useState, useContext, useEffect } from "react"; +import { + createAssignment, + getUserAssignments, + updateAssignment, + deleteAssignment +} from "../utils/assignmentapi"; // Import API functions + +// Create Context +const AssignmentContext = createContext(); + +// Custom hook to use AssignmentContext +export const useAssignments = () => { + return useContext(AssignmentContext); +}; + +// Function to validate MongoDB ObjectId +const isValidObjectId = (id) => /^[a-f\d]{24}$/i.test(id); + +// AssignmentProvider Component +export const AssignmentProvider = ({ children }) => { + const [assignments, setAssignments] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + // Fetch assignments from API + const fetchAssignments = async () => { + setIsLoading(true); + try { + const response = await getUserAssignments(); + setAssignments(response.data.data); // Assuming response.data.data holds the assignments array + } catch (err) { + setError(err.response?.data?.message || "Error fetching assignments"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Add a new assignment + const handleCreate = async (formData) => { + setIsLoading(true); + try { + await createAssignment(formData); + fetchAssignments(); // Refresh assignment list + } catch (err) { + setError(err.response?.data?.message || "Error creating assignment"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Update an existing assignment + const handleUpdate = async (assignmentId, formData) => { + if (!isValidObjectId(assignmentId)) { + setError("Invalid Assignment ID"); + return; + } + + setIsLoading(true); + try { + await updateAssignment(assignmentId, formData); + fetchAssignments(); // Refresh assignment list + } catch (err) { + setError(err.response?.data?.message || "Error updating assignment"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Delete an assignment + const handleDelete = async (assignmentId) => { + if (!isValidObjectId(assignmentId)) { + setError("Invalid Assignment ID"); + return; + } + + setIsLoading(true); + try { + await deleteAssignment(assignmentId); + fetchAssignments(); // Refresh assignment list + } catch (err) { + setError(err.response?.data?.message || "Error deleting assignment"); + console.error(err); + } finally { + setIsLoading(false); + } + }; + + // Load assignments when component mounts + useEffect(() => { + fetchAssignments(); + }, []); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/frontend/src/context/TaskContext.jsx b/frontend/src/context/TaskContext.jsx index 6647bc8..74c9503 100644 --- a/frontend/src/context/TaskContext.jsx +++ b/frontend/src/context/TaskContext.jsx @@ -4,27 +4,29 @@ import { createTask, getUserTasks, updateTask, deleteTask } from "../utils/apita // Create Context const TaskContext = createContext(); -// Custom hook to use the TaskContext +// Custom hook to use TaskContext export const useTasks = () => { return useContext(TaskContext); }; +// Function to validate MongoDB ObjectId +const isValidObjectId = (id) => /^[a-f\d]{24}$/i.test(id); + + // TaskProvider Component export const TaskProvider = ({ children }) => { const [tasks, setTasks] = useState([]); - const [newTask, setNewTask] = useState({ title: "", description: "", due_date: "" }); - const [editingTask, setEditingTask] = useState(null); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); - // Fetch tasks from the API + // Fetch tasks from API const fetchTasks = async () => { setIsLoading(true); try { const response = await getUserTasks(); - setTasks(response.data.data); + setTasks(response.data.data); // Assuming response.data.data holds the tasks array } catch (err) { - setError("Error fetching tasks"); + setError(err.response?.data?.message || "Error fetching tasks"); console.error(err); } finally { setIsLoading(false); @@ -32,15 +34,13 @@ export const TaskProvider = ({ children }) => { }; // Add a new task - const handleCreate = async () => { - if (!newTask.title || !newTask.due_date) return; + const handleCreate = async (taskData) => { setIsLoading(true); try { - await createTask(newTask); - setNewTask({ title: "", description: "", due_date: "" }); - fetchTasks(); // Re-fetch tasks after adding + await createTask(taskData); + fetchTasks(); // Refresh task list } catch (err) { - setError("Error creating task"); + setError(err.response?.data?.message || "Error creating task"); console.error(err); } finally { setIsLoading(false); @@ -48,15 +48,18 @@ export const TaskProvider = ({ children }) => { }; // Update an existing task - const handleUpdate = async (taskId) => { - if (!editingTask) return; + const handleUpdate = async (taskId, taskData) => { + if (!isValidObjectId(taskId)) { + setError("Invalid Task ID"); + return; + } + setIsLoading(true); try { - await updateTask(taskId, editingTask); - setEditingTask(null); - fetchTasks(); // Re-fetch tasks after updating + await updateTask(taskId, taskData); + fetchTasks(); // Refresh task list } catch (err) { - setError("Error updating task"); + setError(err.response?.data?.message || "Error updating task"); console.error(err); } finally { setIsLoading(false); @@ -65,38 +68,30 @@ export const TaskProvider = ({ children }) => { // Delete a task const handleDelete = async (taskId) => { + if (!isValidObjectId(taskId)) { + setError("Invalid Task ID"); + return; + } + setIsLoading(true); try { await deleteTask(taskId); - fetchTasks(); // Re-fetch tasks after deleting + fetchTasks(); // Refresh task list } catch (err) { - setError("Error deleting task"); + setError(err.response?.data?.message || "Error deleting task"); console.error(err); } finally { setIsLoading(false); } }; - // Initialize by fetching tasks + // Load tasks when component mounts useEffect(() => { fetchTasks(); }, []); return ( - + {children} ); diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 638862c..56ed2a9 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -7,10 +7,12 @@ import { UserProvider } from './context/UserContext.jsx' import { BrowserRouter } from 'react-router-dom' import { AuthProvider } from './context/AuthContext.jsx' import { TaskProvider } from './context/TaskContext.jsx' +import { AssignmentProvider } from './context/AssignmentContext.jsx' createRoot(document.getElementById('root')).render( + @@ -20,6 +22,7 @@ createRoot(document.getElementById('root')).render( + , ) diff --git a/frontend/src/pages/Chats.jsx b/frontend/src/pages/Chats.jsx new file mode 100644 index 0000000..4fae5a8 --- /dev/null +++ b/frontend/src/pages/Chats.jsx @@ -0,0 +1,24 @@ +import React from 'react' +import { ThemeProvider } from '../context/ThemeContext' +import Sidebar from '../components/Sidebar' +import Header from '../components/Header'; +import ThemeSwitcher from '../components/ThemeSwitcher'; +import { getUsers } from '../utils/messageapi'; +import { useState,useEffect } from 'react'; + + + + + + +const Chats = () => { + return ( + +
    + + + + ) +}; + +export default Chats diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index b718a82..fabb78c 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -8,6 +8,7 @@ import Header from '../components/Header'; import Sidebar from '../components/Sidebar'; import QuoteDisplay from '../components/QuotesDisplay'; import TaskManager from '../components/Taskmanager'; +import { TaskProvider } from '../context/TaskContext'; const Dashboard = () => { @@ -21,22 +22,9 @@ const Dashboard = () => {
    -
    -
    - Shoes -
    -
    -

    Shoes!

    -

    If a dog chews shoes whose shoes does he choose?

    -
    - -
    -
    -
    + - + diff --git a/frontend/src/pages/SignOut.jsx b/frontend/src/pages/SignOut.jsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 44de5c1..0555216 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -5,7 +5,7 @@ const API = axios.create({ withCredentials: true, // Required for cookies (refresh tokens) }); -// Add JWT to headers if available +// Add JWT to headers API.interceptors.request.use((config) => { const token = localStorage.getItem("token"); if (token) config.headers.Authorization = `Bearer ${token}`; @@ -20,37 +20,3 @@ export const logout = () => API.post("/logout"); export const refreshToken = () => API.post("/refresh-token"); export const getCurrentUser = () => API.get("/current-user"); -// /*-----------Tasks assignment calls-----------*/ -// // Create a new task -// export const createTask = (taskData) => API.post("/tasks", taskData); - -// // Get all tasks for the current user -// export const getUserTasks = () => API.get("/tasks"); - -// // Update a task -// export const updateTask = (taskId, taskData) => -// API.put(`/tasks/${taskId}`, taskData); - -// // Delete a task -// export const deleteTask = (taskId) => API.delete(`/tasks/${taskId}`); - - - - - -// /* ---------- Assignment API Calls ----------*/ - - -// export const createAssignment = (formData) => -// API.post("/assignments", formData, { headers: { "Content-Type": "multipart/form-data" } }); - -// // Get all assignments for the current user -// export const getUserAssignments = () => API.get("/assignments"); - -// // Update an assignment -// export const updateAssignment = (assignmentId, formData) => -// API.put(`/assignments/${assignmentId}`, formData, { headers: { "Content-Type": "multipart/form-data" } }); - -// // Delete an assignment -// export const deleteAssignment = (assignmentId) => API.delete(`/assignments/${assignmentId}`); - diff --git a/frontend/src/utils/apitask.js b/frontend/src/utils/apitask.js index ff1dd61..ca79ac4 100644 --- a/frontend/src/utils/apitask.js +++ b/frontend/src/utils/apitask.js @@ -15,7 +15,7 @@ API.interceptors.request.use((config) => { }); -/* ----------- Task API Calls ----------- */ + // Create a new task export const createTask = (taskData) => API.post("/", taskData); diff --git a/frontend/src/utils/assignmentapi.js b/frontend/src/utils/assignmentapi.js index c0106e8..7538c52 100644 --- a/frontend/src/utils/assignmentapi.js +++ b/frontend/src/utils/assignmentapi.js @@ -1,32 +1,33 @@ import axios from "axios"; const API = axios.create({ - baseURL: "http://localhost:8000/assignments", // Update with your actual backend URL - withCredentials: true, // Required for cookies (refresh tokens) + baseURL: "http://localhost:8000/assignments", // Backend URL + withCredentials: true, // Required for authentication (cookies, sessions, etc.) }); // Add JWT to headers if available API.interceptors.request.use((config) => { const token = localStorage.getItem("token"); - if (token) config.headers.Authorization = `Bearer ${token}`; + if (token) { + console.log("Adding Authorization header:", token); // Log token for debugging + config.headers.Authorization = `Bearer ${token}`; + } return config; }); -/* ----------- Assignment API Calls ----------- */ +// ---------- Assignment API Calls ---------- -// Create a new assignment +// Create a new assignment (supports multiple files) export const createAssignment = (formData) => API.post("/", formData, { headers: { "Content-Type": "multipart/form-data" } }); -// Get all assignments for the current user -export const getUserAssignments = () => API.get("/"); +// Fetch all assignments of the current user +export const getUserAssignments = () => API.get("/getAssignments"); -// Get a specific assignment by ID -export const getAssignmentById = (assignmentId) => API.get(`/${assignmentId}`); - -// Update an assignment +// Update an existing assignment export const updateAssignment = (assignmentId, formData) => - API.put(`/${assignmentId}`, formData, { headers: { "Content-Type": "multipart/form-data" } }); + API.patch(`/${assignmentId}`, formData, { headers: { "Content-Type": "multipart/form-data" } }); // Delete an assignment export const deleteAssignment = (assignmentId) => API.delete(`/${assignmentId}`); + diff --git a/frontend/src/utils/messageapi.js b/frontend/src/utils/messageapi.js new file mode 100644 index 0000000..92c8421 --- /dev/null +++ b/frontend/src/utils/messageapi.js @@ -0,0 +1,40 @@ +// api.js +import axios from "axios"; + +const API_URL = "http://localhost:5000"; // Adjust the URL as per your backend + +// Axios instance with JWT token header +const axiosInstance = axios.create({ + baseURL: API_URL, +}); + +axiosInstance.interceptors.request.use((config) => { + const token = localStorage.getItem("token"); // Assuming token is stored in localStorage + if (token) { + config.headers.Authorization = `Bearer ${token}`; + } + return config; +}); + +// Get all users +export const getUsers = async () => { + const response = await axiosInstance.get("/users"); + return response.data.data; +}; + +// Get messages for a specific user +export const getMessages = async (userId) => { + const response = await axiosInstance.get(`/messages/${userId}`); + return response.data.data; +}; + +// Send a message +export const sendMessage = async (receiverId, text, file) => { + const formData = new FormData(); + formData.append("text", text); + if (file) { + formData.append("img", file); + } + const response = await axiosInstance.post(`/send/${receiverId}`, formData); + return response.data.data; +}; From 1f6b27bf1cde82a4922fdfa7a079b04e4afb117c Mon Sep 17 00:00:00 2001 From: shreesriv12 Date: Fri, 7 Feb 2025 19:35:43 +0530 Subject: [PATCH 10/10] fix:register page --- frontend/src/App.jsx | 1 - .../src/components/AssignmentsManager.jsx | 116 +++++++--- frontend/src/components/Calender.jsx | 31 ++- frontend/src/components/Sidebar.jsx | 211 +++++++++--------- frontend/src/components/Userprofile.jsx | 11 + frontend/src/context/RegisterContext.jsx | 72 ++++++ frontend/src/main.jsx | 3 + frontend/src/pages/Signup.jsx | 44 +--- frontend/src/utils/messageapi.js | 2 +- 9 files changed, 311 insertions(+), 180 deletions(-) create mode 100644 frontend/src/components/Userprofile.jsx create mode 100644 frontend/src/context/RegisterContext.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index dbb43d8..974c93f 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -23,7 +23,6 @@ function App() { }/> }/> }/>; - ) diff --git a/frontend/src/components/AssignmentsManager.jsx b/frontend/src/components/AssignmentsManager.jsx index 3b6d6cd..dbd79fb 100644 --- a/frontend/src/components/AssignmentsManager.jsx +++ b/frontend/src/components/AssignmentsManager.jsx @@ -2,17 +2,18 @@ import React, { useState, useEffect } from "react"; import { useAssignments } from "../context/AssignmentContext"; import Sidebar from "./Sidebar"; import Header from "./Header"; -import { ThemeProvider } from "../context/ThemeContext"; +import { ThemeProvider, useTheme } from "../context/ThemeContext"; export default function AssignmentsPage() { - const { assignments, fetchAssignments, addAssignment, removeAssignment, isLoading, error } = useAssignments(); + const theme=useTheme(); + const { assignments, fetchAssignments, handleCreate, handleUpdate, handleDelete, isLoading, error } = useAssignments(); const [assignmentData, setAssignmentData] = useState({ title: "", description: "", due_date: "", docs: null }); const [editingId, setEditingId] = useState(null); useEffect(() => { fetchAssignments(); - }, []); + }, [fetchAssignments]); const handleFileChange = (e) => { setAssignmentData({ ...assignmentData, docs: e.target.files }); @@ -20,7 +21,12 @@ export default function AssignmentsPage() { const handleEdit = (assignment) => { setEditingId(assignment._id); - setAssignmentData({ title: assignment.title, description: assignment.description, due_date: assignment.due_date || "", docs: null }); + setAssignmentData({ + title: assignment.title, + description: assignment.description, + due_date: assignment.due_date || "", + docs: null + }); }; const handleCancelEdit = () => { @@ -30,60 +36,120 @@ export default function AssignmentsPage() { const handleSubmit = async (e) => { e.preventDefault(); + const formData = new FormData(); formData.append("title", assignmentData.title); formData.append("description", assignmentData.description); formData.append("due_date", assignmentData.due_date); + if (assignmentData.docs) { Array.from(assignmentData.docs).forEach((file) => formData.append("docs", file)); } + if (editingId) { - await addAssignment(editingId, formData); + await handleUpdate(editingId, formData); // ✅ Correct function for updating } else { - await addAssignment(formData); + await handleCreate(formData); // ✅ Correct function for adding new assignment } + handleCancelEdit(); fetchAssignments(); }; return ( - -
    - -
    - +
    + +
    + -
    -
    -

    {editingId ? "Edit Assignment" : "Assignments"}

    + +
    +
    + +

    + {editingId ? "Edit Assignment" : "Assignments"} +

    + {error &&

    {error}

    } +
    - setAssignmentData({ ...assignmentData, title: e.target.value })} className="input input-bordered w-full" /> + setAssignmentData({ ...assignmentData, title: e.target.value })} + className="input input-bordered w-full" + /> + - setAssignmentData({ ...assignmentData, due_date: e.target.value })} className="input input-bordered w-full" /> + setAssignmentData({ ...assignmentData, due_date: e.target.value })} + className="input input-bordered w-full" + /> + - + + - + +
    - - {editingId && } + + {editingId && ( + + )}
    + {isLoading &&

    Loading assignments...

    } +
    {assignments.map((assignment) => ( -
    +

    {assignment.title}

    {assignment.description}

    -

    Due: {assignment.due_date || "No date set"}

    +

    + Due: {assignment.due_date || "No date set"} +

    + + {/* View Document Button */} + {assignment.docs && assignment.docs.length > 0 && ( + + View Document + + )}
    - - + +
    @@ -91,4 +157,4 @@ export default function AssignmentsPage() {
    ); -} \ No newline at end of file +} diff --git a/frontend/src/components/Calender.jsx b/frontend/src/components/Calender.jsx index 76cf272..47c1750 100644 --- a/frontend/src/components/Calender.jsx +++ b/frontend/src/components/Calender.jsx @@ -1,14 +1,14 @@ import React from "react"; import { useTasks } from "../context/TaskContext"; import EventCalendar from "./EventCalender"; -import { ThemeProvider } from "../context/ThemeContext"; import Sidebar from "./Sidebar"; import Header from "./Header"; -import { useTheme } from "../context/ThemeContext"; +import { ThemeProvider, useTheme } from "../context/ThemeContext"; const Calendar = () => { const { tasks } = useTasks(); - const theme=useTheme(); + const theme = useTheme(); + const events = tasks .filter((task) => task.due_date) // Only tasks with due dates .map((task) => ({ @@ -18,16 +18,23 @@ const Calendar = () => { return ( -
    - -
    -
    -

    - My Event Calendar -

    - +
    + +
    +
    +

    + My Event Calendar +

    + +
    -
    ); }; diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index a1b4179..9108800 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -1,119 +1,116 @@ -import React from "react"; -import { useTheme } from "../context/ThemeContext"; // Import Theme Context -import { FaChartBar, FaCalendarAlt, FaFacebookMessenger, FaUsersCog } from "react-icons/fa"; -import { IoIosLogOut } from "react-icons/io"; -import { useState } from "react"; -import { Link } from "react-router-dom"; -import { AssignmentProvider } from "../context/AssignmentContext"; -import { logout } from "../utils/api"; - + import React from "react"; + import { useTheme } from "../context/ThemeContext"; // Import Theme Context + import { FaChartBar, FaCalendarAlt, FaFacebookMessenger, FaUsersCog,FaTasks } from "react-icons/fa"; + import { IoIosLogOut } from "react-icons/io"; + import { useState } from "react"; + import { Link } from "react-router-dom"; + import { AssignmentProvider } from "../context/AssignmentContext"; + import {logout } from "../utils/api" + import { MdAssignment } from "react-icons/md"; + import { useNavigate } from "react-router-dom"; + + const handleLogout = async () => { + try { + await logout(); + localStorage.removeItem("token"); // Remove stored token + window.location.href = "/"; // Redirect to login page + } catch (error) { + console.error("Logout failed:", error); + } + }; + -const handleLogout = async () => { - try { - await logout(); // This will call the logout API on your backend - localStorage.removeItem("token"); // Optionally remove the access token from local storage - window.location.href = "/"; // Redirect to the login page - } catch (error) { - console.error("Error logging out:", error); - alert("Error logging out. Please try again."); - } -}; + const Sidebar = () => { + const { theme } = useTheme(); // Get theme state + const [isSidebarOpen, setIsSidebarOpen] = useState(false); -const Sidebar = () => { - const { theme } = useTheme(); // Get theme state - const [isSidebarOpen, setIsSidebarOpen] = useState(false); + const toggleSidebar = () => { + setIsSidebarOpen(!isSidebarOpen); + }; - const toggleSidebar = () => { - setIsSidebarOpen(!isSidebarOpen); - }; + return ( + +
    + {/* Sidebar toggle button */} + - return ( - -
    - {/* Sidebar toggle button */} - - - {/* Sidebar */} - -
    - ); -}; +
  • + + + Progress + +
  • +
  • + + + + +
  • + +
    + +
    + ); + }; -export default Sidebar; + export default Sidebar; diff --git a/frontend/src/components/Userprofile.jsx b/frontend/src/components/Userprofile.jsx new file mode 100644 index 0000000..e05a5c8 --- /dev/null +++ b/frontend/src/components/Userprofile.jsx @@ -0,0 +1,11 @@ +import React from 'react' + +const Userprofile = () => { + return ( +
    +

    hi

    +
    + ) +} + +export default Userprofile diff --git a/frontend/src/context/RegisterContext.jsx b/frontend/src/context/RegisterContext.jsx new file mode 100644 index 0000000..c43c201 --- /dev/null +++ b/frontend/src/context/RegisterContext.jsx @@ -0,0 +1,72 @@ +import React, { createContext, useEffect, useState } from 'react'; +import { register,getCurrentUser } from '../utils/api'; +import { useNavigate } from 'react-router-dom'; +import { useContext } from 'react'; +export const RegisterContext = createContext(); +export const useRegister = () => useContext(RegisterContext); + +const RegisterProvider = ({ children }) => { + const [user, setUser] = useState(null); // Store user details + const [formData, setFormData] = useState({ + fullName: "", + username: "", + email: "", + password: "", + avatar: null, + }); + const [error, setError] = useState(""); + const navigate = useNavigate(); + + const fetchUser = async () => { + try { + const response = await getCurrentUser(); // Fetch user from API + setUser(response.data.user); // Store user data + } catch (error) { + console.error("Failed to fetch user details:", error); + } + }; + + useEffect(() => { + if (localStorage.getItem("token")) { + fetchUser(); + } + }, []); + + const handleChange = (e) => { + const { name, value, files } = e.target; + if (name === "avatar" && files) { + setFormData((prev) => ({ ...prev, avatar: files[0] })); + } else { + setFormData((prev) => ({ ...prev, [name]: value })); + } + }; + + const handleSubmit = async (e) => { + e.preventDefault(); + + const data = new FormData(); + Object.keys(formData).forEach((key) => data.append(key, formData[key])); + + try { + const response=await register(data); + console.log("Registration Response:", response.data); + if (response.data && response.data.user) { + setUser(response.data.user); // Store user details + localStorage.setItem("token", response.data.token); // Save token + } else { + console.warn("User data missing in registration response"); + } + navigate("/dashboard"); + } catch (error) { + setError(error.response?.data?.message || "Registration failed"); + } + }; + + return ( + + {children} + + ); +}; + +export default RegisterProvider; diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 56ed2a9..5307ae7 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -8,10 +8,12 @@ import { BrowserRouter } from 'react-router-dom' import { AuthProvider } from './context/AuthContext.jsx' import { TaskProvider } from './context/TaskContext.jsx' import { AssignmentProvider } from './context/AssignmentContext.jsx' +import RegisterProvider from './context/RegisterContext.jsx' createRoot(document.getElementById('root')).render( + @@ -23,6 +25,7 @@ createRoot(document.getElementById('root')).render( + , ) diff --git a/frontend/src/pages/Signup.jsx b/frontend/src/pages/Signup.jsx index d6db45f..3f3f607 100644 --- a/frontend/src/pages/Signup.jsx +++ b/frontend/src/pages/Signup.jsx @@ -1,40 +1,16 @@ -import React, { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { register } from '../utils/api'; - +import React, { useContext } from 'react'; +import { RegisterContext } from '../context/RegisterContext'; +import { useEffect } from 'react'; const Signup = () => { - const [formData, setFormData] = useState({ - fullName: "", - username: "", - email: "", - password: "", - avatar: null, - }); - const [error, setError] = useState(""); // State for displaying error message - const navigate = useNavigate(); - - const handleChange = (e) => { - const { name, value } = e.target; - if (name === "avatar") { - setFormData((prev) => ({ ...prev, avatar: e.target.files[0] })); - } else { - setFormData((prev) => ({ ...prev, [name]: value })); - } - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - const data = new FormData(); - Object.keys(formData).forEach((key) => data.append(key, formData[key])); + + + const { formData, error, handleChange, handleSubmit,user,fetchUser } = useContext(RegisterContext); + useEffect(() => { + fetchUser(); // Fetch user details when Signup component mounts + }, []); - try { - await register(data); - navigate("/dashboard"); - } catch (error) { - setError(error.response?.data?.message || "Registration failed"); - } - }; + return (
    diff --git a/frontend/src/utils/messageapi.js b/frontend/src/utils/messageapi.js index 92c8421..28c9b08 100644 --- a/frontend/src/utils/messageapi.js +++ b/frontend/src/utils/messageapi.js @@ -1,7 +1,7 @@ // api.js import axios from "axios"; -const API_URL = "http://localhost:5000"; // Adjust the URL as per your backend +const API_URL = "http://localhost:8000"; // Adjust the URL as per your backend // Axios instance with JWT token header const axiosInstance = axios.create({