diff --git a/public/assets/chillguy-gID_7.webp b/public/assets/chillguy-gID_7.webp new file mode 100644 index 0000000000000000000000000000000000000000..675b468afc1bc4f98029398deac0466daf0c538a Binary files /dev/null and b/public/assets/chillguy-gID_7.webp differ diff --git a/public/assets/dashb.png b/public/assets/dashb.png new file mode 100644 index 0000000000000000000000000000000000000000..e32d56ac34aa88c3dff3951a95d20ce3608ab4b9 Binary files /dev/null and b/public/assets/dashb.png differ diff --git a/public/assets/path288.png b/public/assets/path288.png new file mode 100644 index 0000000000000000000000000000000000000000..0d40a887ec47fdaab926342ef78ae38511a70629 Binary files /dev/null and b/public/assets/path288.png differ diff --git a/src/App.jsx b/src/App.jsx index 4e1cb8fb48fdaa9156393825c59a4c8e321544d2..ed1bfd2177e87d53ae1b9991f6c45aaf7e30a50b 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,13 +1,19 @@ import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -import AdminLogin4 from "./components2/AdminLogin2/AdminLogin4"; -import Dashboard from "./components2/Dashboard/dashboard"; - +import AdminLogin from "./components2/AdminLogin2/AdminLogin4"; +import Dashboard from "./components2/pages/dashboard"; +import AddAgent from "./components2/pages/add-agent"; +import Sidebar from "./components2/pages/Sidebar"; +//import AssignTask from "./components2/pages/assign-task"; function App() { return ( <Router> <Routes> - <Route path="/" element={<AdminLogin4 />} /> + + <Route path="/" element={<AdminLogin />} /> + <Route path="/sidebar" element={<Sidebar />} /> <Route path="/dashboard" element={<Dashboard />} /> + <Route path="/add-agent" element={<AddAgent />} /> + {/*<Route path="/assign-task" element={<AssignTask />} />*/} </Routes> </Router> ); diff --git a/src/components2/AdminLogin2/AdminLogin4.jsx b/src/components2/AdminLogin2/AdminLogin4.jsx index 887f763c3d4e1a3ef918c2ca823f5c906ec2e83f..b5b967fbff44016a8daaa9963c15bfc5551a106e 100644 --- a/src/components2/AdminLogin2/AdminLogin4.jsx +++ b/src/components2/AdminLogin2/AdminLogin4.jsx @@ -1,28 +1,39 @@ -import { useState } from "react"; +import { useState, useEffect } from "react"; import { FiMail, FiEye, FiEyeOff } from "react-icons/fi"; import { useNavigate } from "react-router-dom"; import axios from "axios"; + const loginImage = new URL("/public/assets/login_img/loginadmin.svg", import.meta.url).href; -//const API_BASE_URL = "https://8dfst7hm-3000.inc1.devtunnels.ms/"; const API_BASE_URL = "http://localhost:5000/"; export default function AdminLogin() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [showPassword, setShowPassword] = useState(false); + const [loading, setLoading] = useState(false); const [error, setError] = useState({ email: "", password: "", general: "" }); + + const navigate = useNavigate(); + + // Cleanup function + useEffect(() => { + let isMounted = true; + return () => { + isMounted = false; + }; + }, []); + // Validation functions const validateEmail = (email) => /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email); const validatePassword = (pass) => pass.length >= 8; - const navigate = useNavigate(); - + // Handle input changes const handleEmailChange = (e) => { const value = e.target.value; setEmail(value); setError((prev) => ({ ...prev, - email: value === "" ? "" : !validateEmail(value) ? "Invalid email format" : "" + email: value && !validateEmail(value) ? "Invalid email format" : "", })); }; @@ -31,60 +42,57 @@ export default function AdminLogin() { setPassword(value); setError((prev) => ({ ...prev, - password: value === "" ? "" : !validatePassword(value) ? "Password must be at least 8 characters" : "" + password: value && !validatePassword(value) ? "Password must be at least 8 characters" : "", })); }; - const [loading, setLoading] = useState(false); -const handleSubmit = async (e) => { - e.preventDefault(); + // Handle form submission + const handleSubmit = async (e) => { + e.preventDefault(); + if (loading) return; + setLoading(true); + + let newErrors = { email: "", password: "", general: "" }; + + if (!validateEmail(email)) newErrors.email = "Invalid email format."; + if (!validatePassword(password)) newErrors.password = "Password must be at least 8 characters long."; - if (loading) return; - setLoading(true); - - let newErrors = { email: "", password: "", general: "" }; - - // Validation - if (!validateEmail(email)) newErrors.email = "Invalid email format."; - if (!validatePassword(password)) newErrors.password = "Password must be at least 8 characters long."; - - if (newErrors.email || newErrors.password) { - //newErrors.general = "*Incorrect Email or Password!"; - setError(newErrors); - setLoading(false); - return; - } - - try { - const response = await axios.post( - `${API_BASE_URL}api/auth/login`, - { email, password }, - { - headers: { "Content-Type": "application/json" }, - withCredentials: true, + if (newErrors.email || newErrors.password) { + setError(newErrors); + setLoading(false); + return; + } + + try { + const response = await axios.post( + `${API_BASE_URL}api/auth/login`, + { email, password }, + { + headers: { "Content-Type": "application/json" }, + withCredentials: true, + } + ); + + if (response.status === 200) { + console.log("Login Success:", response.data); + document.cookie = "session_token=sample_token; path=/"; + alert("Login Successful!"); + setError({ email: "", password: "", general: "" }); + navigate("/dashboard"); } - ); - - if (response.status === 200) { - console.log("Login Success:", response.data); - alert("Login Successful!"); - setError({ email: "", password: "", general: "" }); - navigate("/dashboard"); + } catch (error) { + console.error("Login error:", error.response?.data || error.message); + + setError((prev) => ({ + ...prev, + email: error.response?.data?.email ? "Email not found or incorrect" : "", + password: error.response?.data?.password ? "Incorrect password" : "", + general: error.response?.data?.message || "Login failed. Please check your credentials.", + })); + } finally { + setLoading(false); } - } catch (error) { - console.error(error.response?.data || error.message); - setError({ - email: error.response?.data?.email || "", - password: error.response?.data?.password || "", - general: error.response?.data?.message || "Login failed. Please check your credentials.", - - }); - }finally{ - - setLoading(false); - } -}; - + }; return ( <div className="flex flex-col items-center justify-center h-screen bg-[#D9F6F0] px-6 py-4"> @@ -107,27 +115,25 @@ const handleSubmit = async (e) => { <form onSubmit={handleSubmit} className="w-full max-w-md"> <div className="relative w-full mb-8"> - <div className={`relative border-2 rounded-lg border-[#6FE7D1] bg-transparent ${error.email ? "border-[#EEAB4D]" : ""}`}> - <label - className={`absolute -top-3 left-4 px-2 bg-[#2E2E2E] border border-[#6FE7D1] rounded-md text-sm font-font-primary font-medium ${error.email ? "border-[#EEAB4D] text-[#EEAB4D]" : "text-[#6FE7D1]"}`} - > - Admin Email - </label> - <input - type="text" - value={email} - onChange={handleEmailChange} - placeholder="Enter email" - className={`w-full h-14 px-3 pr-12 bg-transparent text-white text-lg focus:outline-none font-font-primary ${ - error.email ? "border-red-500" : "border-gray-300" - }`} - /> - <FiMail - className="absolute right-4 top-1/2 transform -translate-y-1/2 text-xl" - style={{ color: error.email ? "#EEAB4D" : "#6FE7D1" }} - /> - </div> - {error.email && <p className="text-[#EEAB4D] text-sm mt-1">{error.email}</p>} + <div className={`relative border-2 rounded-lg bg-transparent ${error.email ? "border-[#EEAB4D]" : "border-[#6FE7D1]"}`}> + <label + className={`absolute -top-3 left-4 px-2 bg-[#2E2E2E] border rounded-md text-sm font-font-primary font-medium ${error.email ? "border-[#EEAB4D] text-[#EEAB4D]" : "border-[#6FE7D1] text-[#6FE7D1]"}`} + > + Admin Email + </label> + <input + type="text" + value={email} + onChange={handleEmailChange} + placeholder="Enter email" + className="w-full h-14 px-3 pr-12 bg-transparent text-white text-lg focus:outline-none font-font-primary" + /> + <FiMail + className="absolute right-4 top-1/2 transform -translate-y-1/2 text-xl" + style={{ color: error.email ? "#EEAB4D" : "#6FE7D1" }} + /> +</div> +{error.email && <p className="text-[#EEAB4D] text-sm mt-1">{error.email}</p>} </div> {/* Passwd Input */} diff --git a/src/components2/Dashboard/dashboard.jsx b/src/components2/Dashboard/dashboard.jsx deleted file mode 100644 index 5e43f85660e4ebce9973b6985ee48a3986152025..0000000000000000000000000000000000000000 --- a/src/components2/Dashboard/dashboard.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react' - -const Dashboard = () => { - return ( - <div className="flex flex-col items-center justify-center h-screen bg-[#D9F6F0] px-6 py-4"> - <h1 className="text-4xl font-bold text-[#0C4339] mb-6">Dashboard</h1> - <p className="text-lg text-[#6FE7D1] mb-4">Welcome to the Admin Dashboard!</p> - <button className="mt-4 px-4 py-2 bg-[#6FE7D1] text-white rounded hover:bg-[#5CC4AE]"> - Logout - </button> - </div> - ) -} - - -export default Dashboard; diff --git a/src/components2/pages/Sidebar.jsx b/src/components2/pages/Sidebar.jsx new file mode 100644 index 0000000000000000000000000000000000000000..09fd3d285576dc6fb206c8f9eab0b7fabb218fc9 --- /dev/null +++ b/src/components2/pages/Sidebar.jsx @@ -0,0 +1,151 @@ +import { useState, useEffect } from "react"; +import { Link, useLocation, useNavigate } from "react-router-dom"; +import { FaTasks, FaCog, FaSignOutAlt } from "react-icons/fa"; +import axios from "axios"; +import { FiMenu, FiX } from "react-icons/fi"; + +const Sidebar = () => { + const location = useLocation(); // Get current route path + const navigate = useNavigate(); // Navigation hook + const [isOpen, setIsOpen] = useState(false); + + const handleLogout = async () => { + try { + await axios.post("http://localhost:5000/api/auth/logout", {}, { withCredentials: true }); + document.cookie = "session_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + navigate("/"); + } catch (err) { + console.error("Logout failed:", err); + } + }; + + // Automatically close the sidebar when the route changes + useEffect(() => { + setIsOpen(false); + }, [location.pathname]); + + return ( + <> + {/* Mobile Toggle Button */} + <button + onClick={() => setIsOpen(true)} + className="md:hidden p-2 fixed top-4 left-4 z-50 bg-white shadow-lg rounded-lg" + > + <FiMenu size={24} /> + </button> + + {/* Backdrop for Mobile */} + {isOpen && ( + <div + className={`fixed inset-0 z-40 ${ + window.innerWidth >= 768 ? "bg-transparent" : "bg-transparent" + }`} + onClick={() => setIsOpen(false)} + ></div> + )} + + {/* Sidebar Container */} + <div + className={`fixed md:relative top-0 left-0 h-[80vh] md:h-[90vh] w-64 bg-white shadow-lg flex flex-col p-6 rounded-2xl md:ml-4 md:mt-4 md:mb-4 transition-transform transform ${ + isOpen ? "translate-x-0" : "-translate-x-full" + } md:translate-x-0 z-50`} + > + {/* Close Button for Mobile */} + <button + onClick={(e) => { + e.stopPropagation(); + setIsOpen(false); + }} + className="md:hidden p-2 self-end z-50" + > + <FiX size={24} /> + </button> + + {/* Logo Section */} + <div className="flex items-center space-x-2 mb-10"> + <div className="bg-[#136D5E] text-white w-10 h-10 flex items-center justify-center rounded-2xl text-lg font-bold"> + O + </div> + <h2 className="text-xl font-bold text-gray-900">OMPOI</h2> + </div> + + {/* Navigation Menu */} + <nav className="flex flex-col gap-4"> + <Link + to="/dashboard" + className={`flex items-center space-x-3 px-4 py-2 rounded-lg ${ + location.pathname === "/dashboard" + ? "bg-[#136D5E] text-white font-semibold" + : "text-gray-600 hover:text-green-700 hover:bg-transparent" + }`} + > + <img src="/assets/path288.png" alt="Agent Task" className="w-5 h-5" /> + <span>DashBoard</span> + </Link> + + <Link + to="/add-agent" + className={`flex items-center space-x-3 px-4 py-2 rounded-lg ${ + location.pathname === "/add-agent" + ? "bg-[#136D5E] text-white font-semibold" + : "text-gray-600 hover:text-green-700 hover:bg-transparent" + }`} + > + <img src="/assets/path288.png" alt="Agent Task" className="w-5 h-5" /> + <span>Agent Task</span> + </Link> + + <Link + to="/assign-task" + className={`flex items-center space-x-3 px-4 py-2 rounded-lg ${ + location.pathname === "/assign-task" + ? "bg-[#136D5E] text-white font-semibold" + : "text-gray-600 hover:text-green-700 hover:bg-transparent" + }`} + > + <FaTasks /> + <span>Assign Task</span> + </Link> + + <Link + to="/settings" + className={`flex items-center space-x-3 px-4 py-2 rounded-lg ${ + location.pathname === "/settings" + ? "bg-[#136D5E] text-white font-semibold" + : "text-gray-600 hover:text-green-700 hover:bg-transparent" + }`} + > + <FaCog /> + <span>Settings</span> + </Link> + </nav> + + {/* Admin Info + Logout Button */} + <div className="mt-auto flex flex-col space-y-3"> + <div className="flex items-center space-x-3 p-4 border-t"> + <img + src="/assets/chillguy-gID_7.webp" + alt="Admin" + className="w-10 h-10 rounded-full" + /> + <div> + <p className="text-sm font-semibold">Master Admin</p> + <p className="text-xs text-gray-500">Admin@ompoi.com</p> + </div> + </div> + + {/* 🔴 Logout Button */} + <button + onClick={handleLogout} + className="flex items-center space-x-3 px-4 py-2 rounded-lg bg-red-500 text-white font-semibold hover:bg-red-700" + > + <FaSignOutAlt /> + <span>Logout</span> + </button> + </div> + </div> + </> + ); +}; + +export default Sidebar; diff --git a/src/components2/pages/Topbar.jsx b/src/components2/pages/Topbar.jsx new file mode 100644 index 0000000000000000000000000000000000000000..500b34b27b3c08ca1c11f076630dd4433537c0c3 --- /dev/null +++ b/src/components2/pages/Topbar.jsx @@ -0,0 +1,31 @@ +import { useLocation } from "react-router-dom"; + +const TopBar = () => { + const location = useLocation(); + + // Map the paths to their respective titles + const pageTitles = { + "/dashboard": "Admin Dashboard", + "/add-agent": "Add Agent", + "/users": "Manage Users", + "/settings": "Settings", + }; + + // Get the title based on the path, fallback to "Dashboard" + const title = pageTitles[location.pathname] || "Dashboard"; + + return ( + <div className="p-4"> + <div className="flex justify-between items-center px-10 py-10 bg-white rounded-2xl shadow-md w-[100%] h-[10vh] mx-auto"> + + <h1 className="text-xl font-semibold text-teal-700">{title}</h1> + <div className="flex items-center gap-2"> + <img src="/assets/chillguy-gID_7.webp" alt="Admin Icon" className="w-6 h-6 rounded-lg" /> + <span className="text-gray-800 font-medium">Admin</span> + </div> + </div> + </div> + ); +}; + +export default TopBar; diff --git a/src/components2/pages/add-agent.jsx b/src/components2/pages/add-agent.jsx new file mode 100644 index 0000000000000000000000000000000000000000..015fcac24a4baafd5467c74ed35a671e299e3aff --- /dev/null +++ b/src/components2/pages/add-agent.jsx @@ -0,0 +1,51 @@ +import React, { useEffect } from "react"; +import { useNavigate } from "react-router-dom"; +import axios from "axios"; +import Sidebar from "./Sidebar"; +import Topbar from "./Topbar"; +const API_BASE_URL = "http://localhost:5000"; + +const AddAgent = () => { + const navigate = useNavigate(); + + useEffect(() => { + const checkSession = async () => { + try { + await axios.get(`${API_BASE_URL}/api/auth/session`, { withCredentials: true }); + } catch (error) { + console.log("Session expired. Logging out..."); + navigate("/"); + } + }; + + + const interval = setInterval(checkSession, 60000); + + return () => clearInterval(interval); + }, [navigate]); + + // Handle logout + {/* + const handleLogout = async () => { + try { + await axios.post(`${API_BASE_URL}/api/auth/logout`, {}, { withCredentials: true }); + navigate("/"); + } catch (err) { + console.error("Logout failed:", err); + } + };*/} + + return ( + <div className="flex h-screen bg-[#CCF2EC]"> + {/* Sidebar */} + <Sidebar /> + {/* Main Content */} + <div className="flex-1 flex flex-col"> + {/* Top Navbar */} + <Topbar /> + </div> + </div> + ); +}; + +export default AddAgent; \ No newline at end of file diff --git a/src/components2/pages/assign-task.jsx b/src/components2/pages/assign-task.jsx new file mode 100644 index 0000000000000000000000000000000000000000..17eb01e53dbc17c15407f4ad4d5ee6f78523a968 --- /dev/null +++ b/src/components2/pages/assign-task.jsx @@ -0,0 +1,73 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import axios from "axios"; +import Sidebar from "./Sidebar"; +import Topbar from "./Topbar"; // New Top Navbar component + +const API_BASE_URL = "http://localhost:5000"; + +const AssignTask= () => { + const navigate = useNavigate(); + + useEffect(() => { + const abortController = new AbortController(); + + const checkSession = async () => { + try { + await axios.get(`${API_BASE_URL}/api/auth/session`, { + withCredentials: true, + signal: abortController.signal, + }); + } catch (error) { + console.log("Session expired. Logging out..."); + handleLogout(); + } + }; + + const handleLogout = async () => { + try { + await axios.post(`${API_BASE_URL}/api/auth/logout`, {}, { withCredentials: true }); + } catch (err) { + console.error("Logout failed:", err); + } + document.cookie = "session_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + navigate("/", { replace: true }); + }; + + const interval = setInterval(checkSession, 30000); + + return () => { + clearInterval(interval); + abortController.abort(); + }; + }, [navigate]); + + return ( + <div className="flex h-screen bg-[#CCF2EC]"> + {/* Sidebar */} + <Sidebar /> + + {/* Main Content */} + <div className="flex-1 flex flex-col"> + {/* Top Navbar */} + <Topbar /> + + {/* Dashboard Content */} + <div className="p-6 flex flex-wrap gap-6"> + <div className="bg-white p-4 rounded-xl shadow-md w-full md:w-1/2 h-[]"> + <h2 className="font-semibold text-lg">Statistics and Summary</h2> + </div> + <div className="bg-white p-4 rounded-xl shadow-md w-full md:w-1/2"> + <h2 className="font-semibold text-lg">Today's Sales</h2> + </div> + <div className="bg-white p-4 rounded-xl shadow-md w-full"> + <h2 className="font-semibold text-lg">Other Details</h2> + </div> + </div> + </div> + </div> + ); +}; + +export default AssignTask; + diff --git a/src/components2/pages/dashboard.jsx b/src/components2/pages/dashboard.jsx new file mode 100644 index 0000000000000000000000000000000000000000..08919c0f1bc07711542f00677e6d934a213649a3 --- /dev/null +++ b/src/components2/pages/dashboard.jsx @@ -0,0 +1,75 @@ +import React, { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import axios from "axios"; +import Sidebar from "./Sidebar"; +import Topbar from "./Topbar"; // New Top Navbar component + +const API_BASE_URL = "http://localhost:5000"; + +const Dashboard = () => { + const navigate = useNavigate(); + + useEffect(() => { + const abortController = new AbortController(); + + const checkSession = async () => { + try { + await axios.get(`${API_BASE_URL}/api/auth/session`, { + withCredentials: true, + signal: abortController.signal, + }); + } catch (error) { + console.log("Session expired. Logging out..."); + handleLogout(); + } + }; + + const handleLogout = async () => { + try { + await axios.post(`${API_BASE_URL}/api/auth/logout`, {}, { withCredentials: true }); + } catch (err) { + console.error("Logout failed:", err); + } + document.cookie = "session_token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + navigate("/", { replace: true }); + }; + + const interval = setInterval(checkSession, 30000); + + return () => { + clearInterval(interval); + abortController.abort(); + }; + }, [navigate]); + + return ( + <div className="flex h-screen bg-[#CCF2EC]"> + {/* Sidebar */} + <Sidebar /> + + {/* Main Content */} + <div className="flex-1 flex flex-col"> + {/* Top Navbar */} + <Topbar /> + + {/* Dashboard Content */} + <div className="p-6 flex flex-wrap gap-6"> + <div className="bg-white p-4 rounded-xl shadow-md w-[450px] h-[300px]"> + <h2 className="font-semibold text-[#136D5E]">Statistics and Summary</h2> +</div> + + <div className="bg-white p-4 rounded-xl shadow-md w-full md:w-1/2"> + <h2 className="font-semibold text-[#136D5E]">Today's Sales</h2> + </div> + <div className="bg-white p-4 rounded-xl shadow-md w-full "> + <h2 className="font-semibold text-[#136D5E]">Other Details</h2> + </div> + </div> + </div> + </div> + ); +}; + +export default Dashboard; + +