DEV Community

Cover image for Daily UI - Day 12: E-commerce Shop
Johnny Santamaria
Johnny Santamaria

Posted on

Daily UI - Day 12: E-commerce Shop

Welcome to Day 12 of the Daily UI Challenge! Today we'll be building a modern e-commerce shop interface using React and Tailwind CSS. We'll create a responsive product grid with filtering capabilities and a shopping cart preview.

The Challenge

Create an e-commerce shop interface that includes:

  • A grid of products with images, prices, and "Add to Cart" buttons
  • A category filter
  • A shopping cart preview
  • Responsive design for mobile and desktop

Let's start building

First, let's create our main component and the necessary state management:

import React, { useState } from 'react';
import { ShoppingCart } from 'lucide-react';

const products = [
  {
    id: 1,
    name: "Vintage Camera",
    price: 299.99,
    category: "electronics",
    image: "/api/placeholder/200/200"
  },
  {
    id: 2,
    name: "Leather Backpack",
    price: 79.99,
    category: "accessories",
    image: "/api/placeholder/200/200"
  },
  {
    id: 3,
    name: "Mechanical Keyboard",
    price: 159.99,
    category: "electronics",
    image: "/api/placeholder/200/200"
  },
  {
    id: 4,
    name: "Cotton T-Shirt",
    price: 24.99,
    category: "clothing",
    image: "/api/placeholder/200/200"
  }
];

const categories = ["all", "electronics", "accessories", "clothing"];

const Shop = () => {
  const [selectedCategory, setSelectedCategory] = useState("all");
  const [cart, setCart] = useState([]);
  const [isCartOpen, setIsCartOpen] = useState(false);

  // Filter products based on selected category
  const filteredProducts = selectedCategory === "all"
    ? products
    : products.filter(product => product.category === selectedCategory);

  // Add item to cart
  const addToCart = (product) => {
    setCart(prevCart => {
      const existingItem = prevCart.find(item => item.id === product.id);
      if (existingItem) {
        return prevCart.map(item =>
          item.id === product.id
            ? { ...item, quantity: item.quantity + 1 }
            : item
        );
      }
      return [...prevCart, { ...product, quantity: 1 }];
    });
  };

  // Calculate total price
  const cartTotal = cart.reduce((total, item) => 
    total + (item.price * item.quantity), 0
  );

  return (
    <div className="container mx-auto px-4 py-8">
      {/* Shop Header */}
      <div className="flex justify-between items-center mb-8">
        <h1 className="text-3xl font-bold">Our Shop</h1>
        <button
          onClick={() => setIsCartOpen(!isCartOpen)}
          className="relative p-2"
        >
          <ShoppingCart size={24} />
          {cart.length > 0 && (
            <span className="absolute -top-1 -right-1 bg-red-500 text-white rounded-full w-5 h-5 text-xs flex items-center justify-center">
              {cart.reduce((total, item) => total + item.quantity, 0)}
            </span>
          )}
        </button>
      </div>

      {/* Category Filter */}
      <div className="flex gap-4 mb-8">
        {categories.map(category => (
          <button
            key={category}
            onClick={() => setSelectedCategory(category)}
            className={`px-4 py-2 rounded-full ${
              selectedCategory === category
                ? "bg-blue-500 text-white"
                : "bg-gray-200"
            }`}
          >
            {category.charAt(0).toUpperCase() + category.slice(1)}
          </button>
        ))}
      </div>

      {/* Product Grid */}
      <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
        {filteredProducts.map(product => (
          <div
            key={product.id}
            className="border rounded-lg p-4 hover:shadow-lg transition-shadow"
          >
            <img
              src={product.image}
              alt={product.name}
              className="w-full h-48 object-cover rounded-lg mb-4"
            />
            <h3 className="text-lg font-semibold">{product.name}</h3>
            <p className="text-gray-600">${product.price.toFixed(2)}</p>
            <button
              onClick={() => addToCart(product)}
              className="mt-4 w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition-colors"
            >
              Add to Cart
            </button>
          </div>
        ))}
      </div>

      {/* Cart Preview */}
      {isCartOpen && (
        <div className="fixed top-0 right-0 h-full w-80 bg-white shadow-lg p-4 overflow-y-auto">
          <div className="flex justify-between items-center mb-4">
            <h2 className="text-xl font-bold">Shopping Cart</h2>
            <button
              onClick={() => setIsCartOpen(false)}
              className="text-gray-500"
            >
              ×
            </button>
          </div>
          {cart.length === 0 ? (
            <p>Your cart is empty</p>
          ) : (
            <>
              {cart.map(item => (
                <div key={item.id} className="flex items-center gap-4 mb-4">
                  <img
                    src={item.image}
                    alt={item.name}
                    className="w-16 h-16 object-cover rounded"
                  />
                  <div>
                    <h3 className="font-semibold">{item.name}</h3>
                    <p className="text-sm text-gray-600">
                      ${item.price.toFixed(2)} × {item.quantity}
                    </p>
                  </div>
                </div>
              ))}
              <div className="border-t pt-4 mt-4">
                <p className="text-lg font-bold">
                  Total: ${cartTotal.toFixed(2)}
                </p>
              </div>
            </>
          )}
        </div>
      )}
    </div>
  );
};

export default Shop;

Enter fullscreen mode Exit fullscreen mode

Breaking down the components

State management

We're using React's useState hook to manage three pieces of state:

  • selectedCategory: Tracks the current category filter

  • cart: Stores the items in the shopping cart

  • isCartOpen: Controls the cart sidebar visibility

Category Filter

The category filter uses buttons that update the selectedCategory state. The selected category gets a different style to indicate the active filter.

Product Grid

Products are displayed in a responsive grid using CSS Grid. Each product card shows:

  • Product image

  • Name

  • Price

  • Add to Cart button

Shopping Cart

The cart preview slides in from the right side when clicked. It shows:

  • List of items with quantities

  • Total price

  • Empty cart message when applicable

Styling

We're using Tailwind CSS for styling, which provides:

  • Responsive design with breakpoints

  • Hover and transition effects

  • Clean, consistent spacing

  • Easy-to-maintain utility classes

Key Features

Responsive design

  • Grid adapts from 1 to 3 columns based on screen size

  • Cart sidebar is mobile-friendly

Interactive Elements

  • Category Filtering

  • Add to Cart Functionality

  • Cart Quantity badge

  • Sliding Cart Preview

State Management

  • Efficient cart updates

  • Category Filtering

  • Cart total calculation

Further Improvements

Here are some ways I could enhance this shop:

  1. Add quantity controls in the cart

  2. Implement a checkout process

  3. Add product search functionality

  4. Include product details modal

  5. Add animations for cart interactions

  6. Implement persistent cart storage

Playing the code

  1. Go to codesandbox.io

  2. Create a new sandbox

  3. Next.js template

  4. Install dependencies on the terminal

Dependencies are external packages/libraries that a project needs to work properly

npm install lucide-react @shadcn/ui

  1. Replace pages/index.js with this code:
import Shop from '../components/Shop';

export default function Home() {
  return <Shop />;
}

Enter fullscreen mode Exit fullscreen mode
  1. Create new file named components/Shop.jsx and paste the full shop code from earlier in this article

  2. Add required Next.js configuration - create next.config.js:

/** @type {import('next').NextConfig} */
const nextConfig = {
  images: {
    domains: ['api.placeholder.com'],
  },
};

module.exports = nextConfig;

Enter fullscreen mode Exit fullscreen mode

The placeholder images will automatically work through CodeSandbox's API proxy.

Conclusion

This e-commerce shop component demonstrates key React concepts like state management, conditional rendering, and component organization. It provides a solid foundation that you can build upon for more complex e-commerce applications.

Remember to:

  • Keep components modular

  • Maintain consistent styling

  • Consider user experience

  • Handle edge cases

  • Implement proper error handling

Happy coding and see you next challenge

Top comments (0)