stock check, variants in cart, variant buttons in product page

This commit is contained in:
elvira
2026-09-17 21:52:39 +03:00
parent fe55cb5f33
commit afa1993c53
27 changed files with 564 additions and 155 deletions
@@ -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
}
}
+29 -2
View File
@@ -13,7 +13,7 @@ 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)
@@ -78,6 +78,7 @@ export default class extends Controller {
async send(form) {
this.bodyTarget.setAttribute('aria-busy', 'true')
this.clearError()
try {
const response = await fetch(form.action, {
@@ -85,16 +86,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]'))
@@ -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)