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[] = [
{ label: 'Home', href: '/', icon: <Home />, 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 },
];
const adminLinks: NavLink[] = [
{ label: 'Dashboard', href: '/dashboard', icon: <Dashboard />, show: isMerchant },
{ label: 'Admin', href: '/admin', icon: <AdminPanelSettings />, show: isAdmin },
{ label: 'Swagger', href: '/swagger', icon: <Api />, 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 }} />
<Typography variant="body2" noWrap>{user.name}</Typography>
</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' }}>
<Typography variant="body2">Logout</Typography>
</MenuItem>
@@ -184,12 +175,6 @@ export default function ImprinkAppBar() {
<MenuItem component="a" href="/auth/login" onClick={handleMenuClose} sx={{ color: 'primary.main', fontWeight: 'bold' }}>
<Typography variant="body2">Sign Up</Typography>
</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>
</AppBar>
{renderAdminBar()}

View File

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

View File

@@ -44,13 +44,21 @@ export default function MobileFilterDrawer({
ModalProps={{ keepMounted: true }}
PaperProps={{
sx: {
width: 280,
width: '100vw',
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' }}>
Filters
</Typography>
@@ -63,6 +71,11 @@ export default function MobileFilterDrawer({
<CircularProgress size={40} />
</Box>
) : (
<Box sx={{
flex: 1,
overflow: 'auto',
px: 0
}}>
<CategorySidebar
categories={categories}
filters={filters}
@@ -73,6 +86,7 @@ export default function MobileFilterDrawer({
onPriceRangeChange={onPriceRangeChange}
onPriceRangeCommitted={onPriceRangeCommitted}
/>
</Box>
)}
</Drawer>
);

View File

@@ -27,10 +27,6 @@ export default function ProductCard({ product }: ProductCardProps) {
router.push(`/products/${product.id}`);
};
const handleLoginRedirect = () => {
router.push('/auth/login');
};
const handleBuild = () => {
router.push(`/builder/${product.id}`);
};
@@ -147,10 +143,8 @@ export default function ProductCard({ product }: ProductCardProps) {
gap: { xs: 0.5, sm: 0.75 },
flexDirection: { xs: 'column', sm: user ? 'row' : 'column' }
}}>
{user ? (
<>
<Button
variant="outlined"
variant={user ? "outlined" : "contained"}
size="small"
onClick={handleViewProduct}
sx={{
@@ -160,8 +154,9 @@ export default function ProductCard({ product }: ProductCardProps) {
minHeight: { xs: 24, sm: 32 }
}}
>
View
{user ? "View" : "View Product"}
</Button>
{user && (
<Button
variant="contained"
size="small"
@@ -175,41 +170,6 @@ export default function ProductCard({ product }: ProductCardProps) {
>
Build
</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>
)}

View File

@@ -12,7 +12,8 @@ import {
useMediaQuery,
useTheme,
SelectChangeEvent,
Box
Box,
Button
} from '@mui/material';
import {
Search,
@@ -52,28 +53,144 @@ export default function SearchFilters({
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 (
<Paper elevation={1} sx={{
p: { xs: 0.75, sm: 1, md: 1.25 },
mb: { xs: 1, sm: 1.5 }
}}>
<Grid container spacing={{ xs: 0.75, sm: 1, md: 1.5 }} alignItems="center">
{isMobile && (
<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 }}>
<Grid size={{ xs: 12, sm: 6, md: 5 }}>
<TextField
fullWidth
placeholder="Search products..."

View File

@@ -184,17 +184,17 @@ export default function GalleryPage() {
minHeight: 0,
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={{
fontWeight: 700,
fontSize: { xs: '1.5rem', sm: '1.75rem', md: '2rem' },
fontSize: { xs: '1.75rem', sm: '2rem', md: '2.5rem' },
lineHeight: 1.2,
mb: { xs: 0.5, sm: 0.75 }
}}>
Product Gallery
</Typography>
<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
}}>
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 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 {
const [products, setProducts] = useState<Product[]>([]);
const [loading, setLoading] = useState<boolean>(true);