From e1a63461ccb8fcc752e902031528d78a37ddb04c Mon Sep 17 00:00:00 2001 From: sreecharan-desu Date: Mon, 9 Dec 2024 12:06:24 +0530 Subject: [PATCH] ui fixes --- frontend/package-lock.json | 99 +++- frontend/package.json | 30 +- frontend/src/App.jsx | 25 +- frontend/src/components/LoadingSpinner.jsx | 52 ++ frontend/src/index.css | 170 +++---- .../pages/admin/dashboard/admindashboard.jsx | 290 ++++++++--- .../admin/dashboard/components/Users.jsx | 194 +++++--- .../pages/admin/dashboard/components/Wish.jsx | 103 +++- .../admin/dashboard/components/navbar.jsx | 122 +++-- frontend/src/pages/admin/profile/profile.jsx | 181 ++++--- frontend/src/pages/admin/signin/signin.jsx | 179 ++++--- frontend/src/pages/admin/signup/signup.jsx | 101 ++-- frontend/src/pages/home/Home.jsx | 269 ++++++++++ .../src/pages/signup&signin-comp/Warning.jsx | 14 + .../user/Dashboard/Components/Addtodo.jsx | 178 +++++-- .../Dashboard/Components/LoadingOverlay.jsx | 7 + .../user/Dashboard/Components/Navbar.jsx | 135 +++-- .../pages/user/Dashboard/Components/Todos.jsx | 324 +++++++----- .../user/Dashboard/Components/Toolbar.jsx | 49 ++ frontend/src/pages/user/Dashboard/index.jsx | 47 ++ .../pages/user/Dashboard/userdashboard.jsx | 461 ++++++++++++++++-- .../src/pages/user/Profile/userprofile.jsx | 257 +++++++++- frontend/src/pages/user/signin/signin.jsx | 149 +++++- frontend/src/pages/user/signup/signup.jsx | 140 ++++-- frontend/tailwind.config.js | 5 +- tailwind.config.js | 26 + 26 files changed, 2784 insertions(+), 823 deletions(-) create mode 100644 frontend/src/components/LoadingSpinner.jsx create mode 100644 frontend/src/pages/home/Home.jsx create mode 100644 frontend/src/pages/signup&signin-comp/Warning.jsx create mode 100644 frontend/src/pages/user/Dashboard/Components/LoadingOverlay.jsx create mode 100644 frontend/src/pages/user/Dashboard/Components/Toolbar.jsx create mode 100644 frontend/src/pages/user/Dashboard/index.jsx create mode 100644 tailwind.config.js diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4472860..319c3a9 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,24 +8,26 @@ "name": "taskmaster", "version": "0.0.0", "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router": "^6.25.1", - "react-router-dom": "^6.25.1", + "framer-motion": "^11.13.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", + "react-router": "^6.22.3", + "react-router-dom": "^6.22.3", "recoil": "^0.7.7" }, "devDependencies": { - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "autoprefixer": "^10.4.19", + "@types/react": "^18.2.64", + "@types/react-dom": "^18.2.21", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.18", "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.3", - "eslint-plugin-react-hooks": "^4.6.2", - "eslint-plugin-react-refresh": "^0.4.7", - "postcss": "^8.4.40", - "tailwindcss": "^3.4.7", - "vite": "^5.3.4" + "eslint-plugin-react": "^7.34.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1", + "vite": "^5.1.6" } }, "node_modules/@alloc/quick-lru": { @@ -1901,7 +1903,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/data-view-buffer": { @@ -2783,6 +2784,33 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/framer-motion": { + "version": "11.13.1", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-11.13.1.tgz", + "integrity": "sha512-F40tpGTHByhn9h3zdBQPcEro+pSLtzARcocbNqAyfBI+u9S+KZuHH/7O9+z+GEkoF3eqFxfvVw0eBDytohwqmQ==", + "license": "MIT", + "dependencies": { + "motion-dom": "^11.13.0", + "motion-utils": "^11.13.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0", + "react-dom": "^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -2954,6 +2982,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3806,6 +3843,16 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-11.13.0.tgz", + "integrity": "sha512-Oc1MLGJQ6nrvXccXA89lXtOqFyBmvHtaDcTRGT66o8Czl7nuA8BeHAd9MQV1pQKX0d2RHFBFaw5g3k23hQJt0w==" + }, + "node_modules/motion-utils": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-11.13.0.tgz", + "integrity": "sha512-lq6TzXkH5c/ysJQBxgLXgM01qwBH1b4goTPh57VvZWJbVJZF/0SB31UWEn4EIqbVPf3au88n2rvK17SpDTja1A==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4442,6 +4489,22 @@ "react": "^18.3.1" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "license": "MIT", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", @@ -5255,6 +5318,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 85113f8..420e1e7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,23 +10,25 @@ "preview": "vite preview" }, "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router": "^6.25.1", - "react-router-dom": "^6.25.1", + "framer-motion": "^11.13.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-hot-toast": "^2.4.1", + "react-router": "^6.22.3", + "react-router-dom": "^6.22.3", "recoil": "^0.7.7" }, "devDependencies": { - "@types/react": "^18.3.3", - "@types/react-dom": "^18.3.0", - "@vitejs/plugin-react": "^4.3.1", - "autoprefixer": "^10.4.19", + "@types/react": "^18.2.64", + "@types/react-dom": "^18.2.21", + "@vitejs/plugin-react": "^4.2.1", + "autoprefixer": "^10.4.18", "eslint": "^8.57.0", - "eslint-plugin-react": "^7.34.3", - "eslint-plugin-react-hooks": "^4.6.2", - "eslint-plugin-react-refresh": "^0.4.7", - "postcss": "^8.4.40", - "tailwindcss": "^3.4.7", - "vite": "^5.3.4" + "eslint-plugin-react": "^7.34.0", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-refresh": "^0.4.5", + "postcss": "^8.4.35", + "tailwindcss": "^3.4.1", + "vite": "^5.1.6" } } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5a3e06e..8b56a0d 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,9 +1,10 @@ -import React, { Suspense, useEffect } from 'react'; +import React, { Suspense } from 'react'; import './App.css' -import {BrowserRouter, Route, Routes, useNavigate} from 'react-router-dom'; +import {BrowserRouter, Route, Routes} from 'react-router-dom'; import AdminProfile from './pages/admin/profile/profile'; import UserDashboard from './pages/user/Dashboard/userdashboard'; -import UserProfile from './pages/user/Profile/userprofile'; +const UserProfile = React.lazy(() => import('./pages/user/Profile/userprofile')); +import Home from './pages/home/Home'; const AdminSignup = React.lazy((()=>import('./pages/admin/signup/signup'))); const AdminSignin = React.lazy((()=>import('./pages/admin/signin/signin'))); @@ -44,7 +45,7 @@ function App() {
- }>{}}/> + }>}/> }>{}}/> }>}/> }>}/> @@ -60,22 +61,6 @@ function App() { ) } -function Home(){ - const navigate = useNavigate(); - useEffect(()=>{ - navigate('/user/signup') - },[]) - - return(<> - - Hello from frontend - - ) -} - - - - function TodoSkeleton(){ return(<>
diff --git a/frontend/src/components/LoadingSpinner.jsx b/frontend/src/components/LoadingSpinner.jsx new file mode 100644 index 0000000..71eedc1 --- /dev/null +++ b/frontend/src/components/LoadingSpinner.jsx @@ -0,0 +1,52 @@ +import { motion } from "framer-motion"; + +export function LoadingSpinner() { + return ( +
+ + + + + +
+ ); +} \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index bceddc7..d2531b7 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -2,38 +2,7 @@ @tailwind components; @tailwind utilities; -@layer components { - .btn-primary { - @apply px-4 py-2 bg-black text-white rounded-lg hover:bg-gray-800 transition-colors; - } - - .input-field { - @apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-gray-200 focus:border-gray-500 outline-none transition-all; - } - - .card { - @apply bg-white rounded-lg shadow-sm p-6; - } -} - -/* Custom animations */ -@keyframes slideIn { - from { - transform: translateY(-100%); - opacity: 0; - } - to { - transform: translateY(0); - opacity: 1; - } -} - -.slide-in { - animation: slideIn 0.3s ease-out; -} - - - +/* Loader1 Animation */ .loader1 { width: 50px; aspect-ratio: 1; @@ -44,42 +13,41 @@ linear-gradient(90deg,rgb(0 0 0/25%) 30%,#0000 0 70%,rgb(0 0 0/75% ) 0) 50%/100% 8%; background-repeat: no-repeat; animation: l23 1s infinite steps(12); - } - .loader1::before, - .loader1::after { - content: ""; - grid-area: 1/1; - border-radius: 50%; - background: inherit; - opacity: 0.915; - transform: rotate(30deg); - } - .loader1::after { - opacity: 0.83; - transform: rotate(60deg); - } - @keyframes l23 { - 100% {transform: rotate(1turn)} - } - +} +.loader1::before, +.loader1::after { + content: ""; + grid-area: 1/1; + border-radius: 50%; + background: inherit; + opacity: 0.915; + transform: rotate(30deg); +} +.loader1::after { + opacity: 0.83; + transform: rotate(60deg); +} +@keyframes l23 { + 100% {transform: rotate(1turn)} +} -/* HTML:
*/ +/* Loader2 Animation */ .loader2 { width: 120px; height: 20px; -webkit-mask: radial-gradient(circle closest-side,#000 94%,#0000) left/20% 100%; background: linear-gradient(#000 0 0) left/0% 100% no-repeat #ddd; animation: l17 2s infinite steps(6); - } - @keyframes l17 { - 100% {background-size:120% 100%} - } - +} +@keyframes l17 { + 100% {background-size:120% 100%} +} +/* Skeleton Animations */ .skeleton-list-panel-wrapper { padding: 10px; } @@ -92,45 +60,6 @@ animation: fading 1.5s infinite; } -@keyframes fading { - 0% { - opacity: 0.5; - } - - 50% { - opacity: 1; - } - - 100% { - opacity: 0.5; - } -} - - - - - -.skeleton-profile-circle-shimmer { - animation: shimmer 2s infinite; - background: #ddd linear-gradient(to right, rgba(255, 255, 255, .10) 5%, rgba(255, 255, 255, .30) 10%, rgba(255, 255, 255, .50) 15%); - background-size: 150px 150px; - background-repeat: no-repeat; - height: 100px; - width: 100px; - border-radius: 50%; - margin: 0 auto; -} - -@keyframes shimmer { - 0% { - background-position: -500px 0; - } - 100% { - background-position: 500px 0; - } -} - - .skeleton-panel { max-width: 250px; padding: 15px; @@ -144,32 +73,57 @@ border-radius: 100px; max-width: 50px; height: 10px; + background-color: #e1e1e1; + opacity: 0.5; + animation: fading 1.5s infinite; } .skeleton-panel-content { height: 50px; margin: 5px 0; border-radius: 3px; -} - -.skeleton-panel-title, -.skeleton-panel-content { background-color: #e1e1e1; opacity: 0.5; animation: fading 1.5s infinite; - transition: all 1.5s; } @keyframes fading { - 0% { - opacity: 0.5; - } + 0% { opacity: 0.5; } + 50% { opacity: 1; } + 100% { opacity: 0.5; } +} - 50% { - opacity: 1; +/* Custom Utility Classes */ +@layer components { + .btn-primary { + @apply px-4 py-2 bg-black text-white rounded-lg hover:bg-gray-800 transition-colors; } - - 100% { - opacity: 0.5; + + .input-field { + @apply w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-gray-200 focus:border-gray-500 outline-none transition-all; + } + + .card { + @apply bg-white rounded-lg shadow-sm p-6; } +} + +/* Custom Scrollbar */ +::-webkit-scrollbar { + width: 10px; +} + +::-webkit-scrollbar-track { + background: #f1f1f1; + border-radius: 5px; +} + +::-webkit-scrollbar-thumb { + background: linear-gradient(to bottom, #6366f1, #a855f7); + border-radius: 5px; + border: 2px solid #f1f1f1; +} + +::-webkit-scrollbar-thumb:hover { + background: linear-gradient(to bottom, #4f46e5, #9333ea); } \ No newline at end of file diff --git a/frontend/src/pages/admin/dashboard/admindashboard.jsx b/frontend/src/pages/admin/dashboard/admindashboard.jsx index 1982b49..d33a7b7 100644 --- a/frontend/src/pages/admin/dashboard/admindashboard.jsx +++ b/frontend/src/pages/admin/dashboard/admindashboard.jsx @@ -1,92 +1,250 @@ -import { useEffect } from "react"; +import { useEffect, Suspense, useState } from "react"; import { useNavigate } from "react-router" import { Navbar } from "./components/navbar"; import { Users } from "./components/Users"; -import { useRecoilState, useRecoilValue } from "recoil"; +import { useRecoilState } from "recoil"; import { username, UsersList } from "./Dashboardstore/admin-dashboard-store"; import { Wish } from "./components/Wish"; +import toast, { Toaster } from 'react-hot-toast'; -export default function AdminDashboard(){ +export default function AdminDashboard() { const navigate = useNavigate(); - const [Username,setUsername] = useRecoilState(username); - const [users,setUsers] = useRecoilState(UsersList); + const [Username, setUsername] = useRecoilState(username); + const [users, setUsers] = useRecoilState(UsersList); + const [searchTerm, setSearchTerm] = useState(""); - useEffect(()=>{ - if(!localStorage.getItem('Admintoken')){ - setTimeout(()=>{ + const filteredUsers = users?.filter(user => { + if (!searchTerm) return true; + if (!user || !user.username || !user.email) return false; + + const search = searchTerm.toLowerCase().trim(); + return user.username.toLowerCase().includes(search) || + user.email.toLowerCase().includes(search); + }) || []; + + useEffect(() => { + if (!localStorage.getItem('Admintoken')) { + toast.error('Please login first'); + setTimeout(() => { navigate('/admin/signin') - },1000) + }, 1000) + return; } - },[]) - - if(localStorage.getItem('Admintoken')){ - useEffect(()=>{ - const fecthUsername = async()=>{ - const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/details',{ - method : 'GET', - headers : { - 'Content-Type' : 'application/json', - authorization : "Bearer " + JSON.parse(localStorage.getItem('Admintoken')) - } - }); - const data = await response.json(); - setUsername(data.username); + + const fetchData = async () => { + const loadingToast = toast.loading('Loading dashboard...'); + try { + // Fetch username and users in parallel + const [usernameResponse, usersResponse] = await Promise.all([ + fetch('https://task-master-api-psi.vercel.app/api/v1/admin/details', { + headers: { + 'Content-Type': 'application/json', + authorization: "Bearer " + JSON.parse(localStorage.getItem('Admintoken')) + } + }), + fetch('https://task-master-api-psi.vercel.app/api/v1/admin/getusers', { + headers: { + 'Content-Type': 'application/json', + authorization: "Bearer " + JSON.parse(localStorage.getItem('Admintoken')) + } + }) + ]); + + const [usernameData, usersData] = await Promise.all([ + usernameResponse.json(), + usersResponse.json() + ]); + + setUsername(usernameData.username); + setUsers(usersData.users); + toast.success('Dashboard loaded successfully', { id: loadingToast }); + } catch (error) { + toast.error('Failed to load dashboard', { id: loadingToast }); + console.error('Error fetching data:', error); } - fecthUsername(); - },[]) - - - - useEffect(()=>{ - const fetchUsers = async()=>{ - const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/getusers',{ - method : 'GET', - headers : { - "Content-Type" : "application/json", - authorization : "Bearer " + JSON.parse(localStorage.getItem('Admintoken')) - } - }) - const data = await response.json(); - setUsers(data.users); + }; + + fetchData(); + }, [navigate, setUsername, setUsers]); + + const deleteUser = async (userId) => { + const loadingToast = toast.loading('Deleting user...'); + try { + const response = await fetch(`https://task-master-api-psi.vercel.app/api/v1/admin/delete/${userId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + authorization: "Bearer " + JSON.parse(localStorage.getItem('Admintoken')) + } + }); + + const data = await response.json(); + + if (data.success) { + toast.success('User deleted successfully', { id: loadingToast }); + // Refresh users list + setUsers(users.filter(user => user._id !== userId)); + } else { + toast.error(data.msg || 'Failed to delete user', { id: loadingToast }); } - fetchUsers(); - },[]) - return(<> -
-
- -
- -
-
- + } catch (error) { + console.error('Error deleting user:', error); + toast.error('Failed to delete user', { id: loadingToast }); + } + }; + + if (!localStorage.getItem('Admintoken')) { + return ; + } + + return ( +
+ +
+ }> + + +
+ + + {/* Welcome Section with Gradient */} +
+
+
+ +
+
+
+ +
+ {/* Quick Stats Section */} +
+
+
+
+ + + +
+
+

Total Users

+

{users?.length || 0}

+
+
+
+ +
+
+
+ + + +
+
+

Active Users

+

{users?.length || 0}

+
+
+
+ +
+
+
+ + + +
+
+

System Status

+

Operational

-
- ) - }else{ - return(<> - - ) - } - -} + {/* Users Management Section */} +
+
+

+ Manage Users +

+
+
+ {/*
+ + + +
*/} + {/* setSearchTerm(e.target.value)} + placeholder="Search users..." + className="pl-10 pr-4 py-2 w-64 rounded-xl border border-gray-200 + focus:ring-2 focus:ring-indigo-500 focus:border-transparent + bg-white/70 backdrop-blur-sm transition-all duration-300" + /> */} +
+
+
-function Warning(){ - return(<> -
-
-

+
+ +
+

+
+
+ ); +} + +function Warning() { + return ( +
+
+
+ + + +
+

Access Denied

- You need to sign in to access this page + Please sign in to access the admin dashboard

+
- ) + ); +} + +function LoadingSpinner() { + return ( +
+
+
+ ); } diff --git a/frontend/src/pages/admin/dashboard/components/Users.jsx b/frontend/src/pages/admin/dashboard/components/Users.jsx index 922f8ca..dc690bd 100644 --- a/frontend/src/pages/admin/dashboard/components/Users.jsx +++ b/frontend/src/pages/admin/dashboard/components/Users.jsx @@ -1,87 +1,125 @@ -import { useNavigate } from "react-router"; -import { useRecoilState } from "recoil"; -import { UsersList } from "../Dashboardstore/admin-dashboard-store"; +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { motion } from 'framer-motion'; +export function Users({ UsersList }) { + const [isDeleting, setIsDeleting] = useState(false); -export function Users(){ - // https://task-master-api-psi.vercel.app/api/v1/admin/deleteuser - const navigate = useNavigate(); - const [users,setUsers] = useRecoilState(UsersList); - const removeUser = async(userId)=>{ - const response = await fetch(`https://task-master-api-psi.vercel.app/api/v1/admin/deleteuser?userId=${userId}`,{ - method : 'DELETE', - headers : { - 'Content-Type' : 'application/json', - authorization : 'Bearer ' + JSON.parse(localStorage.getItem('Admintoken')) - } - }) - const data = await response.json(); - if(data.success){ - const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/getusers',{ - method : 'GET', - headers : { - "Content-Type" : "application/json", - authorization : "Bearer " + JSON.parse(localStorage.getItem('Admintoken')) + const handleDelete = async (userId) => { + setIsDeleting(true); + const deleteToast = toast.loading('Deleting user...'); + + try { + const response = await fetch(`https://task-master-api-psi.vercel.app/api/v1/admin/deleteuser?userId=${userId}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + authorization: "Bearer " + JSON.parse(localStorage.getItem('Admintoken')) } - }) + }); + const data = await response.json(); - setUsers(data.users); + + if (data.success) { + toast.success(data.msg, { id: deleteToast }); + window.location.reload(); + } else { + toast.error(data.msg || 'Failed to delete user', { id: deleteToast }); + } + } catch (error) { + console.error('Error deleting user:', error); + toast.error('Failed to delete user. Please try again.', { id: deleteToast }); + } finally { + setIsDeleting(false); } - } + }; - return(<> - - - - - - - - - - {/* {console.log(users[0]==undefined)} */} - {(users[0] == undefined) ? - - : - {users.map((user,index)=>{ - return - - - - - - })} - } -
S.NoIdUsernameOptions
-
-
- {index+1} - - {user._id} - - {user.Username} - - removeUser(user._id)}/> -
- ) -} + const getInitial = (username) => { + return username && username.length > 0 ? username[0].toUpperCase() : '?'; + }; + return ( +
+
+

User Management

+
+ Total Users: {UsersList?.length || 0} +
+
+
+ {UsersList && UsersList.map((user, index) => ( + +
+
+
+ {getInitial(user.Username)} +
+
+

+ {user.Username} +

+

+ ID: {user._id.slice(-8)} +

+
+
+ +
+
+ + Active + +
+ +
+
+
+ ))} +
-function Skeletons(){ - return(<> -
-
-
-
-
-
-
-
-
-
-
-
- ) - } - \ No newline at end of file + {(!UsersList || UsersList.length === 0) && ( + +
+ + + +
+

No Users Found

+

There are no users registered in the system yet.

+
+ )} +
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/admin/dashboard/components/Wish.jsx b/frontend/src/pages/admin/dashboard/components/Wish.jsx index fbd3111..c8667b3 100644 --- a/frontend/src/pages/admin/dashboard/components/Wish.jsx +++ b/frontend/src/pages/admin/dashboard/components/Wish.jsx @@ -1,14 +1,99 @@ -import { useRecoilValue } from "recoil"; -import { username } from "../Dashboardstore/admin-dashboard-store"; +import { useEffect, useState } from 'react'; export function Wish() { - const Username = useRecoilValue(username); + const [greeting, setGreeting] = useState(''); + const [currentTime, setCurrentTime] = useState(''); + + useEffect(() => { + const updateGreeting = () => { + const hour = new Date().getHours(); + if (hour >= 5 && hour < 12) { + setGreeting('Good Morning'); + } else if (hour >= 12 && hour < 17) { + setGreeting('Good Afternoon'); + } else if (hour >= 17 && hour < 21) { + setGreeting('Good Evening'); + } else { + setGreeting('Good Night'); + } + }; + + const updateTime = () => { + const now = new Date(); + setCurrentTime(now.toLocaleTimeString('en-US', { + hour: '2-digit', + minute: '2-digit', + hour12: true + })); + }; + + updateGreeting(); + updateTime(); + + const timer = setInterval(() => { + updateTime(); + updateGreeting(); + }, 1000); + + return () => clearInterval(timer); + }, []); + return ( -
-

- Welcome back, {Username}! -

-

Manage your tasks and users from your dashboard.

+
+ {/* Decorative Elements */} +
+
+ +
+ {/* Greeting Section with Time */} +
+

+ {greeting}, Admin! +

+
+ + + + {currentTime} +
+

+ Welcome to your dashboard control center +

+
+ + {/* Quick Stats */} +
+
+
+
+ + + +
+
+

System Load

+

Optimal

+
+
+
+
+
+
+ + + +
+
+

Security

+

Protected

+
+
+
+
+
- ) + ); } \ No newline at end of file diff --git a/frontend/src/pages/admin/dashboard/components/navbar.jsx b/frontend/src/pages/admin/dashboard/components/navbar.jsx index 221862e..af39568 100644 --- a/frontend/src/pages/admin/dashboard/components/navbar.jsx +++ b/frontend/src/pages/admin/dashboard/components/navbar.jsx @@ -1,40 +1,100 @@ -import { useNavigate } from "react-router" +import { useNavigate } from "react-router"; +import { useState, useEffect } from "react"; +export function Navbar({ Username }) { + const navigate = useNavigate(); + const [isScrolled, setIsScrolled] = useState(false); -export function Navbar({Username}){ + // Add scroll listener for navbar effects + useEffect(() => { + const handleScroll = () => { + setIsScrolled(window.scrollY > 10); + }; + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); - const navigate = useNavigate(); + const logout = () => { + localStorage.removeItem('Admintoken'); + navigate('/'); + }; - const logout =()=>{ - localStorage.removeItem('Admintoken') - navigate('/admin/signin') - } - - const naviagateToProfile = ()=>{ - navigate('/admin/profile') - } - - return(<> -
-

- TaskMaster -

-
-
- {(Username[0] == '') ? : Username[0]} -
-
- - - + const navigateToProfile = () => { + navigate('/admin/profile'); + }; + + return ( +
+
+
+ {/* Logo Section */} +
+

+ TaskMaster +

+ + ADMIN + +
+ + {/* Actions Section */} +
+ {/* Profile Button */} +
+
+ {Username ? ( + Username[0].toUpperCase() + ) : ( + + )} +
+
+
+
+ + {/* Logout Button */} + +
- ) + ); } -function Profile(){ - return(<> -
- ) - } \ No newline at end of file +function Profile() { + return ( +
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/admin/profile/profile.jsx b/frontend/src/pages/admin/profile/profile.jsx index df022bb..3a38dfe 100644 --- a/frontend/src/pages/admin/profile/profile.jsx +++ b/frontend/src/pages/admin/profile/profile.jsx @@ -1,88 +1,129 @@ import { useRecoilState } from "recoil"; import { Navbar } from "../dashboard/components/navbar"; -import { adminProfilemessageAtom, adminProfilepasswordAtom, adminProfileusernameAtom, username } from "../dashboard/Dashboardstore/admin-dashboard-store"; -import { useEffect } from "react"; -import {Message} from '../../signup&signin-comp/Message' -import {Heading} from '../../signup&signin-comp/heading' -import {InputBox} from '../../signup&signin-comp/InputBox' -import {Button} from '../../signup&signin-comp/Button' +import { adminProfilepasswordAtom, adminProfileusernameAtom, username } from "../dashboard/Dashboardstore/admin-dashboard-store"; +import { useEffect, useState } from "react"; import { useNavigate } from "react-router"; +import toast, { Toaster } from 'react-hot-toast'; +export default function AdminProfile() { + const [Username, setUsername] = useRecoilState(username); + const [usernameProfile, setusername] = useRecoilState(adminProfileusernameAtom); + const [password, setpassword] = useRecoilState(adminProfilepasswordAtom); + const [isLoading, setIsLoading] = useState(false); + const navigate = useNavigate(); -export default function AdminProfile(){ - - const [Username,setUsername] = useRecoilState(username); - const [message,setMessage] = useRecoilState(adminProfilemessageAtom); - const [usernameProfile,setusername] = useRecoilState(adminProfileusernameAtom); - const [password,setpassword] = useRecoilState(adminProfilepasswordAtom); + const getInitial = (username) => { + return username && username.length > 0 ? username[0].toUpperCase() : '?'; + }; - const usernameHadler = (event)=>{ - setusername(event.target.value) - } - - const passwordHadler = (event)=>{ - setpassword(event.target.value) - } - const navigate = useNavigate(); - const UpdateDetails = ()=>{ - // https://task-master-api-psi.vercel.app/api/v1/admin/update (PUT) - const bodyData = JSON.stringify({username : usernameProfile, password : password }); - const callDB=async()=>{ - try{ - const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/update',{ - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - authorization : 'Bearer ' + JSON.parse(localStorage.getItem('Admintoken')) - }, - body: bodyData - }) - const data = await response.json(); - setMessage([{message : data.msg,success : data.success}]) - localStorage.removeItem('Admintoken') - setTimeout(()=>{ - navigate('/admin/signin') - },2000) - } - catch(e){ - setMessage([{message : 'Error connecting server please check your internet connection',success : 'false'}]) - } + const UpdateDetails = async () => { + if (!usernameProfile || !password) { + toast.error('Username and password cannot be empty'); + return; } - callDB(); - } - - useEffect(()=>{ - const fecthUsername = async()=>{ - const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/details',{ - method : 'GET', - headers : { - 'Content-Type' : 'application/json', - authorization : "Bearer " + JSON.parse(localStorage.getItem('Admintoken')) - } + setIsLoading(true); + const updateToast = toast.loading('Updating profile...'); + + try { + const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/update', { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + authorization: 'Bearer ' + JSON.parse(localStorage.getItem('Admintoken')) + }, + body: JSON.stringify({ + username: usernameProfile, + password + }) }); const data = await response.json(); - setUsername(data.username); + + if (data.success) { + toast.success(data.msg, { id: updateToast }); + localStorage.removeItem('Admintoken'); + setTimeout(() => { + navigate('/admin/signin'); + }, 1500); + } else { + toast.error(data.msg, { id: updateToast }); + } + } catch (e) { + toast.error('Error connecting to server', { id: updateToast }); + } finally { + setIsLoading(false); } - fecthUsername(); - },[]) - return( -
- - {message && } + }; + + return ( +
+ +
-
-
-
- -
- - -
- +
diff --git a/frontend/src/pages/admin/signin/signin.jsx b/frontend/src/pages/admin/signin/signin.jsx index c1fb50d..c279784 100644 --- a/frontend/src/pages/admin/signin/signin.jsx +++ b/frontend/src/pages/admin/signin/signin.jsx @@ -4,80 +4,149 @@ import { Heading } from "../../signup&signin-comp/heading"; import { InputBox } from "../../signup&signin-comp/InputBox"; import { Button } from "../../signup&signin-comp/Button"; import { Hr } from "../../signup&signin-comp/Hr"; -import { Message } from "../../signup&signin-comp/Message"; import { SigninDialogue } from "../../signup&signin-comp/Dialogue"; import { useRecoilState } from "recoil"; -import { adminsigninmessageAtom, adminsigninpasswordAtom, adminsigninusernameatom} from "./store/signinstore"; - -export default function AdminSignin(){ +import { adminsigninpasswordAtom, adminsigninusernameatom } from "./store/signinstore"; +import toast, { Toaster } from 'react-hot-toast'; +import { motion } from 'framer-motion'; +import { Link } from 'react-router-dom'; +import { LoadingSpinner } from '../../../components/LoadingSpinner'; +export default function AdminSignin() { const navigate = useNavigate(); - useEffect(()=>{ - if(localStorage.getItem('Admintoken')){ - navigate('/admin/dashboard') - } - },[]) - - const [message,setMessage] = useRecoilState(adminsigninmessageAtom); - const [username,setusername] = useRecoilState(adminsigninusernameatom); - const [password,setpassword] = useRecoilState(adminsigninpasswordAtom); + const [isLoading, setIsLoading] = useState(false); + const [username, setusername] = useRecoilState(adminsigninusernameatom); + const [password, setpassword] = useRecoilState(adminsigninpasswordAtom); + useEffect(() => { + if (localStorage.getItem('Admintoken')) { + navigate('/admin/dashboard') + } + }, [navigate]) - const usernameHadler = (event)=>{ + const usernameHandler = (event) => { setusername(event.target.value) } - const passwordHadler = (event)=>{ + const passwordHandler = (event) => { setpassword(event.target.value) } - const SigninUser = ()=>{ - if(username == '' || password == ''){ - alert('Username and password must not be empty') + const SigninUser = async () => { + if (username === '' || password === '') { + toast.error('Username and password must not be empty'); + return; } - const bodyData = JSON.stringify({ username, password }); - const callDB=async()=>{ - try{ - const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/signin',{ - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: bodyData - }) - const data = await response.json(); - if(data.msg){ - setMessage([{message : data.msg,success : false}]) - }else if(data.token){ - localStorage.setItem('Admintoken',JSON.stringify(data.token)) - navigate('/admin/dashboard'); - } - } - catch(e){ - setMessage([{message : 'Error connecting server please check your internet connection',success : 'false'}]) + + setIsLoading(true); + const loadingToast = toast.loading('Signing in...'); + try { + const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/signin', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ username, password }) + }); + const data = await response.json(); + + if (data.token) { + localStorage.setItem('Admintoken', JSON.stringify(data.token)); + toast.success('Signed in successfully', { id: loadingToast }); + navigate('/admin/dashboard'); + } else { + toast.error(data.msg || 'Sign in failed', { id: loadingToast }); } + } catch (e) { + toast.error('Network error. Please check your connection.', { id: loadingToast }); + } finally { + setIsLoading(false); } - callDB(); } - return( -
- {message && } - -
-
- - - -
+ + + + + + + + Back to Home + + + + +
+ +
-
) } diff --git a/frontend/src/pages/admin/signup/signup.jsx b/frontend/src/pages/admin/signup/signup.jsx index 513b006..4bec51a 100644 --- a/frontend/src/pages/admin/signup/signup.jsx +++ b/frontend/src/pages/admin/signup/signup.jsx @@ -1,76 +1,79 @@ -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useNavigate } from "react-router"; import { Heading } from "../../signup&signin-comp/heading"; import { InputBox } from "../../signup&signin-comp/InputBox"; import { Button } from "../../signup&signin-comp/Button"; import { Hr } from "../../signup&signin-comp/Hr"; -import { Message } from "../../signup&signin-comp/Message"; import { SigninDialogue } from "../../signup&signin-comp/Dialogue"; import { useRecoilState } from "recoil"; -import { adminsignupmessageAtom, adminsignuppasswordAtom, adminsignupusernameAtom} from "./store/signupstore"; +import { adminsignuppasswordAtom, adminsignupusernameAtom } from "./store/signupstore"; +import toast, { Toaster } from 'react-hot-toast'; -export default function AdminSignup(){ - +export default function AdminSignup() { const navigate = useNavigate(); - useEffect(()=>{ - if(localStorage.getItem('Admintoken')){ - navigate('/admin/dashboard') - } - },[]) - - const [message,setMessage] = useRecoilState(adminsignupmessageAtom); - const [username,setusername] = useRecoilState(adminsignupusernameAtom); - const [password,setpassword] = useRecoilState(adminsignuppasswordAtom); + const [username, setusername] = useRecoilState(adminsignupusernameAtom); + const [password, setpassword] = useRecoilState(adminsignuppasswordAtom); + useEffect(() => { + if (localStorage.getItem('Admintoken')) { + navigate('/admin/dashboard') + } + }, [navigate]) - const usernameHadler = (event)=>{ + const usernameHandler = (event) => { setusername(event.target.value) } - const passwordHadler = (event)=>{ + const passwordHandler = (event) => { setpassword(event.target.value) } - const SignupUser = ()=>{ - // https://task-master-api-psi.vercel.app/api/v1/user/signup (POST) - const bodyData = JSON.stringify({ username, password }); - const callDB=async()=>{ - try{ - const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/signup',{ - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: bodyData - }) - const data = await response.json(); - setMessage([{message : data.msg,success : data.success}]) - } - catch(e){ - setMessage([{message : 'Error connecting server please check your internet connection',success : 'false'}]) + const SignupUser = async () => { + if (username === '' || password === '') { + toast.error('Username and password must not be empty'); + return; + } + + const loadingToast = toast.loading('Creating account...'); + try { + const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/admin/signup', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ username, password }) + }); + const data = await response.json(); + + if (data.success) { + toast.success(data.msg, { id: loadingToast }); + setTimeout(() => { + navigate('/admin/signin'); + }, 1500); + } else { + toast.error(data.msg || 'Signup failed', { id: loadingToast }); } + } catch (e) { + toast.error('Network error. Please check your connection.', { id: loadingToast }); } - callDB(); } - return( -
- {message && } - + return ( +
+
-
- +
+
+ + + ADMIN + +
- - -
-
-
diff --git a/frontend/src/pages/home/Home.jsx b/frontend/src/pages/home/Home.jsx new file mode 100644 index 0000000..47c80b0 --- /dev/null +++ b/frontend/src/pages/home/Home.jsx @@ -0,0 +1,269 @@ +import { useNavigate } from 'react-router-dom'; +import { motion } from 'framer-motion'; +import { useState, useEffect, useCallback } from 'react'; + +export default function Home() { + const navigate = useNavigate(); + const [secretCode, setSecretCode] = useState(''); + + const handleAdminAccess = useCallback((code) => { + if (code === 'admin') { + setTimeout(() => navigate('/admin/signin'), 0); + } + return code; + }, [navigate]); + + useEffect(() => { + const handleKeyPress = (e) => { + setSecretCode(prev => { + const newCode = (prev + e.key).slice(-5); + return handleAdminAccess(newCode); + }); + }; + + window.addEventListener('keypress', handleKeyPress); + return () => window.removeEventListener('keypress', handleKeyPress); + }, [handleAdminAccess]); + + return ( +
+ {/* Navbar - Only show user-related actions */} + + + {/* Hero Section */} +
+
+
+ + Manage Tasks with + + {" "}Efficiency + + + + TaskMaster helps teams and individuals organize, track, and manage + their work with powerful features and a simple interface. + +
+
+
+ + {/* Features Section */} +
+
+
+ {/* Task Management */} + +
+ + + +
+

Task Management

+

Create, organize, and track tasks with ease. Set priorities and deadlines.

+
+ + {/* User Management */} + +
+ + + +
+

User Management

+

Manage team members and their access levels with admin controls.

+
+ + {/* Secure & Reliable */} + +
+ + + +
+

Secure & Reliable

+

Enterprise-grade security with reliable performance and data protection.

+
+
+
+
+ + {/* Interactive Animation Section */} +
+
+ +
+ {/* Animated Background Elements */} + + {[...Array(6)].map((_, i) => ( + + ))} + + + {/* Content */} +
+ + Experience the Future of Task Management + + +
+ + + + Smart Organization +
+
+ + + + Real-time Updates +
+
+ + + + Secure & Reliable +
+
+
+
+
+
+
+ + {/* Footer */} +
+
+
+
+ © 2024 TaskMaster. All rights reserved. +
+
+ Made with + + + + + + by + + sreecharan + +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/signup&signin-comp/Warning.jsx b/frontend/src/pages/signup&signin-comp/Warning.jsx new file mode 100644 index 0000000..463163f --- /dev/null +++ b/frontend/src/pages/signup&signin-comp/Warning.jsx @@ -0,0 +1,14 @@ +export function Warning() { + return ( +
+
+

+ Access Denied +

+

+ You need to sign in to access this page +

+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/user/Dashboard/Components/Addtodo.jsx b/frontend/src/pages/user/Dashboard/Components/Addtodo.jsx index dfa9356..dfef6ca 100644 --- a/frontend/src/pages/user/Dashboard/Components/Addtodo.jsx +++ b/frontend/src/pages/user/Dashboard/Components/Addtodo.jsx @@ -1,59 +1,131 @@ import { useRecoilState } from "recoil"; -import { InputBox } from "./InputBox"; -import { todoDescription, todosAtom, todoTitle } from "../store/dashboardStore"; - - -export function AddTodo(){ - - - const [title,setTitle] = useRecoilState(todoTitle); - const [description,setdescription] = useRecoilState(todoDescription); - const [todos,Settodos] = useRecoilState(todosAtom); - - const titlesetter = (event)=>{ - setTitle(event.target.value) - } - - const descriptionsetter = (event)=>{ - setdescription(event.target.value) - } - - const addTodo = async()=>{ - const bodyData = JSON.stringify({ title,description }) - const resposne = await fetch('https://task-master-api-psi.vercel.app/api/v1/user/addtodo',{ - method : 'POST', - headers : { - 'Content-Type' : 'application/json', - authorization : 'Bearer ' + JSON.parse(localStorage.getItem('token')) - }, - body : bodyData - }) - const data = await resposne.json(); - console.log(data); - const fetchTodos = async()=>{ - const response =await fetch('https://task-master-api-psi.vercel.app/api/v1/user/gettodos',{ - method : 'GET', - headers : { - 'Content-Type' : 'application/json', - authorization : 'Bearer ' + JSON.parse(localStorage.getItem('token')) +import { todoDescription, todosAtom, todoTitle, messageAtom } from "../store/dashboardStore"; +import { useState } from "react"; + +export function AddTodo() { + const [title, setTitle] = useRecoilState(todoTitle); + const [description, setDescription] = useRecoilState(todoDescription); + const [todos, setTodos] = useRecoilState(todosAtom); + const [message, setMessage] = useRecoilState(messageAtom); + const [isLoading, setIsLoading] = useState(false); + + const handleTitleChange = (event) => { + setTitle(event.target.value); + }; + + const handleDescriptionChange = (event) => { + setDescription(event.target.value); + }; + + const fetchTodos = async () => { + try { + const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/user/gettodos', { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + authorization: 'Bearer ' + JSON.parse(localStorage.getItem('token')) } - }) + }); const data = await response.json(); - Settodos(data.todos) + if (data.success) { + setTodos(data.todos); + } + } catch (error) { + console.error('Failed to fetch todos'); } - fetchTodos(); - } - - return(<> -
-

- Add a Todo -

- - - + }; + + const addTodo = async () => { + if (!title.trim()) { + setMessage([{ message: 'Title cannot be empty', success: false }]); + return; + } + + setIsLoading(true); + try { + const response = await fetch('https://task-master-api-psi.vercel.app/api/v1/user/addtodo', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + authorization: 'Bearer ' + JSON.parse(localStorage.getItem('token')) + }, + body: JSON.stringify({ title, description }) + }); + const data = await response.json(); + + if (data.success) { + if (data.todo) { + setTodos(prevTodos => [...prevTodos, data.todo]); + } + setMessage([{ message: 'Task added successfully!', success: true }]); + setTitle(''); + setDescription(''); + } else { + setMessage([{ message: data.msg || 'Failed to add task', success: false }]); + } + } catch (error) { + setMessage([{ message: 'Error connecting to server', success: false }]); + } finally { + setIsLoading(false); + } + }; + + const handleKeyPress = (event) => { + if (event.key === 'Enter' && !event.shiftKey) { + event.preventDefault(); + addTodo(); + } + }; + + return ( +
+
+
+ +
+
+