diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 000000000..ed94f44b1
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "git.ignoreLimitWarning": true
+}
\ No newline at end of file
diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx
index 230bd3a22..5a7efaa9d 100644
--- a/apps/frontend/src/app.tsx
+++ b/apps/frontend/src/app.tsx
@@ -1,10 +1,6 @@
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Root from '@containers/root';
import NotFound from '@containers/404';
-import PantryPastOrders from '@containers/pantryPastOrders';
-import Pantries from '@containers/pantries';
-import Orders from '@containers/orders';
-import PantryDashboard from '@containers/pantryDashboard';
import FormRequests from '@containers/formRequests';
import PantryApplication from '@containers/pantryApplication';
import ApplicationSubmitted from '@containers/applicationSubmitted';
@@ -12,12 +8,12 @@ import { submitPantryApplicationForm } from '@components/forms/pantryApplication
import ApprovePantries from '@containers/approvePantries';
import PantryApplicationDetails from '@containers/pantryApplicationDetails';
import VolunteerManagement from '@containers/volunteerManagement';
-import FoodManufacturerOrderDashboard from '@containers/foodManufacturerOrderDashboard';
import AdminDonation from '@containers/adminDonation';
import Homepage from '@containers/homepage';
import AdminOrderManagement from '@containers/adminOrderManagement';
import { Amplify } from 'aws-amplify';
import CognitoAuthConfig from './aws-exports';
+import { ROUTES } from './routes';
import FoodManufacturerDonationManagement from '@containers/foodManufacturerDonationManagement';
import LoginPage from '@containers/loginPage';
import SignupPage from '@containers/signupPage';
@@ -42,7 +38,7 @@ Amplify.configure(CognitoAuthConfig);
const router = createBrowserRouter([
{
- path: '/',
+ path: ROUTES.HOME,
element: ,
errorElement: ,
children: [
@@ -52,94 +48,37 @@ const router = createBrowserRouter([
element: ,
},
{
- path: '/login',
+ path: ROUTES.LOGIN,
element: ,
},
{
- path: '/signup',
+ path: ROUTES.SIGNUP,
element: ,
},
{
- path: '/forgot-password',
+ path: ROUTES.FORGOT_PASSWORD,
element: ,
},
{
- path: '/pantry-application',
+ path: ROUTES.PANTRY_APPLICATION,
element: ,
action: submitPantryApplicationForm,
},
{
- path: '/food-manufacturer-application',
+ path: ROUTES.FOOD_MANUFACTURER_APPLICATION,
element: ,
action: submitManufacturerApplicationForm,
},
{
- path: '/application-submitted',
+ path: ROUTES.APPLICATION_SUBMITTED,
element: ,
},
{
- path: '/unauthorized',
+ path: ROUTES.UNAUTHORIZED,
element: ,
},
- // Private routes (protected by auth)
{
- path: '/pantry-past-orders',
- element: (
-
-
-
- ),
- },
- {
- path: '/pantries',
- element: (
-
-
-
- ),
- },
- {
- path: '/pantry-dashboard',
- element: (
-
-
-
- ),
- },
- {
- path: '/pantry-past-orders',
- element: (
-
-
-
- ),
- },
- {
- path: '/pantries',
- element: (
-
-
-
- ),
- },
- {
- path: '/food-manufacturer-order-dashboard',
- element: (
-
-
-
- ),
- },
- {
- path: '/orders',
- element: (
-
-
-
- ),
- },
- {
- path: '/request-form',
+ path: ROUTES.REQUEST_FORM,
element: (
@@ -147,7 +86,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/fm-donation-management',
+ path: ROUTES.FM_DONATION_MANAGEMENT,
element: (
@@ -155,7 +94,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/approve-pantries',
+ path: ROUTES.APPROVE_PANTRIES,
element: (
@@ -163,7 +102,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/approve-food-manufacturers',
+ path: ROUTES.APPROVE_FOOD_MANUFACTURERS,
element: (
@@ -171,7 +110,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/pantry-application-details/:applicationId',
+ path: ROUTES.PANTRY_APPLICATION_DETAILS,
element: (
@@ -179,7 +118,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/food-manufacturer-application-details/:applicationId',
+ path: ROUTES.FOOD_MANUFACTURER_APPLICATION_DETAILS,
element: (
@@ -187,7 +126,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/admin-donation',
+ path: ROUTES.ADMIN_DONATION,
element: (
@@ -195,7 +134,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/admin-donation-stats',
+ path: ROUTES.ADMIN_DONATION_STATS,
element: (
@@ -203,31 +142,31 @@ const router = createBrowserRouter([
),
},
{
- path: '/test-admin-dashboard',
+ path: ROUTES.VOLUNTEER_MANAGEMENT,
element: (
-
+
),
},
{
- path: '/admin-request-management',
+ path: ROUTES.TEST_ADMIN_DASHBOARD,
element: (
-
+
),
},
{
- path: '/volunteer-management',
+ path: ROUTES.ADMIN_REQUEST_MANAGEMENT,
element: (
-
+
),
},
{
- path: '/admin-order-management',
+ path: ROUTES.ADMIN_ORDER_MANAGEMENT,
element: (
@@ -235,7 +174,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/pantry-order-management',
+ path: ROUTES.PANTRY_ORDER_MANAGEMENT,
element: (
@@ -243,7 +182,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/profile',
+ path: ROUTES.PROFILE,
element: (
@@ -251,7 +190,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/volunteer-assigned-pantries',
+ path: ROUTES.VOLUNTEER_ASSIGNED_PANTRIES,
element: (
@@ -259,7 +198,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/volunteer-request-management',
+ path: ROUTES.VOLUNTEER_REQUEST_MANAGEMENT,
element: (
@@ -267,7 +206,7 @@ const router = createBrowserRouter([
),
},
{
- path: '/volunteer-order-management',
+ path: ROUTES.VOLUNTEER_ORDER_MANAGEMENT,
element: (
diff --git a/apps/frontend/src/components/AuthHeader.tsx b/apps/frontend/src/components/AuthHeader.tsx
new file mode 100644
index 000000000..bb76e46c1
--- /dev/null
+++ b/apps/frontend/src/components/AuthHeader.tsx
@@ -0,0 +1,23 @@
+import { Box } from '@chakra-ui/react';
+
+const AuthHeader: React.FC = () => (
+
+
+
+
+
+);
+
+export default AuthHeader;
diff --git a/apps/frontend/src/components/Navbar.tsx b/apps/frontend/src/components/Navbar.tsx
new file mode 100644
index 000000000..8875fa34a
--- /dev/null
+++ b/apps/frontend/src/components/Navbar.tsx
@@ -0,0 +1,395 @@
+import React, { useEffect, useState } from 'react';
+import { Link as RouterLink, useLocation, useNavigate } from 'react-router-dom';
+import { Box, Flex, Text, VStack } from '@chakra-ui/react';
+import { useAuthenticator } from '@aws-amplify/ui-react';
+import { signOut } from 'aws-amplify/auth';
+import { ChevronDown, ChevronRight, LogOut } from 'lucide-react';
+import ApiClient from '@api/apiClient';
+import { Role, User } from '../types/types';
+import { ROUTES } from '../routes';
+
+const ROLE_MAP: Record = {
+ [Role.ADMIN]: { label: 'Admin' },
+ [Role.VOLUNTEER]: { label: 'Volunteer' },
+ [Role.PANTRY]: { label: 'Pantry' },
+ [Role.FOODMANUFACTURER]: { label: 'Manufacturer' },
+};
+
+// Nav Structure Types
+type FlatNav = { type: 'flat'; label: string; to: string };
+type GroupNav = {
+ type: 'group';
+ label: string;
+ children: Array<{ label: string; to: string }>;
+};
+type NavSection = FlatNav | GroupNav;
+
+// Role Nav Definitions
+const ROLE_NAV_SECTIONS: Record = {
+ [Role.ADMIN]: [
+ {
+ type: 'group',
+ label: 'Volunteers',
+ children: [
+ { label: 'Volunteer Management', to: ROUTES.VOLUNTEER_MANAGEMENT },
+ ],
+ },
+ {
+ type: 'group',
+ label: 'Pantries',
+ children: [
+ { label: 'Pantry Management', to: ROUTES.PANTRY_MANAGEMENT },
+ { label: 'Application Review', to: ROUTES.APPROVE_PANTRIES },
+ ],
+ },
+ {
+ type: 'group',
+ label: 'Orders',
+ children: [
+ { label: 'Order Management', to: ROUTES.ADMIN_ORDER_MANAGEMENT },
+ { label: 'Food Requests', to: ROUTES.FOOD_REQUESTS },
+ ],
+ },
+ {
+ type: 'group',
+ label: 'Manufacturers',
+ children: [
+ { label: 'Donation Management', to: ROUTES.ADMIN_DONATION },
+ { label: 'Application Review', to: ROUTES.APPROVE_FOOD_MANUFACTURERS },
+ { label: 'Donation Statistics', to: ROUTES.ADMIN_DONATION_STATS },
+ ],
+ },
+ ],
+ [Role.VOLUNTEER]: [
+ {
+ type: 'flat',
+ label: 'Assigned Pantries',
+ to: ROUTES.VOLUNTEER_ASSIGNED_PANTRIES,
+ },
+ {
+ type: 'group',
+ label: 'Orders',
+ children: [
+ {
+ label: 'Order Management',
+ to: ROUTES.VOLUNTEER_ORDER_MANAGEMENT,
+ },
+ {
+ label: 'Food Requests',
+ to: ROUTES.VOLUNTEER_REQUEST_MANAGEMENT,
+ },
+ ],
+ },
+ ],
+ [Role.FOODMANUFACTURER]: [
+ {
+ type: 'group',
+ label: 'Donations',
+ children: [
+ { label: 'Donation Management', to: ROUTES.FM_DONATION_MANAGEMENT },
+ ],
+ },
+ ],
+ [Role.PANTRY]: [
+ {
+ type: 'group',
+ label: 'Orders',
+ children: [
+ { label: 'Order Management', to: ROUTES.PANTRY_ORDER_MANAGEMENT },
+ { label: 'Food Requests', to: ROUTES.REQUEST_FORM },
+ ],
+ },
+ ],
+};
+
+// Subcomponents
+const NavLink: React.FC<{
+ to: string;
+ label: string;
+ isActive: boolean;
+}> = ({ to, label, isActive }) => (
+
+
+
+ {label}
+
+
+
+);
+
+interface NavGroupProps {
+ label: string;
+ children: Array<{ label: string; to: string }>;
+ isOpen: boolean;
+ onToggle: () => void;
+ activePath: string;
+}
+
+const NavGroup: React.FC = ({
+ label,
+ children,
+ isOpen,
+ onToggle,
+ activePath,
+}) => (
+
+
+
+ {label}
+
+ {isOpen ? (
+
+ ) : (
+
+ )}
+
+
+ {isOpen &&
+ children.map((child) => {
+ const isActive = activePath === child.to;
+ return (
+
+
+
+
+
+
+
+
+ {child.label}
+
+
+
+
+ );
+ })}
+
+);
+
+const Navbar: React.FC = () => {
+ const { authStatus } = useAuthenticator((context) => [context.authStatus]);
+ const [currentUser, setCurrentUser] = useState(null);
+ const [openGroups, setOpenGroups] = useState>(new Set());
+ const location = useLocation();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (authStatus === 'authenticated') {
+ ApiClient.getMe()
+ .then(setCurrentUser)
+ .catch(() => setCurrentUser(null));
+ } else {
+ setCurrentUser(null);
+ }
+ }, [authStatus]);
+
+ // On reload or navigation, make sure the currently opened groups stays open
+ useEffect(() => {
+ if (!currentUser) return;
+ const sections = ROLE_NAV_SECTIONS[currentUser.role];
+ setOpenGroups((prev) => {
+ const next = new Set(prev);
+ sections.forEach((s) => {
+ if (
+ s.type === 'group' &&
+ s.children.some((c) => c.to === location.pathname)
+ ) {
+ next.add(s.label);
+ }
+ });
+ return next;
+ });
+ }, [location.pathname, currentUser]);
+
+ if (authStatus !== 'authenticated' || !currentUser) return null;
+
+ const roleLabel = ROLE_MAP[currentUser.role].label;
+ const sections: NavSection[] = ROLE_NAV_SECTIONS[currentUser.role];
+ const email = currentUser.email;
+
+ const toggleGroup = (label: string) => {
+ setOpenGroups((prev) => {
+ const next = new Set(prev);
+ if (next.has(label)) next.delete(label);
+ else next.add(label);
+ return next;
+ });
+ };
+
+ const handleSignOut = async () => {
+ await signOut();
+ navigate(ROUTES.LOGIN, { replace: true });
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {roleLabel ? `${roleLabel} Dashboard` : 'Dashboard'}
+
+
+ {email}
+
+
+
+
+
+
+
+
+ {sections.map((section) =>
+ section.type === 'flat' ? (
+
+ ) : (
+ toggleGroup(section.label)}
+ activePath={location.pathname}
+ />
+ ),
+ )}
+
+
+
+
+
+ Sign Out
+
+
+
+ );
+};
+
+export default Navbar;
diff --git a/apps/frontend/src/components/forms/manufacturerApplicationForm.tsx b/apps/frontend/src/components/forms/manufacturerApplicationForm.tsx
index ab70c93fe..e104581cf 100644
--- a/apps/frontend/src/components/forms/manufacturerApplicationForm.tsx
+++ b/apps/frontend/src/components/forms/manufacturerApplicationForm.tsx
@@ -36,6 +36,7 @@ import {
} from '../../types/manufacturerEnums';
import { FloatingAlert } from '@components/floatingAlert';
import { useAlert } from '../../hooks/alert';
+import { ROUTES } from '../../routes';
const ManufacturerApplicationForm: React.FC = () => {
const [contactPhone, setContactPhone] = useState('');
@@ -107,7 +108,7 @@ const ManufacturerApplicationForm: React.FC = () => {
borderColor="neutral.200"
rounded="sm"
>
-