chore: UX design audit cleanup and receipt flow improvements #186
@@ -6,7 +6,7 @@ import React from 'react';
|
||||
import { useAuth0 } from '@auth0/auth0-react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { useLogout } from '../core/auth/useLogout';
|
||||
import { Container, Paper, Typography, Box, IconButton, Avatar } from '@mui/material';
|
||||
import { Container, Paper, Typography, Box, IconButton, Avatar, Tooltip } from '@mui/material';
|
||||
import HomeRoundedIcon from '@mui/icons-material/HomeRounded';
|
||||
import DirectionsCarRoundedIcon from '@mui/icons-material/DirectionsCarRounded';
|
||||
import LocalGasStationRoundedIcon from '@mui/icons-material/LocalGasStationRounded';
|
||||
@@ -15,7 +15,8 @@ import PlaceRoundedIcon from '@mui/icons-material/PlaceRounded';
|
||||
import SettingsRoundedIcon from '@mui/icons-material/SettingsRounded';
|
||||
import DescriptionRoundedIcon from '@mui/icons-material/DescriptionRounded';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded';
|
||||
import ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';
|
||||
import { useAppStore } from '../core/store';
|
||||
import { Button } from '../shared-minimal/components/Button';
|
||||
import { NotificationBell } from '../features/notifications';
|
||||
@@ -29,7 +30,7 @@ interface LayoutProps {
|
||||
export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false }) => {
|
||||
const { user } = useAuth0();
|
||||
const { logout } = useLogout();
|
||||
const { sidebarOpen, toggleSidebar, setSidebarOpen } = useAppStore();
|
||||
const { sidebarOpen, sidebarCollapsed, toggleSidebar, setSidebarOpen, toggleSidebarCollapse } = useAppStore();
|
||||
const location = useLocation();
|
||||
|
||||
// Sync theme preference with backend
|
||||
@@ -52,6 +53,8 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
||||
{ name: 'Settings', href: '/garage/settings', icon: <SettingsRoundedIcon sx={{ fontSize: 20 }} /> },
|
||||
];
|
||||
|
||||
const sidebarWidth = sidebarCollapsed ? 64 : 256;
|
||||
|
||||
// Mobile layout
|
||||
if (mobileMode) {
|
||||
return (
|
||||
@@ -107,61 +110,65 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: '100vh',
|
||||
width: 256,
|
||||
width: sidebarWidth,
|
||||
zIndex: 1000,
|
||||
borderRadius: 0,
|
||||
borderRight: 1,
|
||||
borderColor: 'divider',
|
||||
transform: sidebarOpen ? 'translateX(0)' : 'translateX(-100%)',
|
||||
transition: 'transform 0.2s ease-in-out',
|
||||
transition: 'transform 0.2s ease-in-out, width 0.2s ease-in-out',
|
||||
display: 'flex',
|
||||
flexDirection: 'column'
|
||||
flexDirection: 'column',
|
||||
overflow: 'hidden',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
justifyContent: sidebarCollapsed ? 'center' : 'space-between',
|
||||
height: 64,
|
||||
px: 2,
|
||||
px: sidebarCollapsed ? 1 : 2,
|
||||
borderBottom: 1,
|
||||
borderColor: 'divider',
|
||||
gap: 1
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={(theme) => ({
|
||||
backgroundColor: 'primary.main',
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: 'transparent',
|
||||
}),
|
||||
borderRadius: 0.5,
|
||||
px: 1,
|
||||
py: 0.5,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center'
|
||||
})}
|
||||
>
|
||||
<img
|
||||
src="/images/logos/motovaultpro-title-slogan.png"
|
||||
alt="MotoVaultPro"
|
||||
style={{ height: 24, width: 'auto', maxWidth: 180 }}
|
||||
/>
|
||||
</Box>
|
||||
{!sidebarCollapsed && (
|
||||
<Box
|
||||
sx={(theme) => ({
|
||||
backgroundColor: 'primary.main',
|
||||
...theme.applyStyles('dark', {
|
||||
backgroundColor: 'transparent',
|
||||
}),
|
||||
borderRadius: 0.5,
|
||||
px: 1,
|
||||
py: 0.5,
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
overflow: 'hidden',
|
||||
})}
|
||||
>
|
||||
<img
|
||||
src="/images/logos/motovaultpro-title-slogan.png"
|
||||
alt="MotoVaultPro"
|
||||
style={{ height: 24, width: 'auto', maxWidth: 180 }}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
<IconButton
|
||||
onClick={toggleSidebar}
|
||||
onClick={toggleSidebarCollapse}
|
||||
size="small"
|
||||
sx={{ color: 'text.secondary', flexShrink: 0 }}
|
||||
>
|
||||
<CloseIcon />
|
||||
{sidebarCollapsed ? <ChevronRightRoundedIcon /> : <ChevronLeftRoundedIcon />}
|
||||
</IconButton>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ mt: 3, px: 2, flex: 1 }}>
|
||||
<Box sx={{ mt: 3, px: sidebarCollapsed ? 1 : 2, flex: 1 }}>
|
||||
{navigation.map((item) => {
|
||||
const isActive = location.pathname.startsWith(item.href);
|
||||
return (
|
||||
const navItem = (
|
||||
<Link
|
||||
key={item.name}
|
||||
to={item.href}
|
||||
@@ -171,7 +178,8 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
px: 2,
|
||||
justifyContent: sidebarCollapsed ? 'center' : 'flex-start',
|
||||
px: sidebarCollapsed ? 1 : 2,
|
||||
py: 1.5,
|
||||
mb: 0.5,
|
||||
borderRadius: 2,
|
||||
@@ -189,52 +197,82 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Box sx={{ mr: 2, display: 'flex', alignItems: 'center' }}>
|
||||
<Box sx={{ mr: sidebarCollapsed ? 0 : 2, display: 'flex', alignItems: 'center' }}>
|
||||
{item.icon}
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }}>
|
||||
{item.name}
|
||||
</Typography>
|
||||
{!sidebarCollapsed && (
|
||||
<Typography variant="body2" sx={{ fontWeight: 500, whiteSpace: 'nowrap' }}>
|
||||
{item.name}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</Link>
|
||||
);
|
||||
return sidebarCollapsed ? (
|
||||
<Tooltip key={item.name} title={item.name} placement="right" arrow>
|
||||
{navItem}
|
||||
</Tooltip>
|
||||
) : (
|
||||
navItem
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
|
||||
<Box sx={{ p: 2, borderTop: 1, borderColor: 'divider', mt: 'auto' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
bgcolor: 'primary.main',
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 600
|
||||
}}
|
||||
>
|
||||
{user?.name?.charAt(0) || user?.email?.charAt(0)}
|
||||
</Avatar>
|
||||
<Box sx={{ ml: 1.5, flex: 1, minWidth: 0 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }} noWrap>
|
||||
{user?.name || user?.email}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => logout()}
|
||||
>
|
||||
Sign Out
|
||||
</Button>
|
||||
<Box sx={{ p: sidebarCollapsed ? 1 : 2, borderTop: 1, borderColor: 'divider', mt: 'auto' }}>
|
||||
{sidebarCollapsed ? (
|
||||
<Tooltip title={user?.name || user?.email || 'User'} placement="right" arrow>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
bgcolor: 'primary.main',
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 600,
|
||||
mx: 'auto',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={() => logout()}
|
||||
>
|
||||
{user?.name?.charAt(0) || user?.email?.charAt(0)}
|
||||
</Avatar>
|
||||
</Tooltip>
|
||||
) : (
|
||||
<>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 32,
|
||||
height: 32,
|
||||
bgcolor: 'primary.main',
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 600
|
||||
}}
|
||||
>
|
||||
{user?.name?.charAt(0) || user?.email?.charAt(0)}
|
||||
</Avatar>
|
||||
<Box sx={{ ml: 1.5, flex: 1, minWidth: 0 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 500 }} noWrap>
|
||||
{user?.name || user?.email}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="w-full"
|
||||
onClick={() => logout()}
|
||||
>
|
||||
Sign Out
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Main content */}
|
||||
<Box
|
||||
sx={{
|
||||
ml: sidebarOpen ? '256px' : '0',
|
||||
ml: sidebarOpen ? `${sidebarWidth}px` : '0',
|
||||
transition: 'margin-left 0.2s ease-in-out',
|
||||
}}
|
||||
>
|
||||
@@ -255,7 +293,7 @@ export const Layout: React.FC<LayoutProps> = ({ children, mobileMode = false })
|
||||
px: 3
|
||||
}}>
|
||||
<IconButton
|
||||
onClick={toggleSidebar}
|
||||
onClick={toggleSidebarCollapse}
|
||||
sx={{ color: 'text.secondary' }}
|
||||
>
|
||||
<MenuIcon />
|
||||
|
||||
@@ -4,21 +4,31 @@ import { Vehicle } from '../../features/vehicles/types/vehicles.types';
|
||||
interface AppState {
|
||||
// UI state
|
||||
sidebarOpen: boolean;
|
||||
sidebarCollapsed: boolean;
|
||||
selectedVehicle: Vehicle | null;
|
||||
|
||||
// Actions
|
||||
toggleSidebar: () => void;
|
||||
setSidebarOpen: (open: boolean) => void;
|
||||
toggleSidebarCollapse: () => void;
|
||||
setSelectedVehicle: (vehicle: Vehicle | null) => void;
|
||||
}
|
||||
|
||||
const savedCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
|
||||
|
||||
export const useAppStore = create<AppState>((set) => ({
|
||||
// Initial state
|
||||
sidebarOpen: false,
|
||||
sidebarCollapsed: savedCollapsed,
|
||||
selectedVehicle: null,
|
||||
|
||||
// Actions
|
||||
toggleSidebar: () => set((state) => ({ sidebarOpen: !state.sidebarOpen })),
|
||||
setSidebarOpen: (open: boolean) => set({ sidebarOpen: open }),
|
||||
toggleSidebarCollapse: () => set((state) => {
|
||||
const next = !state.sidebarCollapsed;
|
||||
localStorage.setItem('sidebarCollapsed', String(next));
|
||||
return { sidebarCollapsed: next };
|
||||
}),
|
||||
setSelectedVehicle: (vehicle: Vehicle | null) => set({ selectedVehicle: vehicle }),
|
||||
}));
|
||||
Reference in New Issue
Block a user