Compare commits

...

2 Commits

Author SHA1 Message Date
ff3e4605a1 Fixed all eslint and prettier issues 2025-06-20 13:56:07 -07:00
2b1fbf2eaf Reduced font size in hero 2025-06-20 12:44:57 -07:00
33 changed files with 713 additions and 380 deletions

View File

@ -1,4 +1,6 @@
node_modules node_modules
dist dist
build build
coverage coverage
src/types/*
src/services/*

View File

@ -49,6 +49,7 @@
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"source-map-explorer": "^2.5.3",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
@ -7664,6 +7665,17 @@
"node-int64": "^0.4.0" "node-int64": "^0.4.0"
} }
}, },
"node_modules/btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
"bin": {
"btoa": "bin/btoa.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/buffer-from": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -21945,6 +21957,95 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/source-map-explorer": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/source-map-explorer/-/source-map-explorer-2.5.3.tgz",
"integrity": "sha512-qfUGs7UHsOBE5p/lGfQdaAj/5U/GWYBw2imEpD6UQNkqElYonkow8t+HBL1qqIl3CuGZx7n8/CQo4x1HwSHhsg==",
"dependencies": {
"btoa": "^1.2.1",
"chalk": "^4.1.0",
"convert-source-map": "^1.7.0",
"ejs": "^3.1.5",
"escape-html": "^1.0.3",
"glob": "^7.1.6",
"gzip-size": "^6.0.0",
"lodash": "^4.17.20",
"open": "^7.3.1",
"source-map": "^0.7.4",
"temp": "^0.9.4",
"yargs": "^16.2.0"
},
"bin": {
"sme": "bin/cli.js",
"source-map-explorer": "bin/cli.js"
},
"engines": {
"node": ">=12"
}
},
"node_modules/source-map-explorer/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/source-map-explorer/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map-explorer/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/source-map-explorer/node_modules/open": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
"dependencies": {
"is-docker": "^2.0.0",
"is-wsl": "^2.1.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/source-map-explorer/node_modules/source-map": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
"integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
"engines": {
"node": ">= 8"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -22967,6 +23068,18 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/temp": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz",
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
"dependencies": {
"mkdirp": "^0.5.1",
"rimraf": "~2.6.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/temp-dir": { "node_modules/temp-dir": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
@ -22975,6 +23088,58 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/temp/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/temp/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/temp/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/temp/node_modules/rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/tempy": { "node_modules/tempy": {
"version": "0.6.0", "version": "0.6.0",
"resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz",

View File

@ -44,10 +44,12 @@
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"remark-gfm": "^4.0.1", "remark-gfm": "^4.0.1",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"source-map-explorer": "^2.5.3",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"web-vitals": "^2.1.4" "web-vitals": "^2.1.4"
}, },
"scripts": { "scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "WDS_SOCKET_HOST=backstory-beta.ketrenos.com WDS_SOCKET_PORT=443 craco start", "start": "WDS_SOCKET_HOST=backstory-beta.ketrenos.com WDS_SOCKET_PORT=443 craco start",
"build": "craco build", "build": "craco build",
"test": "craco test", "test": "craco test",

View File

@ -135,7 +135,7 @@ const ResumeGenerator: React.FC<ResumeGeneratorProps> = (props: ResumeGeneratorP
console.error('Error saving resume:', error); console.error('Error saving resume:', error);
setSnack('Error saving resume.'); setSnack('Error saving resume.');
} }
}, [apiClient, candidate.id, job.id, resume, setSnack]); }, [apiClient, candidate.id, job.id, resume, setSnack, navigate, prompt, systemPrompt]);
return ( return (
<Box <Box

View File

@ -40,7 +40,7 @@ const BackstoryPageContainer = (props: BackstoryPageContainerProps): JSX.Element
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% !important',
height: '100%', height: '100%',
minHeight: 0, minHeight: 0,
...sx, ...sx,
@ -116,7 +116,10 @@ const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutP
.filter(Boolean); .filter(Boolean);
}; };
const findMatchingRoute = (currentPath: string, routes: any[]) => { const findMatchingRoute = (
currentPath: string,
routes: NavigationItem[]
): null | NavigationItem => {
for (const route of routes) { for (const route of routes) {
if (!route.path) continue; if (!route.path) continue;
@ -124,7 +127,7 @@ const BackstoryLayout: React.FC<BackstoryLayoutProps> = (props: BackstoryLayoutP
const match = matchPath( const match = matchPath(
{ {
path: route.path, path: route.path,
exact: true, end: true,
caseSensitive: false, caseSensitive: false,
}, },
currentPath currentPath

View File

@ -1,5 +1,5 @@
// components/layout/Header.tsx // components/layout/Header.tsx
import React, { useState } from 'react'; import React, { JSX, useState } from 'react';
import { NavigateFunction, useLocation } from 'react-router-dom'; import { NavigateFunction, useLocation } from 'react-router-dom';
import { import {
AppBar, AppBar,
@ -113,6 +113,14 @@ interface HeaderProps {
sessionId?: string | null; sessionId?: string | null;
} }
type MenuItem = {
id: string;
label: string;
icon: React.ReactElement | null;
action: () => void;
group?: string;
};
const Header: React.FC<HeaderProps> = (props: HeaderProps) => { const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
const { user, logout } = useAuth(); const { user, logout } = useAuth();
const { setSnack } = useAppState(); const { setSnack } = useAppState();
@ -143,14 +151,8 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
const userMenuGroups = getUserMenuItemsByGroup(user?.userType || null, isAdmin); const userMenuGroups = getUserMenuItemsByGroup(user?.userType || null, isAdmin);
// Create user menu items array with proper actions // Create user menu items array with proper actions
const createUserMenuItems = () => { const createUserMenuItems = (): MenuItem[] => {
const items: Array<{ const items: Array<MenuItem> = [];
id: string;
label: string;
icon: React.ReactElement | null;
action: () => void;
group?: string;
}> = [];
// Add profile group items // Add profile group items
userMenuGroups.profile.forEach(item => { userMenuGroups.profile.forEach(item => {
@ -294,28 +296,28 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
}; };
// Desktop dropdown handlers // Desktop dropdown handlers
const handleDropdownOpen = (event: React.MouseEvent<HTMLElement>, itemId: string) => { const handleDropdownOpen = (event: React.MouseEvent<HTMLElement>, itemId: string): void => {
setDropdownAnchors(prev => ({ ...prev, [itemId]: event.currentTarget })); setDropdownAnchors(prev => ({ ...prev, [itemId]: event.currentTarget }));
}; };
const handleDropdownClose = (itemId: string) => { const handleDropdownClose = (itemId: string): void => {
setDropdownAnchors(prev => ({ ...prev, [itemId]: null })); setDropdownAnchors(prev => ({ ...prev, [itemId]: null }));
}; };
// Mobile accordion handlers // Mobile accordion handlers
const handleMobileToggle = (itemId: string) => { const handleMobileToggle = (itemId: string): void => {
setMobileExpanded(prev => ({ ...prev, [itemId]: !prev[itemId] })); setMobileExpanded(prev => ({ ...prev, [itemId]: !prev[itemId] }));
}; };
const handleDrawerToggle = () => { const handleDrawerToggle = (): void => {
setMobileOpen(!mobileOpen); setMobileOpen(!mobileOpen);
}; };
const handleUserMenuOpen = (event: React.MouseEvent<HTMLElement>) => { const handleUserMenuOpen = (event: React.MouseEvent<HTMLElement>): void => {
setUserMenuAnchor(event.currentTarget); setUserMenuAnchor(event.currentTarget);
}; };
const handleUserMenuClose = () => { const handleUserMenuClose = (): void => {
setUserMenuAnchor(null); setUserMenuAnchor(null);
}; };
@ -325,7 +327,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
icon: React.ReactElement | null; icon: React.ReactElement | null;
action: () => void; action: () => void;
group?: string; group?: string;
}) => { }): void => {
if (item.group !== 'divider') { if (item.group !== 'divider') {
item.action(); item.action();
handleUserMenuClose(); handleUserMenuClose();
@ -333,7 +335,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
}; };
// Navigation handlers // Navigation handlers
const handleNavigate = (path: string) => { const handleNavigate = (path: string): void => {
navigate(path.replace(/:.*$/, '')); navigate(path.replace(/:.*$/, ''));
setMobileOpen(false); setMobileOpen(false);
// Close all dropdowns // Close all dropdowns
@ -341,7 +343,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
}; };
// Render desktop navigation with dropdowns // Render desktop navigation with dropdowns
const renderDesktopNavigation = () => { const renderDesktopNavigation = (): JSX.Element => {
return ( return (
<Box <Box
sx={{ sx={{
@ -364,7 +366,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
}} }}
> >
<DropdownButton <DropdownButton
onClick={e => handleDropdownOpen(e, item.id)} onClick={(e): void => handleDropdownOpen(e, item.id)}
endIcon={<KeyboardArrowDown />} endIcon={<KeyboardArrowDown />}
sx={{ sx={{
backgroundColor: isActive ? 'action.selected' : 'transparent', backgroundColor: isActive ? 'action.selected' : 'transparent',
@ -377,7 +379,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
<Menu <Menu
anchorEl={dropdownAnchors[item.id]} anchorEl={dropdownAnchors[item.id]}
open={Boolean(dropdownAnchors[item.id])} open={Boolean(dropdownAnchors[item.id])}
onClose={() => handleDropdownClose(item.id)} onClose={(): void => handleDropdownClose(item.id)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}
transformOrigin={{ vertical: 'top', horizontal: 'left' }} transformOrigin={{ vertical: 'top', horizontal: 'left' }}
TransitionComponent={Fade} TransitionComponent={Fade}
@ -385,7 +387,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
{item.children?.map(child => ( {item.children?.map(child => (
<MenuItem <MenuItem
key={child.id} key={child.id}
onClick={() => child.path && handleNavigate(child.path)} onClick={(): void => {
child.path && handleNavigate(child.path);
}}
selected={isCurrentPath(child)} selected={isCurrentPath(child)}
disabled={!child.path} disabled={!child.path}
sx={{ sx={{
@ -406,7 +410,9 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
return ( return (
<DropdownButton <DropdownButton
key={item.id} key={item.id}
onClick={() => item.path && handleNavigate(item.path)} onClick={(): void => {
item.path && handleNavigate(item.path);
}}
sx={{ sx={{
backgroundColor: isActive ? 'action.selected' : 'transparent', backgroundColor: isActive ? 'action.selected' : 'transparent',
color: isActive ? 'secondary.main' : 'primary.contrastText', color: isActive ? 'secondary.main' : 'primary.contrastText',
@ -424,8 +430,8 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
}; };
// Render mobile accordion navigation // Render mobile accordion navigation
const renderMobileNavigation = () => { const renderMobileNavigation = (): JSX.Element => {
const renderNavigationItem = (item: NavigationItem, depth = 0) => { const renderNavigationItem = (item: NavigationItem, depth = 0): JSX.Element => {
const hasChildren = item.children && item.children.length > 0; const hasChildren = item.children && item.children.length > 0;
const isActive = isCurrentPath(item) || hasActiveChild(item); const isActive = isCurrentPath(item) || hasActiveChild(item);
const isExpanded = mobileExpanded[item.id]; const isExpanded = mobileExpanded[item.id];
@ -434,7 +440,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
<Box key={item.id}> <Box key={item.id}>
<ListItem disablePadding sx={{ pl: depth * 2 }}> <ListItem disablePadding sx={{ pl: depth * 2 }}>
<ListItemButton <ListItemButton
onClick={() => { onClick={(): void => {
if (hasChildren) { if (hasChildren) {
handleMobileToggle(item.id); handleMobileToggle(item.id);
} else if (item.path) { } else if (item.path) {
@ -485,7 +491,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
<Divider sx={{ my: 1 }} /> <Divider sx={{ my: 1 }} />
{!user && ( {!user && (
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton onClick={() => handleNavigate('/login')}> <ListItemButton onClick={(): void => handleNavigate('/login')}>
<ListItemText primary="Login" /> <ListItemText primary="Login" />
</ListItemButton> </ListItemButton>
</ListItem> </ListItem>
@ -495,7 +501,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
}; };
// Render user menu content // Render user menu content
const renderUserMenu = () => { const renderUserMenu = (): JSX.Element => {
return ( return (
<UserMenuContainer> <UserMenuContainer>
<List dense> <List dense>
@ -504,7 +510,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
<Divider key={`divider-${index}`} /> <Divider key={`divider-${index}`} />
) : ( ) : (
<ListItem key={item.id}> <ListItem key={item.id}>
<ListItemButton onClick={() => handleUserMenuAction(item)}> <ListItemButton onClick={(): void => handleUserMenuAction(item)}>
{item.icon && <ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon>} {item.icon && <ListItemIcon sx={{ minWidth: 36 }}>{item.icon}</ListItemIcon>}
<ListItemText primary={item.label} /> <ListItemText primary={item.label} />
</ListItemButton> </ListItemButton>
@ -517,13 +523,15 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
}; };
// Render user account section // Render user account section
const renderUserSection = () => { const renderUserSection = (): JSX.Element => {
if (!user) { if (!user) {
return ( return (
<Button <Button
color="info" color="info"
variant="contained" variant="contained"
onClick={() => navigate('/login')} onClick={(): void => {
navigate('/login');
}}
sx={{ sx={{
display: { xs: 'none', sm: 'block' }, display: { xs: 'none', sm: 'block' },
color: theme.palette.primary.contrastText, color: theme.palette.primary.contrastText,
@ -619,7 +627,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
'&:hover': { bgcolor: 'action.hover', opacity: 1 }, '&:hover': { bgcolor: 'action.hover', opacity: 1 },
}} }}
content={`${window.location.origin}${window.location.pathname}?id=${sessionId}`} content={`${window.location.origin}${window.location.pathname}?id=${sessionId}`}
onClick={() => { onClick={(): void => {
navigate(`${window.location.pathname}?id=${sessionId}`); navigate(`${window.location.pathname}?id=${sessionId}`);
setSnack('Link copied!'); setSnack('Link copied!');
}} }}
@ -644,7 +652,7 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
</Container> </Container>
<Beta <Beta
sx={{ left: '-90px', '& .mobile': { right: '-72px' } }} sx={{ left: '-90px', '& .mobile': { right: '-72px' } }}
onClick={() => { onClick={(): void => {
navigate('/docs/beta'); navigate('/docs/beta');
}} }}
/> />

View File

@ -1,10 +1,10 @@
import React from 'react'; import React, { JSX } from 'react';
import { Typography, Avatar } from '@mui/material'; import { Typography, Avatar } from '@mui/material';
import { useTheme } from '@mui/material/styles'; import { useTheme } from '@mui/material/styles';
import 'components/layout/Header.css'; import 'components/layout/Header.css';
const BackstoryLogo = () => { const BackstoryLogo = (): JSX.Element => {
const theme = useTheme(); const theme = useTheme();
return ( return (
<Typography <Typography

View File

@ -41,7 +41,7 @@ const Beta: React.FC<BetaProps> = (props: BetaProps) => {
<Box <Box
sx={sx} sx={sx}
className={`beta-clipper ${adaptive && isMobile && 'mobile'}`} className={`beta-clipper ${adaptive && isMobile && 'mobile'}`}
onClick={e => { onClick={(e): void => {
onClick && onClick(e); onClick && onClick(e);
}} }}
> >

View File

@ -40,7 +40,7 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
} }
}, [candidate.description]); }, [candidate.description]);
const deleteCandidate = async (candidateId: string | undefined) => { const deleteCandidate = async (candidateId: string | undefined): Promise<void> => {
if (candidateId) { if (candidateId) {
await apiClient.deleteCandidate(candidateId); await apiClient.deleteCandidate(candidateId);
} }
@ -116,7 +116,7 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
<Box sx={{ fontSize: '0.75rem', alignItems: 'center' }}> <Box sx={{ fontSize: '0.75rem', alignItems: 'center' }}>
<Link href={`/u/${candidate.username}`}>{`/u/${candidate.username}`}</Link> <Link href={`/u/${candidate.username}`}>{`/u/${candidate.username}`}</Link>
<CopyBubble <CopyBubble
onClick={(event: any) => { onClick={(event): void => {
event.stopPropagation(); event.stopPropagation();
}} }}
tooltip="Copy link" tooltip="Copy link"
@ -151,7 +151,7 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
<Link <Link
component="button" component="button"
variant="body2" variant="body2"
onClick={e => { onClick={(e): void => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setIsDescriptionExpanded(!isDescriptionExpanded); setIsDescriptionExpanded(!isDescriptionExpanded);
@ -209,7 +209,7 @@ const CandidateInfo: React.FC<CandidateInfoProps> = (props: CandidateInfoProps)
<Tooltip title="Delete Job"> <Tooltip title="Delete Job">
<IconButton <IconButton
size="small" size="small"
onClick={e => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
deleteCandidate(candidate.id); deleteCandidate(candidate.id);
}} }}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { JSX, useEffect, useState } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import { BackstoryElementProps } from 'components/BackstoryTab'; import { BackstoryElementProps } from 'components/BackstoryTab';
@ -12,7 +12,7 @@ interface CandidatePickerProps extends BackstoryElementProps {
onSelect?: (candidate: Candidate) => void; onSelect?: (candidate: Candidate) => void;
} }
const CandidatePicker = (props: CandidatePickerProps) => { const CandidatePicker = (props: CandidatePickerProps): JSX.Element => {
const { onSelect, sx } = props; const { onSelect, sx } = props;
const { apiClient } = useAuth(); const { apiClient } = useAuth();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate(); const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
@ -23,7 +23,7 @@ const CandidatePicker = (props: CandidatePickerProps) => {
if (candidates !== null) { if (candidates !== null) {
return; return;
} }
const getCandidates = async () => { const getCandidates = async (): Promise<void> => {
try { try {
const results = await apiClient.getCandidates(); const results = await apiClient.getCandidates();
const candidates: Candidate[] = results.data; const candidates: Candidate[] = results.data;
@ -49,7 +49,7 @@ const CandidatePicker = (props: CandidatePickerProps) => {
}; };
getCandidates(); getCandidates();
}, [candidates, setSnack]); }, [candidates, setSnack, apiClient]);
return ( return (
<Box sx={{ display: 'flex', flexDirection: 'column', ...sx }}> <Box sx={{ display: 'flex', flexDirection: 'column', ...sx }}>
@ -64,7 +64,7 @@ const CandidatePicker = (props: CandidatePickerProps) => {
{candidates?.map(u => ( {candidates?.map(u => (
<Paper <Paper
key={`${u.username}`} key={`${u.username}`}
onClick={() => { onClick={(): void => {
onSelect ? onSelect(u) : setSelectedCandidate(u); onSelect ? onSelect(u) : setSelectedCandidate(u);
}} }}
sx={{ cursor: 'pointer' }} sx={{ cursor: 'pointer' }}

View File

@ -67,13 +67,13 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
} }
}, [job.summary]); }, [job.summary]);
const deleteJob = async (jobId: string | undefined) => { const deleteJob = async (jobId: string | undefined): Promise<void> => {
if (jobId) { if (jobId) {
await apiClient.deleteJob(jobId); await apiClient.deleteJob(jobId);
} }
}; };
const handleReset = async () => { const handleReset = async (): Promise<void> => {
setActiveJob({ ...job }); setActiveJob({ ...job });
}; };
@ -81,7 +81,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
return <Box>No job provided.</Box>; return <Box>No job provided.</Box>;
} }
const handleSave = async () => { const handleSave = async (): Promise<void> => {
const newJob = await apiClient.updateJob(job.id || '', { const newJob = await apiClient.updateJob(job.id || '', {
description: activeJob.description, description: activeJob.description,
requirements: activeJob.requirements, requirements: activeJob.requirements,
@ -91,15 +91,15 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
setSnack('Job updated.'); setSnack('Job updated.');
}; };
const handleRefresh = () => { const handleRefresh = (): void => {
setAdminStatus('Re-extracting Job information...'); setAdminStatus('Re-extracting Job information...');
const jobStatusHandlers = { const jobStatusHandlers = {
onStatus: (status: Types.ChatMessageStatus) => { onStatus: (status: Types.ChatMessageStatus): void => {
console.log('status:', status.content); console.log('status:', status.content);
setAdminStatusType(status.activity); setAdminStatusType(status.activity);
setAdminStatus(status.content); setAdminStatus(status.content);
}, },
onMessage: async (jobMessage: Types.JobRequirementsMessage) => { onMessage: async (jobMessage: Types.JobRequirementsMessage): Promise<void> => {
const newJob: Types.Job = jobMessage.job; const newJob: Types.Job = jobMessage.job;
console.log('onMessage - job', newJob); console.log('onMessage - job', newJob);
newJob.id = job.id; newJob.id = job.id;
@ -107,12 +107,12 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
const updatedJob: Types.Job = await apiClient.updateJob(job.id || '', newJob); const updatedJob: Types.Job = await apiClient.updateJob(job.id || '', newJob);
setActiveJob(updatedJob); setActiveJob(updatedJob);
}, },
onError: (error: Types.ChatMessageError) => { onError: (error: Types.ChatMessageError): void => {
console.log('onError', error); console.log('onError', error);
setAdminStatusType(null); setAdminStatusType(null);
setAdminStatus(null); setAdminStatus(null);
}, },
onComplete: () => { onComplete: (): void => {
setAdminStatusType(null); setAdminStatusType(null);
setAdminStatus(null); setAdminStatus(null);
}, },
@ -125,8 +125,8 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
items: string[] | undefined, items: string[] | undefined,
icon: JSX.Element, icon: JSX.Element,
required = false required = false
) => { ): JSX.Element => {
if (!items || items.length === 0) return null; if (!items || items.length === 0) return <></>;
return ( return (
<Box sx={{ mb: 2 }}> <Box sx={{ mb: 2 }}>
@ -162,8 +162,8 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
); );
}; };
const renderJobRequirements = () => { const renderJobRequirements = (): JSX.Element => {
if (!activeJob.requirements) return null; if (!activeJob.requirements) return <></>;
return ( return (
<Card elevation={0} sx={{ m: 0, p: 0, mt: 2, background: 'transparent !important' }}> <Card elevation={0} sx={{ m: 0, p: 0, mt: 2, background: 'transparent !important' }}>
@ -320,7 +320,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
<Link <Link
component="button" component="button"
variant="body2" variant="body2"
onClick={e => { onClick={(e): void => {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
setIsDescriptionExpanded(!isDescriptionExpanded); setIsDescriptionExpanded(!isDescriptionExpanded);
@ -402,7 +402,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
<Tooltip title="Save Job"> <Tooltip title="Save Job">
<IconButton <IconButton
size="small" size="small"
onClick={e => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
handleSave(); handleSave();
}} }}
@ -414,7 +414,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
<Tooltip title="Delete Job"> <Tooltip title="Delete Job">
<IconButton <IconButton
size="small" size="small"
onClick={e => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
deleteJob(job.id); deleteJob(job.id);
setDeleted(true); setDeleted(true);
@ -426,7 +426,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
<Tooltip title="Reset Job"> <Tooltip title="Reset Job">
<IconButton <IconButton
size="small" size="small"
onClick={e => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
handleReset(); handleReset();
}} }}
@ -437,7 +437,7 @@ const JobInfo: React.FC<JobInfoProps> = (props: JobInfoProps) => {
<Tooltip title="Reprocess Job"> <Tooltip title="Reprocess Job">
<IconButton <IconButton
size="small" size="small"
onClick={e => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
handleRefresh(); handleRefresh();
}} }}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { JSX, useEffect, useState } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import { BackstoryElementProps } from 'components/BackstoryTab'; import { BackstoryElementProps } from 'components/BackstoryTab';
@ -12,7 +12,7 @@ interface JobPickerProps extends BackstoryElementProps {
onSelect?: (job: Job) => void; onSelect?: (job: Job) => void;
} }
const JobPicker = (props: JobPickerProps) => { const JobPicker = (props: JobPickerProps): JSX.Element => {
const { onSelect } = props; const { onSelect } = props;
const { apiClient } = useAuth(); const { apiClient } = useAuth();
const { selectedJob } = useSelectedJob(); const { selectedJob } = useSelectedJob();
@ -23,7 +23,7 @@ const JobPicker = (props: JobPickerProps) => {
if (jobs !== null) { if (jobs !== null) {
return; return;
} }
const getJobs = async () => { const getJobs = async (): Promise<void> => {
try { try {
const results = await apiClient.getJobs(); const results = await apiClient.getJobs();
const jobs: Job[] = results.data; const jobs: Job[] = results.data;
@ -41,7 +41,7 @@ const JobPicker = (props: JobPickerProps) => {
}; };
getJobs(); getJobs();
}, [jobs, setSnack]); }, [jobs, setSnack, apiClient]);
return ( return (
<Box sx={{ display: 'flex', flexDirection: 'column', mb: 1 }}> <Box sx={{ display: 'flex', flexDirection: 'column', mb: 1 }}>
@ -56,7 +56,7 @@ const JobPicker = (props: JobPickerProps) => {
{jobs?.map(j => ( {jobs?.map(j => (
<Paper <Paper
key={`${j.id}`} key={`${j.id}`}
onClick={() => { onClick={(): void => {
console.log('Selected job', j); console.log('Selected job', j);
onSelect && onSelect(j); onSelect && onSelect(j);
}} }}

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { JSX, useEffect, useState } from 'react';
import { import {
Box, Box,
Paper, Paper,
@ -71,7 +71,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
useEffect(() => { useEffect(() => {
if (loading) return; // Prevent multiple calls if (loading) return; // Prevent multiple calls
const getJobs = async () => { const getJobs = async (): Promise<void> => {
try { try {
const results = await apiClient.getJobs(); const results = await apiClient.getJobs();
const jobsData: Job[] = results.data || []; const jobsData: Job[] = results.data || [];
@ -102,12 +102,22 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
setLoading(true); setLoading(true);
getJobs(); getJobs();
}, [apiClient, setSnack, loading]); }, [
apiClient,
setSnack,
loading,
jobId,
selectedJob,
onSelect,
sortField,
sortOrder,
setSelectedJob,
]);
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) => {
let aValue: any; let aValue: number | string;
let bValue: any; let bValue: number | string;
switch (field) { switch (field) {
case 'updatedAt': case 'updatedAt':
@ -136,7 +146,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
}); });
}; };
const handleSort = (field: SortField) => { const handleSort = (field: SortField): void => {
if (sortField === field) { if (sortField === field) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
} else { } else {
@ -145,20 +155,20 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
} }
}; };
const handleJobSelect = (job: Job) => { const handleJobSelect = (job: Job): void => {
setSelectedJob(job); setSelectedJob(job);
onSelect?.(job); onSelect?.(job);
setMobileDialogOpen(true); setMobileDialogOpen(true);
navigate(`/candidate/jobs/${job.id}`); navigate(`/candidate/jobs/${job.id}`);
}; };
const handleMobileDialogClose = () => { const handleMobileDialogClose = (): void => {
setMobileDialogOpen(false); setMobileDialogOpen(false);
}; };
const sortedJobs = sortJobs(jobs, sortField, sortOrder); const sortedJobs = sortJobs(jobs, sortField, sortOrder);
const formatDate = (date: Date | undefined) => { const formatDate = (date: Date | undefined): string => {
if (!date) return 'N/A'; if (!date) return 'N/A';
return new Intl.DateTimeFormat('en-US', { return new Intl.DateTimeFormat('en-US', {
month: 'short', month: 'short',
@ -168,8 +178,8 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
}).format(date); }).format(date);
}; };
const getSortIcon = (field: SortField) => { const getSortIcon = (field: SortField): JSX.Element => {
if (sortField !== field) return null; if (sortField !== field) return <></>;
return sortOrder === 'asc' ? ( return sortOrder === 'asc' ? (
<ArrowUpIcon fontSize="small" /> <ArrowUpIcon fontSize="small" />
) : ( ) : (
@ -177,7 +187,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
); );
}; };
const JobList = () => ( const JobList = (): JSX.Element => (
<Paper <Paper
elevation={isMobile ? 0 : 1} elevation={isMobile ? 0 : 1}
sx={{ sx={{
@ -209,7 +219,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
<Select <Select
value={`${sortField}-${sortOrder}`} value={`${sortField}-${sortOrder}`}
label="Sort by" label="Sort by"
onChange={e => { onChange={(e): void => {
const [field, order] = e.target.value.split('-') as [SortField, SortOrder]; const [field, order] = e.target.value.split('-') as [SortField, SortOrder];
setSortField(field); setSortField(field);
setSortOrder(order); setSortOrder(order);
@ -248,7 +258,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
width: isMobile ? '25%' : 'auto', width: isMobile ? '25%' : 'auto',
backgroundColor: 'background.paper', backgroundColor: 'background.paper',
}} }}
onClick={() => handleSort('company')} onClick={(): void => handleSort('company')}
> >
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<BusinessIcon fontSize={isMobile ? 'small' : 'medium'} /> <BusinessIcon fontSize={isMobile ? 'small' : 'medium'} />
@ -267,7 +277,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
width: isMobile ? '45%' : 'auto', width: isMobile ? '45%' : 'auto',
backgroundColor: 'background.paper', backgroundColor: 'background.paper',
}} }}
onClick={() => handleSort('title')} onClick={(): void => handleSort('title')}
> >
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<WorkIcon fontSize={isMobile ? 'small' : 'medium'} /> <WorkIcon fontSize={isMobile ? 'small' : 'medium'} />
@ -286,7 +296,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
px: 1, px: 1,
backgroundColor: 'background.paper', backgroundColor: 'background.paper',
}} }}
onClick={() => handleSort('updatedAt')} onClick={(): void => handleSort('updatedAt')}
> >
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<ScheduleIcon fontSize="medium" /> <ScheduleIcon fontSize="medium" />
@ -317,7 +327,9 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
key={job.id} key={job.id}
hover hover
selected={selectedJob?.id === job.id} selected={selectedJob?.id === job.id}
onClick={() => handleJobSelect(job)} onClick={(): void => {
handleJobSelect(job);
}}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
height: isMobile ? 48 : 'auto', height: isMobile ? 48 : 'auto',
@ -431,7 +443,7 @@ const JobViewer: React.FC<JobViewerProps> = ({ onSelect }) => {
</Paper> </Paper>
); );
const JobDetails = ({ inDialog = false }: { inDialog?: boolean }) => ( const JobDetails = ({ inDialog = false }: { inDialog?: boolean }): JSX.Element => (
<Box <Box
sx={{ sx={{
flex: 1, flex: 1,

View File

@ -87,16 +87,16 @@ const JobsTable: React.FC<JobsTableProps> = ({
setLoading(false); setLoading(false);
} }
}, },
[limit] [limit, apiClient]
); );
// Initial load // Initial load
React.useEffect(() => { React.useEffect(() => {
fetchJobs(0, searchQuery); fetchJobs(0, searchQuery);
}, [fetchJobs]); }, [fetchJobs, searchQuery]);
// Handle search with debouncing // Handle search with debouncing
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const value = event.target.value; const value = event.target.value;
setSearchQuery(value); setSearchQuery(value);
@ -113,13 +113,13 @@ const JobsTable: React.FC<JobsTableProps> = ({
}; };
// Handle page change // Handle page change
const handlePageChange = (event: unknown, newPage: number) => { const handlePageChange = (event: unknown, newPage: number): void => {
setPage(newPage); setPage(newPage);
fetchJobs(newPage, searchQuery); fetchJobs(newPage, searchQuery);
}; };
// Handle rows per page change // Handle rows per page change
const handleRowsPerPageChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleRowsPerPageChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
const newLimit = parseInt(event.target.value, 10); const newLimit = parseInt(event.target.value, 10);
setLimit(newLimit); setLimit(newLimit);
setPage(0); setPage(0);
@ -127,7 +127,7 @@ const JobsTable: React.FC<JobsTableProps> = ({
}; };
// Handle selection // Handle selection
const handleSelectAll = (event: React.ChangeEvent<HTMLInputElement>) => { const handleSelectAll = (event: React.ChangeEvent<HTMLInputElement>): void => {
if (event.target.checked) { if (event.target.checked) {
const newSelected = new Set<string>(jobs.map(job => job.id || '')); const newSelected = new Set<string>(jobs.map(job => job.id || ''));
setSelectedJobs(newSelected); setSelectedJobs(newSelected);
@ -138,7 +138,7 @@ const JobsTable: React.FC<JobsTableProps> = ({
} }
}; };
const handleSelectJob = (jobId: string) => { const handleSelectJob = (jobId: string): void => {
const newSelected = new Set(selectedJobs); const newSelected = new Set(selectedJobs);
if (newSelected.has(jobId)) { if (newSelected.has(jobId)) {
newSelected.delete(jobId); newSelected.delete(jobId);
@ -151,17 +151,17 @@ const JobsTable: React.FC<JobsTableProps> = ({
onJobSelect?.(selectedJobsList); onJobSelect?.(selectedJobsList);
}; };
const getOwnerName = (owner?: Types.Job['owner']) => { const getOwnerName = (owner?: Types.Job['owner']): string => {
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 = 100) => { const truncateDescription = (description: string, maxLength = 100): string => {
if (description.length <= maxLength) return description; if (description.length <= maxLength) return description;
return description.substring(0, maxLength) + '...'; return description.substring(0, maxLength) + '...';
}; };
const isSelected = (jobId: string) => selectedJobs.has(jobId); const isSelected = (jobId: string): boolean => selectedJobs.has(jobId);
const numSelected = selectedJobs.size; const numSelected = selectedJobs.size;
const rowCount = jobs.length; const rowCount = jobs.length;
@ -262,7 +262,7 @@ const JobsTable: React.FC<JobsTableProps> = ({
<Checkbox <Checkbox
color="primary" color="primary"
checked={isItemSelected} checked={isItemSelected}
onChange={() => handleSelectJob(job.id || '')} onChange={(): void => handleSelectJob(job.id || '')}
inputProps={{ inputProps={{
'aria-labelledby': `job-${job.id}`, 'aria-labelledby': `job-${job.id}`,
}} }}
@ -312,21 +312,21 @@ const JobsTable: React.FC<JobsTableProps> = ({
<Box sx={{ display: 'flex', gap: 0.5 }}> <Box sx={{ display: 'flex', gap: 0.5 }}>
{onJobView && ( {onJobView && (
<Tooltip title="View Job"> <Tooltip title="View Job">
<IconButton size="small" onClick={() => onJobView(job)}> <IconButton size="small" onClick={(): void => onJobView(job)}>
<VisibilityIcon fontSize="small" /> <VisibilityIcon fontSize="small" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} )}
{onJobEdit && ( {onJobEdit && (
<Tooltip title="Edit Job"> <Tooltip title="Edit Job">
<IconButton size="small" onClick={() => onJobEdit(job)}> <IconButton size="small" onClick={(): void => onJobEdit(job)}>
<EditIcon fontSize="small" /> <EditIcon fontSize="small" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
)} )}
{onJobDelete && ( {onJobDelete && (
<Tooltip title="Delete Job"> <Tooltip title="Delete Job">
<IconButton size="small" onClick={() => onJobDelete(job)}> <IconButton size="small" onClick={(): void => onJobDelete(job)}>
<DeleteIcon fontSize="small" /> <DeleteIcon fontSize="small" />
</IconButton> </IconButton>
</Tooltip> </Tooltip>

View File

@ -1,11 +1,11 @@
import React from 'react'; import React, { JSX } from 'react';
import { Button, Typography, Paper, Container } from '@mui/material'; import { Button, Typography, Paper, Container } from '@mui/material';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
interface LoginRequiredProps { interface LoginRequiredProps {
asset: string; asset: string;
} }
const LoginRequired = (props: LoginRequiredProps) => { const LoginRequired = (props: LoginRequiredProps): JSX.Element => {
const { asset } = props; const { asset } = props;
const navigate = useNavigate(); const navigate = useNavigate();
@ -17,7 +17,7 @@ const LoginRequired = (props: LoginRequiredProps) => {
</Typography> </Typography>
<Button <Button
variant="contained" variant="contained"
onClick={() => { onClick={(): void => {
navigate('/login'); navigate('/login');
}} }}
color="primary" color="primary"

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { JSX } from 'react';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import './LoginRestricted.css'; import './LoginRestricted.css';
@ -6,7 +6,7 @@ interface LoginRestrictedProps {
children?: React.ReactNode; children?: React.ReactNode;
} }
const LoginRestricted = (props: LoginRestrictedProps) => { const LoginRestricted = (props: LoginRestrictedProps): JSX.Element => {
const { children } = props; const { children } = props;
return ( return (
<Box className="LoginRestricted"> <Box className="LoginRestricted">

View File

@ -95,7 +95,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
}, [resume, activeResume]); }, [resume, activeResume]);
// Check if content needs truncation // Check if content needs truncation
const deleteResume = async (id: string | undefined) => { const deleteResume = async (id: string | undefined): Promise<void> => {
if (id) { if (id) {
try { try {
await apiClient.deleteResume(id); await apiClient.deleteResume(id);
@ -107,11 +107,11 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
} }
}; };
const handleReset = async () => { const handleReset = async (): Promise<void> => {
setActiveResume({ ...resume }); setActiveResume({ ...resume });
}; };
const handleSave = async () => { const handleSave = async (): Promise<void> => {
setSaving(true); setSaving(true);
try { try {
const resumeUpdate = { const resumeUpdate = {
@ -135,7 +135,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
} }
}; };
const handleEditOpen = () => { const handleEditOpen = (): void => {
setEditContent(activeResume.resume); setEditContent(activeResume.resume);
setEditSystemPrompt(activeResume.systemPrompt || ''); setEditSystemPrompt(activeResume.systemPrompt || '');
setEditPrompt(activeResume.prompt || ''); setEditPrompt(activeResume.prompt || '');
@ -146,7 +146,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
return <Box>No resume provided.</Box>; return <Box>No resume provided.</Box>;
} }
const formatDate = (date: Date | undefined) => { const formatDate = (date: Date | undefined): string => {
if (!date) return 'N/A'; if (!date) return 'N/A';
try { try {
return new Intl.DateTimeFormat('en-US', { return new Intl.DateTimeFormat('en-US', {
@ -204,7 +204,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
await request.promise; await request.promise;
}; };
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => { const handleTabChange = (event: React.SyntheticEvent, newValue: string): void => {
if (newValue === 'print') { if (newValue === 'print') {
reactToPrintFn(); reactToPrintFn();
return; return;
@ -384,7 +384,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
<Tooltip title="Edit Resume"> <Tooltip title="Edit Resume">
<IconButton <IconButton
size="small" size="small"
onClick={e => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
handleEditOpen(); handleEditOpen();
}} }}
@ -396,7 +396,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
<Tooltip title="Delete Resume"> <Tooltip title="Delete Resume">
<IconButton <IconButton
size="small" size="small"
onClick={e => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
deleteResume(activeResume.id); deleteResume(activeResume.id);
}} }}
@ -408,7 +408,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
<Tooltip title="Reset Resume"> <Tooltip title="Reset Resume">
<IconButton <IconButton
size="small" size="small"
onClick={e => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
handleReset(); handleReset();
}} }}
@ -432,7 +432,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
{/* Edit Dialog */} {/* Edit Dialog */}
<Dialog <Dialog
open={editDialogOpen} open={editDialogOpen}
onClose={() => { onClose={(): void => {
setEditDialogOpen(false); setEditDialogOpen(false);
}} }}
maxWidth="lg" maxWidth="lg"
@ -527,7 +527,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
{tabValue === 'markdown' && ( {tabValue === 'markdown' && (
<BackstoryTextField <BackstoryTextField
value={editContent} value={editContent}
onChange={value => setEditContent(value)} onChange={(value): void => setEditContent(value)}
style={{ style={{
position: 'relative', position: 'relative',
maxHeight: '100%', maxHeight: '100%',
@ -546,7 +546,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
{tabValue === 'systemPrompt' && ( {tabValue === 'systemPrompt' && (
<BackstoryTextField <BackstoryTextField
value={editSystemPrompt} value={editSystemPrompt}
onChange={value => setEditSystemPrompt(value)} onChange={(value): void => setEditSystemPrompt(value)}
style={{ style={{
position: 'relative', position: 'relative',
maxHeight: '100%', maxHeight: '100%',
@ -564,7 +564,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
{tabValue === 'prompt' && ( {tabValue === 'prompt' && (
<BackstoryTextField <BackstoryTextField
value={editPrompt} value={editPrompt}
onChange={value => setEditPrompt(value)} onChange={(value): void => setEditPrompt(value)}
style={{ style={{
position: 'relative', position: 'relative',
maxHeight: '100%', maxHeight: '100%',
@ -634,7 +634,7 @@ const ResumeInfo: React.FC<ResumeInfoProps> = (props: ResumeInfoProps) => {
</Box> </Box>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions>
<Button onClick={() => setEditDialogOpen(false)}>Cancel</Button> <Button onClick={(): void => setEditDialogOpen(false)}>Cancel</Button>
<Button <Button
onClick={handleSave} onClick={handleSave}
variant="contained" variant="contained"

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { JSX, useEffect, useState } from 'react';
import { import {
Box, Box,
Paper, Paper,
@ -76,7 +76,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
const { resumeId } = useParams<{ resumeId?: string }>(); const { resumeId } = useParams<{ resumeId?: string }>();
useEffect(() => { useEffect(() => {
const getResumes = async () => { const getResumes = async (): Promise<void> => {
try { try {
let results; let results;
@ -114,7 +114,18 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
}; };
getResumes(); getResumes();
}, [apiClient, setSnack, candidateId, jobId]); }, [
apiClient,
setSnack,
candidateId,
jobId,
resumeId,
selectedResume,
setSelectedResume,
onSelect,
sortField,
sortOrder,
]);
// Filter resumes based on search query // Filter resumes based on search query
useEffect(() => { useEffect(() => {
@ -135,8 +146,8 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
const sortResumes = (resumesList: Resume[], field: SortField, order: SortOrder): Resume[] => { const sortResumes = (resumesList: Resume[], field: SortField, order: SortOrder): Resume[] => {
return [...resumesList].sort((a, b) => { return [...resumesList].sort((a, b) => {
let aValue: any; let aValue: number | string;
let bValue: any; let bValue: number | string;
switch (field) { switch (field) {
case 'updatedAt': case 'updatedAt':
@ -165,7 +176,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
}); });
}; };
const handleSort = (field: SortField) => { const handleSort = (field: SortField): void => {
if (sortField === field) { if (sortField === field) {
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
} else { } else {
@ -174,7 +185,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
} }
}; };
const handleResumeSelect = (resume: Resume) => { const handleResumeSelect = (resume: Resume): void => {
setSelectedResume(resume); setSelectedResume(resume);
onSelect?.(resume); onSelect?.(resume);
if (isMobile) { if (isMobile) {
@ -184,17 +195,17 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
} }
}; };
const handleMobileDialogClose = () => { const handleMobileDialogClose = (): void => {
setMobileDialogOpen(false); setMobileDialogOpen(false);
}; };
const handleSearchClear = () => { const handleSearchClear = (): void => {
setSearchQuery(''); setSearchQuery('');
}; };
const sortedResumes = sortResumes(filteredResumes, sortField, sortOrder); const sortedResumes = sortResumes(filteredResumes, sortField, sortOrder);
const formatDate = (date: Date | undefined) => { const formatDate = (date: Date | undefined): string => {
if (!date) return 'N/A'; if (!date) return 'N/A';
return new Intl.DateTimeFormat('en-US', { return new Intl.DateTimeFormat('en-US', {
month: 'short', month: 'short',
@ -204,8 +215,8 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
}).format(date); }).format(date);
}; };
const getSortIcon = (field: SortField) => { const getSortIcon = (field: SortField): JSX.Element => {
if (sortField !== field) return null; if (sortField !== field) return <></>;
return sortOrder === 'asc' ? ( return sortOrder === 'asc' ? (
<ArrowUpIcon fontSize="small" /> <ArrowUpIcon fontSize="small" />
) : ( ) : (
@ -213,13 +224,13 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
); );
}; };
const getDisplayTitle = () => { const getDisplayTitle = (): string => {
if (candidateId) return `Resumes for Candidate`; if (candidateId) return `Resumes for Candidate`;
if (jobId) return `Resumes for Job`; if (jobId) return `Resumes for Job`;
return `All Resumes`; return `All Resumes`;
}; };
const ResumeList = () => ( const ResumeList = (): JSX.Element => (
<Paper <Paper
elevation={isMobile ? 0 : 1} elevation={isMobile ? 0 : 1}
sx={{ sx={{
@ -262,7 +273,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
size="small" size="small"
placeholder="Search resumes..." placeholder="Search resumes..."
value={searchQuery} value={searchQuery}
onChange={e => setSearchQuery(e.target.value)} onChange={(e): void => setSearchQuery(e.target.value)}
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<InputAdornment position="start"> <InputAdornment position="start">
@ -285,7 +296,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
<Select <Select
value={`${sortField}-${sortOrder}`} value={`${sortField}-${sortOrder}`}
label="Sort by" label="Sort by"
onChange={e => { onChange={(e): void => {
const [field, order] = e.target.value.split('-') as [SortField, SortOrder]; const [field, order] = e.target.value.split('-') as [SortField, SortOrder];
setSortField(field); setSortField(field);
setSortOrder(order); setSortOrder(order);
@ -325,7 +336,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
width: isMobile ? '35%' : 'auto', width: isMobile ? '35%' : 'auto',
backgroundColor: 'background.paper', backgroundColor: 'background.paper',
}} }}
onClick={() => handleSort('candidateId')} onClick={(): void => handleSort('candidateId')}
> >
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<PersonIcon fontSize={isMobile ? 'small' : 'medium'} /> <PersonIcon fontSize={isMobile ? 'small' : 'medium'} />
@ -344,7 +355,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
width: isMobile ? '35%' : 'auto', width: isMobile ? '35%' : 'auto',
backgroundColor: 'background.paper', backgroundColor: 'background.paper',
}} }}
onClick={() => handleSort('jobId')} onClick={(): void => handleSort('jobId')}
> >
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<WorkIcon fontSize={isMobile ? 'small' : 'medium'} /> <WorkIcon fontSize={isMobile ? 'small' : 'medium'} />
@ -363,7 +374,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
px: 1, px: 1,
backgroundColor: 'background.paper', backgroundColor: 'background.paper',
}} }}
onClick={() => handleSort('updatedAt')} onClick={(): void => handleSort('updatedAt')}
> >
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5 }}>
<ScheduleIcon fontSize="medium" /> <ScheduleIcon fontSize="medium" />
@ -394,7 +405,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
key={resume.id} key={resume.id}
hover hover
selected={selectedResume?.id === resume.id} selected={selectedResume?.id === resume.id}
onClick={() => handleResumeSelect(resume)} onClick={(): void => handleResumeSelect(resume)}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
height: isMobile ? 48 : 'auto', height: isMobile ? 48 : 'auto',
@ -498,7 +509,7 @@ const ResumeViewer: React.FC<ResumeViewerProps> = ({ onSelect, candidateId, jobI
</Paper> </Paper>
); );
const ResumeDetails = ({ inDialog = false }: { inDialog?: boolean }) => ( const ResumeDetails = ({ inDialog = false }: { inDialog?: boolean }): JSX.Element => (
<Box <Box
sx={{ sx={{
flex: 1, flex: 1,

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { JSX } from 'react';
import { import {
SyncAlt, SyncAlt,
Favorite, Favorite,
@ -29,7 +29,7 @@ const StatusBox = styled(Box)(({ theme }) => ({
minHeight: 48, minHeight: 48,
})); }));
const StatusIcon = (props: StatusIconProps) => { const StatusIcon = (props: StatusIconProps): JSX.Element => {
const { type } = props; const { type } = props;
switch (type) { switch (type) {

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { JSX } from 'react';
import { import {
Chat as ChatIcon, Chat as ChatIcon,
Dashboard as DashboardIcon, Dashboard as DashboardIcon,
@ -33,7 +33,7 @@ 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';
const LogoutPage = () => { const LogoutPage = (): JSX.Element => {
const { logout } = useAuth(); const { logout } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
logout().then(() => { logout().then(() => {
@ -140,10 +140,10 @@ export const navigationConfig: NavigationConfig = {
icon: <WorkIcon />, icon: <WorkIcon />,
component: ( component: (
<JobsTable <JobsTable
onJobSelect={(selectedJobs: Types.Job[]) => console.log('Selected:', selectedJobs)} onJobSelect={(selectedJobs: Types.Job[]): void => console.log('Selected:', selectedJobs)}
onJobView={(job: Types.Job) => console.log('View job:', job)} onJobView={(job: Types.Job): void => console.log('View job:', job)}
onJobEdit={(job: Types.Job) => console.log('Edit job:', job)} onJobEdit={(job: Types.Job): void => console.log('Edit job:', job)}
onJobDelete={(job: Types.Job) => console.log('Delete job:', job)} onJobDelete={(job: Types.Job): void => console.log('Delete job:', job)}
selectable={true} selectable={true}
showActions={true} showActions={true}
/> />

View File

@ -1,6 +1,14 @@
// Replace the existing AuthContext.tsx with these enhancements // Replace the existing AuthContext.tsx with these enhancements
import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react'; import React, {
createContext,
useContext,
useState,
useCallback,
useEffect,
useRef,
JSX,
} from 'react';
import * as Types from '../types/types'; import * as Types from '../types/types';
import { ApiClient, CreateEmployerRequest, GuestConversionRequest } from 'services/api-client'; import { ApiClient, CreateEmployerRequest, GuestConversionRequest } from 'services/api-client';
import { formatApiRequest, toCamelCase } from 'types/conversion'; import { formatApiRequest, toCamelCase } from 'types/conversion';
@ -55,7 +63,11 @@ const TOKEN_STORAGE = {
// JWT Utilities // JWT Utilities
// ============================ // ============================
function parseJwtPayload(token: string): any { type JwtPayload = {
exp?: number; // Expiry time in seconds
};
function parseJwtPayload(token: string): JwtPayload | null {
try { try {
const base64Url = token.split('.')[1]; const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
@ -181,11 +193,33 @@ function getStoredAuthData(): {
}; };
} }
// ============================ type EmailVerificationResponse = {
// Enhanced Authentication Hook message: string;
// ============================ userType: string;
};
function useAuthenticationLogic() { interface AuthenticationLogic extends AuthState {
apiClient: ApiClient;
login: (loginData: LoginRequest) => Promise<boolean>;
logout: () => Promise<void>;
verifyMFA: (mfaData: Types.MFAVerifyRequest) => Promise<boolean>;
resendMFACode: (email: string, deviceId: string, deviceName: string) => Promise<boolean>;
clearMFA: () => void;
verifyEmail: (
verificationData: EmailVerificationRequest
) => Promise<EmailVerificationResponse | null>;
resendEmailVerification: (email: string) => Promise<boolean>;
setPendingVerificationEmail: (email: string) => void;
getPendingVerificationEmail: () => string | null;
createEmployerAccount: (employerData: CreateEmployerRequest) => Promise<boolean>;
requestPasswordReset: (email: string) => Promise<boolean>;
refreshAuth: () => Promise<boolean>;
updateUserData: (updatedUser: Types.User) => void;
convertGuestToUser: (registrationData: GuestConversionRequest) => Promise<boolean>;
createGuestSession: () => Promise<boolean>;
}
function useAuthenticationLogic(): AuthenticationLogic {
const [authState, setAuthState] = useState<AuthState>({ const [authState, setAuthState] = useState<AuthState>({
user: null, user: null,
guest: null, guest: null,
@ -388,7 +422,7 @@ function useAuthenticationLogic() {
return; return;
} }
const refreshTimer = setTimeout(() => { const refreshTimer = setTimeout((): void => {
console.log('🔄 Auto-refreshing token before expiry...'); console.log('🔄 Auto-refreshing token before expiry...');
initializeAuth(); initializeAuth();
}, timeUntilExpiry); }, timeUntilExpiry);
@ -440,7 +474,7 @@ function useAuthenticationLogic() {
console.log('✅ Login successful, converted from guest to authenticated user'); console.log('✅ Login successful, converted from guest to authenticated user');
return true; return true;
} }
} catch (error: any) { } catch (error) {
const errorMessage = const errorMessage =
error instanceof Error ? error.message : 'Network error occurred. Please try again.'; error instanceof Error ? error.message : 'Network error occurred. Please try again.';
setAuthState(prev => ({ setAuthState(prev => ({
@ -483,7 +517,7 @@ function useAuthenticationLogic() {
console.log('✅ Guest successfully converted to permanent user'); console.log('✅ Guest successfully converted to permanent user');
return true; return true;
} catch (error: any) { } catch (error) {
const errorMessage = const errorMessage =
error instanceof Error ? error.message : 'Failed to convert guest account'; error instanceof Error ? error.message : 'Failed to convert guest account';
setAuthState(prev => ({ setAuthState(prev => ({
@ -584,7 +618,9 @@ function useAuthenticationLogic() {
// Email verification functions (unchanged) // Email verification functions (unchanged)
const verifyEmail = useCallback( const verifyEmail = useCallback(
async (verificationData: EmailVerificationRequest) => { async (
verificationData: EmailVerificationRequest
): Promise<EmailVerificationResponse | null> => {
setAuthState(prev => ({ ...prev, isLoading: true, error: null })); setAuthState(prev => ({ ...prev, isLoading: true, error: null }));
try { try {
@ -715,7 +751,7 @@ function useAuthenticationLogic() {
await logout(); await logout();
return false; return false;
} }
}, [refreshAccessToken, logout]); }, [refreshAccessToken, logout, apiClient]);
// Resend MFA code // Resend MFA code
const resendMFACode = useCallback( const resendMFACode = useCallback(
@ -781,13 +817,13 @@ function useAuthenticationLogic() {
const AuthContext = createContext<ReturnType<typeof useAuthenticationLogic> | null>(null); const AuthContext = createContext<ReturnType<typeof useAuthenticationLogic> | null>(null);
function AuthProvider({ children }: { children: React.ReactNode }) { function AuthProvider({ children }: { children: React.ReactNode }): JSX.Element {
const auth = useAuthenticationLogic(); const auth = useAuthenticationLogic();
return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>; return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
} }
function useAuth() { function useAuth(): ReturnType<typeof useAuthenticationLogic> {
const context = useContext(AuthContext); const context = useContext(AuthContext);
if (!context) { if (!context) {
throw new Error('useAuth must be used within an AuthProvider'); throw new Error('useAuth must be used within an AuthProvider');
@ -811,7 +847,7 @@ function ProtectedRoute({
fallback = <div>Please log in to access this page.</div>, fallback = <div>Please log in to access this page.</div>,
requiredUserType, requiredUserType,
allowGuests = false, allowGuests = false,
}: ProtectedRouteProps) { }: ProtectedRouteProps): JSX.Element {
const { isAuthenticated, isInitializing, user, isGuest } = useAuth(); const { isAuthenticated, isInitializing, user, isGuest } = useAuth();
// Show loading while checking stored tokens // Show loading while checking stored tokens

View File

@ -1,4 +1,12 @@
import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from 'react'; import React, {
createContext,
useContext,
useState,
useCallback,
useEffect,
useRef,
JSX,
} from 'react';
import * as Types from 'types/types'; import * as Types from 'types/types';
// Assuming you're using React Router // Assuming you're using React Router
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
@ -27,6 +35,7 @@ const STORAGE_KEYS = {
interface RouteState { interface RouteState {
lastRoute: string | null; lastRoute: string | null;
activeTab: string | null; activeTab: string | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
appliedFilters: Record<string, any>; appliedFilters: Record<string, any>;
sidebarCollapsed: boolean; sidebarCollapsed: boolean;
} }
@ -59,6 +68,7 @@ export interface AppStateActions {
saveCurrentRoute: () => void; saveCurrentRoute: () => void;
restoreLastRoute: () => void; restoreLastRoute: () => void;
setActiveTab: (tab: string) => void; setActiveTab: (tab: string) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setFilters: (filters: Record<string, any>) => void; setFilters: (filters: Record<string, any>) => void;
setSidebarCollapsed: (collapsed: boolean) => void; setSidebarCollapsed: (collapsed: boolean) => void;
clearRouteState: () => void; clearRouteState: () => void;
@ -101,6 +111,7 @@ function getStoredObject<T>(key: string, defaultValue: T): T {
} }
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setStoredObject(key: string, value: any): void { function setStoredObject(key: string, value: any): void {
try { try {
localStorage.setItem(key, JSON.stringify(value)); localStorage.setItem(key, JSON.stringify(value));
@ -153,7 +164,7 @@ export function useAppStateLogic(): AppStateContextType {
// ============================ // ============================
useEffect(() => { useEffect(() => {
const initializeFromStorage = async () => { const initializeFromStorage = async (): Promise<void> => {
setIsInitializing(true); setIsInitializing(true);
try { try {
@ -167,7 +178,7 @@ export function useAppStateLogic(): AppStateContextType {
if (candidateId) { if (candidateId) {
promises.push( promises.push(
(async () => { (async (): Promise<void> => {
try { try {
const candidate = await apiClient.getCandidate(candidateId); const candidate = await apiClient.getCandidate(candidateId);
if (candidate) { if (candidate) {
@ -187,7 +198,7 @@ export function useAppStateLogic(): AppStateContextType {
if (jobId) { if (jobId) {
promises.push( promises.push(
(async () => { (async (): Promise<void> => {
try { try {
const job = await apiClient.getJob(jobId); const job = await apiClient.getJob(jobId);
if (job) { if (job) {
@ -207,7 +218,7 @@ export function useAppStateLogic(): AppStateContextType {
if (employerId) { if (employerId) {
promises.push( promises.push(
(async () => { (async (): Promise<void> => {
try { try {
const employer = await apiClient.getEmployer(employerId); const employer = await apiClient.getEmployer(employerId);
if (employer) { if (employer) {
@ -235,7 +246,7 @@ export function useAppStateLogic(): AppStateContextType {
}; };
initializeFromStorage(); initializeFromStorage();
}, []); }, [apiClient]);
// ============================ // ============================
// Auto-save current route // Auto-save current route
@ -330,6 +341,7 @@ export function useAppStateLogic(): AppStateContextType {
}); });
}, []); }, []);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const setFilters = useCallback((filters: Record<string, any>) => { const setFilters = useCallback((filters: Record<string, any>) => {
setRouteStateState(prev => { setRouteStateState(prev => {
const newState = { ...prev, appliedFilters: filters }; const newState = { ...prev, appliedFilters: filters };
@ -397,9 +409,9 @@ export function useAppStateLogic(): AppStateContextType {
const AppStateContext = createContext<AppStateContextType | null>(null); const AppStateContext = createContext<AppStateContextType | null>(null);
export function AppStateProvider({ children }: { children: React.ReactNode }) { export function AppStateProvider({ children }: { children: React.ReactNode }): JSX.Element {
const appState = useAppStateLogic(); const appState = useAppStateLogic();
const snackRef = useRef<any>(null); const snackRef = useRef<{ setSnack: SetSnackType }>(null);
// Global UI components // Global UI components
appState.setSnack = useCallback( appState.setSnack = useCallback(
@ -417,7 +429,7 @@ export function AppStateProvider({ children }: { children: React.ReactNode }) {
); );
} }
export function useAppState() { export function useAppState(): AppStateContextType {
const context = useContext(AppStateContext); const context = useContext(AppStateContext);
if (!context) { if (!context) {
throw new Error('useAppState must be used within an AppStateProvider'); throw new Error('useAppState must be used within an AppStateProvider');
@ -429,27 +441,47 @@ export function useAppState() {
// Convenience Hooks // Convenience Hooks
// ============================ // ============================
export function useSelectedCandidate() { export function useSelectedCandidate(): {
selectedCandidate: Types.Candidate | null;
setSelectedCandidate: (candidate: Types.Candidate | null) => void;
} {
const { selectedCandidate, setSelectedCandidate } = useAppState(); const { selectedCandidate, setSelectedCandidate } = useAppState();
return { selectedCandidate, setSelectedCandidate }; return { selectedCandidate, setSelectedCandidate };
} }
export function useSelectedJob() { export function useSelectedJob(): {
selectedJob: Types.Job | null;
setSelectedJob: (job: Types.Job | null) => void;
} {
const { selectedJob, setSelectedJob } = useAppState(); const { selectedJob, setSelectedJob } = useAppState();
return { selectedJob, setSelectedJob }; return { selectedJob, setSelectedJob };
} }
export function useSelectedEmployer() { export function useSelectedEmployer(): {
selectedEmployer: Types.Employer | null;
setSelectedEmployer: (employer: Types.Employer | null) => void;
} {
const { selectedEmployer, setSelectedEmployer } = useAppState(); const { selectedEmployer, setSelectedEmployer } = useAppState();
return { selectedEmployer, setSelectedEmployer }; return { selectedEmployer, setSelectedEmployer };
} }
export const useSelectedResume = () => { export const useSelectedResume = (): {
selectedResume: Types.Resume | null;
setSelectedResume: (resume: Types.Resume | null) => void;
} => {
const { selectedResume, setSelectedResume } = useAppState(); const { selectedResume, setSelectedResume } = useAppState();
return { selectedResume, setSelectedResume }; return { selectedResume, setSelectedResume };
}; };
export function useRouteState() { export function useRouteState(): {
routeState: RouteState;
setActiveTab: (tab: string) => void;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
setFilters: (filters: Record<string, any>) => void;
setSidebarCollapsed: (collapsed: boolean) => void;
restoreLastRoute: () => void;
clearRouteState: () => void;
} {
const { const {
routeState, routeState,
setActiveTab, setActiveTab,
@ -469,7 +501,7 @@ export function useRouteState() {
}; };
} }
export function useAppInitializing() { export function useAppInitializing(): boolean {
const { isInitializing } = useAppState(); const { isInitializing } = useAppState();
return isInitializing; return isInitializing;
} }

View File

@ -5,11 +5,15 @@ const debug = false;
type ResizeCallback = () => void; type ResizeCallback = () => void;
// Define the debounce function with cancel capability // Define the debounce function with cancel capability
function debounce<T extends (...args: any[]) => void>(func: T, wait: number) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
function debounce<T extends (...args: any[]) => void>(
func: T,
wait: number
): T & { cancel: () => void } {
let timeout: NodeJS.Timeout | null = null; let timeout: NodeJS.Timeout | null = null;
let lastCall = 0; let lastCall = 0;
const debounced = function (...args: Parameters<T>) { const debounced = function (...args: Parameters<T>): void {
const now = Date.now(); const now = Date.now();
// Execute immediately if wait time has passed since last call // Execute immediately if wait time has passed since last call
@ -35,21 +39,21 @@ function debounce<T extends (...args: any[]) => void>(func: T, wait: number) {
}; };
// Add cancel method to clear pending timeout // Add cancel method to clear pending timeout
debounced.cancel = function () { debounced.cancel = function (): void {
if (timeout) { if (timeout) {
clearTimeout(timeout); clearTimeout(timeout);
timeout = null; timeout = null;
} }
}; };
return debounced; return debounced as T & { cancel: () => void };
} }
const useResizeObserverAndMutationObserver = ( const useResizeObserverAndMutationObserver = (
targetRef: RefObject<HTMLElement | null>, targetRef: RefObject<HTMLElement | null>,
scrollToRef: RefObject<HTMLElement | null> | null, scrollToRef: RefObject<HTMLElement | null> | null,
callback: ResizeCallback callback: ResizeCallback
) => { ): void => {
const callbackRef = useRef(callback); const callbackRef = useRef(callback);
const resizeObserverRef = useRef<ResizeObserver | null>(null); const resizeObserverRef = useRef<ResizeObserver | null>(null);
const mutationObserverRef = useRef<MutationObserver | null>(null); const mutationObserverRef = useRef<MutationObserver | null>(null);
@ -68,10 +72,10 @@ const useResizeObserverAndMutationObserver = (
requestAnimationFrame(() => callbackRef.current()); requestAnimationFrame(() => callbackRef.current());
}, 500); }, 500);
const resizeObserver = new ResizeObserver((_e: any) => { const resizeObserver = new ResizeObserver((): void => {
debouncedCallback('resize'); debouncedCallback('resize');
}); });
const mutationObserver = new MutationObserver((_e: any) => { const mutationObserver = new MutationObserver((): void => {
debouncedCallback('mutation'); debouncedCallback('mutation');
}); });
@ -92,7 +96,7 @@ const useResizeObserverAndMutationObserver = (
resizeObserverRef.current = resizeObserver; resizeObserverRef.current = resizeObserver;
mutationObserverRef.current = mutationObserver; mutationObserverRef.current = mutationObserver;
return () => { return (): void => {
debouncedCallback.cancel(); debouncedCallback.cancel();
resizeObserver.disconnect(); resizeObserver.disconnect();
mutationObserver.disconnect(); mutationObserver.disconnect();
@ -186,7 +190,7 @@ const useAutoScrollToBottom = (
const scrollTo = scrollToRef.current; const scrollTo = scrollToRef.current;
if (!container) return; if (!container) return;
const handleScroll = (ev: Event, pause?: number) => { const handleScroll = (ev: Event, pause?: number): void => {
const currentScrollTop = container.scrollTop; const currentScrollTop = container.scrollTop;
/* If the user is scrolling up *or* they used the scroll wheel and didn't scroll, /* If the user is scrolling up *or* they used the scroll wheel and didn't scroll,
* they may be zooming in a region; pause scrolling */ * they may be zooming in a region; pause scrolling */
@ -205,17 +209,17 @@ const useAutoScrollToBottom = (
); );
}; };
const pauseScroll = (ev: Event) => { const pauseScroll = (ev: Event): void => {
debug && console.log('Pausing for mouse movement'); debug && console.log('Pausing for mouse movement');
handleScroll(ev, 500); handleScroll(ev, 500);
}; };
const pauseClick = (ev: Event) => { const pauseClick = (ev: Event): void => {
debug && console.log('Pausing for mouse click'); debug && console.log('Pausing for mouse click');
handleScroll(ev, 1000); handleScroll(ev, 1000);
}; };
const handlePaste = () => { const handlePaste = (): void => {
console.log('handlePaste'); console.log('handlePaste');
// Delay scroll check to ensure DOM updates // Delay scroll check to ensure DOM updates
setTimeout(() => { setTimeout(() => {

View File

@ -82,7 +82,7 @@ const BetaPage: React.FC<BetaPageProps> = ({
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, []); }, []);
const handleReturn = () => { const handleReturn = (): void => {
if (onReturn) { if (onReturn) {
onReturn(); onReturn();
} else if (returnPath) { } else if (returnPath) {
@ -220,7 +220,7 @@ const BetaPage: React.FC<BetaPageProps> = ({
color: '#808080', color: '#808080',
}, },
}} }}
onClick={() => { onClick={(): void => {
navigate('/docs/beta'); navigate('/docs/beta');
}} }}
/> />

View File

@ -1,4 +1,4 @@
import React, { forwardRef, useState, useEffect, useRef } from 'react'; import React, { forwardRef, useState, useEffect, useRef, JSX } from 'react';
import { Box, Paper, Button, 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';
@ -9,6 +9,7 @@ import {
ChatMessageError, ChatMessageError,
ChatMessageStreaming, ChatMessageStreaming,
ChatMessageStatus, ChatMessageStatus,
ChatMessageMetaData,
} from 'types/types'; } from 'types/types';
import { ConversationHandle } from 'components/Conversation'; import { ConversationHandle } from 'components/Conversation';
import { BackstoryPageProps } from 'components/BackstoryTab'; import { BackstoryPageProps } from 'components/BackstoryTab';
@ -22,6 +23,20 @@ import { BackstoryQuery } from 'components/BackstoryQuery';
import { CandidatePicker } from 'components/ui/CandidatePicker'; import { CandidatePicker } from 'components/ui/CandidatePicker';
import { Scrollable } from 'components/Scrollable'; import { Scrollable } from 'components/Scrollable';
const emptyMetadata: ChatMessageMetaData = {
model: 'qwen2.5',
temperature: 0,
maxTokens: 0,
topP: 0,
frequencyPenalty: 0,
presencePenalty: 0,
stopSequences: [],
evalCount: 0,
evalDuration: 0,
promptEvalCount: 0,
promptEvalDuration: 0,
};
const defaultMessage: ChatMessage = { const defaultMessage: ChatMessage = {
status: 'done', status: 'done',
type: 'text', type: 'text',
@ -29,11 +44,11 @@ const defaultMessage: ChatMessage = {
timestamp: new Date(), timestamp: new Date(),
content: '', content: '',
role: 'user', role: 'user',
metadata: null as any, metadata: emptyMetadata,
}; };
const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>( const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
(_props: BackstoryPageProps, ref) => { (_props: BackstoryPageProps, ref): JSX.Element => {
const { apiClient } = useAuth(); const { apiClient } = useAuth();
const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate(); const { selectedCandidate, setSelectedCandidate } = useSelectedCandidate();
const [processingMessage, setProcessingMessage] = useState< const [processingMessage, setProcessingMessage] = useState<
@ -48,25 +63,9 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
const [messages, setMessages] = useState<ChatMessage[]>([]); const [messages, setMessages] = useState<ChatMessage[]>([]);
const [loading, setLoading] = useState<boolean>(false); const [loading, setLoading] = useState<boolean>(false);
const [streaming, setStreaming] = useState<boolean>(false); const [streaming, setStreaming] = useState<boolean>(false);
const messagesEndRef = useRef(null); const messagesEndRef = useRef<HTMLDivElement>(null);
// Load messages for current session const onDelete = async (session: ChatSession): Promise<void> => {
const loadMessages = async () => {
if (!chatSession?.id) return;
try {
const result = await apiClient.getChatMessages(chatSession.id);
const chatMessages: ChatMessage[] = result.data;
setMessages(chatMessages);
setProcessingMessage(null);
setStreamingMessage(null);
console.log(`getChatMessages returned ${chatMessages.length} messages.`, chatMessages);
} catch (error) {
console.error('Failed to load messages:', error);
}
};
const onDelete = async (session: ChatSession) => {
if (!session.id) { if (!session.id) {
return; return;
} }
@ -82,7 +81,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
}; };
// Send message // Send message
const sendMessage = async (message: string) => { const sendMessage = async (message: string): Promise<void> => {
if (!message.trim() || !chatSession?.id || streaming || !selectedCandidate) return; if (!message.trim() || !chatSession?.id || streaming || !selectedCandidate) return;
const messageContent = message; const messageContent = message;
@ -105,21 +104,21 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
}); });
setMessages(prev => { setMessages(prev => {
const filtered = prev.filter((m: any) => m.id !== chatMessage.id); const filtered = prev.filter(m => m.id !== chatMessage.id);
return [...filtered, chatMessage] as any; return [...filtered, chatMessage] as ChatMessage[];
}); });
try { try {
apiClient.sendMessageStream(chatMessage, { apiClient.sendMessageStream(chatMessage, {
onMessage: (msg: ChatMessage) => { onMessage: (msg: ChatMessage): void => {
setMessages(prev => { setMessages(prev => {
const filtered = prev.filter((m: any) => m.id !== msg.id); const filtered = prev.filter(m => m.id !== msg.id);
return [...filtered, msg] as any; return [...filtered, msg] as ChatMessage[];
}); });
setStreamingMessage(null); setStreamingMessage(null);
setProcessingMessage(null); setProcessingMessage(null);
}, },
onError: (error: string | ChatMessageError) => { onError: (error: string | ChatMessageError): void => {
console.log('onError:', error); console.log('onError:', error);
// 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) {
@ -133,18 +132,18 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
} }
setStreaming(false); setStreaming(false);
}, },
onStreaming: (chunk: ChatMessageStreaming) => { onStreaming: (chunk: ChatMessageStreaming): void => {
// console.log("onStreaming:", chunk); // console.log("onStreaming:", chunk);
setStreamingMessage({ setStreamingMessage({
...chunk, ...chunk,
role: 'assistant', role: 'assistant',
metadata: null as any, metadata: emptyMetadata,
}); });
}, },
onStatus: (status: ChatMessageStatus) => { onStatus: (status: ChatMessageStatus): void => {
setProcessingMessage(status); setProcessingMessage(status);
}, },
onComplete: () => { onComplete: (): void => {
console.log('onComplete'); console.log('onComplete');
setStreamingMessage(null); setStreamingMessage(null);
setProcessingMessage(null); setProcessingMessage(null);
@ -159,7 +158,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
// Auto-scroll to bottom when new messages arrive // Auto-scroll to bottom when new messages arrive
useEffect(() => { useEffect(() => {
(messagesEndRef.current as any)?.scrollIntoView({ behavior: 'smooth' }); messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
}, [messages]); }, [messages]);
// Load sessions when username changes // Load sessions when username changes
@ -182,14 +181,29 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, [selectedCandidate]); }, [selectedCandidate, apiClient, setSnack]);
// Load messages when session changes // Load messages when session changes
useEffect(() => { useEffect(() => {
const loadMessages = async (): Promise<void> => {
if (!chatSession?.id) return;
try {
const result = await apiClient.getChatMessages(chatSession.id);
const chatMessages: ChatMessage[] = result.data;
setMessages(chatMessages);
setProcessingMessage(null);
setStreamingMessage(null);
console.log(`getChatMessages returned ${chatMessages.length} messages.`, chatMessages);
} catch (error) {
console.error('Failed to load messages:', error);
}
};
if (chatSession?.id) { if (chatSession?.id) {
loadMessages(); loadMessages();
} }
}, [chatSession]); }, [chatSession, apiClient]);
if (!selectedCandidate) { if (!selectedCandidate) {
return <CandidatePicker />; return <CandidatePicker />;
@ -202,7 +216,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
status: 'done', status: 'done',
timestamp: new Date(), timestamp: new Date(),
content: `Welcome to the Backstory Chat about ${selectedCandidate.fullName}. Ask any questions you have about ${selectedCandidate.firstName}.`, content: `Welcome to the Backstory Chat about ${selectedCandidate.fullName}. Ask any questions you have about ${selectedCandidate.firstName}.`,
metadata: null as any, metadata: emptyMetadata,
}; };
return ( return (
@ -237,7 +251,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
/> />
<Button <Button
sx={{ maxWidth: 'max-content' }} sx={{ maxWidth: 'max-content' }}
onClick={() => { onClick={(): void => {
setSelectedCandidate(null); setSelectedCandidate(null);
}} }}
variant="contained" variant="contained"
@ -299,7 +313,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
{/* Fixed Message Input */} {/* Fixed Message Input */}
<Box sx={{ display: 'flex', flexShrink: 1, gap: 1 }}> <Box sx={{ display: 'flex', flexShrink: 1, gap: 1 }}>
<DeleteConfirmation <DeleteConfirmation
onDelete={() => { onDelete={(): void => {
chatSession && onDelete(chatSession); chatSession && onDelete(chatSession);
}} }}
disabled={!chatSession} disabled={!chatSession}
@ -325,7 +339,7 @@ const CandidateChatPage = forwardRef<ConversationHandle, BackstoryPageProps>(
> >
<Button <Button
variant="contained" variant="contained"
onClick={() => { onClick={(): void => {
sendMessage( sendMessage(
(backstoryTextRef.current && backstoryTextRef.current.getAndResetValue()) || '' (backstoryTextRef.current && backstoryTextRef.current.getAndResetValue()) || ''
); );

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState, useRef, useCallback } from 'react'; import React, { useEffect, useState, useRef, useCallback, JSX } from 'react';
import Avatar from '@mui/material/Avatar'; import Avatar from '@mui/material/Avatar';
import Box from '@mui/material/Box'; import Box from '@mui/material/Box';
import Tooltip from '@mui/material/Tooltip'; import Tooltip from '@mui/material/Tooltip';
@ -24,11 +24,26 @@ import {
CandidateAI, CandidateAI,
ChatMessageStatus, ChatMessageStatus,
ChatMessageError, ChatMessageError,
ChatMessageMetaData,
} from 'types/types'; } from 'types/types';
import { useAuth } from 'hooks/AuthContext'; import { useAuth } from 'hooks/AuthContext';
import { Message } from 'components/Message'; import { Message } from 'components/Message';
import { useAppState } from 'hooks/GlobalContext'; import { useAppState } from 'hooks/GlobalContext';
const emptyMetadata: ChatMessageMetaData = {
model: 'qwen2.5',
temperature: 0,
maxTokens: 0,
topP: 0,
frequencyPenalty: 0,
presencePenalty: 0,
stopSequences: [],
evalCount: 0,
evalDuration: 0,
promptEvalCount: 0,
promptEvalDuration: 0,
};
const defaultMessage: ChatMessage = { const defaultMessage: ChatMessage = {
status: 'done', status: 'done',
type: 'text', type: 'text',
@ -36,10 +51,10 @@ const defaultMessage: ChatMessage = {
timestamp: new Date(), timestamp: new Date(),
content: '', content: '',
role: 'user', role: 'user',
metadata: null as any, metadata: emptyMetadata,
}; };
const GenerateCandidate = (_props: BackstoryElementProps) => { const GenerateCandidate = (_props: BackstoryElementProps): JSX.Element => {
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);
@ -96,7 +111,7 @@ const GenerateCandidate = (_props: BackstoryElementProps) => {
return; return;
} }
const generatePersona = async (prompt: string) => { const generatePersona = async (prompt: string): Promise<void> => {
const userMessage: ChatMessageUser = { const userMessage: ChatMessageUser = {
type: 'text', type: 'text',
role: 'user', role: 'user',
@ -327,7 +342,7 @@ const GenerateCandidate = (_props: BackstoryElementProps) => {
}} }}
variant="contained" variant="contained"
disabled={processing || !canGenImage} disabled={processing || !canGenImage}
onClick={() => { onClick={(): void => {
setShouldGenerateProfile(true); setShouldGenerateProfile(true);
}} }}
> >

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { JSX } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { import {
Box, Box,
@ -197,12 +197,12 @@ interface HeroButtonProps extends ButtonProps {
children?: string; children?: string;
path: string; path: string;
} }
const HeroButton = (props: HeroButtonProps) => { const HeroButton = (props: HeroButtonProps): JSX.Element => {
const { children, onClick, path, ...rest } = props; const { children, onClick, path, ...rest } = props;
const navigate = useNavigate(); const navigate = useNavigate();
const handleClick = () => { const handleClick = (): void => {
navigate(path); navigate(path);
}; };
@ -227,7 +227,7 @@ const HeroButton = (props: HeroButtonProps) => {
const HowItWorks: React.FC = () => { const HowItWorks: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const handleGetStarted = () => { const handleGetStarted = (): void => {
navigate('/job-analysis'); navigate('/job-analysis');
}; };
@ -253,14 +253,17 @@ const HowItWorks: React.FC = () => {
component="h1" component="h1"
sx={{ sx={{
fontWeight: 700, fontWeight: 700,
fontSize: { xs: '1.5rem', md: '2rem' }, fontSize: { xs: '1.25rem', md: '1.75rem' },
mb: 2, mb: 2,
color: 'white', color: 'white',
}} }}
> >
Your complete professional story, beyond a single page Your complete professional story, beyond a single page
</Typography> </Typography>
<Typography variant="h5" sx={{ mb: 3, fontWeight: 400 }}> <Typography
variant="h5"
sx={{ mb: 3, fontWeight: 400, fontSize: { xs: '1rem', md: '1.25rem' } }}
>
Let potential employers discover the depth of your experience through interactive Let potential employers discover the depth of your experience through interactive
Q&A and tailored resumes Q&A and tailored resumes
</Typography> </Typography>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback, useRef } from 'react'; import React, { useState, useEffect, useCallback, useRef, JSX } from 'react';
import { import {
Box, Box,
Stepper, Stepper,
@ -32,7 +32,7 @@ import { LoginRestricted } from 'components/ui/LoginRestricted';
import { ResumeGenerator } from 'components/ResumeGenerator'; import { ResumeGenerator } from 'components/ResumeGenerator';
import { JobInfo } from 'components/ui/JobInfo'; import { JobInfo } from 'components/ui/JobInfo';
function WorkAddIcon() { function WorkAddIcon(): JSX.Element {
return ( return (
<Box <Box
position="relative" position="relative"
@ -99,7 +99,7 @@ const steps: Step[] = [
return { ...item, index, label: item.title.toLowerCase().replace(/ /g, '-') }; return { ...item, index, label: item.title.toLowerCase().replace(/ /g, '-') };
}); });
const capitalize = (str: string) => { const capitalize = (str: string): string => {
return str.charAt(0).toUpperCase() + str.slice(1); return str.charAt(0).toUpperCase() + str.slice(1);
}; };
@ -123,6 +123,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
if (!analysisState) { if (!analysisState) {
return; return;
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const missing = step.requiredState.find(f => !(analysisState as any)[f]); const missing = step.requiredState.find(f => !(analysisState as any)[f]);
return missing; return missing;
}, },
@ -141,6 +142,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
}; };
setAnalysisState(analysis); setAnalysisState(analysis);
for (let i = steps.length - 1; i >= 0; i--) { for (let i = steps.length - 1; i >= 0; i--) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const missing = steps[i].requiredState.find(f => !(analysis as any)[f]); const missing = steps[i].requiredState.find(f => !(analysis as any)[f]);
if (!missing) { if (!missing) {
setActiveStep(steps[i]); setActiveStep(steps[i]);
@ -166,16 +168,16 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
behavior: 'smooth', behavior: 'smooth',
}); });
} }
}, [setCanAdvance, analysisState, activeStep]); }, [setCanAdvance, analysisState, activeStep, canAccessStep]);
const handleNext = () => { const handleNext = (): void => {
if (activeStep.index === steps.length - 1) { if (activeStep.index === steps.length - 1) {
return; return;
} }
const missing = canAccessStep(steps[activeStep.index + 1]); const missing = canAccessStep(steps[activeStep.index + 1]);
if (missing) { if (missing) {
setError(`${capitalize(missing)} is necessary before continuing.`); setError(`${capitalize(missing)} is necessary before continuing.`);
return missing; return;
} }
if (activeStep.index < steps.length - 1) { if (activeStep.index < steps.length - 1) {
@ -183,7 +185,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
} }
}; };
const handleBack = () => { const handleBack = (): void => {
if (activeStep.index === 0) { if (activeStep.index === 0) {
return; return;
} }
@ -191,7 +193,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
setActiveStep(prevActiveStep => steps[prevActiveStep.index - 1]); setActiveStep(prevActiveStep => steps[prevActiveStep.index - 1]);
}; };
const moveToStep = (step: number) => { const moveToStep = (step: number): void => {
const missing = canAccessStep(steps[step]); const missing = canAccessStep(steps[step]);
if (missing) { if (missing) {
setError(`${capitalize(missing)} is needed to access this step.`); setError(`${capitalize(missing)} is needed to access this step.`);
@ -200,7 +202,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
setActiveStep(steps[step]); setActiveStep(steps[step]);
}; };
const onCandidateSelect = (candidate: Candidate) => { const onCandidateSelect = (candidate: Candidate): void => {
if (!analysisState) { if (!analysisState) {
return; return;
} }
@ -210,7 +212,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
handleNext(); handleNext();
}; };
const onJobSelect = (job: Job) => { const onJobSelect = (job: Job): void => {
if (!analysisState) { if (!analysisState) {
return; return;
} }
@ -221,16 +223,16 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
}; };
// Render function for the candidate selection step // Render function for the candidate selection step
const renderCandidateSelection = () => ( const renderCandidateSelection = (): JSX.Element => (
<CandidatePicker sx={{ pt: 1 }} onSelect={onCandidateSelect} /> <CandidatePicker sx={{ pt: 1 }} onSelect={onCandidateSelect} />
); );
const handleTabChange = (event: React.SyntheticEvent, value: string) => { const handleTabChange = (event: React.SyntheticEvent, value: string): void => {
setJobTab(value); setJobTab(value);
}; };
// Render function for the job description step // Render function for the job description step
const renderJobDescription = () => { const renderJobDescription = (): JSX.Element => {
return ( return (
<Box sx={{ mt: 3, width: '100%' }}> <Box sx={{ mt: 3, width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}> <Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
@ -251,7 +253,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
); );
}; };
const onAnalysisComplete = (skills: SkillAssessment[]) => { const onAnalysisComplete = (skills: SkillAssessment[]): void => {
if (!analysisState) { if (!analysisState) {
return; return;
} }
@ -260,9 +262,9 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
}; };
// Render function for the analysis step // Render function for the analysis step
const renderAnalysis = () => { const renderAnalysis = (): JSX.Element => {
if (!analysisState) { if (!analysisState) {
return; return <></>;
} }
if (!analysisState.job || !analysisState.candidate) { if (!analysisState.job || !analysisState.candidate) {
return ( return (
@ -286,9 +288,9 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
); );
}; };
const renderResume = () => { const renderResume = (): JSX.Element => {
if (!analysisState) { if (!analysisState) {
return; return <></>;
} }
if (!analysisState.job || !analysisState.candidate || !analysisState.analysis) { if (!analysisState.job || !analysisState.candidate || !analysisState.analysis) {
return <></>; return <></>;
@ -326,11 +328,11 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
<Step key={step.index}> <Step key={step.index}>
<StepLabel <StepLabel
sx={{ cursor: 'pointer' }} sx={{ cursor: 'pointer' }}
onClick={() => { onClick={(): void => {
moveToStep(index); moveToStep(index);
}} }}
slots={{ slots={{
stepIcon: () => ( stepIcon: (): JSX.Element => (
<Avatar <Avatar
key={step.index} key={step.index}
sx={{ sx={{
@ -410,7 +412,7 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
{activeStep.index === steps[steps.length - 1].index ? ( {activeStep.index === steps[steps.length - 1].index ? (
<Button <Button
disabled={!canAdvance} disabled={!canAdvance}
onClick={() => { onClick={(): void => {
moveToStep(0); moveToStep(0);
}} }}
variant="outlined" variant="outlined"
@ -428,10 +430,10 @@ const JobAnalysisPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProp
<Snackbar <Snackbar
open={!!error} open={!!error}
autoHideDuration={6000} autoHideDuration={6000}
onClose={() => setError(null)} onClose={(): void => setError(null)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }} anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
> >
<Alert onClose={() => setError(null)} severity="error" sx={{ width: '100%' }}> <Alert onClose={(): void => setError(null)} severity="error" sx={{ width: '100%' }}>
{error} {error}
</Alert> </Alert>
</Snackbar> </Snackbar>

View File

@ -53,7 +53,7 @@ const LoginPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProps) =>
setLoading(false); setLoading(false);
}, 3000); }, 3000);
} }
}, [error, loading]); }, [error, loading, setSnack]);
useEffect(() => { useEffect(() => {
if (tab === 'register') { if (tab === 'register') {
@ -61,7 +61,7 @@ const LoginPage: React.FC<BackstoryPageProps> = (_props: BackstoryPageProps) =>
} }
}, [tab, setTabValue]); }, [tab, setTabValue]);
const handleTabChange = (event: React.SyntheticEvent, newValue: string) => { const handleTabChange = (event: React.SyntheticEvent, newValue: string): void => {
setTabValue(newValue); setTabValue(newValue);
setSuccess(null); setSuccess(null);
}; };

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { JSX } from 'react';
import { Box, Card, CardContent, Typography, Button, LinearProgress, Stack } from '@mui/material'; import { Box, Card, CardContent, Typography, Button, LinearProgress, Stack } from '@mui/material';
import { import {
Add as AddIcon, Add as AddIcon,
@ -17,7 +17,7 @@ import { useAppState } from 'hooks/GlobalContext';
type CandidateDashboardProps = BackstoryElementProps; type CandidateDashboardProps = BackstoryElementProps;
const CandidateDashboard = (_props: CandidateDashboardProps) => { const CandidateDashboard = (_props: CandidateDashboardProps): JSX.Element => {
const { setSnack } = useAppState(); const { setSnack } = useAppState();
const navigate = useNavigate(); const navigate = useNavigate();
const { user } = useAuth(); const { user } = useAuth();
@ -69,7 +69,7 @@ const CandidateDashboard = (_props: CandidateDashboardProps) => {
variant="contained" variant="contained"
color="primary" color="primary"
sx={{ mt: 1 }} sx={{ mt: 1 }}
onClick={e => { onClick={(e): void => {
e.stopPropagation(); e.stopPropagation();
navigate('/candidate/profile'); navigate('/candidate/profile');
}} }}

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, JSX } from 'react';
import { import {
Box, Box,
Button, Button,
@ -71,7 +71,7 @@ interface TabPanelProps {
value: number; value: number;
} }
function TabPanel(props: TabPanelProps) { function TabPanel(props: TabPanelProps): JSX.Element {
const { children, value, index, ...other } = props; const { children, value, index, ...other } = props;
return ( return (
@ -166,12 +166,12 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
} }
// Handle tab change // Handle tab change
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { const handleTabChange = (event: React.SyntheticEvent, newValue: number): void => {
setTabValue(newValue); setTabValue(newValue);
}; };
// Handle form input changes // Handle form input changes
const handleInputChange = (field: string, value: any) => { const handleInputChange = (field: string, value: boolean | string): void => {
setFormData({ setFormData({
...formData, ...formData,
[field]: value, [field]: value,
@ -179,7 +179,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
}; };
// Handle profile image upload // Handle profile image upload
const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>) => { const handleImageUpload = async (e: React.ChangeEvent<HTMLInputElement>): Promise<void> => {
if (!e.target.files || !e.target.files[0]) { if (!e.target.files || !e.target.files[0]) {
return; return;
} }
@ -210,7 +210,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
}; };
// Toggle edit mode for a section // Toggle edit mode for a section
const toggleEditMode = (section: string) => { const toggleEditMode = (section: string): void => {
setEditMode({ setEditMode({
...editMode, ...editMode,
[section]: !editMode[section], [section]: !editMode[section],
@ -218,7 +218,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
}; };
// Save changes // Save changes
const handleSave = async (section: string) => { const handleSave = async (section: string): Promise<void> => {
setLoading(true); setLoading(true);
try { try {
if (candidate.id) { if (candidate.id) {
@ -239,13 +239,13 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
}; };
// Cancel edit // Cancel edit
const handleCancel = (section: string) => { const handleCancel = (section: string): void => {
setFormData(candidate); setFormData(candidate);
toggleEditMode(section); toggleEditMode(section);
}; };
// Add new skill // Add new skill
const handleAddSkill = () => { const handleAddSkill = (): void => {
if (newSkill.name && newSkill.category) { if (newSkill.name && newSkill.category) {
const updatedSkills = [...(formData.skills || []), newSkill as Types.Skill]; const updatedSkills = [...(formData.skills || []), newSkill as Types.Skill];
setFormData({ ...formData, skills: updatedSkills }); setFormData({ ...formData, skills: updatedSkills });
@ -260,13 +260,13 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
}; };
// Remove skill // Remove skill
const handleRemoveSkill = (index: number) => { const handleRemoveSkill = (index: number): void => {
const updatedSkills = (formData.skills || []).filter((_, i) => i !== index); const updatedSkills = (formData.skills || []).filter((_, i) => i !== index);
setFormData({ ...formData, skills: updatedSkills }); setFormData({ ...formData, skills: updatedSkills });
}; };
// Add new work experience // Add new work experience
const handleAddExperience = () => { const handleAddExperience = (): void => {
if (newExperience.companyName && newExperience.position) { if (newExperience.companyName && newExperience.position) {
const updatedExperience = [ const updatedExperience = [
...(formData.experience || []), ...(formData.experience || []),
@ -287,13 +287,13 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
}; };
// Remove work experience // Remove work experience
const handleRemoveExperience = (index: number) => { const handleRemoveExperience = (index: number): void => {
const updatedExperience = (formData.experience || []).filter((_, i) => i !== index); const updatedExperience = (formData.experience || []).filter((_, i) => i !== index);
setFormData({ ...formData, experience: updatedExperience }); setFormData({ ...formData, experience: updatedExperience });
}; };
// Basic Information Tab // Basic Information Tab
const renderBasicInfo = () => ( const renderBasicInfo = (): JSX.Element => (
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
@ -368,7 +368,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
control={ control={
<Switch <Switch
checked={formData.isPublic} checked={formData.isPublic}
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>): void =>
handleInputChange('isPublic', event.target.checked) handleInputChange('isPublic', event.target.checked)
} }
/> />
@ -394,7 +394,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
fullWidth fullWidth
label="First Name" label="First Name"
value={formData.firstName || ''} value={formData.firstName || ''}
onChange={e => handleInputChange('firstName', e.target.value)} onChange={(e): void => handleInputChange('firstName', e.target.value)}
variant="outlined" variant="outlined"
/> />
) : ( ) : (
@ -411,7 +411,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
fullWidth fullWidth
label="Last Name" label="Last Name"
value={formData.lastName || ''} value={formData.lastName || ''}
onChange={e => handleInputChange('lastName', e.target.value)} onChange={(e): void => handleInputChange('lastName', e.target.value)}
variant="outlined" variant="outlined"
/> />
) : ( ) : (
@ -436,7 +436,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
fullWidth fullWidth
label="Phone" label="Phone"
value={formData.phone || ''} value={formData.phone || ''}
onChange={e => handleInputChange('phone', e.target.value)} onChange={(e): void => handleInputChange('phone', e.target.value)}
variant="outlined" variant="outlined"
/> />
) : ( ) : (
@ -458,7 +458,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
rows={3} rows={3}
label="Professional Summary" label="Professional Summary"
value={formData.description || ''} value={formData.description || ''}
onChange={e => handleInputChange('description', e.target.value)} onChange={(e): void => handleInputChange('description', e.target.value)}
variant="outlined" variant="outlined"
/> />
) : ( ) : (
@ -493,7 +493,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
<> <>
<Button <Button
variant="outlined" variant="outlined"
onClick={() => handleCancel('basic')} onClick={(): void => handleCancel('basic')}
startIcon={<Cancel />} startIcon={<Cancel />}
fullWidth={isMobile} fullWidth={isMobile}
> >
@ -501,7 +501,9 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
onClick={() => handleSave('basic')} onClick={(): void => {
handleSave('basic');
}}
startIcon={loading ? <CircularProgress size={20} color="inherit" /> : <Save />} startIcon={loading ? <CircularProgress size={20} color="inherit" /> : <Save />}
disabled={loading} disabled={loading}
fullWidth={isMobile} fullWidth={isMobile}
@ -512,7 +514,9 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
) : ( ) : (
<Button <Button
variant="outlined" variant="outlined"
onClick={() => toggleEditMode('basic')} onClick={(): void => {
toggleEditMode('basic');
}}
startIcon={<Edit />} startIcon={<Edit />}
fullWidth={isMobile} fullWidth={isMobile}
> >
@ -525,7 +529,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
); );
// Skills Tab // Skills Tab
const renderSkills = () => ( const renderSkills = (): JSX.Element => (
<Box> <Box>
<Box <Box
sx={{ sx={{
@ -541,7 +545,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
<Button <Button
variant="outlined" variant="outlined"
startIcon={<Add />} startIcon={<Add />}
onClick={() => setSkillDialog(true)} onClick={(): void => setSkillDialog(true)}
fullWidth={isMobile} fullWidth={isMobile}
size={isMobile ? 'small' : 'medium'} size={isMobile ? 'small' : 'medium'}
> >
@ -605,7 +609,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
</Box> </Box>
<IconButton <IconButton
size="small" size="small"
onClick={() => handleRemoveSkill(index)} onClick={(): void => handleRemoveSkill(index)}
color="error" color="error"
sx={{ ml: 1 }} sx={{ ml: 1 }}
> >
@ -627,7 +631,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
); );
// Experience Tab // Experience Tab
const renderExperience = () => ( const renderExperience = (): JSX.Element => (
<Box> <Box>
<Box <Box
sx={{ sx={{
@ -643,7 +647,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
<Button <Button
variant="outlined" variant="outlined"
startIcon={<Add />} startIcon={<Add />}
onClick={() => setExperienceDialog(true)} onClick={(): void => setExperienceDialog(true)}
fullWidth={isMobile} fullWidth={isMobile}
size={isMobile ? 'small' : 'medium'} size={isMobile ? 'small' : 'medium'}
> >
@ -721,7 +725,9 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
)} )}
</Box> </Box>
<IconButton <IconButton
onClick={() => handleRemoveExperience(index)} onClick={(): void => {
handleRemoveExperience(index);
}}
color="error" color="error"
size="small" size="small"
sx={{ sx={{
@ -752,7 +758,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
</Box> </Box>
); );
const renderEducation = () => ( const renderEducation = (): JSX.Element => (
<Box> <Box>
<Box <Box
sx={{ sx={{
@ -868,7 +874,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
{/* Add Skill Dialog */} {/* Add Skill Dialog */}
<Dialog <Dialog
open={skillDialog} open={skillDialog}
onClose={() => setSkillDialog(false)} onClose={(): void => setSkillDialog(false)}
maxWidth="sm" maxWidth="sm"
fullWidth fullWidth
fullScreen={isMobile} fullScreen={isMobile}
@ -896,7 +902,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
fullWidth fullWidth
label="Skill Name" label="Skill Name"
value={newSkill.name || ''} value={newSkill.name || ''}
onChange={e => setNewSkill({ ...newSkill, name: e.target.value })} onChange={(e): void => setNewSkill({ ...newSkill, name: e.target.value })}
size={isMobile ? 'small' : 'medium'} size={isMobile ? 'small' : 'medium'}
/> />
</Grid> </Grid>
@ -905,7 +911,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
fullWidth fullWidth
label="Category" label="Category"
value={newSkill.category || ''} value={newSkill.category || ''}
onChange={e => setNewSkill({ ...newSkill, category: e.target.value })} onChange={(e): void => setNewSkill({ ...newSkill, category: e.target.value })}
placeholder="e.g., Programming, Design, Marketing" placeholder="e.g., Programming, Design, Marketing"
size={isMobile ? 'small' : 'medium'} size={isMobile ? 'small' : 'medium'}
/> />
@ -915,7 +921,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
<InputLabel>Proficiency Level</InputLabel> <InputLabel>Proficiency Level</InputLabel>
<Select <Select
value={newSkill.level || 'beginner'} value={newSkill.level || 'beginner'}
onChange={e => onChange={(e): void =>
setNewSkill({ setNewSkill({
...newSkill, ...newSkill,
level: e.target.value as Types.SkillLevel, level: e.target.value as Types.SkillLevel,
@ -936,7 +942,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
type="number" type="number"
label="Years of Experience" label="Years of Experience"
value={newSkill.yearsOfExperience || 0} value={newSkill.yearsOfExperience || 0}
onChange={e => onChange={(e): void =>
setNewSkill({ setNewSkill({
...newSkill, ...newSkill,
yearsOfExperience: parseInt(e.target.value) || 0, yearsOfExperience: parseInt(e.target.value) || 0,
@ -955,7 +961,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
}} }}
> >
<Button <Button
onClick={() => setSkillDialog(false)} onClick={(): void => setSkillDialog(false)}
fullWidth={isMobile} fullWidth={isMobile}
size={isMobile ? 'small' : 'medium'} size={isMobile ? 'small' : 'medium'}
> >
@ -975,7 +981,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
{/* Add Experience Dialog */} {/* Add Experience Dialog */}
<Dialog <Dialog
open={experienceDialog} open={experienceDialog}
onClose={() => setExperienceDialog(false)} onClose={(): void => setExperienceDialog(false)}
maxWidth="md" maxWidth="md"
fullWidth fullWidth
fullScreen={isMobile} fullScreen={isMobile}
@ -1003,7 +1009,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
fullWidth fullWidth
label="Company Name" label="Company Name"
value={newExperience.companyName || ''} value={newExperience.companyName || ''}
onChange={e => onChange={(e): void =>
setNewExperience({ setNewExperience({
...newExperience, ...newExperience,
companyName: e.target.value, companyName: e.target.value,
@ -1017,7 +1023,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
fullWidth fullWidth
label="Position/Title" label="Position/Title"
value={newExperience.position || ''} value={newExperience.position || ''}
onChange={e => onChange={(e): void =>
setNewExperience({ setNewExperience({
...newExperience, ...newExperience,
position: e.target.value, position: e.target.value,
@ -1032,7 +1038,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
type="date" type="date"
label="Start Date" label="Start Date"
value={newExperience.startDate?.toISOString().split('T')[0] || ''} value={newExperience.startDate?.toISOString().split('T')[0] || ''}
onChange={e => onChange={(e): void =>
setNewExperience({ setNewExperience({
...newExperience, ...newExperience,
startDate: new Date(e.target.value), startDate: new Date(e.target.value),
@ -1047,7 +1053,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
control={ control={
<Switch <Switch
checked={newExperience.isCurrent || false} checked={newExperience.isCurrent || false}
onChange={e => onChange={(e): void =>
setNewExperience({ setNewExperience({
...newExperience, ...newExperience,
isCurrent: e.target.checked, isCurrent: e.target.checked,
@ -1071,7 +1077,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
rows={isMobile ? 3 : 4} rows={isMobile ? 3 : 4}
label="Job Description" label="Job Description"
value={newExperience.description || ''} value={newExperience.description || ''}
onChange={e => onChange={(e): void =>
setNewExperience({ setNewExperience({
...newExperience, ...newExperience,
description: e.target.value, description: e.target.value,
@ -1091,7 +1097,7 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
}} }}
> >
<Button <Button
onClick={() => setExperienceDialog(false)} onClick={(): void => setExperienceDialog(false)}
fullWidth={isMobile} fullWidth={isMobile}
size={isMobile ? 'small' : 'medium'} size={isMobile ? 'small' : 'medium'}
> >
@ -1112,10 +1118,10 @@ const CandidateProfile: React.FC<BackstoryPageProps> = (_props: BackstoryPagePro
<Snackbar <Snackbar
open={snackbar.open} open={snackbar.open}
autoHideDuration={6000} autoHideDuration={6000}
onClose={() => setSnackbar({ ...snackbar, open: false })} onClose={(): void => setSnackbar({ ...snackbar, open: false })}
> >
<Alert <Alert
onClose={() => setSnackbar({ ...snackbar, open: false })} onClose={(): void => setSnackbar({ ...snackbar, open: false })}
severity={snackbar.severity} severity={snackbar.severity}
sx={{ width: '100%' }} sx={{ width: '100%' }}
> >

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { JSX, useState } from 'react';
import { import {
Paper, Paper,
Box, Box,
@ -24,13 +24,13 @@ import {
InputAdornment, InputAdornment,
} from '@mui/material'; } from '@mui/material';
import { Visibility, VisibilityOff } from '@mui/icons-material'; import { Visibility, VisibilityOff } from '@mui/icons-material';
import { ApiClient } from 'services/api-client'; import { ApiClient, RegistrationResponse } from 'services/api-client';
import { RegistrationSuccessDialog } from 'components/EmailVerificationComponents'; import { RegistrationSuccessDialog } from 'components/EmailVerificationComponents';
import { useAuth } from 'hooks/AuthContext'; import { useAuth } from 'hooks/AuthContext';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
// Candidate Registration Form // Candidate Registration Form
const CandidateRegistrationForm = () => { const CandidateRegistrationForm = (): JSX.Element => {
const { apiClient } = useAuth(); const { apiClient } = useAuth();
const navigate = useNavigate(); const navigate = useNavigate();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
@ -48,13 +48,13 @@ const CandidateRegistrationForm = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({}); const [errors, setErrors] = useState<Record<string, string>>({});
const [showSuccess, setShowSuccess] = useState(false); const [showSuccess, setShowSuccess] = useState(false);
const [registrationResult, setRegistrationResult] = useState<any>(null); const [registrationResult, setRegistrationResult] = useState<RegistrationResponse | null>(null);
// Password visibility states // Password visibility states
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const [showConfirmPassword, setShowConfirmPassword] = useState(false); const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const validateForm = () => { const validateForm = (): boolean => {
const newErrors: Record<string, string> = {}; const newErrors: Record<string, string> = {};
// Email validation // Email validation
@ -128,7 +128,7 @@ const CandidateRegistrationForm = () => {
return errors.length > 0 ? [`Password must contain ${errors.join(', ')}`] : []; return errors.length > 0 ? [`Password must contain ${errors.join(', ')}`] : [];
}; };
const handleInputChange = (field: string, value: string) => { const handleInputChange = (field: string, value: string): void => {
setFormData(prev => ({ ...prev, [field]: value })); setFormData(prev => ({ ...prev, [field]: value }));
// Clear error when user starts typing // Clear error when user starts typing
@ -137,7 +137,7 @@ const CandidateRegistrationForm = () => {
} }
}; };
const handleSubmit = async () => { const handleSubmit = async (): Promise<void> => {
if (!validateForm()) { if (!validateForm()) {
return; return;
} }
@ -158,24 +158,30 @@ const CandidateRegistrationForm = () => {
setRegistrationResult(result); setRegistrationResult(result);
setShowSuccess(true); setShowSuccess(true);
} catch (error: any) { } catch (error) {
if (error.message.includes('already exists')) { if (error instanceof Error) {
if (error.message.includes('email')) { if (error.message.includes('already exists')) {
setErrors({ email: 'An account with this email already exists' }); if (error.message.includes('email')) {
} else if (error.message.includes('username')) { setErrors({ email: 'An account with this email already exists' });
setErrors({ username: 'This username is already taken' }); } else if (error.message.includes('username')) {
setErrors({ username: 'This username is already taken' });
}
} else {
setErrors({
general: error.message || 'Registration failed. Please try again.',
});
} }
} else { } else {
setErrors({ console.error(error);
general: error.message || 'Registration failed. Please try again.',
});
} }
} finally { } finally {
setLoading(false); setLoading(false);
} }
}; };
const getPasswordStrength = (password: string) => { const getPasswordStrength = (
password: string
): { level: string; color: string; value: number } => {
const validations = [ const validations = [
password.length >= 8, password.length >= 8,
/[a-z]/.test(password), /[a-z]/.test(password),
@ -210,7 +216,7 @@ const CandidateRegistrationForm = () => {
label="Email Address" label="Email Address"
type="email" type="email"
value={formData.email} value={formData.email}
onChange={e => handleInputChange('email', e.target.value)} onChange={(e): void => handleInputChange('email', e.target.value)}
placeholder="your.email@example.com" placeholder="your.email@example.com"
error={!!errors.email} error={!!errors.email}
helperText={errors.email} helperText={errors.email}
@ -221,7 +227,7 @@ const CandidateRegistrationForm = () => {
fullWidth fullWidth
label="Username" label="Username"
value={formData.username} value={formData.username}
onChange={e => handleInputChange('username', e.target.value.toLowerCase())} onChange={(e): void => handleInputChange('username', e.target.value.toLowerCase())}
placeholder="johndoe123" placeholder="johndoe123"
error={!!errors.username} error={!!errors.username}
helperText={errors.username} helperText={errors.username}
@ -233,7 +239,7 @@ const CandidateRegistrationForm = () => {
fullWidth fullWidth
label="First Name" label="First Name"
value={formData.firstName} value={formData.firstName}
onChange={e => handleInputChange('firstName', e.target.value)} onChange={(e): void => handleInputChange('firstName', e.target.value)}
placeholder="John" placeholder="John"
error={!!errors.firstName} error={!!errors.firstName}
helperText={errors.firstName} helperText={errors.firstName}
@ -243,7 +249,7 @@ const CandidateRegistrationForm = () => {
fullWidth fullWidth
label="Last Name" label="Last Name"
value={formData.lastName} value={formData.lastName}
onChange={e => handleInputChange('lastName', e.target.value)} onChange={(e): void => handleInputChange('lastName', e.target.value)}
placeholder="Doe" placeholder="Doe"
error={!!errors.lastName} error={!!errors.lastName}
helperText={errors.lastName} helperText={errors.lastName}
@ -256,7 +262,7 @@ const CandidateRegistrationForm = () => {
label="Phone Number" label="Phone Number"
type="tel" type="tel"
value={formData.phone} value={formData.phone}
onChange={e => handleInputChange('phone', e.target.value)} onChange={(e): void => handleInputChange('phone', e.target.value)}
placeholder="+1 (555) 123-4567" placeholder="+1 (555) 123-4567"
error={!!errors.phone} error={!!errors.phone}
helperText={errors.phone || 'Optional'} helperText={errors.phone || 'Optional'}
@ -268,7 +274,7 @@ const CandidateRegistrationForm = () => {
label="Password" label="Password"
type={showPassword ? 'text' : 'password'} type={showPassword ? 'text' : 'password'}
value={formData.password} value={formData.password}
onChange={e => handleInputChange('password', e.target.value)} onChange={(e): void => handleInputChange('password', e.target.value)}
placeholder="Create a strong password" placeholder="Create a strong password"
error={!!errors.password} error={!!errors.password}
helperText={errors.password} helperText={errors.password}
@ -278,8 +284,8 @@ const CandidateRegistrationForm = () => {
<InputAdornment position="end"> <InputAdornment position="end">
<IconButton <IconButton
aria-label="toggle password visibility" aria-label="toggle password visibility"
onClick={() => setShowPassword(!showPassword)} onClick={(): void => setShowPassword(!showPassword)}
onMouseDown={e => e.preventDefault()} onMouseDown={(e): void => e.preventDefault()}
edge="end" edge="end"
> >
{showPassword ? <VisibilityOff /> : <Visibility />} {showPassword ? <VisibilityOff /> : <Visibility />}
@ -293,6 +299,7 @@ const CandidateRegistrationForm = () => {
<LinearProgress <LinearProgress
variant="determinate" variant="determinate"
value={passwordStrength.value} value={passwordStrength.value}
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
color={passwordStrength.color as any} color={passwordStrength.color as any}
sx={{ height: 6, borderRadius: 3 }} sx={{ height: 6, borderRadius: 3 }}
/> />
@ -312,7 +319,7 @@ const CandidateRegistrationForm = () => {
label="Confirm Password" label="Confirm Password"
type={showConfirmPassword ? 'text' : 'password'} type={showConfirmPassword ? 'text' : 'password'}
value={formData.confirmPassword} value={formData.confirmPassword}
onChange={e => handleInputChange('confirmPassword', e.target.value)} onChange={(e): void => handleInputChange('confirmPassword', e.target.value)}
placeholder="Confirm your password" placeholder="Confirm your password"
error={!!errors.confirmPassword} error={!!errors.confirmPassword}
helperText={errors.confirmPassword} helperText={errors.confirmPassword}
@ -322,8 +329,8 @@ const CandidateRegistrationForm = () => {
<InputAdornment position="end"> <InputAdornment position="end">
<IconButton <IconButton
aria-label="toggle confirm password visibility" aria-label="toggle confirm password visibility"
onClick={() => setShowConfirmPassword(!showConfirmPassword)} onClick={(): void => setShowConfirmPassword(!showConfirmPassword)}
onMouseDown={e => e.preventDefault()} onMouseDown={(e): void => e.preventDefault()}
edge="end" edge="end"
> >
{showConfirmPassword ? <VisibilityOff /> : <Visibility />} {showConfirmPassword ? <VisibilityOff /> : <Visibility />}
@ -358,7 +365,7 @@ const CandidateRegistrationForm = () => {
Already have an account?{' '} Already have an account?{' '}
<Link <Link
component="button" component="button"
onClick={e => { onClick={(e): void => {
e.preventDefault(); e.preventDefault();
navigate('/login'); navigate('/login');
}} }}
@ -372,7 +379,7 @@ const CandidateRegistrationForm = () => {
{showSuccess && registrationResult && ( {showSuccess && registrationResult && (
<RegistrationSuccessDialog <RegistrationSuccessDialog
open={showSuccess} open={showSuccess}
onClose={() => setShowSuccess(false)} onClose={(): void => setShowSuccess(false)}
email={registrationResult.email} email={registrationResult.email}
userType="candidate" userType="candidate"
/> />
@ -382,7 +389,7 @@ const CandidateRegistrationForm = () => {
}; };
// Employer Registration Form // Employer Registration Form
const EmployerRegistrationForm = () => { const EmployerRegistrationForm = (): JSX.Element => {
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
email: '', email: '',
username: '', username: '',
@ -399,7 +406,7 @@ const EmployerRegistrationForm = () => {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({}); const [errors, setErrors] = useState<Record<string, string>>({});
const [showSuccess, setShowSuccess] = useState(false); const [showSuccess, setShowSuccess] = useState(false);
const [registrationResult, setRegistrationResult] = useState<any>(null); const [registrationResult, setRegistrationResult] = useState<RegistrationResponse | null>(null);
// Password visibility states // Password visibility states
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
@ -430,7 +437,7 @@ const EmployerRegistrationForm = () => {
'1000+ employees', '1000+ employees',
]; ];
const validateForm = () => { const validateForm = (): boolean => {
const newErrors: Record<string, string> = {}; const newErrors: Record<string, string> = {};
// Email validation // Email validation
@ -514,7 +521,7 @@ const EmployerRegistrationForm = () => {
return errors.length > 0 ? [`Password must contain ${errors.join(', ')}`] : []; return errors.length > 0 ? [`Password must contain ${errors.join(', ')}`] : [];
}; };
const handleInputChange = (field: string, value: string) => { const handleInputChange = (field: string, value: string): void => {
setFormData(prev => ({ ...prev, [field]: value })); setFormData(prev => ({ ...prev, [field]: value }));
// Clear error when user starts typing // Clear error when user starts typing
@ -523,7 +530,7 @@ const EmployerRegistrationForm = () => {
} }
}; };
const handleSubmit = async () => { const handleSubmit = async (): Promise<void> => {
if (!validateForm()) { if (!validateForm()) {
return; return;
} }
@ -547,17 +554,21 @@ const EmployerRegistrationForm = () => {
setRegistrationResult(result); setRegistrationResult(result);
setShowSuccess(true); setShowSuccess(true);
} catch (error: any) { } catch (error) {
if (error.message.includes('already exists')) { if (error instanceof Error) {
if (error.message.includes('email')) { if (error.message.includes('already exists')) {
setErrors({ email: 'An account with this email already exists' }); if (error.message.includes('email')) {
} else if (error.message.includes('username')) { setErrors({ email: 'An account with this email already exists' });
setErrors({ username: 'This username is already taken' }); } else if (error.message.includes('username')) {
setErrors({ username: 'This username is already taken' });
}
} else {
setErrors({
general: error.message || 'Registration failed. Please try again.',
});
} }
} else { } else {
setErrors({ console.error(error);
general: error.message || 'Registration failed. Please try again.',
});
} }
} finally { } finally {
setLoading(false); setLoading(false);
@ -590,7 +601,7 @@ const EmployerRegistrationForm = () => {
label="Email Address" label="Email Address"
type="email" type="email"
value={formData.email} value={formData.email}
onChange={e => handleInputChange('email', e.target.value)} onChange={(e): void => handleInputChange('email', e.target.value)}
placeholder="company@example.com" placeholder="company@example.com"
error={!!errors.email} error={!!errors.email}
helperText={errors.email} helperText={errors.email}
@ -600,7 +611,9 @@ const EmployerRegistrationForm = () => {
fullWidth fullWidth
label="Username" label="Username"
value={formData.username} value={formData.username}
onChange={e => handleInputChange('username', e.target.value.toLowerCase())} onChange={(e): void =>
handleInputChange('username', e.target.value.toLowerCase())
}
placeholder="company123" placeholder="company123"
error={!!errors.username} error={!!errors.username}
helperText={errors.username} helperText={errors.username}
@ -614,7 +627,7 @@ const EmployerRegistrationForm = () => {
label="Password" label="Password"
type={showPassword ? 'text' : 'password'} type={showPassword ? 'text' : 'password'}
value={formData.password} value={formData.password}
onChange={e => handleInputChange('password', e.target.value)} onChange={(e): void => handleInputChange('password', e.target.value)}
placeholder="Create a strong password" placeholder="Create a strong password"
error={!!errors.password} error={!!errors.password}
helperText={errors.password} helperText={errors.password}
@ -624,8 +637,8 @@ const EmployerRegistrationForm = () => {
<InputAdornment position="end"> <InputAdornment position="end">
<IconButton <IconButton
aria-label="toggle password visibility" aria-label="toggle password visibility"
onClick={() => setShowPassword(!showPassword)} onClick={(): void => setShowPassword(!showPassword)}
onMouseDown={e => e.preventDefault()} onMouseDown={(e): void => e.preventDefault()}
edge="end" edge="end"
> >
{showPassword ? <VisibilityOff /> : <Visibility />} {showPassword ? <VisibilityOff /> : <Visibility />}
@ -639,7 +652,7 @@ const EmployerRegistrationForm = () => {
label="Confirm Password" label="Confirm Password"
type={showConfirmPassword ? 'text' : 'password'} type={showConfirmPassword ? 'text' : 'password'}
value={formData.confirmPassword} value={formData.confirmPassword}
onChange={e => handleInputChange('confirmPassword', e.target.value)} onChange={(e): void => handleInputChange('confirmPassword', e.target.value)}
placeholder="Confirm your password" placeholder="Confirm your password"
error={!!errors.confirmPassword} error={!!errors.confirmPassword}
helperText={errors.confirmPassword} helperText={errors.confirmPassword}
@ -649,8 +662,8 @@ const EmployerRegistrationForm = () => {
<InputAdornment position="end"> <InputAdornment position="end">
<IconButton <IconButton
aria-label="toggle confirm password visibility" aria-label="toggle confirm password visibility"
onClick={() => setShowConfirmPassword(!showConfirmPassword)} onClick={(): void => setShowConfirmPassword(!showConfirmPassword)}
onMouseDown={e => e.preventDefault()} onMouseDown={(e): void => e.preventDefault()}
edge="end" edge="end"
> >
{showConfirmPassword ? <VisibilityOff /> : <Visibility />} {showConfirmPassword ? <VisibilityOff /> : <Visibility />}
@ -674,7 +687,7 @@ const EmployerRegistrationForm = () => {
fullWidth fullWidth
label="Company Name" label="Company Name"
value={formData.companyName} value={formData.companyName}
onChange={e => handleInputChange('companyName', e.target.value)} onChange={(e): void => handleInputChange('companyName', e.target.value)}
placeholder="Your Company Inc." placeholder="Your Company Inc."
error={!!errors.companyName} error={!!errors.companyName}
helperText={errors.companyName} helperText={errors.companyName}
@ -686,7 +699,7 @@ const EmployerRegistrationForm = () => {
<InputLabel>Industry</InputLabel> <InputLabel>Industry</InputLabel>
<Select <Select
value={formData.industry} value={formData.industry}
onChange={e => handleInputChange('industry', e.target.value)} onChange={(e): void => handleInputChange('industry', e.target.value)}
label="Industry" label="Industry"
> >
{industryOptions.map(industry => ( {industryOptions.map(industry => (
@ -702,7 +715,7 @@ const EmployerRegistrationForm = () => {
<InputLabel>Company Size</InputLabel> <InputLabel>Company Size</InputLabel>
<Select <Select
value={formData.companySize} value={formData.companySize}
onChange={e => handleInputChange('companySize', e.target.value)} onChange={(e): void => handleInputChange('companySize', e.target.value)}
label="Company Size" label="Company Size"
> >
{companySizeOptions.map(size => ( {companySizeOptions.map(size => (
@ -722,7 +735,7 @@ const EmployerRegistrationForm = () => {
multiline multiline
rows={4} rows={4}
value={formData.companyDescription} value={formData.companyDescription}
onChange={e => handleInputChange('companyDescription', e.target.value)} onChange={(e): void => handleInputChange('companyDescription', e.target.value)}
placeholder="Tell us about your company, what you do, your mission, and what makes you unique..." placeholder="Tell us about your company, what you do, your mission, and what makes you unique..."
error={!!errors.companyDescription} error={!!errors.companyDescription}
helperText={ helperText={
@ -739,7 +752,7 @@ const EmployerRegistrationForm = () => {
label="Website URL" label="Website URL"
type="url" type="url"
value={formData.websiteUrl} value={formData.websiteUrl}
onChange={e => handleInputChange('websiteUrl', e.target.value)} onChange={(e): void => handleInputChange('websiteUrl', e.target.value)}
placeholder="https://www.yourcompany.com" placeholder="https://www.yourcompany.com"
error={!!errors.websiteUrl} error={!!errors.websiteUrl}
helperText={errors.websiteUrl || 'Optional'} helperText={errors.websiteUrl || 'Optional'}
@ -749,7 +762,7 @@ const EmployerRegistrationForm = () => {
label="Phone Number" label="Phone Number"
type="tel" type="tel"
value={formData.phone} value={formData.phone}
onChange={e => handleInputChange('phone', e.target.value)} onChange={(e): void => handleInputChange('phone', e.target.value)}
placeholder="+1 (555) 123-4567" placeholder="+1 (555) 123-4567"
error={!!errors.phone} error={!!errors.phone}
helperText={errors.phone || 'Optional'} helperText={errors.phone || 'Optional'}
@ -792,7 +805,7 @@ const EmployerRegistrationForm = () => {
{showSuccess && registrationResult && ( {showSuccess && registrationResult && (
<RegistrationSuccessDialog <RegistrationSuccessDialog
open={showSuccess} open={showSuccess}
onClose={() => setShowSuccess(false)} onClose={(): void => setShowSuccess(false)}
email={registrationResult.email} email={registrationResult.email}
userType="employer" userType="employer"
/> />
@ -803,6 +816,7 @@ const EmployerRegistrationForm = () => {
// Registration Type Selector Component // Registration Type Selector Component
export function RegistrationTypeSelector() { export function RegistrationTypeSelector() {
const navigate = useNavigate();
return ( return (
<Paper elevation={3}> <Paper elevation={3}>
<Box sx={{ p: 5 }}> <Box sx={{ p: 5 }}>
@ -829,7 +843,9 @@ export function RegistrationTypeSelector() {
borderColor: 'primary.main', borderColor: 'primary.main',
}, },
}} }}
onClick={() => (window.location.href = '/register/candidate')} onClick={(): void => {
navigate('/register/candidate');
}}
> >
<CardContent sx={{ textAlign: 'center', py: 4 }}> <CardContent sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="h1" sx={{ mb: 2 }}> <Typography variant="h1" sx={{ mb: 2 }}>
@ -862,7 +878,9 @@ export function RegistrationTypeSelector() {
borderColor: 'primary.main', borderColor: 'primary.main',
}, },
}} }}
onClick={() => (window.location.href = '/register/employer')} onClick={(): void => {
navigate('/register/employer');
}}
> >
<CardContent sx={{ textAlign: 'center', py: 4 }}> <CardContent sx={{ textAlign: 'center', py: 4 }}>
<Typography variant="h1" sx={{ mb: 2 }}> <Typography variant="h1" sx={{ mb: 2 }}>

View File

@ -1,4 +1,4 @@
import React, { useState, useEffect, ReactElement } from 'react'; import React, { useState, useEffect, ReactElement, JSX } from 'react';
// import FormGroup from '@mui/material/FormGroup'; // import FormGroup from '@mui/material/FormGroup';
// import FormControlLabel from '@mui/material/FormControlLabel'; // import FormControlLabel from '@mui/material/FormControlLabel';
// import Switch from '@mui/material/Switch'; // import Switch from '@mui/material/Switch';
@ -39,7 +39,7 @@ const SystemInfoComponent: React.FC<{
}> = ({ systemInfo }) => { }> = ({ systemInfo }) => {
const [systemElements, setSystemElements] = useState<ReactElement[]>([]); const [systemElements, setSystemElements] = useState<ReactElement[]>([]);
const convertToSymbols = (text: string) => { const convertToSymbols = (text: string): string => {
return text return text
.replace(/\(R\)/g, '®') // Replace (R) with the ® symbol .replace(/\(R\)/g, '®') // Replace (R) with the ® symbol
.replace(/\(C\)/g, '©') // Replace (C) with the © symbol .replace(/\(C\)/g, '©') // Replace (C) with the © symbol
@ -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): JSX.Element => {
const { apiClient } = useAuth(); const { apiClient } = useAuth();
const { setSnack } = useAppState(); const { setSnack } = useAppState();
// const [editSystemPrompt, setEditSystemPrompt] = useState<string>(""); // const [editSystemPrompt, setEditSystemPrompt] = useState<string>("");
@ -176,7 +176,7 @@ const Settings = (_props: BackstoryPageProps) => {
if (systemInfo !== undefined) { if (systemInfo !== undefined) {
return; return;
} }
const fetchSystemInfo = async () => { const fetchSystemInfo = async (): Promise<void> => {
try { try {
const response: Types.SystemInfo = await apiClient.getSystemInfo(); const response: Types.SystemInfo = await apiClient.getSystemInfo();
setSystemInfo(response); setSystemInfo(response);