This document contains 10 structured tasks for you to complete. Each task focuses on improving the existing codebase by fixing bugs, adding functionality, or optimizing user experience.
The application is already built with all pages styled and functional at a basic level. Your job is to improve logic, fix bugs, and implement missing features — not rebuild UI from scratch.
Wire the Products Listing page to use the filterProducts() function from the service layer instead of manually loading all products.
Real e-commerce apps process filtering, sorting, and pagination server-side. Using a dedicated service function mirrors this pattern and keeps UI components clean.
- Import and call
filterProducts()fromsrc/services/productService.js - Pass the current
search,selectedCategory,sortBy,sortOrder,currentPage, andproductsPerPageas parameters - Update state with the returned
{ data, total, page, totalPages, limit }object - Trigger a re-fetch when any filter, sort, or pagination value changes (use
useEffectwith proper dependencies) - Remove the manual client-side slicing logic
- Typing in the search box filters products by title/description
- Selecting a category shows only products in that category
- Changing sort order reorders the results
- Pagination updates correctly based on filtered total
- All filters can be combined (e.g., search "leather" + category "Fashion" + price low-to-high)
- Search filters products in real-time (debounce optional)
- Category dropdown filters products
- Sort dropdown reorders products
- Pagination reflects filtered totals, not total product count
- Changing any filter resets pagination to page 1
- Empty search shows all products
- Search query that matches zero products
- Selecting a category with only 1-2 products (pagination should hide)
- Rapid filter changes (no stale results displayed)
- Look at the
filterProductsfunction signature insrc/services/productService.js - Use a single
useEffectwith all filter values in the dependency array - Consider adding
minPriceandmaxPricefilter UI for bonus points
Persist search, filters, sorting, and pagination state in URL query parameters so users can share links and use browser back/forward navigation.
URL-driven state is a core UX pattern in e-commerce. It enables shareable links, browser history navigation, and refresh persistence — all critical for usability.
- Use
useSearchParamsfromreact-router-dom - Read initial state from URL parameters on page load
- Update URL parameters when any filter changes
- Support parameters:
search,category,sortBy,sortOrder,page
- Navigating to
/products?category=Electronics&sortBy=price&sortOrder=ascshows filtered results immediately - Changing filters updates the URL without full page reload
- Browser back button restores previous filter state
- Refreshing the page preserves all active filters
- All filter values are reflected in the URL
- Page loads with correct filters when URL has parameters
- Changing filters updates URL in real-time
- Browser navigation (back/forward) works with filter states
- Empty/default values are omitted from URL (clean URLs)
- Invalid parameter values in URL (e.g.,
page=999,category=NonExistent) - URL with partially set parameters
- Direct navigation to URL with all parameters set
useSearchParams()returns[searchParams, setSearchParams]- Use
searchParams.get('key')to read values - Call
setSearchParams(params)to update the URL - Initialize component state from URL parameters in a
useEffectoruseMemo
Complete the wishlist feature so users can add, remove, and manage items in their wishlist.
Wishlist is a standard e-commerce feature. Implementing it demonstrates state management with Zustand and data flow between components.
- Complete
removeFromWishlistinsrc/features/wishlist/useWishlistStore.js - Add toggle functionality (add if not in wishlist, remove if already added)
- Optionally add persistence using Zustand's
persistmiddleware - Ensure the wishlist page "Move to Cart" button works correctly
- Ensure the "Remove" button on the wishlist page works
- Clicking the heart icon on a product card toggles wishlist status
- Heart icon is filled/colored when product is in wishlist
- Wishlist page shows all saved items
- "Move to Cart" adds item to cart and removes from wishlist
- "Remove" button removes item from wishlist
-
removeFromWishlistcorrectly removes items by ID - Heart icon toggles between active and inactive states
- Wishlist page updates in real-time when items are added/removed
- "Move to Cart" button works end-to-end
- Wishlist count is accurate
- Adding the same product twice (should not duplicate)
- Removing an item that does not exist
- Empty wishlist state displays correctly
- Look at how
addToCartis implemented inuseCartStore.jsfor patterns - Use
Array.filter()to implement removal - Consider adding
persistmiddleware similar to the cart store
Build a full comparison feature that highlights differences between two selected products.
Product comparison is a key conversion feature in e-commerce. Implementation involves dynamic data rendering and conditional formatting.
- Allow selecting exactly two products from the dropdown or from product cards (e.g., "Add to Compare" button)
- Display a side-by-side table comparing all product attributes
- Highlight the "better" value in each row (e.g., lower price in green, higher rating in green)
- Add an "Add to Compare" button on
ProductCard.jsxorProductDetailsPage.jsx
- Users can select two products using the dropdowns on the Compare page
- The comparison table shows all relevant attributes
- Visual indicators highlight which product has the better value for each attribute
- Users can clear selections and compare different products
- Two products can be selected for comparison
- All fields are displayed in the comparison table
- Better values are visually highlighted
- Users can swap or clear selected products
- Cannot compare a product with itself
- Selecting the same product in both dropdowns
- Comparing products from different categories
- Products with identical values for some attributes
- Create a
useCompareStore.jswith Zustand if you want a global compare feature - For highlighting, compare numeric values and apply conditional CSS classes
- Limit compare to 2 products for simplicity
Display customer reviews on the Product Details page using the mock review data.
Reviews build trust and influence purchase decisions. This task involves data fetching, rendering lists, and computing aggregates.
- Import and call
getReviewsByProductId()from the service layer - Display reviews in the "Customer Reviews" placeholder section
- Show: reviewer name, rating (stars), comment, and date
- Show average rating and total review count at the top
- Sort reviews by date (newest first)
- Reviews load when the product page loads
- Each review shows the user name, star rating, comment, and date
- An aggregate section shows average rating and "X reviews"
- Products with no reviews show "No reviews yet"
- Reviews are fetched using
getReviewsByProductId - Each review displays all fields (user, rating, comment, date)
- Average rating is calculated correctly
- Reviews are sorted newest first
- Empty state shown for products without reviews
- Products with zero reviews
- Products with many reviews (consider showing top 5 with "Show all")
- Reviews with ratings of 1 or 5 (star rendering edge)
- Use the existing
renderStars()function already in the component - Calculate average:
reviews.reduce((sum, r) => sum + r.rating, 0) / reviews.length - Format the date nicely using
new Date(date).toLocaleDateString()
Implement client-side validation for the checkout form.
Form validation prevents invalid data submission and improves UX. It demonstrates handling user input, error states, and conditional rendering.
- Validate all fields are required (not empty)
- Validate email format (use regex:
/^[^\s@]+@[^\s@]+\.[^\s@]+$/) - Validate phone format (accept: digits, spaces, dashes, parentheses, + prefix)
- Show inline error messages below each invalid field
- Prevent form submission when validation fails
- Show errors only after the user attempts to submit or after the field loses focus
- Submitting with empty fields shows "This field is required" errors
- Invalid email shows "Please enter a valid email address"
- Invalid phone shows "Please enter a valid phone number"
- Valid submission proceeds as before
- Error messages disappear when the field is corrected
- All fields require non-empty values
- Email validation with proper regex
- Phone validation with proper regex
- Inline error messages displayed below fields
- Form cannot be submitted with errors
- Errors clear when fields are corrected
- Error styles (red border, red text) applied to invalid fields
- Whitespace-only input (should be treated as empty)
- Email with multiple @ signs
- Phone number with international prefix
- Submitting, fixing one field, and submitting again
- Create a
validateForm(form)function that returns an errors object - Store errors in state:
const [errors, setErrors] = useState({}) - Add a
touchedorsubmittedstate to control when errors display - Apply conditional classes:
border-red-500when field has error
Fix the bug in the cart where product quantity can reach 0 or become negative.
A quantity of 0 or less makes no logical sense and can cause calculation errors. This teaches defensive programming and input validation.
- Modify
updateQuantityinsrc/features/cart/useCartStore.js - Add a minimum quantity check: if
newQuantity < 1, either:- Clamp to 1 (prevent going below), OR
- Remove the item from the cart
- Ensure the UI reflects this constraint properly
- Clicking "−" when quantity is 1 either keeps it at 1 or removes the item
- Quantity never displays as 0 or negative
- The total price calculation remains correct
- Quantity cannot go below 1
- Decided behavior for quantity at 1 (clamp or remove)
- Total price is always correct
- UI buttons disabled or provide feedback at minimum quantity
- Rapid clicking of the "−" button
- Direct quantity input (if implemented later)
- Cart with multiple items at quantity 1
- Add
if (newQuantity < 1) return;before the state update, or - Add
if (newQuantity < 1) { removeFromCart(productId); return; } - Consider disabling the "−" button in the UI when quantity is 1
Replace the loading spinner in the Products Listing page with skeleton loading UI (content placeholders).
Skeleton screens reduce perceived loading time and feel more polished than spinners. They are the industry standard for modern web applications (used by Facebook, YouTube, LinkedIn, etc.).
- Create a
ProductCardSkeletoncomponent that matches theProductCardlayout - Use animated Tailwind classes:
animate-pulsewithbg-gray-200blocks - Show a grid of 8 skeleton cards during loading
- Replace the spinner in
ProductsPage.jsxwith the skeleton grid
- While loading, users see a grid of placeholder cards that pulse/shimmer
- Skeleton cards match the real card dimensions (image, title, price, button areas)
- Transition to real content is smooth
-
ProductCardSkeletoncomponent created - Skeleton matches real
ProductCardproportions - 8 skeleton cards shown during loading
- Spinner is fully replaced
- Loading animation uses
animate-pulse
- Very fast load times (skeleton should not flash briefly — optional: add minimum display time)
- Different screen sizes (skeleton should be responsive like real cards)
- Structure:
<div className="animate-pulse"><div className="bg-gray-200 aspect-square rounded-2xl" /><div className="h-4 bg-gray-200 rounded mt-3 w-3/4" /></div> - Match the grid layout used for real products
- Tailwind's
animate-pulsehandles the shimmer animation automatically
Implement toast notifications for cart and wishlist actions to provide immediate feedback to users.
Toast notifications are a standard UI pattern for providing immediate feedback to users. They are the industry standard for modern web applications (used by Facebook, YouTube, LinkedIn, etc.).
- Import and use
toastfromreact-hot-toast - Trigger toast notifications for cart and wishlist actions
- Use appropriate toast variants (e.g.,
default,success,error) - Provide clear messages for each action
- Ensure toasts are dismissible
- Adding to cart shows a toast notification
- Removing from cart shows a toast notification
- Adding to wishlist shows a toast notification
- Removing from wishlist shows a toast notification
- Toast notifications are triggered for cart actions
- Toast notifications are triggered for wishlist actions
- Toast notifications are dismissible
- Toast notifications provide clear messages
- Toast notifications use appropriate variants
- Rapid clicking of the "Add to Cart" button
- Direct quantity input (if implemented later)
- Cart with multiple items at quantity 1
Configure the Cart, Wishlist, and Comparison stores to persist their state in localStorage so data is not lost when the user refreshes the page.
State persistence is essential for e-commerce UX. Users expect their shopping cart and wishlist to remain intact across sessions. This task teaches how to use Zustand's built-in middleware for common storage patterns.
- Import
persistfromzustand/middleware - Wrap each store implementation with the
persistmiddleware - Provide a unique
namefor each store's storage key (e.g.,"cart-storage","wishlist-storage","compare-storage") - Ensure the state is correctly rehydrated on page load
- Adding items to the cart and refreshing the page preserves the items
- Adding products to the wishlist and refreshing the page preserves the items
- Selected comparison products remain visible after a page reload
-
useCartStoreusespersistmiddleware -
useWishlistStoreusespersistmiddleware -
useCompareStore(if implemented) usespersistmiddleware - Each store has a unique key name in
localStorage - No errors occur when the storage is empty (first load)
- State structure changes (versioning - optional)
- Manual clearing of
localStoragefrom browser dev tools - Storing empty arrays initially
- The
persistmiddleware is the second argument tocreatebut wraps the whole store function:create(persist((set, get) => ({ ... }), { name: '...' })) - Check the Application tab in Chrome DevTools under Local Storage to verify your keys are being created