From 74201d0a71c334d56f9f1615e790c00335728a16 Mon Sep 17 00:00:00 2001 From: James Ketrenos Date: Wed, 11 Jun 2025 15:11:35 -0700 Subject: [PATCH] Menus restructured --- frontend/src/components/JobCreator.tsx | 2 +- frontend/src/components/ResumeGenerator.tsx | 53 ++- frontend/src/components/layout/Header.tsx | 11 + frontend/src/components/ui/AIBanner.css | 17 +- frontend/src/components/ui/AIBanner.tsx | 11 +- frontend/src/components/ui/CandidateInfo.tsx | 10 +- .../src/components/ui/CandidatePicker.tsx | 15 +- frontend/src/components/ui/JobInfo.tsx | 21 +- frontend/src/components/ui/JobPicker.tsx | 15 +- frontend/src/config/navigationConfig.tsx | 443 +++++++++++------- frontend/src/pages/JobAnalysisPage.tsx | 6 +- src/backend/main.py | 1 - 12 files changed, 389 insertions(+), 216 deletions(-) diff --git a/frontend/src/components/JobCreator.tsx b/frontend/src/components/JobCreator.tsx index d4874c0..27f79ee 100644 --- a/frontend/src/components/JobCreator.tsx +++ b/frontend/src/components/JobCreator.tsx @@ -89,7 +89,7 @@ const JobCreator = (props: JobCreatorProps) => { const [company, setCompany] = useState(''); const [summary, setSummary] = useState(''); const [job, setJob] = useState(null); - const [jobStatus, setJobStatus] = useState(''); + const [jobStatus, setJobStatus] = useState(''); const [jobStatusType, setJobStatusType] = useState(null); const [isProcessing, setIsProcessing] = useState(false); diff --git a/frontend/src/components/ResumeGenerator.tsx b/frontend/src/components/ResumeGenerator.tsx index 5bb025e..10e339a 100644 --- a/frontend/src/components/ResumeGenerator.tsx +++ b/frontend/src/components/ResumeGenerator.tsx @@ -6,6 +6,7 @@ import { Button, Paper, Typography, + LinearProgress, } from '@mui/material'; import { Job, Candidate, SkillAssessment } from "types/types"; import { Scrollable } from './Scrollable'; @@ -16,6 +17,9 @@ import { Message } from './Message'; import InputIcon from '@mui/icons-material/Input'; import TuneIcon from '@mui/icons-material/Tune'; import ArticleIcon from '@mui/icons-material/Article'; +import { StatusBox, StatusIcon } from './ui/StatusIcon'; +import { CopyBubble } from './CopyBubble'; +import { useAppState } from 'hooks/GlobalContext'; interface ResumeGeneratorProps { job: Job; @@ -30,12 +34,15 @@ const defaultMessage: Types.ChatMessageStatus = { const ResumeGenerator: React.FC = (props: ResumeGeneratorProps) => { const { job, candidate, skills, onComplete } = props; + const { setSnack } = useAppState(); const { apiClient, user } = useAuth(); const [resume, setResume] = useState(''); const [prompt, setPrompt] = useState(''); const [systemPrompt, setSystemPrompt] = useState(''); const [generated, setGenerated] = useState(false); const [tabValue, setTabValue] = useState('resume'); + const [status, setStatus] = useState(''); + const [statusType, setStatusType] = useState(null); const handleTabChange = (event: React.SyntheticEvent, newValue: string) => { setTabValue(newValue); @@ -48,18 +55,36 @@ const ResumeGenerator: React.FC = (props: ResumeGeneratorP setGenerated(true); + setStatusType("thinking"); + setStatus("Starting resume generation..."); + const generateResumeHandlers = { + onMessage: (message: Types.ChatMessageResume) => { + setSystemPrompt(message.systemPrompt || ''); + setPrompt(message.prompt || ''); + setResume(message.resume || ''); + setStatus(''); + }, onStreaming: (chunk: Types.ChatMessageStreaming) => { + if (status === '') { + setStatus('Generating resume...'); + setStatusType("generating"); + } 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 request: any = await apiClient.generateResume(candidate.id || '', skills, generateResumeHandlers); const result = await request.promise; - setSystemPrompt(result.systemPrompt) - setPrompt(result.prompt) - setResume(result.resume) }; generateResume(); @@ -72,18 +97,28 @@ const ResumeGenerator: React.FC = (props: ResumeGeneratorP display: "flex", flexDirection: "column", }}> - {user?.isAdmin && + {user?.isAdmin && - } label="System" /> - } label="Prompt" /> - } label="Resume" /> + } label="System" /> + } label="Prompt" /> + } label="Resume" /> } - + {status && + + {statusType && } + + {status || 'Processing...'} + + + {status && } + } + + {tabValue === 'system' &&
{systemPrompt}
} {tabValue === 'prompt' &&
{prompt}
} - {tabValue === 'resume' && } + {tabValue === 'resume' && <> { setSnack('Resume copied to clipboard!'); }} sx={{ position: "absolute", top: 0, right: 0 }} content={resume} />}
) diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx index 7547f02..d72edd4 100644 --- a/frontend/src/components/layout/Header.tsx +++ b/frontend/src/components/layout/Header.tsx @@ -175,6 +175,17 @@ const Header: React.FC = (props: HeaderProps) => { } }); + // 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 userMenuGroups.account.forEach(item => { if (!item.divider) { diff --git a/frontend/src/components/ui/AIBanner.css b/frontend/src/components/ui/AIBanner.css index de4c90b..c14323e 100644 --- a/frontend/src/components/ui/AIBanner.css +++ b/frontend/src/components/ui/AIBanner.css @@ -16,17 +16,17 @@ } .aibanner-label { - width: 300px; - position: absolute; - display: flex; right: -70px; top: 40px; height: 32px; + font-size: 20px; + width: 300px; + position: absolute; + display: flex; justify-content: center; align-items: center; transform: rotate(45deg); transform-origin: center center; - font-size: 20px; text-align: center; font-weight: bold; color: #484848; @@ -36,3 +36,12 @@ pointer-events: auto; opacity: 0.5; } + +.aibanner-label-minimal, +.aibanner-label-small { + right: -100px; + top: 30px; + height: 20px; + font-size: 15px; + width: 300px; +} \ No newline at end of file diff --git a/frontend/src/components/ui/AIBanner.tsx b/frontend/src/components/ui/AIBanner.tsx index b99ce51..3e03868 100644 --- a/frontend/src/components/ui/AIBanner.tsx +++ b/frontend/src/components/ui/AIBanner.tsx @@ -3,18 +3,25 @@ import Box from '@mui/material/Box'; import { SxProps } from '@mui/material/styles'; import './AIBanner.css'; +import { useMediaQuery, useTheme } from '@mui/material'; type AIBannerProps = { sx?: SxProps; + variant?: "minimal" | "small" | "normal" | undefined; } const AIBanner: React.FC = (props : AIBannerProps) => { - const { sx = {} } = props; + const theme = useTheme(); + const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const { + sx = {}, + variant = isMobile ? "small" : "normal", + } = props; const aibannerRef = useRef(null); return ( - + AI Generated diff --git a/frontend/src/components/ui/CandidateInfo.tsx b/frontend/src/components/ui/CandidateInfo.tsx index c11eb16..da6513e 100644 --- a/frontend/src/components/ui/CandidateInfo.tsx +++ b/frontend/src/components/ui/CandidateInfo.tsx @@ -20,7 +20,7 @@ interface CandidateInfoProps { sx?: SxProps; action?: string; elevation?: number; - variant?: "small" | "normal" | null + variant?: "minimal" | "small" | "normal" | undefined; }; const CandidateInfo: React.FC = (props: CandidateInfoProps) => { @@ -33,7 +33,7 @@ const CandidateInfo: React.FC = (props: CandidateInfoProps) variant = "normal" } = props; const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === "minimal"; const ai: CandidateAI | null = ('isAI' in candidate) ? candidate as CandidateAI : null; const isAdmin = user?.isAdmin; @@ -77,7 +77,7 @@ const CandidateInfo: React.FC = (props: CandidateInfoProps) }} {...rest} > - {ai && } + {ai && } = (props: CandidateInfoProps) - {(!isMobile || variant !== "small") && ( + {(!isMobile && variant === "normal") && ( = (props: CandidateInfoProps) )} - {variant !== "small" && <> + {(variant !== "small" && variant !== "minimal") && <> {candidate.location && diff --git a/frontend/src/components/ui/CandidatePicker.tsx b/frontend/src/components/ui/CandidatePicker.tsx index 441961f..1eb2ccc 100644 --- a/frontend/src/components/ui/CandidatePicker.tsx +++ b/frontend/src/components/ui/CandidatePicker.tsx @@ -61,7 +61,20 @@ const CandidatePicker = (props: CandidatePickerProps) => { { onSelect ? onSelect(u) : setSelectedCandidate(u); }} sx={{ cursor: "pointer" }}> - + )} diff --git a/frontend/src/components/ui/JobInfo.tsx b/frontend/src/components/ui/JobInfo.tsx index efebfb2..b98d5be 100644 --- a/frontend/src/components/ui/JobInfo.tsx +++ b/frontend/src/components/ui/JobInfo.tsx @@ -27,7 +27,7 @@ interface JobInfoProps { sx?: SxProps; action?: string; elevation?: number; - variant?: "small" | "normal" | null + variant?: "minimal" | "small" | "normal" | null }; @@ -42,7 +42,7 @@ const JobInfo: React.FC = (props: JobInfoProps) => { variant = "normal" } = props; const theme = useTheme(); - const isMobile = useMediaQuery(theme.breakpoints.down('md')); + const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === "minimal"; const isAdmin = user?.isAdmin; const [adminStatus, setAdminStatus] = useState(null); const [adminStatusType, setAdminStatusType] = useState(null); @@ -212,21 +212,22 @@ const JobInfo: React.FC = (props: JobInfoProps) => { borderStyle: 'solid', transition: 'all 0.3s ease', flexDirection: "column", - ...sx + ...sx, + minWidth: 0, }} {...rest} > div > div > :first-of-type": { fontWeight: "bold" }, + "& > div > div > :first-of-type": { fontWeight: "bold", whiteSpace: "nowrap" }, "& > div > div > :last-of-type": { mb: 0.75, mr: 1 } }}> {activeJob.company && Company - {activeJob.company} + {activeJob.company} } {activeJob.title && @@ -236,10 +237,10 @@ const JobInfo: React.FC = (props: JobInfoProps) => { } - + {!isMobile && activeJob.summary && Summary - + = (props: JobInfoProps) => { - {variant !== "small" && <> + {(variant !== "small" && variant !== "minimal") && <> {activeJob.details && Location: {activeJob.details.location.city}, {activeJob.details.location.state || activeJob.details.location.country} } {activeJob.owner && - Created by: {activeJob.owner.fullName} + Submitted by: {activeJob.owner.fullName} } {activeJob.createdAt && Created: {activeJob.createdAt.toISOString()} @@ -304,7 +305,7 @@ const JobInfo: React.FC = (props: JobInfoProps) => { Job ID: {job.id} } - {variant !== 'small' && <>{renderJobRequirements()}} + {(variant !== 'small' && variant !== 'minimal') && <>{renderJobRequirements()}} {isAdmin && diff --git a/frontend/src/components/ui/JobPicker.tsx b/frontend/src/components/ui/JobPicker.tsx index cff75cf..81e5344 100644 --- a/frontend/src/components/ui/JobPicker.tsx +++ b/frontend/src/components/ui/JobPicker.tsx @@ -52,7 +52,20 @@ const JobPicker = (props: JobPickerProps) => { { onSelect ? onSelect(j) : setSelectedJob(j); }} sx={{ cursor: "pointer" }}> - + )} diff --git a/frontend/src/config/navigationConfig.tsx b/frontend/src/config/navigationConfig.tsx index 6939fe3..1847a2f 100644 --- a/frontend/src/config/navigationConfig.tsx +++ b/frontend/src/config/navigationConfig.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React from "react"; import { Chat as ChatIcon, Dashboard as DashboardIcon, @@ -17,239 +17,319 @@ import { Quiz as QuizIcon, Analytics as AnalyticsIcon, BubbleChart, -} from '@mui/icons-material'; +} from "@mui/icons-material"; -import { BackstoryLogo } from 'components/ui/BackstoryLogo'; -import { HomePage } from 'pages/HomePage'; -import { CandidateChatPage } from 'pages/CandidateChatPage'; -import { DocsPage } from 'pages/DocsPage'; -import { CreateProfilePage } from 'pages/candidate/ProfileWizard'; -import { VectorVisualizerPage } from 'pages/VectorVisualizerPage'; -import { BetaPage } from 'pages/BetaPage'; -import { CandidateListingPage } from 'pages/FindCandidatePage'; -import { JobAnalysisPage } from 'pages/JobAnalysisPage'; -import { GenerateCandidate } from 'pages/GenerateCandidate'; -import { LoginPage } from 'pages/LoginPage'; -import { EmailVerificationPage } from 'components/EmailVerificationComponents'; -import { Box, Typography } from '@mui/material'; -import { CandidateDashboard } from 'pages/candidate/Dashboard'; -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'; +import { BackstoryLogo } from "components/ui/BackstoryLogo"; +import { HomePage } from "pages/HomePage"; +import { CandidateChatPage } from "pages/CandidateChatPage"; +import { DocsPage } from "pages/DocsPage"; +import { CreateProfilePage } from "pages/candidate/ProfileWizard"; +import { VectorVisualizerPage } from "pages/VectorVisualizerPage"; +import { BetaPage } from "pages/BetaPage"; +import { CandidateListingPage } from "pages/FindCandidatePage"; +import { JobAnalysisPage } from "pages/JobAnalysisPage"; +import { GenerateCandidate } from "pages/GenerateCandidate"; +import { LoginPage } from "pages/LoginPage"; +import { EmailVerificationPage } from "components/EmailVerificationComponents"; +import { Box, Typography } from "@mui/material"; +import { CandidateDashboard } from "pages/candidate/Dashboard"; +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"; +import { useAuth } from "hooks/AuthContext"; +import { useNavigate } from "react-router-dom"; // Beta page components for placeholder routes -const BackstoryPage = () => (Backstory); -const ResumesPage = () => (Resumes); -const QASetupPage = () => (Q&A Setup); -const SearchPage = () => (Search); -const SavedPage = () => (Saved); -const JobsPage = () => (Jobs); -const CompanyPage = () => (Company); -const LogoutPage = () => (Logout page...); -const AnalyticsPage = () => (Analytics); -const SettingsPage = () => (Settings); +const BackstoryPage = () => ( + + Backstory + +); +const ResumesPage = () => ( + + Resumes + +); +const QASetupPage = () => ( + + Q&A Setup + +); +const SearchPage = () => ( + + Search + +); +const SavedPage = () => ( + + Saved + +); +const JobsPage = () => ( + + Jobs + +); +const CompanyPage = () => ( + + Company + +); + +const LogoutPage = () => { + const { logout } = useAuth(); + const navigate = useNavigate(); + logout().then(() => { + navigate("/"); + }); + return ( + Logging out... + ); +} +const AnalyticsPage = () => ( + + Analytics + +); +const SettingsPage = () => ( + + Settings + +); export const navigationConfig: NavigationConfig = { items: [ - { id: 'home', label: , path: '/', component: , userTypes: ['guest', 'candidate', 'employer'], exact: true, }, - // { id: 'how-it-works', label: 'How It Works', path: '/how-it-works', icon: , component: , userTypes: ['guest', 'candidate', 'employer',], }, - { id: 'job-analysis', label: 'Job Analysis', path: '/job-analysis', icon: , component: , userTypes: ['guest', 'candidate', 'employer',], }, - { id: 'chat', label: 'Candidate Chat', path: '/chat', icon: , component: , userTypes: ['guest', 'candidate', 'employer',], }, { - id: 'candidate-menu', label: 'Tools', icon: , userTypes: ['candidate'], children: [ - { id: 'candidate-dashboard', label: 'Dashboard', path: '/candidate/dashboard', icon: , component: , userTypes: ['candidate'] }, - { id: 'candidate-profile', label: 'Profile', icon: , path: '/candidate/profile', component: , userTypes: ['candidate'] }, - { id: 'candidate-docs', label: 'Documents', icon: , path: '/candidate/documents', component: , userTypes: ['candidate'] }, - { id: 'candidate-qa-setup', label: 'Q&A Setup', icon: , path: '/candidate/qa-setup', component: Candidate q&a setup page, userTypes: ['candidate'] }, - { id: 'candidate-analytics', label: 'Analytics', icon: , path: '/candidate/analytics', component: Candidate analytics page, userTypes: ['candidate'] }, - { id: 'candidate-job-analysis', label: 'Job Analysis', path: '/candidate/job-analysis', icon: , component: , userTypes: ['candidate'], showInNavigation: false, - showInUserMenu: true, - userMenuGroup: 'profile',}, - { id: 'candidate-resumes', label: 'Resumes', icon: , path: '/candidate/resumes', component: Candidate resumes page, userTypes: ['candidate'] }, - { id: 'candidate-settings', label: 'Settings', path: '/candidate/settings', icon: , component: , userTypes: ['candidate'], }, - ], - }, - { - id: 'employer-menu', label: 'Employer Tools', icon: , userTypes: ['employer'], children: [ - { id: 'employer-job-analysis', label: 'Job Analysis', path: '/employer/job-analysis', icon: , component: , userTypes: ['employer'], }, - { id: 'employer-knowledge-explorer', label: 'Knowledge Explorer', path: '/employer/knowledge-explorer', icon: , component: , userTypes: ['employer'], }, - { id: 'employer-search', label: 'Search', path: '/employer/search', icon: , component: , userTypes: ['employer'], }, - { id: 'employer-saved', label: 'Saved', path: '/employer/saved', icon: , component: , userTypes: ['employer'], }, - { id: 'employer-jobs', label: 'Jobs', path: '/employer/jobs', icon: , component: , userTypes: ['employer'], }, - { id: 'employer-company', label: 'Company', path: '/employer/company', icon: , component: , userTypes: ['employer'], }, - { id: 'employer-analytics', label: 'Analytics', path: '/employer/analytics', icon: , component: , userTypes: ['employer'], }, - { id: 'employer-settings', label: 'Settings', path: '/employer/settings', icon: , component: , userTypes: ['employer'], }, - ], + { + id: "home", + label: , + path: "/", + component: , + userTypes: ["guest", "candidate", "employer"], + exact: true, }, { - id: 'global-tools', - label: 'Tools', - icon: , - userTypes: ['candidate', 'employer'], + id: "job-analysis", + label: "Job Analysis", + path: "/job-analysis", + icon: , + component: , + userTypes: ["guest", "candidate", "employer"], + }, + { + id: "chat", + label: "Candidate Chat", + path: "/chat", + icon: , + component: , + userTypes: ["guest", "candidate", "employer"], + }, + { + id: "generate-candidate", + label: "Generate Candidate", + path: "/admin/generate-candidate", + icon: , + component: , + userTypes: ["admin"], showInNavigation: true, - children: [ - { - id: 'knowledge-explorer', - label: 'Knowledge Explorer', - path: '/knowledge-explorer', - icon: , - component: , - userTypes: ['candidate', 'employer'], - showInNavigation: true, - }, - { - id: 'job-analysis', - label: 'Job Analysis', - path: '/job-analysis', - icon: , - component: , - userTypes: ['candidate', 'employer'], - showInNavigation: true, - }, - { - id: 'generate-candidate', - label: 'Generate Candidate', - path: '/generate-candidate', - icon: , - component: , - userTypes: ['candidate', 'employer'], - showInNavigation: true, - }, - ], + userMenuGroup: "system", }, // User menu only items (not shown in main navigation) { - id: 'user-profile', - label: 'Profile', - path: '/profile', + id: "candidate-profile", + label: "Profile", icon: , - component: , // Replace with actual profile page - userTypes: ['candidate', 'employer'], + path: "/candidate/profile", + component: , + userTypes: ["candidate"], + userMenuGroup: "profile", showInNavigation: false, showInUserMenu: true, - userMenuGroup: 'profile', }, { - id: 'account-settings', - label: 'Account Settings', - path: '/account/settings', + id: "candidate-dashboard", + label: "Dashboard", + path: "/candidate/dashboard", + icon: , + component: , + userTypes: ["candidate"], + userMenuGroup: "profile", + showInNavigation: false, + showInUserMenu: true, + }, + { + id: "candidate-docs", + label: "Content", + icon: , + path: "/candidate/documents", + component: ( + + + + + ), + userTypes: ["candidate"], + userMenuGroup: "profile", + showInNavigation: false, + showInUserMenu: true, + }, + // { + // id: "candidate-qa-setup", + // label: "Q&A Setup", + // icon: , + // path: "/candidate/qa-setup", + // component: ( + // + // Candidate q&a setup page + // + // ), + // userTypes: ["candidate"], + // showInNavigation: false, + // showInUserMenu: true, + // }, + // { + // id: "candidate-analytics", + // label: "Analytics", + // icon: , + // path: "/candidate/analytics", + // component: ( + // + // Candidate analytics page + // + // ), + // userTypes: ["candidate"], + // showInNavigation: false, + // showInUserMenu: true, + // }, + // { + // id: "candidate-resumes", + // label: "Resumes", + // icon: , + // path: "/candidate/resumes", + // component: ( + // + // Candidate resumes page + // + // ), + // userTypes: ["candidate"], + // showInNavigation: false, + // showInUserMenu: true, + // }, + { + id: "candidate-settings", + label: "Settings", + path: "/candidate/settings", icon: , - component: , - userTypes: ['candidate', 'employer'], + component: , + userTypes: ["candidate"], + userMenuGroup: "account", showInNavigation: false, showInUserMenu: true, - userMenuGroup: 'account', }, { - id: 'billing', - label: 'Billing', - path: '/billing', - icon: , - component: , // Replace with actual billing page - userTypes: ['candidate', 'employer'], - showInNavigation: false, - showInUserMenu: true, - userMenuGroup: 'account', - }, - { - id: 'user-menu-divider', - label: '', - userTypes: ['candidate', 'employer'], - showInNavigation: false, - showInUserMenu: true, - divider: true, - }, - { - id: 'logout', - label: 'Logout', + id: "logout", + label: "Logout", icon: , // This will be handled specially in Header - userTypes: ['candidate', 'employer'], + userTypes: ["candidate", "employer"], showInNavigation: false, showInUserMenu: true, - userMenuGroup: 'system', + userMenuGroup: "system", }, // Auth routes (special handling) { - id: 'auth', - label: 'Auth', - userTypes: ['guest', 'candidate', 'employer'], + id: "auth", + label: "Auth", + userTypes: ["guest", "candidate", "employer"], showInNavigation: false, children: [ { - id: 'register', - label: 'Register', - path: '/register', - component: , - userTypes: ['guest'], + id: "register", + label: "Register", + path: "/login/register", + component: ( + + + + ), + userTypes: ["guest"], showInNavigation: false, }, { - id: 'login', - label: 'Login', - path: '/login/*', + id: "login", + label: "Login", + path: "/login/*", component: , - userTypes: ['guest', 'candidate', 'employer'], + userTypes: ["guest", "candidate", "employer"], showInNavigation: false, }, { - id: 'verify-email', - label: 'Verify Email', - path: '/login/verify-email', + id: "verify-email", + label: "Verify Email", + path: "/login/verify-email", component: , - userTypes: ['guest', 'candidate', 'employer'], + userTypes: ["guest", "candidate", "employer"], showInNavigation: false, }, { - id: 'logout-page', - label: 'Logout', - path: '/logout', + id: "logout-page", + label: "Logout", + path: "/logout", component: , - userTypes: ['candidate', 'employer'], + userTypes: ["candidate", "employer"], showInNavigation: false, }, ], }, // Catch-all route { - id: 'catch-all', - label: 'Not Found', - path: '*', + id: "catch-all", + label: "Not Found", + path: "*", component: , - userTypes: ['guest', 'candidate', 'employer'], + userTypes: ["guest", "candidate", "employer"], showInNavigation: false, }, ], }; // Utility functions for working with navigation config -export const getNavigationItemsForUser = (userType: 'guest' | 'candidate' | 'employer' | null, isAdmin: boolean = false): NavigationItem[] => { - const currentUserType = userType || 'guest'; +export const getNavigationItemsForUser = ( + userType: "guest" | "candidate" | "employer" | null, + isAdmin: boolean = false +): NavigationItem[] => { + const currentUserType = userType || "guest"; const filterItems = (items: NavigationItem[]): NavigationItem[] => { return items - .filter(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 => ({ + .filter( + (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, 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); }; -export const getAllRoutes = (userType: 'guest' | 'candidate' | 'employer' | null, isAdmin: boolean = false): NavigationItem[] => { - const currentUserType = userType || 'guest'; - +export const getAllRoutes = ( + userType: "guest" | "candidate" | "employer" | null, + isAdmin: boolean = false +): NavigationItem[] => { + const currentUserType = userType || "guest"; + const extractRoutes = (items: NavigationItem[]): NavigationItem[] => { const routes: NavigationItem[] = []; - - items.forEach(item => { - if (!item.userTypes || item.userTypes.includes(currentUserType) || (item.userTypes.includes('admin') && isAdmin)) { + + items.forEach((item) => { + if (!item.userTypes || item.userTypes.includes(currentUserType) || (item.userTypes.includes("admin") && isAdmin)) { if (item.path && item.component) { routes.push(item); } @@ -258,30 +338,33 @@ export const getAllRoutes = (userType: 'guest' | 'candidate' | 'employer' | null } } }); - + return routes; }; return extractRoutes(navigationConfig.items); }; -export const getMainNavigationItems = (userType: 'guest' | 'candidate' | 'employer' | null, isAdmin: boolean = false): NavigationItem[] => { - return getNavigationItemsForUser(userType, isAdmin) - .filter(item => - item.id !== 'auth' && - item.id !== 'catch-all' && +export const getMainNavigationItems = ( + userType: "guest" | "candidate" | "employer" | null, + isAdmin: boolean = false +): NavigationItem[] => { + return getNavigationItemsForUser(userType, isAdmin).filter( + (item) => + item.id !== "auth" && + item.id !== "catch-all" && item.showInNavigation !== false && (item.path || (item.children && item.children.length > 0)) - ); + ); }; -export const getUserMenuItems = (userType: 'candidate' | 'employer' | 'guest' | null): NavigationItem[] => { +export const getUserMenuItems = (userType: "candidate" | "employer" | "guest" | null): NavigationItem[] => { if (!userType) return []; const extractUserMenuItems = (items: NavigationItem[]): NavigationItem[] => { const menuItems: NavigationItem[] = []; - items.forEach(item => { + items.forEach((item) => { if (!item.userTypes || item.userTypes.includes(userType)) { if (item.showInUserMenu) { menuItems.push(item); @@ -298,17 +381,19 @@ export const getUserMenuItems = (userType: 'candidate' | 'employer' | 'guest' | return extractUserMenuItems(navigationConfig.items); }; -export const getUserMenuItemsByGroup = (userType: 'candidate' | 'employer' | 'guest' | null): { [key: string]: NavigationItem[] } => { +export const getUserMenuItemsByGroup = ( + userType: "candidate" | "employer" | "guest" | null +): { [key: string]: NavigationItem[] } => { const menuItems = getUserMenuItems(userType); const grouped: { [key: string]: NavigationItem[] } = { profile: [], account: [], system: [], - other: [] + other: [], }; - menuItems.forEach(item => { - const group = item.userMenuGroup || 'other'; + menuItems.forEach((item) => { + const group = item.userMenuGroup || "other"; if (!grouped[group]) { grouped[group] = []; } @@ -316,4 +401,4 @@ export const getUserMenuItemsByGroup = (userType: 'candidate' | 'employer' | 'gu }); return grouped; -}; \ No newline at end of file +}; diff --git a/frontend/src/pages/JobAnalysisPage.tsx b/frontend/src/pages/JobAnalysisPage.tsx index 8f316a4..17c233d 100644 --- a/frontend/src/pages/JobAnalysisPage.tsx +++ b/frontend/src/pages/JobAnalysisPage.tsx @@ -336,14 +336,14 @@ const JobAnalysisPage: React.FC = (props: BackstoryPageProps } - + } - {isMobile && } + {isMobile && } {!isMobile && } {analysisState && analysisState.candidate && - + } diff --git a/src/backend/main.py b/src/backend/main.py index 303c096..1180a86 100644 --- a/src/backend/main.py +++ b/src/backend/main.py @@ -4936,7 +4936,6 @@ async def generate_resume( yield error_message return - # Generate new skill match final_message = None async for generated_message in agent.generate_resume( llm=llm_manager.get_llm(),