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 { CopyBubble } from './CopyBubble';
import { useAppState } from 'hooks/GlobalContext'; import { useAppState } from 'hooks/GlobalContext';
import { StreamingOptions } from 'services/api-client'; import { StreamingOptions } from 'services/api-client';
import { Navigate, useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
interface ResumeGeneratorProps { interface ResumeGeneratorProps {
job: Job; job: Job;

View File

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

View File

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

View File

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

View File

@ -1,11 +1,9 @@
import React, { useEffect, useState } from 'react'; 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 Box from '@mui/material/Box';
import { BackstoryElementProps } from 'components/BackstoryTab'; import { BackstoryElementProps } from 'components/BackstoryTab';
import { CandidateInfo } from 'components/ui/CandidateInfo'; import { CandidateInfo } from 'components/ui/CandidateInfo';
import { Candidate, CandidateAI } from 'types/types'; import { Candidate } from 'types/types';
import { useAuth } from 'hooks/AuthContext'; import { useAuth } from 'hooks/AuthContext';
import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext'; import { useAppState, useSelectedCandidate } from 'hooks/GlobalContext';
import { Paper } from '@mui/material'; import { Paper } from '@mui/material';
@ -16,9 +14,8 @@ interface CandidatePickerProps extends BackstoryElementProps {
const CandidatePicker = (props: CandidatePickerProps) => { const CandidatePicker = (props: CandidatePickerProps) => {
const { onSelect, sx } = props; const { onSelect, sx } = props;
const { apiClient, user } = useAuth(); const { apiClient } = useAuth();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate(); const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
const navigate = useNavigate();
const { setSnack } = useAppState(); const { setSnack } = useAppState();
const [candidates, setCandidates] = useState<Candidate[] | null>(null); const [candidates, setCandidates] = useState<Candidate[] | null>(null);
@ -64,7 +61,7 @@ const CandidatePicker = (props: CandidatePickerProps) => {
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
{candidates?.map((u, i) => ( {candidates?.map(u => (
<Paper <Paper
key={`${u.username}`} key={`${u.username}`}
onClick={() => { 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 Box from '@mui/material/Box';
import useMediaQuery from '@mui/material/useMediaQuery';
import { SxProps, useTheme } from '@mui/material/styles';
import './ComingSoon.css'; import './ComingSoon.css';
@ -11,7 +9,6 @@ type ComingSoonProps = {
const ComingSoon: React.FC<ComingSoonProps> = (props: ComingSoonProps) => { const ComingSoon: React.FC<ComingSoonProps> = (props: ComingSoonProps) => {
const { children } = props; const { children } = props;
const theme = useTheme();
return ( return (
<Box className="ComingSoon"> <Box className="ComingSoon">
<Box className="ComingSoon-label">Coming Soon</Box> <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 { import {
Box, Box,
Link, Link,
Typography, Typography,
Avatar,
Grid,
SxProps, SxProps,
CardActions,
Chip, Chip,
Stack, Stack,
CardHeader, CardHeader,
Button,
styled,
LinearProgress, LinearProgress,
IconButton, IconButton,
Tooltip, Tooltip,
@ -20,11 +15,8 @@ import { Card, CardContent, Divider, useTheme } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete'; import DeleteIcon from '@mui/icons-material/Delete';
import { useMediaQuery } from '@mui/material'; import { useMediaQuery } from '@mui/material';
import { Job } from 'types/types'; import { Job } from 'types/types';
import { CopyBubble } from 'components/CopyBubble';
import { rest } from 'lodash'; import { rest } from 'lodash';
import { AIBanner } from 'components/ui/AIBanner';
import { useAuth } from 'hooks/AuthContext'; import { useAuth } from 'hooks/AuthContext';
import { DeleteConfirmation } from '../DeleteConfirmation';
import { Build, CheckCircle, Description, Psychology, Star, Work } from '@mui/icons-material'; import { Build, CheckCircle, Description, Psychology, Star, Work } from '@mui/icons-material';
import ModelTrainingIcon from '@mui/icons-material/ModelTraining'; import ModelTrainingIcon from '@mui/icons-material/ModelTraining';
import { StatusIcon, StatusBox } from 'components/ui/StatusIcon'; import { StatusIcon, StatusBox } from 'components/ui/StatusIcon';
@ -45,7 +37,7 @@ interface JobInfoProps {
const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => { const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
const { setSnack } = useAppState(); const { setSnack } = useAppState();
const { user, apiClient } = useAuth(); const { user, apiClient } = useAuth();
const { sx, action = '', elevation = 1, variant = 'normal', job } = props; const { sx, variant = 'normal', job } = props;
const theme = useTheme(); const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal'; const isMobile = useMediaQuery(theme.breakpoints.down('md')) || variant === 'minimal';
const isAdmin = user?.isAdmin; const isAdmin = user?.isAdmin;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,33 +2,17 @@ import React from 'react';
import { import {
Chat as ChatIcon, Chat as ChatIcon,
Dashboard as DashboardIcon, Dashboard as DashboardIcon,
Description as DescriptionIcon,
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,
Search as SearchIcon,
Bookmark as BookmarkIcon,
History as HistoryIcon,
QuestionAnswer as QuestionAnswerIcon,
AttachMoney as AttachMoneyIcon,
Quiz as QuizIcon,
Analytics as AnalyticsIcon,
BubbleChart, BubbleChart,
AutoFixHigh, AutoFixHigh,
} from '@mui/icons-material'; } from '@mui/icons-material';
import EditDocumentIcon from '@mui/icons-material/EditDocument'; import EditDocumentIcon from '@mui/icons-material/EditDocument';
import { BackstoryLogo } from 'components/ui/BackstoryLogo'; import { BackstoryLogo } from 'components/ui/BackstoryLogo';
import { HomePage } from 'pages/HomePage';
import { CandidateChatPage } from 'pages/CandidateChatPage'; 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 { BetaPage } from 'pages/BetaPage';
import { CandidateListingPage } from 'pages/FindCandidatePage';
import { JobAnalysisPage } from 'pages/JobAnalysisPage'; import { JobAnalysisPage } from 'pages/JobAnalysisPage';
import { GenerateCandidate } from 'pages/GenerateCandidate'; import { GenerateCandidate } from 'pages/GenerateCandidate';
import { LoginPage } from 'pages/LoginPage'; import { LoginPage } from 'pages/LoginPage';
@ -37,7 +21,6 @@ import { Box, Typography } from '@mui/material';
import { CandidateDashboard } from 'pages/candidate/Dashboard'; import { CandidateDashboard } from 'pages/candidate/Dashboard';
import { NavigationConfig, NavigationItem } from 'types/navigation'; import { NavigationConfig, NavigationItem } from 'types/navigation';
import { HowItWorks } from 'pages/HowItWorks'; import { HowItWorks } from 'pages/HowItWorks';
import SchoolIcon from '@mui/icons-material/School';
import { CandidateProfile } from 'pages/candidate/Profile'; import { CandidateProfile } from 'pages/candidate/Profile';
import { Settings } from 'pages/candidate/Settings'; import { Settings } from 'pages/candidate/Settings';
import { VectorVisualizer } from 'components/VectorVisualizer'; import { VectorVisualizer } from 'components/VectorVisualizer';
@ -45,49 +28,11 @@ import { DocumentManager } from 'components/DocumentManager';
import { useAuth } from 'hooks/AuthContext'; import { useAuth } from 'hooks/AuthContext';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { JobViewer } from 'components/ui/JobViewer'; import { JobViewer } from 'components/ui/JobViewer';
import { CandidatePicker } from 'components/ui/CandidatePicker';
import { ResumeViewer } from 'components/ui/ResumeViewer'; import { ResumeViewer } from 'components/ui/ResumeViewer';
import { JobsTable } from 'components/ui/JobsTable'; import { JobsTable } from 'components/ui/JobsTable';
import * as Types from 'types/types'; 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 LogoutPage = () => {
const { logout } = useAuth(); const { logout } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
@ -96,16 +41,6 @@ const LogoutPage = () => {
}); });
return <Typography variant="h4">Logging out...</Typography>; 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: [

View File

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

View File

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

View File

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

View File

@ -202,7 +202,7 @@ const BetaPage: React.FC<BetaPageProps> = ({
}} }}
/> />
<Typography> <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>
<Typography color="textSecondary" sx={{ mt: 1 }}> <Typography color="textSecondary" sx={{ mt: 1 }}>
Check back soon for updates. Check back soon for updates.

View File

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

View File

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

View File

@ -7,15 +7,11 @@ import {
Paper, Paper,
Typography, Typography,
Grid, Grid,
Card,
CardContent,
Chip,
Step, Step,
StepLabel, StepLabel,
Stepper, Stepper,
Stack, Stack,
ButtonProps, ButtonProps,
useMediaQuery,
useTheme, useTheme,
} from '@mui/material'; } from '@mui/material';
import { styled } from '@mui/material/styles'; 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 = [ const steps = [
'Select Job Analysis', 'Select Job Analysis',
'Choose a Job', 'Choose a Job',
@ -241,8 +227,6 @@ const HeroButton = (props: HeroButtonProps) => {
const HowItWorks: React.FC = () => { const HowItWorks: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const handleGetStarted = () => { const handleGetStarted = () => {
navigate('/job-analysis'); navigate('/job-analysis');
@ -270,7 +254,7 @@ const HowItWorks: React.FC = () => {
component="h1" component="h1"
sx={{ sx={{
fontWeight: 700, fontWeight: 700,
fontSize: { xs: '2rem', md: '3rem' }, fontSize: { xs: '1.5rem', md: '2rem' },
mb: 2, mb: 2,
color: 'white', color: 'white',
}} }}

View File

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

View File

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

View File

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

View File

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

View File

@ -811,7 +811,7 @@ export function RegistrationTypeSelector() {
Join Backstory Join Backstory
</Typography> </Typography>
<Typography variant="h6" color="text.secondary"> <Typography variant="h6" color="text.secondary">
Choose how you'd like to get started Choose how you&apos;d like to get started
</Typography> </Typography>
</Box> </Box>
@ -836,7 +836,7 @@ export function RegistrationTypeSelector() {
👤 👤
</Typography> </Typography>
<Typography variant="h5" component="h3" sx={{ mb: 1.5 }}> <Typography variant="h5" component="h3" sx={{ mb: 1.5 }}>
I'm looking for work I&apos;m looking for work
</Typography> </Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Create a candidate profile to find your next opportunity Create a candidate profile to find your next opportunity
@ -869,7 +869,7 @@ export function RegistrationTypeSelector() {
🏢 🏢
</Typography> </Typography>
<Typography variant="h5" component="h3" sx={{ mb: 1.5 }}> <Typography variant="h5" component="h3" sx={{ mb: 1.5 }}>
I'm hiring I&apos;m hiring
</Typography> </Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
Create a company account to find and hire talent 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>; return <div className="SystemInfo">{systemElements}</div>;
}; };
const Settings = (props: BackstoryPageProps) => { const Settings = (_props: BackstoryPageProps) => {
const { apiClient } = useAuth(); const { apiClient } = useAuth();
const { setSnack } = useAppState(); const { setSnack } = useAppState();
// const [editSystemPrompt, setEditSystemPrompt] = useState<string>(""); // const [editSystemPrompt, setEditSystemPrompt] = useState<string>("");

View File

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

View File

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