Fixed tab errors
This commit is contained in:
parent
4f7b2f3e6a
commit
efef926e45
@ -23,52 +23,51 @@ import { AuthProvider, useAuth, ProtectedRoute } from 'hooks/AuthContext';
|
||||
import { useSelectedCandidate } from 'hooks/GlobalContext';
|
||||
|
||||
type NavigationLinkType = {
|
||||
name: string;
|
||||
label: ReactElement<any> | string;
|
||||
path: string;
|
||||
icon?: ReactElement<any>;
|
||||
label?: ReactElement<any>;
|
||||
icon?: ReactElement<any>;
|
||||
};
|
||||
|
||||
const DefaultNavItems: NavigationLinkType[] = [
|
||||
{ name: 'Find a Candidate', path: '/find-a-candidate', icon: <InfoIcon /> },
|
||||
{ name: 'Docs', path: '/docs', icon: <InfoIcon /> },
|
||||
// { name: 'How It Works', path: '/how-it-works', icon: <InfoIcon/> },
|
||||
// { name: 'For Candidates', path: '/for-candidates', icon: <PersonIcon/> },
|
||||
// { name: 'For Employers', path: '/for-employers', icon: <BusinessIcon/> },
|
||||
// { name: 'Pricing', path: '/pricing', icon: <AttachMoneyIcon/> },
|
||||
{ label: 'Find a Candidate', path: '/find-a-candidate', icon: <InfoIcon /> },
|
||||
{ label: 'Docs', path: '/docs', icon: <InfoIcon /> },
|
||||
// { label: 'How It Works', path: '/how-it-works', icon: <InfoIcon/> },
|
||||
// { label: 'For Candidates', path: '/for-candidates', icon: <PersonIcon/> },
|
||||
// { label: 'For Employers', path: '/for-employers', icon: <BusinessIcon/> },
|
||||
// { label: 'Pricing', path: '/pricing', icon: <AttachMoneyIcon/> },
|
||||
];
|
||||
|
||||
const ViewerNavItems: NavigationLinkType[] = [
|
||||
{ name: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
{ label: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
];
|
||||
|
||||
const CandidateNavItems : NavigationLinkType[]= [
|
||||
{ name: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
{ name: 'Job Analysis', path: '/candidate/job-analysis', icon: <WorkIcon /> },
|
||||
{ name: 'Resume Builder', path: '/candidate/resume-builder', icon: <WorkIcon /> },
|
||||
// { name: 'Knowledge Explorer', path: '/candidate/knowledge-explorer', icon: <WorkIcon /> },
|
||||
// { name: 'Dashboard', icon: <DashboardIcon />, path: '/candidate/dashboard' },
|
||||
// { name: 'Profile', icon: <PersonIcon />, path: '/candidate/profile' },
|
||||
// { name: 'Backstory', icon: <HistoryIcon />, path: '/candidate/backstory' },
|
||||
// { name: 'Resumes', icon: <DescriptionIcon />, path: '/candidate/resumes' },
|
||||
// { name: 'Q&A Setup', icon: <QuestionAnswerIcon />, path: '/candidate/qa-setup' },
|
||||
// { name: 'Analytics', icon: <BarChartIcon />, path: '/candidate/analytics' },
|
||||
// { name: 'Settings', icon: <SettingsIcon />, path: '/candidate/settings' },
|
||||
{ label: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
{ label: 'Job Analysis', path: '/candidate/job-analysis', icon: <WorkIcon /> },
|
||||
{ label: 'Resume Builder', path: '/candidate/resume-builder', icon: <WorkIcon /> },
|
||||
// { label: 'Knowledge Explorer', path: '/candidate/knowledge-explorer', icon: <WorkIcon /> },
|
||||
// { label: 'Dashboard', icon: <DashboardIcon />, path: '/candidate/dashboard' },
|
||||
// { label: 'Profile', icon: <PersonIcon />, path: '/candidate/profile' },
|
||||
// { label: 'Backstory', icon: <HistoryIcon />, path: '/candidate/backstory' },
|
||||
// { label: 'Resumes', icon: <DescriptionIcon />, path: '/candidate/resumes' },
|
||||
// { label: 'Q&A Setup', icon: <QuestionAnswerIcon />, path: '/candidate/qa-setup' },
|
||||
// { label: 'Analytics', icon: <BarChartIcon />, path: '/candidate/analytics' },
|
||||
// { label: 'Settings', icon: <SettingsIcon />, path: '/candidate/settings' },
|
||||
];
|
||||
|
||||
const EmployerNavItems: NavigationLinkType[] = [
|
||||
{ name: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
{ name: 'Job Analysis', path: '/employer/job-analysis', icon: <WorkIcon /> },
|
||||
{ name: 'Resume Builder', path: '/employer/resume-builder', icon: <WorkIcon /> },
|
||||
{ name: 'Knowledge Explorer', path: '/employer/knowledge-explorer', icon: <WorkIcon /> },
|
||||
{ name: 'Find a Candidate', path: '/find-a-candidate', icon: <InfoIcon /> },
|
||||
// { name: 'Dashboard', icon: <DashboardIcon />, path: '/employer/dashboard' },
|
||||
// { name: 'Search', icon: <SearchIcon />, path: '/employer/search' },
|
||||
// { name: 'Saved', icon: <BookmarkIcon />, path: '/employer/saved' },
|
||||
// { name: 'Jobs', icon: <WorkIcon />, path: '/employer/jobs' },
|
||||
// { name: 'Company', icon: <BusinessIcon />, path: '/employer/company' },
|
||||
// { name: 'Analytics', icon: <BarChartIcon />, path: '/employer/analytics' },
|
||||
// { name: 'Settings', icon: <SettingsIcon />, path: '/employer/settings' },
|
||||
{ label: 'Chat', path: '/chat', icon: <ChatIcon /> },
|
||||
{ label: 'Job Analysis', path: '/employer/job-analysis', icon: <WorkIcon /> },
|
||||
{ label: 'Resume Builder', path: '/employer/resume-builder', icon: <WorkIcon /> },
|
||||
{ label: 'Knowledge Explorer', path: '/employer/knowledge-explorer', icon: <WorkIcon /> },
|
||||
{ label: 'Find a Candidate', path: '/find-a-candidate', icon: <InfoIcon /> },
|
||||
// { label: 'Dashboard', icon: <DashboardIcon />, path: '/employer/dashboard' },
|
||||
// { label: 'Search', icon: <SearchIcon />, path: '/employer/search' },
|
||||
// { label: 'Saved', icon: <BookmarkIcon />, path: '/employer/saved' },
|
||||
// { label: 'Jobs', icon: <WorkIcon />, path: '/employer/jobs' },
|
||||
// { label: 'Company', icon: <BusinessIcon />, path: '/employer/company' },
|
||||
// { label: 'Analytics', icon: <BarChartIcon />, path: '/employer/analytics' },
|
||||
// { label: 'Settings', icon: <SettingsIcon />, path: '/employer/settings' },
|
||||
];
|
||||
|
||||
// Navigation links based on user type
|
||||
|
@ -170,7 +170,7 @@ interface HeaderProps {
|
||||
navigationLinks: NavigationLinkType[];
|
||||
currentPath: string;
|
||||
sessionId?: string | null;
|
||||
setSnack: SetSnackType,
|
||||
setSnack: SetSnackType;
|
||||
}
|
||||
|
||||
const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
@ -190,13 +190,13 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
|
||||
const name = (user?.firstName || user?.email || '');
|
||||
|
||||
const navLinks : NavigationLinkType[] = [
|
||||
{name: "Home", path: "/", label: <BackstoryLogo/>},
|
||||
const mainNavSections: NavigationLinkType[] = [
|
||||
{ path: '/', label: <BackstoryLogo /> },
|
||||
...navigationLinks
|
||||
];
|
||||
|
||||
// State for page navigation
|
||||
const [ currentTab, setCurrentTab ] = useState<string>("/");
|
||||
// State for page navigation - only for main navigation
|
||||
const [currentTab, setCurrentTab] = useState<string | false>("/");
|
||||
const [userMenuTab, setUserMenuTab] = useState<string>("");
|
||||
|
||||
// State for mobile drawer
|
||||
@ -206,6 +206,26 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
const [userMenuAnchor, setUserMenuAnchor] = useState<null | HTMLElement>(null);
|
||||
const userMenuOpen = Boolean(userMenuAnchor);
|
||||
|
||||
// Helper function to determine which action page we're in
|
||||
const getTailSection = (pathname: string): string | false => {
|
||||
// Handle home page
|
||||
if (pathname === '/') return '/';
|
||||
|
||||
// Split path and check against main sections
|
||||
const segments = pathname.split('/').filter(Boolean);
|
||||
if (segments.length === 0) return '/';
|
||||
|
||||
const lastSegment = `/${segments[segments.length - 1]}`;
|
||||
|
||||
// Check if this matches any of our main navigation sections
|
||||
const matchingSection = mainNavSections.find(section =>
|
||||
section.path === lastSegment ||
|
||||
(section.path !== '/' && pathname.endsWith(section.path))
|
||||
);
|
||||
|
||||
return matchingSection ? matchingSection.path : false; // Return false for routes that shouldn't show in main nav
|
||||
};
|
||||
|
||||
// User menu items
|
||||
const userMenuItems = [
|
||||
{
|
||||
@ -244,15 +264,13 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
const parts = location.pathname.split('/');
|
||||
let tab = '/';
|
||||
if (parts.length > 1) {
|
||||
tab = `/${parts[1]}`;
|
||||
const mainSection = getTailSection(location.pathname);
|
||||
|
||||
// Only update if the section is different from current tab
|
||||
if (mainSection !== currentTab) {
|
||||
setCurrentTab(mainSection); // mainSection is either a string or false
|
||||
}
|
||||
if (tab !== currentTab) {
|
||||
setCurrentTab(tab);
|
||||
}
|
||||
}, [location, currentTab]);
|
||||
}, [location.pathname, currentTab]);
|
||||
|
||||
const handleUserMenuOpen = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setUserMenuAnchor(event.currentTarget);
|
||||
@ -274,28 +292,34 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
setMobileOpen(!mobileOpen);
|
||||
};
|
||||
|
||||
const handleTabChange = (event: React.SyntheticEvent, newValue: string | false) => {
|
||||
if (newValue !== false) {
|
||||
setCurrentTab(newValue);
|
||||
navigate(newValue);
|
||||
}
|
||||
};
|
||||
|
||||
// Render desktop navigation links
|
||||
const renderNavLinks = () => {
|
||||
return (
|
||||
<Tabs value={currentTab} onChange={(e, newValue) => setCurrentTab(newValue)}
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
onChange={handleTabChange}
|
||||
indicatorColor="secondary"
|
||||
textColor="inherit"
|
||||
variant="fullWidth"
|
||||
allowScrollButtonsMobile
|
||||
aria-label="Backstory navigation"
|
||||
>
|
||||
{navLinks.map((link) => (
|
||||
<Tab
|
||||
sx={{
|
||||
minWidth: link.path === '/' ? "max-content" : "auto",
|
||||
}}
|
||||
key={link.name}
|
||||
value={link.path}
|
||||
label={link.label ? link.label : link.name}
|
||||
onClick={() => {
|
||||
navigate(link.path);
|
||||
}}
|
||||
/>
|
||||
>
|
||||
{mainNavSections.map((section) => (
|
||||
<Tab
|
||||
sx={{
|
||||
minWidth: section.path === '/' ? "max-content" : "auto",
|
||||
}}
|
||||
key={section.path}
|
||||
value={section.path}
|
||||
label={section.label}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
);
|
||||
@ -308,33 +332,21 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
<MobileMenuTabs
|
||||
orientation="vertical"
|
||||
value={currentTab}
|
||||
onChange={(e, newValue) => setCurrentTab(newValue)}
|
||||
onChange={handleTabChange}
|
||||
>
|
||||
{navLinks.map((link) => (
|
||||
{mainNavSections.map((section) => (
|
||||
<Tab
|
||||
key={link.name}
|
||||
value={link.path}
|
||||
key={section.path || '/'}
|
||||
value={section.path}
|
||||
label={
|
||||
<MenuItemBox>
|
||||
{link.path === '/' ? (
|
||||
<Avatar
|
||||
sx={{ width: 20, height: 20 }}
|
||||
variant="rounded"
|
||||
alt="Backstory logo"
|
||||
src="/logo192.png"
|
||||
/>
|
||||
) : (
|
||||
link.icon && link.icon
|
||||
)}
|
||||
<Typography variant="body2">
|
||||
{link.path === '/' ? 'Backstory' : (link.label && typeof link.label === 'object' ? link.name : (link.label || link.name))}
|
||||
</Typography>
|
||||
{section.label}
|
||||
</MenuItemBox>
|
||||
}
|
||||
onClick={(e) => {
|
||||
handleDrawerToggle();
|
||||
setCurrentTab(link.path);
|
||||
navigate(link.path);
|
||||
setCurrentTab(section.path);
|
||||
navigate(section.path);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@ -458,8 +470,6 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
>
|
||||
<Container maxWidth="xl">
|
||||
<Toolbar disableGutters>
|
||||
{/* Logo Section */}
|
||||
|
||||
{/* Navigation Links - Desktop */}
|
||||
<NavLinksContainer>
|
||||
{renderNavLinks()}
|
||||
@ -482,22 +492,22 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
{sessionId && <CopyBubble
|
||||
tooltip="Copy link"
|
||||
color="inherit"
|
||||
aria-label="copy link"
|
||||
edge="end"
|
||||
sx={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
opacity: 1,
|
||||
bgcolor: 'inherit',
|
||||
'&:hover': { bgcolor: 'action.hover', opacity: 1 },
|
||||
}}
|
||||
content={`${window.location.origin}${window.location.pathname}?id=${sessionId}`}
|
||||
onClick={() => { navigate(`${window.location.pathname}?id=${sessionId}`); setSnack("Link copied!") }}
|
||||
size="large"
|
||||
/>}
|
||||
{sessionId && <CopyBubble
|
||||
tooltip="Copy link"
|
||||
color="inherit"
|
||||
aria-label="copy link"
|
||||
edge="end"
|
||||
sx={{
|
||||
width: 36,
|
||||
height: 36,
|
||||
opacity: 1,
|
||||
bgcolor: 'inherit',
|
||||
'&:hover': { bgcolor: 'action.hover', opacity: 1 },
|
||||
}}
|
||||
content={`${window.location.origin}${window.location.pathname}?id=${sessionId}`}
|
||||
onClick={() => { navigate(`${window.location.pathname}?id=${sessionId}`); setSnack("Link copied!") }}
|
||||
size="large"
|
||||
/>}
|
||||
</UserActionsContainer>
|
||||
|
||||
{/* Mobile Navigation Drawer */}
|
||||
@ -507,18 +517,16 @@ const Header: React.FC<HeaderProps> = (props: HeaderProps) => {
|
||||
open={mobileOpen}
|
||||
onClose={handleDrawerToggle}
|
||||
ModalProps={{
|
||||
keepMounted: true, // Better open performance on mobile
|
||||
keepMounted: true,
|
||||
}}
|
||||
>
|
||||
{renderDrawerContent()}
|
||||
</MobileDrawer>
|
||||
</Toolbar>
|
||||
</Container>
|
||||
<Beta sx={{ left: "-90px", "& .mobile": { right: "-72px" } }} onClick={() => { navigate('/docs/beta'); }} />
|
||||
<Beta sx={{ left: "-90px", "& .mobile": { right: "-72px" } }} onClick={() => { navigate('/docs/beta'); }} />
|
||||
</StyledAppBar>
|
||||
);
|
||||
};
|
||||
|
||||
export {
|
||||
Header
|
||||
};
|
||||
export { Header };
|
@ -1,19 +1,66 @@
|
||||
import React, { createContext, useContext, useState, useCallback, useEffect } from 'react';
|
||||
import * as Types from 'types/types';
|
||||
// Assuming you're using React Router
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { useAuth } from 'hooks/AuthContext';
|
||||
|
||||
// ============================
|
||||
// Local Storage Keys
|
||||
// Storage Keys
|
||||
// ============================
|
||||
|
||||
const STORAGE_KEYS = {
|
||||
SELECTED_CANDIDATE_ID: 'selectedCandidateId',
|
||||
SELECTED_JOB_ID: 'selectedJobId',
|
||||
SELECTED_EMPLOYER_ID: 'selectedEmployerId'
|
||||
SELECTED_EMPLOYER_ID: 'selectedEmployerId',
|
||||
LAST_ROUTE: 'lastVisitedRoute',
|
||||
ROUTE_STATE: 'routeState',
|
||||
ACTIVE_TAB: 'activeTab',
|
||||
APPLIED_FILTERS: 'appliedFilters',
|
||||
SIDEBAR_COLLAPSED: 'sidebarCollapsed'
|
||||
} as const;
|
||||
|
||||
// ============================
|
||||
// Local Storage Utilities
|
||||
// Route State Interface
|
||||
// ============================
|
||||
|
||||
interface RouteState {
|
||||
lastRoute: string | null;
|
||||
activeTab: string | null;
|
||||
appliedFilters: Record<string, any>;
|
||||
sidebarCollapsed: boolean;
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Enhanced App State Interface
|
||||
// ============================
|
||||
|
||||
export interface AppState {
|
||||
selectedCandidate: Types.Candidate | null;
|
||||
selectedJob: Types.Job | null;
|
||||
selectedEmployer: Types.Employer | null;
|
||||
routeState: RouteState;
|
||||
isInitializing: boolean;
|
||||
}
|
||||
|
||||
export interface AppStateActions {
|
||||
setSelectedCandidate: (candidate: Types.Candidate | null) => void;
|
||||
setSelectedJob: (job: Types.Job | null) => void;
|
||||
setSelectedEmployer: (employer: Types.Employer | null) => void;
|
||||
clearSelections: () => void;
|
||||
|
||||
// Route management
|
||||
saveCurrentRoute: () => void;
|
||||
restoreLastRoute: () => void;
|
||||
setActiveTab: (tab: string) => void;
|
||||
setFilters: (filters: Record<string, any>) => void;
|
||||
setSidebarCollapsed: (collapsed: boolean) => void;
|
||||
clearRouteState: () => void;
|
||||
}
|
||||
|
||||
export type AppStateContextType = AppState & AppStateActions;
|
||||
|
||||
// ============================
|
||||
// Storage Utilities
|
||||
// ============================
|
||||
|
||||
function getStoredId(key: string): string | null {
|
||||
@ -37,42 +84,62 @@ function setStoredId(key: string, id: string | null): void {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================
|
||||
// App State Interface
|
||||
// ============================
|
||||
|
||||
export interface AppState {
|
||||
selectedCandidate: Types.Candidate | null;
|
||||
selectedJob: Types.Job | null;
|
||||
selectedEmployer: Types.Employer | null;
|
||||
isInitializing: boolean;
|
||||
// Add more global state as needed:
|
||||
// currentView: string;
|
||||
// filters: Record<string, any>;
|
||||
// searchQuery: string;
|
||||
function getStoredObject<T>(key: string, defaultValue: T): T {
|
||||
try {
|
||||
const stored = localStorage.getItem(key);
|
||||
return stored ? JSON.parse(stored) : defaultValue;
|
||||
} catch (error) {
|
||||
console.warn(`Failed to read ${key} from localStorage:`, error);
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
export interface AppStateActions {
|
||||
setSelectedCandidate: (candidate: Types.Candidate | null) => void;
|
||||
setSelectedJob: (job: Types.Job | null) => void;
|
||||
setSelectedEmployer: (employer: Types.Employer | null) => void;
|
||||
clearSelections: () => void;
|
||||
// Future actions can be added here
|
||||
function setStoredObject(key: string, value: any): void {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (error) {
|
||||
console.warn(`Failed to write ${key} to localStorage:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
export type AppStateContextType = AppState & AppStateActions;
|
||||
// ============================
|
||||
// Route State Management
|
||||
// ============================
|
||||
|
||||
function getInitialRouteState(): RouteState {
|
||||
return {
|
||||
lastRoute: getStoredId(STORAGE_KEYS.LAST_ROUTE),
|
||||
activeTab: getStoredId(STORAGE_KEYS.ACTIVE_TAB),
|
||||
appliedFilters: getStoredObject(STORAGE_KEYS.APPLIED_FILTERS, {}),
|
||||
sidebarCollapsed: getStoredObject(STORAGE_KEYS.SIDEBAR_COLLAPSED, false)
|
||||
};
|
||||
}
|
||||
|
||||
function persistRouteState(routeState: RouteState): void {
|
||||
setStoredId(STORAGE_KEYS.LAST_ROUTE, routeState.lastRoute);
|
||||
setStoredId(STORAGE_KEYS.ACTIVE_TAB, routeState.activeTab);
|
||||
setStoredObject(STORAGE_KEYS.APPLIED_FILTERS, routeState.appliedFilters);
|
||||
setStoredObject(STORAGE_KEYS.SIDEBAR_COLLAPSED, routeState.sidebarCollapsed);
|
||||
}
|
||||
|
||||
// ============================
|
||||
// App State Hook
|
||||
// Enhanced App State Hook
|
||||
// ============================
|
||||
|
||||
export function useAppStateLogic(): AppStateContextType {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { apiClient } = useAuth();
|
||||
|
||||
// Entity state
|
||||
const [selectedCandidate, setSelectedCandidateState] = useState<Types.Candidate | null>(null);
|
||||
const [selectedJob, setSelectedJobState] = useState<Types.Job | null>(null);
|
||||
const [selectedEmployer, setSelectedEmployerState] = useState<Types.Employer | null>(null);
|
||||
const [isInitializing, setIsInitializing] = useState<boolean>(true);
|
||||
|
||||
// Route state
|
||||
const [routeState, setRouteStateState] = useState<RouteState>(getInitialRouteState);
|
||||
|
||||
// ============================
|
||||
// Initialization Effect
|
||||
// ============================
|
||||
@ -82,7 +149,7 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
setIsInitializing(true);
|
||||
|
||||
try {
|
||||
// Get stored IDs
|
||||
// Get stored entity IDs
|
||||
const candidateId = getStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID);
|
||||
const jobId = getStoredId(STORAGE_KEYS.SELECTED_JOB_ID);
|
||||
const employerId = getStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID);
|
||||
@ -94,13 +161,11 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
promises.push(
|
||||
(async () => {
|
||||
try {
|
||||
// Assuming apiClient.getCandidate exists
|
||||
const candidate = await apiClient.getCandidate(candidateId);
|
||||
if (candidate) {
|
||||
setSelectedCandidateState(candidate);
|
||||
console.log('Restored candidate from storage:', candidate);
|
||||
} else {
|
||||
// Data not available, clear stored ID
|
||||
setStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID, null);
|
||||
console.log('Candidate not found, cleared from storage');
|
||||
}
|
||||
@ -116,13 +181,11 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
promises.push(
|
||||
(async () => {
|
||||
try {
|
||||
// Assuming apiClient.getJob exists
|
||||
const job = await apiClient.getJob(jobId);
|
||||
if (job) {
|
||||
setSelectedJobState(job);
|
||||
console.log('Restored job from storage:', job);
|
||||
} else {
|
||||
// Data not available, clear stored ID
|
||||
setStoredId(STORAGE_KEYS.SELECTED_JOB_ID, null);
|
||||
console.log('Job not found, cleared from storage');
|
||||
}
|
||||
@ -138,13 +201,11 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
promises.push(
|
||||
(async () => {
|
||||
try {
|
||||
// Assuming apiClient.getEmployer exists
|
||||
const employer = await apiClient.getEmployer(employerId);
|
||||
if (employer) {
|
||||
setSelectedEmployerState(employer);
|
||||
console.log('Restored employer from storage:', employer);
|
||||
} else {
|
||||
// Data not available, clear stored ID
|
||||
setStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID, null);
|
||||
console.log('Employer not found, cleared from storage');
|
||||
}
|
||||
@ -170,13 +231,29 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
}, []);
|
||||
|
||||
// ============================
|
||||
// State Setters with Persistence
|
||||
// Auto-save current route
|
||||
// ============================
|
||||
|
||||
useEffect(() => {
|
||||
// Don't save certain routes (login, register, etc.)
|
||||
const excludedRoutes = ['/login', '/register', '/verify-email', '/reset-password'];
|
||||
const shouldSaveRoute = !excludedRoutes.some(route => location.pathname.startsWith(route));
|
||||
|
||||
if (shouldSaveRoute && !isInitializing) {
|
||||
setRouteStateState(prev => {
|
||||
const newState = { ...prev, lastRoute: location.pathname };
|
||||
persistRouteState(newState);
|
||||
return newState;
|
||||
});
|
||||
}
|
||||
}, [location.pathname, isInitializing]);
|
||||
|
||||
// ============================
|
||||
// Entity State Setters with Persistence
|
||||
// ============================
|
||||
|
||||
const setSelectedCandidate = useCallback((candidate: Types.Candidate | null) => {
|
||||
setSelectedCandidateState(candidate);
|
||||
|
||||
// Persist ID to localStorage
|
||||
setStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID, candidate?.id || null);
|
||||
|
||||
if (candidate) {
|
||||
@ -188,8 +265,6 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
|
||||
const setSelectedJob = useCallback((job: Types.Job | null) => {
|
||||
setSelectedJobState(job);
|
||||
|
||||
// Persist ID to localStorage
|
||||
setStoredId(STORAGE_KEYS.SELECTED_JOB_ID, job?.id || null);
|
||||
|
||||
if (job) {
|
||||
@ -201,8 +276,6 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
|
||||
const setSelectedEmployer = useCallback((employer: Types.Employer | null) => {
|
||||
setSelectedEmployerState(employer);
|
||||
|
||||
// Persist ID to localStorage
|
||||
setStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID, employer?.id || null);
|
||||
|
||||
if (employer) {
|
||||
@ -217,7 +290,6 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
setSelectedJobState(null);
|
||||
setSelectedEmployerState(null);
|
||||
|
||||
// Clear all from localStorage
|
||||
setStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID, null);
|
||||
setStoredId(STORAGE_KEYS.SELECTED_JOB_ID, null);
|
||||
setStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID, null);
|
||||
@ -225,15 +297,83 @@ export function useAppStateLogic(): AppStateContextType {
|
||||
console.log('Cleared all selections');
|
||||
}, []);
|
||||
|
||||
// ============================
|
||||
// Route State Actions
|
||||
// ============================
|
||||
|
||||
const saveCurrentRoute = useCallback(() => {
|
||||
setRouteStateState(prev => {
|
||||
const newState = { ...prev, lastRoute: location.pathname };
|
||||
persistRouteState(newState);
|
||||
return newState;
|
||||
});
|
||||
}, [location.pathname]);
|
||||
|
||||
const restoreLastRoute = useCallback(() => {
|
||||
if (routeState.lastRoute && routeState.lastRoute !== location.pathname) {
|
||||
navigate(routeState.lastRoute);
|
||||
}
|
||||
}, [routeState.lastRoute, location.pathname, navigate]);
|
||||
|
||||
const setActiveTab = useCallback((tab: string) => {
|
||||
setRouteStateState(prev => {
|
||||
const newState = { ...prev, activeTab: tab };
|
||||
persistRouteState(newState);
|
||||
return newState;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setFilters = useCallback((filters: Record<string, any>) => {
|
||||
setRouteStateState(prev => {
|
||||
const newState = { ...prev, appliedFilters: filters };
|
||||
persistRouteState(newState);
|
||||
return newState;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const setSidebarCollapsed = useCallback((collapsed: boolean) => {
|
||||
setRouteStateState(prev => {
|
||||
const newState = { ...prev, sidebarCollapsed: collapsed };
|
||||
persistRouteState(newState);
|
||||
return newState;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const clearRouteState = useCallback(() => {
|
||||
const clearedState: RouteState = {
|
||||
lastRoute: null,
|
||||
activeTab: null,
|
||||
appliedFilters: {},
|
||||
sidebarCollapsed: false
|
||||
};
|
||||
|
||||
setRouteStateState(clearedState);
|
||||
|
||||
// Clear from localStorage
|
||||
localStorage.removeItem(STORAGE_KEYS.LAST_ROUTE);
|
||||
localStorage.removeItem(STORAGE_KEYS.ACTIVE_TAB);
|
||||
localStorage.removeItem(STORAGE_KEYS.APPLIED_FILTERS);
|
||||
localStorage.removeItem(STORAGE_KEYS.SIDEBAR_COLLAPSED);
|
||||
|
||||
console.log('Cleared all route state');
|
||||
}, []);
|
||||
|
||||
return {
|
||||
selectedCandidate,
|
||||
selectedJob,
|
||||
selectedEmployer,
|
||||
routeState,
|
||||
isInitializing,
|
||||
setSelectedCandidate,
|
||||
setSelectedJob,
|
||||
setSelectedEmployer,
|
||||
clearSelections
|
||||
clearSelections,
|
||||
saveCurrentRoute,
|
||||
restoreLastRoute,
|
||||
setActiveTab,
|
||||
setFilters,
|
||||
setSidebarCollapsed,
|
||||
clearRouteState
|
||||
};
|
||||
}
|
||||
|
||||
@ -262,72 +402,45 @@ export function useAppState() {
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Convenience Hooks for Specific State
|
||||
// Convenience Hooks
|
||||
// ============================
|
||||
|
||||
/**
|
||||
* Hook specifically for candidate selection
|
||||
* Useful when a component only cares about candidate state
|
||||
*/
|
||||
export function useSelectedCandidate() {
|
||||
const { selectedCandidate, setSelectedCandidate } = useAppState();
|
||||
return { selectedCandidate, setSelectedCandidate };
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook specifically for job selection
|
||||
*/
|
||||
export function useSelectedJob() {
|
||||
const { selectedJob, setSelectedJob } = useAppState();
|
||||
return { selectedJob, setSelectedJob };
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook specifically for employer selection
|
||||
*/
|
||||
export function useSelectedEmployer() {
|
||||
const { selectedEmployer, setSelectedEmployer } = useAppState();
|
||||
return { selectedEmployer, setSelectedEmployer };
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to check if the app is still initializing
|
||||
*/
|
||||
export function useRouteState() {
|
||||
const {
|
||||
routeState,
|
||||
setActiveTab,
|
||||
setFilters,
|
||||
setSidebarCollapsed,
|
||||
restoreLastRoute,
|
||||
clearRouteState
|
||||
} = useAppState();
|
||||
|
||||
return {
|
||||
routeState,
|
||||
setActiveTab,
|
||||
setFilters,
|
||||
setSidebarCollapsed,
|
||||
restoreLastRoute,
|
||||
clearRouteState
|
||||
};
|
||||
}
|
||||
|
||||
export function useAppInitializing() {
|
||||
const { isInitializing } = useAppState();
|
||||
return isInitializing;
|
||||
}
|
||||
|
||||
// ============================
|
||||
// Development Utilities
|
||||
// ============================
|
||||
|
||||
/**
|
||||
* Debug utility to log current app state (development only)
|
||||
*/
|
||||
export function useAppStateDebug() {
|
||||
const appState = useAppState();
|
||||
|
||||
useEffect(() => {
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.group('🔍 App State Debug');
|
||||
console.log('Is Initializing:', appState.isInitializing);
|
||||
console.log('Selected Candidate:', appState.selectedCandidate);
|
||||
console.log('Selected Job:', appState.selectedJob);
|
||||
console.log('Selected Employer:', appState.selectedEmployer);
|
||||
console.groupEnd();
|
||||
}
|
||||
}, [appState.selectedCandidate, appState.selectedJob, appState.selectedEmployer, appState.isInitializing]);
|
||||
|
||||
return appState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to manually clear all localStorage for this app (development/debugging)
|
||||
*/
|
||||
export function clearAppLocalStorage() {
|
||||
setStoredId(STORAGE_KEYS.SELECTED_CANDIDATE_ID, null);
|
||||
setStoredId(STORAGE_KEYS.SELECTED_JOB_ID, null);
|
||||
setStoredId(STORAGE_KEYS.SELECTED_EMPLOYER_ID, null);
|
||||
console.log('Cleared all app localStorage');
|
||||
}
|
@ -34,7 +34,6 @@ const CandidateListingPage = (props: BackstoryPageProps) => {
|
||||
}
|
||||
return result;
|
||||
});
|
||||
console.log(candidates);
|
||||
setCandidates(candidates);
|
||||
} catch (err) {
|
||||
setSnack("" + err);
|
||||
|
Loading…
x
Reference in New Issue
Block a user