Форма оптового заказа может помочь вашим клиентам быстрее оформить заказ. Обычно покупатель, желающий добавить много уникальных товаров в свою корзину, должен переходить между несколькими страницами.

Используя форму оптового заказа, покупатель может установить количество нескольких товаров и добавить их все в свою корзину нажатием одной кнопки.

В форме массового заказа, которую мы собираемся создать, используется бесплатная тема BigCommerce Cornerstone, BigCommerce Storefront API и React. Он реализуется путем установки файла пользовательского макета на странице категории с простыми товарами и автоматической подачи данных о товарах в форму.

Прежде чем начать, убедитесь, что у вас установлен Stencil CLI. Вы можете найти инструкции и ссылки для скачивания здесь: https://developer.bigcommerce.com/stencil-docs/getting-started/installing-stencil

Использование CLI позволит запускать тему локально, помогая нам перебирать наши изменения и просматривать их в режиме реального времени.

Мы собираемся использовать последнюю тему Cornerstone в качестве основы. Вы можете скачать тему из репозитория GitHub здесь: https://github.com/bigcommerce/cornerstone

Кроме того, вы можете загрузить свежую версию последней версии Cornerstone из панели управления вашего магазина.

Установка зависимостей и настройка Webpack

После того, как вы установили интерфейс командной строки Stencil и загрузили тему Cornerstone, перейдите в каталог тем в своем терминале. В связи с обновлением зависимостей Babel между версиями Cornerstone, вам необходимо установить эти зависимости в зависимости от версии вашей темы:

Cornerstone 3.0 и более ранние версии

npm install — save react react-dom css-loader style-loader babel-preset-react

Cornerstone 3.1 и новее

npm install — save react react-dom css-loader style-loader @babel/preset-react

Поскольку мы вводим React в тему, нам необходимо настроить Webpack для использования загрузчика файлов JSX. Мы также добавим загрузчик для CSS, чтобы мы могли внести свой собственный стиль в форму оптового заказа. Включите это в массив правил модуля в webpack.common.js:

Cornerstone 3.0 и более ранние версии

webpack.common.js

{
 test: /\.jsx$/,
 exclude: /node_modules/,
 use: {
 loader: “babel-loader”,
 options: {
 presets: [“react”],
 },
 }
},
{
 test: /\.css/,
 loader:[ “style-loader”, “css-loader” ]
}

Cornerstone 3.1 и новее

webpack.common.js

{
 test: /\.jsx$/,
 exclude: /node_modules/,
 use: {
 loader: “babel-loader”,
 options: {
 presets: [“@babel/preset-react”],
 },
 }
},
{
 test: /\.css/,
 loader:[ “style-loader”, “css-loader” ]
}

В любой версии темы вам также нужно будет добавить следующее к объекту разрешения:

webpack.common.js

extensions: [“.js”, “.jsx”]

Подготовка шаблона макета

Чтобы включить форму массового заказа на странице категории, мы должны максимально упростить ее переключение для продавца. Как только наше приложение будет завершено, продавец должен иметь возможность редактировать категорию на своей панели управления и просто выбирать шаблон формы оптового заказа.

В темах BigCommerce файлы пользовательского макета необходимо добавить в templates/pages/custom. Создайте собственный каталог, затем добавьте в него другую папку с именем category. Это позволяет магазину знать, что для страниц категорий доступен файл настраиваемого макета.

Создайте файл с именем bulk-order-form.html в templates/pages/custom/category. Затем давайте скопируем содержимое из существующего category.html шаблона, чтобы использовать его в качестве основы для нашего настраиваемого шаблона. Нам не нужна никакая фильтрация товаров или боковая навигация, поэтому давайте удалим следующий код:

bulk-order-form.html

// …
{{#if category.faceted_search_enabled}}
 <aside class=”page-sidebar” id=”faceted-search-container”>
 {{> components/category/sidebar}}
 </aside>
 {{else if category.subcategories}}
 <aside class=”page-sidebar” id=”faceted-search-container”>
 {{> components/category/sidebar}}
 </aside>
 {{else if category.shop_by_price}}
 {{#if theme_settings.shop_by_price_visibility}}
 <aside class=”page-sidebar” id=”faceted-search-container”>
 {{> components/category/sidebar}}
 </aside>
 {{/if}}
{{/if}}
// …

Поскольку мы заменяем контент продукта, который обычно отображается на странице категории, давайте также удалим все, что находится между <main> elements (оставьте <main> на месте):

bulk-order-form.html

//…
{{#if category.products}}
 {{> components/category/product-listing settings=../settings}}
{{else}}
 <p>{{lang “categories.no_products”}}</p>
{{/if}}
//…

React нуждается в элементе для развития, поэтому мы создадим div с идентификатором bulk-order-form внутри элемента <main>:

bulk-order-form.html

<div id=”bulk-order-form”></div>

Настройка приложения React

Теперь мы создадим каталог нашего приложения React в файлах темы. Создайте папку с именем bulk-order-form в assets/js. Затем создайте файл в каталоге формы оптового заказа с именем bulk-order-form.jsx. Он будет служить родительским компонентом для формы заказа.

В конечном итоге мы хотим использовать данные о продуктах, которые уже предоставлены для страницы, поэтому нам не нужно вручную вводить идентификаторы продуктов или включать прямые ссылки на изображения. Наш компонент будет использовать массив для продуктов в состоянии компонента, который будет обновляться после того, как данные о продукте будут введены в качестве реквизита.

bulk-order-form.jsx

import React, { Component } from “react”;
export default class BulkOrderForm extends Component {
    constructor(props) {
        super(props);
        
        this.state = {
            products: []
        }
    }
    render() {
        return <div>Bulk order form placeholder</div>
    }
}

У нас есть базовая основа, настроенная для того, чтобы действительно начать работу над нашим приложением, но по-прежнему нет ничего, что можно было бы показать при локальном запуске темы в интерфейсе командной строки Stencil и просмотре страниц категорий в браузере.

Чтобы наше приложение React появилось в магазине, мы должны создать функцию для его инициализации в assets/js/app.js.

Во-первых, давайте импортируем React, ReactDOM и сам компонент BulkOrderForm:

app.js

import React from ‘react’;
import ReactDOM from ‘react-dom’;
import BulkOrderForm from ‘./bulk-order-form/bulk-order-form’;

Внизу файла напишем нашу функцию:

app.js

window.initBulkOrderForm = function initBulkOrderForm(productData) {
    ReactDOM.render(
    React.createElement(BulkOrderForm, productData, null),
    document.getElementById(‘bulk-order-form’)
    );
};

Параметр productData будет передан в компонент BulkOrderForm как реквизит. Когда мы вызываем эту функцию в нашем файле настраиваемого шаблона, мы хотим добавить в категорию товары. Вернемся к файлу макета.

Нам нужно получить данные о товарах способом, доступным для нашего JavaScript. Обычно в шаблоне категории используются помощники Handlebars для обработки данных о продуктах и ​​их передачи в такие компоненты, как карточки продуктов. Однако нам нужен способ получить эти данные в нашем приложении React вне файла шаблона на основе Handlebars. Для этого мы можем комбинировать специальные помощники BigCommerce inject и pluck.

Подробнее о помощниках inject и pluck:

Https://developer.bigcommerce.com/stencil-docs/handlebars-syntax-and-helpers/handlebars-helpers-reference/injection-helpers/injection-helpers#handlebars_inject-and-jscontext

Https://developer.bigcommerce.com/stencil-docs/handlebars-syntax-and-helpers/handlebars-helpers-reference/array-helpers/custom-array-helpers#handlebars_pluck

Добавьте следующее в конце файла после {{> layout/base}}

bulk-order-form.html

{{inject “productIds” (pluck category.products “id”)}}
{{inject “productNames” (pluck category.products “name”)}}
{{inject “productImages” (pluck category.products “image”)}}
{{inject “productPrices” (pluck category.products “price”)}}
<script>
const pageContext = JSON.parse({{jsContext}});
let productData = [];
pageContext['productIds'].forEach((id, index) => {
  productData.push({
    id: id,
    name: pageContext['productNames'][index],
    image: pageContext['productImages'][index].data.replace('{:size}', '100x100'),
    price: pageContext['productPrices'][index].without_tax.formatted,
    quantity: 0
    })
});
window.initBulkOrderForm(productData)
</script>

Помощник inject создаст свойство для объекта jsContext со значениями, которые мы запрашиваем в помощнике pluck. В этом случае мы получаем идентификатор продукта, название продукта, изображение продукта и цену продукта. Нам также необходимо передать количество по умолчанию, равное 0 для каждого продукта.

Данные о товарах, которые мы передаем в формы оптовых заказов, создаются после циклического перебора идентификаторов товаров в jsContext. Все свойства продукта вводятся в jsContext в одном и том же порядке, поэтому мы можем получить их в рамках одной итерации.

Обратите внимание на замену строки на productImages? Это связано с тем, что изображения возвращаются с {:size}, включенным в путь к изображению. Обычно это заполняется значениями размера, указанными в конфигурации темы, когда изображения передаются помощнику getImage, но в нашей ситуации он просто прерывает URL-адрес изображения. Поскольку мы хотим включить несколько продуктов в форму оптового заказа, мы передаем 100x100 в URL-адрес изображения, который автоматически возвращает копию изображения продукта в размере 100x100 пикселей. Вы также можете настроить это по своему усмотрению.

Мы действительно близки к тому, чтобы впервые увидеть наше приложение React по этой теме. Теперь нам просто нужно обновить наш .stencil файл в каталоге темы, чтобы CLI знал, что нужно использовать наш файл пользовательского макета для категории.

Если вы не видите .stencil файл в каталоге своей темы, обязательно запустите команду stencil init в своем терминале. В результате будет создан файл с именем .stencil, содержащий информацию о подключении к вашему магазину. В документации для разработчиков BigCommerce это подробно описано: https://developer.bigcommerce.com/stencil-docs/template-files/custom-templates/authoring-testing-uploading-custom-templates#authoring-testing-uploading_local-mapping

Включите в свой .stencil файл следующее:

.stencil

“customLayouts”: {
 “brand”: {},
 “category”: {
 “bulk-order-form.html”: “/url-of-your-category-with-simple-products”
 },
 “page”: {},
 “product”: {}
 }

На этом этапе выполнение команды the stencil start должно показать, что приложение React запущено на странице выбранной вами категории.

Мы подтвердили, что наше приложение React успешно внедрено на страницу! Давайте загрузим продукты.

Отображение сведений о продукте

Мы уже запрашиваем данные о продукте и передаем их в качестве реквизита в функции initBulkOrderForm внутри нашего файла макета bulk-order-form.html. Теперь нам нужно использовать их для обновления массива продуктов в состоянии нашего компонента в bulk-order-form.jsx.

Давайте позаботимся об этом, используя метод жизненного цикла React componentDidMount. Добавьте в bulk-order-form.jsx следующее:

bulk-order-form.jsx

componentDidMount() {
    let keys = Object.keys(this.props);
    let categoryProducts = []
    keys.forEach(key => {
    key != “children” ? categoryProducts.push(this.props[key]) : null
    });
    this.setState({products: categoryProducts})
}

Когда данные о продукте передаются как реквизиты, они становятся объектом со свойством children, которое нам не нужно в состоянии нашего компонента. Этот код выполняет итерацию по всем ключам и помещает только фактические данные о продукте в новый массив, который мы затем устанавливаем в состояние после монтирования компонента.

Вы можете увидеть обновление состояния, если включите console.log в метод рендеринга.

bulk-order-form.jsx

render() {
    console.log(this.state)
    return <div>Bulk order form placeholder</div>
}

Все идет нормально. Теперь нам нужно превратить эти данные в визуальный контент, видимый на странице. Мы собираемся создать новый компонент, который будет принимать данные о товарах и помогать отображать их на странице.

Создайте новый файл в каталоге assets/js/bulk-order-form с именем product-group.jsx. Наша форма оптового заказа будет иметь структуру, подобную таблице, со столбцами для изображения продукта, названия, цены и количества. Состояние продукта будет управляться родительским компонентом, поэтому мы можем сделать ProductGroup функциональный компонент без сохранения состояния.

product-group.jsx

import React from “react”;
const ProductGroup = (props) => {
 return (
 <div className=”bulk-form-field”>
 <div className=”bulk-form-row”>
 <div className=”bulk-form-col”></div>
 <div className=”bulk-form-col”><strong>Product</strong></div>
 <div className=”bulk-form-col”><strong>Price</strong></div>
 <div className=”bulk-form-col”><strong>Quantity</strong></div>
 </div>
 
 <div className=”bulk-form-row”>
 <div className=”bulk-form-col message”></div>
 <button className=”button button--primary bulk-add-cart”>Add to Cart</button>
 </div>
 </div>
 )
}
export default ProductGroup;

Чтобы упорядочить наши столбцы и строки, мы воспользуемся сеткой CSS и добавим легкий стиль. Создайте в assets/js/bulk-order-form файл с именем bulk-order-form.css. Мы включим следующие стили, которые вы, конечно, можете изменить по своему усмотрению:

bulk-order-form.css

#bulk-order-form {
 margin: 0 auto;
 width: 70%;
}
.bulk-form-field {
 display: grid;
 text-align: center;
}
.bulk-form-row {
 display: grid;
 grid-template-columns: 100px repeat(3, 1fr);
 grid-column-gap: 8px;
}
.bulk-form-col {
 padding: 16px;
 display: flex;
 flex-direction: column;
 justify-content: center;
}
.bulk-product-image {
 max-height: 100px;
 width: auto;
 box-shadow: 1px 2px 1px rgba(0, 0, 0, 0.25);
}
.bulk-add-cart {
 width: 180px;
 margin: 0 auto;
 grid-column: 4;
}
.message {
 position: absolute;
 padding: 0;
 margin: 10px;
}

Теперь мы можем импортировать таблицу стилей и компонент ProductGroup в bulk-order-form.jsx:

bulk-order-form.jsx

import ProductGroup from “./product-group”;
import “./bulk-order-form.css”;

В методе рендеринга в bulk-order-form.jsx мы извлекаем наш заполнитель и передаем компонент ProductGroup:

bulk-order-form.jsx

render() {
    return (
        <div>
        <ProductGroup />
        </div>
    )
}

Теперь вы должны увидеть кнопку «Добавить в корзину» с 3 заголовками столбцов для продукта, цены и количества.

Затем нам нужно передать данные о продуктах в компонент ProductGroup и сопоставить продукты с отдельными строками.

Сначала мы передадим данные о продукте в состоянии в качестве реквизита в ProductGroup:

bulk-order-form.jsx

render() {
    return (
        <div>
        <ProductGroup 
          products={this.state.products}
        />
        </div>
    )
}

Теперь, когда данные о продукте переданы в ProductGroup в качестве свойств, нам нужно сопоставить эти данные с несколькими компонентами, представляющими отдельные строки продуктов в форме.

Нам понадобится новый компонент, поэтому создайте в assets/js/bulk-order-form новый файл с именем product.jsx.

Этот компонент будет принимать данные отдельного продукта в качестве реквизита и возвращать строку.

product.jsx

import React from “react”;
const Product = (props) => {
    const {name, image, price, quantity} = props;
 
    return (
        <div className=”bulk-form-row”>
        <div className=”bulk-form-col”>
            <img src={image} className=”bulk-product-image”/>
        </div>
        <div className=”bulk-form-col”>{name}</div>
        <div className=”bulk-form-col”>{price}</div>
        <div className=”bulk-form-col”>{quantity}</div>
    </div>
    )
}
export default Product;

Возвращаясь к product-group.jsx, мы можем сопоставить свойства и передать их нескольким Product компонентам. Сначала нам нужно импортировать компонент Product, а затем написать функцию, которая возвращает результат сопоставления данных продукта. Мы назовем результат productRows, а затем включим его после первого div с классом bulk-form-row. Каждый Product компонент возвращает другой bulk-form-row, который будет отображаться под заголовками.

product-group.jsx

import React from “react”;
import Product from “./product”;
const ProductGroup = (props) => {
    const productRows = props.products.map((product, index) => {
      return (
        <Product 
          key={index}
          name={product.name}
          image={product.image}
          price={product.price}
          quantity={product.quantity}
        />
      )
    });
    return (
    <div className=”bulk-form-field”>
    <div className=”bulk-form-row”>
    <div className=”bulk-form-col”></div>
    <div className=”bulk-form-col”><strong>Product</strong></div>
    <div className=”bulk-form-col”><strong>Price</strong></div>
    <div className=”bulk-form-col”><strong>Quantity</strong></div>
    </div>
    {productRows}
    <div className=”bulk-form-row”>
    <div className=”bulk-form-col message”></div>
    <button className=”button button--primary bulk-add-cart”>Add to Cart</button>
    </div>
    </div>
    )
}
export default ProductGroup;

Так выглядит немного лучше! У нас есть изображения продуктов, названия, цены и количественное значение для заполнения на странице. Теперь нам нужно добавить функции для регулировки количества и, в конечном итоге, добавить товары в корзину.

Обновление количества продуктов

В bulk-order-form.jsx мы напишем функцию, которая будет принимать количество и идентификатор продукта, позволяя пользователю обновлять количество в состоянии нашего компонента для конкретного продукта. Внутри конструктора добавьте следующий код:

bulk-order-form.jsx

this.updateQuantity = (quantity, id) => {
    const products = [...this.state.products];
    quantity = parseInt(quantity);
    if (quantity >= 0) {
        products.forEach(product => {
        product.id === id ? product.quantity = quantity : null
        });
    } else {
        products.forEach(product => {
        product.id === id ? product.quantity = 0 : null
        });
    }
    this.setState({
        products: products
    });
}

Сначала мы копируем массив объектов продукта из состояния, чтобы мы могли обновить вложенные значения количества в нашей копии, а затем применяем скопированный массив к состоянию. Это необходимо, поскольку вложенные значения в состоянии не могут быть обновлены напрямую.

Количество приводится к целому числу, поскольку мы будем использовать текстовое поле для полей количества, но нам нужно будет отправить количество как целое число в полезной нагрузке, чтобы создать корзину с использованием API витрины. Также существует условие, чтобы количество не могло быть уменьшено ниже 0, поскольку мы не хотим, чтобы пользователи могли устанавливать отрицательное значение количества.

Нам нужно передать функцию updateQuantity в наши отдельные строки продуктов. Давайте передадим это в качестве реквизита ProductGroup:

bulk-order-form.jsx

render() {
    return (
    <div>
    <ProductGroup 
      products={this.state.products}
      updateQuantity={this.updateQuantity}
    />
    </div>
    )
}

Затем в product-group.jsx мы можем передать функцию updateQuantity нашим Product компонентам. Нам также необходимо передать идентификатор продукта, чтобы мы могли привязать изменения количества к конкретному продукту.

product-group.jsx

const productRows = props.products.map((product, index) => {
    return (
        <Product 
          key={index}
          name={product.name}
          product_id={product.id}
          image={product.image}
          price={product.price}
          quantity={product.quantity}
          updateQuantity={props.updateQuantity}
        />
    )
});

В компоненте Product нам нужно передать функцию updateQuantity и product_id реквизиты. Мы также собираемся добавить стрелки вверх и вниз, а также поле для ввода текста, чтобы покупатель мог вводить количество вручную или увеличивать количество по одному. Классы кнопок и значки стрелок заимствованы из темы Cornerstone по умолчанию, поэтому они выглядят как селектор количества, который вы обычно видите на любой странице отдельного продукта.

product.jsx

import React from "react";
const Product = (props) => {
    const {name, product_id, image, price, quantity, updateQuantity} = props;
    return (
        <div className="bulk-form-row">
        <div className="bulk-form-col">
        <img src={image} className="bulk-product-image"/>
        </div>
        <div className="bulk-form-col">{name}</div>
        <div className="bulk-form-col">{price}</div>
        <div className="bulk-form-col">
        <div className="form-increment">
        <button className="button button - icon" onClick={() => updateQuantity(quantity - 1, product_id)}>
        <span className="is-srOnly">Decrease Quantity:</span>
        <i className="icon" aria-hidden="true">
        <svg viewBox="0 0 24 24" id="icon-keyboard-arrow-down" width="100%" height="100%"><path d="M7.41 7.84L12 12.42l4.59–4.58L18 9.25l-6 6–6–6z"></path></svg>
        </i>
        </button>
        <input type="text" className="form-input form-input - incrementTotal" value={quantity} onChange={(e) => {updateQuantity(e.target.value, product_id)}}></input>
        <button className="button button - icon" onClick={() => updateQuantity(quantity + 1, product_id)}>
        <span className="is-srOnly">Increase Quantity:</span>
        <i className="icon" aria-hidden="true">
        <svg viewBox="0 0 24 24" id="icon-keyboard-arrow-up" width="100%" height="100%"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6–6–6 6z"></path></svg>
        </i>
        </button>
        </div>
        </div>
        </div>
    )
}
export default Product;

Обратите внимание, что мы передаем функцию updateQuantity обработчику onClick кнопок увеличения и уменьшения. Эта функция должна принимать количество и идентификатор продукта, поэтому мы передаем текущее количество плюс или минус 1, а также свойство идентификатора продукта компонента Product.

Количество теперь обновляется, когда вы нажимаете стрелки вверх или вниз! Если вы все еще используете console.log(this.state) в методе рендеринга в bulk-order-form.jsx, вы должны увидеть обновление количества продукта в состоянии.

У нас есть все наши продукты, и количество регулируется. Теперь нам нужно добавить реальный функционал к кнопке «Добавить в корзину». Здесь на помощь приходит API корзины витрины.

Использование API корзины витрины для добавления товаров в корзину

Вернемся к bulk-order-form.jsx. Добавьте в конструктор новый метод с именем addToCart. Для полезной нагрузки API корзины требуется массив объектов продукта, включая идентификатор и количество продукта. Поскольку свойство называется lineItems, мы назовем нашу переменную таким же образом. В приведенном ниже коде продукты, хранящиеся в состоянии, отображаются в новый массив, если у них есть хотя бы 1 количество. Отображение массива таким образом вернет то же количество значений, что и исходный массив, но с null значениями, присутствующими во всех индексах, где условие равно false. Чтобы очистить это, мы также применяем фильтр, чтобы вернуть массив, свободный от null значений.

bulk-order-form.jsx

this.addToCart = () => {
    const lineItems = this.state.products.map(product => {
      if (product.quantity > 0) {
        return {
            productId: product.id,
            quantity: product.quantity
        }
      }
    }).filter(item => item != null);
}

Прежде чем что-либо делать, нам нужно проверить, есть ли у lineItems продуктов какое-либо количество. Мы можем настроить условие на основе длины массива lineItems, а затем сделать запрос к API корзины, чтобы проверить, существует ли уже корзина или нам нужно создать новую.

bulk-order-form.jsx

this.addToCart = () => {
    const lineItems = this.state.products.map(product => {
        if (product.quantity > 0) {
            return {
                productId: product.id,
                quantity: product.quantity
            }
        }
    }).filter(item => item != null);
    if (lineItems.length > 0) {
        fetch(`/api/storefront/cart`)
        .then(response => response.json())
        .then(cart => console.log(cart))
    }
}

Если в lineItems есть какие-либо значения, мы делаем запрос на получение существующей корзины на витрине. Если корзины не существует, этот запрос API возвращает пустой массив. В противном случае мы можем извлечь уникальный идентификатор корзины из ответа.

Мы можем назначить эту функцию обработчику onClick на кнопке добавления в корзину, чтобы проверить, как будут отображаться результаты из API корзины. Сначала передайте функцию addToCart как опору в ProductGroup:

bulk-order-form.jsx

render() {
    return (
        <div>
        <ProductGroup 
          products={this.state.products}
          updateQuantity={this.updateQuantity}
          addToCart={this.addToCart}
        />
        </div>
    )
}

Затем в product-group.jsx мы можем использовать функцию addToCart, переданную как опору, и прикрепить ее к обработчику onClick кнопки добавления в корзину:

product-group.jsx

return (
 <div className=”bulk-form-field”>
 <div className=”bulk-form-row”>
 <div className=”bulk-form-col”></div>
 <div className=”bulk-form-col”><strong>Product</strong></div>
 <div className=”bulk-form-col”><strong>Price</strong></div>
 <div className=”bulk-form-col”><strong>Quantity</strong></div>
 </div>
 { productRows }
 <div className=”bulk-form-row”>
 <div className=”bulk-form-col message”></div>
 <button className=”button button--primary bulk-add-cart” onClick={props.addToCart}>Add to Cart</button>
 </div>
 </div>
 )

Когда вы нажмете кнопку «Добавить в корзину», вы должны увидеть результаты запроса API корзины в консоли браузера. Если в данный момент корзины нет, вы должны увидеть пустой массив. В противном случае вы можете развернуть возвращенный массив, чтобы увидеть сведения о тележке, включая идентификатор тележки.

Нет корзины

Корзина существует

Мы напишем две отдельные функции для обработки результатов: создание новой корзины с выбором пользователя или добавление их в существующую корзину. Вместо того, чтобы просто регистрировать результат запроса API корзины, мы берем результаты и определяем, вызывать ли ту или иную функцию. Мы также будем регистрировать любые ошибки, если возникнут проблемы с добавлением товаров в корзину.

bulk-order-form.jsx

this.addToCart = () => {
    const lineItems = this.state.products.map(product => {
        if (product.quantity > 0) {
            return {
                productId: product.id,
                quantity: product.quantity
            }
        }
    }).filter(item => item != null);
if (lineItems.length > 0) {
        this.setState({message: "Adding items to your cart..."});
fetch(`/api/storefront/cart`)
        .then(response => response.json())
        .then(cart => {
            if(cart.length > 0) {
                return addToExistingCart(cart[0].id)
            } else {
                return createNewCart()
            }
        })
        .catch(err => console.log(err))
    }
async function createNewCart() {
        const response = await fetch(`/api/storefront/carts`, {
            credentials: "include",
            method: "POST",
            body: JSON.stringify({ lineItems: lineItems })
        });
        const data = await response.json();
        if (!response.ok) {
            return Promise.reject("There was an issue adding items to your cart. Please try again.")
        } else {
            console.log(data);
        }
    }
async function addToExistingCart(cart_id) {
        const response = await fetch(`/api/storefront/carts/${cart_id}/items`, {
            credentials: "include",
            method: "POST",
            body: JSON.stringify({ lineItems: lineItems })
        });
        const data = await response.json();
        if (!response.ok) {
            return Promise.reject("There was an issue adding items to your cart. Please try again.")
        } else {
            console.log(data);
        }
    }
}

Если корзина не возвращается, мы можем сделать POST-запрос на /api/storefront/carts конечную точку с помощью нашего lineItems, чтобы создать новую корзину с выбором пользователя. В противном случае мы передаем идентификатор корзины в качестве параметра /api/storefront/carts/{cart_id}/items с той же полезной нагрузкой. Если какой-либо из запросов завершится ошибкой, мы регистрируем ошибку в консоли браузера. В противном случае мы регистрируем ответ от API корзины, который должен включать все текущие сведения о корзине.

К настоящему времени вы должны иметь возможность добавлять в корзину несколько товаров одновременно. Однако пользовательского опыта немного не хватает; нет видимых отзывов о добавлении элементов. Мы должны сообщить покупателям, что товары добавляются в корзину, а затем перенаправить их на страницу корзины после завершения запроса API.

Улучшение пользовательского опыта

В строке кнопки добавления в корзину есть пустой столбец, в котором мы можем отображать сообщения для покупателей. Давайте добавим свойство сообщения к состоянию, чтобы мы могли обновлять сообщения при необходимости. В методе addToCart в bulk-order-form.jsx мы можем установить сообщение, указывающее, что товары добавляются в корзину. Если для какого-либо продукта количество не указано, мы должны посоветовать покупателю установить количество.

Мы также можем обработать перенаправление, добавив еще один обработчик .then в цепочку обещаний после завершения запросов API.

bulk-order-form.jsx

if (lineItems.length > 0) {
    this.setState({message: "Adding items to your cart..."});

    fetch(`/api/storefront/cart`)
    .then(response => response.json())
    .then(cart => {
        if(cart.length > 0) {
            return addToExistingCart(cart[0].id)
        } else {
            return createNewCart()
        }
    })
    .then(() => window.location = "/cart.php")
    .catch(err => console.log(err))
} else {
    this.setState({message: "Please select a quantity for at least 1 item"});
}

Поскольку сейчас мы устанавливаем сообщение в состоянии, мы можем передать это сообщение в качестве свойств ProductGroup и отобразить его в столбце слева от кнопки «Добавить в корзину».

bulk-order-form.jsx

render() {
    return (
        <div>
            <ProductGroup 
                products={this.state.products}
                updateQuantity={this.updateQuantity}
                addToCart={this.addToCart}
                message={this.state.message}/>
        </div>
    )
}

product-group-jsx

return (
    <div className="bulk-form-field">
        <div className="bulk-form-row">
            <div className="bulk-form-col"></div>
            <div className="bulk-form-col"><strong>Product</strong></div>
            <div className="bulk-form-col"><strong>Price</strong></div>
            <div className="bulk-form-col"><strong>Quantity</strong></div>
        </div>
        { productRows }
        <div className="bulk-form-row">
            <div className="bulk-form-col message"><strong>{props.message}</strong></div>
            <button className="button button--primary bulk-add-cart" onClick={props.addToCart}>Add to Cart</button>
        </div>
    </div>
)

Мы можем пойти еще дальше и отключить кнопку «Добавить в корзину», чтобы избежать многократного нажатия кнопки «Добавить в корзину». В методе addToCart мы можем выбрать кнопку и впоследствии настроить, будет ли она отключена.

bulk-order-form.jsx

this.addToCart = (e) => {
 const button = e.target;

if (lineItems.length > 0) {
 button.disabled = true;

Если по какой-либо причине добавить в корзину не удалось, мы также можем снова включить кнопку. Вместо того, чтобы записывать сообщение об ошибке в консоль, мы можем передать его как сообщение.

bulk-order-form.jsx

fetch(`/api/storefront/cart`)
    .then(cart => cart.json())
    .then(data => {
        if(data.length > 0) {
            return addToExistingCart(data[0].id)
        } else {
            return createNewCart()
        }
    })
    .then(() => window.location = "/cart.php")
    .catch(err => handleFailedAddToCart(err, this, button))

Вместо console.log мы передаем сообщение об ошибке функции с именем handleFailedAddToCart, которая находится в рамках метода addToCart.

function handleFailedAddToCart(message, self,  button) {
    self.setState({
        message: message
    });
    return button.disabled = false;
}

Мы также можем удалить сообщения об ошибках, если покупатель решит изменить количество товара. В методе updateQuantity мы можем сбросить сообщение.

bulk-order-form.jsx

this.updateQuantity = (quantity, id) => {
    const products = [...this.state.products];
    quantity = parseInt(quantity);
    if (quantity >= 0) {
        products.forEach(product => {
            product.id === id ? product.quantity = quantity : null
        });
    } else {
        products.forEach(product => {
            product.id === id ? product.quantity = 0 : null
        });
    }
    this.setState({
        products: products,
        message: ""
    });
}

Заключение

Теперь у нас есть рабочая форма, позволяющая покупателям добавлять товары в корзину оптом! В следующем месяце во второй части этого руководства я расскажу, как создать форму массового заказа для страниц продуктов, чтобы одновременно добавлять в корзину несколько вариантов.

Сообщите нам свое мнение. Вы нашли это полезным, или мы что-то упустили? Оставьте нам комментарий или напишите нам в Твиттере @BigCommerceDevs!