sutty-base-jekyll-theme/_packs/controllers/cart_controller.js

318 lines
7.7 KiB
JavaScript
Raw Normal View History

2021-11-22 17:51:50 +00:00
import { CartBaseController } from "./cart_base_controller";
2021-06-01 21:33:49 +00:00
/*
* Manages the cart and its contents.
*
* We need to create an order in Spree API to get a token that allows to
* make changes to it.
*
* The order contains attributes for the order and other data can be
* included, like line items, variants, payments, promotions, shipments,
* billing and shipping addresses, and the user.
*
* Variants are products added to the cart. To remove an item or change
* its quantity, a line item for the variant must be found. We store
* this information into localStorage so we don't have to make annoying
* queries to JSON:API everytime.
*/
export default class extends CartBaseController {
2021-11-22 17:51:50 +00:00
static targets = ["quantity", "subtotal", "addedQuantity"];
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
connect() {
if (!this.hasQuantityTarget) return;
2021-06-01 21:33:49 +00:00
this.change_event = this._change_event.bind(this);
2021-06-01 21:33:49 +00:00
/*
* When the quantity selector changes, we update the order to have
* that amount of items.
*
* TODO: Go back to previous amount if there's not enough.
*/
this.quantityTarget.addEventListener("change", this.change_event);
}
2021-06-01 21:33:49 +00:00
disconnect() {
this.quantityTarget.removeEventListener("change", this.change_event);
}
2021-06-01 21:33:49 +00:00
async _change_event(event) {
const quantity = event.target.value;
if (quantity < 1) return;
2021-06-01 21:33:49 +00:00
const orderToken = await this.tokenGetOrCreate();
const product = this.product;
2021-06-01 21:33:49 +00:00
if (!product) return;
2021-06-01 21:33:49 +00:00
event.target.disabled = true;
2021-06-01 21:33:49 +00:00
const response = await this.spree.cart.setQuantity(
{ orderToken },
{
line_item_id: product.line_item.id,
quantity,
include: "line_items",
2021-06-01 21:33:49 +00:00
}
);
event.target.disabled = false;
event.target.focus();
// If we're failing here it could be due to a missing order, so we
// ask the user to decide what they want to do about it
if (response.isFail()) {
this.handleFailure(response);
return;
}
2021-06-01 21:33:49 +00:00
this.cart = response;
this.subtotalUpdate();
this.counterUpdate();
await this.itemStore();
2021-06-01 21:33:49 +00:00
if (!this.hasSubtotalTarget) return;
2021-06-01 21:33:49 +00:00
this.subtotalTarget.innerText =
product.line_item.attributes.discounted_amount;
2021-06-01 21:33:49 +00:00
}
2021-11-22 17:51:50 +00:00
subtotalUpdate() {
window.dispatchEvent(new Event("cart:subtotal:update"));
2021-06-01 21:33:49 +00:00
}
/*
* Creates an order and stores the data into localStorage.
*
* @return [String]
*/
2021-11-22 17:51:50 +00:00
async cartCreate() {
const response = await this.spree.cart.create();
2021-06-01 21:33:49 +00:00
// If we fail here it's probably a server error, so we inform the
// user.
if (response.isFail()) {
2021-11-22 17:51:50 +00:00
this.handleFailure(response);
return;
2021-06-01 21:33:49 +00:00
}
2021-11-22 17:51:50 +00:00
this.cart = response;
this.storage.setItem("token", response.success().data.attributes.token);
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
return this.token;
2021-06-01 21:33:49 +00:00
}
/*
* Gets the order token and creates a cart if it doesn't exist.
*
* @return [String]
*/
2021-11-22 17:51:50 +00:00
async tokenGetOrCreate() {
let token = this.storage.getItem("token");
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
return token || (await this.cartCreate());
2021-06-01 21:33:49 +00:00
}
/*
* The variant ID is used to identify products
*
* @return [String]
*/
2021-11-22 17:51:50 +00:00
get variantId() {
return this.data.get("variantId");
2021-06-01 21:33:49 +00:00
}
2021-11-22 17:51:50 +00:00
get product() {
const product = JSON.parse(this.storage.getItem(this.storageId));
if (!product) {
2021-11-22 17:51:50 +00:00
console.error(
"El producto es nulo!",
this.storageId,
this.storage.length,
this.cart
);
}
2021-11-22 17:51:50 +00:00
return product;
2021-06-01 21:33:49 +00:00
}
/*
* Obtains the line_item_id by a variant_id by inspecting the cart and
* its included items
*
* @return [Object]
*/
2021-11-22 17:51:50 +00:00
findLineItem() {
const line_item = this.cart.included.find(
(x) =>
x.type === "line_item" &&
x.relationships.variant.data.id == this.variantId
);
return line_item || {};
2021-06-01 21:33:49 +00:00
}
2021-11-22 17:51:50 +00:00
get storageId() {
return `cart:item:${this.variantId}`;
2021-06-01 21:33:49 +00:00
}
/*
* Stores an item for later usage.
*
* @see {./order_controller.js}
*/
2021-11-22 17:51:50 +00:00
itemStore() {
this.storage.setItem(
this.storageId,
JSON.stringify({
variant_id: this.variantId,
line_item: this.findLineItem(),
image: this.data.get("image"),
title: this.data.get("title"),
url: this.data.get("url"),
stock: this.data.get("stock"),
in_stock: this.data.get("inStock"),
extra: this.data.get("extra") ? this.data.get("extra").split("|") : [],
})
);
2021-06-01 21:33:49 +00:00
}
/*
* Adds item to cart. This is meant to be used by an "Add to cart"
* button. If the item already exists in the cart it updates the
* quantity by +1.
*
* The item needs a variant ID to be added.
*/
async add(event, quantity = 1, floating_alert = true) {
2021-11-22 17:51:50 +00:00
const addedQuantity = this.addedQuantity();
if (addedQuantity > 1) quantity = addedQuantity;
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
const orderToken = await this.tokenGetOrCreate();
const response = await this.spree.cart.addItem(
{ orderToken },
{ variant_id: this.variantId, quantity, include: "line_items" }
);
2021-06-01 21:33:49 +00:00
if (response.isFail()) {
2021-11-22 17:51:50 +00:00
this.handleFailure(response);
return;
2021-06-01 21:33:49 +00:00
}
2021-11-22 17:51:50 +00:00
this.cart = response;
this.itemStore();
this.counterUpdate();
this.fireCajon();
2021-06-01 21:33:49 +00:00
if (floating_alert) {
2021-11-22 17:51:50 +00:00
const site = window.site;
const content = site.cart.added;
window.dispatchEvent(
new CustomEvent("floating:alert", { detail: { content } })
);
2021-06-01 21:33:49 +00:00
}
}
/*
* Remove the element from the cart. It contacts the API and if the
* item is removed, it removes itself from the page and the storage.
*/
2021-11-22 17:51:50 +00:00
async remove() {
const product = this.product;
2021-11-22 17:51:50 +00:00
if (!product) return;
if (!product.line_item) return;
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
const orderToken = this.token;
const response = await this.spree.cart.removeItem(
{ orderToken },
product.line_item.id,
{ include: "line_items" }
);
2021-06-01 21:33:49 +00:00
if (response.isFail()) {
2021-11-22 17:51:50 +00:00
this.handleFailure(response);
return;
2021-06-01 21:33:49 +00:00
}
2021-11-22 17:51:50 +00:00
this.cart = response;
this.storage.removeItem(this.storageId);
this.element.remove();
this.subtotalUpdate();
this.counterUpdate();
2021-06-01 21:33:49 +00:00
}
/*
* Shows variants
*/
2021-11-22 17:51:50 +00:00
async variants() {
const template = "variants";
2021-06-01 21:33:49 +00:00
const data = {
product: {
2021-11-22 17:51:50 +00:00
variant_id: this.data.get("variantId"),
digital_variant_id: this.data.get("digitalVariantId"),
image: this.data.get("image"),
title: this.data.get("title"),
price: this.data.get("price"),
digital_price: this.data.get("digitalPrice"),
in_stock: this.data.get("inStock"),
extra: this.data.get("extra").split("|"),
},
};
window.dispatchEvent(
new CustomEvent("notification", { detail: { template, data } })
);
2021-06-01 21:33:49 +00:00
}
/*
* Recovers the order if something failed
*/
2021-11-22 17:51:50 +00:00
async recover() {
console.error("Recuperando pedido", this.token);
2021-06-01 21:33:49 +00:00
// Removes the failing token
2021-11-22 17:51:50 +00:00
this.storage.removeItem("token");
2021-06-01 21:33:49 +00:00
// Get a new token and cart
2021-11-22 17:51:50 +00:00
await this.tokenGetOrCreate();
2021-06-01 21:33:49 +00:00
// Stores the previous cart
2021-11-22 17:51:50 +00:00
const cart = this.cart;
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
if (!cart) return;
2021-06-01 21:33:49 +00:00
// Add previous items and their quantities to the new cart by
// mimicking user's actions
//
// XXX: We don't use forEach because it's not async
for (const variant of cart.data.relationships.variants.data) {
2021-11-22 17:51:50 +00:00
this.data.set("variantId", variant.id);
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
const product = this.product;
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
if (!product) continue;
2021-11-22 17:51:50 +00:00
this.data.set("image", product.image);
this.data.set("title", product.title);
this.data.set("extra", product.extra.join("|"));
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
await this.add(null, product.line_item.attributes.quantity, false);
2021-06-01 21:33:49 +00:00
}
}
/*
* Si le compradore aumenta la cantidad antes de agregar
*/
2021-11-22 17:51:50 +00:00
addedQuantity() {
if (!this.hasAddedQuantityTarget) return 0;
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
const addedQuantity = parseInt(this.addedQuantityTarget.value);
2021-06-01 21:33:49 +00:00
2021-11-22 17:51:50 +00:00
return isNaN(addedQuantity) ? 0 : addedQuantity;
2021-06-01 21:33:49 +00:00
}
}