Compare commits

..

1 Commits

Author SHA1 Message Date
hardik 43b60b5a78 added css 2025-07-14 11:36:18 +05:30
11 changed files with 1442 additions and 139 deletions

View File

@ -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",
};
}
};

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
);
};

View File

@ -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>
);