Compare commits

...

3 Commits

29 changed files with 125 additions and 306 deletions

View File

@ -12,7 +12,7 @@ import { StatusBox, StatusIcon } from './ui/StatusIcon';
import { CopyBubble } from './CopyBubble';
import { useAppState } from 'hooks/GlobalContext';
import { StreamingOptions } from 'services/api-client';
import { Navigate, useNavigate } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
interface ResumeGeneratorProps {
job: Job;

View File

@ -39,7 +39,7 @@ const BackstoryPageContainer = (props: BackstoryPageContainerProps): JSX.Element
flexGrow: 1,
p: '0 !important',
m: '0 auto !important',
minWidth: variant === 'normal' ? '1024px' : '100%',
// minWidth: variant === 'normal' ? '1024px' : '100%',
maxWidth: variant === 'normal' ? '1024px' : '100%',
height: '100%',
minHeight: 0,

View File

@ -1,19 +1,16 @@
// components/layout/Header.tsx
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { NavigateFunction, useLocation } from 'react-router-dom';
import {
AppBar,
Toolbar,
Tooltip,
Typography,
Button,
IconButton,
Box,
Drawer,
Divider,
Avatar,
Tabs,
Tab,
Container,
Fade,
Popover,
@ -26,25 +23,18 @@ import {
List,
ListItem,
ListItemButton,
SxProps,
} from '@mui/material';
import { styled, useTheme } from '@mui/material/styles';
import {
Menu as MenuIcon,
Dashboard,
Person,
Logout,
Settings,
ExpandMore,
ExpandLess,
KeyboardArrowDown,
} from '@mui/icons-material';
import FaceRetouchingNaturalIcon from '@mui/icons-material/FaceRetouchingNatural';
import { getUserMenuItemsByGroup } from 'config/navigationConfig';
import { NavigationItem } from 'types/navigation';
import { Beta } from 'components/ui/Beta';
import { Candidate, Employer } from 'types/types';
import { SetSnackType } from 'components/Snack';
import { CopyBubble } from 'components/CopyBubble';
import 'components/layout/Header.css';
@ -181,7 +171,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
id: 'divider',
label: '',
icon: null,
action: () => {},
action: () => {
console.log('Divider clicked');
},
group: 'divider',
});
}
@ -193,7 +185,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
id: item.id,
label: item.label as string,
icon: item.icon || null,
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
action: () => {
item.path && navigate(item.path.replace(/:.*$/, ''));
},
group: 'account',
});
}
@ -205,7 +199,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
id: 'divider',
label: '',
icon: null,
action: () => {},
action: () => {
console.log('Divider clicked');
},
group: 'divider',
});
}
@ -217,7 +213,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
id: item.id,
label: item.label as string,
icon: item.icon || null,
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
action: () => {
item.path && navigate(item.path.replace(/:.*$/, ''));
},
group: 'admin',
});
}
@ -229,7 +227,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
id: 'divider',
label: '',
icon: null,
action: () => {},
action: () => {
console.log('Divider clicked');
},
group: 'divider',
});
}
@ -252,7 +252,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
id: item.id,
label: item.label as string,
icon: item.icon || null,
action: () => item.path && navigate(item.path.replace(/:.*$/, '')),
action: () => {
item.path && navigate(item.path.replace(/:.*$/, ''));
},
group: 'system',
});
}

View File

@ -1,6 +1,6 @@
import React, { useState, useRef, useEffect } from 'react';
import { Box, Link, Typography, Avatar, Grid, SxProps, Tooltip, IconButton } from '@mui/material';
import { Card, CardContent, Divider, useTheme } from '@mui/material';
import { Box, Link, Typography, Avatar, SxProps, Tooltip, IconButton } from '@mui/material';
import { Divider, useTheme } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import { useMediaQuery } from '@mui/material';
import { Candidate, CandidateAI } from 'types/types';
@ -8,7 +8,6 @@ import { CopyBubble } from 'components/CopyBubble';
import { rest } from 'lodash';
import { AIBanner } from 'components/ui/AIBanner';
import { useAuth } from 'hooks/AuthContext';
import { DeleteConfirmation } from '../DeleteConfirmation';
interface CandidateInfoProps {
candidate: Candidate;
@ -21,7 +20,7 @@ interface CandidateInfoProps {
const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps) => {
const { candidate } = props;
const { user, apiClient } = useAuth();
const { sx, action = '', elevation = 1, variant = 'normal' } = props;
const { sx, action = '', variant = 'normal' } = props;
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal';
const ai: CandidateAI | null = 'isAI' in candidate ? (candidate as CandidateAI) : null;

View File

@ -1,11 +1,9 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import { BackstoryElementProps } from 'components/BackstoryTab';
import { CandidateInfo } from 'components/ui/CandidateInfo';
import { Candidate, CandidateAI } from 'types/types';
import { Candidate } from 'types/types';
import { useAuth } from 'hooks/AuthContext';
import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
import { Paper } from '@mui/material';
@ -16,9 +14,8 @@ interface CandidatePickerProps extends BackstoryElementProps {
const CandidatePicker = (props: CandidatePickerProps) => {
const { onSelect, sx } = props;
const { apiClient, user } = useAuth();
const { apiClient } = useAuth();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
const navigate = useNavigate();
const { setSnack } = useAppState();
const [candidates, setCandidates] = useState<Candidate[] | null>(null);
@ -64,7 +61,7 @@ const CandidatePicker = (props: CandidatePickerProps) => {
justifyContent: 'center',
}}
>
{candidates?.map((u, i) => (
{candidates?.map(u => (
<Paper
key={`${u.username}`}
onClick={() => {

View File

@ -1,7 +1,5 @@
import React, { useEffect, useRef, useState } from 'react';
import React from 'react';
import Box from '@mui/material/Box';
import useMediaQuery from '@mui/material/useMediaQuery';
import { SxProps, useTheme } from '@mui/material/styles';
import './ComingSoon.css';
@ -11,7 +9,6 @@ type ComingSoonProps = {
const ComingSoon: React.FC<ComingSoonProps> = (props: ComingSoonProps) => {
const { children } = props;
const theme = useTheme();
return (
<Box className="ComingSoon">
<Box className="ComingSoon-label">Coming Soon</Box>

View File

@ -1,17 +1,12 @@
import React, { JSX, useActionState, useEffect, useRef, useState } from 'react';
import React, { JSX, useEffect, useRef, useState } from 'react';
import {
Box,
Link,
Typography,
Avatar,
Grid,
SxProps,
CardActions,
Chip,
Stack,
CardHeader,
Button,
styled,
LinearProgress,
IconButton,
Tooltip,
@ -20,11 +15,8 @@ import { Card, CardContent, Divider, useTheme } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import { useMediaQuery } from '@mui/material';
import { Job } from 'types/types';
import { CopyBubble } from 'components/CopyBubble';
import { rest } from 'lodash';
import { AIBanner } from 'components/ui/AIBanner';
import { useAuth } from 'hooks/AuthContext';
import { DeleteConfirmation } from '../DeleteConfirmation';
import { Build, CheckCircle, Description, Psychology, Star, Work } from '@mui/icons-material';
import ModelTrainingIcon from '@mui/icons-material/ModelTraining';
import { StatusIcon, StatusBox } from 'components/ui/StatusIcon';
@ -45,7 +37,7 @@ interface JobInfoProps {
const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
const { setSnack } = useAppState();
const { user, apiClient } = useAuth();
const { sx, action = '', elevation = 1, variant = 'normal', job } = props;
const { sx, variant = 'normal', job } = props;
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal';
const isAdmin = user?.isAdmin;

View File

@ -1,6 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import Button from '@mui/material/Button';
import Box from '@mui/material/Box';
import { BackstoryElementProps } from 'components/BackstoryTab';
@ -17,7 +15,7 @@ interface JobPickerProps extends BackstoryElementProps {
const JobPicker = (props: JobPickerProps) => {
const { onSelect } = props;
const { apiClient } = useAuth();
const { selectedJob, setSelectedJob } = useSelectedJob();
const { selectedJob } = useSelectedJob();
const { setSnack } = useAppState();
const [jobs, setJobs] = useState<Job[] | null>(null);
@ -55,7 +53,7 @@ const JobPicker = (props: JobPickerProps) => {
justifyContent: 'center',
}}
>
{jobs?.map((j, i) => (
{jobs?.map(j => (
<Paper
key={`${j.id}`}
onClick={() => {

View File

@ -28,7 +28,6 @@ import {
Business as BusinessIcon,
Work as WorkIcon,
Schedule as ScheduleIcon,
Close as CloseIcon,
ArrowBack as ArrowBackIcon,
} from '@mui/icons-material';
import { TransitionProps } from '@mui/material/transitions';
@ -36,7 +35,7 @@ import { JobInfo } from 'components/ui/JobInfo';
import { Job } from 'types/types';
import { useAuth } from 'hooks/AuthContext';
import { useAppState, useSelectedJob } from 'hooks/GlobalContext';
import { Navigate, useNavigate, useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
type SortField = 'updatedAt' | 'createdAt' | 'company' | 'title';
type SortOrder = 'asc' | 'desc';
@ -71,8 +70,8 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
const { jobId } = useParams<{ jobId?: string }>();
useEffect(() => {
if (loading) return; // Prevent multiple calls
const getJobs = async () => {
setLoading(true);
try {
const results = await apiClient.getJobs();
const jobsData: Job[] = results.data || [];
@ -101,8 +100,9 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
}
};
setLoading(true);
getJobs();
}, [apiClient, setSnack]);
}, [apiClient, setSnack, loading]);
const sortJobs = (jobsList: Job[], field: SortField, order: SortOrder): Job[] => {
return [...jobsList].sort((a, b) => {

View File

@ -59,7 +59,7 @@ const JobsTable: React.FC<JobsTableProps> = ({
// Fetch jobs from API
const fetchJobs = React.useCallback(
async (pageNum: number = 0, searchTerm: string = '') => {
async (pageNum = 0, searchTerm = '') => {
try {
setLoading(true);
setError(null);
@ -71,7 +71,6 @@ const JobsTable: React.FC<JobsTableProps> = ({
};
let paginationResponse: Types.PaginatedResponse;
let url = `/api/1.0/jobs`;
if (searchTerm.trim()) {
paginationResponse = await apiClient.searchJobs(searchTerm);
} else {
@ -152,17 +151,12 @@ const JobsTable: React.FC<JobsTableProps> = ({
onJobSelect?.(selectedJobsList);
};
// Utility functions
const formatDate = (dateString: string) => {
return new Date(dateString).toLocaleDateString();
};
const getOwnerName = (owner?: Types.Job['owner']) => {
if (!owner) return 'Unknown';
return `${owner.firstName || ''} ${owner.lastName || ''}`.trim() || owner.email || 'Unknown';
};
const truncateDescription = (description: string, maxLength: number = 100) => {
const truncateDescription = (description: string, maxLength = 100) => {
if (description.length <= maxLength) return description;
return description.substring(0, maxLength) + '...';
};

View File

@ -1,3 +1,4 @@
import React from 'react';
import Box from '@mui/material/Box';
import './LoginRestricted.css';

View File

@ -1,13 +1,9 @@
import React, { useEffect, useRef, useState } from 'react';
import {
Box,
Link,
Typography,
Avatar,
Grid,
SxProps,
CardActions,
Chip,
Stack,
CardHeader,
Button,
@ -19,7 +15,6 @@ import {
Divider,
useTheme,
useMediaQuery,
TextField,
Dialog,
DialogTitle,
DialogContent,
@ -38,8 +33,6 @@ import {
Work as WorkIcon,
Person as PersonIcon,
Schedule as ScheduleIcon,
Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon,
ModelTraining,
} from '@mui/icons-material';
import InputIcon from '@mui/icons-material/Input';
@ -73,14 +66,13 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
const { setSnack } = useAppState();
const { resume } = props;
const { user, apiClient } = useAuth();
const { sx, action = '', elevation = 1, variant = 'normal' } = props;
const { sx, variant = 'normal' } = props;
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal';
const isAdmin = user?.isAdmin;
const [activeResume, setActiveResume] = useState<Resume>({ ...resume });
const [deleted, setDeleted] = useState<boolean>(false);
const [editDialogOpen, setEditDialogOpen] = useState<boolean>(false);
const [printDialogOpen, setPrintDialogOpen] = useState<boolean>(false);
const [editContent, setEditContent] = useState<string>('');
const [editSystemPrompt, setEditSystemPrompt] = useState<string>('');
const [editPrompt, setEditPrompt] = useState<string>('');
@ -437,32 +429,12 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
</Box>
)}
{/* Print Dialog */}
<Dialog
open={printDialogOpen}
onClose={() => {}} //setPrintDialogOpen(false)}
maxWidth="lg"
fullWidth
fullScreen={true}
>
<StyledMarkdown
content={activeResume.resume}
sx={{
position: 'relative',
maxHeight: '100%',
width: '100%',
display: 'flex',
flexGrow: 1,
flex: 1 /* Take remaining space in some-container */,
overflowY: 'auto' /* Scroll if content overflows */,
}}
/>
</Dialog>
{/* Edit Dialog */}
<Dialog
open={editDialogOpen}
onClose={() => setEditDialogOpen(false)}
onClose={() => {
setEditDialogOpen(false);
}}
maxWidth="lg"
fullWidth
disableEscapeKeyDown={true}

View File

@ -13,7 +13,6 @@ import {
Select,
MenuItem,
InputLabel,
Chip,
IconButton,
Dialog,
AppBar,
@ -27,11 +26,9 @@ import {
import {
KeyboardArrowUp as ArrowUpIcon,
KeyboardArrowDown as ArrowDownIcon,
Description as DescriptionIcon,
Work as WorkIcon,
Person as PersonIcon,
Schedule as ScheduleIcon,
Close as CloseIcon,
ArrowBack as ArrowBackIcon,
Search as SearchIcon,
Clear as ClearIcon,
@ -40,7 +37,7 @@ import { TransitionProps } from '@mui/material/transitions';
import { ResumeInfo } from 'components/ui/ResumeInfo';
import { useAuth } from 'hooks/AuthContext';
import { useAppState, useSelectedResume } from 'hooks/GlobalContext'; // Assuming similar context exists
import { Navigate, useNavigate, useParams } from 'react-router-dom';
import { useNavigate, useParams } from 'react-router-dom';
import { Resume } from 'types/types';
type SortField = 'updatedAt' | 'createdAt' | 'candidateId' | 'jobId';
@ -71,7 +68,6 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
const { selectedResume, setSelectedResume } = useSelectedResume(); // Assuming similar context
const { setSnack } = useAppState();
const [resumes, setResumes] = useState<Resume[]>([]);
const [loading, setLoading] = useState(false);
const [sortField, setSortField] = useState<SortField>('updatedAt');
const [sortOrder, setSortOrder] = useState<SortOrder>('desc');
const [mobileDialogOpen, setMobileDialogOpen] = useState(false);
@ -81,7 +77,6 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
useEffect(() => {
const getResumes = async () => {
setLoading(true);
try {
let results;
@ -115,8 +110,6 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
} catch (err) {
console.error('Failed to load resumes:', err);
setSnack('Failed to load resumes: ' + err, 'error');
} finally {
setLoading(false);
}
};

View File

@ -2,33 +2,17 @@ import React from 'react';
import {
Chat as ChatIcon,
Dashboard as DashboardIcon,
Description as DescriptionIcon,
BarChart as BarChartIcon,
Settings as SettingsIcon,
Work as WorkIcon,
Info as InfoIcon,
Person as PersonIcon,
Business as BusinessIcon,
Search as SearchIcon,
Bookmark as BookmarkIcon,
History as HistoryIcon,
QuestionAnswer as QuestionAnswerIcon,
AttachMoney as AttachMoneyIcon,
Quiz as QuizIcon,
Analytics as AnalyticsIcon,
BubbleChart,
AutoFixHigh,
} from '@mui/icons-material';
import EditDocumentIcon from '@mui/icons-material/EditDocument';
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';
@ -37,7 +21,6 @@ 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';
@ -45,49 +28,11 @@ import { DocumentManager } from 'components/DocumentManager';
import { useAuth } from 'hooks/AuthContext';
import { useNavigate } from 'react-router-dom';
import { JobViewer } from 'components/ui/JobViewer';
import { CandidatePicker } from 'components/ui/CandidatePicker';
import { ResumeViewer } from 'components/ui/ResumeViewer';
import { JobsTable } from 'components/ui/JobsTable';
import * as Types from 'types/types';
// Beta page components for placeholder routes
const BackstoryPage = () => (
<BetaPage>
<Typography variant="h4">Backstory</Typography>
</BetaPage>
);
const ResumesPage = () => (
<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();
@ -96,16 +41,6 @@ const LogoutPage = () => {
});
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 = {
items: [

View File

@ -2,12 +2,7 @@
import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react';
import * as Types from '../types/types';
import {
ApiClient,
CreateCandidateRequest,
CreateEmployerRequest,
GuestConversionRequest,
} from 'services/api-client';
import { ApiClient, CreateEmployerRequest, GuestConversionRequest } from 'services/api-client';
import { formatApiRequest, toCamelCase } from 'types/conversion';
// ============================

View File

@ -365,7 +365,7 @@ export function useAppStateLogic(): AppStateContextType {
console.log('Cleared all route state');
}, []);
const emptySetSnack: SetSnackType = (message: string, severity?: SeverityType) => {
const emptySetSnack: SetSnackType = (_message: string, _severity?: SeverityType) => {
return;
};

View File

@ -68,10 +68,10 @@ const useResizeObserverAndMutationObserver = (
requestAnimationFrame(() => callbackRef.current());
}, 500);
const resizeObserver = new ResizeObserver((e: any) => {
const resizeObserver = new ResizeObserver((_e: any) => {
debouncedCallback('resize');
});
const mutationObserver = new MutationObserver((e: any) => {
const mutationObserver = new MutationObserver((_e: any) => {
debouncedCallback('mutation');
});

View File

@ -202,7 +202,7 @@ const BetaPage: React.FC<BetaPageProps> = ({
}}
/>
<Typography>
We're working hard to bring you this exciting new feature!
We&apos;re working hard to bring you this exciting new feature!
</Typography>
<Typography color="textSecondary" sx={{ mt: 1 }}>
Check back soon for updates.

View File

@ -1,5 +1,5 @@
import React, { forwardRef, useState, useEffect, useRef } from 'react';
import { Box, Paper, Button, Divider, useTheme, useMediaQuery, Tooltip } from '@mui/material';
import { Box, Paper, Button, Tooltip } from '@mui/material';
import { Send as SendIcon } from '@mui/icons-material';
import { useAuth } from 'hooks/AuthContext';
import {
@ -15,7 +15,6 @@ import { BackstoryPageProps } from 'components/BackstoryTab';
import { Message } from 'components/Message';
import { DeleteConfirmation } from 'components/DeleteConfirmation';
import { CandidateInfo } from 'components/ui/CandidateInfo';
import { useNavigate } from 'react-router-dom';
import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
import PropagateLoader from 'react-spinners/PropagateLoader';
import { BackstoryTextField, BackstoryTextFieldRef } from 'components/BackstoryTextField';
@ -34,11 +33,9 @@ const defaultMessage: ChatMessage = {
};
const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
(props: BackstoryPageProps, ref) => {
(_props: BackstoryPageProps, ref) => {
const { apiClient } = useAuth();
const navigate = useNavigate();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
const theme = useTheme();
const [processingMessage, setProcessingMessage] = useState<
ChatMessageStatus | ChatMessageError | null
>(null);
@ -124,11 +121,9 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
},
onError: (error: string | ChatMessageError) => {
console.log('onError:', error);
let message: string;
// Type-guard to determine if this is a ChatMessageBase or a string
if (typeof error === 'object' && error !== null && 'content' in error) {
setProcessingMessage(error);
message = error.content as string;
} else {
setProcessingMessage({
...defaultMessage,
@ -300,7 +295,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
</Scrollable>
)}
{selectedCandidate.questions?.length !== 0 &&
selectedCandidate.questions?.map(q => <BackstoryQuery question={q} />)}
selectedCandidate.questions?.map((q, i) => <BackstoryQuery key={i} question={q} />)}
{/* Fixed Message Input */}
<Box sx={{ display: 'flex', flexShrink: 1, gap: 1 }}>
<DeleteConfirmation
@ -346,5 +341,5 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
);
}
);
CandidateChatPage.displayName = 'CandidateChatPage';
export { CandidateChatPage };

View File

@ -39,7 +39,7 @@ const defaultMessage: ChatMessage = {
metadata: null as any,
};
const GenerateCandidate = (props: BackstoryElementProps) => {
const GenerateCandidate = (_props: BackstoryElementProps) => {
const { apiClient, user } = useAuth();
const { setSnack } = useAppState();
const [processingMessage, setProcessingMessage] = useState<ChatMessage | null>(null);

View File

@ -7,15 +7,11 @@ import {
Paper,
Typography,
Grid,
Card,
CardContent,
Chip,
Step,
StepLabel,
Stepper,
Stack,
ButtonProps,
useMediaQuery,
useTheme,
} from '@mui/material';
import { styled } from '@mui/material/styles';
@ -80,16 +76,6 @@ const ImageContainer = styled(Box)(({ theme }) => ({
},
}));
const StepCard = styled(Card)(({ theme }) => ({
height: '100%',
display: 'flex',
flexDirection: 'column',
border: `1px solid ${theme.palette.action.active}`,
'&:hover': {
boxShadow: theme.shadows[4],
},
}));
const steps = [
'Select Job Analysis',
'Choose a Job',
@ -241,8 +227,6 @@ const HeroButton = (props: HeroButtonProps) => {
const HowItWorks: React.FC = () => {
const navigate = useNavigate();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleGetStarted = () => {
navigate('/job-analysis');
@ -270,7 +254,7 @@ const HowItWorks: React.FC = () => {
component="h1"
sx={{
fontWeight: 700,
fontSize: { xs: '2rem', md: '3rem' },
fontSize: { xs: '1.5rem', md: '2rem' },
mb: 2,
color: 'white',
}}

View File

@ -5,7 +5,6 @@ import {
Step,
StepLabel,
Button,
Typography,
Paper,
useTheme,
Snackbar,
@ -14,7 +13,6 @@ import {
Tab,
Avatar,
useMediaQuery,
Divider,
} from '@mui/material';
import { Add, WorkOutline } from '@mui/icons-material';
import PersonIcon from '@mui/icons-material/Person';
@ -22,19 +20,15 @@ import WorkIcon from '@mui/icons-material/Work';
import AssessmentIcon from '@mui/icons-material/Assessment';
import { JobMatchAnalysis } from 'components/JobMatchAnalysis';
import { Candidate, Job, SkillAssessment } from 'types/types';
import { useNavigate } from 'react-router-dom';
import { BackstoryPageProps } from 'components/BackstoryTab';
import { useAuth } from 'hooks/AuthContext';
import { useAppState, useSelectedCandidate, useSelectedJob } from 'hooks/GlobalContext';
import { useSelectedCandidate, useSelectedJob } from 'hooks/GlobalContext';
import { CandidateInfo } from 'components/ui/CandidateInfo';
import { ComingSoon } from 'components/ui/ComingSoon';
import { LoginRequired } from 'components/ui/LoginRequired';
import { Scrollable } from 'components/Scrollable';
import { CandidatePicker } from 'components/ui/CandidatePicker';
import { JobPicker } from 'components/ui/JobPicker';
import { JobCreator } from 'components/JobCreator';
import { LoginRestricted } from 'components/ui/LoginRestricted';
import JsonView from '@uiw/react-json-view';
import { ResumeGenerator } from 'components/ResumeGenerator';
import { JobInfo } from 'components/ui/JobInfo';
@ -110,7 +104,7 @@ const capitalize = (str: string) => {
};
// Main component
const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProps) => {
const theme = useTheme();
const { user, guest } = useAuth();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
@ -329,7 +323,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps
<Paper elevation={4} sx={{ m: 0, borderRadius: 0, mb: 1, p: 0, gap: 1 }}>
<Stepper activeStep={activeStep.index} alternativeLabel sx={{ mt: 2, mb: 2 }}>
{steps.map((step, index) => (
<Step>
<Step key={step.index}>
<StepLabel
sx={{ cursor: 'pointer' }}
onClick={() => {

View File

@ -1,7 +1,6 @@
import React, { useState, useEffect } from 'react';
import {
Box,
Container,
Paper,
Typography,
Alert,
@ -25,17 +24,14 @@ import { LoginForm } from 'components/EmailVerificationComponents';
import { CandidateRegistrationForm } from 'pages/candidate/RegistrationForms';
import { useNavigate, useParams } from 'react-router-dom';
import { useAppState } from 'hooks/GlobalContext';
import * as Types from 'types/types';
const LoginPage: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
const LoginPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProps) => {
const navigate = useNavigate();
const { setSnack } = useAppState();
const [tabValue, setTabValue] = useState<string>('login');
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState<string | null>(null);
const { guest, user, login, isLoading, error } = useAuth();
const name =
user?.userType === 'candidate' ? (user as Types.Candidate).username : user?.email || '';
const { guest, user, error } = useAuth();
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));

View File

@ -17,7 +17,7 @@ import { useAppState } from 'hooks/GlobalContext';
type CandidateDashboardProps = BackstoryElementProps;
const CandidateDashboard = (props: CandidateDashboardProps) => {
const CandidateDashboard = (_props: CandidateDashboardProps) => {
const { setSnack } = useAppState();
const navigate = useNavigate();
const { user } = useAuth();

View File

@ -1,5 +1,4 @@
import React, { useState, useEffect } from 'react';
import CheckIcon from '@mui/icons-material/Check';
import {
Box,
Button,
@ -29,8 +28,6 @@ import {
InputLabel,
Switch,
FormControlLabel,
ToggleButton,
Checkbox,
} from '@mui/material';
import { styled } from '@mui/material/styles';
import {
@ -100,12 +97,11 @@ function TabPanel(props: TabPanelProps) {
);
}
const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProps) => {
const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPageProps) => {
const { setSnack } = useAppState();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('sm'));
const { user, updateUserData, apiClient } = useAuth();
const [isPublic, setIsPublic] = React.useState(true);
// Check if user is a candidate
const candidate = user?.userType === 'candidate' ? (user as Types.Candidate) : null;
@ -427,24 +423,11 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
</Box>
<Box className="entry">
{false && editMode.basic ? (
<TextField
fullWidth
label="Email"
type="email"
value={formData.email || ''}
onChange={e => handleInputChange('email', e.target.value)}
variant="outlined"
/>
) : (
<>
<Box className="title">
<Email sx={{ mr: 1, verticalAlign: 'middle' }} />
Email
</Box>
<Box className="value">{candidate.email}</Box>
</>
)}
</Box>
<Box className="entry">
@ -487,22 +470,6 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
</Box>
<Box className="entry">
{false && editMode.basic ? (
<TextField
fullWidth
label="Location"
value={formData.location?.city || ''}
onChange={e =>
handleInputChange('location', {
...formData.location,
city: e.target.value,
})
}
variant="outlined"
placeholder="City, State, Country"
/>
) : (
<>
<Box className="title">
<LocationOn sx={{ mr: 1, verticalAlign: 'middle' }} />
Location
@ -510,8 +477,6 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
<Box className="value">
{candidate.location?.city || 'Not specified'} {candidate.location?.country || ''}
</Box>
</>
)}
</Box>
<Box className="entry">
@ -655,7 +620,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
{(!formData.skills || formData.skills.length === 0) && (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 4 }}>
No skills added yet. Click "Add Skill" to get started.
No skills added yet. Click &quot;Add Skill&quot; to get started.
</Typography>
)}
</Box>
@ -781,7 +746,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
fontSize: { xs: '0.8rem', sm: '0.875rem' },
}}
>
No work experience added yet. Click "Add Experience" to get started.
No work experience added yet. Click &quot;Add Experience&quot; to get started.
</Typography>
)}
</Box>
@ -820,7 +785,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (props: BackstoryPageProp
fontSize: { xs: '0.8rem', sm: '0.875rem' },
}}
>
No work experience added yet. Click "Add Experience" to get started.
No work experience added yet. Click &quot;Add Experience&quot; to get started.
</Typography>
)}
</Box>

View File

@ -811,7 +811,7 @@ export function RegistrationTypeSelector() {
Join Backstory
</Typography>
<Typography variant="h6" color="text.secondary">
Choose how you'd like to get started
Choose how you&apos;d like to get started
</Typography>
</Box>
@ -836,7 +836,7 @@ export function RegistrationTypeSelector() {
👤
</Typography>
<Typography variant="h5" component="h3" sx={{ mb: 1.5 }}>
I'm looking for work
I&apos;m looking for work
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Create a candidate profile to find your next opportunity
@ -869,7 +869,7 @@ export function RegistrationTypeSelector() {
🏢
</Typography>
<Typography variant="h5" component="h3" sx={{ mb: 1.5 }}>
I'm hiring
I&apos;m hiring
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Create a company account to find and hire talent

View File

@ -83,7 +83,7 @@ const SystemInfoComponent: React.FC<{
return <div className="SystemInfo">{systemElements}</div>;
};
const Settings = (props: BackstoryPageProps) => {
const Settings = (_props: BackstoryPageProps) => {
const { apiClient } = useAuth();
const { setSnack } = useAppState();
// const [editSystemPrompt, setEditSystemPrompt] = useState<string>("");

View File

@ -2,7 +2,6 @@ import React, { useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { Box } from '@mui/material';
import { SetSnackType } from '../components/Snack';
import { LoadingComponent } from '../components/LoadingComponent';
import { User, Guest, Candidate } from 'types/types';
import { useAuth } from 'hooks/AuthContext';
@ -13,7 +12,7 @@ interface CandidateRouteProps {
user?: User | null;
}
const CandidateRoute: React.FC<CandidateRouteProps> = (props: CandidateRouteProps) => {
const CandidateRoute: React.FC<CandidateRouteProps> = (_props: CandidateRouteProps) => {
const { apiClient } = useAuth();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
const { setSnack } = useAppState();

View File

@ -1442,7 +1442,8 @@ class ApiClient {
const messageId = '';
let finalMessage: T | null = null;
const promise = new Promise<T>(async (resolve, reject) => {
const processStream = async (): Promise<T> => {
try {
const response = await fetch(`${this.baseUrl}${api}`, {
method,
@ -1467,12 +1468,15 @@ class ApiClient {
const decoder = new TextDecoder();
let buffer = '';
let streamingMessage: Types.ChatMessageStreaming | null = null;
try {
while (true) {
let exit = false;
while (!exit) {
const { done, value } = await reader.read();
if (done) {
// Stream ended naturally
exit = true;
break;
}
@ -1493,6 +1497,7 @@ class ApiClient {
// Handle different status types
switch (incoming.status) {
case 'streaming':
{
const streaming = Types.convertChatMessageStreamingFromApi(incoming);
if (streamingMessage === null) {
streamingMessage = { ...streaming };
@ -1504,19 +1509,25 @@ class ApiClient {
streamingMessage.timestamp = streaming.timestamp;
}
options.onStreaming?.(streamingMessage);
}
break;
case 'status':
{
const status = Types.convertChatMessageStatusFromApi(incoming);
options.onStatus?.(status);
}
break;
case 'error':
{
const error = Types.convertChatMessageErrorFromApi(incoming);
options.onError?.(error);
}
break;
case 'done':
{
const message = (
modelType
? convertFromApi<T>(parseApiResponse<T>(incoming), modelType)
@ -1528,6 +1539,7 @@ class ApiClient {
} catch (error) {
console.error('onMessage handler failed: ', error);
}
}
break;
}
}
@ -1536,7 +1548,6 @@ class ApiClient {
if (error instanceof Error) {
options.onWarn?.(error.message);
}
// Continue processing other lines
}
}
}
@ -1545,11 +1556,11 @@ class ApiClient {
}
options.onComplete?.();
resolve(finalMessage as T);
return finalMessage as T;
} catch (error) {
if (signal.aborted) {
options.onComplete?.();
reject(new Error('Request was aborted'));
throw new Error('Request was aborted');
} else {
console.error(error);
options.onError?.({
@ -1559,15 +1570,15 @@ class ApiClient {
content: (error as Error).message,
});
options.onComplete?.();
reject(error);
throw error;
}
}
});
};
return {
messageId,
cancel: () => abortController.abort(),
promise,
promise: processStream(),
};
}