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 - - - - - + } /> } /> @@ -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} + + + + + + My Profile + + + Settings + + + + Logout + + + + ); +}; + +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 + + @@ -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