Mobile friendly gallery
This commit is contained in:
@@ -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()}
|
||||||
|
|||||||
@@ -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}`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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..."
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user