Menu re-work almost done

This commit is contained in:
James Ketr 2025-06-11 10:36:35 -07:00
parent 4689aa66c6
commit 7a166fe920
5 changed files with 35 additions and 31 deletions

View File

@ -12,7 +12,7 @@ import { Snack, SetSnackType } from 'components/Snack';
import { User } from 'types/types'; import { User } from 'types/types';
import { LoadingComponent } from "components/LoadingComponent"; import { LoadingComponent } from "components/LoadingComponent";
import { AuthProvider, useAuth, ProtectedRoute } from 'hooks/AuthContext'; import { AuthProvider, useAuth, ProtectedRoute } from 'hooks/AuthContext';
import { useSelectedCandidate } from 'hooks/GlobalContext'; import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
import { import {
getMainNavigationItems, getMainNavigationItems,
getAllRoutes, getAllRoutes,
@ -76,29 +76,28 @@ const BackstoryPageContainer = (props: BackstoryPageContainerProps) => {
interface BackstoryLayoutProps { interface BackstoryLayoutProps {
page: string; page: string;
chatRef: React.Ref<any>; chatRef: React.Ref<any>;
snackRef: React.Ref<any>;
submitQuery: any;
} }
const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutProps) => { const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutProps) => {
const { page, chatRef, snackRef, submitQuery } = props; const { page, chatRef, } = props;
const { setSnack } = useAppState();
const navigate = useNavigate(); const navigate = useNavigate();
const location = useLocation(); const location = useLocation();
const { guest, user } = useAuth(); const { guest, user } = useAuth();
const { selectedCandidate } = useSelectedCandidate();
const [navigationItems, setNavigationItems] = useState<NavigationItem[]>([]); const [navigationItems, setNavigationItems] = useState<NavigationItem[]>([]);
useEffect(() => { useEffect(() => {
const userType = user?.userType || null; const userType = user?.userType || null;
setNavigationItems(getMainNavigationItems(userType)); setNavigationItems(getMainNavigationItems(userType, user?.isAdmin ? true : false));
}, [user]); }, [user]);
// Generate dynamic routes from navigation config // Generate dynamic routes from navigation config
const generateRoutes = () => { const generateRoutes = () => {
if (!guest) return null; if (!guest && !user) return null;
const userType = user?.userType || null; const userType = user?.userType || null;
const routes = getAllRoutes(userType); const isAdmin = user?.isAdmin ? true : false;
const routes = getAllRoutes(userType, isAdmin);
return routes.map((route, index) => { return routes.map((route, index) => {
if (!route.path || !route.component) return null; if (!route.path || !route.component) return null;
@ -158,7 +157,7 @@ const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutP
}} }}
> >
<BackstoryPageContainer> <BackstoryPageContainer>
{!guest && ( {!guest && !user && (
<Box> <Box>
<LoadingComponent <LoadingComponent
loadingText="Creating session..." loadingText="Creating session..."
@ -168,7 +167,7 @@ const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutP
/> />
</Box> </Box>
)} )}
{guest && ( {(guest || user) && (
<> <>
<Outlet /> <Outlet />
<Routes> <Routes>
@ -179,7 +178,6 @@ const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutP
{location.pathname === "/" && <Footer />} {location.pathname === "/" && <Footer />}
</BackstoryPageContainer> </BackstoryPageContainer>
</Scrollable> </Scrollable>
<Snack ref={snackRef} />
</Box> </Box>
</Box> </Box>
); );

View File

@ -20,7 +20,7 @@ const getBackstoryDynamicRoutes = (props: BackstoryDynamicRoutesProps): ReactNod
const isAdmin = user?.isAdmin ? true : false; const isAdmin = user?.isAdmin ? true : false;
// Get all routes from navigation config // Get all routes from navigation config
const routes = getAllRoutes(userType); const routes = getAllRoutes(userType, isAdmin);
return routes.map((route: NavigationItem, index: number) => { return routes.map((route: NavigationItem, index: number) => {
if (!route.path || !route.component) return null; if (!route.path || !route.component) return null;

View File

@ -26,6 +26,7 @@ import {
List, List,
ListItem, ListItem,
ListItemButton, ListItemButton,
SxProps,
} from '@mui/material'; } from '@mui/material';
import { styled, useTheme } from '@mui/material/styles'; import { styled, useTheme } from '@mui/material/styles';
import { import {
@ -38,7 +39,7 @@ import {
ExpandLess, ExpandLess,
KeyboardArrowDown, KeyboardArrowDown,
} from '@mui/icons-material'; } from '@mui/icons-material';
import FaceRetouchingNaturalIcon from '@mui/icons-material/FaceRetouchingNatural';
import { getUserMenuItemsByGroup } from 'config/navigationConfig'; import { getUserMenuItemsByGroup } from 'config/navigationConfig';
import { NavigationItem } from 'types/navigation'; import { NavigationItem } from 'types/navigation';
import { Beta } from 'components/ui/Beta'; import { Beta } from 'components/ui/Beta';
@ -125,8 +126,6 @@ interface HeaderProps {
const Header: React.FC<HeaderProps> = (props: HeaderProps) => { const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
const { user, logout } = useAuth(); const { user, logout } = useAuth();
const { setSnack } = useAppState(); const { setSnack } = useAppState();
const candidate: Candidate | null = (user && user.userType === "candidate") ? user as Candidate : null;
const employer: Employer | null = (user && user.userType === "employer") ? user as Employer : null;
const { const {
transparent = false, transparent = false,
className, className,
@ -254,7 +253,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
// Helper function to check if any child is current path // Helper function to check if any child is current path
const hasActiveChild = (item: NavigationItem): boolean => { const hasActiveChild = (item: NavigationItem): boolean => {
if (!item.children) return false; if (!item.children) return false;
return item.children.some((child: any) => isCurrentPath(child) || hasActiveChild(child)); return item.children.some(child => isCurrentPath(child) || hasActiveChild(child));
}; };
// Desktop dropdown handlers // Desktop dropdown handlers
@ -328,7 +327,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
transformOrigin={{ vertical: 'top', horizontal: 'left' }} transformOrigin={{ vertical: 'top', horizontal: 'left' }}
TransitionComponent={Fade} TransitionComponent={Fade}
> >
{item.children?.map((child: any) => ( {item.children?.map(child => (
<MenuItem <MenuItem
key={child.id} key={child.id}
onClick={() => child.path && handleNavigate(child.path)} onClick={() => child.path && handleNavigate(child.path)}
@ -416,7 +415,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
{hasChildren && ( {hasChildren && (
<Collapse in={isExpanded} timeout="auto" unmountOnExit> <Collapse in={isExpanded} timeout="auto" unmountOnExit>
<List disablePadding> <List disablePadding>
{item.children?.map((child: any) => renderNavigationItem(child, depth + 1))} {item.children?.map(child => renderNavigationItem(child, depth + 1))}
</List> </List>
</Collapse> </Collapse>
)} )}

View File

@ -14,6 +14,9 @@ import {
History as HistoryIcon, History as HistoryIcon,
QuestionAnswer as QuestionAnswerIcon, QuestionAnswer as QuestionAnswerIcon,
AttachMoney as AttachMoneyIcon, AttachMoney as AttachMoneyIcon,
Quiz as QuizIcon,
Analytics as AnalyticsIcon,
BubbleChart,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { BackstoryLogo } from 'components/ui/BackstoryLogo'; import { BackstoryLogo } from 'components/ui/BackstoryLogo';
@ -28,9 +31,15 @@ import { JobAnalysisPage } from 'pages/JobAnalysisPage';
import { GenerateCandidate } from 'pages/GenerateCandidate'; import { GenerateCandidate } from 'pages/GenerateCandidate';
import { LoginPage } from 'pages/LoginPage'; import { LoginPage } from 'pages/LoginPage';
import { EmailVerificationPage } from 'components/EmailVerificationComponents'; import { EmailVerificationPage } from 'components/EmailVerificationComponents';
import { Typography } from '@mui/material'; import { Box, Typography } from '@mui/material';
import { CandidateDashboard } from 'pages/candidate/Dashboard'; import { CandidateDashboard } from 'pages/candidate/Dashboard';
import { NavigationConfig, NavigationItem } from 'types/navigation'; import { NavigationConfig, NavigationItem } from 'types/navigation';
import { HowItWorks } from 'pages/HowItWorks';
import SchoolIcon from '@mui/icons-material/School';
import { CandidateProfile } from 'pages/candidate/Profile';
import { Settings } from 'pages/candidate/Settings';
import { VectorVisualizer } from 'components/VectorVisualizer';
import { DocumentManager } from 'components/DocumentManager';
// Beta page components for placeholder routes // Beta page components for placeholder routes
const BackstoryPage = () => (<BetaPage><Typography variant="h4">Backstory</Typography></BetaPage>); const BackstoryPage = () => (<BetaPage><Typography variant="h4">Backstory</Typography></BetaPage>);
@ -46,8 +55,8 @@ const SettingsPage = () => (<BetaPage><Typography variant="h4">Settings</Typogra
export const navigationConfig: NavigationConfig = { export const navigationConfig: NavigationConfig = {
items: [ items: [
{ id: 'home', label: <BackstoryLogo />, path: '/', component: <HomePage />, userTypes: ['guest', 'candidate', 'employer'], exact: true, }, { id: 'home', label: <BackstoryLogo />, path: '/', component: <HowItWorks />, userTypes: ['guest', 'candidate', 'employer'], exact: true, },
{ id: 'how-it-works', label: 'How It Works', path: '/how-it-works', icon: <SchoolIcon />, component: <HowItWorks />, userTypes: ['guest', 'candidate', 'employer',], }, // { id: 'how-it-works', label: 'How It Works', path: '/how-it-works', icon: <SchoolIcon />, component: <HowItWorks />, userTypes: ['guest', 'candidate', 'employer',], },
{ id: 'job-analysis', label: 'Job Analysis', path: '/job-analysis', icon: <WorkIcon />, component: <JobAnalysisPage />, userTypes: ['guest', 'candidate', 'employer',], }, { id: 'job-analysis', label: 'Job Analysis', path: '/job-analysis', icon: <WorkIcon />, component: <JobAnalysisPage />, userTypes: ['guest', 'candidate', 'employer',], },
{ id: 'chat', label: 'Candidate Chat', path: '/chat', icon: <ChatIcon />, component: <CandidateChatPage />, userTypes: ['guest', 'candidate', 'employer',], }, { { id: 'chat', label: 'Candidate Chat', path: '/chat', icon: <ChatIcon />, component: <CandidateChatPage />, userTypes: ['guest', 'candidate', 'employer',], }, {
id: 'candidate-menu', label: 'Tools', icon: <PersonIcon />, userTypes: ['candidate'], children: [ id: 'candidate-menu', label: 'Tools', icon: <PersonIcon />, userTypes: ['candidate'], children: [
@ -73,9 +82,7 @@ export const navigationConfig: NavigationConfig = {
{ id: 'employer-company', label: 'Company', path: '/employer/company', icon: <BusinessIcon />, component: <CompanyPage />, userTypes: ['employer'], }, { id: 'employer-company', label: 'Company', path: '/employer/company', icon: <BusinessIcon />, component: <CompanyPage />, userTypes: ['employer'], },
{ id: 'employer-analytics', label: 'Analytics', path: '/employer/analytics', icon: <BarChartIcon />, component: <AnalyticsPage />, userTypes: ['employer'], }, { id: 'employer-analytics', label: 'Analytics', path: '/employer/analytics', icon: <BarChartIcon />, component: <AnalyticsPage />, userTypes: ['employer'], },
{ id: 'employer-settings', label: 'Settings', path: '/employer/settings', icon: <SettingsIcon />, component: <SettingsPage />, userTypes: ['employer'], }, { id: 'employer-settings', label: 'Settings', path: '/employer/settings', icon: <SettingsIcon />, component: <SettingsPage />, userTypes: ['employer'], },
], ],
},
],
}, },
{ {
id: 'global-tools', id: 'global-tools',
@ -218,12 +225,12 @@ export const navigationConfig: NavigationConfig = {
}; };
// Utility functions for working with navigation config // Utility functions for working with navigation config
export const getNavigationItemsForUser = (userType: 'guest' | 'candidate' | 'employer' | null): NavigationItem[] => { export const getNavigationItemsForUser = (userType: 'guest' | 'candidate' | 'employer' | null, isAdmin: boolean = false): NavigationItem[] => {
const currentUserType = userType || 'guest'; const currentUserType = userType || 'guest';
const filterItems = (items: NavigationItem[]): NavigationItem[] => { const filterItems = (items: NavigationItem[]): NavigationItem[] => {
return items return items
.filter(item => !item.userTypes || item.userTypes.includes(currentUserType)) .filter(item => !item.userTypes || item.userTypes.includes(currentUserType) || (item.userTypes.includes('admin') && isAdmin))
.filter(item => item.showInNavigation !== false) // Default to true if not specified .filter(item => item.showInNavigation !== false) // Default to true if not specified
.map(item => ({ .map(item => ({
...item, ...item,
@ -235,14 +242,14 @@ export const getNavigationItemsForUser = (userType: 'guest' | 'candidate' | 'emp
return filterItems(navigationConfig.items); return filterItems(navigationConfig.items);
}; };
export const getAllRoutes = (userType: 'guest' | 'candidate' | 'employer' | null): NavigationItem[] => { export const getAllRoutes = (userType: 'guest' | 'candidate' | 'employer' | null, isAdmin: boolean = false): NavigationItem[] => {
const currentUserType = userType || 'guest'; const currentUserType = userType || 'guest';
const extractRoutes = (items: NavigationItem[]): NavigationItem[] => { const extractRoutes = (items: NavigationItem[]): NavigationItem[] => {
const routes: NavigationItem[] = []; const routes: NavigationItem[] = [];
items.forEach(item => { items.forEach(item => {
if (!item.userTypes || item.userTypes.includes(currentUserType)) { if (!item.userTypes || item.userTypes.includes(currentUserType) || (item.userTypes.includes('admin') && isAdmin)) {
if (item.path && item.component) { if (item.path && item.component) {
routes.push(item); routes.push(item);
} }
@ -258,8 +265,8 @@ export const getAllRoutes = (userType: 'guest' | 'candidate' | 'employer' | null
return extractRoutes(navigationConfig.items); return extractRoutes(navigationConfig.items);
}; };
export const getMainNavigationItems = (userType: 'guest' | 'candidate' | 'employer' | null): NavigationItem[] => { export const getMainNavigationItems = (userType: 'guest' | 'candidate' | 'employer' | null, isAdmin: boolean = false): NavigationItem[] => {
return getNavigationItemsForUser(userType) return getNavigationItemsForUser(userType, isAdmin)
.filter(item => .filter(item =>
item.id !== 'auth' && item.id !== 'auth' &&
item.id !== 'catch-all' && item.id !== 'catch-all' &&

View File

@ -7,7 +7,7 @@ export interface NavigationItem {
icon?: ReactElement; icon?: ReactElement;
children?: NavigationItem[]; children?: NavigationItem[];
component?: ReactElement; component?: ReactElement;
userTypes?: ('candidate' | 'employer' | 'guest')[]; userTypes?: ('candidate' | 'employer' | 'guest' | 'admin')[];
exact?: boolean; exact?: boolean;
divider?: boolean; divider?: boolean;
showInNavigation?: boolean; // Controls if item appears in main navigation showInNavigation?: boolean; // Controls if item appears in main navigation