Compare commits
1 Commits
main
...
feature/cs
| Author | SHA1 | Date |
|---|---|---|
|
|
43b60b5a78 |
|
|
@ -0,0 +1,132 @@
|
||||||
|
const API_BASE_URL = "http://localhost:3000";
|
||||||
|
|
||||||
|
interface ApiResponse {
|
||||||
|
success: boolean;
|
||||||
|
data?: any;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const postCategory = async (categoryData: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
parent_category_id?: number;
|
||||||
|
}): Promise<ApiResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/categories`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(categoryData),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to post category");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, data: await response.json() };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const postBrand = async (brandData: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
website: string;
|
||||||
|
}): Promise<ApiResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/brands`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(brandData),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to post brand");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, data: await response.json() };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const postProduct = async (productData: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
category_id: number;
|
||||||
|
brand_id: number;
|
||||||
|
is_active: boolean;
|
||||||
|
}): Promise<ApiResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/products`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(productData),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to post product");
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: true, data: await response.json() };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCategories = async (): Promise<ApiResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/categories`);
|
||||||
|
if (!response.ok) throw new Error("Failed to fetch categories");
|
||||||
|
return { success: true, data: await response.json() };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getBrands = async (): Promise<ApiResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/brands`);
|
||||||
|
if (!response.ok) throw new Error("Failed to fetch brands");
|
||||||
|
return { success: true, data: await response.json() };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProducts = async (): Promise<ApiResponse> => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${API_BASE_URL}/products`);
|
||||||
|
if (!response.ok) throw new Error("Failed to fetch products");
|
||||||
|
return { success: true, data: await response.json() };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { AppBar, Toolbar, Typography, IconButton, Avatar } from "@mui/material";
|
||||||
|
import MenuIcon from "@mui/icons-material/Menu";
|
||||||
|
|
||||||
|
const AdminAppBar: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<AppBar
|
||||||
|
position="fixed"
|
||||||
|
sx={{ zIndex: (theme) => theme.zIndex.drawer + 1 }}
|
||||||
|
>
|
||||||
|
<Toolbar>
|
||||||
|
<IconButton color="inherit" edge="start" sx={{ mr: 2 }}>
|
||||||
|
<MenuIcon />
|
||||||
|
</IconButton>
|
||||||
|
<Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
|
||||||
|
Furniture Admin
|
||||||
|
</Typography>
|
||||||
|
<IconButton color="inherit">
|
||||||
|
<Avatar sx={{ width: 32, height: 32 }}>A</Avatar>
|
||||||
|
</IconButton>
|
||||||
|
</Toolbar>
|
||||||
|
</AppBar>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminAppBar;
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
// AdminLayout.tsx
|
||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
import { Box, CssBaseline, styled } from "@mui/material";
|
||||||
|
import AdminAppBar from "./adminappbar";
|
||||||
|
import AdminSidebar from "./adminsidebar";
|
||||||
|
|
||||||
|
const MainContent = styled(Box)(({ theme }) => ({
|
||||||
|
flexGrow: 1,
|
||||||
|
padding: theme.spacing(3),
|
||||||
|
backgroundColor: "#f5f7fa",
|
||||||
|
minHeight: "100vh",
|
||||||
|
}));
|
||||||
|
|
||||||
|
const AdminLayout = () => {
|
||||||
|
return (
|
||||||
|
<Box sx={{ display: "flex", bgcolor: "background.default" }}>
|
||||||
|
<CssBaseline />
|
||||||
|
<AdminAppBar />
|
||||||
|
<AdminSidebar />
|
||||||
|
<MainContent>
|
||||||
|
<Outlet />
|
||||||
|
</MainContent>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminLayout;
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import {
|
||||||
|
Drawer,
|
||||||
|
List,
|
||||||
|
ListItem,
|
||||||
|
ListItemButton,
|
||||||
|
ListItemIcon,
|
||||||
|
ListItemText,
|
||||||
|
Divider,
|
||||||
|
Toolbar,
|
||||||
|
Box,
|
||||||
|
} from "@mui/material";
|
||||||
|
import {
|
||||||
|
Dashboard as DashboardIcon,
|
||||||
|
Category as CategoryIcon,
|
||||||
|
BrandingWatermark as BrandIcon,
|
||||||
|
Inventory as ProductIcon,
|
||||||
|
} from "@mui/icons-material";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
const drawerWidth = 240;
|
||||||
|
|
||||||
|
const AdminSidebar: React.FC = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Drawer
|
||||||
|
variant="permanent"
|
||||||
|
sx={{
|
||||||
|
width: drawerWidth,
|
||||||
|
flexShrink: 0,
|
||||||
|
[`& .MuiDrawer-paper`]: { width: drawerWidth, boxSizing: "border-box" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Toolbar />
|
||||||
|
<Box sx={{ overflow: "auto" }}>
|
||||||
|
<List>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton onClick={() => navigate("/admin")}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<DashboardIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Dashboard" />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
<Divider />
|
||||||
|
<List>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton onClick={() => navigate("/admin/categoriesform")}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<CategoryIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Categories" />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton onClick={() => navigate("/admin/brands")}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<BrandIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Brands" />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
<ListItem disablePadding>
|
||||||
|
<ListItemButton onClick={() => navigate("/admin/products")}>
|
||||||
|
<ListItemIcon>
|
||||||
|
<ProductIcon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText primary="Products" />
|
||||||
|
</ListItemButton>
|
||||||
|
</ListItem>
|
||||||
|
</List>
|
||||||
|
</Box>
|
||||||
|
</Drawer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminSidebar;
|
||||||
|
|
@ -0,0 +1,191 @@
|
||||||
|
// BrandPage.tsx
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
Button,
|
||||||
|
Paper,
|
||||||
|
Alert,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardMedia,
|
||||||
|
Link,
|
||||||
|
Chip,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Add, Public } from "@mui/icons-material";
|
||||||
|
import { postBrand, getBrands } from "./adminAPIservices";
|
||||||
|
|
||||||
|
const BrandPage = () => {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
website: "",
|
||||||
|
});
|
||||||
|
const [brands, setBrands] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchBrands();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchBrands = async () => {
|
||||||
|
const result = await getBrands();
|
||||||
|
if (result.success) setBrands(result.data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
const result = await postBrand(formData);
|
||||||
|
if (result.success) {
|
||||||
|
setFormData({ name: "", description: "", website: "" });
|
||||||
|
fetchBrands();
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ mb: 3, color: "primary.main" }}
|
||||||
|
>
|
||||||
|
Brand Management
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
<Grid item xs={12} md={4}>
|
||||||
|
<Paper elevation={3} sx={{ p: 3, borderRadius: 2 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Add New Brand
|
||||||
|
</Typography>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Brand Name"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, name: e.target.value })
|
||||||
|
}
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Description"
|
||||||
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, description: e.target.value })
|
||||||
|
}
|
||||||
|
margin="normal"
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Website"
|
||||||
|
name="website"
|
||||||
|
value={formData.website}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, website: e.target.value })
|
||||||
|
}
|
||||||
|
margin="normal"
|
||||||
|
type="url"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<Add />}
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? "Creating..." : "Create Brand"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={12} md={8}>
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ mb: 2 }}>
|
||||||
|
Brand Directory ({brands.length})
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
{brands.map((brand) => (
|
||||||
|
<Grid item xs={12} sm={6} md={4} key={brand.id}>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
boxShadow: 3,
|
||||||
|
"&:hover": { boxShadow: 6 },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardMedia
|
||||||
|
component="div"
|
||||||
|
sx={{
|
||||||
|
pt: "56.25%", // 16:9 ratio
|
||||||
|
background: `linear-gradient(45deg, ${stringToColor(
|
||||||
|
brand.name
|
||||||
|
)} 30%, ${stringToHoverColor(brand.name)} 90%)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CardContent>
|
||||||
|
<Typography gutterBottom variant="h6" component="div">
|
||||||
|
{brand.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ mb: 1 }}
|
||||||
|
>
|
||||||
|
{brand.description || "No description"}
|
||||||
|
</Typography>
|
||||||
|
{brand.website && (
|
||||||
|
<Link href={brand.website} target="_blank" rel="noopener">
|
||||||
|
<Chip
|
||||||
|
icon={<Public fontSize="small" />}
|
||||||
|
label="Website"
|
||||||
|
clickable
|
||||||
|
color="primary"
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper functions for card colors
|
||||||
|
function stringToColor(string: string) {
|
||||||
|
let hash = 0;
|
||||||
|
for (let i = 0; i < string.length; i++) {
|
||||||
|
hash = string.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
let color = "#";
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
const value = (hash >> (i * 8)) & 0xff;
|
||||||
|
color += `00${value.toString(16)}`.slice(-2);
|
||||||
|
}
|
||||||
|
return color;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringToHoverColor(string: string) {
|
||||||
|
return `${stringToColor(string)}80`; // Add opacity
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BrandPage;
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
// CategoryPage.tsx
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
Button,
|
||||||
|
Paper,
|
||||||
|
Alert,
|
||||||
|
Grid,
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardActions,
|
||||||
|
Chip,
|
||||||
|
IconButton,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { Add, Edit, Delete } from "@mui/icons-material";
|
||||||
|
import { postCategory, getCategories } from "./adminAPIservices";
|
||||||
|
|
||||||
|
const CategoryForm = () => {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
parent_category_id: 0,
|
||||||
|
});
|
||||||
|
const [categories, setCategories] = useState<any[]>([]);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchCategories();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchCategories = async () => {
|
||||||
|
const result = await getCategories();
|
||||||
|
if (result.success) {
|
||||||
|
setCategories(result.data);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
const result = await postCategory(formData);
|
||||||
|
if (result.success) {
|
||||||
|
setSuccess(true);
|
||||||
|
setFormData({ name: "", description: "", parent_category_id: 0 });
|
||||||
|
fetchCategories();
|
||||||
|
} else {
|
||||||
|
setError(result.error || "Failed to create category");
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box sx={{ p: 3 }}>
|
||||||
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ mb: 3, color: "primary.main" }}
|
||||||
|
>
|
||||||
|
Category Management
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid container spacing={3}>
|
||||||
|
{/* Form Section */}
|
||||||
|
<Grid item xs={12} md={4}>
|
||||||
|
<Paper elevation={3} sx={{ p: 3, borderRadius: 2 }}>
|
||||||
|
<Typography variant="h6" gutterBottom>
|
||||||
|
Add New Category
|
||||||
|
</Typography>
|
||||||
|
{error && (
|
||||||
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{success && (
|
||||||
|
<Alert severity="success" sx={{ mb: 2 }}>
|
||||||
|
Category created!
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Name"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, name: e.target.value })
|
||||||
|
}
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Description"
|
||||||
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={(e) =>
|
||||||
|
setFormData({ ...formData, description: e.target.value })
|
||||||
|
}
|
||||||
|
margin="normal"
|
||||||
|
multiline
|
||||||
|
rows={3}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
startIcon={<Add />}
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
{loading ? "Creating..." : "Create Category"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Paper>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Categories List */}
|
||||||
|
<Grid item xs={12} md={8}>
|
||||||
|
<Typography variant="h6" gutterBottom sx={{ mb: 2 }}>
|
||||||
|
Existing Categories ({categories.length})
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
{categories.map((category) => (
|
||||||
|
<Grid item xs={12} sm={6} md={4} key={category.id}>
|
||||||
|
<Card
|
||||||
|
sx={{
|
||||||
|
height: "100%",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
transition: "transform 0.2s",
|
||||||
|
"&:hover": { transform: "scale(1.02)" },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent sx={{ flexGrow: 1 }}>
|
||||||
|
<Typography gutterBottom variant="h6" component="div">
|
||||||
|
{category.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="text.secondary"
|
||||||
|
sx={{ mb: 1 }}
|
||||||
|
>
|
||||||
|
{category.description || "No description"}
|
||||||
|
</Typography>
|
||||||
|
{category.parent_category_id > 0 && (
|
||||||
|
<Chip
|
||||||
|
label={`Parent ID: ${category.parent_category_id}`}
|
||||||
|
size="small"
|
||||||
|
sx={{ mr: 1 }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
<CardActions sx={{ justifyContent: "flex-end" }}>
|
||||||
|
<IconButton size="small" color="primary">
|
||||||
|
<Edit fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
<IconButton size="small" color="error">
|
||||||
|
<Delete fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CategoryForm;
|
||||||
|
|
@ -0,0 +1,203 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Typography,
|
||||||
|
TextField,
|
||||||
|
Button,
|
||||||
|
Paper,
|
||||||
|
Alert,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
Select,
|
||||||
|
MenuItem,
|
||||||
|
Checkbox,
|
||||||
|
FormControlLabel,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { SelectChangeEvent } from "@mui/material/Select";
|
||||||
|
import { postProduct } from "./adminAPIservices";
|
||||||
|
import Layout from "./adminlayout";
|
||||||
|
|
||||||
|
const mockCategories = [
|
||||||
|
{ id: 1, name: "Sofas" },
|
||||||
|
{ id: 2, name: "Chairs" },
|
||||||
|
{ id: 3, name: "Tables" },
|
||||||
|
{ id: 4, name: "Beds" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockBrands = [
|
||||||
|
{ id: 1, name: "IKEA" },
|
||||||
|
{ id: 2, name: "Ashley" },
|
||||||
|
{ id: 3, name: "Herman Miller" },
|
||||||
|
{ id: 4, name: "West Elm" },
|
||||||
|
];
|
||||||
|
|
||||||
|
interface ProductFormData {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
category_id: number;
|
||||||
|
brand_id: number;
|
||||||
|
is_active: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProductForm: React.FC = () => {
|
||||||
|
const [formData, setFormData] = useState<ProductFormData>({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
category_id: 1,
|
||||||
|
brand_id: 1,
|
||||||
|
is_active: true,
|
||||||
|
});
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
|
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData((prev) => ({ ...prev, [name]: value }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectChange = (e: SelectChangeEvent<number>) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setFormData((prev) => ({ ...prev, [name]: value as number }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const { name, checked } = e.target;
|
||||||
|
setFormData((prev) => ({ ...prev, [name]: checked }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
setError("");
|
||||||
|
setSuccess(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await postProduct(formData);
|
||||||
|
if (result.success) {
|
||||||
|
setSuccess(true);
|
||||||
|
setFormData({
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
category_id: 1,
|
||||||
|
brand_id: 1,
|
||||||
|
is_active: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setError(result.error || "Failed to create product");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError("An unexpected error occurred");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Box sx={{ mb: 4 }}>
|
||||||
|
<Typography variant="h4" gutterBottom>
|
||||||
|
Add New Product
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
Manage furniture products for your e-commerce store
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Paper sx={{ p: 3, mb: 3 }}>
|
||||||
|
{error && (
|
||||||
|
<Alert severity="error" sx={{ mb: 3 }}>
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{success && (
|
||||||
|
<Alert severity="success" sx={{ mb: 3 }}>
|
||||||
|
Product created successfully!
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Product Name"
|
||||||
|
name="name"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Description"
|
||||||
|
name="description"
|
||||||
|
value={formData.description}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
margin="normal"
|
||||||
|
multiline
|
||||||
|
rows={4}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormControl fullWidth margin="normal">
|
||||||
|
<InputLabel>Category</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="category_id"
|
||||||
|
value={formData.category_id}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
label="Category"
|
||||||
|
>
|
||||||
|
{mockCategories.map((category) => (
|
||||||
|
<MenuItem key={category.id} value={category.id}>
|
||||||
|
{category.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth margin="normal">
|
||||||
|
<InputLabel>Brand</InputLabel>
|
||||||
|
<Select
|
||||||
|
name="brand_id"
|
||||||
|
value={formData.brand_id}
|
||||||
|
onChange={handleSelectChange}
|
||||||
|
label="Brand"
|
||||||
|
>
|
||||||
|
{mockBrands.map((brand) => (
|
||||||
|
<MenuItem key={brand.id} value={brand.id}>
|
||||||
|
{brand.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
name="is_active"
|
||||||
|
checked={formData.is_active}
|
||||||
|
onChange={handleCheckboxChange}
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="Active Product"
|
||||||
|
sx={{ mt: 2 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3 }}>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
variant="contained"
|
||||||
|
color="primary"
|
||||||
|
disabled={loading}
|
||||||
|
size="large"
|
||||||
|
>
|
||||||
|
{loading ? "Creating..." : "Create Product"}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</Paper>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductForm;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect } from "react";
|
import React from "react";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Container,
|
Container,
|
||||||
|
|
@ -8,6 +8,8 @@ import {
|
||||||
Card,
|
Card,
|
||||||
Paper,
|
Paper,
|
||||||
Divider,
|
Divider,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { Category } from "./types";
|
import { Category } from "./types";
|
||||||
|
|
@ -15,6 +17,9 @@ import chairone from "../images/chairone.png";
|
||||||
import chairtwo from "../images/chairtwo.png";
|
import chairtwo from "../images/chairtwo.png";
|
||||||
import chairthree from "../images/chairthree.png";
|
import chairthree from "../images/chairthree.png";
|
||||||
import carimage from "../images/caraone.png";
|
import carimage from "../images/caraone.png";
|
||||||
|
import ContactForm from "../contactus/contactus";
|
||||||
|
import Footer from "../foorter";
|
||||||
|
import HeaderSection from "../header";
|
||||||
|
|
||||||
export const categories: Category[] = [
|
export const categories: Category[] = [
|
||||||
{
|
{
|
||||||
|
|
@ -70,32 +75,57 @@ export const categories: Category[] = [
|
||||||
|
|
||||||
const CategoryPage = () => {
|
const CategoryPage = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ backgroundColor: "#f5f5f5", minHeight: "100vh" }}>
|
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh" }}>
|
||||||
|
<HeaderSection />
|
||||||
|
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: { xs: "60vh", md: "80vh" },
|
height: { xs: "70vh", md: "85vh" },
|
||||||
backgroundImage: `url(${carimage})`,
|
backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url(${carimage})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
|
backgroundAttachment: isMobile ? "scroll" : "fixed",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
animation: "fadeIn 1.5s ease-in-out",
|
||||||
|
"@keyframes fadeIn": {
|
||||||
|
"0%": { opacity: 0 },
|
||||||
|
"100%": { opacity: 1 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 2,
|
||||||
|
maxWidth: "800px",
|
||||||
|
px: 3,
|
||||||
|
transform: "translateY(0)",
|
||||||
|
transition: "transform 0.5s ease",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "translateY(-10px)",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ position: "relative", zIndex: 2, maxWidth: "800px", px: 3 }}>
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="h1"
|
variant="h1"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: { xs: "2.5rem", md: "4rem" },
|
fontSize: { xs: "2.8rem", md: "4.5rem" },
|
||||||
mb: 3,
|
mb: 3,
|
||||||
textShadow: "2px 2px 4px rgba(0,0,0,0.5)",
|
textShadow: "2px 2px 8px rgba(0,0,0,0.7)",
|
||||||
|
lineHeight: 1.2,
|
||||||
|
letterSpacing: "0.03em",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Crafted for Comfort, Designed for Life
|
Crafted for Comfort, Designed for Life
|
||||||
|
|
@ -104,12 +134,35 @@ const CategoryPage = () => {
|
||||||
variant="h5"
|
variant="h5"
|
||||||
sx={{
|
sx={{
|
||||||
mb: 4,
|
mb: 4,
|
||||||
color: "#FFA500",
|
color: "#ed9506d6",
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
|
fontStyle: "italic",
|
||||||
|
letterSpacing: "0.05em",
|
||||||
|
textShadow: "1px 1px 3px rgba(0,0,0,0.5)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Discover furniture that transforms your space
|
Discover furniture that transforms your space
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
sx={{
|
||||||
|
background: "#ed9506d6",
|
||||||
|
px: 4,
|
||||||
|
py: 1.5,
|
||||||
|
borderRadius: "50px",
|
||||||
|
fontSize: "1.1rem",
|
||||||
|
fontWeight: 600,
|
||||||
|
boxShadow: "0 4px 15px rgba(255, 165, 0, 0.3)",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "translateY(-3px)",
|
||||||
|
boxShadow: "0 6px 20px rgba(255, 165, 0, 0.4)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Explore Collections
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
|
@ -118,16 +171,39 @@ const CategoryPage = () => {
|
||||||
elevation={0}
|
elevation={0}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: "#fff",
|
backgroundColor: "#fff",
|
||||||
py: 8,
|
py: { xs: 6, md: 10 },
|
||||||
borderBottom: "1px solid rgba(0,0,0,0.1)",
|
borderBottom: "1px solid rgba(0,0,0,0.05)",
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
"&::before": {
|
||||||
|
content: '""',
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: "4px",
|
||||||
|
background: "linear-gradient(90deg, #FFA500, #FF8C00, #FFA500)",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Container maxWidth="lg">
|
<Container maxWidth="lg">
|
||||||
<Typography
|
<Typography
|
||||||
variant="h3"
|
variant="h2"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
align="center"
|
align="center"
|
||||||
sx={{ mb: 4 }}
|
sx={{
|
||||||
|
mb: 6,
|
||||||
|
position: "relative",
|
||||||
|
"&::after": {
|
||||||
|
content: '""',
|
||||||
|
display: "block",
|
||||||
|
width: "80px",
|
||||||
|
height: "4px",
|
||||||
|
background: "orange",
|
||||||
|
margin: "20px auto 0",
|
||||||
|
borderRadius: "2px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Our Craftsmanship Philosophy
|
Our Craftsmanship Philosophy
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
@ -135,7 +211,12 @@ const CategoryPage = () => {
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{ mb: 3, fontSize: "1.1rem", lineHeight: 1.8 }}
|
sx={{
|
||||||
|
mb: 3,
|
||||||
|
fontSize: "1.15rem",
|
||||||
|
lineHeight: 1.8,
|
||||||
|
color: "#555",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
At Panto, we believe furniture should be both beautiful and
|
At Panto, we believe furniture should be both beautiful and
|
||||||
functional. Each piece in our collection is meticulously crafted
|
functional. Each piece in our collection is meticulously crafted
|
||||||
|
|
@ -144,7 +225,11 @@ const CategoryPage = () => {
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{ fontSize: "1.1rem", lineHeight: 1.8 }}
|
sx={{
|
||||||
|
fontSize: "1.15rem",
|
||||||
|
lineHeight: 1.8,
|
||||||
|
color: "#555",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Our designers work closely with ergonomic specialists to create
|
Our designers work closely with ergonomic specialists to create
|
||||||
pieces that don't just look good, but feel good to use every
|
pieces that don't just look good, but feel good to use every
|
||||||
|
|
@ -155,7 +240,12 @@ const CategoryPage = () => {
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{ mb: 3, fontSize: "1.1rem", lineHeight: 1.8 }}
|
sx={{
|
||||||
|
mb: 3,
|
||||||
|
fontSize: "1.15rem",
|
||||||
|
lineHeight: 1.8,
|
||||||
|
color: "#555",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
We source our materials from responsible suppliers, ensuring
|
We source our materials from responsible suppliers, ensuring
|
||||||
that our environmental impact is minimized. The hardwoods in our
|
that our environmental impact is minimized. The hardwoods in our
|
||||||
|
|
@ -164,7 +254,11 @@ const CategoryPage = () => {
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{ fontSize: "1.1rem", lineHeight: 1.8 }}
|
sx={{
|
||||||
|
fontSize: "1.15rem",
|
||||||
|
lineHeight: 1.8,
|
||||||
|
color: "#555",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
With over 15 years in the industry, we've perfected the balance
|
With over 15 years in the industry, we've perfected the balance
|
||||||
between form and function, offering designs that stand the test
|
between form and function, offering designs that stand the test
|
||||||
|
|
@ -176,38 +270,57 @@ const CategoryPage = () => {
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Category Grid */}
|
{/* Category Grid */}
|
||||||
<Container maxWidth="xl" sx={{ py: 10 }}>
|
<Container maxWidth="xl" sx={{ py: { xs: 6, md: 10 } }}>
|
||||||
|
<Box sx={{ textAlign: "center", mb: { xs: 4, md: 8 } }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h2"
|
variant="h2"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
align="center"
|
|
||||||
sx={{
|
sx={{
|
||||||
mb: 2,
|
mb: 2,
|
||||||
color: "#333",
|
color: "#333",
|
||||||
|
position: "relative",
|
||||||
|
display: "inline-block",
|
||||||
|
"&::after": {
|
||||||
|
content: '""',
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "-10px",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translateX(-50%)",
|
||||||
|
width: "100px",
|
||||||
|
height: "4px",
|
||||||
|
background: "orange",
|
||||||
|
borderRadius: "2px",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Explore Our Collections
|
Explore Our Collections
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h5"
|
variant="h5"
|
||||||
align="center"
|
|
||||||
sx={{
|
sx={{
|
||||||
mb: 6,
|
mt: 3,
|
||||||
color: "#666",
|
color: "#666",
|
||||||
|
fontStyle: "italic",
|
||||||
|
fontWeight: 400,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Each collection tells a unique design story
|
Each collection tells a unique design story
|
||||||
</Typography>
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Grid container spacing={6}>
|
<Grid container spacing={{ xs: 4, md: 6 }}>
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<Grid item xs={12} sm={6} md={4} key={category.id}>
|
<Grid item xs={12} sm={6} md={4} lg={3} key={category.id}>
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
height: "100%",
|
height: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
transition: "all 0.3s ease",
|
transition:
|
||||||
|
"all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)",
|
||||||
|
borderRadius: "12px",
|
||||||
|
overflow: "hidden",
|
||||||
|
boxShadow: "0 5px 15px rgba(0,0,0,0.08)",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
transform: "translateY(-10px)",
|
transform: "translateY(-10px)",
|
||||||
boxShadow: "0 15px 30px rgba(0,0,0,0.15)",
|
boxShadow: "0 15px 30px rgba(0,0,0,0.15)",
|
||||||
|
|
@ -216,29 +329,63 @@ const CategoryPage = () => {
|
||||||
>
|
>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: "300px",
|
height: "280px",
|
||||||
backgroundImage: `url(${category.image})`,
|
backgroundImage: `url(${category.image})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "contain",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
|
position: "relative",
|
||||||
|
"&::before": {
|
||||||
|
content: '""',
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background:
|
||||||
|
"linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.03) 100%)",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Box sx={{ p: 4, flexGrow: 1 }}>
|
<Box sx={{ p: 4, flexGrow: 1, backgroundColor: "#fff" }}>
|
||||||
<Typography variant="h4" fontWeight="bold" sx={{ mb: 2 }}>
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
fontWeight="bold"
|
||||||
|
sx={{
|
||||||
|
mb: 2,
|
||||||
|
color: "#333",
|
||||||
|
minHeight: "64px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{category.name}
|
{category.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ mb: 3, color: "#666" }}>
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{
|
||||||
|
mb: 3,
|
||||||
|
color: "#666",
|
||||||
|
minHeight: "60px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{category.description}
|
{category.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
color="secondary"
|
||||||
onClick={() => navigate(`/collections/${category.id}`)}
|
onClick={() => navigate(`/collections/${category.id}`)}
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: "#FFA500",
|
|
||||||
color: "#fff",
|
|
||||||
py: 1.5,
|
py: 1.5,
|
||||||
|
background: "orange",
|
||||||
|
borderRadius: "8px",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "1rem",
|
||||||
|
letterSpacing: "0.03em",
|
||||||
|
boxShadow: "none",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: "#E59400",
|
transform: "translateY(-2px)",
|
||||||
|
boxShadow: `0 4px 12px ${theme.palette.secondary.main}40`,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -254,35 +401,115 @@ const CategoryPage = () => {
|
||||||
{/* Testimonials */}
|
{/* Testimonials */}
|
||||||
<Paper
|
<Paper
|
||||||
elevation={0}
|
elevation={0}
|
||||||
sx={{ backgroundColor: "#333", color: "#fff", py: 10 }}
|
sx={{
|
||||||
|
backgroundColor: "#2c3e50",
|
||||||
|
color: "#fff",
|
||||||
|
py: { xs: 6, md: 10 },
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
"&::before": {
|
||||||
|
content: '""',
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: "4px",
|
||||||
|
background: "linear-gradient(90deg, #FFA500, #FF8C00, #FFA500)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Container maxWidth="lg">
|
<Container maxWidth="lg">
|
||||||
<Typography
|
<Typography
|
||||||
variant="h3"
|
variant="h2"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
align="center"
|
align="center"
|
||||||
sx={{ mb: 6 }}
|
sx={{
|
||||||
|
mb: { xs: 4, md: 8 },
|
||||||
|
position: "relative",
|
||||||
|
"&::after": {
|
||||||
|
content: '""',
|
||||||
|
display: "block",
|
||||||
|
width: "80px",
|
||||||
|
height: "4px",
|
||||||
|
background: "orange",
|
||||||
|
margin: "20px auto 0",
|
||||||
|
borderRadius: "2px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
What Our Customers Say
|
What Our Customers Say
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={4}>
|
<Grid container spacing={4}>
|
||||||
{[
|
{[
|
||||||
|
{
|
||||||
|
quote:
|
||||||
"The most comfortable chair I've ever owned - worth every penny!",
|
"The most comfortable chair I've ever owned - worth every penny!",
|
||||||
|
author: "Sarah J.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote:
|
||||||
"Transformed my living room completely. The quality is exceptional.",
|
"Transformed my living room completely. The quality is exceptional.",
|
||||||
|
author: "Michael T.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
quote:
|
||||||
"Best furniture purchase I've made in years. Stunning and sturdy.",
|
"Best furniture purchase I've made in years. Stunning and sturdy.",
|
||||||
].map((quote, i) => (
|
author: "Emily R.",
|
||||||
|
},
|
||||||
|
].map((testimonial, i) => (
|
||||||
<Grid item xs={12} md={4} key={i}>
|
<Grid item xs={12} md={4} key={i}>
|
||||||
<Box
|
<Box
|
||||||
sx={{ p: 4, backgroundColor: "#444", borderRadius: "8px" }}
|
sx={{
|
||||||
|
p: 4,
|
||||||
|
height: "100%",
|
||||||
|
|
||||||
|
backgroundColor: "rgba(255,255,255,0.1)",
|
||||||
|
borderRadius: "12px",
|
||||||
|
border: "1px solid rgba(255,255,255,0.1)",
|
||||||
|
backdropFilter: "blur(8px)",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "translateY(-5px)",
|
||||||
|
boxShadow: "0 10px 25px rgba(0,0,0,0.2)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
|
<Box sx={{ display: "flex", mb: 2 }}>
|
||||||
|
{[1, 2, 3, 4, 5].map((star) => (
|
||||||
|
<Box
|
||||||
|
key={star}
|
||||||
|
sx={{
|
||||||
|
color: "orange",
|
||||||
|
fontSize: "1.5rem",
|
||||||
|
lineHeight: 1,
|
||||||
|
mr: 0.5,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
★
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{ fontStyle: "italic", mb: 2 }}
|
sx={{
|
||||||
|
fontStyle: "italic",
|
||||||
|
mb: 2,
|
||||||
|
fontSize: "1.1rem",
|
||||||
|
lineHeight: 1.7,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
"{quote}"
|
"{testimonial.quote}"
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="#FFA500">
|
<Typography
|
||||||
— Happy Customer
|
variant="body2"
|
||||||
|
// color="secondary"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 600,
|
||||||
|
color: "orange",
|
||||||
|
fontSize: "1rem",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
— {testimonial.author}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
@ -290,6 +517,9 @@ const CategoryPage = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
|
<ContactForm />
|
||||||
|
<Footer />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,12 +8,18 @@ import {
|
||||||
Button,
|
Button,
|
||||||
Paper,
|
Paper,
|
||||||
Divider,
|
Divider,
|
||||||
|
useTheme,
|
||||||
|
useMediaQuery,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useParams, Link } from "react-router-dom";
|
import { useParams, Link } from "react-router-dom";
|
||||||
import { Product } from "./types";
|
import { Product } from "./types";
|
||||||
import chairone from "../images/chairone.png";
|
import chairone from "../images/chairone.png";
|
||||||
import chairtwo from "../images/chairtwo.png";
|
import chairtwo from "../images/chairtwo.png";
|
||||||
import chairthree from "../images/chairthree.png";
|
import chairthree from "../images/chairthree.png";
|
||||||
|
import carimg from "../images/caraone.png";
|
||||||
|
import Header from "../header";
|
||||||
|
import ContactForm from "../contactus/contactus";
|
||||||
|
import Footer from "../foorter";
|
||||||
|
|
||||||
const collectionItems: Record<string, Product[]> = {
|
const collectionItems: Record<string, Product[]> = {
|
||||||
chairs: [
|
chairs: [
|
||||||
|
|
@ -92,74 +98,191 @@ const collectionItems: Record<string, Product[]> = {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// Add more collections as needed
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const CollectionPage = () => {
|
const CollectionPage = () => {
|
||||||
const { collectionId } = useParams<{ collectionId: string }>();
|
const { collectionId } = useParams<{ collectionId: string }>();
|
||||||
const collection = collectionId ? collectionItems[collectionId] || [] : [];
|
const collection = collectionId ? collectionItems[collectionId] || [] : [];
|
||||||
|
const theme = useTheme();
|
||||||
|
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ backgroundColor: "#f5f5f5", minHeight: "100vh" }}>
|
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh" }}>
|
||||||
|
<Header />
|
||||||
{/* Collection Hero */}
|
{/* Collection Hero */}
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
height: "50vh",
|
height: { xs: "60vh", md: "90vh" },
|
||||||
backgroundImage:
|
backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url(${carimg})`,
|
||||||
"linear-gradient(rgba(0,0,0,0.2), rgba(0,0,0,0.2)), url(/images/collection-bg.jpg)",
|
|
||||||
backgroundSize: "cover",
|
backgroundSize: "cover",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
|
backgroundAttachment: isMobile ? "scroll" : "fixed",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
animation: "fadeIn 1s ease-in-out",
|
||||||
|
"@keyframes fadeIn": {
|
||||||
|
"0%": { opacity: 0 },
|
||||||
|
"100%": { opacity: 1 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: "relative",
|
||||||
|
zIndex: 2,
|
||||||
|
px: 3,
|
||||||
|
transform: "translateY(0)",
|
||||||
|
transition: "transform 0.5s ease",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "translateY(-10px)",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{ position: "relative", zIndex: 2 }}>
|
|
||||||
<Typography
|
<Typography
|
||||||
variant="h1"
|
variant="h1"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: { xs: "2.5rem", md: "4rem" },
|
fontSize: { xs: "2.8rem", md: "4rem" },
|
||||||
mb: 2,
|
mb: 2,
|
||||||
textTransform: "capitalize",
|
textTransform: "capitalize",
|
||||||
|
textShadow: "2px 2px 8px rgba(0,0,0,0.7)",
|
||||||
|
lineHeight: 1.2,
|
||||||
|
letterSpacing: "0.03em",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{collectionId} Collection
|
{collectionId} Collection
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h5" sx={{ color: "#FFA500" }}>
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{
|
||||||
|
color: "orange",
|
||||||
|
fontWeight: 500,
|
||||||
|
fontStyle: "italic",
|
||||||
|
letterSpacing: "0.05em",
|
||||||
|
textShadow: "1px 1px 3px rgba(0,0,0,0.5)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{collection.length} premium designs available
|
{collection.length} premium designs available
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
size="large"
|
||||||
|
sx={{
|
||||||
|
background: "orange",
|
||||||
|
mt: 4,
|
||||||
|
px: 4,
|
||||||
|
py: 1.5,
|
||||||
|
borderRadius: "50px",
|
||||||
|
fontSize: "1.1rem",
|
||||||
|
fontWeight: 600,
|
||||||
|
boxShadow: "0 4px 15px rgba(255, 165, 0, 0.3)",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
|
"&:hover": {
|
||||||
|
transform: "translateY(-3px)",
|
||||||
|
boxShadow: "0 6px 20px rgba(255, 165, 0, 0.4)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Shop Now
|
||||||
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* Collection Description */}
|
{/* Collection Description */}
|
||||||
<Paper elevation={0} sx={{ backgroundColor: "#fff", py: 8 }}>
|
<Paper
|
||||||
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
py: { xs: 6, md: 10 },
|
||||||
|
borderBottom: "1px solid rgba(0,0,0,0.05)",
|
||||||
|
position: "relative",
|
||||||
|
overflow: "hidden",
|
||||||
|
"&::before": {
|
||||||
|
content: '""',
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
height: "4px",
|
||||||
|
background: "linear-gradient(90deg, #FFA500, #FF8C00, #FFA500)",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Container maxWidth="lg">
|
<Container maxWidth="lg">
|
||||||
<Typography variant="h3" fontWeight="bold" sx={{ mb: 4 }}>
|
<Typography
|
||||||
|
variant="h2"
|
||||||
|
fontWeight="bold"
|
||||||
|
sx={{
|
||||||
|
mb: 6,
|
||||||
|
position: "relative",
|
||||||
|
"&::after": {
|
||||||
|
content: '""',
|
||||||
|
display: "block",
|
||||||
|
width: "80px",
|
||||||
|
height: "4px",
|
||||||
|
background: "orange",
|
||||||
|
margin: "20px 0 0",
|
||||||
|
borderRadius: "2px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
About This Collection
|
About This Collection
|
||||||
</Typography>
|
</Typography>
|
||||||
|
<Grid container spacing={6}>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{ mb: 3, fontSize: "1.1rem", lineHeight: 1.8 }}
|
sx={{
|
||||||
|
mb: 3,
|
||||||
|
fontSize: "1.15rem",
|
||||||
|
lineHeight: 1.8,
|
||||||
|
color: "#555",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Our {collectionId} collection represents the pinnacle of design and
|
Our {collectionId} collection represents the pinnacle of design
|
||||||
craftsmanship. Each piece is created with meticulous attention to
|
and craftsmanship. Each piece is created with meticulous
|
||||||
detail, using only the finest materials sourced from sustainable
|
attention to detail, using only the finest materials sourced
|
||||||
suppliers.
|
from sustainable suppliers.
|
||||||
</Typography>
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} md={6}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="body1"
|
variant="body1"
|
||||||
sx={{ fontSize: "1.1rem", lineHeight: 1.8 }}
|
sx={{
|
||||||
|
fontSize: "1.15rem",
|
||||||
|
lineHeight: 1.8,
|
||||||
|
color: "#555",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
From the initial sketches to the final quality inspection, every
|
From the initial sketches to the final quality inspection, every
|
||||||
step in our process is carefully monitored to ensure exceptional
|
step in our process is carefully monitored to ensure exceptional
|
||||||
quality and durability that will last for generations.
|
quality and durability that will last for generations.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider sx={{ my: 6 }} />
|
</Grid>
|
||||||
<Typography variant="h4" fontWeight="bold" sx={{ mb: 3 }}>
|
</Grid>
|
||||||
|
<Divider sx={{ my: 6, borderColor: "rgba(0,0,0,0.1)" }} />
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
fontWeight="bold"
|
||||||
|
sx={{
|
||||||
|
mb: 4,
|
||||||
|
position: "relative",
|
||||||
|
"&::after": {
|
||||||
|
content: '""',
|
||||||
|
display: "block",
|
||||||
|
width: "60px",
|
||||||
|
height: "4px",
|
||||||
|
background: "orange",
|
||||||
|
margin: "15px 0 0",
|
||||||
|
borderRadius: "2px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
Collection Highlights
|
Collection Highlights
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={4} sx={{ mb: 6 }}>
|
<Grid container spacing={4} sx={{ mb: 6 }}>
|
||||||
|
|
@ -173,13 +296,22 @@ const CollectionPage = () => {
|
||||||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
width: "8px",
|
width: "10px",
|
||||||
height: "8px",
|
height: "10px",
|
||||||
backgroundColor: "#FFA500",
|
backgroundColor: "orange",
|
||||||
mr: 2,
|
mr: 2,
|
||||||
|
borderRadius: "50%",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Typography>{item}</Typography>
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{
|
||||||
|
fontSize: "1.1rem",
|
||||||
|
color: "#555",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
|
|
@ -188,13 +320,26 @@ const CollectionPage = () => {
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|
||||||
{/* Products Grid */}
|
{/* Products Grid */}
|
||||||
<Container maxWidth="xl" sx={{ py: 10 }}>
|
<Container maxWidth="xl" sx={{ py: { xs: 6, md: 10 } }}>
|
||||||
|
<Box sx={{ textAlign: "left", mb: { xs: 4, md: 8 } }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h2"
|
variant="h2"
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
sx={{
|
sx={{
|
||||||
mb: 2,
|
mb: 2,
|
||||||
color: "#333",
|
color: "#333",
|
||||||
|
position: "relative",
|
||||||
|
display: "inline-block",
|
||||||
|
"&::after": {
|
||||||
|
content: '""',
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "-10px",
|
||||||
|
left: 0,
|
||||||
|
width: "100px",
|
||||||
|
height: "4px",
|
||||||
|
background: "orange",
|
||||||
|
borderRadius: "2px",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Featured {collectionId}
|
Featured {collectionId}
|
||||||
|
|
@ -202,14 +347,17 @@ const CollectionPage = () => {
|
||||||
<Typography
|
<Typography
|
||||||
variant="h5"
|
variant="h5"
|
||||||
sx={{
|
sx={{
|
||||||
mb: 6,
|
mt: 3,
|
||||||
color: "#666",
|
color: "#666",
|
||||||
|
fontStyle: "italic",
|
||||||
|
fontWeight: 400,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Browse our curated selection
|
Browse our curated selection
|
||||||
</Typography>
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Grid container spacing={6}>
|
<Grid container spacing={{ xs: 4, md: 6 }}>
|
||||||
{collection.map((product) => (
|
{collection.map((product) => (
|
||||||
<Grid item xs={12} sm={6} md={4} key={product.id}>
|
<Grid item xs={12} sm={6} md={4} key={product.id}>
|
||||||
<Card
|
<Card
|
||||||
|
|
@ -217,7 +365,12 @@ const CollectionPage = () => {
|
||||||
height: "100%",
|
height: "100%",
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
transition: "all 0.3s ease",
|
transition:
|
||||||
|
"all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275)",
|
||||||
|
borderRadius: "12px",
|
||||||
|
overflow: "hidden",
|
||||||
|
boxShadow: "0 5px 15px rgba(0,0,0,0.08)",
|
||||||
|
backgroundColor: "#fff",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
transform: "translateY(-10px)",
|
transform: "translateY(-10px)",
|
||||||
boxShadow: "0 15px 30px rgba(0,0,0,0.15)",
|
boxShadow: "0 15px 30px rgba(0,0,0,0.15)",
|
||||||
|
|
@ -228,15 +381,41 @@ const CollectionPage = () => {
|
||||||
sx={{
|
sx={{
|
||||||
height: "300px",
|
height: "300px",
|
||||||
backgroundImage: `url(${product.image})`,
|
backgroundImage: `url(${product.image})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "contain", // fits the whole image, may leave empty space
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
|
position: "relative",
|
||||||
|
"&::before": {
|
||||||
|
content: '""',
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
background:
|
||||||
|
"linear-gradient(to bottom, rgba(0,0,0,0) 0%, rgba(0,0,0,0.03) 100%)",
|
||||||
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Box sx={{ p: 4, flexGrow: 1 }}>
|
<Box sx={{ p: 4, flexGrow: 1 }}>
|
||||||
<Typography variant="h4" fontWeight="bold" sx={{ mb: 2 }}>
|
<Typography
|
||||||
|
variant="h4"
|
||||||
|
fontWeight="bold"
|
||||||
|
sx={{
|
||||||
|
mb: 2,
|
||||||
|
color: "#333",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{product.name}
|
{product.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ mb: 2, color: "#666" }}>
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{
|
||||||
|
mb: 3,
|
||||||
|
color: "#666",
|
||||||
|
minHeight: "60px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{product.description}
|
{product.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
|
|
@ -247,17 +426,32 @@ const CollectionPage = () => {
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
mb: 1,
|
mb: 1.5,
|
||||||
|
color: "#555",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
color: "orange",
|
||||||
|
mr: 1.5,
|
||||||
|
fontSize: "1.5rem",
|
||||||
|
lineHeight: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span style={{ color: "#FFA500", marginRight: "8px" }}>
|
|
||||||
•
|
•
|
||||||
</span>
|
</Box>
|
||||||
{detail}
|
{detail}
|
||||||
</Typography>
|
</Typography>
|
||||||
))}
|
))}
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="h5" color="#FFA500" sx={{ mb: 3 }}>
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
color="secondary"
|
||||||
|
sx={{
|
||||||
|
mb: 3,
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
{product.price}
|
{product.price}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -266,11 +460,17 @@ const CollectionPage = () => {
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="contained"
|
variant="contained"
|
||||||
sx={{
|
sx={{
|
||||||
backgroundColor: "#FFA500",
|
background: "orange",
|
||||||
color: "#fff",
|
|
||||||
py: 1.5,
|
py: 1.5,
|
||||||
|
borderRadius: "8px",
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "1rem",
|
||||||
|
letterSpacing: "0.03em",
|
||||||
|
boxShadow: "none",
|
||||||
|
transition: "all 0.3s ease",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
backgroundColor: "#E59400",
|
transform: "translateY(-2px)",
|
||||||
|
boxShadow: `0 4px 12px ${theme.palette.secondary.main}40`,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
@ -282,6 +482,8 @@ const CollectionPage = () => {
|
||||||
))}
|
))}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Container>
|
</Container>
|
||||||
|
<ContactForm />
|
||||||
|
<Footer />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ import StarIcon from "@mui/icons-material/Star";
|
||||||
import chairone from "../images/chairone.png";
|
import chairone from "../images/chairone.png";
|
||||||
import chairtwo from "../images/chairtwo.png";
|
import chairtwo from "../images/chairtwo.png";
|
||||||
import chairthree from "../images/chairthree.png";
|
import chairthree from "../images/chairthree.png";
|
||||||
|
import HeaderSection from "../header";
|
||||||
|
import ContactForm from "../contactus/contactus";
|
||||||
|
import Footer from "../foorter";
|
||||||
|
|
||||||
const products: Record<string, Record<string, Product>> = {
|
const products: Record<string, Record<string, Product>> = {
|
||||||
chairs: {
|
chairs: {
|
||||||
|
|
@ -109,14 +112,15 @@ const ProductPage = () => {
|
||||||
|
|
||||||
const productImages = [
|
const productImages = [
|
||||||
product.image,
|
product.image,
|
||||||
"/images/chair-alt1.jpg",
|
`${chairone}`,
|
||||||
"/images/chair-alt2.jpg",
|
`${chairtwo}`,
|
||||||
"/images/chair-detail.jpg",
|
`${chairthree}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh", py: 8 }}>
|
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh" }}>
|
||||||
<Container maxWidth="lg">
|
<HeaderSection />
|
||||||
|
<Container maxWidth="lg" sx={{ mt: 8 }}>
|
||||||
<Grid container spacing={6}>
|
<Grid container spacing={6}>
|
||||||
{/* Product Images */}
|
{/* Product Images */}
|
||||||
<Grid item xs={12} md={6}>
|
<Grid item xs={12} md={6}>
|
||||||
|
|
@ -134,10 +138,15 @@ const ProductPage = () => {
|
||||||
>
|
>
|
||||||
<CardMedia
|
<CardMedia
|
||||||
component="img"
|
component="img"
|
||||||
height="500"
|
|
||||||
image={productImages[activeImage]}
|
image={productImages[activeImage]}
|
||||||
alt={product.name}
|
alt={product.name}
|
||||||
sx={{ objectFit: "cover" }}
|
sx={{
|
||||||
|
objectFit: "contain", // show full image
|
||||||
|
width: "100%", // or a fixed size like "80%" or "400px"
|
||||||
|
height: "400px", // decrease from 500 to a smaller value
|
||||||
|
mx: "auto", // center horizontally if width < 100%
|
||||||
|
display: "block", // ensure image behaves like a block element
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
@ -164,12 +173,16 @@ const ProductPage = () => {
|
||||||
>
|
>
|
||||||
<CardMedia
|
<CardMedia
|
||||||
component="img"
|
component="img"
|
||||||
height="100"
|
|
||||||
image={img}
|
image={img}
|
||||||
alt={`${product.name} view ${index + 1}`}
|
alt={`${product.name} view ${index + 1}`}
|
||||||
sx={{
|
sx={{
|
||||||
objectFit: "cover",
|
objectFit: "contain",
|
||||||
|
width: "80px",
|
||||||
|
height: "80px",
|
||||||
|
mx: "auto",
|
||||||
|
display: "block",
|
||||||
opacity: activeImage === index ? 1 : 0.8,
|
opacity: activeImage === index ? 1 : 0.8,
|
||||||
|
transition: "opacity 0.3s ease",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
@ -512,19 +525,19 @@ const ProductPage = () => {
|
||||||
id: "chair2",
|
id: "chair2",
|
||||||
name: "Luxury Lounge Chair",
|
name: "Luxury Lounge Chair",
|
||||||
price: "$399",
|
price: "$399",
|
||||||
image: "/images/chair2.jpg",
|
image: `${chairthree}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "chair3",
|
id: "chair3",
|
||||||
name: "Modern Minimal Chair",
|
name: "Modern Minimal Chair",
|
||||||
price: "$279",
|
price: "$279",
|
||||||
image: "/images/chair3.jpg",
|
image: `${chairtwo}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "chair4",
|
id: "chair4",
|
||||||
name: "Executive Leather Chair",
|
name: "Executive Leather Chair",
|
||||||
price: "$599",
|
price: "$599",
|
||||||
image: "/images/chair4.jpg",
|
image: `${chairthree}`,
|
||||||
},
|
},
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<Grid item xs={12} sm={6} md={4} key={item.id}>
|
<Grid item xs={12} sm={6} md={4} key={item.id}>
|
||||||
|
|
@ -547,7 +560,8 @@ const ProductPage = () => {
|
||||||
sx={{
|
sx={{
|
||||||
height: "250px",
|
height: "250px",
|
||||||
backgroundImage: `url(${item.image})`,
|
backgroundImage: `url(${item.image})`,
|
||||||
backgroundSize: "cover",
|
backgroundSize: "contain",
|
||||||
|
backgroundRepeat: "no-repeat",
|
||||||
backgroundPosition: "center",
|
backgroundPosition: "center",
|
||||||
transition: "transform 0.5s ease",
|
transition: "transform 0.5s ease",
|
||||||
"&:hover": {
|
"&:hover": {
|
||||||
|
|
@ -555,6 +569,7 @@ const ProductPage = () => {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ p: 3, flexGrow: 1 }}>
|
<Box sx={{ p: 3, flexGrow: 1 }}>
|
||||||
<Typography
|
<Typography
|
||||||
variant="h5"
|
variant="h5"
|
||||||
|
|
@ -600,6 +615,8 @@ const ProductPage = () => {
|
||||||
</Grid>
|
</Grid>
|
||||||
</Box>
|
</Box>
|
||||||
</Container>
|
</Container>
|
||||||
|
<ContactForm />
|
||||||
|
<Footer />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,23 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { Routes as ReactRouterRoutes, Route } from "react-router-dom";
|
import { Routes as ReactRouterRoutes, Route } from "react-router-dom";
|
||||||
import LandingPage from "./components/homepage/Landingpage";
|
import LandingPage from "./components/homepage/Landingpage";
|
||||||
import CategoryPage from "./components/category/CategoryPage";
|
|
||||||
import ProductPage from "./components/product/ProductPage";
|
|
||||||
import CollectionPage from "./components/category/collectionpage";
|
import CollectionPage from "./components/category/collectionpage";
|
||||||
import CartPage from "./components/cart/cart";
|
import CartPage from "./components/cart/cart";
|
||||||
import AboutPage from "./components/cart/aboutus";
|
import AboutPage from "./components/cart/aboutus";
|
||||||
|
|
||||||
|
import BrandPage from "./components/adminportal/brand";
|
||||||
|
import ProductPage from "./components/product/ProductPage";
|
||||||
|
import Layout from "./components/adminportal/adminlayout";
|
||||||
|
|
||||||
|
import ProductForm from "./components/adminportal/product";
|
||||||
|
import CategoryPage from "./components/category/CategoryPage";
|
||||||
|
import CategoryForm from "./components/adminportal/category";
|
||||||
|
|
||||||
const Routes: React.FC = () => {
|
const Routes: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<ReactRouterRoutes>
|
<ReactRouterRoutes>
|
||||||
|
{/* Public routes */}
|
||||||
<Route path="/" element={<LandingPage />} />
|
<Route path="/" element={<LandingPage />} />
|
||||||
<Route path="/cat" element={<CategoryPage />} />
|
<Route path="/cat" element={<CategoryPage />} />
|
||||||
<Route path="/collections/:collectionId" element={<CollectionPage />} />
|
<Route path="/collections/:collectionId" element={<CollectionPage />} />
|
||||||
|
|
@ -19,6 +27,17 @@ const Routes: React.FC = () => {
|
||||||
/>
|
/>
|
||||||
<Route path="/cart" element={<CartPage />} />
|
<Route path="/cart" element={<CartPage />} />
|
||||||
<Route path="/aboutus" element={<AboutPage />} />
|
<Route path="/aboutus" element={<AboutPage />} />
|
||||||
|
|
||||||
|
{/* Admin routes - using the layout we created */}
|
||||||
|
<Route path="/admin" element={<Layout />}>
|
||||||
|
<Route path="categoriesform" element={<CategoryForm />} />
|
||||||
|
<Route path="brands" element={<BrandPage />} />
|
||||||
|
<Route path="products" element={<ProductForm />} />
|
||||||
|
|
||||||
|
{/* Redirect /admin to /admin/products by default */}
|
||||||
|
<Route index element={<ProductPage />} />
|
||||||
|
</Route>
|
||||||
|
|
||||||
{/* <Route path="*" element={<NotFoundPage />} /> */}
|
{/* <Route path="*" element={<NotFoundPage />} /> */}
|
||||||
</ReactRouterRoutes>
|
</ReactRouterRoutes>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue