Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import './App.css';
import { BrowserRouter as Router, Routes, Route, Link } from 'react-router-dom';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import HomePage from './components/HomePage';
import AboutPage from './components/AboutPage';
import ShopPage from './components/ShopPage';
Expand Down
82 changes: 82 additions & 0 deletions src/components/ProductCard.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// filepath: e:\GSSOC\new_eco\EcoStore\src\components\ProductCard.js
import React, { useState } from 'react';
import { Link } from 'react-router-dom';
import '../styles/ProductCard.css';

const ProductCard = ({ product, featured = false, onAddToCart }) => {
const { id, title, price, description, image, rating, category } = product;
const [isHovered, setIsHovered] = useState(false);

const handleAddToCart = (e) => {
e.preventDefault();
if (onAddToCart) {
onAddToCart(product);
}
};

// Calculate discount if product is on sale
const discountPercent = product.sale ? Math.floor(Math.random() * 30) + 10 : 0; // Random discount between 10-40%
const originalPrice = product.sale ? (price / (1 - discountPercent / 100)).toFixed(2) : null;

return (
<div
className={`product-card ${featured ? 'featured' : ''} ${isHovered ? 'hovered' : ''}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<div className="product-image-container">
<Link to={`/product/${id}`} className="product-link">
<img
src={image}
alt={title}
className="product-image"
onError={(e) => {
e.target.onerror = null;
e.target.src = 'https://via.placeholder.com/300x200?text=Product+Image';
}}
/>
</Link>
{featured && <span className="featured-badge">Featured</span>}
{product.sale && <span className="sale-badge">SALE {discountPercent}% OFF</span>}
<div className="product-quick-actions">
<button className="quick-action-btn" title="Add to Wishlist">❤</button>
<button className="quick-action-btn" title="Quick View">👁️</button>
</div>
</div>
<div className="product-info">
<p className="product-category">{category}</p>
<Link to={`/product/${id}`} className="product-title-link">
<h3 className="product-title">{title}</h3>
</Link>
<div className="product-rating">
{'★'.repeat(Math.round(rating))}
{'☆'.repeat(5 - Math.round(rating))}
<span className="rating-number">({rating})</span>
</div>
<div className="product-price-container">
{product.sale && (
<span className="product-original-price">${originalPrice}</span>
)}
<p className="product-price">${price.toFixed(2)}</p>
</div>
<p className="product-description">{description.substring(0, 80)}...</p>
<div className="product-actions">
<button className="add-to-cart-btn" onClick={handleAddToCart}>
Add to Cart
</button>
<Link to={`/product/${id}`} className="view-details-btn">
View Details
</Link>
</div>
</div>
{product.stock <= 5 && product.stock > 0 && (
<div className="stock-warning">Only {product.stock} left!</div>
)}
{product.stock === 0 && (
<div className="out-of-stock">Out of Stock</div>
)}
</div>
);
};

export default ProductCard;
182 changes: 176 additions & 6 deletions src/components/ShopPage.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,184 @@
import React from 'react';
import React, { useState, useEffect } from 'react';
import Navbar from './Navbar';
import ProductCard from './ProductCard';
import SkeletonLoader from './SkeletonLoader';
import { mockProducts } from '../data/mockProducts';
import shopHeaderImage from '../assets/products_image_1.jpg';
import '../styles/ShopPage.css';

const ShopPage = () => {
const [products, setProducts] = useState([]);
const [featuredProducts, setFeaturedProducts] = useState([]);
const [filteredProducts, setFilteredProducts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
const [category, setCategory] = useState('');
const [sortBy, setSortBy] = useState('featured');
const [error, setError] = useState(null);
const productsPerPage = 8;

// Fetch products from API (mock data for now)
useEffect(() => {
const fetchProducts = async () => {
setIsLoading(true);
try {
// In the future, replace this with actual API call
// const response = await fetch('/api/products');
// const data = await response.json();

// Simulating API delay
await new Promise(resolve => setTimeout(resolve, 1500));

// Using mock data for now
const data = mockProducts;

setProducts(data);
setFeaturedProducts(data.filter(product => product.featured));
setFilteredProducts(data);
setError(null);
} catch (err) {
console.error('Error fetching products:', err);
setError('Failed to load products. Please try again later.');
} finally {
setIsLoading(false);
}
};

fetchProducts();
}, []);

// Filter and sort products
useEffect(() => {
let result = [...products];

// Apply category filter
if (category) {
result = result.filter(product => product.category === category);
}

// Apply sorting
switch(sortBy) {
case 'price-asc':
result.sort((a, b) => a.price - b.price);
break;
case 'price-desc':
result.sort((a, b) => b.price - a.price);
break;
case 'rating':
result.sort((a, b) => b.rating - a.rating);
break;
default: // featured or any other
result.sort((a, b) => (b.featured ? 1 : 0) - (a.featured ? 1 : 0));
}

setFilteredProducts(result);
// Reset to first page when filters change
setCurrentPage(1);
}, [products, category, sortBy]);

// Get current products for pagination
const indexOfLastProduct = currentPage * productsPerPage;
const indexOfFirstProduct = indexOfLastProduct - productsPerPage;
const currentProducts = filteredProducts.slice(indexOfFirstProduct, indexOfLastProduct);

const paginate = (pageNumber) => setCurrentPage(pageNumber);

return (
<>
<Navbar />
<div>
<h1>Shop Page</h1>
<p>Browse our products here.</p>
</div>
<Navbar />
<div className="shop-header" style={{ backgroundImage: `url(${shopHeaderImage})` }}>
<div className="shop-header-overlay">
<h1>Eco-Friendly Products</h1>
<p>Sustainable choices for a better tomorrow</p>
</div>
</div>
<div className="shop-container">
{/* Filter and Sort Controls */}
<div className="shop-controls">
<div className="shop-filter">
<select
value={category}
onChange={(e) => setCategory(e.target.value)}
className="control-select"
>
<option value="">All Categories</option>
<option value="Personal Care">Personal Care</option>
<option value="Kitchen & Grocery">Kitchen & Grocery</option>
<option value="Electronics">Electronics</option>
<option value="Home">Home</option>
<option value="Fashion">Fashion</option>
<option value="Garden">Garden</option>
<option value="Stationery">Stationery</option>
<option value="Fitness">Fitness</option>
</select>
</div>
<div className="shop-sort">
<select
value={sortBy}
onChange={(e) => setSortBy(e.target.value)}
className="control-select"
>
<option value="featured">Featured</option>
<option value="price-asc">Price: Low to High</option>
<option value="price-desc">Price: High to Low</option>
<option value="rating">Highest Rated</option>
</select>
</div>
</div>

{/* Error state */}
{error && (
<div className="error-message">
<p>{error}</p>
<button onClick={() => window.location.reload()}>Try Again</button>
</div>
)}

{/* Featured Products Section */}
<section className="featured-products">
<h2>Featured Products</h2>
<div className="featured-products-grid">
{isLoading ? (
Array(3).fill().map((_, index) => (
<SkeletonLoader key={index} type="product" />
))
) : (
featuredProducts.map(product => (
<ProductCard key={product.id} product={product} featured={true} />
))
)}
</div>
</section>

{/* Main Products Grid */}
<section className="products-section">
<h2>All Products</h2>
<div className="products-grid">
{isLoading ? (
Array(productsPerPage).fill().map((_, index) => (
<SkeletonLoader key={index} type="product" />
))
) : (
currentProducts.map(product => (
<ProductCard key={product.id} product={product} />
))
)}
</div>
</section>

{/* Pagination */}
<div className="pagination">
{Array(Math.ceil(filteredProducts.length / productsPerPage)).fill().map((_, index) => (
<button
key={index}
className={`page-button ${currentPage === index + 1 ? 'active' : ''}`}
onClick={() => paginate(index + 1)}
>
{index + 1}
</button>
))}
</div>
</div>
</>
);
};
Expand Down
54 changes: 54 additions & 0 deletions src/components/SkeletonLoader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// filepath: e:\GSSOC\new_eco\EcoStore\src\components\SkeletonLoader.js
import React from 'react';
import '../styles/SkeletonLoader.css';

const SkeletonLoader = ({ type, style = {} }) => {
if (type === 'product') {
return (
<div className="skeleton-product" style={style}>
<div className="skeleton-image">
<div className="skeleton-shine"></div>
</div>
<div className="skeleton-content">
<div className="skeleton-title">
<div className="skeleton-shine"></div>
</div>
<div className="skeleton-category">
<div className="skeleton-shine"></div>
</div>
<div className="skeleton-rating">
<div className="skeleton-shine"></div>
</div>
<div className="skeleton-price">
<div className="skeleton-shine"></div>
</div>
<div className="skeleton-description">
<div className="skeleton-shine"></div>
</div>
<div className="skeleton-actions">
<div className="skeleton-button">
<div className="skeleton-shine"></div>
</div>
<div className="skeleton-button">
<div className="skeleton-shine"></div>
</div>
</div>
</div>
</div>
);
} else if (type === 'banner') {
return (
<div className="skeleton-banner" style={style}>
<div className="skeleton-shine"></div>
</div>
);
}

return (
<div className="skeleton-generic" style={style}>
<div className="skeleton-shine"></div>
</div>
);
};

export default SkeletonLoader;
Loading