diff --git a/frontend/.eslintignore b/frontend/.eslintignore index 385644c..38d826d 100644 --- a/frontend/.eslintignore +++ b/frontend/.eslintignore @@ -1,4 +1,6 @@ node_modules dist build -coverage \ No newline at end of file +coverage +src/types/* +src/services/* diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9ca3703..6500c53 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -49,6 +49,7 @@ "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", + "source-map-explorer": "^2.5.3", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, @@ -7664,6 +7665,17 @@ "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": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -21945,6 +21957,95 @@ "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": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -22967,6 +23068,18 @@ "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": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", @@ -22975,6 +23088,58 @@ "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": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index 82b4af4..a100d81 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -44,10 +44,12 @@ "rehype-katex": "^7.0.1", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", + "source-map-explorer": "^2.5.3", "typescript": "^4.9.5", "web-vitals": "^2.1.4" }, "scripts": { + "analyze": "source-map-explorer 'build/static/js/*.js'", "start": "WDS_SOCKET_HOST=backstory-beta.ketrenos.com WDS_SOCKET_PORT=443 craco start", "build": "craco build", "test": "craco test", diff --git a/frontend/src/components/ResumeGenerator.tsx b/frontend/src/components/ResumeGenerator.tsx index 62bfd96..44fb829 100644 --- a/frontend/src/components/ResumeGenerator.tsx +++ b/frontend/src/components/ResumeGenerator.tsx @@ -135,7 +135,7 @@ const ResumeGenerator: React.FC = (props: ResumeGeneratorP console.error('Error saving resume:', error); setSnack('Error saving resume.'); } - }, [apiClient, candidate.id, job.id, resume, setSnack]); + }, [apiClient, candidate.id, job.id, resume, setSnack, navigate, prompt, systemPrompt]); return ( = (props: BackstoryLayoutP .filter(Boolean); }; - const findMatchingRoute = (currentPath: string, routes: any[]) => { + const findMatchingRoute = ( + currentPath: string, + routes: NavigationItem[] + ): null | NavigationItem => { for (const route of routes) { if (!route.path) continue; @@ -124,7 +127,7 @@ const BackstoryLayout: React.FC = (props: BackstoryLayoutP const match = matchPath( { path: route.path, - exact: true, + end: true, caseSensitive: false, }, currentPath diff --git a/frontend/src/components/layout/Header.tsx b/frontend/src/components/layout/Header.tsx index 22e5e43..c3ddacc 100644 --- a/frontend/src/components/layout/Header.tsx +++ b/frontend/src/components/layout/Header.tsx @@ -1,5 +1,5 @@ // components/layout/Header.tsx -import React, { useState } from 'react'; +import React, { JSX, useState } from 'react'; import { NavigateFunction, useLocation } from 'react-router-dom'; import { AppBar, @@ -113,6 +113,14 @@ interface HeaderProps { sessionId?: string | null; } +type MenuItem = { + id: string; + label: string; + icon: React.ReactElement | null; + action: () => void; + group?: string; +}; + const Header: React.FC = (props: HeaderProps) => { const { user, logout } = useAuth(); const { setSnack } = useAppState(); @@ -143,14 +151,8 @@ const Header: React.FC = (props: HeaderProps) => { const userMenuGroups = getUserMenuItemsByGroup(user?.userType || null, isAdmin); // Create user menu items array with proper actions - const createUserMenuItems = () => { - const items: Array<{ - id: string; - label: string; - icon: React.ReactElement | null; - action: () => void; - group?: string; - }> = []; + const createUserMenuItems = (): MenuItem[] => { + const items: Array = []; // Add profile group items userMenuGroups.profile.forEach(item => { @@ -294,28 +296,28 @@ const Header: React.FC = (props: HeaderProps) => { }; // Desktop dropdown handlers - const handleDropdownOpen = (event: React.MouseEvent, itemId: string) => { + const handleDropdownOpen = (event: React.MouseEvent, itemId: string): void => { setDropdownAnchors(prev => ({ ...prev, [itemId]: event.currentTarget })); }; - const handleDropdownClose = (itemId: string) => { + const handleDropdownClose = (itemId: string): void => { setDropdownAnchors(prev => ({ ...prev, [itemId]: null })); }; // Mobile accordion handlers - const handleMobileToggle = (itemId: string) => { + const handleMobileToggle = (itemId: string): void => { setMobileExpanded(prev => ({ ...prev, [itemId]: !prev[itemId] })); }; - const handleDrawerToggle = () => { + const handleDrawerToggle = (): void => { setMobileOpen(!mobileOpen); }; - const handleUserMenuOpen = (event: React.MouseEvent) => { + const handleUserMenuOpen = (event: React.MouseEvent): void => { setUserMenuAnchor(event.currentTarget); }; - const handleUserMenuClose = () => { + const handleUserMenuClose = (): void => { setUserMenuAnchor(null); }; @@ -325,7 +327,7 @@ const Header: React.FC = (props: HeaderProps) => { icon: React.ReactElement | null; action: () => void; group?: string; - }) => { + }): void => { if (item.group !== 'divider') { item.action(); handleUserMenuClose(); @@ -333,7 +335,7 @@ const Header: React.FC = (props: HeaderProps) => { }; // Navigation handlers - const handleNavigate = (path: string) => { + const handleNavigate = (path: string): void => { navigate(path.replace(/:.*$/, '')); setMobileOpen(false); // Close all dropdowns @@ -341,7 +343,7 @@ const Header: React.FC = (props: HeaderProps) => { }; // Render desktop navigation with dropdowns - const renderDesktopNavigation = () => { + const renderDesktopNavigation = (): JSX.Element => { return ( = (props: HeaderProps) => { }} > handleDropdownOpen(e, item.id)} + onClick={(e): void => handleDropdownOpen(e, item.id)} endIcon={} sx={{ backgroundColor: isActive ? 'action.selected' : 'transparent', @@ -377,7 +379,7 @@ const Header: React.FC = (props: HeaderProps) => { handleDropdownClose(item.id)} + onClose={(): void => handleDropdownClose(item.id)} anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }} transformOrigin={{ vertical: 'top', horizontal: 'left' }} TransitionComponent={Fade} @@ -385,7 +387,9 @@ const Header: React.FC = (props: HeaderProps) => { {item.children?.map(child => ( child.path && handleNavigate(child.path)} + onClick={(): void => { + child.path && handleNavigate(child.path); + }} selected={isCurrentPath(child)} disabled={!child.path} sx={{ @@ -406,7 +410,9 @@ const Header: React.FC = (props: HeaderProps) => { return ( item.path && handleNavigate(item.path)} + onClick={(): void => { + item.path && handleNavigate(item.path); + }} sx={{ backgroundColor: isActive ? 'action.selected' : 'transparent', color: isActive ? 'secondary.main' : 'primary.contrastText', @@ -424,8 +430,8 @@ const Header: React.FC = (props: HeaderProps) => { }; // Render mobile accordion navigation - const renderMobileNavigation = () => { - const renderNavigationItem = (item: NavigationItem, depth = 0) => { + const renderMobileNavigation = (): JSX.Element => { + const renderNavigationItem = (item: NavigationItem, depth = 0): JSX.Element => { const hasChildren = item.children && item.children.length > 0; const isActive = isCurrentPath(item) || hasActiveChild(item); const isExpanded = mobileExpanded[item.id]; @@ -434,7 +440,7 @@ const Header: React.FC = (props: HeaderProps) => { { + onClick={(): void => { if (hasChildren) { handleMobileToggle(item.id); } else if (item.path) { @@ -485,7 +491,7 @@ const Header: React.FC = (props: HeaderProps) => { {!user && ( - handleNavigate('/login')}> + handleNavigate('/login')}> @@ -495,7 +501,7 @@ const Header: React.FC = (props: HeaderProps) => { }; // Render user menu content - const renderUserMenu = () => { + const renderUserMenu = (): JSX.Element => { return ( @@ -504,7 +510,7 @@ const Header: React.FC = (props: HeaderProps) => { ) : ( - handleUserMenuAction(item)}> + handleUserMenuAction(item)}> {item.icon && {item.icon}} @@ -517,13 +523,15 @@ const Header: React.FC = (props: HeaderProps) => { }; // Render user account section - const renderUserSection = () => { + const renderUserSection = (): JSX.Element => { if (!user) { return (