From 43b60b5a78f68a3bd1557074b546a65af096d6a3 Mon Sep 17 00:00:00 2001 From: hardik Date: Mon, 14 Jul 2025 11:36:18 +0530 Subject: [PATCH] added css --- .../adminportal/adminAPIservices.tsx | 132 +++++++ src/components/adminportal/adminappbar.tsx | 26 ++ src/components/adminportal/adminlayout.tsx | 27 ++ src/components/adminportal/adminsidebar.tsx | 79 ++++ src/components/adminportal/brand.tsx | 191 ++++++++++ src/components/adminportal/category.tsx | 177 +++++++++ src/components/adminportal/product.tsx | 203 ++++++++++ src/components/category/CategoryPage.tsx | 352 +++++++++++++++--- src/components/category/collectionpage.tsx | 328 ++++++++++++---- src/components/product/ProductPage.tsx | 43 ++- src/routes.tsx | 23 +- 11 files changed, 1442 insertions(+), 139 deletions(-) create mode 100644 src/components/adminportal/adminAPIservices.tsx create mode 100644 src/components/adminportal/adminappbar.tsx create mode 100644 src/components/adminportal/adminlayout.tsx create mode 100644 src/components/adminportal/adminsidebar.tsx create mode 100644 src/components/adminportal/brand.tsx create mode 100644 src/components/adminportal/category.tsx create mode 100644 src/components/adminportal/product.tsx diff --git a/src/components/adminportal/adminAPIservices.tsx b/src/components/adminportal/adminAPIservices.tsx new file mode 100644 index 0000000..f468d64 --- /dev/null +++ b/src/components/adminportal/adminAPIservices.tsx @@ -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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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 => { + 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", + }; + } +}; diff --git a/src/components/adminportal/adminappbar.tsx b/src/components/adminportal/adminappbar.tsx new file mode 100644 index 0000000..fc9c4a5 --- /dev/null +++ b/src/components/adminportal/adminappbar.tsx @@ -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 ( + theme.zIndex.drawer + 1 }} + > + + + + + + Furniture Admin + + + A + + + + ); +}; + +export default AdminAppBar; diff --git a/src/components/adminportal/adminlayout.tsx b/src/components/adminportal/adminlayout.tsx new file mode 100644 index 0000000..5e719ca --- /dev/null +++ b/src/components/adminportal/adminlayout.tsx @@ -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 ( + + + + + + + + + ); +}; + +export default AdminLayout; diff --git a/src/components/adminportal/adminsidebar.tsx b/src/components/adminportal/adminsidebar.tsx new file mode 100644 index 0000000..db7deba --- /dev/null +++ b/src/components/adminportal/adminsidebar.tsx @@ -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 ( + + + + + + navigate("/admin")}> + + + + + + + + + + + navigate("/admin/categoriesform")}> + + + + + + + + navigate("/admin/brands")}> + + + + + + + + navigate("/admin/products")}> + + + + + + + + + + ); +}; + +export default AdminSidebar; diff --git a/src/components/adminportal/brand.tsx b/src/components/adminportal/brand.tsx new file mode 100644 index 0000000..0396d6c --- /dev/null +++ b/src/components/adminportal/brand.tsx @@ -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([]); + 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 ( + + + Brand Management + + + + + + + Add New Brand + +
+ + setFormData({ ...formData, name: e.target.value }) + } + margin="normal" + required + /> + + setFormData({ ...formData, description: e.target.value }) + } + margin="normal" + multiline + rows={3} + /> + + setFormData({ ...formData, website: e.target.value }) + } + margin="normal" + type="url" + /> + + +
+
+ + + + Brand Directory ({brands.length}) + + + + {brands.map((brand) => ( + + + + + + {brand.name} + + + {brand.description || "No description"} + + {brand.website && ( + + } + label="Website" + clickable + color="primary" + size="small" + /> + + )} + + + + ))} + + +
+
+ ); +}; + +// 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; diff --git a/src/components/adminportal/category.tsx b/src/components/adminportal/category.tsx new file mode 100644 index 0000000..8a8ffb5 --- /dev/null +++ b/src/components/adminportal/category.tsx @@ -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([]); + 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 ( + + + Category Management + + + + {/* Form Section */} + + + + Add New Category + + {error && ( + + {error} + + )} + {success && ( + + Category created! + + )} + +
+ + setFormData({ ...formData, name: e.target.value }) + } + margin="normal" + required + /> + + setFormData({ ...formData, description: e.target.value }) + } + margin="normal" + multiline + rows={3} + /> + + +
+
+ + {/* Categories List */} + + + Existing Categories ({categories.length}) + + + + {categories.map((category) => ( + + + + + {category.name} + + + {category.description || "No description"} + + {category.parent_category_id > 0 && ( + + )} + + + + + + + + + + + + ))} + + +
+
+ ); +}; + +export default CategoryForm; diff --git a/src/components/adminportal/product.tsx b/src/components/adminportal/product.tsx new file mode 100644 index 0000000..8f617f3 --- /dev/null +++ b/src/components/adminportal/product.tsx @@ -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({ + 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) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + }; + + const handleSelectChange = (e: SelectChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value as number })); + }; + + const handleCheckboxChange = (e: React.ChangeEvent) => { + 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 ( + <> + + + Add New Product + + + Manage furniture products for your e-commerce store + + + + + {error && ( + + {error} + + )} + {success && ( + + Product created successfully! + + )} + +
+ + + + + Category + + + + + Brand + + + + + } + label="Active Product" + sx={{ mt: 2 }} + /> + + + + + +
+ + ); +}; + +export default ProductForm; diff --git a/src/components/category/CategoryPage.tsx b/src/components/category/CategoryPage.tsx index 4764586..7718515 100644 --- a/src/components/category/CategoryPage.tsx +++ b/src/components/category/CategoryPage.tsx @@ -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 ( - + + + {/* Hero Section */} - + 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 + @@ -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)", + }, }} > Our Craftsmanship Philosophy @@ -135,7 +211,12 @@ const CategoryPage = () => { 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 = () => { 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 = () => { We source our materials from responsible suppliers, ensuring that our environmental impact is minimized. The hardwoods in our @@ -164,7 +254,11 @@ const CategoryPage = () => { 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 = () => { {/* Category Grid */} - - - Explore Our Collections - - - Each collection tells a unique design story - + + + + Explore Our Collections + + + Each collection tells a unique design story + + - + {categories.map((category) => ( - + { > - - + + {category.name} - + {category.description} {/* Collection Description */} - + - + About This Collection + + + + 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. + + + + + 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. + + + + - 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. - - - 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. - - - Collection Highlights @@ -173,13 +296,22 @@ const CollectionPage = () => { - {item} + + {item} + ))} @@ -188,28 +320,44 @@ const CollectionPage = () => { {/* Products Grid */} - - - Featured {collectionId} - - - Browse our curated selection - + + + + Featured {collectionId} + + + Browse our curated selection + + - + {collection.map((product) => ( { 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%)", + }, }} /> - + {product.name} - + {product.description} @@ -247,17 +426,32 @@ const CollectionPage = () => { sx={{ display: "flex", alignItems: "center", - mb: 1, + mb: 1.5, + color: "#555", }} > - + • - + {detail} ))} - + {product.price}