generated from boboko/starter
add to cart button disabled when 0 stock and newsletter form theming
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 }}"> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td width="{{ $offset }}" bgcolor="#000000" style="width:{{ $offset }}px;background-color:#000000;{{ $spacer }}"> </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 }}"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
<td width="{{ $offset }}" height="{{ $offset }}" bgcolor="#000000" style="width:{{ $offset }}px;height:{{ $offset }}px;background-color:#000000;{{ $spacer }}"> </td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
@@ -195,7 +195,5 @@ class="max-w-xs"
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="py-20">
|
||||
<x-newsletter-split />
|
||||
</div>
|
||||
<x-newsletter-split />
|
||||
@endsection
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user