Skip to content
Merged
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
7 changes: 7 additions & 0 deletions src/components/Products/ProductCard.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useState, forwardRef, useRef, useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { addRecentlyViewed } from '../../utils/recentlyViewed';

const HeartIcon = ({ isWishlisted = false, animate = false, className = 'h-6 w-6' }) => (
<svg
Expand Down Expand Up @@ -40,6 +41,12 @@ const ProductCard = forwardRef(({ product }, ref) => {
const cartAddedTimeoutRef = useRef(null);

const handleProductClick = () => {
// add to recently viewed list (stored in localStorage) before navigating
try {
addRecentlyViewed(product);
} catch {
// ignore errors (localStorage not available)
}
navigate(`/product/${product.id}`);
};

Expand Down
84 changes: 84 additions & 0 deletions src/components/RecentlyViewed.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { useState } from 'react';
import ProductCard from './Products/ProductCard';
import { getRecentlyViewed } from '../utils/recentlyViewed';

// SVG component for the navigation arrows
const ChevronLeftIcon = (props) => (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5L8.25 12l7.5-7.5" />
</svg>
);

const ChevronRightIcon = (props) => (
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" {...props}>
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5" />
</svg>
);


export default function RecentlyViewed() {
const [items] = useState(() => getRecentlyViewed());
const [currentIndex, setCurrentIndex] = useState(0);
const itemsPerPage = 3;

const nextSlide = () => {
const maxStartIndex = Math.max(0, items.length - itemsPerPage);
const newIndex = Math.min(currentIndex + itemsPerPage, maxStartIndex);
setCurrentIndex(newIndex);
};

const prevSlide = () => {
const newIndex = Math.max(currentIndex - itemsPerPage, 0);
setCurrentIndex(newIndex);
};

if (!items || items.length === 0) {
return null;
}

const canGoNext = currentIndex < items.length - itemsPerPage;
const canGoPrev = currentIndex > 0;

const visibleItems = items.slice(currentIndex, currentIndex + itemsPerPage);

return (
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
{/* Header section with title and navigation arrows */}
<div className="flex items-center justify-between mb-8">
<h2 className="text-5xl lg:text-heading-xxl font-montserrat leading-none uppercase text-center tracking-tight py-10 text-stroke-black">
RECENTLY
<span className="ml-5 text-[#f7faff]">VIEWED</span>
</h2>

{/* Navigation Buttons */}
<div className="flex items-center gap-3">
<button
onClick={prevSlide}
disabled={!canGoPrev}
className="flex h-15 w-15 items-center justify-center rounded-full border border-gray-300 text-gray-500 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-40 transition-all duration-200"
aria-label="Previous"
>
<ChevronLeftIcon className="h-5 w-5" />
</button>
<button
onClick={nextSlide}
disabled={!canGoNext}
className="flex h-15 w-15 items-center justify-center rounded-full border border-gray-300 text-gray-500 hover:bg-gray-100 disabled:cursor-not-allowed disabled:opacity-40 transition-all duration-200"
aria-label="Next"
>
<ChevronRightIcon className="h-5 w-5" />
</button>
</div>
</div>
{/* Recently Viewed Products Grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-12 transition-all duration-500 ease-in-out">
{visibleItems.map((product) => (
<div key={product.id} className="opacity-100">
<ProductCard product={product} />
</div>
))}
</div>
</section>
);

}
24 changes: 22 additions & 2 deletions src/index.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
@import url('https://fonts.googleapis.com/css2?family=Inter&family=Montserrat:ital,wght@0,700;1,700&family=Poppins&display=swap');
@import 'tailwindcss';

:root {
@theme {
/* Accent Fonts */
--font-montserrat: 'Montserrat', sans-serif;
--font-poppins: 'Poppins', sans-serif;
Expand All @@ -18,6 +18,26 @@
-webkit-text-stroke: 2px black;
}

/* Black text stroke utility (explicit name used in RecentlyViewed)Use multiple text-shadow layers to create an outer-only outline so the stroke doesn't bleed into inner counters of glyphs.*/
.text-stroke-black {
/* multiple offsets around the glyph to simulate an even outline */
text-shadow:
-1px -1px 0 #000,
1px -1px 0 #000,
-1px 1px 0 #000,
1px 1px 0 #000,
0 -2px 0 #000,
2px 0 0 #000,
0 2px 0 #000,
-2px 0 0 #000,
-2px -2px 0 #000,
2px -2px 0 #000,
2px 2px 0 #000,
-2px 2px 0 #000;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}

/* Product Card Action Button Animations */
@keyframes double-pop {
0% { transform: scale(1); }
Expand All @@ -30,4 +50,4 @@
.animate-double-pop {
transform-origin: center;
animation: double-pop 520ms cubic-bezier(.22,1,.36,1) both;
}
}
4 changes: 4 additions & 0 deletions src/pages/Products/Products.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import SEO from '../../components/SEO';
import ProductGrid from '../../components/Products/ProductGrid';
import SortDropdown from '../../components/Products/SortDropdown';
import ProductSkeleton from '../../components/Products/ProductSkeleton';
import RecentlyViewed from '../../components/RecentlyViewed';

export default function Products() {
const dispatch = useDispatch();
Expand Down Expand Up @@ -173,6 +174,9 @@ export default function Products() {
</div>
</div>
</section>

{/* Recently Viewed Section (bottom of page) */}
{!loading && !error && <RecentlyViewed />}
</main>
</>
);
Expand Down
32 changes: 32 additions & 0 deletions src/utils/recentlyViewed.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Simple utility to manage recently viewed products.
// Stores/retrieves an array of product objects in localStorage under the key `recently_viewed`.

export function getRecentlyViewed() {
try {
const raw = localStorage.getItem('recently_viewed');
if (!raw) return [];
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) return [];
return parsed;
} catch {
// If localStorage access fails (SSR or permission), return empty array
return [];
}
}

export function addRecentlyViewed(product) {
if (!product) return;
try {
const current = getRecentlyViewed();
// Remove any existing occurrence
const deduped = current.filter((p) => p.id !== product.id);
// Prepend the new product and cap at 10
deduped.unshift(product);
const trimmed = deduped.slice(0, 10);
localStorage.setItem('recently_viewed', JSON.stringify(trimmed));
} catch {
// ignore
}
}

export default { getRecentlyViewed, addRecentlyViewed };