Skip to main content

Navbar Components

The Navbar components provide application-wide navigation, user management, and switching functionality between different portals, companies, and countries. These components handle user authentication, permissions, and multi-tenant operations.

Overview

This document covers all navbar-related components that provide navigation, user profile management, company/country switching, and portal management within the WorkPayCore Frontend application.

Components Overview

  • Navbar - Main application navbar with user profile and navigation
  • GetHelpDrawer - Help and support drawer component

Switching Components

Utility Components

  • EWASignupGuide - EWA (Employee Wallet App) signup guide
  • SwitchedCountryFlag - Country flag indicator

The main application navbar providing user profile access, navigation controls, and switching functionality.

Component Location

import Navbar from 'components/Navbar';

Props

PropTypeRequiredDefaultDescription
onDrawerOpenfunction--Callback function to open mobile drawer

TypeScript Interface

interface NavbarProps {
onDrawerOpen?: () => void;
}

Features

User Profile Management

  • User avatar and profile access
  • Account settings navigation
  • Profile information display

Company/Portal Switching

  • Company and branch switching
  • Country switching (Kenya/Nigeria)
  • EOR portal switching
  • Legacy system access
  • Hamburger menu for mobile
  • Admin portal switcher
  • Time tracker integration
  • Plan details banner

Support Features

  • Help drawer integration
  • Changelog widget (for specific users)
  • Support channel access

Usage Examples

Basic Usage

import Navbar from 'components/Navbar';

function AppLayout() {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);

return (
<Box>
<Navbar onDrawerOpen={() => setIsDrawerOpen(true)} />
<MainContent />
</Box>
);
}

With Drawer Integration

import Navbar from 'components/Navbar';
import { useDisclosure } from '@chakra-ui/react';

function AppWithDrawer() {
const { isOpen, onOpen, onClose } = useDisclosure();

return (
&lt;Box&gt;
<Navbar onDrawerOpen={onOpen} />
<Drawer isOpen={isOpen} onClose={onClose}>
&lt;DrawerContent&gt;
<SidebarContent />
</DrawerContent>
</Drawer>
<MainContent />
</Box>
);
}

The navbar provides contextual menu items based on user permissions:

Employee/Admin Items

  • My Profile: Navigate to employee profile
  • Account Settings: User account management
  • Switch Company: Company/branch switching
  • Switch to/from EOR: EOR portal switching

Product Admin Items

  • Switch to Customer's Company: Client company access

Permission-Based Display

The navbar adapts its content based on user permissions:

// Example of permission-based navigation
const navbarItems = [
{
title: 'My profile',
icon: <UserOutline />,
props: { as: Link, to: `/employees/${userPermissions.employee_id}` },
view: isEmployeeAdmin() || isEmployeeOnly(),
},
{
title: 'Account Settings',
icon: <SettingsOutline />,
props: { as: Link, to: '/profile/user-account' },
view: !isEorPortalView(),
},
// ... more items
];

Responsive Design

The navbar adapts to different screen sizes:

  • Desktop: Full navbar with all features
  • Mobile: Hamburger menu with drawer navigation
  • Tablet: Adapted layout with essential features

Integration Features

Time Tracking

  • Displays active time tracker when available
  • Integrates with timesheet system

Plan Management

  • Shows plan details banner
  • Subscription status indicators

Country/Regional Features

  • Country flag indicators
  • Regional feature toggles
  • Multi-country support

SwitchCompanyModal

Modal component for switching between companies and branches within the system.

Component Location

import SwitchCompanyModal from 'components/Navbar/SwitchCompanyModal';

Props

PropTypeRequiredDefaultDescription
isOpenboolean-Modal open state
onClosefunction-Modal close handler
companiesobject-Companies data object

TypeScript Interface

interface SwitchCompanyModalProps {
isOpen: boolean;
onClose: () => void;
companies: {
data: {
data: {
data: Company[];
};
};
};
}

interface Company {
id: number;
name: string;
branches?: Branch[];
country_id: number;
country_name: string;
}

interface Branch {
id: number;
name: string;
}

Features

Company Selection

  • Dropdown with all accessible companies
  • Search functionality
  • Current company highlighting

Branch Selection

  • Branches for selected company
  • Head office option
  • Branch-specific switching

Global vs Local Switching

  • Handles global entity switching
  • Local branch switching within company
  • Cross-country company switching

Usage Examples

Basic Usage

import SwitchCompanyModal from 'components/Navbar/SwitchCompanyModal';
import { useDisclosure } from '@chakra-ui/react';

function CompanySwitcher() {
const { isOpen, onOpen, onClose } = useDisclosure();
const companies = useCompanies();

return (
<>
<Button onClick={onOpen}>Switch Company</Button>
<SwitchCompanyModal
isOpen={isOpen}
onClose={onClose}
companies={companies}
/>
</>
);
}

With Loading States

import SwitchCompanyModal from 'components/Navbar/SwitchCompanyModal';

function CompanySwitcherWithLoading() {
const [isLoading, setIsLoading] = useState(false);
const { isOpen, onOpen, onClose } = useDisclosure();

const handleSwitch = async companyData => {
setIsLoading(true);
try {
await switchCompany(companyData);
onClose();
} finally {
setIsLoading(false);
}
};

return (
<SwitchCompanyModal
isOpen={isOpen}
onClose={onClose}
companies={companies}
isLoading={isLoading}
/>
);
}

Switching Logic

The modal handles different switching scenarios:

Global Company Switching

function onSubmitGlobal(values: any) {
const payload = {
id:
values.id?.value === 1 || !values.id?.value
? values.auth_company_id?.value
: values.id?.value,
};

mutate(payload, {
onSuccess: data => {
if (data?.data?.success) {
onClose();
}
},
});
}

Local/Cross-Country Switching

function onSubmitLocal() {
mutateCountry(
{ name: selectedSwitchedCompany?.data?.country_name },
{
onSuccess: data => {
if (data?.data?.success) {
// Update local storage with new country
window.localStorage.setItem(
'SWITCHED_COUNTRY_KEY',
JSON.stringify({
country_name: data?.data?.data?.name,
country_code: data?.data?.data?.alpha_two_code,
country_id: data?.data?.data?.country_id,
}),
);
mutate({ id: selectedSwitchedCompany?.value });
onClose();
}
},
},
);
}

SwitchCountryModal

Modal component for switching between different countries (Kenya/Nigeria).

Component Location

import SwitchCountryModal from 'components/Navbar/SwitchCountryModal';

Props

PropTypeRequiredDefaultDescription
isOpenboolean-Modal open state
onClosefunction-Modal close handler

TypeScript Interface

interface SwitchCountryModalProps {
isOpen: boolean;
onClose: () => void;
}

Supported Countries

The modal supports switching between:

  • Kenya: Complete WorkPay functionality
  • Nigeria: Full feature set with local compliance

Features

Country Selection

  • Flag-based country display
  • Search functionality
  • Current country highlighting

Local Storage Management

  • Updates country preferences
  • Maintains session state
  • Handles country-specific data

Usage Examples

Basic Usage

import SwitchCountryModal from 'components/Navbar/SwitchCountryModal';
import { useDisclosure } from '@chakra-ui/react';

function CountrySwitcher() {
const { isOpen, onOpen, onClose } = useDisclosure();

return (
<>
<Button onClick={onOpen}>Switch Country</Button>
<SwitchCountryModal isOpen={isOpen} onClose={onClose} />
</>
);
}

With Current Country Display

import SwitchCountryModal from 'components/Navbar/SwitchCountryModal';
import { authSwitchedCountry } from 'utils/helpers';

function CountrySwitcherWithCurrent() {
const currentCountry = authSwitchedCountry();
const { isOpen, onOpen, onClose } = useDisclosure();

return (
&lt;HStack&gt;
&lt;Text&gt;Current: {currentCountry?.country_name}</Text>
<Button onClick={onOpen}>Switch Country</Button>
<SwitchCountryModal isOpen={isOpen} onClose={onClose} />
</HStack>
);
}

Country Options

The modal provides predefined country options:

const countryOptions = [
{
value: 'Kenya',
label: 'Kenya',
flag: <KenyanFlagIcon boxSize={5} />,
},
{
value: 'Nigeria',
label: 'Nigeria',
flag: <NigerianFlagIcon boxSize={5} />,
},
];

Switching Process

The country switching process:

  1. Select Country: User selects from available countries
  2. Validation: System validates country access
  3. Update Storage: Local storage updated with new country
  4. Company Switch: Associated company data updated
  5. Session Refresh: Application state refreshed

EorPortalSwitcher

Utility functions for switching between EOR (Employer of Record) portal and other portals.

Component Location

import {
eorPortalSwitcher,
getUndoEorPortalName,
} from 'components/Navbar/EorPortalSwitcher';

Functions

eorPortalSwitcher

Main function to switch between portals:

function eorPortalSwitcher(): void;

getUndoEorPortalName

Gets the name of the previous portal for switching back:

function getUndoEorPortalName(): string;

Features

Portal Management

  • Switches between EOR and other portals
  • Maintains portal history
  • Handles portal-specific cleanup

State Management

  • Local storage management
  • Portal preferences
  • Session state handling

User Feedback

  • Toast notifications
  • Success/error messages
  • Loading states

Usage Examples

Basic Portal Switching

import { eorPortalSwitcher } from 'components/Navbar/EorPortalSwitcher';

function PortalSwitcher() {
const handleSwitchPortal = () => {
eorPortalSwitcher();
};

return <Button onClick={handleSwitchPortal}>Switch to EOR Portal</Button>;
}

With Portal Name Display

import {
eorPortalSwitcher,
getUndoEorPortalName,
} from 'components/Navbar/EorPortalSwitcher';
import { isEorPortalView } from 'utils/helpers';

function DynamicPortalSwitcher() {
const previousPortal = getUndoEorPortalName();
const isEorView = isEorPortalView();

return (
<Button onClick={eorPortalSwitcher}>
{isEorView ? `Switch back to ${previousPortal}` : 'Switch to EOR Portal'}
</Button>
);
}

Portal Switching Logic

The switching logic handles:

EOR Portal Entry

  1. Save Current Portal: Store current portal reference
  2. Switch to EOR: Update portal state
  3. Update Storage: Save portal preferences
  4. Refresh Application: Reload with new portal context

EOR Portal Exit

  1. Get Previous Portal: Retrieve saved portal reference
  2. Cleanup EOR State: Remove EOR-specific data
  3. Switch Back: Return to previous portal
  4. Clean Storage: Remove temporary portal data

SwitchedCountryFlag

Utility component for displaying the current country flag in the navbar.

Component Location

// Internal component - part of Navbar
function SwitchedCountryFlag(): JSX.Element;

Supported Flags

  • Kenya: KenyanFlagIcon
  • Nigeria: NigerianFlagIcon
  • Default: Avatar with country name

Features

Dynamic Flag Display

  • Shows flag based on current country
  • Fallback to text avatar
  • Responsive sizing

Integration

  • Integrates with country switching
  • Shows current country state
  • Handles country transitions

Usage Examples

Implementation (Internal)

function SwitchedCountryFlag() {
const switchedCountry = authSwitchedCountry();

switch (switchedCountry?.country_name) {
case 'Kenya':
return <KenyanFlagIcon boxSize={7} />;
case 'Nigeria':
return <NigerianFlagIcon boxSize={7} />;
default:
return <Avatar size='sm' name={switchedCountry?.country_name} />;
}
}

Usage in Navbar

// Part of main navbar component
{
SwitchedCountryFlag() && !remoteEnabled && (
<IconButton
bgColor='transparent'
size='md'
isRound
_hover={{ bgColor: 'background' }}
icon={SwitchedCountryFlag()}
onClick={countryModalOnOpen}
overflow='hidden'
/>
);
}

Integration Patterns

Layout Integration

The navbar integrates with application layouts:

import Navbar from 'components/Navbar';
import Sidebar from 'components/Sidebar';

function AppLayout({ children }) {
const { isOpen, onOpen, onClose } = useDisclosure();

return (
&lt;Box&gt;
<Navbar onDrawerOpen={onOpen} />
&lt;Flex&gt;
<Sidebar display={{ base: 'none', md: 'block' }} />
<Drawer isOpen={isOpen} onClose={onClose}>
&lt;DrawerContent&gt;
<Sidebar />
</DrawerContent>
</Drawer>
<Box flex={1} p={4}>
{children}
</Box>
</Flex>
</Box>
);
}

Permission Integration

Components integrate with permission systems:

// Example permission checks in navbar
const navbarItems = [
{
title: 'My profile',
icon: <UserOutline />,
props: { as: Link, to: `/employees/${userPermissions.employee_id}` },
view: isEmployeeAdmin() || isEmployeeOnly(),
},
{
title: 'Switch company',
icon: <RefreshOutline />,
props: { onClick: switchCompanyDisclosure.onOpen },
view: canSwitchCompany(),
},
];

State Management Integration

// Integration with global state
function NavbarWithState() {
const user = useSelector(state => state.auth.user);
const dispatch = useDispatch();

const handleLogout = () => {
dispatch(logoutHandler());
};

return <Navbar user={user} onLogout={handleLogout} />;
}

Styling

Common Styling Patterns

  • Height: 70px
  • Background: white
  • Padding: 5px 8px
  • Drop shadow: 0px 1px 0px rgba(11, 15, 20, 0.12)

Button Styling

  • Solid buttons: green-dark background
  • Outline buttons: green border
  • Icon buttons: transparent background with hover states
  • Consistent modal sizing
  • Form layouts with proper spacing
  • Button groups with consistent styling

Responsive Design

Mobile Adaptations

  • Hamburger menu for mobile navigation
  • Drawer-based navigation
  • Responsive spacing and sizing

Tablet Adaptations

  • Adaptive layout components
  • Responsive text sizing
  • Optimized touch targets

Custom Styling

Components support custom styling:

// Custom navbar styling
<Navbar
style={{
backgroundColor: 'custom.color',
borderBottom: '1px solid custom.border',
}}
/>

// Custom modal styling
<SwitchCompanyModal
modalProps={{
size: 'lg',
isCentered: true,
}}
/>

Accessibility

Keyboard Navigation

  • Tab Navigation: Proper tab order for all interactive elements
  • Enter/Space: Button activation
  • Escape: Modal and drawer closing
  • Arrow Keys: Menu navigation

Screen Reader Support

  • ARIA Labels: Descriptive labels for all interactive elements
  • Role Attributes: Proper semantic roles
  • Live Regions: Dynamic content announcements
  • Focus Management: Proper focus handling in modals

Visual Accessibility

  • High Contrast: Sufficient color contrast ratios
  • Focus Indicators: Clear focus indicators
  • Text Scaling: Components work with text scaling
  • Reduced Motion: Respects motion preferences

Performance Considerations

Optimization Techniques

  • Lazy Loading: Components loaded on demand
  • Memoization: Expensive calculations memoized
  • Debouncing: API calls debounced appropriately
  • Caching: User preferences cached locally

Best Practices

  • Efficient Updates: Minimal re-renders
  • Proper Dependencies: Correct useEffect dependencies
  • Memory Management: Cleanup of event listeners
  • Bundle Optimization: Code splitting where appropriate

Testing Strategies

Unit Testing

import { render, screen, fireEvent } from '@testing-library/react';
import Navbar from 'components/Navbar';

describe('Navbar', () => {
it('renders user menu', () => {
render(<Navbar />);

expect(
screen.getByRole('button', { name: /user menu/i }),
).toBeInTheDocument();
});

it('opens drawer on mobile menu click', () => {
const onDrawerOpen = jest.fn();
render(<Navbar onDrawerOpen={onDrawerOpen} />);

fireEvent.click(screen.getByRole('button', { name: /hamburger menu/i }));

expect(onDrawerOpen).toHaveBeenCalled();
});
});

Integration Testing

  • Permission Integration: Test with different permission levels
  • State Management: Test with various application states
  • Modal Integration: Test modal interactions
  • Responsive Testing: Test across screen sizes

E2E Testing

  • User Workflows: Test complete navigation flows
  • Company Switching: Test switching between companies
  • Portal Switching: Test EOR portal switching
  • Cross-browser Testing: Test across browsers

Migration Notes

From Legacy Navbar

Update Navbar Props

// Old approach
<Navbar user={user} onLogout={handleLogout} />

// New approach
<Navbar onDrawerOpen={handleDrawerOpen} />

Update Modal Usage

// Old approach
<CompanyModal isOpen={isOpen} onClose={onClose} />

// New approach
<SwitchCompanyModal
isOpen={isOpen}
onClose={onClose}
companies={companies}
/>

Breaking Changes

  • Props Structure: Navbar props simplified
  • Modal API: Modal props updated
  • Event Handlers: Event signatures changed

Upgrade Paths

  1. Update Imports: Change component imports
  2. Update Props: Modify props to match new API
  3. Update Handlers: Update event handlers
  4. Test Integration: Verify functionality works
  5. Update Styling: Migrate custom styles

Best Practices

Component Usage

  • Use Navbar as the main application navigation
  • Use SwitchCompanyModal for company switching workflows
  • Use SwitchCountryModal for country switching
  • Use EorPortalSwitcher for portal management

Permission Management

  • Check permissions before rendering sensitive components
  • Implement proper guards for protected actions
  • Provide feedback for unauthorized actions
  • Handle permission changes gracefully

State Management

  • Manage local storage consistently
  • Handle state updates properly
  • Implement proper cleanup for component unmounting
  • Cache user preferences appropriately

User Experience

  • Provide clear feedback for all actions
  • Implement loading states for async operations
  • Handle errors gracefully with user-friendly messages
  • Maintain consistent navigation patterns

Common Issues and Solutions

// Problem: Component renders for unauthorized users
// Solution: Implement proper permission checks
const shouldShowComponent = usePermissions(['required-permission']);

if (!shouldShowComponent) {
return null;
}

State Management Issues

// Problem: State not updating after company switch
// Solution: Refresh relevant queries after switching
const handleCompanySwitch = async () => {
await switchCompany();
queryClient.invalidateQueries(['user-data']);
queryClient.invalidateQueries(['company-data']);
};

Performance Issues

// Problem: Navbar re-renders frequently
// Solution: Memoize expensive operations
const memoizedNavbarItems = useMemo(() => {
return computeNavbarItems(userPermissions);
}, [userPermissions]);

Mobile Navigation Issues

// Problem: Drawer not closing properly
// Solution: Implement proper event handlers
const handleDrawerClose = useCallback(() => {
onClose();
// Additional cleanup if needed
}, [onClose]);