Compare commits
No commits in common. "74201d0a71c334d56f9f1615e790c00335728a16" and "7c78f39b02afecf47299995a516ca508ac1500c4" have entirely different histories.
74201d0a71
...
7c78f39b02
@ -89,7 +89,7 @@ const JobCreator = (props: JobCreatorProps) => {
|
|||||||
const [company, setCompany] = useState<string>('');
|
const [company, setCompany] = useState<string>('');
|
||||||
const [summary, setSummary] = useState<string>('');
|
const [summary, setSummary] = useState<string>('');
|
||||||
const [job, setJob] = useState<Types.Job | null>(null);
|
const [job, setJob] = useState<Types.Job | null>(null);
|
||||||
const [jobStatus, setJobStatus] = useState<string>('');
|
const [jobStatus, setJobStatus] = useState<string>('');
|
||||||
const [jobStatusType, setJobStatusType] = useState<Types.ApiActivityType | null>(null);
|
const [jobStatusType, setJobStatusType] = useState<Types.ApiActivityType | null>(null);
|
||||||
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
const [isProcessing, setIsProcessing] = useState<boolean>(false);
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
Paper,
|
Paper,
|
||||||
Typography,
|
Typography,
|
||||||
LinearProgress,
|
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Job, Candidate, SkillAssessment } from "types/types";
|
import { Job, Candidate, SkillAssessment } from "types/types";
|
||||||
import { Scrollable } from './Scrollable';
|
import { Scrollable } from './Scrollable';
|
||||||
@ -17,9 +16,6 @@ import { Message } from './Message';
|
|||||||
import InputIcon from '@mui/icons-material/Input';
|
import InputIcon from '@mui/icons-material/Input';
|
||||||
import TuneIcon from '@mui/icons-material/Tune';
|
import TuneIcon from '@mui/icons-material/Tune';
|
||||||
import ArticleIcon from '@mui/icons-material/Article';
|
import ArticleIcon from '@mui/icons-material/Article';
|
||||||
import { StatusBox, StatusIcon } from './ui/StatusIcon';
|
|
||||||
import { CopyBubble } from './CopyBubble';
|
|
||||||
import { useAppState } from 'hooks/GlobalContext';
|
|
||||||
|
|
||||||
interface ResumeGeneratorProps {
|
interface ResumeGeneratorProps {
|
||||||
job: Job;
|
job: Job;
|
||||||
@ -34,15 +30,12 @@ const defaultMessage: Types.ChatMessageStatus = {
|
|||||||
|
|
||||||
const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorProps) => {
|
const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorProps) => {
|
||||||
const { job, candidate, skills, onComplete } = props;
|
const { job, candidate, skills, onComplete } = props;
|
||||||
const { setSnack } = useAppState();
|
|
||||||
const { apiClient, user } = useAuth();
|
const { apiClient, user } = useAuth();
|
||||||
const [resume, setResume] = useState<string>('');
|
const [resume, setResume] = useState<string>('');
|
||||||
const [prompt, setPrompt] = useState<string>('');
|
const [prompt, setPrompt] = useState<string>('');
|
||||||
const [systemPrompt, setSystemPrompt] = useState<string>('');
|
const [systemPrompt, setSystemPrompt] = useState<string>('');
|
||||||
const [generated, setGenerated] = useState<boolean>(false);
|
const [generated, setGenerated] = useState<boolean>(false);
|
||||||
const [tabValue, setTabValue] = useState<string>('resume');
|
const [tabValue, setTabValue] = useState<string>('resume');
|
||||||
const [status, setStatus] = useState<string>('');
|
|
||||||
const [statusType, setStatusType] = useState<Types.ApiActivityType | null>(null);
|
|
||||||
|
|
||||||
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
|
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => {
|
||||||
setTabValue(newValue);
|
setTabValue(newValue);
|
||||||
@ -55,36 +48,18 @@ const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorP
|
|||||||
|
|
||||||
setGenerated(true);
|
setGenerated(true);
|
||||||
|
|
||||||
setStatusType("thinking");
|
|
||||||
setStatus("Starting resume generation...");
|
|
||||||
|
|
||||||
const generateResumeHandlers = {
|
const generateResumeHandlers = {
|
||||||
onMessage: (message: Types.ChatMessageResume) => {
|
|
||||||
setSystemPrompt(message.systemPrompt || '');
|
|
||||||
setPrompt(message.prompt || '');
|
|
||||||
setResume(message.resume || '');
|
|
||||||
setStatus('');
|
|
||||||
},
|
|
||||||
onStreaming: (chunk: Types.ChatMessageStreaming) => {
|
onStreaming: (chunk: Types.ChatMessageStreaming) => {
|
||||||
if (status === '') {
|
|
||||||
setStatus('Generating resume...');
|
|
||||||
setStatusType("generating");
|
|
||||||
}
|
|
||||||
setResume(chunk.content);
|
setResume(chunk.content);
|
||||||
},
|
|
||||||
onStatus: (status: Types.ChatMessageStatus) => {
|
|
||||||
console.log('status:', status.content);
|
|
||||||
setStatusType(status.activity);
|
|
||||||
setStatus(status.content);
|
|
||||||
},
|
|
||||||
onComplete: () => {
|
|
||||||
onComplete && onComplete(resume);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const generateResume = async () => {
|
const generateResume = async () => {
|
||||||
const request: any = await apiClient.generateResume(candidate.id || '', skills, generateResumeHandlers);
|
const request: any = await apiClient.generateResume(candidate.id || '', skills, generateResumeHandlers);
|
||||||
const result = await request.promise;
|
const result = await request.promise;
|
||||||
|
setSystemPrompt(result.systemPrompt)
|
||||||
|
setPrompt(result.prompt)
|
||||||
|
setResume(result.resume)
|
||||||
};
|
};
|
||||||
|
|
||||||
generateResume();
|
generateResume();
|
||||||
@ -97,28 +72,18 @@ const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorP
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
}}>
|
}}>
|
||||||
{user?.isAdmin && <Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 1 }}>
|
{user?.isAdmin && <Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
|
||||||
<Tabs value={tabValue} onChange={handleTabChange} centered>
|
<Tabs value={tabValue} onChange={handleTabChange} centered>
|
||||||
<Tab disabled={systemPrompt === ''} value="system" icon={<TuneIcon />} label="System" />
|
<Tab sx={{ display: systemPrompt ? "flex" : "none" }} value="system" icon={<TuneIcon />} label="System" />
|
||||||
<Tab disabled={prompt === ''} value="prompt" icon={<InputIcon />} label="Prompt" />
|
<Tab sx={{ display: prompt ? "flex" : "none" }} value="prompt" icon={<InputIcon />} label="Prompt" />
|
||||||
<Tab disabled={resume === ''} value="resume" icon={<ArticleIcon />} label="Resume" />
|
<Tab sx={{ display: resume ? "flex" : "none" }} value="resume" icon={<ArticleIcon />} label="Resume" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>}
|
</Box>}
|
||||||
|
|
||||||
{status && <Box sx={{ mt: 0, mb: 1 }}>
|
<Paper elevation={3} sx={{ p: 3, m: 1, mt: 0 }}><Scrollable autoscroll sx={{ display: "flex", flexGrow: 1 }}>
|
||||||
<StatusBox>
|
|
||||||
{statusType && <StatusIcon type={statusType} />}
|
|
||||||
<Typography variant="body2" sx={{ ml: 1 }}>
|
|
||||||
{status || 'Processing...'}
|
|
||||||
</Typography>
|
|
||||||
</StatusBox>
|
|
||||||
{status && <LinearProgress sx={{ mt: 1 }} />}
|
|
||||||
</Box>}
|
|
||||||
|
|
||||||
<Paper elevation={3} sx={{ p: 3, m: 1, mt: 0 }}><Scrollable autoscroll sx={{ display: "flex", flexGrow: 1, position: "relative" }}>
|
|
||||||
{tabValue === 'system' && <pre>{systemPrompt}</pre>}
|
{tabValue === 'system' && <pre>{systemPrompt}</pre>}
|
||||||
{tabValue === 'prompt' && <pre>{prompt}</pre>}
|
{tabValue === 'prompt' && <pre>{prompt}</pre>}
|
||||||
{tabValue === 'resume' && <><CopyBubble onClick={() => { setSnack('Resume copied to clipboard!'); }} sx={{ position: "absolute", top: 0, right: 0 }} content={resume} /><StyledMarkdown content={resume} /></>}
|
{tabValue === 'resume' && <StyledMarkdown content={resume} />}
|
||||||
</Scrollable></Paper>
|
</Scrollable></Paper>
|
||||||
</Box>
|
</Box>
|
||||||
)
|
)
|
||||||
|
@ -50,7 +50,7 @@ const BackstoryPageContainer = (props: BackstoryPageContainerProps) => {
|
|||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
p: { xs: 0, sm: 0.5 },
|
p: { xs: 0, sm: 0.5 },
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
minHeight: "min-content",
|
minHeight: "min-content",
|
||||||
}}>
|
}}>
|
||||||
<Paper
|
<Paper
|
||||||
@ -79,7 +79,7 @@ interface BackstoryLayoutProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutProps) => {
|
const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutProps) => {
|
||||||
const { page, chatRef, } = props;
|
const { page, chatRef } = props;
|
||||||
const { setSnack } = useAppState();
|
const { setSnack } = useAppState();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -91,12 +91,18 @@ const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutP
|
|||||||
setNavigationItems(getMainNavigationItems(userType, user?.isAdmin ? true : false));
|
setNavigationItems(getMainNavigationItems(userType, user?.isAdmin ? true : false));
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log({ guest, user });
|
||||||
|
}, [guest, user]);
|
||||||
|
|
||||||
// Generate dynamic routes from navigation config
|
// Generate dynamic routes from navigation config
|
||||||
const generateRoutes = () => {
|
const generateRoutes = () => {
|
||||||
if (!guest && !user) return null;
|
if (!guest && !user) return null;
|
||||||
|
|
||||||
const userType = user?.userType || null;
|
const userType = user?.userType || null;
|
||||||
const isAdmin = user?.isAdmin ? true : false;
|
const isAdmin = user?.isAdmin ? true : false;
|
||||||
|
|
||||||
|
// Get all routes from navigation config
|
||||||
const routes = getAllRoutes(userType, isAdmin);
|
const routes = getAllRoutes(userType, isAdmin);
|
||||||
|
|
||||||
return routes.map((route, index) => {
|
return routes.map((route, index) => {
|
||||||
@ -123,9 +129,10 @@ const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutP
|
|||||||
height: "100%",
|
height: "100%",
|
||||||
maxHeight: "100%",
|
maxHeight: "100%",
|
||||||
minHeight: "100%",
|
minHeight: "100%",
|
||||||
flexDirection: "column"
|
flexDirection: "column"
|
||||||
}}>
|
}}>
|
||||||
<Header
|
<Header
|
||||||
|
setSnack={setSnack}
|
||||||
currentPath={page}
|
currentPath={page}
|
||||||
navigate={navigate}
|
navigate={navigate}
|
||||||
navigationItems={navigationItems}
|
navigationItems={navigationItems}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// components/layout/Header.tsx
|
// components/layout/Header.tsx
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { JSX, useEffect, useState } from 'react';
|
||||||
import { NavigateFunction, useLocation } from 'react-router-dom';
|
import { NavigateFunction, useLocation } from 'react-router-dom';
|
||||||
import {
|
import {
|
||||||
AppBar,
|
AppBar,
|
||||||
@ -40,7 +40,7 @@ import {
|
|||||||
KeyboardArrowDown,
|
KeyboardArrowDown,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
import FaceRetouchingNaturalIcon from '@mui/icons-material/FaceRetouchingNatural';
|
import FaceRetouchingNaturalIcon from '@mui/icons-material/FaceRetouchingNatural';
|
||||||
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';
|
||||||
import { Candidate, Employer } from 'types/types';
|
import { Candidate, Employer } from 'types/types';
|
||||||
@ -121,11 +121,11 @@ interface HeaderProps {
|
|||||||
navigationItems: NavigationItem[];
|
navigationItems: NavigationItem[];
|
||||||
currentPath: string;
|
currentPath: string;
|
||||||
sessionId?: string | null;
|
sessionId?: string | null;
|
||||||
|
setSnack: SetSnackType;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
const {
|
||||||
transparent = false,
|
transparent = false,
|
||||||
className,
|
className,
|
||||||
@ -133,6 +133,8 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
navigationItems,
|
navigationItems,
|
||||||
sessionId,
|
sessionId,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const { setSnack } = useAppState();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
@ -149,108 +151,52 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
const [userMenuAnchor, setUserMenuAnchor] = useState<null | HTMLElement>(null);
|
const [userMenuAnchor, setUserMenuAnchor] = useState<null | HTMLElement>(null);
|
||||||
const userMenuOpen = Boolean(userMenuAnchor);
|
const userMenuOpen = Boolean(userMenuAnchor);
|
||||||
|
|
||||||
// Get user menu items from navigation config
|
// User menu items
|
||||||
const userMenuGroups = getUserMenuItemsByGroup(user?.userType || null);
|
const userMenuItems = [
|
||||||
|
{
|
||||||
// Create user menu items array with proper actions
|
id: 'profile',
|
||||||
const createUserMenuItems = () => {
|
label: 'Profile',
|
||||||
const items: Array<{
|
icon: <Person fontSize="small" />,
|
||||||
id: string;
|
action: () => navigate(`/${user?.userType}/profile`)
|
||||||
label: string;
|
},
|
||||||
icon: React.ReactElement | null;
|
{
|
||||||
action: () => void;
|
id: 'dashboard',
|
||||||
group?: string;
|
label: 'Dashboard',
|
||||||
}> = [];
|
icon: <Dashboard fontSize="small" />,
|
||||||
|
action: () => navigate(`/${user?.userType}/dashboard`)
|
||||||
// Add profile group items
|
},
|
||||||
userMenuGroups.profile.forEach(item => {
|
{
|
||||||
if (!item.divider) {
|
id: 'settings',
|
||||||
items.push({
|
label: 'Settings',
|
||||||
id: item.id,
|
icon: <Settings fontSize="small" />,
|
||||||
label: item.label as string,
|
action: () => navigate(`/${user?.userType}/settings`)
|
||||||
icon: item.icon || null,
|
},
|
||||||
action: () => item.path && navigate(item.path),
|
{
|
||||||
group: 'profile'
|
id: 'divider',
|
||||||
});
|
label: '',
|
||||||
|
icon: null,
|
||||||
|
action: () => { }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'logout',
|
||||||
|
label: 'Logout',
|
||||||
|
icon: <Logout fontSize="small" />,
|
||||||
|
action: () => {
|
||||||
|
logout();
|
||||||
|
navigate('/');
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
// Add divider if we have items before system group
|
|
||||||
if (items.length > 0 && userMenuGroups.system.length > 0) {
|
|
||||||
items.push({
|
|
||||||
id: 'divider',
|
|
||||||
label: '',
|
|
||||||
icon: null,
|
|
||||||
action: () => { },
|
|
||||||
group: 'divider'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
];
|
||||||
|
|
||||||
// Add account group items
|
if (user?.isAdmin) {
|
||||||
userMenuGroups.account.forEach(item => {
|
const divider = userMenuItems.findIndex(p => p.id === 'divider');
|
||||||
if (!item.divider) {
|
if (divider !== -1) {
|
||||||
items.push({
|
userMenuItems.splice(divider, 0, ...[
|
||||||
id: item.id,
|
{ id: 'divider', label: '', icon: null, action: () => { } },
|
||||||
label: item.label as string,
|
{ id: 'generate', label: 'Generate Candidate', icon: <FaceRetouchingNaturalIcon fontSize="small" />, action: () => { navigate('/admin/generate-candidate'); } }
|
||||||
icon: item.icon || null,
|
]);
|
||||||
action: () => item.path && navigate(item.path),
|
|
||||||
group: 'account'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add divider if we have items before system group
|
|
||||||
if (items.length > 0 && userMenuGroups.system.length > 0) {
|
|
||||||
items.push({
|
|
||||||
id: 'divider',
|
|
||||||
label: '',
|
|
||||||
icon: null,
|
|
||||||
action: () => { },
|
|
||||||
group: 'divider'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Add system group items with special handling for logout
|
|
||||||
userMenuGroups.system.forEach(item => {
|
|
||||||
if (item.id === 'logout') {
|
|
||||||
items.push({
|
|
||||||
id: 'logout',
|
|
||||||
label: 'Logout',
|
|
||||||
icon: <Logout fontSize="small" />,
|
|
||||||
action: () => {
|
|
||||||
logout();
|
|
||||||
navigate('/');
|
|
||||||
},
|
|
||||||
group: 'system'
|
|
||||||
});
|
|
||||||
} else if (!item.divider) {
|
|
||||||
items.push({
|
|
||||||
id: item.id,
|
|
||||||
label: item.label as string,
|
|
||||||
icon: item.icon || null,
|
|
||||||
action: () => item.path && navigate(item.path),
|
|
||||||
group: 'system'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add other group items
|
|
||||||
userMenuGroups.other.forEach(item => {
|
|
||||||
if (!item.divider) {
|
|
||||||
items.push({
|
|
||||||
id: item.id,
|
|
||||||
label: item.label as string,
|
|
||||||
icon: item.icon || null,
|
|
||||||
action: () => item.path && navigate(item.path),
|
|
||||||
group: 'other'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return items;
|
|
||||||
};
|
|
||||||
|
|
||||||
const userMenuItems = createUserMenuItems();
|
|
||||||
|
|
||||||
// Helper function to check if current path matches navigation item
|
// Helper function to check if current path matches navigation item
|
||||||
const isCurrentPath = (item: NavigationItem): boolean => {
|
const isCurrentPath = (item: NavigationItem): boolean => {
|
||||||
@ -293,8 +239,8 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
setUserMenuAnchor(null);
|
setUserMenuAnchor(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUserMenuAction = (item: { id: string; label: string; icon: React.ReactElement | null; action: () => void; group?: string }) => {
|
const handleUserMenuAction = (item: typeof userMenuItems[0]) => {
|
||||||
if (item.group !== 'divider') {
|
if (item.id !== 'divider') {
|
||||||
item.action();
|
item.action();
|
||||||
handleUserMenuClose();
|
handleUserMenuClose();
|
||||||
}
|
}
|
||||||
@ -311,14 +257,23 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
// Render desktop navigation with dropdowns
|
// Render desktop navigation with dropdowns
|
||||||
const renderDesktopNavigation = () => {
|
const renderDesktopNavigation = () => {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', width: "100%", alignItems: 'center', justifyContent: "space-between", "& > :last-of-type": { marginRight: "auto"} }}>
|
||||||
{navigationItems.map((item) => {
|
{navigationItems.map((item, index) => {
|
||||||
const hasChildren = item.children && item.children.length > 0;
|
const hasChildren = item.children && item.children.length > 0;
|
||||||
const isActive = isCurrentPath(item) || hasActiveChild(item);
|
const isActive = isCurrentPath(item) || hasActiveChild(item);
|
||||||
|
// Default is centered for all menu items
|
||||||
|
let sx : SxProps = { justifycontent: "center"};
|
||||||
|
// First item ("Backstory") is left aligned
|
||||||
|
if (index === 0) {
|
||||||
|
sx = { marginRight: "auto" };
|
||||||
|
}
|
||||||
|
// If there is an Admin menu, it is on the far right
|
||||||
|
if (item.userTypes?.includes('admin')) {
|
||||||
|
sx = { marginLeft: "auto"};
|
||||||
|
}
|
||||||
if (hasChildren) {
|
if (hasChildren) {
|
||||||
return (
|
return (
|
||||||
<Box key={item.id}>
|
<Box key={item.id} sx={sx}>
|
||||||
<DropdownButton
|
<DropdownButton
|
||||||
onClick={(e) => handleDropdownOpen(e, item.id)}
|
onClick={(e) => handleDropdownOpen(e, item.id)}
|
||||||
endIcon={<KeyboardArrowDown />}
|
endIcon={<KeyboardArrowDown />}
|
||||||
@ -338,15 +293,16 @@ 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 => (
|
{item.children?.map((child) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={child.id}
|
key={child.id}
|
||||||
onClick={() => child.path && handleNavigate(child.path)}
|
onClick={() => child.path && handleNavigate(child.path)}
|
||||||
selected={isCurrentPath(child)}
|
selected={isCurrentPath(child)}
|
||||||
disabled={!child.path}
|
disabled={!child.path}
|
||||||
|
sx={{ alignContent: 'center', margin: "0 !important" }}
|
||||||
>
|
>
|
||||||
{child.icon && <ListItemIcon>{child.icon}</ListItemIcon>}
|
{child.icon && <ListItemIcon>{child.icon}</ListItemIcon>}
|
||||||
<ListItemText>{child.label}</ListItemText>
|
<ListItemText sx={{ p: 0, margin: "0 !important", "& > *": { m: 0 } }}>{child.label}</ListItemText>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
</Menu>
|
</Menu>
|
||||||
@ -360,6 +316,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
sx={{
|
sx={{
|
||||||
backgroundColor: isActive ? 'action.selected' : 'transparent',
|
backgroundColor: isActive ? 'action.selected' : 'transparent',
|
||||||
color: isActive ? 'secondary.main' : 'primary.contrastText',
|
color: isActive ? 'secondary.main' : 'primary.contrastText',
|
||||||
|
...sx
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{item.icon && <Box sx={{ mr: 1, display: 'flex' }}>{item.icon}</Box>}
|
{item.icon && <Box sx={{ mr: 1, display: 'flex' }}>{item.icon}</Box>}
|
||||||
@ -410,11 +367,11 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
<ListItemText
|
<ListItemText
|
||||||
primary={item.label}
|
primary={item.label}
|
||||||
sx={{
|
sx={{
|
||||||
'& .MuiTypography-root': {
|
'& .MuiTypography-root': {
|
||||||
fontSize: depth > 0 ? '0.875rem' : '1rem',
|
fontSize: depth > 0 ? '0.875rem' : '1rem',
|
||||||
fontWeight: depth === 0 ? 500 : 400,
|
fontWeight: depth === 0 ? 500 : 400,
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{hasChildren && (
|
{hasChildren && (
|
||||||
<IconButton size="small">
|
<IconButton size="small">
|
||||||
@ -426,7 +383,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 => renderNavigationItem(child, depth + 1))}
|
{item.children?.map((child) => renderNavigationItem(child, depth + 1))}
|
||||||
</List>
|
</List>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
)}
|
)}
|
||||||
@ -455,13 +412,13 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
<UserMenuContainer>
|
<UserMenuContainer>
|
||||||
<List dense>
|
<List dense>
|
||||||
{userMenuItems.map((item, index) => (
|
{userMenuItems.map((item, index) => (
|
||||||
item.group === 'divider' ? (
|
item.id === 'divider' ? (
|
||||||
<Divider key={`divider-${index}`} />
|
<Divider key={`divider-${index}`} />
|
||||||
) : (
|
) : (
|
||||||
<ListItem key={item.id} disablePadding>
|
<ListItem key={item.id} disablePadding>
|
||||||
<ListItemButton onClick={() => handleUserMenuAction(item)}>
|
<ListItemButton onClick={() => handleUserMenuAction(item)} sx={{ alignContent: "center" }}>
|
||||||
{item.icon && <ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon>}
|
{item.icon && <ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon>}
|
||||||
<ListItemText primary={item.label} />
|
<ListItemText primary={item.label} sx={{ padding: 0 }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)
|
)
|
||||||
@ -578,7 +535,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
content={`${window.location.origin}${window.location.pathname}?id=${sessionId}`}
|
content={`${window.location.origin}${window.location.pathname}?id=${sessionId}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`${window.location.pathname}?id=${sessionId}`);
|
navigate(`${window.location.pathname}?id=${sessionId}`);
|
||||||
setSnack("Link copied!");
|
setSnack("Link copied!");
|
||||||
}}
|
}}
|
||||||
size="large"
|
size="large"
|
||||||
/>
|
/>
|
||||||
@ -601,7 +558,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
|||||||
</Container>
|
</Container>
|
||||||
<Beta
|
<Beta
|
||||||
sx={{ left: "-90px", "& .mobile": { right: "-72px" } }}
|
sx={{ left: "-90px", "& .mobile": { right: "-72px" } }}
|
||||||
onClick={() => { navigate('/docs/beta'); }}
|
onClick={() => { navigate('/docs/beta'); }}
|
||||||
/>
|
/>
|
||||||
</StyledAppBar>
|
</StyledAppBar>
|
||||||
);
|
);
|
||||||
|
@ -16,17 +16,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.aibanner-label {
|
.aibanner-label {
|
||||||
right: -70px;
|
|
||||||
top: 40px;
|
|
||||||
height: 32px;
|
|
||||||
font-size: 20px;
|
|
||||||
width: 300px;
|
width: 300px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
right: -70px;
|
||||||
|
top: 40px;
|
||||||
|
height: 32px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transform: rotate(45deg);
|
transform: rotate(45deg);
|
||||||
transform-origin: center center;
|
transform-origin: center center;
|
||||||
|
font-size: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #484848;
|
color: #484848;
|
||||||
@ -36,12 +36,3 @@
|
|||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aibanner-label-minimal,
|
|
||||||
.aibanner-label-small {
|
|
||||||
right: -100px;
|
|
||||||
top: 30px;
|
|
||||||
height: 20px;
|
|
||||||
font-size: 15px;
|
|
||||||
width: 300px;
|
|
||||||
}
|
|
@ -3,25 +3,18 @@ import Box from '@mui/material/Box';
|
|||||||
import { SxProps } from '@mui/material/styles';
|
import { SxProps } from '@mui/material/styles';
|
||||||
|
|
||||||
import './AIBanner.css';
|
import './AIBanner.css';
|
||||||
import { useMediaQuery, useTheme } from '@mui/material';
|
|
||||||
|
|
||||||
type AIBannerProps = {
|
type AIBannerProps = {
|
||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
variant?: "minimal" | "small" | "normal" | undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const AIBanner: React.FC<AIBannerProps> = (props : AIBannerProps) => {
|
const AIBanner: React.FC<AIBannerProps> = (props : AIBannerProps) => {
|
||||||
const theme = useTheme();
|
const { sx = {} } = props;
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
|
||||||
const {
|
|
||||||
sx = {},
|
|
||||||
variant = isMobile ? "small" : "normal",
|
|
||||||
} = props;
|
|
||||||
const aibannerRef = useRef<HTMLElement | null>(null);
|
const aibannerRef = useRef<HTMLElement | null>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={sx} className='aibanner-clipper'>
|
<Box sx={sx} className='aibanner-clipper'>
|
||||||
<Box ref={aibannerRef} className={` aibanner-label-${variant} aibanner-label`}>
|
<Box ref={aibannerRef} className='aibanner-label'>
|
||||||
<Box>AI Generated</Box>
|
<Box>AI Generated</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -20,7 +20,7 @@ interface CandidateInfoProps {
|
|||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
action?: string;
|
action?: string;
|
||||||
elevation?: number;
|
elevation?: number;
|
||||||
variant?: "minimal" | "small" | "normal" | undefined;
|
variant?: "small" | "normal" | null
|
||||||
};
|
};
|
||||||
|
|
||||||
const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps) => {
|
const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps) => {
|
||||||
@ -33,7 +33,7 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
|||||||
variant = "normal"
|
variant = "normal"
|
||||||
} = props;
|
} = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === "minimal";
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const ai: CandidateAI | null = ('isAI' in candidate) ? candidate as CandidateAI : null;
|
const ai: CandidateAI | null = ('isAI' in candidate) ? candidate as CandidateAI : null;
|
||||||
const isAdmin = user?.isAdmin;
|
const isAdmin = user?.isAdmin;
|
||||||
|
|
||||||
@ -77,7 +77,7 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
|||||||
}}
|
}}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{ai && <AIBanner variant={variant} />}
|
{ai && <AIBanner />}
|
||||||
|
|
||||||
<Box sx={{ display: "flex", flexDirection: "row" }}>
|
<Box sx={{ display: "flex", flexDirection: "row" }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
@ -132,7 +132,7 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box>
|
<Box>
|
||||||
{(!isMobile && variant === "normal") && (
|
{(!isMobile || variant !== "small") && (
|
||||||
<Box sx={{ minHeight: "5rem" }}>
|
<Box sx={{ minHeight: "5rem" }}>
|
||||||
<Typography
|
<Typography
|
||||||
ref={descriptionRef}
|
ref={descriptionRef}
|
||||||
@ -178,7 +178,7 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
|
|||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(variant !== "small" && variant !== "minimal") && <>
|
{variant !== "small" && <>
|
||||||
<Divider sx={{ my: 2 }} />
|
<Divider sx={{ my: 2 }} />
|
||||||
|
|
||||||
{candidate.location &&
|
{candidate.location &&
|
||||||
|
@ -61,20 +61,7 @@ const CandidatePicker = (props: CandidatePickerProps) => {
|
|||||||
<Paper key={`${u.username}`}
|
<Paper key={`${u.username}`}
|
||||||
onClick={() => { onSelect ? onSelect(u) : setSelectedCandidate(u); }}
|
onClick={() => { onSelect ? onSelect(u) : setSelectedCandidate(u); }}
|
||||||
sx={{ cursor: "pointer" }}>
|
sx={{ cursor: "pointer" }}>
|
||||||
<CandidateInfo variant="small"
|
<CandidateInfo variant="small" sx={{ maxWidth: "320px", "cursor": "pointer", backgroundColor: (selectedCandidate?.id === u.id) ? "#f0f0f0" : "inherit", border: "2px solid transparent", "&:hover": { border: "2px solid orange" } }} candidate={u} />
|
||||||
sx={{
|
|
||||||
maxWidth: "100%",
|
|
||||||
minWidth: "320px",
|
|
||||||
width: "320px",
|
|
||||||
"cursor": "pointer",
|
|
||||||
backgroundColor: (selectedCandidate?.id === u.id) ? "#f0f0f0" : "inherit",
|
|
||||||
border: "2px solid transparent",
|
|
||||||
"&:hover": {
|
|
||||||
border: "2px solid orange"
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
candidate={u}
|
|
||||||
/>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -27,7 +27,7 @@ interface JobInfoProps {
|
|||||||
sx?: SxProps;
|
sx?: SxProps;
|
||||||
action?: string;
|
action?: string;
|
||||||
elevation?: number;
|
elevation?: number;
|
||||||
variant?: "minimal" | "small" | "normal" | null
|
variant?: "small" | "normal" | null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -42,7 +42,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
variant = "normal"
|
variant = "normal"
|
||||||
} = props;
|
} = props;
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === "minimal";
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
||||||
const isAdmin = user?.isAdmin;
|
const isAdmin = user?.isAdmin;
|
||||||
const [adminStatus, setAdminStatus] = useState<string | null>(null);
|
const [adminStatus, setAdminStatus] = useState<string | null>(null);
|
||||||
const [adminStatusType, setAdminStatusType] = useState<Types.ApiActivityType | null>(null);
|
const [adminStatusType, setAdminStatusType] = useState<Types.ApiActivityType | null>(null);
|
||||||
@ -212,22 +212,21 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
borderStyle: 'solid',
|
borderStyle: 'solid',
|
||||||
transition: 'all 0.3s ease',
|
transition: 'all 0.3s ease',
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
...sx,
|
...sx
|
||||||
minWidth: 0,
|
|
||||||
}}
|
}}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
<Box sx={{ display: "flex", flexGrow: 1, p: 1, pb: 0, height: '100%', flexDirection: 'column', alignItems: 'stretch', position: "relative" }}>
|
<Box sx={{ display: "flex", flexGrow: 1, p: 1, pb: 0, height: '100%', flexDirection: 'column', alignItems: 'stretch', position: "relative" }}>
|
||||||
<Box sx={{
|
<Box sx={{
|
||||||
display: "flex", flexDirection: (isMobile || variant === "small") ? "column" : "row",
|
display: "flex", flexDirection: (isMobile || variant === "small") ? "column" : "row",
|
||||||
"& > div > div > :first-of-type": { fontWeight: "bold", whiteSpace: "nowrap" },
|
"& > div > div > :first-of-type": { fontWeight: "bold" },
|
||||||
"& > div > div > :last-of-type": { mb: 0.75, mr: 1 }
|
"& > div > div > :last-of-type": { mb: 0.75, mr: 1 }
|
||||||
}}>
|
}}>
|
||||||
<Box sx={{ display: "flex", flexDirection: isMobile ? "row" : "column", flexGrow: 1, gap: 1 }}>
|
<Box sx={{ display: "flex", flexDirection: isMobile ? "row" : "column", flexGrow: 1, gap: 1 }}>
|
||||||
{activeJob.company &&
|
{activeJob.company &&
|
||||||
<Box sx={{ fontSize: "0.8rem" }}>
|
<Box sx={{ fontSize: "0.8rem" }}>
|
||||||
<Box>Company</Box>
|
<Box>Company</Box>
|
||||||
<Box sx={{ whiteSpace: "nowrap" }}>{activeJob.company}</Box>
|
<Box>{activeJob.company}</Box>
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
{activeJob.title &&
|
{activeJob.title &&
|
||||||
@ -237,10 +236,10 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", width: (variant !== "small" && variant !== "minimal") ? "75%" : "100%" }}>
|
<Box sx={{ display: "flex", flexDirection: "column", width: (variant !== "small") ? "75%" : "100%" }}>
|
||||||
{!isMobile && activeJob.summary && <Box sx={{ fontSize: "0.8rem" }}>
|
{!isMobile && activeJob.summary && <Box sx={{ fontSize: "0.8rem" }}>
|
||||||
<Box>Summary</Box>
|
<Box>Summary</Box>
|
||||||
<Box sx={{ minHeight: variant === "small" ? "5rem" : "inherit" }}>
|
<Box sx={{ minHeight: "5rem" }}>
|
||||||
<Typography
|
<Typography
|
||||||
ref={descriptionRef}
|
ref={descriptionRef}
|
||||||
variant="body1"
|
variant="body1"
|
||||||
@ -287,14 +286,14 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{(variant !== "small" && variant !== "minimal") && <>
|
{variant !== "small" && <>
|
||||||
{activeJob.details &&
|
{activeJob.details &&
|
||||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||||
<strong>Location:</strong> {activeJob.details.location.city}, {activeJob.details.location.state || activeJob.details.location.country}
|
<strong>Location:</strong> {activeJob.details.location.city}, {activeJob.details.location.state || activeJob.details.location.country}
|
||||||
</Typography>
|
</Typography>
|
||||||
}
|
}
|
||||||
{activeJob.owner && <Typography variant="body2">
|
{activeJob.owner && <Typography variant="body2">
|
||||||
<strong>Submitted by:</strong> {activeJob.owner.fullName}
|
<strong>Created by:</strong> {activeJob.owner.fullName}
|
||||||
</Typography>}
|
</Typography>}
|
||||||
{activeJob.createdAt &&
|
{activeJob.createdAt &&
|
||||||
<Typography variant="caption">Created: {activeJob.createdAt.toISOString()}</Typography>
|
<Typography variant="caption">Created: {activeJob.createdAt.toISOString()}</Typography>
|
||||||
@ -305,7 +304,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
|
|||||||
<Typography variant="caption">Job ID: {job.id}</Typography>
|
<Typography variant="caption">Job ID: {job.id}</Typography>
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
{(variant !== 'small' && variant !== 'minimal') && <><Divider />{renderJobRequirements()}</>}
|
{variant !== 'small' && <><Divider />{renderJobRequirements()}</>}
|
||||||
|
|
||||||
</Box >
|
</Box >
|
||||||
{isAdmin &&
|
{isAdmin &&
|
||||||
|
@ -52,20 +52,7 @@ const JobPicker = (props: JobPickerProps) => {
|
|||||||
<Paper key={`${j.id}`}
|
<Paper key={`${j.id}`}
|
||||||
onClick={() => { onSelect ? onSelect(j) : setSelectedJob(j); }}
|
onClick={() => { onSelect ? onSelect(j) : setSelectedJob(j); }}
|
||||||
sx={{ cursor: "pointer" }}>
|
sx={{ cursor: "pointer" }}>
|
||||||
<JobInfo variant="small"
|
<JobInfo variant="small" sx={{ maxWidth: "320px", "cursor": "pointer", backgroundColor: (selectedJob?.id === j.id) ? "#f0f0f0" : "inherit", border: "2px solid transparent", "&:hover": { border: "2px solid orange" } }} job={j} />
|
||||||
sx={{
|
|
||||||
maxWidth: "100%",
|
|
||||||
minWidth: "320px",
|
|
||||||
width: "320px",
|
|
||||||
"cursor": "pointer",
|
|
||||||
backgroundColor: (selectedJob?.id === j.id) ? "#f0f0f0" : "inherit",
|
|
||||||
border: "2px solid transparent",
|
|
||||||
"&:hover": {
|
|
||||||
border: "2px solid orange"
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
job={j}
|
|
||||||
/>
|
|
||||||
</Paper>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import React from "react";
|
|
||||||
|
// config/navigationConfig.tsx
|
||||||
|
import React from 'react';
|
||||||
import {
|
import {
|
||||||
Chat as ChatIcon,
|
Chat as ChatIcon,
|
||||||
Dashboard as DashboardIcon,
|
Dashboard as DashboardIcon,
|
||||||
@ -6,330 +8,158 @@ import {
|
|||||||
BarChart as BarChartIcon,
|
BarChart as BarChartIcon,
|
||||||
Settings as SettingsIcon,
|
Settings as SettingsIcon,
|
||||||
Work as WorkIcon,
|
Work as WorkIcon,
|
||||||
Info as InfoIcon,
|
|
||||||
Person as PersonIcon,
|
Person as PersonIcon,
|
||||||
Business as BusinessIcon,
|
Business as BusinessIcon,
|
||||||
|
Quiz as QuizIcon,
|
||||||
|
Analytics as AnalyticsIcon,
|
||||||
Search as SearchIcon,
|
Search as SearchIcon,
|
||||||
Bookmark as BookmarkIcon,
|
Bookmark as BookmarkIcon,
|
||||||
History as HistoryIcon,
|
|
||||||
QuestionAnswer as QuestionAnswerIcon,
|
|
||||||
AttachMoney as AttachMoneyIcon,
|
|
||||||
Quiz as QuizIcon,
|
|
||||||
Analytics as AnalyticsIcon,
|
|
||||||
BubbleChart,
|
BubbleChart,
|
||||||
} from "@mui/icons-material";
|
} from '@mui/icons-material';
|
||||||
|
import SchoolIcon from '@mui/icons-material/School';
|
||||||
|
|
||||||
import { BackstoryLogo } from "components/ui/BackstoryLogo";
|
import FaceRetouchingNaturalIcon from '@mui/icons-material/FaceRetouchingNatural';
|
||||||
import { HomePage } from "pages/HomePage";
|
import LibraryBooksIcon from '@mui/icons-material/LibraryBooks';
|
||||||
import { CandidateChatPage } from "pages/CandidateChatPage";
|
import { BackstoryLogo } from 'components/ui/BackstoryLogo';
|
||||||
import { DocsPage } from "pages/DocsPage";
|
import { HomePage } from 'pages/HomePage';
|
||||||
import { CreateProfilePage } from "pages/candidate/ProfileWizard";
|
import { CandidateChatPage } from 'pages/CandidateChatPage';
|
||||||
import { VectorVisualizerPage } from "pages/VectorVisualizerPage";
|
import { DocsPage } from 'pages/DocsPage';
|
||||||
import { BetaPage } from "pages/BetaPage";
|
import { CreateProfilePage } from 'pages/candidate/ProfileWizard';
|
||||||
import { CandidateListingPage } from "pages/FindCandidatePage";
|
import { VectorVisualizerPage } from 'pages/VectorVisualizerPage';
|
||||||
import { JobAnalysisPage } from "pages/JobAnalysisPage";
|
import { BetaPage } from 'pages/BetaPage';
|
||||||
import { GenerateCandidate } from "pages/GenerateCandidate";
|
import { JobAnalysisPage } from 'pages/JobAnalysisPage';
|
||||||
import { LoginPage } from "pages/LoginPage";
|
import { GenerateCandidate } from 'pages/GenerateCandidate';
|
||||||
import { EmailVerificationPage } from "components/EmailVerificationComponents";
|
import { Settings } from 'pages/candidate/Settings';
|
||||||
import { Box, Typography } from "@mui/material";
|
import { LoginPage } from 'pages/LoginPage';
|
||||||
import { CandidateDashboard } from "pages/candidate/Dashboard";
|
import { CandidateDashboard } from 'pages/candidate/Dashboard';
|
||||||
import { NavigationConfig, NavigationItem } from "types/navigation";
|
import { EmailVerificationPage } from 'components/EmailVerificationComponents';
|
||||||
import { HowItWorks } from "pages/HowItWorks";
|
import { Box, Typography } from '@mui/material';
|
||||||
import SchoolIcon from "@mui/icons-material/School";
|
import { NavigationConfig, NavigationItem } from 'types/navigation';
|
||||||
import { CandidateProfile } from "pages/candidate/Profile";
|
import { CandidateProfile } from 'pages/candidate/Profile';
|
||||||
import { Settings } from "pages/candidate/Settings";
|
import { DocumentManager } from 'components/DocumentManager';
|
||||||
import { VectorVisualizer } from "components/VectorVisualizer";
|
import { VectorVisualizer } from 'components/VectorVisualizer';
|
||||||
import { DocumentManager } from "components/DocumentManager";
|
import { HowItWorks } from 'pages/HowItWorks';
|
||||||
import { useAuth } from "hooks/AuthContext";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
// Beta page components for placeholder routes
|
// Beta page components for placeholder routes
|
||||||
const BackstoryPage = () => (
|
const SearchPage = () => (<BetaPage><Typography variant="h4">Search</Typography></BetaPage>);
|
||||||
<BetaPage>
|
const SavedPage = () => (<BetaPage><Typography variant="h4">Saved</Typography></BetaPage>);
|
||||||
<Typography variant="h4">Backstory</Typography>
|
const JobsPage = () => (<BetaPage><Typography variant="h4">Jobs</Typography></BetaPage>);
|
||||||
</BetaPage>
|
const CompanyPage = () => (<BetaPage><Typography variant="h4">Company</Typography></BetaPage>);
|
||||||
);
|
const LogoutPage = () => (<BetaPage><Typography variant="h4">Logout page...</Typography></BetaPage>);
|
||||||
const ResumesPage = () => (
|
const AnalyticsPage = () => (<BetaPage><Typography variant="h4">Analytics</Typography></BetaPage>);
|
||||||
<BetaPage>
|
const SettingsPage = () => (<BetaPage><Typography variant="h4">Settings</Typography></BetaPage>);
|
||||||
<Typography variant="h4">Resumes</Typography>
|
|
||||||
</BetaPage>
|
|
||||||
);
|
|
||||||
const QASetupPage = () => (
|
|
||||||
<BetaPage>
|
|
||||||
<Typography variant="h4">Q&A Setup</Typography>
|
|
||||||
</BetaPage>
|
|
||||||
);
|
|
||||||
const SearchPage = () => (
|
|
||||||
<BetaPage>
|
|
||||||
<Typography variant="h4">Search</Typography>
|
|
||||||
</BetaPage>
|
|
||||||
);
|
|
||||||
const SavedPage = () => (
|
|
||||||
<BetaPage>
|
|
||||||
<Typography variant="h4">Saved</Typography>
|
|
||||||
</BetaPage>
|
|
||||||
);
|
|
||||||
const JobsPage = () => (
|
|
||||||
<BetaPage>
|
|
||||||
<Typography variant="h4">Jobs</Typography>
|
|
||||||
</BetaPage>
|
|
||||||
);
|
|
||||||
const CompanyPage = () => (
|
|
||||||
<BetaPage>
|
|
||||||
<Typography variant="h4">Company</Typography>
|
|
||||||
</BetaPage>
|
|
||||||
);
|
|
||||||
|
|
||||||
const LogoutPage = () => {
|
|
||||||
const { logout } = useAuth();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
logout().then(() => {
|
|
||||||
navigate("/");
|
|
||||||
});
|
|
||||||
return (
|
|
||||||
<Typography variant="h4">Logging out...</Typography>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const AnalyticsPage = () => (
|
|
||||||
<BetaPage>
|
|
||||||
<Typography variant="h4">Analytics</Typography>
|
|
||||||
</BetaPage>
|
|
||||||
);
|
|
||||||
const SettingsPage = () => (
|
|
||||||
<BetaPage>
|
|
||||||
<Typography variant="h4">Settings</Typography>
|
|
||||||
</BetaPage>
|
|
||||||
);
|
|
||||||
|
|
||||||
export const navigationConfig: NavigationConfig = {
|
export const navigationConfig: NavigationConfig = {
|
||||||
items: [
|
items: [
|
||||||
{
|
{ id: 'home', label: <BackstoryLogo />, path: '/', component: <HowItWorks />, userTypes: ['guest', 'candidate', 'employer'], exact: true, },
|
||||||
id: "home",
|
// { id: 'how-it-works', label: 'How It Works', path: '/how-it-works', icon: <SchoolIcon />, component: <HowItWorks />, userTypes: ['guest', 'candidate', 'employer',], },
|
||||||
label: <BackstoryLogo />,
|
{ id: 'job-analysis', label: 'Job Analysis', path: '/job-analysis', icon: <WorkIcon />, component: <JobAnalysisPage />, userTypes: ['guest', 'candidate', 'employer',], },
|
||||||
path: "/",
|
{ id: 'chat', label: 'Candidate Chat', path: '/chat', icon: <ChatIcon />, component: <CandidateChatPage />, userTypes: ['guest', 'candidate', 'employer',], }, {
|
||||||
component: <HowItWorks />,
|
id: 'candidate-menu', label: 'Tools', icon: <PersonIcon />, userTypes: ['candidate'], children: [
|
||||||
userTypes: ["guest", "candidate", "employer"],
|
{ id: 'candidate-dashboard', label: 'Dashboard', path: '/candidate/dashboard', icon: <DashboardIcon />, component: <CandidateDashboard />, userTypes: ['candidate'] },
|
||||||
exact: true,
|
{ id: 'candidate-profile', label: 'Profile', icon: <PersonIcon />, path: '/candidate/profile', component: <CandidateProfile />, userTypes: ['candidate'] },
|
||||||
|
{ id: 'candidate-docs', label: 'Documents', icon: <BubbleChart />, path: '/candidate/documents', component: <Box sx={{ display: "flex", width: "100%", flexDirection: "column" }}><VectorVisualizer /><DocumentManager /></Box>, userTypes: ['candidate'] },
|
||||||
|
{ id: 'candidate-qa-setup', label: 'Q&A Setup', icon: <QuizIcon />, path: '/candidate/qa-setup', component: <BetaPage><Box>Candidate q&a setup page</Box></BetaPage>, userTypes: ['candidate'] },
|
||||||
|
{ id: 'candidate-analytics', label: 'Analytics', icon: <AnalyticsIcon />, path: '/candidate/analytics', component: <BetaPage><Box>Candidate analytics page</Box></BetaPage>, userTypes: ['candidate'] },
|
||||||
|
{ id: 'candidate-job-analysis', label: 'Job Analysis', path: '/candidate/job-analysis', icon: <WorkIcon />, component: <JobAnalysisPage />, userTypes: ['candidate'], },
|
||||||
|
{ id: 'candidate-resumes', label: 'Resumes', icon: <DescriptionIcon />, path: '/candidate/resumes', component: <BetaPage><Box>Candidate resumes page</Box></BetaPage>, userTypes: ['candidate'] },
|
||||||
|
{ id: 'candidate-settings', label: 'Settings', path: '/candidate/settings', icon: <SettingsIcon />, component: <Settings />, userTypes: ['candidate'], },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'employer-menu', label: 'Employer Tools', icon: <BusinessIcon />, userTypes: ['employer'], children: [
|
||||||
|
{ id: 'employer-job-analysis', label: 'Job Analysis', path: '/employer/job-analysis', icon: <WorkIcon />, component: <JobAnalysisPage />, userTypes: ['employer'], },
|
||||||
|
{ id: 'employer-knowledge-explorer', label: 'Knowledge Explorer', path: '/employer/knowledge-explorer', icon: <WorkIcon />, component: <VectorVisualizerPage />, userTypes: ['employer'], },
|
||||||
|
{ id: 'employer-search', label: 'Search', path: '/employer/search', icon: <SearchIcon />, component: <SearchPage />, userTypes: ['employer'], },
|
||||||
|
{ id: 'employer-saved', label: 'Saved', path: '/employer/saved', icon: <BookmarkIcon />, component: <SavedPage />, userTypes: ['employer'], },
|
||||||
|
{ id: 'employer-jobs', label: 'Jobs', path: '/employer/jobs', icon: <WorkIcon />, component: <JobsPage />, 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-settings', label: 'Settings', path: '/employer/settings', icon: <SettingsIcon />, component: <SettingsPage />, userTypes: ['employer'], },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// { id: 'find-candidate', label: 'Find a Candidate', path: '/find-a-candidate', icon: <PersonSearchIcon />, component: <CandidateListingPage />, userTypes: ['guest', 'candidate', 'employer'], },
|
||||||
{
|
{
|
||||||
id: "job-analysis",
|
id: 'admin-menu',
|
||||||
label: "Job Analysis",
|
label: 'Admin',
|
||||||
path: "/job-analysis",
|
icon: <PersonIcon />,
|
||||||
icon: <WorkIcon />,
|
userTypes: ['admin'],
|
||||||
component: <JobAnalysisPage />,
|
children: [
|
||||||
userTypes: ["guest", "candidate", "employer"],
|
{ id: 'generate-candidate', label: 'Generate Candidate', path: '/admin/generate-candidate', icon: <FaceRetouchingNaturalIcon />, component: <GenerateCandidate />, userTypes: ['admin'] },
|
||||||
},
|
{ id: 'docs', label: 'Docs', path: '/docs/*', icon: <LibraryBooksIcon />, component: <DocsPage />, userTypes: ['admin'], },
|
||||||
{
|
],
|
||||||
id: "chat",
|
|
||||||
label: "Candidate Chat",
|
|
||||||
path: "/chat",
|
|
||||||
icon: <ChatIcon />,
|
|
||||||
component: <CandidateChatPage />,
|
|
||||||
userTypes: ["guest", "candidate", "employer"],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "generate-candidate",
|
|
||||||
label: "Generate Candidate",
|
|
||||||
path: "/admin/generate-candidate",
|
|
||||||
icon: <PersonIcon />,
|
|
||||||
component: <GenerateCandidate />,
|
|
||||||
userTypes: ["admin"],
|
|
||||||
showInNavigation: true,
|
|
||||||
userMenuGroup: "system",
|
|
||||||
},
|
|
||||||
// User menu only items (not shown in main navigation)
|
|
||||||
{
|
|
||||||
id: "candidate-profile",
|
|
||||||
label: "Profile",
|
|
||||||
icon: <PersonIcon />,
|
|
||||||
path: "/candidate/profile",
|
|
||||||
component: <CandidateProfile />,
|
|
||||||
userTypes: ["candidate"],
|
|
||||||
userMenuGroup: "profile",
|
|
||||||
showInNavigation: false,
|
|
||||||
showInUserMenu: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "candidate-dashboard",
|
|
||||||
label: "Dashboard",
|
|
||||||
path: "/candidate/dashboard",
|
|
||||||
icon: <DashboardIcon />,
|
|
||||||
component: <CandidateDashboard />,
|
|
||||||
userTypes: ["candidate"],
|
|
||||||
userMenuGroup: "profile",
|
|
||||||
showInNavigation: false,
|
|
||||||
showInUserMenu: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "candidate-docs",
|
|
||||||
label: "Content",
|
|
||||||
icon: <BubbleChart />,
|
|
||||||
path: "/candidate/documents",
|
|
||||||
component: (
|
|
||||||
<Box sx={{ display: "flex", width: "100%", flexDirection: "column" }}>
|
|
||||||
<VectorVisualizer />
|
|
||||||
<DocumentManager />
|
|
||||||
</Box>
|
|
||||||
),
|
|
||||||
userTypes: ["candidate"],
|
|
||||||
userMenuGroup: "profile",
|
|
||||||
showInNavigation: false,
|
|
||||||
showInUserMenu: true,
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// id: "candidate-qa-setup",
|
|
||||||
// label: "Q&A Setup",
|
|
||||||
// icon: <QuizIcon />,
|
|
||||||
// path: "/candidate/qa-setup",
|
|
||||||
// component: (
|
|
||||||
// <BetaPage>
|
|
||||||
// <Box>Candidate q&a setup page</Box>
|
|
||||||
// </BetaPage>
|
|
||||||
// ),
|
|
||||||
// userTypes: ["candidate"],
|
|
||||||
// showInNavigation: false,
|
|
||||||
// showInUserMenu: true,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: "candidate-analytics",
|
|
||||||
// label: "Analytics",
|
|
||||||
// icon: <AnalyticsIcon />,
|
|
||||||
// path: "/candidate/analytics",
|
|
||||||
// component: (
|
|
||||||
// <BetaPage>
|
|
||||||
// <Box>Candidate analytics page</Box>
|
|
||||||
// </BetaPage>
|
|
||||||
// ),
|
|
||||||
// userTypes: ["candidate"],
|
|
||||||
// showInNavigation: false,
|
|
||||||
// showInUserMenu: true,
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// id: "candidate-resumes",
|
|
||||||
// label: "Resumes",
|
|
||||||
// icon: <DescriptionIcon />,
|
|
||||||
// path: "/candidate/resumes",
|
|
||||||
// component: (
|
|
||||||
// <BetaPage>
|
|
||||||
// <Box>Candidate resumes page</Box>
|
|
||||||
// </BetaPage>
|
|
||||||
// ),
|
|
||||||
// userTypes: ["candidate"],
|
|
||||||
// showInNavigation: false,
|
|
||||||
// showInUserMenu: true,
|
|
||||||
// },
|
|
||||||
{
|
|
||||||
id: "candidate-settings",
|
|
||||||
label: "Settings",
|
|
||||||
path: "/candidate/settings",
|
|
||||||
icon: <SettingsIcon />,
|
|
||||||
component: <Settings />,
|
|
||||||
userTypes: ["candidate"],
|
|
||||||
userMenuGroup: "account",
|
|
||||||
showInNavigation: false,
|
|
||||||
showInUserMenu: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "logout",
|
|
||||||
label: "Logout",
|
|
||||||
icon: <PersonIcon />, // This will be handled specially in Header
|
|
||||||
userTypes: ["candidate", "employer"],
|
|
||||||
showInNavigation: false,
|
|
||||||
showInUserMenu: true,
|
|
||||||
userMenuGroup: "system",
|
|
||||||
},
|
},
|
||||||
// Auth routes (special handling)
|
// Auth routes (special handling)
|
||||||
{
|
{
|
||||||
id: "auth",
|
id: 'auth',
|
||||||
label: "Auth",
|
label: 'Auth',
|
||||||
userTypes: ["guest", "candidate", "employer"],
|
userTypes: ['guest', 'candidate', 'employer'],
|
||||||
showInNavigation: false,
|
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
id: "register",
|
id: 'register',
|
||||||
label: "Register",
|
label: 'Register',
|
||||||
path: "/login/register",
|
path: '/register',
|
||||||
component: (
|
component: <BetaPage><CreateProfilePage /></BetaPage>,
|
||||||
<BetaPage>
|
userTypes: ['guest'],
|
||||||
<CreateProfilePage />
|
|
||||||
</BetaPage>
|
|
||||||
),
|
|
||||||
userTypes: ["guest"],
|
|
||||||
showInNavigation: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "login",
|
id: 'login',
|
||||||
label: "Login",
|
label: 'Login',
|
||||||
path: "/login/*",
|
path: '/login/*',
|
||||||
component: <LoginPage />,
|
component: <LoginPage />,
|
||||||
userTypes: ["guest", "candidate", "employer"],
|
userTypes: ['guest', 'candidate', 'employer'],
|
||||||
showInNavigation: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "verify-email",
|
id: 'verify-email',
|
||||||
label: "Verify Email",
|
label: 'Verify Email',
|
||||||
path: "/login/verify-email",
|
path: '/login/verify-email',
|
||||||
component: <EmailVerificationPage />,
|
component: <EmailVerificationPage />,
|
||||||
userTypes: ["guest", "candidate", "employer"],
|
userTypes: ['guest', 'candidate', 'employer'],
|
||||||
showInNavigation: false,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "logout-page",
|
id: 'logout',
|
||||||
label: "Logout",
|
label: 'Logout',
|
||||||
path: "/logout",
|
path: '/logout',
|
||||||
component: <LogoutPage />,
|
component: <LogoutPage />,
|
||||||
userTypes: ["candidate", "employer"],
|
userTypes: ['candidate', 'employer'],
|
||||||
showInNavigation: false,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
// Catch-all route
|
// Catch-all route
|
||||||
{
|
{
|
||||||
id: "catch-all",
|
id: 'catch-all',
|
||||||
label: "Not Found",
|
label: 'Not Found',
|
||||||
path: "*",
|
path: '*',
|
||||||
component: <BetaPage />,
|
component: <BetaPage />,
|
||||||
userTypes: ["guest", "candidate", "employer"],
|
userTypes: ['guest', 'candidate', 'employer'],
|
||||||
showInNavigation: false,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Utility functions for working with navigation config
|
// Utility functions for working with navigation config
|
||||||
export const getNavigationItemsForUser = (
|
export const getNavigationItemsForUser = (userType: 'guest' | 'candidate' | 'employer' | null, isAdmin: boolean = false): NavigationItem[] => {
|
||||||
userType: "guest" | "candidate" | "employer" | null,
|
const currentUserType = userType || 'guest';
|
||||||
isAdmin: boolean = false
|
|
||||||
): NavigationItem[] => {
|
|
||||||
const currentUserType = userType || "guest";
|
|
||||||
|
|
||||||
const filterItems = (items: NavigationItem[]): NavigationItem[] => {
|
const filterItems = (items: NavigationItem[]): NavigationItem[] => {
|
||||||
return items
|
return items
|
||||||
.filter(
|
.filter(item => !item.userTypes || item.userTypes.includes(currentUserType) || (item.userTypes.includes('admin') && isAdmin))
|
||||||
(item) =>
|
.map(item => ({
|
||||||
!item.userTypes || item.userTypes.includes(currentUserType) || (item.userTypes.includes("admin") && isAdmin)
|
|
||||||
)
|
|
||||||
.filter((item) => item.showInNavigation !== false) // Default to true if not specified
|
|
||||||
.map((item) => ({
|
|
||||||
...item,
|
...item,
|
||||||
children: item.children ? filterItems(item.children) : undefined,
|
children: item.children ? filterItems(item.children) : undefined,
|
||||||
}))
|
}))
|
||||||
.filter((item) => item.path || (item.children && item.children.length > 0));
|
.filter(item => item.path || (item.children && item.children.length > 0));
|
||||||
};
|
};
|
||||||
|
|
||||||
return filterItems(navigationConfig.items);
|
return filterItems(navigationConfig.items);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAllRoutes = (
|
export const getAllRoutes = (userType: 'guest' | 'candidate' | 'employer' | null, isAdmin: boolean = false): NavigationItem[] => {
|
||||||
userType: "guest" | "candidate" | "employer" | null,
|
const currentUserType = userType || 'guest';
|
||||||
isAdmin: boolean = false
|
|
||||||
): NavigationItem[] => {
|
|
||||||
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) || (item.userTypes.includes("admin") && isAdmin)) {
|
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);
|
||||||
}
|
}
|
||||||
@ -338,67 +168,18 @@ export const getAllRoutes = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return routes;
|
return routes;
|
||||||
};
|
};
|
||||||
|
|
||||||
return extractRoutes(navigationConfig.items);
|
return extractRoutes(navigationConfig.items);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getMainNavigationItems = (
|
export const getMainNavigationItems = (userType: 'guest' | 'candidate' | 'employer' | null, isAdmin: boolean = false): NavigationItem[] => {
|
||||||
userType: "guest" | "candidate" | "employer" | null,
|
return getNavigationItemsForUser(userType, isAdmin)
|
||||||
isAdmin: boolean = false
|
.filter(item =>
|
||||||
): NavigationItem[] => {
|
item.id !== 'auth' &&
|
||||||
return getNavigationItemsForUser(userType, isAdmin).filter(
|
item.id !== 'catch-all' &&
|
||||||
(item) =>
|
|
||||||
item.id !== "auth" &&
|
|
||||||
item.id !== "catch-all" &&
|
|
||||||
item.showInNavigation !== false &&
|
|
||||||
(item.path || (item.children && item.children.length > 0))
|
(item.path || (item.children && item.children.length > 0))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getUserMenuItems = (userType: "candidate" | "employer" | "guest" | null): NavigationItem[] => {
|
|
||||||
if (!userType) return [];
|
|
||||||
|
|
||||||
const extractUserMenuItems = (items: NavigationItem[]): NavigationItem[] => {
|
|
||||||
const menuItems: NavigationItem[] = [];
|
|
||||||
|
|
||||||
items.forEach((item) => {
|
|
||||||
if (!item.userTypes || item.userTypes.includes(userType)) {
|
|
||||||
if (item.showInUserMenu) {
|
|
||||||
menuItems.push(item);
|
|
||||||
}
|
|
||||||
if (item.children) {
|
|
||||||
menuItems.push(...extractUserMenuItems(item.children));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return menuItems;
|
|
||||||
};
|
|
||||||
|
|
||||||
return extractUserMenuItems(navigationConfig.items);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getUserMenuItemsByGroup = (
|
|
||||||
userType: "candidate" | "employer" | "guest" | null
|
|
||||||
): { [key: string]: NavigationItem[] } => {
|
|
||||||
const menuItems = getUserMenuItems(userType);
|
|
||||||
const grouped: { [key: string]: NavigationItem[] } = {
|
|
||||||
profile: [],
|
|
||||||
account: [],
|
|
||||||
system: [],
|
|
||||||
other: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
menuItems.forEach((item) => {
|
|
||||||
const group = item.userMenuGroup || "other";
|
|
||||||
if (!grouped[group]) {
|
|
||||||
grouped[group] = [];
|
|
||||||
}
|
|
||||||
grouped[group].push(item);
|
|
||||||
});
|
|
||||||
|
|
||||||
return grouped;
|
|
||||||
};
|
|
@ -336,14 +336,14 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
|
|||||||
<WorkIcon />
|
<WorkIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
}
|
}
|
||||||
<JobInfo variant="minimal" job={analysisState.job} />
|
<JobInfo variant="small" job={analysisState.job} />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
{isMobile && <Box sx={{ display: "flex", borderBottom: "1px solid lightgrey" }} />}
|
{isMobile && <Box sx={{ display: "flex", borderBottom: "1px solid grey" }} />}
|
||||||
{!isMobile && <Box sx={{ display: "flex", borderLeft: "1px solid lightgrey" }} />}
|
{!isMobile && <Box sx={{ display: "flex", borderLeft: "1px solid lightgrey" }} />}
|
||||||
{analysisState && analysisState.candidate &&
|
{analysisState && analysisState.candidate &&
|
||||||
<Box sx={{ display: "flex", flexDirection: "row", width: "100%" }}>
|
<Box sx={{ display: "flex", flexDirection: "row", width: "100%" }}>
|
||||||
<CandidateInfo variant="minimal" candidate={analysisState.candidate} sx={{}} />
|
<CandidateInfo variant="small" candidate={analysisState.candidate} sx={{}} />
|
||||||
</Box>
|
</Box>
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
// types/navigation.ts
|
||||||
import { ReactElement } from 'react';
|
import { ReactElement } from 'react';
|
||||||
|
|
||||||
export interface NavigationItem {
|
export interface NavigationItem {
|
||||||
@ -10,9 +11,6 @@ export interface NavigationItem {
|
|||||||
userTypes?: ('candidate' | 'employer' | 'guest' | 'admin')[];
|
userTypes?: ('candidate' | 'employer' | 'guest' | 'admin')[];
|
||||||
exact?: boolean;
|
exact?: boolean;
|
||||||
divider?: boolean;
|
divider?: boolean;
|
||||||
showInNavigation?: boolean; // Controls if item appears in main navigation
|
|
||||||
showInUserMenu?: boolean; // Controls if item appears in user menu
|
|
||||||
userMenuGroup?: 'profile' | 'account' | 'system'; // Groups items in user menu
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface NavigationConfig {
|
export interface NavigationConfig {
|
||||||
|
@ -4936,6 +4936,7 @@ async def generate_resume(
|
|||||||
yield error_message
|
yield error_message
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Generate new skill match
|
||||||
final_message = None
|
final_message = None
|
||||||
async for generated_message in agent.generate_resume(
|
async for generated_message in agent.generate_resume(
|
||||||
llm=llm_manager.get_llm(),
|
llm=llm_manager.get_llm(),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user