Reorganize navigation structure and improve UX

- Reorganized main navigation with cleaner structure
- Updated navigation icons: Feed (RssFeed), Network (Hub), Groups, Post (PostAdd), Chat
- Simplified left menu: removed hierarchical sub-items, added Groups back as main item
- Enhanced Contacts page with Import button and cleaner layout
- Removed unnecessary descriptive text from navigation
- Fixed horizontal overflow in left navigation column
- Updated mobile navigation to match new structure
- Improved navigation item styling with proper text wrapping

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
main
Oliver Sylvester-Bradley 2 months ago
parent 4ec193928c
commit 77d0da34a6
  1. 169
      src/components/layout/DashboardLayout.tsx
  2. 29
      src/components/navigation/BottomNavigation.tsx
  3. 24
      src/pages/ContactListPage.tsx

@ -20,6 +20,7 @@ import {
Menu,
MenuItem,
Badge,
Collapse,
} from '@mui/material';
import {
Menu as MenuIcon,
@ -31,10 +32,16 @@ import {
Logout,
NotificationsNone,
SearchRounded,
Group,
Home,
Groups,
RssFeed,
PostAdd,
Message,
Chat,
ExpandLess,
ExpandMore,
Hub,
List as ListIcon,
AccountTree,
Map,
} from '@mui/icons-material';
import BottomNavigation from '../navigation/BottomNavigation';
@ -45,6 +52,7 @@ interface NavItem {
icon: ReactNode;
path: string;
badge?: number;
children?: NavItem[];
}
interface DashboardLayoutProps {
@ -56,18 +64,16 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [mobileOpen, setMobileOpen] = useState(false);
const [profileMenuAnchor, setProfileMenuAnchor] = useState<null | HTMLElement>(null);
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set(['Network']));
const location = useLocation();
const navigate = useNavigate();
const navItems: NavItem[] = [
{ text: 'Feed', icon: <Home />, path: '/feed' },
{ text: 'Contacts', icon: <People />, path: '/contacts' },
{ text: 'Groups', icon: <Group />, path: '/groups' },
{ text: 'Posts & Offers', icon: <PostAdd />, path: '/posts' },
{ text: 'Messages', icon: <Message />, path: '/messages' },
{ text: 'Import', icon: <CloudDownload />, path: '/import' },
{ text: 'Invite', icon: <QrCode />, path: '/invite' },
{ text: 'Join Network', icon: <PersonAdd />, path: '/onboarding' },
{ text: 'Feed', icon: <RssFeed />, path: '/feed' },
{ text: 'Network', icon: <Hub />, path: '/contacts' },
{ text: 'Groups', icon: <Groups />, path: '/groups' },
{ text: 'Post', icon: <PostAdd />, path: '/posts' },
{ text: 'Chat', icon: <Chat />, path: '/messages' },
];
const handleDrawerToggle = () => {
@ -89,66 +95,111 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
}
};
const toggleExpanded = (itemText: string) => {
setExpandedItems(prev => {
const newSet = new Set(prev);
if (newSet.has(itemText)) {
newSet.delete(itemText);
} else {
newSet.add(itemText);
}
return newSet;
});
};
const isActiveRoute = (path: string) => {
if (path === '/feed' && (location.pathname === '/' || location.pathname === '/feed')) return true;
if (path !== '/feed' && location.pathname.startsWith(path)) return true;
return false;
};
const isParentActive = (item: NavItem) => {
if (item.children) {
return item.children.some(child => isActiveRoute(child.path));
}
return false;
};
const renderNavItem = (item: NavItem, level: number = 0) => {
const hasChildren = item.children && item.children.length > 0;
const isExpanded = expandedItems.has(item.text);
const isActive = isActiveRoute(item.path);
const isParentOfActive = isParentActive(item);
return (
<div key={item.text}>
<ListItem disablePadding>
<ListItemButton
onClick={() => {
if (hasChildren) {
toggleExpanded(item.text);
} else {
handleNavigation(item.path);
}
}}
selected={isActive || isParentOfActive}
sx={{
mx: 1,
ml: level > 0 ? 3 : 1,
borderRadius: 2,
minHeight: 48,
'&.Mui-selected': {
backgroundColor: 'primary.main',
color: 'primary.contrastText',
ml: level > 0 ? 3 : 1,
mr: 2,
'&:hover': {
backgroundColor: 'primary.dark',
},
'& .MuiListItemIcon-root': {
color: 'primary.contrastText',
},
},
}}
>
<ListItemIcon sx={{ minWidth: 40 }}>
{item.badge ? (
<Badge badgeContent={item.badge} color="error">
{item.icon}
</Badge>
) : (
item.icon
)}
</ListItemIcon>
<ListItemText
primary={item.text}
primaryTypographyProps={{
fontSize: '0.875rem',
fontWeight: isActive || isParentOfActive ? 600 : 500,
noWrap: true,
}}
/>
{hasChildren && (
isExpanded ? <ExpandLess /> : <ExpandMore />
)}
</ListItemButton>
</ListItem>
{hasChildren && (
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{item.children?.map((child) => renderNavItem(child, level + 1))}
</List>
</Collapse>
)}
</div>
);
};
const drawerContent = (
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
<Box sx={{ p: 3, borderBottom: 1, borderColor: 'divider' }}>
<Box sx={{ height: '100%', display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
<Box sx={{ p: 3, borderBottom: 1, borderColor: 'divider', flexShrink: 0 }}>
<Typography variant="h6" sx={{ fontWeight: 700, color: 'primary.main' }}>
NAO
</Typography>
<Typography variant="body2" color="text.secondary">
Personal Network Hub
</Typography>
</Box>
<List sx={{ flex: 1, py: 2 }}>
{navItems.map((item) => (
<ListItem key={item.text} disablePadding>
<ListItemButton
onClick={() => handleNavigation(item.path)}
selected={isActiveRoute(item.path)}
sx={{
mx: 1,
borderRadius: 2,
minHeight: 48,
'&.Mui-selected': {
backgroundColor: 'primary.main',
color: 'primary.contrastText',
ml: 1,
mr: 2,
'&:hover': {
backgroundColor: 'primary.dark',
},
'& .MuiListItemIcon-root': {
color: 'primary.contrastText',
},
},
}}
>
<ListItemIcon sx={{ minWidth: 40 }}>
{item.badge ? (
<Badge badgeContent={item.badge} color="error">
{item.icon}
</Badge>
) : (
item.icon
)}
</ListItemIcon>
<ListItemText
primary={item.text}
primaryTypographyProps={{
fontSize: '0.875rem',
fontWeight: isActiveRoute(item.path) ? 600 : 500,
}}
/>
</ListItemButton>
</ListItem>
))}
<List sx={{ flex: 1, py: 2, overflow: 'hidden' }}>
{navItems.map((item) => renderNavItem(item))}
</List>
<Divider />

@ -5,11 +5,11 @@ import {
Paper
} from '@mui/material';
import {
Home,
People,
RssFeed,
Hub,
PostAdd,
Message,
AccountCircle,
Chat,
Groups,
} from '@mui/icons-material';
const BottomNavigation = () => {
@ -17,16 +17,27 @@ const BottomNavigation = () => {
const navigate = useNavigate();
const navigationItems = [
{ label: 'Feed', icon: <Home />, path: '/feed' },
{ label: 'Contacts', icon: <People />, path: '/contacts' },
{ label: 'Posts', icon: <PostAdd />, path: '/posts' },
{ label: 'Messages', icon: <Message />, path: '/messages' },
{ label: 'Account', icon: <AccountCircle />, path: '/account' },
{ label: 'Feed', icon: <RssFeed />, path: '/feed' },
{ label: 'Network', icon: <Hub />, path: '/contacts' },
{ label: 'Groups', icon: <Groups />, path: '/groups' },
{ label: 'Post', icon: <PostAdd />, path: '/posts' },
{ label: 'Chat', icon: <Chat />, path: '/messages' },
];
const getCurrentValue = () => {
const currentPath = location.pathname;
if (currentPath === '/' || currentPath === '/feed') return '/feed';
// Handle network path - should highlight Network tab
if (currentPath.startsWith('/contacts')) {
return '/contacts';
}
// Groups has its own tab now
if (currentPath.startsWith('/groups')) {
return '/groups';
}
const activeItem = navigationItems.find(item =>
item.path === currentPath || (item.path !== '/feed' && currentPath.startsWith(item.path))
);

@ -19,15 +19,16 @@ import {
} from '@mui/material';
import {
List as ListIcon,
AccountTree,
ScatterPlot,
Hub,
Map,
Search,
Add,
LinkedIn,
Phone,
Email,
QrCode,
Group
Group,
CloudDownload
} from '@mui/icons-material';
import { dataService } from '../services/dataService';
import type { Contact } from '../types/contact';
@ -109,11 +110,16 @@ const ContactListPage = () => {
<Typography variant="h4" component="h1" sx={{ fontWeight: 700, mb: 1 }}>
Contacts
</Typography>
<Typography variant="body1" color="text.secondary">
Manage your network of {filteredContacts.length} contacts
</Typography>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
variant="outlined"
startIcon={<CloudDownload />}
onClick={() => navigate('/import')}
sx={{ borderRadius: 2 }}
>
Import
</Button>
<Button
variant="outlined"
startIcon={<QrCode />}
@ -148,15 +154,15 @@ const ContactListPage = () => {
}
}}
>
<Tab icon={<ListIcon />} label="List View" />
<Tab icon={<ListIcon />} label="List" />
<Tooltip title="Coming soon">
<span>
<Tab icon={<AccountTree />} label="Network View" disabled />
<Tab icon={<Hub />} label="Network" disabled />
</span>
</Tooltip>
<Tooltip title="Coming soon">
<span>
<Tab icon={<ScatterPlot />} label="Graph View" disabled />
<Tab icon={<Map />} label="Map" disabled />
</span>
</Tooltip>
</Tabs>

Loading…
Cancel
Save