diff --git a/src/App.tsx b/src/App.tsx
index 4a1c43a..64a77a6 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,25 +1,16 @@
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
-import { ThemeProvider, createTheme } from '@mui/material/styles';
+import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
-import { AppBar, Toolbar, Typography, Box, Button } from '@mui/material';
-import { QrCode, PersonAdd } from '@mui/icons-material';
import { OnboardingProvider } from './context/OnboardingContext';
+import DashboardLayout from './components/layout/DashboardLayout';
import ImportPage from './pages/ImportPage';
import ContactListPage from './pages/ContactListPage';
import ContactViewPage from './pages/ContactViewPage';
import InvitationPage from './pages/InvitationPage';
import OnboardingPage from './pages/OnboardingPage';
+import { createAppTheme } from './theme/theme';
-const theme = createTheme({
- palette: {
- primary: {
- main: '#1976d2',
- },
- secondary: {
- main: '#dc004e',
- },
- },
-});
+const theme = createAppTheme('light');
function App() {
return (
@@ -27,29 +18,7 @@ function App() {
-
-
-
-
- Personal Network Manager
-
- }
- href="/invite"
- sx={{ mr: 1 }}
- >
- Invite
-
- }
- href="/onboarding"
- >
- Join
-
-
-
+
} />
} />
@@ -57,7 +26,7 @@ function App() {
} />
} />
-
+
diff --git a/src/components/layout/DashboardLayout.tsx b/src/components/layout/DashboardLayout.tsx
new file mode 100644
index 0000000..7bb5a11
--- /dev/null
+++ b/src/components/layout/DashboardLayout.tsx
@@ -0,0 +1,314 @@
+import { useState } from 'react';
+import type { ReactNode } from 'react';
+import { useLocation, useNavigate } from 'react-router-dom';
+import {
+ Box,
+ Drawer,
+ AppBar,
+ Toolbar,
+ List,
+ Typography,
+ Divider,
+ IconButton,
+ ListItem,
+ ListItemButton,
+ ListItemIcon,
+ ListItemText,
+ useTheme,
+ useMediaQuery,
+ Avatar,
+ Menu,
+ MenuItem,
+ Badge,
+} from '@mui/material';
+import {
+ Menu as MenuIcon,
+ People,
+ QrCode,
+ PersonAdd,
+ CloudDownload,
+ Settings,
+ Logout,
+ NotificationsNone,
+ SearchRounded,
+} from '@mui/icons-material';
+
+const drawerWidth = 280;
+
+interface NavItem {
+ text: string;
+ icon: ReactNode;
+ path: string;
+ badge?: number;
+}
+
+interface DashboardLayoutProps {
+ children: ReactNode;
+}
+
+const DashboardLayout = ({ children }: DashboardLayoutProps) => {
+ const theme = useTheme();
+ const isMobile = useMediaQuery(theme.breakpoints.down('lg'));
+ const [mobileOpen, setMobileOpen] = useState(false);
+ const [profileMenuAnchor, setProfileMenuAnchor] = useState(null);
+ const location = useLocation();
+ const navigate = useNavigate();
+
+ const navItems: NavItem[] = [
+ { text: 'Contacts', icon: , path: '/contacts' },
+ { text: 'Import', icon: , path: '/' },
+ { text: 'Invite', icon: , path: '/invite' },
+ { text: 'Join Network', icon: , path: '/onboarding' },
+ ];
+
+ const handleDrawerToggle = () => {
+ setMobileOpen(!mobileOpen);
+ };
+
+ const handleProfileMenuOpen = (event: React.MouseEvent) => {
+ setProfileMenuAnchor(event.currentTarget);
+ };
+
+ const handleProfileMenuClose = () => {
+ setProfileMenuAnchor(null);
+ };
+
+ const handleNavigation = (path: string) => {
+ navigate(path);
+ if (isMobile) {
+ setMobileOpen(false);
+ }
+ };
+
+ const isActiveRoute = (path: string) => {
+ if (path === '/' && location.pathname === '/') return true;
+ if (path !== '/' && location.pathname.startsWith(path)) return true;
+ return false;
+ };
+
+ const drawerContent = (
+
+
+
+ Network Manager
+
+
+ Personal Network Hub
+
+
+
+
+ {navItems.map((item) => (
+
+ handleNavigation(item.path)}
+ selected={isActiveRoute(item.path)}
+ sx={{
+ mx: 1,
+ borderRadius: 2,
+ minHeight: 48,
+ '&.Mui-selected': {
+ backgroundColor: 'primary.main',
+ color: 'primary.contrastText',
+ '&:hover': {
+ backgroundColor: 'primary.dark',
+ },
+ '& .MuiListItemIcon-root': {
+ color: 'primary.contrastText',
+ },
+ },
+ }}
+ >
+
+ {item.badge ? (
+
+ {item.icon}
+
+ ) : (
+ item.icon
+ )}
+
+
+
+
+ ))}
+
+
+
+
+
+
+ handleNavigation('/settings')}
+ sx={{
+ mx: 1,
+ borderRadius: 2,
+ minHeight: 48,
+ }}
+ >
+
+
+
+
+
+
+
+
+ );
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ U
+
+
+
+
+
+
+
+
+ {drawerContent}
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+};
+
+export default DashboardLayout;
\ No newline at end of file
diff --git a/src/pages/ContactListPage.tsx b/src/pages/ContactListPage.tsx
index 168444d..b09c5fb 100644
--- a/src/pages/ContactListPage.tsx
+++ b/src/pages/ContactListPage.tsx
@@ -1,23 +1,21 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
- Container,
Typography,
Box,
Tabs,
Tab,
- List,
- ListItem,
- ListItemAvatar,
- ListItemText,
Avatar,
Chip,
- Paper,
- Divider,
TextField,
InputAdornment,
Button,
- Tooltip
+ Tooltip,
+ Card,
+ CardContent,
+ Grid,
+ alpha,
+ useTheme
} from '@mui/material';
import {
List as ListIcon,
@@ -39,6 +37,7 @@ const ContactListPage = () => {
const [searchQuery, setSearchQuery] = useState('');
const [tabValue, setTabValue] = useState(0);
const [isLoading, setIsLoading] = useState(true);
+ const theme = useTheme();
const navigate = useNavigate();
useEffect(() => {
@@ -103,16 +102,22 @@ const ContactListPage = () => {
};
return (
-
+
-
- Contacts ({filteredContacts.length})
-
+
+
+ Contacts
+
+
+ Manage your network of {filteredContacts.length} contacts
+
+
}
onClick={handleInvite}
+ sx={{ borderRadius: 2 }}
>
Invite
@@ -120,18 +125,27 @@ const ContactListPage = () => {
variant="contained"
startIcon={}
onClick={handleAddContact}
+ sx={{ borderRadius: 2 }}
>
Add Contacts
-
+
} label="List View" />
@@ -146,7 +160,7 @@ const ContactListPage = () => {
-
+
{
),
}}
- sx={{ mb: 2 }}
+ sx={{ mb: 3 }}
/>
{isLoading ? (
-
-
+
+
Loading contacts...
+
+ Please wait while we fetch your contacts
+
) : filteredContacts.length === 0 ? (
-
-
- {searchQuery ? 'No contacts found matching your search.' : 'No contacts yet. Import some contacts to get started!'}
+
+
+ {searchQuery ? 'No contacts found' : 'No contacts yet'}
+
+
+ {searchQuery ? 'Try adjusting your search terms.' : 'Import some contacts to get started!'}
) : (
-
- {filteredContacts.map((contact, index) => (
-
-
+ {filteredContacts.map((contact) => (
+
+ handleContactClick(contact.id)}
sx={{
cursor: 'pointer',
- borderRadius: 1,
+ transition: 'all 0.2s ease-in-out',
+ border: 1,
+ borderColor: 'divider',
'&:hover': {
- backgroundColor: 'action.hover',
+ borderColor: 'primary.main',
+ boxShadow: theme.shadows[4],
+ transform: 'translateY(-2px)',
},
}}
>
-
-
- {contact.name.charAt(0)}
-
-
-
-
- {contact.name}
-
- {getSourceIcon(contact.source)}
-
- }
- secondary={
-
-
+
+
+
+ {contact.name.charAt(0)}
+
+
+
+
+
+ {contact.name}
+
+ {getSourceIcon(contact.source)}
+
+
+
{contact.position} {contact.company && `at ${contact.company}`}
-
+
+
{contact.email}
-
+
+
{contact.tags?.map((tag) => (
-
+
))}
-
+
+
Added {formatDate(contact.createdAt)}
- }
- />
-
- {index < filteredContacts.length - 1 && }
-
+
+
+
+
))}
-
+
)}
-
-
+
+
);
};
diff --git a/src/pages/ImportPage.tsx b/src/pages/ImportPage.tsx
index 71cc49a..b823c8a 100644
--- a/src/pages/ImportPage.tsx
+++ b/src/pages/ImportPage.tsx
@@ -1,7 +1,6 @@
import { useState, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import {
- Container,
Typography,
Grid,
Card,
@@ -85,13 +84,15 @@ const ImportPage = () => {
};
return (
-
-
- Import Your Contacts
-
-
- Choose a source to import your contacts from
-
+
+
+
+ Import Your Contacts
+
+
+ Choose a source to import your contacts from
+
+
{importSources.map((source) => (
@@ -101,30 +102,34 @@ const ImportPage = () => {
height: '100%',
display: 'flex',
flexDirection: 'column',
- transition: 'all 0.3s ease-in-out',
+ transition: 'all 0.2s ease-in-out',
+ border: 1,
+ borderColor: 'divider',
'&:hover': {
- transform: 'translateY(-4px)',
- boxShadow: 3,
+ transform: 'translateY(-2px)',
+ boxShadow: 4,
+ borderColor: 'primary.main',
}
}}
>
-
-
+
+
{getSourceIcon(source.icon)}
-
+
{source.name}
-
+
{source.description}
-
+
@@ -182,7 +187,7 @@ const ImportPage = () => {
)}
-
+
);
};
diff --git a/src/theme/theme.ts b/src/theme/theme.ts
new file mode 100644
index 0000000..29ea538
--- /dev/null
+++ b/src/theme/theme.ts
@@ -0,0 +1,384 @@
+import { createTheme, alpha } from '@mui/material/styles';
+import type { PaletteMode } from '@mui/material';
+
+// Custom color palette inspired by professional dashboards
+const colors = {
+ primary: {
+ 50: '#e8f4fd',
+ 100: '#d1e9fb',
+ 200: '#a3d2f7',
+ 300: '#75bcf3',
+ 400: '#47a5ef',
+ 500: '#1976d2', // Main brand color
+ 600: '#155a9f',
+ 700: '#10436c',
+ 800: '#0a2d39',
+ 900: '#051616',
+ },
+ secondary: {
+ 50: '#fce4ec',
+ 100: '#f8bbd9',
+ 200: '#f48fb1',
+ 300: '#f06292',
+ 400: '#ec407a',
+ 500: '#dc004e', // Accent color
+ 600: '#c2185b',
+ 700: '#ad1457',
+ 800: '#880e4f',
+ 900: '#560e2e',
+ },
+ neutral: {
+ 50: '#fafafa',
+ 100: '#f5f5f5',
+ 200: '#eeeeee',
+ 300: '#e0e0e0',
+ 400: '#bdbdbd',
+ 500: '#9e9e9e',
+ 600: '#757575',
+ 700: '#616161',
+ 800: '#424242',
+ 900: '#212121',
+ },
+ success: {
+ 50: '#e8f5e8',
+ 100: '#c8e6c9',
+ 200: '#a5d6a7',
+ 300: '#81c784',
+ 400: '#66bb6a',
+ 500: '#4caf50',
+ 600: '#43a047',
+ 700: '#388e3c',
+ 800: '#2e7d32',
+ 900: '#1b5e20',
+ },
+ warning: {
+ 50: '#fff8e1',
+ 100: '#ffecb3',
+ 200: '#ffe082',
+ 300: '#ffd54f',
+ 400: '#ffca28',
+ 500: '#ffc107',
+ 600: '#ffb300',
+ 700: '#ffa000',
+ 800: '#ff8f00',
+ 900: '#ff6f00',
+ },
+ error: {
+ 50: '#ffebee',
+ 100: '#ffcdd2',
+ 200: '#ef9a9a',
+ 300: '#e57373',
+ 400: '#ef5350',
+ 500: '#f44336',
+ 600: '#e53935',
+ 700: '#d32f2f',
+ 800: '#c62828',
+ 900: '#b71c1c',
+ },
+};
+
+// Enhanced theme configuration
+export const createAppTheme = (mode: PaletteMode) => {
+ const isDark = mode === 'dark';
+
+ return createTheme({
+ palette: {
+ mode,
+ primary: {
+ main: colors.primary[500],
+ light: colors.primary[300],
+ dark: colors.primary[700],
+ contrastText: '#ffffff',
+ },
+ secondary: {
+ main: colors.secondary[500],
+ light: colors.secondary[300],
+ dark: colors.secondary[700],
+ contrastText: '#ffffff',
+ },
+ success: {
+ main: colors.success[500],
+ light: colors.success[300],
+ dark: colors.success[700],
+ },
+ warning: {
+ main: colors.warning[500],
+ light: colors.warning[300],
+ dark: colors.warning[700],
+ },
+ error: {
+ main: colors.error[500],
+ light: colors.error[300],
+ dark: colors.error[700],
+ },
+ background: {
+ default: isDark ? '#0a1929' : '#f8fafc',
+ paper: isDark ? '#1e293b' : '#ffffff',
+ },
+ text: {
+ primary: isDark ? '#e2e8f0' : '#334155',
+ secondary: isDark ? '#94a3b8' : '#64748b',
+ },
+ divider: isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08),
+ action: {
+ hover: isDark ? alpha('#e2e8f0', 0.04) : alpha('#334155', 0.04),
+ selected: isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08),
+ },
+ },
+ typography: {
+ fontFamily: '"Inter", "Roboto", "Helvetica", "Arial", sans-serif',
+ h1: {
+ fontSize: '2.5rem',
+ fontWeight: 700,
+ lineHeight: 1.2,
+ letterSpacing: '-0.02em',
+ },
+ h2: {
+ fontSize: '2rem',
+ fontWeight: 600,
+ lineHeight: 1.3,
+ letterSpacing: '-0.01em',
+ },
+ h3: {
+ fontSize: '1.75rem',
+ fontWeight: 600,
+ lineHeight: 1.3,
+ letterSpacing: '-0.01em',
+ },
+ h4: {
+ fontSize: '1.5rem',
+ fontWeight: 600,
+ lineHeight: 1.4,
+ letterSpacing: '-0.005em',
+ },
+ h5: {
+ fontSize: '1.25rem',
+ fontWeight: 600,
+ lineHeight: 1.4,
+ },
+ h6: {
+ fontSize: '1.125rem',
+ fontWeight: 600,
+ lineHeight: 1.4,
+ },
+ subtitle1: {
+ fontSize: '1rem',
+ fontWeight: 500,
+ lineHeight: 1.5,
+ },
+ subtitle2: {
+ fontSize: '0.875rem',
+ fontWeight: 500,
+ lineHeight: 1.5,
+ },
+ body1: {
+ fontSize: '1rem',
+ fontWeight: 400,
+ lineHeight: 1.6,
+ },
+ body2: {
+ fontSize: '0.875rem',
+ fontWeight: 400,
+ lineHeight: 1.6,
+ },
+ button: {
+ fontSize: '0.875rem',
+ fontWeight: 500,
+ lineHeight: 1.5,
+ textTransform: 'none' as const,
+ },
+ caption: {
+ fontSize: '0.75rem',
+ fontWeight: 400,
+ lineHeight: 1.5,
+ },
+ overline: {
+ fontSize: '0.75rem',
+ fontWeight: 500,
+ lineHeight: 1.5,
+ textTransform: 'uppercase' as const,
+ letterSpacing: '0.08em',
+ },
+ },
+ spacing: 8,
+ shape: {
+ borderRadius: 12,
+ },
+ shadows: [
+ 'none',
+ '0px 1px 3px rgba(0, 0, 0, 0.04), 0px 1px 2px rgba(0, 0, 0, 0.06)',
+ '0px 2px 4px rgba(0, 0, 0, 0.04), 0px 2px 3px rgba(0, 0, 0, 0.06)',
+ '0px 3px 6px rgba(0, 0, 0, 0.04), 0px 3px 4px rgba(0, 0, 0, 0.06)',
+ '0px 4px 8px rgba(0, 0, 0, 0.04), 0px 4px 6px rgba(0, 0, 0, 0.06)',
+ '0px 6px 12px rgba(0, 0, 0, 0.04), 0px 6px 8px rgba(0, 0, 0, 0.06)',
+ '0px 8px 16px rgba(0, 0, 0, 0.04), 0px 8px 12px rgba(0, 0, 0, 0.06)',
+ '0px 12px 24px rgba(0, 0, 0, 0.04), 0px 12px 18px rgba(0, 0, 0, 0.06)',
+ '0px 16px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.06)',
+ '0px 24px 48px rgba(0, 0, 0, 0.04), 0px 24px 36px rgba(0, 0, 0, 0.06)',
+ '0px 32px 64px rgba(0, 0, 0, 0.04), 0px 32px 48px rgba(0, 0, 0, 0.06)',
+ '0px 40px 80px rgba(0, 0, 0, 0.04), 0px 40px 60px rgba(0, 0, 0, 0.06)',
+ '0px 48px 96px rgba(0, 0, 0, 0.04), 0px 48px 72px rgba(0, 0, 0, 0.06)',
+ '0px 56px 112px rgba(0, 0, 0, 0.04), 0px 56px 84px rgba(0, 0, 0, 0.06)',
+ '0px 64px 128px rgba(0, 0, 0, 0.04), 0px 64px 96px rgba(0, 0, 0, 0.06)',
+ '0px 72px 144px rgba(0, 0, 0, 0.04), 0px 72px 108px rgba(0, 0, 0, 0.06)',
+ '0px 80px 160px rgba(0, 0, 0, 0.04), 0px 80px 120px rgba(0, 0, 0, 0.06)',
+ '0px 88px 176px rgba(0, 0, 0, 0.04), 0px 88px 132px rgba(0, 0, 0, 0.06)',
+ '0px 96px 192px rgba(0, 0, 0, 0.04), 0px 96px 144px rgba(0, 0, 0, 0.06)',
+ '0px 104px 208px rgba(0, 0, 0, 0.04), 0px 104px 156px rgba(0, 0, 0, 0.06)',
+ '0px 112px 224px rgba(0, 0, 0, 0.04), 0px 112px 168px rgba(0, 0, 0, 0.06)',
+ '0px 120px 240px rgba(0, 0, 0, 0.04), 0px 120px 180px rgba(0, 0, 0, 0.06)',
+ '0px 128px 256px rgba(0, 0, 0, 0.04), 0px 128px 192px rgba(0, 0, 0, 0.06)',
+ '0px 136px 272px rgba(0, 0, 0, 0.04), 0px 136px 204px rgba(0, 0, 0, 0.06)',
+ '0px 144px 288px rgba(0, 0, 0, 0.04), 0px 144px 216px rgba(0, 0, 0, 0.06)',
+ ],
+ components: {
+ MuiCssBaseline: {
+ styleOverrides: {
+ '*': {
+ boxSizing: 'border-box',
+ },
+ html: {
+ MozOsxFontSmoothing: 'grayscale',
+ WebkitFontSmoothing: 'antialiased',
+ display: 'flex',
+ flexDirection: 'column',
+ minHeight: '100%',
+ width: '100%',
+ },
+ body: {
+ display: 'flex',
+ flex: '1 1 auto',
+ flexDirection: 'column',
+ minHeight: '100%',
+ width: '100%',
+ },
+ '#root': {
+ display: 'flex',
+ flex: '1 1 auto',
+ flexDirection: 'column',
+ height: '100%',
+ width: '100%',
+ },
+ },
+ },
+ MuiButton: {
+ styleOverrides: {
+ root: {
+ borderRadius: 8,
+ padding: '8px 16px',
+ fontWeight: 500,
+ fontSize: '0.875rem',
+ lineHeight: 1.5,
+ textTransform: 'none',
+ boxShadow: 'none',
+ '&:hover': {
+ boxShadow: 'none',
+ },
+ '&:active': {
+ boxShadow: 'none',
+ },
+ },
+ contained: {
+ '&:hover': {
+ boxShadow: '0px 2px 4px rgba(0, 0, 0, 0.08), 0px 2px 3px rgba(0, 0, 0, 0.12)',
+ },
+ },
+ },
+ },
+ MuiCard: {
+ styleOverrides: {
+ root: {
+ borderRadius: 12,
+ boxShadow: '0px 1px 3px rgba(0, 0, 0, 0.04), 0px 1px 2px rgba(0, 0, 0, 0.06)',
+ border: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`,
+ '&:hover': {
+ boxShadow: '0px 4px 8px rgba(0, 0, 0, 0.08), 0px 4px 6px rgba(0, 0, 0, 0.12)',
+ },
+ },
+ },
+ },
+ MuiPaper: {
+ styleOverrides: {
+ root: {
+ borderRadius: 12,
+ border: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`,
+ },
+ },
+ },
+ MuiTextField: {
+ styleOverrides: {
+ root: {
+ '& .MuiOutlinedInput-root': {
+ borderRadius: 8,
+ backgroundColor: isDark ? alpha('#e2e8f0', 0.02) : alpha('#334155', 0.02),
+ '&:hover': {
+ backgroundColor: isDark ? alpha('#e2e8f0', 0.04) : alpha('#334155', 0.04),
+ },
+ '&.Mui-focused': {
+ backgroundColor: isDark ? alpha('#e2e8f0', 0.04) : alpha('#334155', 0.04),
+ },
+ },
+ },
+ },
+ },
+ MuiAppBar: {
+ styleOverrides: {
+ root: {
+ backgroundColor: isDark ? '#1e293b' : '#ffffff',
+ color: isDark ? '#e2e8f0' : '#334155',
+ boxShadow: 'none',
+ borderBottom: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`,
+ },
+ },
+ },
+ MuiDrawer: {
+ styleOverrides: {
+ paper: {
+ backgroundColor: isDark ? '#0f172a' : '#ffffff',
+ borderRight: `1px solid ${isDark ? alpha('#e2e8f0', 0.08) : alpha('#334155', 0.08)}`,
+ },
+ },
+ },
+ MuiListItem: {
+ styleOverrides: {
+ root: {
+ borderRadius: 8,
+ margin: '2px 8px',
+ '&:hover': {
+ backgroundColor: isDark ? alpha('#e2e8f0', 0.04) : alpha('#334155', 0.04),
+ },
+ '&.Mui-selected': {
+ backgroundColor: isDark ? alpha('#1976d2', 0.12) : alpha('#1976d2', 0.08),
+ '&:hover': {
+ backgroundColor: isDark ? alpha('#1976d2', 0.16) : alpha('#1976d2', 0.12),
+ },
+ },
+ },
+ },
+ },
+ MuiTabs: {
+ styleOverrides: {
+ indicator: {
+ borderRadius: 2,
+ height: 3,
+ },
+ },
+ },
+ MuiTab: {
+ styleOverrides: {
+ root: {
+ textTransform: 'none',
+ fontWeight: 500,
+ fontSize: '0.875rem',
+ minHeight: 48,
+ '&.Mui-selected': {
+ fontWeight: 600,
+ },
+ },
+ },
+ },
+ },
+ });
+};
+
+export default createAppTheme;
\ No newline at end of file