generated from boboko/starter
Merge branch 'cart-temp' into box-now-test-
This commit is contained in:
@@ -6,12 +6,15 @@ import { csrfToken } from './csrf'
|
||||
// `bbk-cart:changed` window event. No DOM building here — the drawer
|
||||
// (bbk-cart-controller) owns rendering.
|
||||
export default class extends Controller {
|
||||
static targets = ['error']
|
||||
|
||||
async add(event) {
|
||||
event.preventDefault()
|
||||
|
||||
const form = this.element
|
||||
const submit = form.querySelector('[type="submit"]')
|
||||
|
||||
this.clearError()
|
||||
form.setAttribute('data-bbk-add-to-cart-state', 'loading')
|
||||
if (submit) submit.disabled = true
|
||||
|
||||
@@ -21,11 +24,16 @@ export default class extends Controller {
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': csrfToken(),
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: new FormData(form),
|
||||
})
|
||||
|
||||
if (!response.ok) return
|
||||
if (!response.ok) {
|
||||
const data = await response.json().catch(() => null)
|
||||
this.showError(data?.error)
|
||||
return
|
||||
}
|
||||
|
||||
window.dispatchEvent(new CustomEvent('bbk-cart:changed', {
|
||||
detail: { html: await response.text() },
|
||||
@@ -35,4 +43,15 @@ export default class extends Controller {
|
||||
if (submit) submit.disabled = false
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
if (!this.hasErrorTarget || !message) return
|
||||
this.errorTarget.textContent = message
|
||||
this.errorTarget.hidden = false
|
||||
}
|
||||
|
||||
clearError() {
|
||||
if (!this.hasErrorTarget) return
|
||||
this.errorTarget.hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@ import { csrfToken } from './csrf'
|
||||
// Appearance is entirely CSS-driven: open state is the data-bbk-cart-state
|
||||
// attribute on the root, nothing here touches styles or class lists.
|
||||
export default class extends Controller {
|
||||
static targets = ['panel', 'body']
|
||||
static targets = ['panel', 'body', 'error']
|
||||
|
||||
connect() {
|
||||
this.onChanged = this.onChanged.bind(this)
|
||||
this.onKeydown = this.onKeydown.bind(this)
|
||||
this.updateTimers = new Map() // line id -> pending debounce timer
|
||||
|
||||
window.addEventListener('bbk-cart:changed', this.onChanged)
|
||||
window.addEventListener('bbk-cart:open', this.open.bind(this))
|
||||
@@ -30,6 +31,7 @@ export default class extends Controller {
|
||||
disconnect() {
|
||||
window.removeEventListener('bbk-cart:changed', this.onChanged)
|
||||
document.removeEventListener('keydown', this.onKeydown)
|
||||
this.updateTimers.forEach((timer) => clearTimeout(timer))
|
||||
}
|
||||
|
||||
onChanged(event) {
|
||||
@@ -63,7 +65,11 @@ export default class extends Controller {
|
||||
submit(event) {
|
||||
event.preventDefault()
|
||||
const form = event.target.closest('form')
|
||||
if (form) this.send(form)
|
||||
if (!form) return
|
||||
|
||||
// A remove is a deliberate, one-shot action — only the quantity form
|
||||
// (typing, or the +/- stepper below) benefits from debouncing.
|
||||
form.classList.contains('bbk-cart-qty') ? this.scheduleSend(form) : this.send(form)
|
||||
}
|
||||
|
||||
// +/- stepper buttons inside a line
|
||||
@@ -73,11 +79,27 @@ export default class extends Controller {
|
||||
const input = form.querySelector('input[type="number"]')
|
||||
const next = Math.max(0, parseInt(input.value || '0', 10) + Number(event.params.dir))
|
||||
input.value = String(next)
|
||||
this.send(form)
|
||||
this.scheduleSend(form)
|
||||
}
|
||||
|
||||
// Repeated clicks (or spinner nudges) update the input instantly but only
|
||||
// send once they settle for 300ms — sending on every single click was
|
||||
// firing overlapping requests that raced each other and made the drawer
|
||||
// visibly flicker/lag under quick clicking.
|
||||
scheduleSend(form) {
|
||||
const lineId = form.closest('[data-bbk-line-id]')?.dataset.bbkLineId
|
||||
if (!lineId) return this.send(form)
|
||||
|
||||
clearTimeout(this.updateTimers.get(lineId))
|
||||
this.updateTimers.set(lineId, setTimeout(() => {
|
||||
this.updateTimers.delete(lineId)
|
||||
this.send(form)
|
||||
}, 300))
|
||||
}
|
||||
|
||||
async send(form) {
|
||||
this.bodyTarget.setAttribute('aria-busy', 'true')
|
||||
this.clearError()
|
||||
|
||||
try {
|
||||
const response = await fetch(form.action, {
|
||||
@@ -85,16 +107,42 @@ export default class extends Controller {
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': csrfToken(),
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: new FormData(form),
|
||||
})
|
||||
|
||||
if (response.ok) this.replaceBody(await response.text())
|
||||
if (response.ok) {
|
||||
this.replaceBody(await response.text())
|
||||
return
|
||||
}
|
||||
|
||||
const data = await response.json().catch(() => null)
|
||||
this.showError(data?.error)
|
||||
|
||||
// The rejected quantity (typed, or from a +/- click) is left
|
||||
// sitting in the input with nothing to correct it — the update
|
||||
// never reached the cart, so the input must be put back to what
|
||||
// the cart actually still holds, not just left showing whatever
|
||||
// was rejected.
|
||||
const input = form.querySelector('[data-bbk-cart-confirmed-quantity]')
|
||||
if (input) input.value = input.dataset.bbkCartConfirmedQuantity
|
||||
} finally {
|
||||
this.bodyTarget.removeAttribute('aria-busy')
|
||||
}
|
||||
}
|
||||
|
||||
showError(message) {
|
||||
if (!this.hasErrorTarget || !message) return
|
||||
this.errorTarget.textContent = message
|
||||
this.errorTarget.hidden = false
|
||||
}
|
||||
|
||||
clearError() {
|
||||
if (!this.hasErrorTarget) return
|
||||
this.errorTarget.hidden = true
|
||||
}
|
||||
|
||||
replaceBody(html) {
|
||||
this.bodyTarget.innerHTML = html
|
||||
this.emitUpdated(this.bodyTarget.querySelector('[data-bbk-cart-count]'))
|
||||
|
||||
@@ -13,7 +13,6 @@ import { csrfToken } from './csrf'
|
||||
// already uses). Shipping-method radios post to selectShippingUrl the same way.
|
||||
export default class extends Controller {
|
||||
static targets = [
|
||||
'guestTab', 'loginTab', 'guestPanel', 'loginPanel',
|
||||
'sameAsBilling', 'shippingFields',
|
||||
'form', 'shippingOptions', 'status',
|
||||
]
|
||||
@@ -41,22 +40,6 @@ export default class extends Controller {
|
||||
this.saveController?.abort()
|
||||
}
|
||||
|
||||
// ── Contact tabs ────────────────────────────────────────────────────
|
||||
|
||||
showGuest() {
|
||||
this.guestPanelTarget.hidden = false
|
||||
this.loginPanelTarget.hidden = true
|
||||
this.guestTabTarget.setAttribute('aria-selected', 'true')
|
||||
this.loginTabTarget.setAttribute('aria-selected', 'false')
|
||||
}
|
||||
|
||||
showLogin() {
|
||||
this.guestPanelTarget.hidden = true
|
||||
this.loginPanelTarget.hidden = false
|
||||
this.guestTabTarget.setAttribute('aria-selected', 'false')
|
||||
this.loginTabTarget.setAttribute('aria-selected', 'true')
|
||||
}
|
||||
|
||||
// ── Same as billing ────────────────────────────────────────────────
|
||||
|
||||
toggleSameAsBilling() {
|
||||
@@ -93,7 +76,6 @@ export default class extends Controller {
|
||||
// react to fields that actually belong to the address form.
|
||||
const el = event.target
|
||||
const belongsToForm = el.form?.id === 'bbk-address-form'
|
||||
|| el.closest('[data-bbk-checkout-form-target="guestPanel"]')
|
||||
if (!belongsToForm) return
|
||||
|
||||
// No status during the wait — it only shows once the request is in flight,
|
||||
|
||||
@@ -43,6 +43,16 @@ export default class extends Controller {
|
||||
this.amountValue = total
|
||||
this.elements.update({ amount: Math.max(total, 1) })
|
||||
}
|
||||
|
||||
// Removing the last line while sitting on the checkout page (via
|
||||
// the order summary's own remove form) must not leave "place
|
||||
// order" clickable with nothing left to charge for — this fires
|
||||
// from both the drawer and the checkout page's own summary
|
||||
// instance, whichever the shopper actually used.
|
||||
const count = event.detail?.count
|
||||
if (typeof count === 'number' && this.hasSubmitTarget) {
|
||||
this.submitTarget.disabled = count === 0
|
||||
}
|
||||
}
|
||||
window.addEventListener('bbk-cart:updated', this.onSummaryUpdate)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user