commercetools-8.x-1.2-alpha1/modules/commercetools_decoupled/js/organisms/CartForm.js

modules/commercetools_decoupled/js/organisms/CartForm.js
class CartForm extends HTMLElement {
  constructor() {
    super();
    this.actionHandler = this.actionHandler.bind(this);
    this.maxItems = window.drupalSettings.commercetoolsDecoupled.maxItems;
  }

  connectedCallback() {
    this.renderForm();
  }

  async actionHandler(action, data) {
    window.clearTimeout(this.delay);
    this.delay = window.setTimeout(async () => {
      this.isLoading = true;
      this.renderForm();

      const actions = [];
      let successMessage;
      switch (action) {
        case 'plus-quantity':
        case 'minus-quantity':
          actions.push({
            changeLineItemQuantity: {
              lineItemId: data.lineItem.id,
              quantity: data.quantity,
            },
          });
          break;

        case 'add-discount':
          actions.push({ addDiscountCode: { code: data.code } });
          successMessage = () => {
            new Drupal.Message().add(
              Drupal.t("Successfully redeemed code: '@code'", {
                '@code': data.code,
              }),
            );
          };
          break;

        case 'remove-discount':
          actions.push({
            removeDiscountCode: {
              discountCode: { typeId: 'discount-code', id: data.id },
            },
          });
          successMessage = () => {
            new Drupal.Message().add(
              Drupal.t(
                "The following code has been removed from the cart: '@code'",
                { '@code': data.code },
              ),
            );
          };
          break;

        default:
          actions.push({ removeLineItem: { lineItemId: data.lineItem.id } });
          break;
      }

      try {
        const result = await window.commercetools.updateCart({
          id: '[current_cart:id]',
          version: '[current_cart:version]',
          actions,
          allowErrors: true,
        });
        if (result?.errors) {
          if (action === 'add-discount') {
            CartForm.handleAddDiscountErrors(result.errors, data.code);
          } else {
            result.errors.forEach((error) => {
              new Drupal.Message().add(error, { type: 'warning' });
            });
          }
        } else {
          this.cart = result;
          if (typeof successMessage === 'function') {
            successMessage();
          } else {
            new Drupal.Message().add(successMessage);
          }
        }
      } catch (e) {
        // If failed, show current cart.
      }
      document.dispatchEvent(
        new CustomEvent('commercetoolsDecoupledCartUpdated', {
          detail: {
            totalItems: this.cart.lineItems.length,
          },
        }),
      );
      this.isLoading = false;
      this.renderForm();
    }, 1000);
  }

  generateSummaryContent() {
    const checkoutPath =
      window.drupalSettings.commercetoolsDecoupled?.checkoutPath;
    if (!this.isSummaryCart) {
      const discountBlock = this.isLoading ? '' : this.discountPart();

      return `
        <div class="col-lg-3 bg-body-tertiary">
          <div class="p-5">
            <h3 class="fw-bold mb-5">${Drupal.t('Summary')}</h3>
            ${discountBlock}
            <hr class="my-4">
            <div class="d-flex justify-content-between mb-5">
              <p class='h6 fw-bold'>${Drupal.t('Total price')}</p>
              <p class='h6 fw-bold'>${this.isLoading ? CartForm.placeholder(this.isLoading) : this.cart?.totalPrice?.localizedPrice || ''}</p>
            </div>
            ${
              this.isLoading
                ? ''
                : `
              <div class="form-actions form-wrapper mb-3">
                <a href="${!checkoutPath ? '#' : Drupal.url(checkoutPath.substring(1))}" class="btn btn-primary ${!checkoutPath ? 'disabled' : ''}">${Drupal.t('Checkout')}</a>
              </div>
            `
            }
          </div>
        </div>`;
    }
    return '';
  }

  renderForm() {
    const summaryContent = this.generateSummaryContent();
    this.innerHTML = `
      <form class="commercetools-content-cart-form">
        <section class="pb-4">
          <div class="overflow-hidden">
            <section class="w-100">
              <div class="row">
                <div class="col-12">
                  <div class="card card-registration card-registration-2 overflow-hidden border-0">
                    <div class="card-body p-0">
                      <div class="row g-0">
                        <div class="${this.isSummaryCart ? 'col-lg-12' : 'col-lg-9'}">
                          <div class="${this.isSummaryCart ? 'pt-2' : 'pt-5 pe-5'}">
                            ${
                              !this.isSummaryCart
                                ? `<div class="d-flex justify-content-between align-items-center mb-5">
                                      <h3 class="fw-bold mb-0">${Drupal.t('Cart items')}</h3>
                                      <h4 class="mb-0 text-muted fs-6">${this.isLoading ? CartForm.placeholder(this.isLoading) : Drupal.formatPlural(this.cart?.totalLineItemQuantity || 0, '1 item', '@count items')}</h4>
                                  </div>`
                                : ''
                            }
                            ${
                              !this.isLoading &&
                              (!this.cart?.lineItems ||
                                this.cart?.lineItems?.length === 0)
                                ? `<div class="cart-empty-text">${Drupal.t('Your cart is empty.')}</div>`
                                : `<div class="cart-line-items"></div>`
                            }
                          </div>
                        </div>
                        ${summaryContent}
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </section>
          </div>
        </section>
      </form>
    `;
    if (!!this.cart?.lineItems?.length || this.isLoading) {
      const LineItems = document.createElement('ct-line-items');
      LineItems.lineStyle = this.isSummaryCart ? 'summary' : 'form';
      LineItems.actionHandler = this.actionHandler;
      LineItems.isLoading = this.isLoading;
      const visibleItems = this.isSummaryCart
        ? this.cart.lineItems.slice(0, this.maxItems)
        : this.cart.lineItems;
      LineItems.lineItems = visibleItems;
      this.querySelector('.cart-line-items').append(LineItems);
    }
    if (!this.isLoading && !this.isSummaryCart) {
      this.querySelector('.discounts-inputs').appendChild(
        this.createDiscountInputToCart(),
      );
      this.querySelector('.discounts-codes').appendChild(
        this.createDiscountCodesButtons(),
      );
    }
  }

  discountPart() {
    let discountPart = `<div class="discounts-inputs"></div><div class="discounts-codes"></div>`;

    if (this.cart.isDiscounted) {
      discountPart += `
        <div class="d-flex justify-content-between">
          <h6>${Drupal.t('Subtotal')}</h6>
          <h6>${this.cart.originalTotalPrice.localizedPrice}</h6>
        </div>
        ${
          this.cart.discount?.centAmount > 0
            ? `
            <div class="d-flex justify-content-between text-success">
              <h6>${Drupal.t('Discount')}</h6>
              <h6>- ${this.cart.discount.localizedPrice}</h6>
            </div>
          `
            : ''
        }
        ${(this.cart.discountCodes || [])
          .map(
            (dc) => `
          <div class="d-flex justify-content-between text-success">
            <h6>${dc.code}</h6>
            <h6>- ${dc.amount.localizedPrice}</h6>
          </div>
        `,
          )
          .join('')}
      `;
    }

    return discountPart;
  }

  createDiscountInputToCart() {
    const template = document.createElement('template');
    template.innerHTML = `
    <div class="js-form-wrapper form-wrapper mb-3">
      <div class="js-form-item js-form-type-textfield form-type-textfield mb-3">
        <label for="edit-discount-code">${Drupal.t('Your discount code (optional)')}</label>
        <input type="text" id="edit-discount-code" name="discount_code" class="form-control"/>
      </div>
      <div class="form-actions mb-3">
        <button type="button" class="btn btn-primary disabled" data-cart-action="add-discount">
          ${Drupal.t('Redeem Code')}
        </button>
      </div>
    </div>
  `.trim();

    const formItem = template.content.firstElementChild;
    const input = formItem.querySelector('input');
    const button = formItem.querySelector('button');

    input.addEventListener('input', () => {
      const isEmpty = input.value.trim() === '';
      button.classList.toggle('disabled', isEmpty);
      button.disabled = isEmpty;
    });
    input.addEventListener('keydown', (e) => {
      if (e.key === 'Enter') {
        e.preventDefault();
        button.click();
      }
    });

    const onMultiple = (target, events, handler) =>
      events.forEach((evt) => target.addEventListener(evt, handler));
    onMultiple(button, ['click', 'pointerup', 'keydown'], (e) => {
      if (input.value.trim() === '') {
        return;
      }
      const action = button.dataset.cartAction;
      const data = { code: input.value.trim() };
      this.actionHandler(action, data);
    });

    return formItem;
  }

  createDiscountCodesButtons() {
    const codes = this.cart.discountCodes;
    if (!Array.isArray(codes) || codes.length === 0) {
      return document.createElement('span');
    }

    const template = document.createElement('template');
    template.innerHTML = `
    <div class="d-flex flex-nowrap gap-2 mb-3">
      ${codes
        .map(
          ({ id, code, name }) => `
        <div class="bg-success p-1 d-flex align-items-center">
          <span>${name}</span>
          <button
            type="button"
            class="btn-close ms-2"
            aria-label="Remove"
            data-cart-action="remove-discount"
            data-id="${id}"
            data-key="${code}"
          ></button>
        </div>
      `,
        )
        .join('')}
    </div>
  `.trim();

    const wrap = template.content.firstElementChild;

    wrap.querySelectorAll('button[data-cart-action]').forEach((btn) => {
      btn.addEventListener('pointerup', (e) => {
        const { cartAction: action, id, key } = e.currentTarget.dataset;
        this.actionHandler(action, { id, key });
      });
    });

    return wrap;
  }

  static handleAddDiscountErrors(errors, code) {
    const invalidMsg = errors.find((msg) =>
      msg.toString().startsWith("The discount code '"),
    );

    if (invalidMsg) {
      new Drupal.Message().add(
        Drupal.t("The discount code '@code' was not found.", {
          '@code': code,
        }),
        { type: 'warning' },
      );
    } else {
      window.commercetools.throwCommercetoolsException();
    }
  }

  static placeholder() {
    return `<span class="placeholderify"><span style="color: initial;">${Drupal.t('Loading...')}</span></span>`;
  }
}

customElements.define('ct-cart-form', CartForm);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc