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

283 lines
7.3 KiB
JavaScript
Raw Normal View History

2021-06-01 21:33:49 +00:00
import { CartBaseController } from './cart_base_controller'
/*
* 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 {
static targets = [ 'quantity', 'subtotal', 'addedQuantity' ]
connect () {
if (!this.hasQuantityTarget) return
/*
* 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', async (event) => {
const quantity = event.target.value
if (quantity < 1) return;
const orderToken = await this.tokenGetOrCreate()
const product = this.product
if (!product) return
2021-06-01 21:33:49 +00:00
event.target.disabled = true
const response = await this.spree.cart.setQuantity({ orderToken }, {
line_item_id: product.line_item.id,
2021-06-01 21:33:49 +00:00
quantity,
include: 'line_items'
})
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
}
this.cart = response
this.subtotalUpdate()
this.counterUpdate()
await this.itemStore()
if (!this.hasSubtotalTarget) return
this.subtotalTarget.innerText = product.line_item.attributes.discounted_amount
2021-06-01 21:33:49 +00:00
})
}
subtotalUpdate () {
window.dispatchEvent(new Event('cart:subtotal:update'))
}
/*
* Creates an order and stores the data into localStorage.
*
* @return [String]
*/
async cartCreate () {
const response = await this.spree.cart.create()
// If we fail here it's probably a server error, so we inform the
// user.
if (response.isFail()) {
this.handleFailure(response)
return
}
this.cart = response
this.storage.setItem('token', response.success().data.attributes.token)
return this.token
}
/*
* Gets the order token and creates a cart if it doesn't exist.
*
* @return [String]
*/
async tokenGetOrCreate () {
let token = this.storage.getItem('token')
return token || await this.cartCreate()
}
/*
* The variant ID is used to identify products
*
* @return [String]
*/
get variantId () {
return this.data.get('variantId')
}
get product () {
const product = JSON.parse(this.storage.getItem(this.storageId))
if (!product) {
console.error("El producto es nulo!", this.storageId, this.storage.length, this.cart)
}
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]
*/
findLineItem () {
const line_item = this.cart.included.find(x => (x.type === 'line_item' && x.relationships.variant.data.id == this.variantId))
return (line_item || {})
}
get storageId () {
return `cart:item:${this.variantId}`
}
/*
* Stores an item for later usage.
*
* @see {./order_controller.js}
*/
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('|') : []
}))
}
/*
* 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) {
const addedQuantity = this.addedQuantity()
if (addedQuantity > 1) quantity = addedQuantity
const orderToken = await this.tokenGetOrCreate()
const response = await this.spree.cart.addItem({ orderToken }, { variant_id: this.variantId, quantity, include: 'line_items' })
if (response.isFail()) {
this.handleFailure(response)
return
}
this.cart = response
this.itemStore()
this.counterUpdate()
this.fireCajon()
if (floating_alert) {
2021-10-28 17:59:19 +00:00
const site = window.site
2021-06-01 21:33:49 +00:00
const content = site.cart.added
window.dispatchEvent(new CustomEvent('floating:alert', { detail: { content }}))
}
}
/*
* 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.
*/
async remove () {
const product = this.product
if (!product) return
if (!product.line_item) return
2021-06-01 21:33:49 +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()) {
this.handleFailure(response)
return
}
this.cart = response
this.storage.removeItem(this.storageId)
this.element.remove()
this.subtotalUpdate()
this.counterUpdate()
}
/*
* Shows variants
*/
async variants () {
const template = 'variants'
const data = {
product: {
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 } }))
}
/*
* Recovers the order if something failed
*/
async recover () {
console.error('Recuperando pedido', this.token)
2021-06-01 21:33:49 +00:00
// Removes the failing token
this.storage.removeItem('token')
// Get a new token and cart
await this.tokenGetOrCreate()
2021-06-01 21:33:49 +00:00
// Stores the previous cart
const cart = this.cart
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) {
this.data.set('variantId', variant.id)
const product = this.product
if (!product) continue
2021-06-01 21:33:49 +00:00
this.data.set('image', product.image)
this.data.set('title', product.title)
this.data.set('extra', product.extra.join('|'))
await this.add(null, product.line_item.attributes.quantity, false)
}
}
/*
* Si le compradore aumenta la cantidad antes de agregar
*/
addedQuantity () {
if (!this.hasAddedQuantityTarget) return 0
const addedQuantity = parseInt(this.addedQuantityTarget.value)
return (isNaN(addedQuantity) ? 0 : addedQuantity)
}
}