Mobile friendly gallery

This commit is contained in:
lumijiez
2025-06-29 23:46:58 +03:00
parent 4cb6dd0759
commit 430fb4fd09
7 changed files with 222 additions and 122 deletions

View File

@@ -58,14 +58,13 @@ export default function ImprinkAppBar() {
const navigationLinks: NavLink[] = [ const navigationLinks: NavLink[] = [
{ label: 'Home', href: '/', icon: <Home />, show: true }, { label: 'Home', href: '/', icon: <Home />, show: true },
{ label: 'Gallery', href: '/gallery', icon: <PhotoLibrary />, show: true }, { label: 'Gallery', href: '/gallery', icon: <PhotoLibrary />, show: true },
{ label: 'Orders', href: '/orders', icon: <ShoppingBag />, show: true }, { label: 'Orders', href: '/orders', icon: <ShoppingBag />, show: !!user },
{ label: 'Merchant', href: '/merchant', icon: <Store />, show: isMerchant }, { label: 'Merchant', href: '/merchant', icon: <Store />, show: isMerchant },
]; ];
const adminLinks: NavLink[] = [ const adminLinks: NavLink[] = [
{ label: 'Dashboard', href: '/dashboard', icon: <Dashboard />, show: isMerchant }, { label: 'Dashboard', href: '/dashboard', icon: <Dashboard />, show: isMerchant },
{ label: 'Admin', href: '/admin', icon: <AdminPanelSettings />, show: isAdmin }, { label: 'Admin', href: '/admin', icon: <AdminPanelSettings />, show: isAdmin },
{ label: 'Swagger', href: '/swagger', icon: <Api />, show: isAdmin },
{ label: 'SEQ', href: '/seq', icon: <BugReport />, show: isAdmin }, { label: 'SEQ', href: '/seq', icon: <BugReport />, show: isAdmin },
]; ];
@@ -164,14 +163,6 @@ export default function ImprinkAppBar() {
<Avatar src={user.picture ?? undefined} alt={user.name ?? 'User'} sx={{ width: 24, height: 24 }} /> <Avatar src={user.picture ?? undefined} alt={user.name ?? 'User'} sx={{ width: 24, height: 24 }} />
<Typography variant="body2" noWrap>{user.name}</Typography> <Typography variant="body2" noWrap>{user.name}</Typography>
</MenuItem> </MenuItem>
<MenuItem onClick={e => e.stopPropagation()}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, width: '100%' }}>
<Typography variant="body2">Theme</Typography>
<Box sx={{ ml: 'auto' }}>
<ThemeToggleButton />
</Box>
</Box>
</MenuItem>
<MenuItem component="a" href="/auth/logout" onClick={handleMenuClose} sx={{ color: 'error.main' }}> <MenuItem component="a" href="/auth/logout" onClick={handleMenuClose} sx={{ color: 'error.main' }}>
<Typography variant="body2">Logout</Typography> <Typography variant="body2">Logout</Typography>
</MenuItem> </MenuItem>
@@ -184,12 +175,6 @@ export default function ImprinkAppBar() {
<MenuItem component="a" href="/auth/login" onClick={handleMenuClose} sx={{ color: 'primary.main', fontWeight: 'bold' }}> <MenuItem component="a" href="/auth/login" onClick={handleMenuClose} sx={{ color: 'primary.main', fontWeight: 'bold' }}>
<Typography variant="body2">Sign Up</Typography> <Typography variant="body2">Sign Up</Typography>
</MenuItem> </MenuItem>
<MenuItem onClick={toggleTheme}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, width: '100%' }}>
<Typography variant="body2">Theme</Typography>
<Box sx={{ ml: 'auto' }}>{isDarkMode ? '🌙' : '☀️'}</Box>
</Box>
</MenuItem>
</> </>
)} )}
</> </>
@@ -230,6 +215,12 @@ export default function ImprinkAppBar() {
)} )}
</> </>
)} )}
{/* Add theme toggle button to the right side of the toolbar on mobile */}
{isMobile && (
<Box sx={{ ml: 'auto' }}>
<ThemeToggleButton />
</Box>
)}
</Toolbar> </Toolbar>
</AppBar> </AppBar>
{renderAdminBar()} {renderAdminBar()}

View File

@@ -46,7 +46,6 @@ export default function CategorySidebar({
return ( return (
<Box sx={{ <Box sx={{
width: 300,
height: '100%', height: '100%',
display: 'flex', display: 'flex',
flexDirection: 'column' flexDirection: 'column'
@@ -56,9 +55,7 @@ export default function CategorySidebar({
mb: 2, mb: 2,
p: 2, p: 2,
pb: 1, pb: 1,
flexShrink: 0, flexShrink: 0
borderBottom: 1,
borderColor: 'divider'
}}> }}>
Categories Categories
</Typography> </Typography>
@@ -90,7 +87,7 @@ export default function CategorySidebar({
<ListItem disablePadding> <ListItem disablePadding>
<ListItemButton <ListItemButton
selected={filters.categoryId === category.id} selected={filters.categoryId === category.id}
onClick={() => onFilterChange('categoryId', category.id)} onClick={() => onToggleCategoryExpansion(category.id)}
sx={{ borderRadius: 1, mb: 0.5 }} sx={{ borderRadius: 1, mb: 0.5 }}
> >
<ListItemText primary={category.name} /> <ListItemText primary={category.name} />
@@ -151,7 +148,7 @@ export default function CategorySidebar({
onChangeCommitted={onPriceRangeCommitted} onChangeCommitted={onPriceRangeCommitted}
valueLabelDisplay="auto" valueLabelDisplay="auto"
min={0} min={0}
max={1000} max={100}
step={5} step={5}
valueLabelFormat={(value) => `$${value}`} valueLabelFormat={(value) => `$${value}`}
/> />

View File

@@ -44,13 +44,21 @@ export default function MobileFilterDrawer({
ModalProps={{ keepMounted: true }} ModalProps={{ keepMounted: true }}
PaperProps={{ PaperProps={{
sx: { sx: {
width: 280, width: '100vw',
display: 'flex', display: 'flex',
flexDirection: 'column' flexDirection: 'column',
overflow: 'hidden'
} }
}} }}
> >
<Box sx={{ display: 'flex', alignItems: 'center', p: 2, borderBottom: 1, borderColor: 'divider', flexShrink: 0 }}> <Box sx={{
display: 'flex',
alignItems: 'center',
p: 2,
borderBottom: 1,
borderColor: 'divider',
flexShrink: 0
}}>
<Typography variant="h6" sx={{ flexGrow: 1, fontWeight: 'bold' }}> <Typography variant="h6" sx={{ flexGrow: 1, fontWeight: 'bold' }}>
Filters Filters
</Typography> </Typography>
@@ -63,16 +71,22 @@ export default function MobileFilterDrawer({
<CircularProgress size={40} /> <CircularProgress size={40} />
</Box> </Box>
) : ( ) : (
<CategorySidebar <Box sx={{
categories={categories} flex: 1,
filters={filters} overflow: 'auto',
expandedCategories={expandedCategories} px: 0
priceRange={priceRange} }}>
onFilterChange={onFilterChange} <CategorySidebar
onToggleCategoryExpansion={onToggleCategoryExpansion} categories={categories}
onPriceRangeChange={onPriceRangeChange} filters={filters}
onPriceRangeCommitted={onPriceRangeCommitted} expandedCategories={expandedCategories}
/> priceRange={priceRange}
onFilterChange={onFilterChange}
onToggleCategoryExpansion={onToggleCategoryExpansion}
onPriceRangeChange={onPriceRangeChange}
onPriceRangeCommitted={onPriceRangeCommitted}
/>
</Box>
)} )}
</Drawer> </Drawer>
); );

View File

@@ -27,10 +27,6 @@ export default function ProductCard({ product }: ProductCardProps) {
router.push(`/products/${product.id}`); router.push(`/products/${product.id}`);
}; };
const handleLoginRedirect = () => {
router.push('/auth/login');
};
const handleBuild = () => { const handleBuild = () => {
router.push(`/builder/${product.id}`); router.push(`/builder/${product.id}`);
}; };
@@ -147,69 +143,33 @@ export default function ProductCard({ product }: ProductCardProps) {
gap: { xs: 0.5, sm: 0.75 }, gap: { xs: 0.5, sm: 0.75 },
flexDirection: { xs: 'column', sm: user ? 'row' : 'column' } flexDirection: { xs: 'column', sm: user ? 'row' : 'column' }
}}> }}>
{user ? ( <Button
<> variant={user ? "outlined" : "contained"}
<Button size="small"
variant="outlined" onClick={handleViewProduct}
size="small" sx={{
onClick={handleViewProduct} fontSize: { xs: '0.65rem', sm: '0.7rem' },
sx={{ py: { xs: 0.25, sm: 0.5 },
fontSize: { xs: '0.65rem', sm: '0.7rem' }, flex: 1,
py: { xs: 0.25, sm: 0.5 }, minHeight: { xs: 24, sm: 32 }
flex: 1, }}
minHeight: { xs: 24, sm: 32 } >
}} {user ? "View" : "View Product"}
> </Button>
View {user && (
</Button> <Button
<Button variant="contained"
variant="contained" size="small"
size="small" onClick={handleBuild}
onClick={handleBuild} sx={{
sx={{ fontSize: { xs: '0.65rem', sm: '0.7rem' },
fontSize: { xs: '0.65rem', sm: '0.7rem' }, py: { xs: 0.25, sm: 0.5 },
py: { xs: 0.25, sm: 0.5 }, flex: 1,
flex: 1, minHeight: { xs: 24, sm: 32 }
minHeight: { xs: 24, sm: 32 } }}
}} >
> Build
Build </Button>
</Button>
</>
) : (
<>
<Button
variant="contained"
size="small"
onClick={handleViewProduct}
sx={{
fontSize: { xs: '0.65rem', sm: '0.7rem' },
py: { xs: 0.25, sm: 0.5 },
minHeight: { xs: 24, sm: 32 }
}}
>
View Product
</Button>
<Typography
component="button"
onClick={handleLoginRedirect}
sx={{
fontSize: { xs: '0.6rem', sm: '0.65rem' },
color: 'primary.main',
textDecoration: 'underline',
cursor: 'pointer',
border: 'none',
background: 'none',
p: 0,
textAlign: 'center',
'&:hover': {
color: 'primary.dark'
}
}}
>
Login to customize
</Typography>
</>
)} )}
</Box> </Box>
)} )}

View File

@@ -12,7 +12,8 @@ import {
useMediaQuery, useMediaQuery,
useTheme, useTheme,
SelectChangeEvent, SelectChangeEvent,
Box Box,
Button
} from '@mui/material'; } from '@mui/material';
import { import {
Search, Search,
@@ -52,28 +53,144 @@ export default function SearchFilters({
onFilterChange('sortDirection', sortDirection); onFilterChange('sortDirection', sortDirection);
}; };
if (isMobile) {
return (
<Paper elevation={1} sx={{
p: { xs: 1, sm: 1.25 },
mb: { xs: 1, sm: 1.5 }
}}>
<Box sx={{ mb: 1 }}>
<TextField
fullWidth
placeholder="Search products..."
value={searchInput}
onChange={(e: ChangeEvent<HTMLInputElement>) => setSearchInput(e.target.value)}
onKeyPress={(e: KeyboardEvent) => e.key === 'Enter' && handleSearch()}
size="small"
sx={{
'& .MuiInputBase-input': {
fontSize: '0.85rem',
py: 1.25
}
}}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<Search />
</InputAdornment>
),
endAdornment: searchInput && (
<InputAdornment position="end">
<IconButton
size="small"
onClick={handleSearch}
edge="end"
>
<Search />
</IconButton>
</InputAdornment>
)
}}
/>
</Box>
<Box sx={{
display: 'flex',
alignItems: 'center',
gap: 1,
mb: 1
}}>
<Button
variant="outlined"
onClick={onOpenMobileDrawer}
startIcon={<TuneIcon />}
size="small"
sx={{
fontSize: '0.8rem',
px: 1.5,
py: 0.75,
flexShrink: 0,
minWidth: 'fit-content'
}}
>
Filters
</Button>
<FormControl size="small" sx={{ flex: 1 }}>
<InputLabel sx={{ fontSize: '0.8rem' }}>
Sort
</InputLabel>
<Select
value={`${filters.sortBy}-${filters.sortDirection}`}
label="Sort"
onChange={(e: SelectChangeEvent) => handleSortChange(e.target.value)}
sx={{
'& .MuiSelect-select': {
fontSize: '0.8rem',
py: 0.75
}
}}
>
{SORT_OPTIONS.map((option) => (
<MenuItem
key={option.value}
value={option.value}
sx={{ fontSize: '0.8rem' }}
>
{option.label.split(' ')[0]}
</MenuItem>
))}
</Select>
</FormControl>
<FormControl size="small" sx={{ flex: 1 }}>
<InputLabel sx={{ fontSize: '0.8rem' }}>
Per
</InputLabel>
<Select
value={filters.pageSize}
label="Per"
onChange={(e: SelectChangeEvent<number>) =>
onFilterChange('pageSize', e.target.value as number)
}
sx={{
'& .MuiSelect-select': {
fontSize: '0.8rem',
py: 0.75
}
}}
>
{PAGE_SIZE_OPTIONS.map((size) => (
<MenuItem
key={size}
value={size}
sx={{ fontSize: '0.8rem' }}
>
{size}
</MenuItem>
))}
</Select>
</FormControl>
</Box>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="body2" color="text.secondary" sx={{
fontSize: '0.75rem'
}}>
{productsCount} of {totalCount} products
</Typography>
</Box>
</Paper>
);
}
return ( return (
<Paper elevation={1} sx={{ <Paper elevation={1} sx={{
p: { xs: 0.75, sm: 1, md: 1.25 }, p: { xs: 0.75, sm: 1, md: 1.25 },
mb: { xs: 1, sm: 1.5 } mb: { xs: 1, sm: 1.5 }
}}> }}>
<Grid container spacing={{ xs: 0.75, sm: 1, md: 1.5 }} alignItems="center"> <Grid container spacing={{ xs: 0.75, sm: 1, md: 1.5 }} alignItems="center">
{isMobile && ( <Grid size={{ xs: 12, sm: 6, md: 5 }}>
<Grid>
<IconButton
onClick={onOpenMobileDrawer}
size="small"
sx={{
mr: { xs: 0.5, sm: 1 },
p: { xs: 0.5, sm: 0.75 }
}}
>
<TuneIcon fontSize="small" />
</IconButton>
</Grid>
)}
<Grid size={{ xs: isMobile ? 8 : 12, sm: 6, md: 5 }}>
<TextField <TextField
fullWidth fullWidth
placeholder="Search products..." placeholder="Search products..."

View File

@@ -184,17 +184,17 @@ export default function GalleryPage() {
minHeight: 0, minHeight: 0,
pr: { xs: 0, md: 0.5 } pr: { xs: 0, md: 0.5 }
}}> }}>
<Box sx={{ mb: { xs: 1, sm: 1.5, md: 2 } }}> <Box sx={{ my: { xs: 1.5, sm: 2, md: 3 } }}>
<Typography variant="h3" gutterBottom sx={{ <Typography variant="h3" gutterBottom sx={{
fontWeight: 700, fontWeight: 700,
fontSize: { xs: '1.5rem', sm: '1.75rem', md: '2rem' }, fontSize: { xs: '1.75rem', sm: '2rem', md: '2.5rem' },
lineHeight: 1.2, lineHeight: 1.2,
mb: { xs: 0.5, sm: 0.75 } mb: { xs: 0.5, sm: 0.75 }
}}> }}>
Product Gallery Product Gallery
</Typography> </Typography>
<Typography variant="h6" color="text.secondary" sx={{ <Typography variant="h6" color="text.secondary" sx={{
fontSize: { xs: '0.8rem', sm: '0.9rem' }, fontSize: { xs: '0.9rem', sm: '1rem', md: '1.1rem' },
lineHeight: 1.3 lineHeight: 1.3
}}> }}>
Explore our complete collection of customizable products Explore our complete collection of customizable products

View File

@@ -17,6 +17,27 @@ import {useState, useEffect, JSX} from 'react';
import { ShoppingCart, Palette, ImageOutlined, CreditCard, LocalShipping, CheckCircle } from '@mui/icons-material'; import { ShoppingCart, Palette, ImageOutlined, CreditCard, LocalShipping, CheckCircle } from '@mui/icons-material';
import clientApi from "@/lib/clientApi"; import clientApi from "@/lib/clientApi";
interface Product {
id: string;
name: string;
description: string;
imageUrl?: string;
basePrice: number;
isCustomizable: boolean;
}
interface ApiResponse {
items: Product[];
}
interface Step {
number: number;
label: string;
description: string;
icon: JSX.Element;
details: string;
}
export default function HomePage(): JSX.Element { export default function HomePage(): JSX.Element {
const [products, setProducts] = useState<Product[]>([]); const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState<boolean>(true); const [loading, setLoading] = useState<boolean>(true);