diff --git a/recent view.jpg b/recent view.jpg
new file mode 100644
index 00000000..3f768b68
Binary files /dev/null and b/recent view.jpg differ
diff --git a/src/api/productService.js b/src/api/productService.js
index 65b0ccab..ee160c51 100644
--- a/src/api/productService.js
+++ b/src/api/productService.js
@@ -1,9 +1,10 @@
import axiosInstance from './axiosInstance.js';
const transformProduct = (apiProduct) => {
- const originalPrice = apiProduct.sale > 0
- ? Number((apiProduct.price / (1 - apiProduct.sale / 100)).toFixed(2))
- : null;
+ const originalPrice =
+ apiProduct.sale > 0
+ ? Number((apiProduct.price / (1 - apiProduct.sale / 100)).toFixed(2))
+ : null;
return {
id: apiProduct._id,
@@ -24,7 +25,7 @@ const transformProduct = (apiProduct) => {
sizes: apiProduct.sizes || [],
salePercentage: apiProduct.sale,
longDescription: apiProduct.longDescription,
- usageTips: apiProduct.usageTips
+ usageTips: apiProduct.usageTips,
};
};
@@ -34,7 +35,7 @@ const transformApiResponse = (apiResponse) => {
totalCount: apiResponse.total,
currentPage: apiResponse.page,
hasNextPage: apiResponse.page < apiResponse.pages,
- totalPages: apiResponse.pages
+ totalPages: apiResponse.pages,
};
};
@@ -42,7 +43,7 @@ export const getProducts = async (params = {}) => {
try {
// Map our filter names to API parameter names
const apiParams = {};
-
+
if (params.page) apiParams.page = params.page;
if (params.limit) apiParams.limit = params.limit;
if (params.category) apiParams.category = params.category;
@@ -53,8 +54,10 @@ export const getProducts = async (params = {}) => {
if (params.sortBy) apiParams.sortBy = params.sortBy;
if (params.sortOrder) apiParams.sortOrder = params.sortOrder;
- const response = await axiosInstance.get('/products', { params: apiParams });
-
+ const response = await axiosInstance.get('/products', {
+ params: apiParams,
+ });
+
return {
success: true,
data: transformApiResponse(response.data),
@@ -76,7 +79,7 @@ export const getProductById = async (id) => {
}
const response = await axiosInstance.get(`/products/${id}`);
-
+
return {
success: true,
data: transformProduct(response.data),
@@ -99,7 +102,7 @@ export const searchProducts = async (query, params = {}) => {
...params,
},
});
-
+
return {
success: true,
data: response.data,
@@ -117,7 +120,7 @@ export const searchProducts = async (query, params = {}) => {
export const getProductCategories = async () => {
try {
const response = await axiosInstance.get('/products/categories');
-
+
return {
success: true,
data: response.data,
@@ -130,4 +133,4 @@ export const getProductCategories = async () => {
status: error.response?.status || 500,
};
}
-};
\ No newline at end of file
+};
diff --git a/src/components/GarageSaleRecentlyViewed.jsx b/src/components/GarageSaleRecentlyViewed.jsx
new file mode 100644
index 00000000..ced97e61
--- /dev/null
+++ b/src/components/GarageSaleRecentlyViewed.jsx
@@ -0,0 +1,163 @@
+import { useState, useMemo } from 'react';
+import ProductCard from './Products/ProductCard';
+import { getRecentlyViewed } from '../utils/recentlyViewed';
+
+// SVG component for the navigation arrows
+const ChevronLeftIcon = (props) => (
+
+);
+
+const ChevronRightIcon = (props) => (
+
+);
+
+export default function GarageSaleRecentlyViewed() {
+ // Filter recently viewed items to only show products that are on sale
+ const filterSaleProducts = (products) => {
+ return products.filter(product => {
+ // Check if product has a sale price (originalPrice exists and is greater than current price)
+ const hasSalePrice = product.originalPrice && product.originalPrice > product.price;
+ // Or check if there's a sale field/property
+ const hasSaleField = product.sale && product.sale > 0;
+ return hasSalePrice || hasSaleField;
+ });
+ };
+
+ // Load and filter recently viewed items using useState with function initializer
+ const [recentlyViewedItems] = useState(() => {
+ const allRecentlyViewed = getRecentlyViewed();
+ return filterSaleProducts(allRecentlyViewed);
+ });
+ const [currentIndex, setCurrentIndex] = useState(0);
+ const itemsPerPage = 5; // 5 items per view as per requirements
+
+ // Memoize navigation logic for better performance
+ const navigationState = useMemo(() => {
+ const canGoNext = currentIndex < recentlyViewedItems.length - itemsPerPage;
+ const canGoPrev = currentIndex > 0;
+ const visibleItems = recentlyViewedItems.slice(currentIndex, currentIndex + itemsPerPage);
+ const totalPages = Math.ceil(recentlyViewedItems.length / itemsPerPage);
+
+ return { canGoNext, canGoPrev, visibleItems, totalPages };
+ }, [currentIndex, recentlyViewedItems, itemsPerPage]);
+
+ const nextSlide = () => {
+ const maxStartIndex = Math.max(0, recentlyViewedItems.length - itemsPerPage);
+ const newIndex = Math.min(currentIndex + itemsPerPage, maxStartIndex);
+ setCurrentIndex(newIndex);
+ };
+
+ const prevSlide = () => {
+ const newIndex = Math.max(currentIndex - itemsPerPage, 0);
+ setCurrentIndex(newIndex);
+ };
+
+ if (!recentlyViewedItems || recentlyViewedItems.length === 0) {
+ return null;
+ }
+
+ return (
+
+ {/* Header section with title and navigation arrows */}
+
+
+ RECENTLY
+ VIEWED
+
+
+ {/* Navigation Buttons - Only show if there are more than 5 items */}
+ {recentlyViewedItems.length > itemsPerPage && (
+
+
+
+
+ )}
+
+
+ {/* Recently Viewed Products Slider */}
+
+ {/* Left Arrow */}
+ {recentlyViewedItems.length > itemsPerPage && navigationState.canGoPrev && (
+
+ )}
+
+ {/* Slider Container */}
+
+
+ {recentlyViewedItems.map((product) => (
+
+ ))}
+
+
+
+ {/* Right Arrow */}
+ {recentlyViewedItems.length > itemsPerPage && navigationState.canGoNext && (
+
+ )}
+
+
+ {/* Pagination Dots */}
+ {recentlyViewedItems.length > itemsPerPage && (
+
+ {Array.from({ length: navigationState.totalPages }).map((_, index) => (
+
+ )}
+
+ );
+}
diff --git a/src/components/Header/MainHeader.jsx b/src/components/Header/MainHeader.jsx
index 105f3a75..42b9abbd 100644
--- a/src/components/Header/MainHeader.jsx
+++ b/src/components/Header/MainHeader.jsx
@@ -50,9 +50,9 @@ export default function Header() {
)}
-
+
Garage Sale
-
+
All Products
@@ -119,9 +119,13 @@ export default function Header() {
)}
-
+ setMobileOpen(false)}
+ >
Garage Sale
-
+
{
({product.reviewCount || 0})
-
- {product.originalPrice && (
-
{formatPrice(product.originalPrice)}
- )}
-
{formatPrice(product.price)}
+
+
+ {product.originalPrice && (
+ {formatPrice(product.originalPrice)}
+ )}
+ {formatPrice(product.price)}
+
@@ -242,33 +244,35 @@ const ProductCard = forwardRef(({ product }, ref) => {
diff --git a/src/pages/GarageSale/GarageSale.jsx b/src/pages/GarageSale/GarageSale.jsx
new file mode 100644
index 00000000..90f62645
--- /dev/null
+++ b/src/pages/GarageSale/GarageSale.jsx
@@ -0,0 +1,141 @@
+import { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import { fetchProducts } from '../../store/productSlice';
+import SEO from '../../components/SEO';
+import ProductGrid from '../../components/Products/ProductGrid';
+import ProductSkeleton from '../../components/Products/ProductSkeleton';
+import GarageSaleRecentlyViewed from '../../components/GarageSaleRecentlyViewed';
+
+function GarageSale() {
+ const dispatch = useDispatch();
+ const {
+ products: allProducts,
+ loading,
+ error,
+ } = useSelector((state) => state.products);
+
+ // Filter products that are on sale (have sale > 0 or originalPrice > price)
+ const saleProducts = allProducts.filter(product => {
+ // Check if product has a sale price (originalPrice exists and is greater than current price)
+ const hasSalePrice = product.originalPrice && product.originalPrice > product.price;
+ // Or check if there's a sale field/property
+ const hasSaleField = product.sale && product.sale > 0;
+ return hasSalePrice || hasSaleField;
+ });
+
+ useEffect(() => {
+ dispatch(fetchProducts({ page: 1, limit: 1000 }));
+ }, [dispatch]);
+
+ return (
+ <>
+
+
+
+ {/* Banner Section */}
+
+
+
+
+ Garage Sale
+
+
+ Limited time offers on premium sports nutrition supplements.
+ Don't miss out on these incredible deals!
+
+
+
+ {saleProducts.length} Products on Sale
+
+
+
+
+
+
+ {/* Product Grid Section */}
+
+
+
+ {error && (
+
+
+
+ Unable to Load Products
+
+
{error}
+
+
+ )}
+
+ {loading &&
}
+
+ {!loading && !error && saleProducts.length > 0 && (
+ <>
+
+ >
+ )}
+
+ {!loading && !error && saleProducts.length === 0 && (
+
+
+
+ No Sale Products Available
+
+
+ Check back later for amazing deals on our premium supplements.
+
+
+ )}
+
+
+
+
+ {/* Recently Viewed Section (bottom of page) */}
+ {!loading && !error && }
+
+ >
+ );
+}
+
+export default GarageSale;
diff --git a/src/routes/RouterConfig.jsx b/src/routes/RouterConfig.jsx
index 12b5899a..36c0b5d8 100755
--- a/src/routes/RouterConfig.jsx
+++ b/src/routes/RouterConfig.jsx
@@ -17,6 +17,7 @@ const ShippingPolicy = lazy(
);
const Products = lazy(() => import('../pages/Products/Products'));
const ProductPage = lazy(() => import('../pages/Products/ProductPage'));
+const GarageSale = lazy(() => import('../pages/GarageSale/GarageSale'));
const TermsOfService = lazy(
() => import('../pages/TermsOfService/TermsOfService')
);
@@ -32,6 +33,7 @@ export const RouterConfig = () =>
} />
} />
} />
+ } />
} />
} />
} />{' '}