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
|
|
|
|
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*/
|
2021-11-22 17:51:50 +00:00
|
|
|
this.quantityTarget.addEventListener("change", async (event) => {
|
|
|
|
const quantity = event.target.value;
|
2021-06-01 21:33:49 +00:00
|
|
|
|
|
|
|
if (quantity < 1) return;
|
|
|
|
|
2021-11-22 17:51:50 +00:00
|
|
|
const orderToken = await this.tokenGetOrCreate();
|
|
|
|
const product = this.product;
|
2021-10-27 18:32:59 +00:00
|
|
|
|
2021-11-22 17:51:50 +00:00
|
|
|
if (!product) return;
|
2021-06-01 21:33:49 +00:00
|
|
|
|
2021-11-22 17:51:50 +00:00
|
|
|
event.target.disabled = true;
|
2021-06-01 21:33:49 +00:00
|
|
|
|
2021-11-22 17:51:50 +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
|
|
|
|
2021-11-22 17:51:50 +00:00
|
|
|
event.target.disabled = false;
|
|
|
|
event.target.focus();
|
2021-06-01 21:33:49 +00:00
|
|
|
|
|
|
|
// 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()) {
|
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.subtotalUpdate();
|
|
|
|
this.counterUpdate();
|
|
|
|
await this.itemStore();
|
2021-06-01 21:33:49 +00:00
|
|
|
|
2021-11-22 17:51:50 +00:00
|
|
|
if (!this.hasSubtotalTarget) return;
|
2021-06-01 21:33:49 +00:00
|
|
|
|
2021-11-22 17:51:50 +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));
|
2021-10-27 18:32:59 +00:00
|
|
|
|
|
|
|
if (!product) {
|
2021-11-22 17:51:50 +00:00
|
|
|
console.error(
|
|
|
|
"El producto es nulo!",
|
|
|
|
this.storageId,
|
|
|
|
this.storage.length,
|
|
|
|
this.cart
|
|
|
|
);
|
2021-10-27 18:32:59 +00:00
|
|
|
}
|
|
|
|
|
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-10-27 18:32:59 +00:00
|
|
|
|
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-10-27 18:32:59 +00:00
|
|
|
|
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
|
|
|
|
2021-10-27 18:32:59 +00:00
|
|
|
// Get a new token and cart
|
2021-11-22 17:51:50 +00:00
|
|
|
await this.tokenGetOrCreate();
|
2021-10-27 18:32:59 +00:00
|
|
|
|
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-10-27 18:32:59 +00:00
|
|
|
|
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
|
|
|
}
|
|
|
|
}
|