added product categories pages to the application

This commit is contained in:
Suraj Birewar 2024-08-01 15:21:35 +05:30
parent b10f323455
commit 04b3c4e07b
14 changed files with 643 additions and 41 deletions

14
.env
View File

@ -1,2 +1,12 @@
VITE_ENVIRONMENT=dev
VITE_API_URL=https://api.yourproductionurl.com
const environment = import.meta.env.VITE_ENVIRONMENT;
const apiUrl = import.meta.env.VITE_API_URL;
console.log('Environment:', environment);
console.log('API URL:', apiUrl);
// Example of using the API URL
fetch(`${apiUrl}/endpoint`)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

View File

@ -14,6 +14,13 @@ import EmployeeList from "./components/admin/EmployeeList";
import AddEmployee from "./components/admin/AddEmployee";
import EmployeeProfile from "./components/admin/EmployeeProfile";
import CategoryList from "./components/categories/CategoryList";
import ShirtsProductList from "./components/categories/ShirtsProductList";
import SuitsProductList from "./components/categories/SuitsProductList";
import TuxedosProductList from "./components/categories/TuxedosProductList";
import JacketsProductList from "./components/categories/JacketsProductList";
import Customize from "./components/customize/Customize";
import { Refine } from "@refinedev/core";
import { RefineKbarProvider } from "@refinedev/kbar";
import { ToastContainer, toast } from "react-toastify";
@ -46,6 +53,11 @@ const App = () => {
<Route path="/add-employee" element={<PrivateRoute><AddEmployee /></PrivateRoute>} />
<Route path="/employee-profile" element={<PrivateRoute><EmployeeProfile /></PrivateRoute>} />
<Route path="/CategoryList" element={<PrivateRoute><CategoryList /></PrivateRoute>} />
<Route path="/products/shirts" element={<ShirtsProductList />} />
<Route path="/products/suits" element={<SuitsProductList />} />
<Route path="/products/tuxedos" element={<TuxedosProductList />} />
<Route path="/products/jackets" element={<JacketsProductList />} />
<Route path="/customize/:id" element={<Customize />} />
</Routes>
</Content>
</div>

View File

@ -2,10 +2,12 @@ import axios from 'axios';
// Determine environment and set baseURL accordingly
const environment = import.meta.env.VITE_ENVIRONMENT;
const baseURL = environment === 'dev'
const baseURL = environment === 'production'
? import.meta.env.VITE_API_URL
: 'http://localhost:5000'; // Default for development
console.log(`Axios baseURL set to: ${baseURL}`); // Log the base URL for debugging
const axiosInstance = axios.create({
baseURL,
headers: {
@ -13,18 +15,23 @@ const axiosInstance = axios.create({
},
});
// Request interceptor to add JWT token
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
}, error => {
// Handle request error
console.error('Request error:', error);
return Promise.reject(error);
});
// Response interceptor to handle responses globally
axiosInstance.interceptors.response.use(
response => response,
error => {
// Handle errors globally
if (error.response) {
// Server responded with a status other than 2xx
console.error('Error response:', error.response.data);

View File

@ -1,4 +1,4 @@
import axiosInstance from './axiosConfig';
import axiosInstance from '../axiosConfig';
export const getEmployees = async () => {

View File

@ -1,15 +1,17 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import Sidebar from '../sidebar/Sidebar'; // Ensure the correct path to Sidebar
import Sidebar from '../sidebar/Sidebar';
import '../../styles/ProductList.css';
const CategoryList = () => {
const navigate = useNavigate();
// Updated categories array with names and specifications
const categories = [
{ name: 'shirts', image: 'https://www2.hm.com/en_in/productpage.1032522123.html' },
{ name: 'suits', image: 'https://www2.hm.com/en_in/productpage.1032522123.html' },
{ name: 'tuxedos', image: 'https://www2.hm.com/en_in/productpage.1032522123.html' },
{ name: 'jackets', image: 'https://image.coolblue.nl/422x390/products/1101120' },
{ name: 'Shirts', image: 'https://www2.hm.com/en_in/productpage.1032522123.html', description: 'shirts' },
{ name: 'Suits', image: 'https://www2.hm.com/en_in/productpage.1032522123.html', description: 'suits' },
{ name: 'Tuxedos', image: 'https://www2.hm.com/en_in/productpage.1032522123.html', description: 'tuxedos' },
{ name: 'Jackets', image: 'https://image.coolblue.nl/422x390/products/1101120', description: 'jackets' },
];
const handleClick = (category) => {
@ -22,20 +24,25 @@ const CategoryList = () => {
<Sidebar />
{/* Main content */}
<div className="flex-grow p-8">
<h1 className="text-3xl font-bold mb-8 text-left pl-4">categories</h1>
<div className="grid grid-cols-2 gap-8">
{categories.map((category, index) => (
<div
key={index}
className="border border-gray-300 p-4 rounded-lg cursor-pointer hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col items-center"
onClick={() => handleClick(category.name)}
>
<img src={category.image} alt={category.name} className="w-48 h-48 object-cover" />
<h2 className="text-xl font-bold text-center mt-2 capitalize">{category.name}</h2>
<p className="text-center text-sm text-gray-600">click to view products</p>
</div>
))}
<div className="flex-grow flex flex-col">
<div className="bg-white p-4 flex items-center">
<h1 className="ml-4 text-2xl font-medium text-gray-700">Categories</h1>
</div>
<div className="flex-grow p-8">
<div className="grid grid-cols-2 gap-8">
{categories.map((category, index) => (
<div
key={index}
className="border border-gray-300 p-4 rounded-lg cursor-pointer hover:shadow-lg transition-shadow duration-300 ease-in-out flex flex-col items-center"
onClick={() => handleClick(category.name.toLowerCase())} // Updated to handle lowercase category names
>
<img src={category.image} alt={category.name} className="w-48 h-48 object-cover" />
<h2 className="text-xl font-bold text-center mt-2 capitalize">{category.name}</h2>
<p className="ml-4 text-2xl font-medium text-gray-700">{category.description}</p>
<p className="text-center text-sm text-gray-600 mt-1">Click to view products</p>
</div>
))}
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,66 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import Sidebar from '../sidebar/Sidebar';
import ProductRow from './ProductRow';
import '../../styles/ProductList.css';
const products = [
{ id: 1, name: 'red bomber jacket', price: 129, image: 'path/to/red-bomber-jacket-image.png' },
{ id: 2, name: 'green utility jacket', price: 149, image: 'path/to/green-utility-jacket-image.png' },
{ id: 3, name: 'black leather jacket', price: 199, image: 'path/to/black-leather-jacket-image.png' },
{ id: 4, name: 'blue aces', price: 99, image: 'path/to/blue-aces-image.png' },
{ id: 5, name: 'blue birdseye', price: 99, image: 'path/to/blue-birdseye-image.png' },
{ id: 6, name: 'blue hydrangea', price: 99, image: 'path/to/blue-hydrangea-image.png' },
];
const JacketsProductList = () => {
const navigate = useNavigate();
const handleCustomize = (id) => {
navigate(`/customize/${id}`);
};
return (
<div className="flex h-screen">
{/* Sidebar */}
<Sidebar />
{/* Main content */}
<div className="flex-grow p-8 product-list">
<h1 className="ml-4 text-2xl font-medium text-gray-700 ">Products (Jackets)</h1>
<div className="product-actions">
<p className="text-sm text-gray-600 ">Total jackets: ({products.length})</p>
<div className="flex items-center ">
<svg
className="w-5 h-5 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
/>
</svg>
<span className="text-sm text-gray-600">filter</span>
</div>
</div>
<div className="product-grid">
{products.map((product) => (
<ProductRow
key={product.id}
image17={product.image} // Ensure this matches the prop expected in ProductRow
shirts={product.name} // Ensure this matches the prop expected in ProductRow
onClick={() => handleCustomize(product.id)}
/>
))}
</div>
</div>
</div>
);
};
export default JacketsProductList;

View File

@ -1,7 +1,8 @@
import { useMemo } from "react";
import PropTypes from "prop-types";
import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import '../../styles/ProductList.css';
const ProductRow = ({ className = "", image17, shirts, propLeft, propTop }) => {
const ProductRow = ({ className = "", image17, shirts, onClick, propLeft, propTop }) => {
const productRowStyle = useMemo(() => {
return {
left: propLeft,
@ -11,35 +12,41 @@ const ProductRow = ({ className = "", image17, shirts, propLeft, propTop }) => {
return (
<div
className={`h-[350px] w-[350px] !m-[0] absolute top-[20px] left-[20px] rounded-3xs box-border flex flex-col items-start justify-start py-5 px-0 gap-[15px] text-center text-29xl text-tertiary font-button border-[0.7px] border-solid border-tertiary ${className}`}
className={`h-88 w-88 m-0 rounded-lg flex flex-col items-start justify-start py-5 px-0 gap-4 text-center text-3xl font-semibold border border-gray-300 ${className}`}
style={productRowStyle}
>
<img
className="self-stretch flex-1 relative max-w-full overflow-hidden max-h-full object-cover"
className="w-full h-full object-cover"
loading="lazy"
alt=""
alt={shirts}
src={image17}
/>
<div className="self-stretch flex flex-row items-start justify-center py-0 pr-[21px] pl-5">
<div className="relative leading-[53px] lowercase font-semibold inline-block min-w-[124px] mq450:text-10xl mq450:leading-[32px] mq1025:text-19xl mq1025:leading-[42px]">
<div className="w-full flex flex-row items-center justify-center py-0 pr-5 pl-5">
<div className="leading-9 font-semibold">
{shirts}
</div>
</div>
<div className="flex flex-row items-start justify-start py-0 px-[66px] text-3xl">
<div className="relative leading-[110%] lowercase font-semibold mq450:text-lg mq450:leading-[19px]">
Click to view products
<div className="w-full flex flex-row items-start justify-start py-0 px-16 text-lg">
<div className="leading-tight font-semibold">
Click to view product details
</div>
</div>
{/* Customize Button */}
<button
className="customize-button w-full "
onClick={onClick}
>
Customize
</button>
</div>
);
};
ProductRow.propTypes = {
className: PropTypes.string,
image17: PropTypes.string,
shirts: PropTypes.string,
/** Style props */
image17: PropTypes.string.isRequired,
shirts: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
propLeft: PropTypes.any,
propTop: PropTypes.any,
};

View File

@ -0,0 +1,66 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import Sidebar from '../sidebar/Sidebar';
import ProductRow from './ProductRow';
import '../../styles/ProductList.css';
const products = [
{ id: 1, name: 'blue aces', price: 99, image: 'path/to/blue-aces-image.png' },
{ id: 2, name: 'blue birdseye', price: 99, image: 'path/to/blue-birdseye-image.png' },
{ id: 3, name: 'blue hydrangea', price: 99, image: 'path/to/blue-hydrangea-image.png' },
{ id: 4, name: 'blue aces', price: 99, image: 'path/to/blue-aces-image.png' },
{ id: 5, name: 'blue birdseye', price: 99, image: 'path/to/blue-birdseye-image.png' },
{ id: 6, name: 'blue hydrangea', price: 99, image: 'path/to/blue-hydrangea-image.png' },
];
const ShirtsProductList = () => {
const navigate = useNavigate();
const handleCustomize = (id) => {
navigate(`/customize/${id}`);
};
return (
<div className="flex h-screen">
{/* Sidebar */}
<Sidebar />
{/* Main content */}
<div className="flex-grow p-8 product-list">
<h1 className="ml-4 text-2xl font-medium text-gray-700 " >Products (Shirts)</h1>
<div className="product-actions">
<p className="text-sm text-gray-600 ">Total shirts: ({products.length})</p>
<div className="flex items-center ">
<svg
className="w-5 h-5 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
/>
</svg>
<span className="text-sm text-gray-600">filter</span>
</div>
</div>
<div className="product-grid">
{products.map((product) => (
<ProductRow
key={product.id}
image17={product.image} // Updated to match prop name
shirts={product.name} // Updated to match prop name
onClick={() => handleCustomize(product.id)}
/>
))}
</div>
</div>
</div>
);
};
export default ShirtsProductList;

View File

@ -0,0 +1,66 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import Sidebar from '../sidebar/Sidebar';
import ProductRow from './ProductRow';
import '../../styles/ProductList.css';
const products = [
{ id: 1, name: 'blue aces', price: 99, image: 'path/to/blue-aces-image.png' },
{ id: 2, name: 'blue birdseye', price: 99, image: 'path/to/blue-birdseye-image.png' },
{ id: 3, name: 'blue hydrangea', price: 99, image: 'path/to/blue-hydrangea-image.png' },
{ id: 4, name: 'blue aces', price: 99, image: 'path/to/blue-aces-image.png' },
{ id: 5, name: 'blue birdseye', price: 99, image: 'path/to/blue-birdseye-image.png' },
{ id: 6, name: 'blue hydrangea', price: 99, image: 'path/to/blue-hydrangea-image.png' },
];
const SuitsProductList = () => {
const navigate = useNavigate();
const handleCustomize = (id) => {
navigate(`/customize/${id}`);
};
return (
<div className="flex h-screen">
{/* Sidebar */}
<Sidebar />
{/* Main content */}
<div className="flex-grow p-8 product-list">
<h1 className="ml-4 text-2xl font-medium text-gray-700 ">Products (Suits)</h1>
<div className="product-actions">
<p className="text-sm text-gray-600 ">Total suits: ({products.length})</p>
<div className="flex items-center ">
<svg
className="w-5 h-5 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
/>
</svg>
<span className="text-sm text-gray-600">filter</span>
</div>
</div>
<div className="product-grid">
{products.map((product) => (
<ProductRow
key={product.id}
image17={product.image} // Ensure this matches the prop expected in ProductRow
shirts={product.name} // Ensure this matches the prop expected in ProductRow
onClick={() => handleCustomize(product.id)}
/>
))}
</div>
</div>
</div>
);
};
export default SuitsProductList;

View File

@ -0,0 +1,66 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import Sidebar from '../sidebar/Sidebar';
import ProductRow from './ProductRow';
import '../../styles/ProductList.css';
const products = [
{ id: 1, name: 'black tuxedo', price: 199, image: 'path/to/black-tuxedo-image.png' },
{ id: 2, name: 'navy tuxedo', price: 199, image: 'path/to/navy-tuxedo-image.png' },
{ id: 3, name: 'grey tuxedo', price: 199, image: 'path/to/grey-tuxedo-image.png' },
{ id: 4, name: 'blue aces', price: 99, image: 'path/to/blue-aces-image.png' },
{ id: 5, name: 'blue birdseye', price: 99, image: 'path/to/blue-birdseye-image.png' },
{ id: 6, name: 'blue hydrangea', price: 99, image: 'path/to/blue-hydrangea-image.png' },
];
const TuxedosProductList = () => {
const navigate = useNavigate();
const handleCustomize = (id) => {
navigate(`/customize/${id}`);
};
return (
<div className="flex h-screen">
{/* Sidebar */}
<Sidebar />
{/* Main content */}
<div className="flex-grow p-8 product-list">
<h1 className="ml-4 text-2xl font-medium text-gray-700 ">Products (Tuxedos)</h1>
<div className="product-actions">
<p className="text-sm text-gray-600 ">Total tuxedos: ({products.length})</p>
<div className="flex items-center ">
<svg
className="w-5 h-5 text-gray-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"
/>
</svg>
<span className="text-sm text-gray-600">filter</span>
</div>
</div>
<div className="product-grid">
{products.map((product) => (
<ProductRow
key={product.id}
image17={product.image} // Ensure this matches the prop expected in ProductRow
shirts={product.name} // Ensure this matches the prop expected in ProductRow
onClick={() => handleCustomize(product.id)}
/>
))}
</div>
</div>
</div>
);
};
export default TuxedosProductList;

View File

@ -0,0 +1,106 @@
import React, { useState } from 'react';
import { useParams, useNavigate, Link } from 'react-router-dom';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
import Sidebar from '../sidebar/Sidebar';
import '../../styles/Customize.css';
const Customize = () => {
const { id } = useParams();
const navigate = useNavigate();
const [style, setStyle] = useState('');
const [material, setMaterial] = useState('');
const handleStyleChange = (event) => {
setStyle(event.target.value);
};
const handleMaterialChange = (event) => {
setMaterial(event.target.value);
};
const handleSave = () => {
const customization = {
productId: id,
style,
material,
};
fetch('/api/saveCustomization', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(customization),
})
.then(response => response.json())
.then(data => {
console.log('Success:', data);
navigate('/');
})
.catch((error) => {
console.error('Error:', error);
});
};
return (
<div className="flex h-screen ">
{/* Sidebar */}
<Sidebar />
{/* Main content */}
<div className="flex-grow flex flex-col">
<div className="bg-white p-4 flex items-center">
<Link to="/CategoryList" className="">
<FontAwesomeIcon icon={faChevronLeft} />
</Link>
<h1 className="ml-4 text-2xl font-medium text-gray-700 ">Customize Product</h1>
</div>
<div className="flex-grow flex items-center justify-center p-8">
<div className="bg-white p-6 rounded-lg shadow-lg w-full max-w-md">
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700">
Style:
<select
value={style}
onChange={handleStyleChange}
className="mt-1 block w-full py-2 px-3 "
>
<option value="">Select style</option>
<option value="collar">Collar</option>
<option value="cuff">Cuff</option>
<option value="placket">Placket</option>
</select>
</label>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700">
Material:
<select
value={material}
onChange={handleMaterialChange}
className="mt-1 block w-full "
>
<option value="">Select material</option>
<option value="cotton">Cotton</option>
<option value="polyester">Polyester</option>
<option value="linen">Linen</option>
</select>
</label>
</div>
<button
onClick={handleSave}
className="w-full py-2 px-4"
>
Save Style and Material
</button>
</div>
</div>
</div>
</div>
);
};
export default Customize;

View File

@ -1,4 +1,4 @@
import axiosInstance from "../api/axiosConfig"; // Import the global axios instance
import axiosInstance from "../api/axiosConfig";
// Get all users with their metadata
export const getAllUsers = async () => {

82
src/styles/Customize.css Normal file
View File

@ -0,0 +1,82 @@
.container {
display: flex;
flex-direction: row;
height: 100vh;
}
.main-content {
flex-grow: 1;
display: flex;
flex-direction: column;
}
.header {
background-color: #ffffff;
padding: 1rem;
display: flex;
align-items: center;
border-bottom: 1px solid #ffffff;
}
.header-title {
display: flex;
align-items: center;
font-size: 1.5rem; /* text-2xl */
font-weight: 600; /* font-semibold */
color: #0e355b; /* Dark blue */
margin-left: 1rem;
}
.back-button {
color: #0e355b;
font-size: 1.25rem;
}
.inner-content {
flex-grow: 1;
padding: 1.5rem;
background-color: #f9fafb;
color: #4a5568;
}
.form-input,
.form-select {
width: 100%;
padding: 0.75rem; /* p-3 */
border: 1px solid #cbd5e0; /* border-gray-400 */
border-radius: 0.375rem; /* rounded-md */
}
.form-input:focus,
.form-select:focus {
border-color: #2d3748; /* Darker gray border on focus */
outline: none;
}
.submit-button {
background-color: #0e355b; /* Dark blue */
color: white;
padding: 0.75rem 1.5rem; /* p-3 */
border-radius: 0.375rem; /* rounded-md */
font-weight: 600; /* font-semibold */
cursor: pointer;
border: none;
transition: background-color 0.3s ease;
}
.submit-button:hover {
background-color: #1e3a8a; /* Darker blue on hover */
}

107
src/styles/ProductList.css Normal file
View File

@ -0,0 +1,107 @@
.product-list {
padding: 20px;
background-color: #ffffff;
border-radius: 8px;
max-width: 1200px;
margin: 0 auto;
}
.product-list h1 {
color: #0e355b;
text-align: left;
margin-bottom: 20px;
}
.product-actions {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.filter-button {
padding: 10px 20px;
background-color: #0e355b;
color: #ffffff;
border: none;
cursor: pointer;
border-radius: 5px;
transition: background-color 0.3s;
}
.filter-button:hover {
background-color: #154676;
}
.product-grid {
display: grid;
grid-template-columns: repeat(1, 1fr);
gap: 20px;
}
@media (min-width: 640px) {
.product-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 768px) {
.product-grid {
grid-template-columns: repeat(3, 1fr);
}
}
.product-row {
display: flex;
flex-direction: column;
border: 1px solid #e8e8e8;
border-radius: 8px;
overflow: hidden;
transition: box-shadow 0.3s;
}
.product-row:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.product-row img {
width: 100%;
height: auto;
}
.product-row .product-name {
font-size: 1.5rem;
font-weight: 600;
color: #0e355b;
padding: 10px 20px;
text-align: center;
}
.product-row .view-products {
font-size: 1rem;
color: #ffffff;
background-color: #0e355b;
padding: 10px 20px;
text-align: center;
cursor: pointer;
transition: background-color 0.3s;
}
.product-row .view-products:hover {
background-color: #154676;
}
.customize-button {
background-color: #0e355b;
color: #d1d5db;
border: none;
border-radius: 4px;
padding: 8px 16px;
cursor: pointer;
text-align: center;
}
.customize-button:hover {
background-color: #154676;
}