← JavaScript/Проект: E-commerce Store#383 из 383← Предыдущий+200 XP
Полезно по теме:Гайд: как учить JavaScriptПрактика: JS базаПрактика: async и сетьТермин: Closure

Капстоун проект: E-commerce Store

О проекте

E-commerce — комплексный проект уровня Junior+. Демонстрирует полный цикл разработки интернет-магазина.

Что вы создадите:

  • Каталог товаров с фильтрами
  • Корзина и оформление заказа
  • Авторизация пользователей
  • Личный кабинет
  • Функциональные требования

    Публичная часть

    1. Каталог товаров

    - Grid/List переключение

    - Фильтры: категория, цена, бренд

    - Сортировка: цена, популярность, новинки

    - Пагинация или infinite scroll

    - Поиск

    2. Карточка товара

    - Галерея изображений

    - Варианты (размер, цвет)

    - Отзывы и рейтинг

    - Похожие товары

    3. Корзина

    - Добавление/удаление

    - Изменение количества

    - Расчёт итоговой суммы

    - Промокоды

    4. Оформление заказа

    - Форма доставки

    - Способ оплаты

    - Подтверждение заказа

    Личный кабинет

  • История заказов
  • Избранное
  • Настройки профиля
  • Структура проекта

    src/
    ├── pages/
    │   ├── Home.jsx
    │   ├── Catalog.jsx
    │   ├── Product.jsx
    │   ├── Cart.jsx
    │   ├── Checkout.jsx
    │   └── Account/
    ├── components/
    │   ├── ProductCard.jsx
    │   ├── ProductGrid.jsx
    │   ├── Filters.jsx
    │   ├── CartItem.jsx
    │   └── ...
    ├── store/              # Redux или Zustand
    │   ├── cartSlice.js
    │   ├── productsSlice.js
    │   └── userSlice.js
    ├── hooks/
    │   ├── useCart.js
    │   └── useProducts.js
    └── api/
        └── products.js

    Модель данных

    Product

    {
      id: 'prod_123',
      name: 'Кроссовки Nike Air Max',
      slug: 'nike-air-max',
      description: '...',
      price: 12990,
      oldPrice: 15990,  // для скидки
      images: ['url1', 'url2'],
      category: 'shoes',
      brand: 'Nike',
      variants: [
        { size: '42', color: 'black', stock: 5 },
        { size: '43', color: 'black', stock: 3 }
      ],
      rating: 4.5,
      reviewsCount: 128,
      tags: ['new', 'sale']
    }

    CartItem

    {
      productId: 'prod_123',
      variantId: 'var_456',  // size + color
      quantity: 2,
      price: 12990,
      name: '...',
      image: '...'
    }

    Order

    {
      id: 'order_789',
      items: [...],
      total: 25980,
      status: 'processing',
      shipping: { name, phone, address },
      payment: { method: 'card' },
      createdAt: '...'
    }

    Технологии

  • React 18 или Vue 3
  • Redux Toolkit / Pinia для state
  • React Router / Vue Router
  • Tailwind CSS
  • React Query / RTK Query для API
  • Опционально: Stripe для оплаты
  • Ключевые паттерны

    1. Optimistic Updates (корзина)

    const addToCart = async (product) => {
      // Сразу обновляем UI
      dispatch(cartActions.addItem(product))
      
      try {
        // Потом синхронизируем с сервером
        await api.addToCart(product.id)
      } catch {
        // Откатываем при ошибке
        dispatch(cartActions.removeItem(product.id))
      }
    }

    2. URL-based фильтры

    // URL: /catalog?category=shoes&minPrice=5000&sort=price_asc
    
    const [searchParams, setSearchParams] = useSearchParams()
    
    const filters = {
      category: searchParams.get('category'),
      minPrice: searchParams.get('minPrice'),
      sort: searchParams.get('sort')
    }

    3. Debounced поиск

    const debouncedSearch = useMemo(
      () => debounce((query) => {
        setSearchParams({ q: query })
      }, 300),
      []
    )

    Чек-лист готовности

  • [ ] Каталог с фильтрами и сортировкой
  • [ ] Страница товара с вариантами
  • [ ] Корзина с изменением количества
  • [ ] Оформление заказа (форма)
  • [ ] Авторизация (mock)
  • [ ] Личный кабинет
  • [ ] Responsive дизайн
  • [ ] Хорошая производительность
  • Бонусы для портфолио

  • Анимации при добавлении в корзину
  • Skeleton loading
  • PWA с офлайн-каталогом
  • Интеграция с реальным API
  • Тесты (Jest + Testing Library)
  • Примеры

    E-commerce: корзина с хуками и localStorage

    // Custom Hook для корзины e-commerce
    
    function useCart() {
      const [items, setItems] = React.useState(() => {
        const saved = localStorage.getItem('cart')
        return saved ? JSON.parse(saved) : []
      })
    
      // Сохранение в localStorage
      React.useEffect(() => {
        localStorage.setItem('cart', JSON.stringify(items))
      }, [items])
    
      // Добавление товара
      const addItem = (product, variant = null) => {
        setItems(prev => {
          const key = variant ? product.id + '-' + variant.id : product.id
          const existing = prev.find(item => item.key === key)
    
          if (existing) {
            return prev.map(item =>
              item.key === key
                ? { ...item, quantity: item.quantity + 1 }
                : item
            )
          }
    
          return [...prev, {
            key,
            productId: product.id,
            variantId: variant?.id,
            name: product.name,
            price: product.price,
            image: product.image,
            variant: variant?.name,
            quantity: 1
          }]
        })
      }
    
      // Обновление количества
      const updateQuantity = (key, quantity) => {
        if (quantity < 1) {
          removeItem(key)
          return
        }
        setItems(prev =>
          prev.map(item =>
            item.key === key ? { ...item, quantity } : item
          )
        )
      }
    
      // Удаление товара
      const removeItem = (key) => {
        setItems(prev => prev.filter(item => item.key !== key))
      }
    
      // Очистка корзины
      const clearCart = () => setItems([])
    
      // Вычисляемые значения
      const itemsCount = items.reduce((sum, item) => sum + item.quantity, 0)
      const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
      const shipping = subtotal > 5000 ? 0 : 500
      const total = subtotal + shipping
    
      return {
        items,
        addItem,
        updateQuantity,
        removeItem,
        clearCart,
        itemsCount,
        subtotal,
        shipping,
        total
      }
    }
    
    // === Демонстрация ===
    console.log('=== E-commerce Cart Demo ===\n')
    
    // Симуляция хука
    let cartState = []
    const setCart = (fn) => { cartState = typeof fn === 'function' ? fn(cartState) : fn }
    
    const addToCart = (product) => {
      setCart(prev => {
        const existing = prev.find(item => item.productId === product.id)
        if (existing) {
          return prev.map(item =>
            item.productId === product.id
              ? { ...item, quantity: item.quantity + 1 }
              : item
          )
        }
        return [...prev, { ...product, productId: product.id, quantity: 1 }]
      })
      console.log('🛒 Добавлено:', product.name)
    }
    
    // Товары
    const products = [
      { id: 1, name: 'React Книга', price: 1500, image: '📕' },
      { id: 2, name: 'TypeScript Курс', price: 3500, image: '💻' },
      { id: 3, name: 'Node.js Гайд', price: 2000, image: '📗' },
    ]
    
    // Добавляем товары
    addToCart(products[0])
    addToCart(products[1])
    addToCart(products[0]) // Повторно — увеличит quantity
    
    console.log('\nКорзина:')
    cartState.forEach(item => {
      console.log(`  ${item.image} ${item.name} x${item.quantity} = ${item.price * item.quantity}₽`)
    })
    
    const total = cartState.reduce((sum, item) => sum + item.price * item.quantity, 0)
    console.log('\nИтого:', total, '₽')

    Капстоун проект: E-commerce Store

    О проекте

    E-commerce — комплексный проект уровня Junior+. Демонстрирует полный цикл разработки интернет-магазина.

    Что вы создадите:

  • Каталог товаров с фильтрами
  • Корзина и оформление заказа
  • Авторизация пользователей
  • Личный кабинет
  • Функциональные требования

    Публичная часть

    1. Каталог товаров

    - Grid/List переключение

    - Фильтры: категория, цена, бренд

    - Сортировка: цена, популярность, новинки

    - Пагинация или infinite scroll

    - Поиск

    2. Карточка товара

    - Галерея изображений

    - Варианты (размер, цвет)

    - Отзывы и рейтинг

    - Похожие товары

    3. Корзина

    - Добавление/удаление

    - Изменение количества

    - Расчёт итоговой суммы

    - Промокоды

    4. Оформление заказа

    - Форма доставки

    - Способ оплаты

    - Подтверждение заказа

    Личный кабинет

  • История заказов
  • Избранное
  • Настройки профиля
  • Структура проекта

    src/
    ├── pages/
    │   ├── Home.jsx
    │   ├── Catalog.jsx
    │   ├── Product.jsx
    │   ├── Cart.jsx
    │   ├── Checkout.jsx
    │   └── Account/
    ├── components/
    │   ├── ProductCard.jsx
    │   ├── ProductGrid.jsx
    │   ├── Filters.jsx
    │   ├── CartItem.jsx
    │   └── ...
    ├── store/              # Redux или Zustand
    │   ├── cartSlice.js
    │   ├── productsSlice.js
    │   └── userSlice.js
    ├── hooks/
    │   ├── useCart.js
    │   └── useProducts.js
    └── api/
        └── products.js

    Модель данных

    Product

    {
      id: 'prod_123',
      name: 'Кроссовки Nike Air Max',
      slug: 'nike-air-max',
      description: '...',
      price: 12990,
      oldPrice: 15990,  // для скидки
      images: ['url1', 'url2'],
      category: 'shoes',
      brand: 'Nike',
      variants: [
        { size: '42', color: 'black', stock: 5 },
        { size: '43', color: 'black', stock: 3 }
      ],
      rating: 4.5,
      reviewsCount: 128,
      tags: ['new', 'sale']
    }

    CartItem

    {
      productId: 'prod_123',
      variantId: 'var_456',  // size + color
      quantity: 2,
      price: 12990,
      name: '...',
      image: '...'
    }

    Order

    {
      id: 'order_789',
      items: [...],
      total: 25980,
      status: 'processing',
      shipping: { name, phone, address },
      payment: { method: 'card' },
      createdAt: '...'
    }

    Технологии

  • React 18 или Vue 3
  • Redux Toolkit / Pinia для state
  • React Router / Vue Router
  • Tailwind CSS
  • React Query / RTK Query для API
  • Опционально: Stripe для оплаты
  • Ключевые паттерны

    1. Optimistic Updates (корзина)

    const addToCart = async (product) => {
      // Сразу обновляем UI
      dispatch(cartActions.addItem(product))
      
      try {
        // Потом синхронизируем с сервером
        await api.addToCart(product.id)
      } catch {
        // Откатываем при ошибке
        dispatch(cartActions.removeItem(product.id))
      }
    }

    2. URL-based фильтры

    // URL: /catalog?category=shoes&minPrice=5000&sort=price_asc
    
    const [searchParams, setSearchParams] = useSearchParams()
    
    const filters = {
      category: searchParams.get('category'),
      minPrice: searchParams.get('minPrice'),
      sort: searchParams.get('sort')
    }

    3. Debounced поиск

    const debouncedSearch = useMemo(
      () => debounce((query) => {
        setSearchParams({ q: query })
      }, 300),
      []
    )

    Чек-лист готовности

  • [ ] Каталог с фильтрами и сортировкой
  • [ ] Страница товара с вариантами
  • [ ] Корзина с изменением количества
  • [ ] Оформление заказа (форма)
  • [ ] Авторизация (mock)
  • [ ] Личный кабинет
  • [ ] Responsive дизайн
  • [ ] Хорошая производительность
  • Бонусы для портфолио

  • Анимации при добавлении в корзину
  • Skeleton loading
  • PWA с офлайн-каталогом
  • Интеграция с реальным API
  • Тесты (Jest + Testing Library)
  • Примеры

    E-commerce: корзина с хуками и localStorage

    // Custom Hook для корзины e-commerce
    
    function useCart() {
      const [items, setItems] = React.useState(() => {
        const saved = localStorage.getItem('cart')
        return saved ? JSON.parse(saved) : []
      })
    
      // Сохранение в localStorage
      React.useEffect(() => {
        localStorage.setItem('cart', JSON.stringify(items))
      }, [items])
    
      // Добавление товара
      const addItem = (product, variant = null) => {
        setItems(prev => {
          const key = variant ? product.id + '-' + variant.id : product.id
          const existing = prev.find(item => item.key === key)
    
          if (existing) {
            return prev.map(item =>
              item.key === key
                ? { ...item, quantity: item.quantity + 1 }
                : item
            )
          }
    
          return [...prev, {
            key,
            productId: product.id,
            variantId: variant?.id,
            name: product.name,
            price: product.price,
            image: product.image,
            variant: variant?.name,
            quantity: 1
          }]
        })
      }
    
      // Обновление количества
      const updateQuantity = (key, quantity) => {
        if (quantity < 1) {
          removeItem(key)
          return
        }
        setItems(prev =>
          prev.map(item =>
            item.key === key ? { ...item, quantity } : item
          )
        )
      }
    
      // Удаление товара
      const removeItem = (key) => {
        setItems(prev => prev.filter(item => item.key !== key))
      }
    
      // Очистка корзины
      const clearCart = () => setItems([])
    
      // Вычисляемые значения
      const itemsCount = items.reduce((sum, item) => sum + item.quantity, 0)
      const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0)
      const shipping = subtotal > 5000 ? 0 : 500
      const total = subtotal + shipping
    
      return {
        items,
        addItem,
        updateQuantity,
        removeItem,
        clearCart,
        itemsCount,
        subtotal,
        shipping,
        total
      }
    }
    
    // === Демонстрация ===
    console.log('=== E-commerce Cart Demo ===\n')
    
    // Симуляция хука
    let cartState = []
    const setCart = (fn) => { cartState = typeof fn === 'function' ? fn(cartState) : fn }
    
    const addToCart = (product) => {
      setCart(prev => {
        const existing = prev.find(item => item.productId === product.id)
        if (existing) {
          return prev.map(item =>
            item.productId === product.id
              ? { ...item, quantity: item.quantity + 1 }
              : item
          )
        }
        return [...prev, { ...product, productId: product.id, quantity: 1 }]
      })
      console.log('🛒 Добавлено:', product.name)
    }
    
    // Товары
    const products = [
      { id: 1, name: 'React Книга', price: 1500, image: '📕' },
      { id: 2, name: 'TypeScript Курс', price: 3500, image: '💻' },
      { id: 3, name: 'Node.js Гайд', price: 2000, image: '📗' },
    ]
    
    // Добавляем товары
    addToCart(products[0])
    addToCart(products[1])
    addToCart(products[0]) // Повторно — увеличит quantity
    
    console.log('\nКорзина:')
    cartState.forEach(item => {
      console.log(`  ${item.image} ${item.name} x${item.quantity} = ${item.price * item.quantity}₽`)
    })
    
    const total = cartState.reduce((sum, item) => sum + item.price * item.quantity, 0)
    console.log('\nИтого:', total, '₽')

    Задание

    Создай мини e-commerce: каталог товаров с фильтром по категории, корзину с изменением количества, и простой checkout. Используй localStorage для сохранения корзины.

    Подсказка

    localStorage: JSON.stringify(cart). filter: item.quantity > 0 (удаляем товары с нулевым количеством).

    Загружаем среду выполнения...
    Загружаем AI-помощника...