add to cart button disabled when 0 stock and newsletter form theming

This commit is contained in:
elvira
2026-09-25 20:23:22 +03:00
parent 081893ef57
commit ab7e560a3d
8 changed files with 64 additions and 21 deletions
@@ -109,6 +109,9 @@ private function buildOptionPicker(int $productId): array
'id' => $variant->id,
'price' => $variant->prices->first()?->price?->decimal(),
'image' => $variant->images->first()?->getUrl(),
// Live from the DB (not the index's in_stock), with Lunar's own
// purchasability rule — drives the disabled add-to-cart button.
'inStock' => $variant->canBeFulfilledAtQuantity(1),
// handle => selected value id, for matching a combination of
// selections back to this variant — see selectVariant() in
// product-form-controller.js.
+1 -1
View File
@@ -331,7 +331,7 @@ @layer components {
transition: transform 0.35s cubic-bezier(0.2, 0.78, 0.12, 0.86);
}
.btn-primary:hover::after {
.btn-primary:not(:disabled):hover::after {
transform: translate(0, 0);
}
@@ -2,7 +2,7 @@ import { Controller } from '@hotwired/stimulus'
import { formatPrice } from '../utils/format-price'
export default class extends Controller {
static targets = ['price', 'image', 'swatch', 'colorName', 'stockError']
static targets = ['price', 'image', 'swatch', 'colorName', 'stockError', 'submit']
static values = {
variants: Array,
selected: Number,
@@ -90,7 +90,7 @@ export default class extends Controller {
// own server-side check have the final word rather than
// silently blocking the shopper here.
} finally {
if (submit) submit.disabled = false
if (submit) submit.disabled = !this.selectedVariant?.inStock
}
form.dataset.bbkStockChecked = 'true'
@@ -111,6 +111,10 @@ export default class extends Controller {
this.stockErrorTarget.hidden = true
}
get selectedVariant() {
return this.variantsValue.find(v => v.id === this.selectedValue)
}
selectVariant(event) {
const option = event.currentTarget.dataset.option
const valueId = parseInt(event.currentTarget.dataset.valueId)
@@ -156,6 +160,9 @@ export default class extends Controller {
const purchasableInput = this.element.querySelector('[data-bbk-purchasable-input]')
if (purchasableInput) purchasableInput.value = id
// Out-of-stock variant (as of page load) can't be added at all.
if (this.hasSubmitTarget) this.submitTarget.disabled = !variant.inStock
this.swatchTargets.forEach(swatch => {
const isSelected = this.selections[swatch.dataset.option] === parseInt(swatch.dataset.valueId)
swatch.classList.toggle('is-selected', isSelected)
@@ -3,10 +3,10 @@
])
<section {{ $attributes }}>
<div class="max-w-6xl mx-auto grid grid-cols-1 lg:grid-cols-2 border border-black">
<div class="flex flex-col justify-center gap-8 px-8 py-16 lg:px-16">
<div class="grid grid-cols-1 lg:grid-cols-2">
<div class="flex flex-col justify-center gap-8 px-8 py-16 lg:px-16 items-center">
<h2
class="text-h2 leading-tight"
class="text-h1 leading-[1.3] max-w-xl text-center"
aria-label="Γράψου στο newsletter μας"
data-controller="appear"
data-appear-visible-class="is-visible"
@@ -17,13 +17,13 @@ class="text-h2 leading-tight"
μας
</h2>
<p class="max-w-md text-neutral-600">
<p class="max-w-xl text-neutral-600 text-center">
Γίνε μέλος της λίστας μας και κέρδισε <span class="font-bold">5% έκπτωση</span> στην πρώτη σου παραγγελία, μαζί με αποκλειστικές προσφορές και νέα.
</p>
<form method="POST" action="#" class="flex flex-col gap-6 max-w-md">
<form method="POST" action="#" class="flex flex-col gap-6 max-w-xl items-center">
@csrf
<div class="relative">
<div class="relative max-w-xs w-full">
<x-ui.input
name="email"
type="email"
@@ -44,7 +44,7 @@ class="absolute right-0 bottom-2 cursor-pointer"
</div>
<x-ui.checkbox name="gdpr_consent" :required="true" id="newsletter_split_gdpr_consent" class="after:mix-blend-multiply">
<span class="text-sm text-left">Επιθυμώ διακαώς 🔥 να εγγραφώ στη λίστα αποστολής ενημερωτικού υλικού</span>
<span class="text-sm">Επιθυμώ διακαώς 🔥 να εγγραφώ στη λίστα αποστολής ενημερωτικού υλικού</span>
</x-ui.checkbox>
</form>
</div>
@@ -33,7 +33,7 @@
// "absolute" via the class prop — Tailwind's generated stylesheet always
// orders the "relative" utility after "absolute", so on a class clash
// "relative" silently wins and the button never actually gets positioned.
$class = trim($position.' isolate inline-flex items-center justify-center border border-black text-black cursor-pointer no-underline '.$variantClasses.' '.$sizeClasses);
$class = trim($position.' isolate inline-flex items-center justify-center border border-black text-black cursor-pointer no-underline disabled:cursor-not-allowed disabled:opacity-50 '.$variantClasses.' '.$sizeClasses);
$attrs = $href
? $attributes->merge(['href' => $href, 'class' => $class])
@@ -1,13 +1,43 @@
{{--
@include('emails.partials.button', ['url' => ..., 'label' => ...])
Bulletproof button: the padding sits on the link (whole area clickable)
with mso-padding-alt on the cell so Outlook keeps the same size.
The site's primary button (x-ui.button, .btn-primary): neutral-200 fill,
black border, bold italic uppercase, black offset shadow. The shadow is
built from table cells, not box-shadow (Gmail and Outlook drop it): a
black column on the right starting $offset down, and a black row at the
bottom starting $offset in. The button cell spans the first two rows, so
the black column always matches its height.
Uppercased here with the tonos removed, since emails can't run the site's
strip-accents script and CSS uppercase would keep the accents.
--}}
<table role="presentation" cellpadding="0" cellspacing="0" style="margin:0;">
@php
$offset = 8;
$label = strtr(mb_strtoupper($label), [
'Ά' => 'Α', 'Έ' => 'Ε', 'Ή' => 'Η', 'Ί' => 'Ι', 'Ό' => 'Ο', 'Ύ' => 'Υ', 'Ώ' => 'Ω',
'ΐ' => 'Ϊ', 'ΰ' => 'Ϋ',
]);
$spacer = "font-size:1px;line-height:1px;mso-line-height-rule:exactly;";
@endphp
<table role="presentation" cellpadding="0" cellspacing="0" border="0" style="margin:0;border-collapse:separate;">
<tr>
<td bgcolor="#18c28a" style="background-color:#18c28a;border:1px solid #000000;border-radius:0;mso-padding-alt:14px 28px;">
<a href="{{ $url }}" target="_blank" style="display:inline-block;padding:14px 28px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:16px;line-height:1.2;font-weight:700;color:#000000;text-decoration:none;">{{ $label }}</a>
<td rowspan="2" bgcolor="#e5e5e5" style="background-color:#e5e5e5;border:1px solid #000000;border-radius:0;mso-padding-alt:14px 28px;">
<a href="{{ $url }}" target="_blank" style="display:inline-block;padding:14px 28px;font-family:'Manrope',Arial,Helvetica,sans-serif;font-size:16px;line-height:20px;mso-line-height-rule:exactly;font-weight:700;font-style:italic;color:#000000;text-decoration:none;">{{ $label }}</a>
</td>
<td width="{{ $offset }}" height="{{ $offset }}" style="width:{{ $offset }}px;height:{{ $offset }}px;{{ $spacer }}">&nbsp;</td>
</tr>
<tr>
<td width="{{ $offset }}" bgcolor="#000000" style="width:{{ $offset }}px;background-color:#000000;{{ $spacer }}">&nbsp;</td>
</tr>
<tr>
<td height="{{ $offset }}" style="height:{{ $offset }}px;padding:0 0 0 {{ $offset }}px;{{ $spacer }}">
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0">
<tr>
<td height="{{ $offset }}" bgcolor="#000000" style="height:{{ $offset }}px;background-color:#000000;{{ $spacer }}">&nbsp;</td>
</tr>
</table>
</td>
<td width="{{ $offset }}" height="{{ $offset }}" bgcolor="#000000" style="width:{{ $offset }}px;height:{{ $offset }}px;background-color:#000000;{{ $spacer }}">&nbsp;</td>
</tr>
</table>
+1 -3
View File
@@ -195,7 +195,5 @@ class="max-w-xs"
</div>
</div>
<div class="py-20">
<x-newsletter-split />
</div>
<x-newsletter-split />
@endsection
+7 -2
View File
@@ -196,8 +196,13 @@ class="underline-slide font-semibold whitespace-nowrap"
@endif
@endforeach
@php
// Same initial pick as product-form-controller.js's connect()
// (?variant= or the first), so the button doesn't flip on load.
$initialVariant = collect($variantsData)->firstWhere('id', (int) request('variant')) ?? ($variantsData[0] ?? null);
@endphp
<x-checkout::add-to-cart
:purchasable="$variantsData[0]['id'] ?? null"
:purchasable="$initialVariant['id'] ?? null"
:quantity="false"
class="flex flex-col gap-6"
>
@@ -207,7 +212,7 @@ class="flex flex-col gap-6"
<div class="flex items-stretch gap-10">
<x-ui.quantity name="quantity" />
<x-ui.button type="submit" class="flex-1">{{ __('storefront.product.add_to_cart') }}</x-ui.button>
<x-ui.button type="submit" class="flex-1" data-product-form-target="submit" :disabled="! ($initialVariant['inStock'] ?? true)">{{ __('storefront.product.add_to_cart') }}</x-ui.button>
</div>
</x-checkout::add-to-cart>