diff --git a/frontend-web/src/components/auth/ProtectedRoute.jsx b/frontend-web/src/components/auth/ProtectedRoute.jsx new file mode 100644 index 00000000..22384a45 --- /dev/null +++ b/frontend-web/src/components/auth/ProtectedRoute.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { useAuth } from '@/hooks/useAuth'; +import { Loader2 } from 'lucide-react'; + +export default function ProtectedRoute({ children }) { + const { user, loading } = useAuth(); + + if (loading) { + return ( +
+
+ +

Loading...

+
+
+ ); + } + + if (!user) { + return ; + } + + return children; +} \ No newline at end of file diff --git a/frontend-web/src/components/auth/PublicRoute.jsx b/frontend-web/src/components/auth/PublicRoute.jsx new file mode 100644 index 00000000..55d4d0d8 --- /dev/null +++ b/frontend-web/src/components/auth/PublicRoute.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { Navigate } from 'react-router-dom'; +import { useAuth } from '@/hooks/useAuth'; +import { Loader2 } from 'lucide-react'; + +export default function PublicRoute({ children }) { + const { user, loading } = useAuth(); + + if (loading) { + return ( +
+
+ +

Loading...

+
+
+ ); + } + + if (user) { + return ; + } + + return children; +} \ No newline at end of file diff --git a/frontend-web/src/hooks/useAuth.js b/frontend-web/src/hooks/useAuth.js new file mode 100644 index 00000000..51d3b33e --- /dev/null +++ b/frontend-web/src/hooks/useAuth.js @@ -0,0 +1,19 @@ +import { useState, useEffect } from 'react'; +import { onAuthStateChanged } from 'firebase/auth'; +import { auth } from '@/firebase'; + +export function useAuth() { + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const unsubscribe = onAuthStateChanged(auth, (user) => { + setUser(user); + setLoading(false); + }); + + return () => unsubscribe(); + }, []); + + return { user, loading }; +} \ No newline at end of file diff --git a/frontend-web/src/pages/Login.jsx b/frontend-web/src/pages/Login.jsx new file mode 100644 index 00000000..c5263c9f --- /dev/null +++ b/frontend-web/src/pages/Login.jsx @@ -0,0 +1,86 @@ +import React, { useState } from "react"; +import { useNavigate, Link } from "react-router-dom"; +import { signInWithEmailAndPassword } from "firebase/auth"; +import { auth } from "@/firebase"; +import { Button } from "@/components/ui/button"; +import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Loader2 } from "lucide-react"; + +export default function Login() { + const navigate = useNavigate(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const handleLogin = async (e) => { + e.preventDefault(); + setError(null); + + if (!email || !password) { + setError("Email and password are required."); + return; + } + + setLoading(true); + try { + await signInWithEmailAndPassword(auth, email, password); + navigate("/"); + } catch (error) { + setError("Invalid credentials. Please try again."); + } finally { + setLoading(false); + } + }; + + return ( +
+ + + Login + Enter your credentials to access your account. + + +
+
+
+ + setEmail(e.target.value)} + /> +
+
+ + setPassword(e.target.value)} + /> +
+ {error &&

{error}

} +
+
+
+ + +

+ Don't have an account?{" "} + + Register + +

+
+
+
+ ); +} \ No newline at end of file diff --git a/frontend-web/src/pages/Register.jsx b/frontend-web/src/pages/Register.jsx new file mode 100644 index 00000000..7e1931e5 --- /dev/null +++ b/frontend-web/src/pages/Register.jsx @@ -0,0 +1,128 @@ +import React, { useState } from "react"; +import { useNavigate, Link } from "react-router-dom"; +import { createUserWithEmailAndPassword } from "firebase/auth"; +import { auth } from "@/firebase"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardFooter, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Loader2 } from "lucide-react"; + +export default function Register() { + const navigate = useNavigate(); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(null); + const [loading, setLoading] = useState(false); + + const validatePassword = (password) => { + if (password.length < 6) { + return "Password must be at least 6 characters long."; + } + return null; + }; + + const validateEmail = (email) => { + const re = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + if (!re.test(String(email).toLowerCase())) { + return "Invalid email address."; + } + return null; + }; + + const handleRegister = async (e) => { + e.preventDefault(); + setError(null); + + if (!email || !password) { + setError("Email and password are required."); + return; + } + + const emailError = validateEmail(email); + if (emailError) { + setError(emailError); + return; + } + + const passwordError = validatePassword(password); + if (passwordError) { + setError(passwordError); + return; + } + + setLoading(true); + try { + await createUserWithEmailAndPassword(auth, email, password); + navigate("/"); + } catch (error) { + setError(error.message || "Something went wrong. Please try again."); + } finally { + setLoading(false); + } + }; + + return ( +
+ + + Register + Create a new account. + + + +
+
+
+ + setEmail(e.target.value)} + autoComplete="email" + /> +
+ +
+ + setPassword(e.target.value)} + autoComplete="new-password" + /> +
+ + {error &&

{error}

} +
+
+
+ + + + +

+ Already have an account?{" "} + + Login + +

+
+
+
+ ); +} diff --git a/frontend-web/src/pages/index.jsx b/frontend-web/src/pages/index.jsx index c3d5213d..6caeef9f 100644 --- a/frontend-web/src/pages/index.jsx +++ b/frontend-web/src/pages/index.jsx @@ -1,477 +1,267 @@ -import Layout from "./Layout.jsx"; - -import Dashboard from "./Dashboard"; - -import StaffDirectory from "./StaffDirectory"; - -import AddStaff from "./AddStaff"; - -import EditStaff from "./EditStaff"; - -import Events from "./Events"; - -import CreateEvent from "./CreateEvent"; - -import EditEvent from "./EditEvent"; - -import EventDetail from "./EventDetail"; - -import Business from "./Business"; - -import Invoices from "./Invoices"; - -import Payroll from "./Payroll"; - -import Certification from "./Certification"; - -import Support from "./Support"; - -import Reports from "./Reports"; - -import Settings from "./Settings"; - -import ActivityLog from "./ActivityLog"; - -import AddBusiness from "./AddBusiness"; - -import EditBusiness from "./EditBusiness"; - -import ProcurementDashboard from "./ProcurementDashboard"; - -import OperatorDashboard from "./OperatorDashboard"; - -import VendorDashboard from "./VendorDashboard"; - -import WorkforceDashboard from "./WorkforceDashboard"; - -import Messages from "./Messages"; - -import ClientDashboard from "./ClientDashboard"; - -import Onboarding from "./Onboarding"; - -import ClientOrders from "./ClientOrders"; - -import ClientInvoices from "./ClientInvoices"; - -import VendorOrders from "./VendorOrders"; - -import VendorStaff from "./VendorStaff"; - -import VendorInvoices from "./VendorInvoices"; - -import VendorPerformance from "./VendorPerformance"; - -import WorkforceShifts from "./WorkforceShifts"; - -import WorkforceEarnings from "./WorkforceEarnings"; - -import WorkforceProfile from "./WorkforceProfile"; - -import UserManagement from "./UserManagement"; - -import Home from "./Home"; - -import VendorRateCard from "./VendorRateCard"; - -import Permissions from "./Permissions"; - -import WorkforceCompliance from "./WorkforceCompliance"; - -import Teams from "./Teams"; - -import CreateTeam from "./CreateTeam"; - -import TeamDetails from "./TeamDetails"; - -import VendorManagement from "./VendorManagement"; - -import PartnerManagement from "./PartnerManagement"; - -import EnterpriseManagement from "./EnterpriseManagement"; - -import VendorOnboarding from "./VendorOnboarding"; - -import SectorManagement from "./SectorManagement"; - -import AddEnterprise from "./AddEnterprise"; - -import AddSector from "./AddSector"; - -import AddPartner from "./AddPartner"; - -import EditVendor from "./EditVendor"; - -import SmartVendorOnboarding from "./SmartVendorOnboarding"; - -import InviteVendor from "./InviteVendor"; - -import VendorCompliance from "./VendorCompliance"; - -import EditPartner from "./EditPartner"; - -import EditSector from "./EditSector"; - -import EditEnterprise from "./EditEnterprise"; - -import VendorRates from "./VendorRates"; - -import VendorDocumentReview from "./VendorDocumentReview"; - -import VendorMarketplace from "./VendorMarketplace"; - -import RapidOrder from "./RapidOrder"; - -import SmartScheduler from "./SmartScheduler"; - -import StaffOnboarding from "./StaffOnboarding"; - -import NotificationSettings from "./NotificationSettings"; - -import TaskBoard from "./TaskBoard"; - -import InvoiceDetail from "./InvoiceDetail"; - -import InvoiceEditor from "./InvoiceEditor"; - -//import api-docs-raw from "./api-docs-raw"; - -import Tutorials from "./Tutorials"; - -import Schedule from "./Schedule"; - -import StaffAvailability from "./StaffAvailability"; - -import WorkerShiftProposals from "./WorkerShiftProposals"; - import { BrowserRouter as Router, Route, Routes, useLocation } from 'react-router-dom'; +// Auth components +import ProtectedRoute from '@/components/auth/ProtectedRoute'; +import PublicRoute from '@/components/auth/PublicRoute'; + +// Layout and pages +import Layout from "./Layout.jsx"; +import Home from "./Home"; +import Login from "./Login"; +import Register from "./Register"; +import Dashboard from "./Dashboard"; +import StaffDirectory from "./StaffDirectory"; +import AddStaff from "./AddStaff"; +import EditStaff from "./EditStaff"; +import Events from "./Events"; +import CreateEvent from "./CreateEvent"; +import EditEvent from "./EditEvent"; +import EventDetail from "./EventDetail"; +import Business from "./Business"; +import Invoices from "./Invoices"; +import Payroll from "./Payroll"; +import Certification from "./Certification"; +import Support from "./Support"; +import Reports from "./Reports"; +import Settings from "./Settings"; +import ActivityLog from "./ActivityLog"; +import AddBusiness from "./AddBusiness"; +import EditBusiness from "./EditBusiness"; +import ProcurementDashboard from "./ProcurementDashboard"; +import OperatorDashboard from "./OperatorDashboard"; +import VendorDashboard from "./VendorDashboard"; +import WorkforceDashboard from "./WorkforceDashboard"; +import Messages from "./Messages"; +import ClientDashboard from "./ClientDashboard"; +import Onboarding from "./Onboarding"; +import ClientOrders from "./ClientOrders"; +import ClientInvoices from "./ClientInvoices"; +import VendorOrders from "./VendorOrders"; +import VendorStaff from "./VendorStaff"; +import VendorInvoices from "./VendorInvoices"; +import VendorPerformance from "./VendorPerformance"; +import WorkforceShifts from "./WorkforceShifts"; +import WorkforceEarnings from "./WorkforceEarnings"; +import WorkforceProfile from "./WorkforceProfile"; +import UserManagement from "./UserManagement"; +import VendorRateCard from "./VendorRateCard"; +import Permissions from "./Permissions"; +import WorkforceCompliance from "./WorkforceCompliance"; +import Teams from "./Teams"; +import CreateTeam from "./CreateTeam"; +import TeamDetails from "./TeamDetails"; +import VendorManagement from "./VendorManagement"; +import PartnerManagement from "./PartnerManagement"; +import EnterpriseManagement from "./EnterpriseManagement"; +import VendorOnboarding from "./VendorOnboarding"; +import SectorManagement from "./SectorManagement"; +import AddEnterprise from "./AddEnterprise"; +import AddSector from "./AddSector"; +import AddPartner from "./AddPartner"; +import EditVendor from "./EditVendor"; +import SmartVendorOnboarding from "./SmartVendorOnboarding"; +import InviteVendor from "./InviteVendor"; +import VendorCompliance from "./VendorCompliance"; +import EditPartner from "./EditPartner"; +import EditSector from "./EditSector"; +import EditEnterprise from "./EditEnterprise"; +import VendorRates from "./VendorRates"; +import VendorDocumentReview from "./VendorDocumentReview"; +import VendorMarketplace from "./VendorMarketplace"; +import RapidOrder from "./RapidOrder"; +import SmartScheduler from "./SmartScheduler"; +import StaffOnboarding from "./StaffOnboarding"; +import NotificationSettings from "./NotificationSettings"; +import TaskBoard from "./TaskBoard"; +import InvoiceDetail from "./InvoiceDetail"; +import InvoiceEditor from "./InvoiceEditor"; +import Tutorials from "./Tutorials"; +import Schedule from "./Schedule"; +import StaffAvailability from "./StaffAvailability"; +import WorkerShiftProposals from "./WorkerShiftProposals"; + const PAGES = { - - Dashboard: Dashboard, - - StaffDirectory: StaffDirectory, - - AddStaff: AddStaff, - - EditStaff: EditStaff, - - Events: Events, - - CreateEvent: CreateEvent, - - EditEvent: EditEvent, - - EventDetail: EventDetail, - - Business: Business, - - Invoices: Invoices, - - Payroll: Payroll, - - Certification: Certification, - - Support: Support, - - Reports: Reports, - - Settings: Settings, - - ActivityLog: ActivityLog, - - AddBusiness: AddBusiness, - - EditBusiness: EditBusiness, - - ProcurementDashboard: ProcurementDashboard, - - OperatorDashboard: OperatorDashboard, - - VendorDashboard: VendorDashboard, - - WorkforceDashboard: WorkforceDashboard, - - Messages: Messages, - - ClientDashboard: ClientDashboard, - - Onboarding: Onboarding, - - ClientOrders: ClientOrders, - - ClientInvoices: ClientInvoices, - - VendorOrders: VendorOrders, - - VendorStaff: VendorStaff, - - VendorInvoices: VendorInvoices, - - VendorPerformance: VendorPerformance, - - WorkforceShifts: WorkforceShifts, - - WorkforceEarnings: WorkforceEarnings, - - WorkforceProfile: WorkforceProfile, - - UserManagement: UserManagement, - - Home: Home, - - VendorRateCard: VendorRateCard, - - Permissions: Permissions, - - WorkforceCompliance: WorkforceCompliance, - - Teams: Teams, - - CreateTeam: CreateTeam, - - TeamDetails: TeamDetails, - - VendorManagement: VendorManagement, - - PartnerManagement: PartnerManagement, - - EnterpriseManagement: EnterpriseManagement, - - VendorOnboarding: VendorOnboarding, - - SectorManagement: SectorManagement, - - AddEnterprise: AddEnterprise, - - AddSector: AddSector, - - AddPartner: AddPartner, - - EditVendor: EditVendor, - - SmartVendorOnboarding: SmartVendorOnboarding, - - InviteVendor: InviteVendor, - - VendorCompliance: VendorCompliance, - - EditPartner: EditPartner, - - EditSector: EditSector, - - EditEnterprise: EditEnterprise, - - VendorRates: VendorRates, - - VendorDocumentReview: VendorDocumentReview, - - VendorMarketplace: VendorMarketplace, - - RapidOrder: RapidOrder, - - SmartScheduler: SmartScheduler, - - StaffOnboarding: StaffOnboarding, - - NotificationSettings: NotificationSettings, - - TaskBoard: TaskBoard, - - InvoiceDetail: InvoiceDetail, - - InvoiceEditor: InvoiceEditor, - - //api-docs-raw: api-docs-raw, - - Tutorials: Tutorials, - - Schedule: Schedule, - - StaffAvailability: StaffAvailability, - - WorkerShiftProposals: WorkerShiftProposals, - -} + Dashboard, + StaffDirectory, + AddStaff, + EditStaff, + Events, + CreateEvent, + EditEvent, + EventDetail, + Business, + Invoices, + Payroll, + Certification, + Support, + Reports, + Settings, + ActivityLog, + AddBusiness, + EditBusiness, + ProcurementDashboard, + OperatorDashboard, + VendorDashboard, + WorkforceDashboard, + Messages, + ClientDashboard, + Onboarding, + ClientOrders, + ClientInvoices, + VendorOrders, + VendorStaff, + VendorInvoices, + VendorPerformance, + WorkforceShifts, + WorkforceEarnings, + WorkforceProfile, + UserManagement, + Home, + VendorRateCard, + Permissions, + WorkforceCompliance, + Teams, + CreateTeam, + TeamDetails, + VendorManagement, + PartnerManagement, + EnterpriseManagement, + VendorOnboarding, + SectorManagement, + AddEnterprise, + AddSector, + AddPartner, + EditVendor, + SmartVendorOnboarding, + InviteVendor, + VendorCompliance, + EditPartner, + EditSector, + EditEnterprise, + VendorRates, + VendorDocumentReview, + VendorMarketplace, + RapidOrder, + SmartScheduler, + StaffOnboarding, + NotificationSettings, + TaskBoard, + InvoiceDetail, + InvoiceEditor, + Tutorials, + Schedule, + StaffAvailability, + WorkerShiftProposals, +}; function _getCurrentPage(url) { - if (url.endsWith('/')) { - url = url.slice(0, -1); - } - let urlLastPart = url.split('/').pop(); - if (urlLastPart.includes('?')) { - urlLastPart = urlLastPart.split('?')[0]; - } - - const pageName = Object.keys(PAGES).find(page => page.toLowerCase() === urlLastPart.toLowerCase()); - return pageName || Object.keys(PAGES)[0]; + if (url.endsWith('/')) url = url.slice(0, -1); + let last = url.split('/').pop(); + if (last.includes('?')) last = last.split('?')[0]; + const pageName = Object.keys(PAGES).find(p => p.toLowerCase() === last.toLowerCase()); + return pageName || 'Home'; // Default to Home } -// Create a wrapper component that uses useLocation inside the Router context -function PagesContent() { + +function AppRoutes() { const location = useLocation(); const currentPage = _getCurrentPage(location.pathname); - + return ( - - - - } /> - - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - } /> - - - + + {/* Public Routes */} + } /> + } /> + + {/* Private Routes */} + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + } /> + ); } export default function Pages() { return ( - + ); } \ No newline at end of file