diff --git a/frontend/src/components/BackstoryTab.tsx b/frontend/src/components/BackstoryTab.tsx index 1f4d502..247969d 100644 --- a/frontend/src/components/BackstoryTab.tsx +++ b/frontend/src/components/BackstoryTab.tsx @@ -5,8 +5,8 @@ import { ChatSubmitQueryInterface } from './BackstoryQuery'; import { SetSnackType } from './Snack'; interface BackstoryElementProps { - setSnack: SetSnackType, - submitQuery: ChatSubmitQueryInterface, + // setSnack: SetSnackType, + // submitQuery: ChatSubmitQueryInterface, sx?: SxProps, } diff --git a/frontend/src/components/Conversation.tsx b/frontend/src/components/Conversation.tsx index c5eb631..7dd7d23 100644 --- a/frontend/src/components/Conversation.tsx +++ b/frontend/src/components/Conversation.tsx @@ -19,7 +19,7 @@ import { ChatMessage, ChatContext, ChatSession, ChatQuery, ChatMessageUser, Chat import { PaginatedResponse } from 'types/conversion'; import './Conversation.css'; -import { useSelectedCandidate } from 'hooks/GlobalContext'; +import { useAppState } from 'hooks/GlobalContext'; const defaultMessage: ChatMessage = { status: "done", type: "text", sessionId: "", timestamp: new Date(), content: "", role: "assistant" @@ -65,8 +65,6 @@ const Conversation = forwardRef((props: C preamble, resetAction, resetLabel, - setSnack, - submitQuery, sx, type, } = props; @@ -84,6 +82,7 @@ const Conversation = forwardRef((props: C const stopRef = useRef(false); const controllerRef = useRef(null); const [chatSession, setChatSession] = useState(null); + const { setSnack } = useAppState(); // Keep the ref updated whenever items changes useEffect(() => { @@ -326,16 +325,16 @@ const Conversation = forwardRef((props: C { filteredConversation.map((message, index) => - + ) } { processingMessage !== undefined && - + } { streamingMessage !== undefined && - + } { - const { setSnack, submitQuery, filepath } = props; - const backstoryProps = { - submitQuery, - setSnack, - }; + const { filepath } = props; const [document, setDocument] = useState(""); @@ -43,7 +39,7 @@ const Document = (props: DocumentProps) => { }, [document, setDocument, filepath]) return (<> - + ); }; diff --git a/frontend/src/components/DocumentManager.tsx b/frontend/src/components/DocumentManager.tsx index c6c1e17..c68889c 100644 --- a/frontend/src/components/DocumentManager.tsx +++ b/frontend/src/components/DocumentManager.tsx @@ -36,6 +36,7 @@ import { useTheme } from '@mui/material/styles'; import { useAuth } from "hooks/AuthContext"; import * as Types from 'types/types'; import { BackstoryElementProps } from './BackstoryTab'; +import { useAppState } from 'hooks/GlobalContext'; const VisuallyHiddenInput = styled('input')({ clip: 'rect(0 0 0 0)', @@ -50,8 +51,8 @@ const VisuallyHiddenInput = styled('input')({ }); const DocumentManager = (props: BackstoryElementProps) => { - const { setSnack, submitQuery } = props; const theme = useTheme(); + const { setSnack } = useAppState(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const { user, apiClient } = useAuth(); diff --git a/frontend/src/components/GenerateImage.tsx b/frontend/src/components/GenerateImage.tsx index 1236d13..88ff4bf 100644 --- a/frontend/src/components/GenerateImage.tsx +++ b/frontend/src/components/GenerateImage.tsx @@ -5,6 +5,7 @@ import { Quote } from 'components/Quote'; import { BackstoryElementProps } from 'components/BackstoryTab'; import { Candidate, ChatSession } from 'types/types'; import { useAuth } from 'hooks/AuthContext'; +import { useAppState } from 'hooks/GlobalContext'; interface GenerateImageProps extends BackstoryElementProps { prompt: string; @@ -13,7 +14,8 @@ interface GenerateImageProps extends BackstoryElementProps { const GenerateImage = (props: GenerateImageProps) => { const { user } = useAuth(); - const { setSnack, chatSession, prompt } = props; + const { chatSession, prompt } = props; + const { setSnack } = useAppState(); const [processing, setProcessing] = useState(false); const [status, setStatus] = useState(''); const [image, setImage] = useState(''); diff --git a/frontend/src/components/JobCreator.tsx b/frontend/src/components/JobCreator.tsx index d248f47..f3cd020 100644 --- a/frontend/src/components/JobCreator.tsx +++ b/frontend/src/components/JobCreator.tsx @@ -46,7 +46,7 @@ import DescriptionIcon from '@mui/icons-material/Description'; import FileUploadIcon from '@mui/icons-material/FileUpload'; import { useAuth } from 'hooks/AuthContext'; -import { useSelectedCandidate, useSelectedJob } from 'hooks/GlobalContext'; +import { useAppState, useSelectedCandidate, useSelectedJob } from 'hooks/GlobalContext'; import { BackstoryElementProps } from './BackstoryTab'; import { LoginRequired } from 'components/ui/LoginRequired'; @@ -122,8 +122,7 @@ const JobCreator = (props: JobCreator) => { const { onSave } = props; const { selectedCandidate } = useSelectedCandidate(); const { selectedJob, setSelectedJob } = useSelectedJob(); - const { setSnack, submitQuery } = props; - const backstoryProps = { setSnack, submitQuery }; + const { setSnack } = useAppState(); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); const isTablet = useMediaQuery(theme.breakpoints.down('md')); diff --git a/frontend/src/components/JobMatchAnalysis.tsx b/frontend/src/components/JobMatchAnalysis.tsx index 9c41f09..b39af5b 100644 --- a/frontend/src/components/JobMatchAnalysis.tsx +++ b/frontend/src/components/JobMatchAnalysis.tsx @@ -31,6 +31,7 @@ import { StyledMarkdown } from './StyledMarkdown'; import { Scrollable } from './Scrollable'; import { start } from 'repl'; import { TypesElement } from '@uiw/react-json-view'; +import { useAppState } from 'hooks/GlobalContext'; interface JobAnalysisProps extends BackstoryPageProps { job: Job; @@ -45,11 +46,9 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = const { job, candidate, - setSnack, - submitQuery } = props const { apiClient } = useAuth(); - const backstoryProps = { setSnack, submitQuery }; + const { setSnack } = useAppState(); const theme = useTheme(); const [requirements, setRequirements] = useState<{ requirement: string, domain: string }[]>([]); const [skillMatches, setSkillMatches] = useState([]); @@ -250,7 +249,7 @@ const JobMatchAnalysis: React.FC = (props: JobAnalysisProps) = - + diff --git a/frontend/src/components/Message.tsx b/frontend/src/components/Message.tsx index 432de91..f57ee50 100644 --- a/frontend/src/components/Message.tsx +++ b/frontend/src/components/Message.tsx @@ -358,12 +358,8 @@ const MessageContainer = (props: MessageContainerProps) => { }; const Message = (props: MessageProps) => { - const { message, title, submitQuery, sx, className, chatSession, onExpand, setSnack, expanded, expandable } = props; + const { message, title, sx, className, chatSession, onExpand, expanded, expandable } = props; const [metaExpanded, setMetaExpanded] = useState(false); - const backstoryProps = { - submitQuery, - setSnack - }; const theme = useTheme(); const type: ApiActivityType | ChatSenderType | "error" = ('activity' in message) ? message.activity : ('error' in message) ? 'error' : (message as ChatMessage).role; const style: any = getStyle(theme, type); @@ -385,7 +381,7 @@ const Message = (props: MessageProps) => { }; const messageView = ( - + ); let metadataView = (<>); diff --git a/frontend/src/components/StyledMarkdown.tsx b/frontend/src/components/StyledMarkdown.tsx index cf24b57..e415d7f 100644 --- a/frontend/src/components/StyledMarkdown.tsx +++ b/frontend/src/components/StyledMarkdown.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { MuiMarkdown } from 'mui-markdown'; import { useTheme } from '@mui/material/styles'; import { Link } from '@mui/material'; -import { BackstoryQuery } from 'components/BackstoryQuery'; +import { BackstoryQuery, BackstoryQueryInterface } from 'components/BackstoryQuery'; import Box from '@mui/material/Box'; import JsonView from '@uiw/react-json-view'; import { vscodeTheme } from '@uiw/react-json-view/vscode'; @@ -13,17 +13,19 @@ import { GenerateImage } from 'components/GenerateImage'; import './StyledMarkdown.css'; import { BackstoryElementProps } from './BackstoryTab'; -import { CandidateQuestion, ChatSession } from 'types/types'; +import { CandidateQuestion, ChatQuery, ChatSession } from 'types/types'; +import { ChatSubmitQueryInterface } from 'components/BackstoryQuery'; interface StyledMarkdownProps extends BackstoryElementProps { className?: string, content: string, streaming?: boolean, chatSession?: ChatSession, + submitQuery?: ChatSubmitQueryInterface }; const StyledMarkdown: React.FC = (props: StyledMarkdownProps) => { - const { className, content, chatSession, submitQuery, sx, streaming, setSnack } = props; + const { className, content, chatSession, submitQuery, sx, streaming } = props; const theme = useTheme(); const overrides: any = { @@ -110,7 +112,7 @@ const StyledMarkdown: React.FC = (props: StyledMarkdownProp question: queryString } - return + return submitQuery ? : query.question; } catch (e) { console.log("StyledMarkdown error:", queryString, e); return props.query; @@ -124,7 +126,7 @@ const StyledMarkdown: React.FC = (props: StyledMarkdownProp component: (props: { prompt: string }) => { const prompt = props.prompt.replace(/(\w+):/g, '"$1":'); try { - return + return } catch (e) { console.log("StyledMarkdown error:", prompt, e); return props.prompt; diff --git a/frontend/src/components/VectorVisualizer.tsx b/frontend/src/components/VectorVisualizer.tsx index 7755cf8..8386d38 100644 --- a/frontend/src/components/VectorVisualizer.tsx +++ b/frontend/src/components/VectorVisualizer.tsx @@ -23,7 +23,7 @@ import './VectorVisualizer.css'; import { BackstoryPageProps } from './BackstoryTab'; import { useAuth } from 'hooks/AuthContext'; import * as Types from 'types/types'; -import { useSelectedCandidate } from 'hooks/GlobalContext'; +import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext'; import { useNavigate } from 'react-router-dom'; interface VectorVisualizerProps extends BackstoryPageProps { @@ -180,8 +180,8 @@ type Node = { const VectorVisualizer: React.FC = (props: VectorVisualizerProps) => { const { user, apiClient } = useAuth(); - const { setSnack, submitQuery, rag, inline, sx } = props; - const backstoryProps = { setSnack, submitQuery }; + const { rag, inline, sx } = props; + const { setSnack } = useAppState(); const [plotData, setPlotData] = useState(null); const [newQuery, setNewQuery] = useState(''); const [querySet, setQuerySet] = useState(rag || emptyQuerySet); diff --git a/frontend/src/components/layout/BackstoryLayout.tsx b/frontend/src/components/layout/BackstoryLayout.tsx index 0744d2e..70eb72e 100644 --- a/frontend/src/components/layout/BackstoryLayout.tsx +++ b/frontend/src/components/layout/BackstoryLayout.tsx @@ -1,168 +1,136 @@ +// components/layout/BackstoryLayout.tsx import React, { ReactElement, useEffect, useState } from 'react'; -import { Outlet, useLocation, Routes } from "react-router-dom"; +import { Outlet, useLocation, Routes, Route } from "react-router-dom"; import { Box, Container, Paper } from '@mui/material'; import { useNavigate } from "react-router-dom"; -import ChatIcon from '@mui/icons-material/Chat'; -import DashboardIcon from '@mui/icons-material/Dashboard'; -import DescriptionIcon from '@mui/icons-material/Description'; -import BarChartIcon from '@mui/icons-material/BarChart'; -import SettingsIcon from '@mui/icons-material/Settings'; -import WorkIcon from '@mui/icons-material/Work'; -import InfoIcon from '@mui/icons-material/Info'; import { SxProps, Theme } from '@mui/material'; - import { Header } from 'components/layout/Header'; import { Scrollable } from 'components/Scrollable'; import { Footer } from 'components/layout/Footer'; import { Snack, SetSnackType } from 'components/Snack'; import { User } from 'types/types'; -import { getBackstoryDynamicRoutes } from 'components/layout/BackstoryRoutes'; import { LoadingComponent } from "components/LoadingComponent"; import { AuthProvider, useAuth, ProtectedRoute } from 'hooks/AuthContext'; -import { useSelectedCandidate } from 'hooks/GlobalContext'; +import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext'; +import { + getMainNavigationItems, + getAllRoutes, +} from 'config/navigationConfig'; +import { NavigationItem } from 'types/navigation'; -type NavigationLinkType = { +// Legacy type for backward compatibility +export type NavigationLinkType = { label: ReactElement | string; - path: string; + path: string; icon?: ReactElement; }; -const DefaultNavItems: NavigationLinkType[] = [ - { label: 'Find a Candidate', path: '/find-a-candidate', icon: }, - { label: 'Docs', path: '/docs', icon: }, - // { label: 'How It Works', path: '/how-it-works', icon: }, - // { label: 'For Candidates', path: '/for-candidates', icon: }, - // { label: 'For Employers', path: '/for-employers', icon: }, - // { label: 'Pricing', path: '/pricing', icon: }, -]; - -const ViewerNavItems: NavigationLinkType[] = [ - { label: 'Chat', path: '/chat', icon: }, -]; - -const CandidateNavItems : NavigationLinkType[]= [ - { label: 'Chat', path: '/chat', icon: }, - { label: 'Job Analysis', path: '/candidate/job-analysis', icon: }, - { label: 'Resume Builder', path: '/candidate/resume-builder', icon: }, - // { label: 'Knowledge Explorer', path: '/candidate/knowledge-explorer', icon: }, - // { label: 'Dashboard', icon: , path: '/candidate/dashboard' }, - // { label: 'Profile', icon: , path: '/candidate/dashboard/profile' }, - // { label: 'Backstory', icon: , path: '/candidate/backstory' }, - // { label: 'Resumes', icon: , path: '/candidate/resumes' }, - // { label: 'Q&A Setup', icon: , path: '/candidate/qa-setup' }, - // { label: 'Analytics', icon: , path: '/candidate/analytics' }, - // { label: 'Settings', icon: , path: '/candidate/settings' }, -]; - -const EmployerNavItems: NavigationLinkType[] = [ - { label: 'Chat', path: '/chat', icon: }, - { label: 'Job Analysis', path: '/employer/job-analysis', icon: }, - { label: 'Resume Builder', path: '/employer/resume-builder', icon: }, - { label: 'Knowledge Explorer', path: '/employer/knowledge-explorer', icon: }, - { label: 'Find a Candidate', path: '/find-a-candidate', icon: }, - // { label: 'Dashboard', icon: , path: '/employer/dashboard' }, - // { label: 'Search', icon: , path: '/employer/search' }, - // { label: 'Saved', icon: , path: '/employer/saved' }, - // { label: 'Jobs', icon: , path: '/employer/jobs' }, - // { label: 'Company', icon: , path: '/employer/company' }, - // { label: 'Analytics', icon: , path: '/employer/analytics' }, - // { label: 'Settings', icon: , path: '/employer/settings' }, -]; - -// Navigation links based on user type -const getNavigationLinks = (user: User | null): NavigationLinkType[] => { - if (!user) { - return DefaultNavItems; - } - - switch (user.userType) { - case 'candidate': - return DefaultNavItems.concat(CandidateNavItems); - case 'employer': - return DefaultNavItems.concat(EmployerNavItems); - default: - return DefaultNavItems; - } -}; - interface BackstoryPageContainerProps { children?: React.ReactNode; sx?: SxProps; -}; - -const BackstoryPageContainer = (props : BackstoryPageContainerProps) => { - const { children, sx } = props; - return ( - - - - {children} - - - - ); } -interface BackstoryLayoutProps { - setSnack: SetSnackType; - page: string; - chatRef: React.Ref; - snackRef: React.Ref; - submitQuery: any; +const BackstoryPageContainer = (props: BackstoryPageContainerProps) => { + const { children, sx } = props; + return ( + + + + {children} + + + + ); }; +interface BackstoryLayoutProps { + page: string; + chatRef: React.Ref; +} + const BackstoryLayout: React.FC = (props: BackstoryLayoutProps) => { - const { setSnack, page, chatRef, snackRef, submitQuery } = props; + const { page, chatRef } = props; + const { setSnack } = useAppState(); const navigate = useNavigate(); const location = useLocation(); const { guest, user } = useAuth(); const { selectedCandidate } = useSelectedCandidate(); - const [navigationLinks, setNavigationLinks] = useState([]); + const [navigationItems, setNavigationItems] = useState([]); useEffect(() => { - setNavigationLinks(getNavigationLinks(user)); + const userType = user?.userType || null; + setNavigationItems(getMainNavigationItems(userType)); }, [user]); - let dynamicRoutes; - if (guest) { - dynamicRoutes = getBackstoryDynamicRoutes({ - user, - setSnack, - submitQuery, - chatRef - }); - } + // Generate dynamic routes from navigation config + const generateRoutes = () => { + if (!guest) return null; + + const userType = user?.userType || null; + const routes = getAllRoutes(userType); + + return routes.map((route, index) => { + if (!route.path || !route.component) return null; + + // Clone the component and pass necessary props if it's a page component + const componentWithProps = React.cloneElement(route.component as ReactElement, { + ...(route.id === 'chat' && { ref: chatRef }), + ...(route.component.props || {}), + }); + + return ( + + ); + }).filter(Boolean); + }; return ( - -
+ +
= (props: BackstoryLayoutP flexDirection: "column", backgroundColor: "#D3CDBF", /* Warm Gray */ }}> - - - {!guest && - - - - } - {guest && <> - - {dynamicRoutes !== undefined && {dynamicRoutes}} - - } - {location.pathname === "/" &&