added css #1
|
|
@ -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 {
|
||||
Box,
|
||||
Container,
|
||||
|
|
@ -8,6 +8,8 @@ import {
|
|||
Card,
|
||||
Paper,
|
||||
Divider,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Category } from "./types";
|
||||
|
|
@ -15,6 +17,9 @@ import chairone from "../images/chairone.png";
|
|||
import chairtwo from "../images/chairtwo.png";
|
||||
import chairthree from "../images/chairthree.png";
|
||||
import carimage from "../images/caraone.png";
|
||||
import ContactForm from "../contactus/contactus";
|
||||
import Footer from "../foorter";
|
||||
import HeaderSection from "../header";
|
||||
|
||||
export const categories: Category[] = [
|
||||
{
|
||||
|
|
@ -70,32 +75,57 @@ export const categories: Category[] = [
|
|||
|
||||
const CategoryPage = () => {
|
||||
const navigate = useNavigate();
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
return (
|
||||
<Box sx={{ backgroundColor: "#f5f5f5", minHeight: "100vh" }}>
|
||||
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh" }}>
|
||||
<HeaderSection />
|
||||
|
||||
{/* Hero Section */}
|
||||
<Box
|
||||
sx={{
|
||||
height: { xs: "60vh", md: "80vh" },
|
||||
backgroundImage: `url(${carimage})`,
|
||||
height: { xs: "70vh", md: "85vh" },
|
||||
backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url(${carimage})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundAttachment: isMobile ? "scroll" : "fixed",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
textAlign: "center",
|
||||
color: "#fff",
|
||||
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
|
||||
variant="h1"
|
||||
fontWeight="bold"
|
||||
sx={{
|
||||
fontSize: { xs: "2.5rem", md: "4rem" },
|
||||
fontSize: { xs: "2.8rem", md: "4.5rem" },
|
||||
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
|
||||
|
|
@ -104,12 +134,35 @@ const CategoryPage = () => {
|
|||
variant="h5"
|
||||
sx={{
|
||||
mb: 4,
|
||||
color: "#FFA500",
|
||||
color: "#ed9506d6",
|
||||
fontWeight: 500,
|
||||
fontStyle: "italic",
|
||||
letterSpacing: "0.05em",
|
||||
textShadow: "1px 1px 3px rgba(0,0,0,0.5)",
|
||||
}}
|
||||
>
|
||||
Discover furniture that transforms your space
|
||||
</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>
|
||||
|
||||
|
|
@ -118,16 +171,39 @@ const CategoryPage = () => {
|
|||
elevation={0}
|
||||
sx={{
|
||||
backgroundColor: "#fff",
|
||||
py: 8,
|
||||
borderBottom: "1px solid rgba(0,0,0,0.1)",
|
||||
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">
|
||||
<Typography
|
||||
variant="h3"
|
||||
variant="h2"
|
||||
fontWeight="bold"
|
||||
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
|
||||
</Typography>
|
||||
|
|
@ -135,7 +211,12 @@ const CategoryPage = () => {
|
|||
<Grid item xs={12} md={6}>
|
||||
<Typography
|
||||
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
|
||||
functional. Each piece in our collection is meticulously crafted
|
||||
|
|
@ -144,7 +225,11 @@ const CategoryPage = () => {
|
|||
</Typography>
|
||||
<Typography
|
||||
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
|
||||
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}>
|
||||
<Typography
|
||||
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
|
||||
that our environmental impact is minimized. The hardwoods in our
|
||||
|
|
@ -164,7 +254,11 @@ const CategoryPage = () => {
|
|||
</Typography>
|
||||
<Typography
|
||||
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
|
||||
between form and function, offering designs that stand the test
|
||||
|
|
@ -176,38 +270,57 @@ const CategoryPage = () => {
|
|||
</Paper>
|
||||
|
||||
{/* 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
|
||||
variant="h2"
|
||||
fontWeight="bold"
|
||||
align="center"
|
||||
sx={{
|
||||
mb: 2,
|
||||
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
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="h5"
|
||||
align="center"
|
||||
sx={{
|
||||
mb: 6,
|
||||
mt: 3,
|
||||
color: "#666",
|
||||
fontStyle: "italic",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Each collection tells a unique design story
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={6}>
|
||||
<Grid container spacing={{ xs: 4, md: 6 }}>
|
||||
{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
|
||||
sx={{
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
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": {
|
||||
transform: "translateY(-10px)",
|
||||
boxShadow: "0 15px 30px rgba(0,0,0,0.15)",
|
||||
|
|
@ -216,29 +329,63 @@ const CategoryPage = () => {
|
|||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: "300px",
|
||||
height: "280px",
|
||||
backgroundImage: `url(${category.image})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundSize: "contain",
|
||||
backgroundRepeat: "no-repeat",
|
||||
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 }}>
|
||||
<Typography variant="h4" fontWeight="bold" sx={{ mb: 2 }}>
|
||||
<Box sx={{ p: 4, flexGrow: 1, backgroundColor: "#fff" }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
fontWeight="bold"
|
||||
sx={{
|
||||
mb: 2,
|
||||
color: "#333",
|
||||
minHeight: "64px",
|
||||
}}
|
||||
>
|
||||
{category.name}
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ mb: 3, color: "#666" }}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
mb: 3,
|
||||
color: "#666",
|
||||
minHeight: "60px",
|
||||
}}
|
||||
>
|
||||
{category.description}
|
||||
</Typography>
|
||||
<Button
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => navigate(`/collections/${category.id}`)}
|
||||
sx={{
|
||||
backgroundColor: "#FFA500",
|
||||
color: "#fff",
|
||||
py: 1.5,
|
||||
background: "orange",
|
||||
borderRadius: "8px",
|
||||
fontWeight: 600,
|
||||
fontSize: "1rem",
|
||||
letterSpacing: "0.03em",
|
||||
boxShadow: "none",
|
||||
transition: "all 0.3s ease",
|
||||
"&:hover": {
|
||||
backgroundColor: "#E59400",
|
||||
transform: "translateY(-2px)",
|
||||
boxShadow: `0 4px 12px ${theme.palette.secondary.main}40`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -254,35 +401,115 @@ const CategoryPage = () => {
|
|||
{/* Testimonials */}
|
||||
<Paper
|
||||
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">
|
||||
<Typography
|
||||
variant="h3"
|
||||
variant="h2"
|
||||
fontWeight="bold"
|
||||
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
|
||||
</Typography>
|
||||
<Grid container spacing={4}>
|
||||
{[
|
||||
{
|
||||
quote:
|
||||
"The most comfortable chair I've ever owned - worth every penny!",
|
||||
author: "Sarah J.",
|
||||
},
|
||||
{
|
||||
quote:
|
||||
"Transformed my living room completely. The quality is exceptional.",
|
||||
author: "Michael T.",
|
||||
},
|
||||
{
|
||||
quote:
|
||||
"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}>
|
||||
<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
|
||||
variant="body1"
|
||||
sx={{ fontStyle: "italic", mb: 2 }}
|
||||
sx={{
|
||||
fontStyle: "italic",
|
||||
mb: 2,
|
||||
fontSize: "1.1rem",
|
||||
lineHeight: 1.7,
|
||||
}}
|
||||
>
|
||||
"{quote}"
|
||||
"{testimonial.quote}"
|
||||
</Typography>
|
||||
<Typography variant="body2" color="#FFA500">
|
||||
— Happy Customer
|
||||
<Typography
|
||||
variant="body2"
|
||||
// color="secondary"
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
color: "orange",
|
||||
fontSize: "1rem",
|
||||
}}
|
||||
>
|
||||
— {testimonial.author}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
|
@ -290,6 +517,9 @@ const CategoryPage = () => {
|
|||
</Grid>
|
||||
</Container>
|
||||
</Paper>
|
||||
|
||||
<ContactForm />
|
||||
<Footer />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,12 +8,18 @@ import {
|
|||
Button,
|
||||
Paper,
|
||||
Divider,
|
||||
useTheme,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import { useParams, Link } from "react-router-dom";
|
||||
import { Product } from "./types";
|
||||
import chairone from "../images/chairone.png";
|
||||
import chairtwo from "../images/chairtwo.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[]> = {
|
||||
chairs: [
|
||||
|
|
@ -92,74 +98,191 @@ const collectionItems: Record<string, Product[]> = {
|
|||
],
|
||||
},
|
||||
],
|
||||
// Add more collections as needed
|
||||
};
|
||||
|
||||
const CollectionPage = () => {
|
||||
const { collectionId } = useParams<{ collectionId: string }>();
|
||||
const collection = collectionId ? collectionItems[collectionId] || [] : [];
|
||||
const theme = useTheme();
|
||||
const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
return (
|
||||
<Box sx={{ backgroundColor: "#f5f5f5", minHeight: "100vh" }}>
|
||||
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh" }}>
|
||||
<Header />
|
||||
{/* Collection Hero */}
|
||||
<Box
|
||||
sx={{
|
||||
height: "50vh",
|
||||
backgroundImage:
|
||||
"linear-gradient(rgba(0,0,0,0.2), rgba(0,0,0,0.2)), url(/images/collection-bg.jpg)",
|
||||
height: { xs: "60vh", md: "90vh" },
|
||||
backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4)), url(${carimg})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundPosition: "center",
|
||||
backgroundAttachment: isMobile ? "scroll" : "fixed",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
textAlign: "center",
|
||||
color: "#fff",
|
||||
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
|
||||
variant="h1"
|
||||
fontWeight="bold"
|
||||
sx={{
|
||||
fontSize: { xs: "2.5rem", md: "4rem" },
|
||||
fontSize: { xs: "2.8rem", md: "4rem" },
|
||||
mb: 2,
|
||||
textTransform: "capitalize",
|
||||
textShadow: "2px 2px 8px rgba(0,0,0,0.7)",
|
||||
lineHeight: 1.2,
|
||||
letterSpacing: "0.03em",
|
||||
}}
|
||||
>
|
||||
{collectionId} Collection
|
||||
</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
|
||||
</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>
|
||||
|
||||
{/* 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">
|
||||
<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
|
||||
</Typography>
|
||||
<Grid container spacing={6}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography
|
||||
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
|
||||
craftsmanship. Each piece is created with meticulous attention to
|
||||
detail, using only the finest materials sourced from sustainable
|
||||
suppliers.
|
||||
Our {collectionId} collection represents the pinnacle of design
|
||||
and craftsmanship. Each piece is created with meticulous
|
||||
attention to detail, using only the finest materials sourced
|
||||
from sustainable suppliers.
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography
|
||||
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
|
||||
step in our process is carefully monitored to ensure exceptional
|
||||
quality and durability that will last for generations.
|
||||
</Typography>
|
||||
<Divider sx={{ my: 6 }} />
|
||||
<Typography variant="h4" fontWeight="bold" sx={{ mb: 3 }}>
|
||||
</Grid>
|
||||
</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
|
||||
</Typography>
|
||||
<Grid container spacing={4} sx={{ mb: 6 }}>
|
||||
|
|
@ -173,13 +296,22 @@ const CollectionPage = () => {
|
|||
<Box sx={{ display: "flex", alignItems: "center" }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: "8px",
|
||||
height: "8px",
|
||||
backgroundColor: "#FFA500",
|
||||
width: "10px",
|
||||
height: "10px",
|
||||
backgroundColor: "orange",
|
||||
mr: 2,
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
/>
|
||||
<Typography>{item}</Typography>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
fontSize: "1.1rem",
|
||||
color: "#555",
|
||||
}}
|
||||
>
|
||||
{item}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
|
|
@ -188,13 +320,26 @@ const CollectionPage = () => {
|
|||
</Paper>
|
||||
|
||||
{/* 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
|
||||
variant="h2"
|
||||
fontWeight="bold"
|
||||
sx={{
|
||||
mb: 2,
|
||||
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}
|
||||
|
|
@ -202,14 +347,17 @@ const CollectionPage = () => {
|
|||
<Typography
|
||||
variant="h5"
|
||||
sx={{
|
||||
mb: 6,
|
||||
mt: 3,
|
||||
color: "#666",
|
||||
fontStyle: "italic",
|
||||
fontWeight: 400,
|
||||
}}
|
||||
>
|
||||
Browse our curated selection
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={6}>
|
||||
<Grid container spacing={{ xs: 4, md: 6 }}>
|
||||
{collection.map((product) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={product.id}>
|
||||
<Card
|
||||
|
|
@ -217,7 +365,12 @@ const CollectionPage = () => {
|
|||
height: "100%",
|
||||
display: "flex",
|
||||
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": {
|
||||
transform: "translateY(-10px)",
|
||||
boxShadow: "0 15px 30px rgba(0,0,0,0.15)",
|
||||
|
|
@ -228,15 +381,41 @@ const CollectionPage = () => {
|
|||
sx={{
|
||||
height: "300px",
|
||||
backgroundImage: `url(${product.image})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundSize: "contain", // fits the whole image, may leave empty space
|
||||
backgroundRepeat: "no-repeat",
|
||||
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 }}>
|
||||
<Typography variant="h4" fontWeight="bold" sx={{ mb: 2 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
fontWeight="bold"
|
||||
sx={{
|
||||
mb: 2,
|
||||
color: "#333",
|
||||
}}
|
||||
>
|
||||
{product.name}
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ mb: 2, color: "#666" }}>
|
||||
<Typography
|
||||
variant="body1"
|
||||
sx={{
|
||||
mb: 3,
|
||||
color: "#666",
|
||||
minHeight: "60px",
|
||||
}}
|
||||
>
|
||||
{product.description}
|
||||
</Typography>
|
||||
<Box sx={{ mb: 3 }}>
|
||||
|
|
@ -247,17 +426,32 @@ const CollectionPage = () => {
|
|||
sx={{
|
||||
display: "flex",
|
||||
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}
|
||||
</Typography>
|
||||
))}
|
||||
</Box>
|
||||
<Typography variant="h5" color="#FFA500" sx={{ mb: 3 }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
color="secondary"
|
||||
sx={{
|
||||
mb: 3,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{product.price}
|
||||
</Typography>
|
||||
<Button
|
||||
|
|
@ -266,11 +460,17 @@ const CollectionPage = () => {
|
|||
fullWidth
|
||||
variant="contained"
|
||||
sx={{
|
||||
backgroundColor: "#FFA500",
|
||||
color: "#fff",
|
||||
background: "orange",
|
||||
py: 1.5,
|
||||
borderRadius: "8px",
|
||||
fontWeight: 600,
|
||||
fontSize: "1rem",
|
||||
letterSpacing: "0.03em",
|
||||
boxShadow: "none",
|
||||
transition: "all 0.3s ease",
|
||||
"&:hover": {
|
||||
backgroundColor: "#E59400",
|
||||
transform: "translateY(-2px)",
|
||||
boxShadow: `0 4px 12px ${theme.palette.secondary.main}40`,
|
||||
},
|
||||
}}
|
||||
>
|
||||
|
|
@ -282,6 +482,8 @@ const CollectionPage = () => {
|
|||
))}
|
||||
</Grid>
|
||||
</Container>
|
||||
<ContactForm />
|
||||
<Footer />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ import StarIcon from "@mui/icons-material/Star";
|
|||
import chairone from "../images/chairone.png";
|
||||
import chairtwo from "../images/chairtwo.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>> = {
|
||||
chairs: {
|
||||
|
|
@ -109,14 +112,15 @@ const ProductPage = () => {
|
|||
|
||||
const productImages = [
|
||||
product.image,
|
||||
"/images/chair-alt1.jpg",
|
||||
"/images/chair-alt2.jpg",
|
||||
"/images/chair-detail.jpg",
|
||||
`${chairone}`,
|
||||
`${chairtwo}`,
|
||||
`${chairthree}`,
|
||||
];
|
||||
|
||||
return (
|
||||
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh", py: 8 }}>
|
||||
<Container maxWidth="lg">
|
||||
<Box sx={{ backgroundColor: "#f9f9f9", minHeight: "100vh" }}>
|
||||
<HeaderSection />
|
||||
<Container maxWidth="lg" sx={{ mt: 8 }}>
|
||||
<Grid container spacing={6}>
|
||||
{/* Product Images */}
|
||||
<Grid item xs={12} md={6}>
|
||||
|
|
@ -134,10 +138,15 @@ const ProductPage = () => {
|
|||
>
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="500"
|
||||
image={productImages[activeImage]}
|
||||
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>
|
||||
|
||||
|
|
@ -164,12 +173,16 @@ const ProductPage = () => {
|
|||
>
|
||||
<CardMedia
|
||||
component="img"
|
||||
height="100"
|
||||
image={img}
|
||||
alt={`${product.name} view ${index + 1}`}
|
||||
sx={{
|
||||
objectFit: "cover",
|
||||
objectFit: "contain",
|
||||
width: "80px",
|
||||
height: "80px",
|
||||
mx: "auto",
|
||||
display: "block",
|
||||
opacity: activeImage === index ? 1 : 0.8,
|
||||
transition: "opacity 0.3s ease",
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
|
|
@ -512,19 +525,19 @@ const ProductPage = () => {
|
|||
id: "chair2",
|
||||
name: "Luxury Lounge Chair",
|
||||
price: "$399",
|
||||
image: "/images/chair2.jpg",
|
||||
image: `${chairthree}`,
|
||||
},
|
||||
{
|
||||
id: "chair3",
|
||||
name: "Modern Minimal Chair",
|
||||
price: "$279",
|
||||
image: "/images/chair3.jpg",
|
||||
image: `${chairtwo}`,
|
||||
},
|
||||
{
|
||||
id: "chair4",
|
||||
name: "Executive Leather Chair",
|
||||
price: "$599",
|
||||
image: "/images/chair4.jpg",
|
||||
image: `${chairthree}`,
|
||||
},
|
||||
].map((item) => (
|
||||
<Grid item xs={12} sm={6} md={4} key={item.id}>
|
||||
|
|
@ -547,7 +560,8 @@ const ProductPage = () => {
|
|||
sx={{
|
||||
height: "250px",
|
||||
backgroundImage: `url(${item.image})`,
|
||||
backgroundSize: "cover",
|
||||
backgroundSize: "contain",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "center",
|
||||
transition: "transform 0.5s ease",
|
||||
"&:hover": {
|
||||
|
|
@ -555,6 +569,7 @@ const ProductPage = () => {
|
|||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
<Box sx={{ p: 3, flexGrow: 1 }}>
|
||||
<Typography
|
||||
variant="h5"
|
||||
|
|
@ -600,6 +615,8 @@ const ProductPage = () => {
|
|||
</Grid>
|
||||
</Box>
|
||||
</Container>
|
||||
<ContactForm />
|
||||
<Footer />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,23 @@
|
|||
import React from "react";
|
||||
import { Routes as ReactRouterRoutes, Route } from "react-router-dom";
|
||||
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 CartPage from "./components/cart/cart";
|
||||
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 = () => {
|
||||
return (
|
||||
<ReactRouterRoutes>
|
||||
{/* Public routes */}
|
||||
<Route path="/" element={<LandingPage />} />
|
||||
<Route path="/cat" element={<CategoryPage />} />
|
||||
<Route path="/collections/:collectionId" element={<CollectionPage />} />
|
||||
|
|
@ -19,6 +27,17 @@ const Routes: React.FC = () => {
|
|||
/>
|
||||
<Route path="/cart" element={<CartPage />} />
|
||||
<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 />} /> */}
|
||||
</ReactRouterRoutes>
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in New Issue