checkout and shipping part

This commit is contained in:
elvira
2026-09-09 13:51:00 +03:00
parent 2d85591a43
commit 0fb606ff3f
11 changed files with 614 additions and 183 deletions
@@ -24,7 +24,6 @@
@if ($required) required @endif
{{ $attributes->class(['bbk-field-input', 'bbk-field-input--error' => $errors->has($name)]) }}
>
@error($name)
<p class="bbk-field-error">{{ $message }}</p>
@enderror
{{-- Always present so bbk-checkout-form can fill it live on an autosave. --}}
<p class="bbk-field-error" data-bbk-field-error="{{ $name }}" @unless ($errors->has($name)) hidden @endunless>{{ $errors->first($name) }}</p>
</div>
@@ -0,0 +1,46 @@
{{--
The state/region + country pair for one address (billing or shipping).
Single-country store ($storeCountry set): region is a <select> of that
country's Lunar states, submitting `->name` (table-rate-shipping resolves
zones with State::whereName()), and country is a fixed hidden field + label.
Otherwise: free-text region + full country <select>, as before.
--}}
@props([
'prefix',
'storeCountry' => null,
'regions' => [],
'countries' => [],
'address' => null,
])
<div class="bbk-field-row">
@if ($storeCountry)
<x-checkout::select
:name="$prefix . '_state'"
label="{{ __('checkout.page.state') }}"
:options="$regions"
value-field="name"
:value="$address?->state"
placeholder="{{ __('checkout.page.state_placeholder') }}"
required
/>
<div class="bbk-field">
<span class="bbk-field-label">{{ __('checkout.page.country') }}</span>
<p class="bbk-field-static">{{ $storeCountry->name }}</p>
<input type="hidden" name="{{ $prefix }}_country_id" value="{{ $storeCountry->id }}">
</div>
@else
<x-checkout::field :name="$prefix . '_state'" label="{{ __('checkout.page.state') }}" :value="$address?->state" />
<x-checkout::select
:name="$prefix . '_country_id'"
label="{{ __('checkout.page.country') }}"
:options="$countries"
:value="$address?->country_id"
placeholder="{{ __('checkout.page.country_placeholder') }}"
required
/>
@endif
</div>
@@ -1,8 +1,11 @@
{{--
<x-checkout::select name="billing_country_id" label="Country" :options="$countries" required />
<x-checkout::select name="shipping_state" label="Region" :options="$regions" value-field="name" required />
`options` is an iterable of {id, name} (a Collection of models works
directly) — value/label pulled from those two keys.
`options` is an iterable of models/objects; `label` is always read from
`->name`, the submitted value from `->{$valueField}` (default `id`, but e.g.
`name` for Lunar states — table-rate-shipping resolves those with
State::whereName(), so the address must carry the exact name string).
--}}
@props([
'name',
@@ -11,6 +14,7 @@
'value' => null,
'placeholder' => null,
'required' => false,
'valueField' => 'id',
])
@php($selected = old($name, $value))
@@ -27,12 +31,10 @@
<option value="" @selected(! $selected)>{{ $placeholder }}</option>
@endif
@foreach ($options as $option)
<option value="{{ $option->id }}" @selected((string) $selected === (string) $option->id)>
<option value="{{ $option->{$valueField} }}" @selected((string) $selected === (string) $option->{$valueField})>
{{ $option->name }}
</option>
@endforeach
</select>
@error($name)
<p class="bbk-field-error">{{ $message }}</p>
@enderror
<p class="bbk-field-error" data-bbk-field-error="{{ $name }}" @unless ($errors->has($name)) hidden @endunless>{{ $errors->first($name) }}</p>
</div>
@@ -14,7 +14,5 @@
@if ($required) required @endif
{{ $attributes->class(['bbk-field-input', 'bbk-field-input--error' => $errors->has($name)]) }}
>{{ old($name, $value) }}</textarea>
@error($name)
<p class="bbk-field-error">{{ $message }}</p>
@enderror
<p class="bbk-field-error" data-bbk-field-error="{{ $name }}" @unless ($errors->has($name)) hidden @endunless>{{ $errors->first($name) }}</p>
</div>
+52 -65
View File
@@ -17,7 +17,16 @@
<h1 class="bbk-checkout-heading">{{ __('checkout.page.title') }}</h1>
<div class="bbk-checkout">
<div class="bbk-checkout-main">
<div
class="bbk-checkout-main"
data-controller="bbk-checkout-form"
data-action="input->bbk-checkout-form#scheduleSave"
data-bbk-checkout-form-save-url-value="{{ route('checkout.address.save', app()->getLocale()) }}"
data-bbk-checkout-form-select-shipping-url-value="{{ route('checkout.shipping-option.select', app()->getLocale()) }}"
data-bbk-checkout-form-status-saving-value="{{ __('checkout.page.saving') }}"
data-bbk-checkout-form-status-saved-value="{{ __('checkout.page.saved') }}"
data-bbk-checkout-form-status-error-value="{{ __('checkout.page.save_error') }}"
>
{{-- Contact --}}
<section class="bbk-checkout-section">
@@ -25,7 +34,6 @@
class="bbk-checkout-tabs"
role="tablist"
aria-label="{{ __('checkout.page.contact_heading') }}"
data-controller="bbk-checkout-form"
>
<button
type="button"
@@ -89,7 +97,16 @@ class="bbk-checkout-tab"
</div>
</section>
<form id="bbk-address-form" method="POST" action="{{ route('checkout.address.save', app()->getLocale()) }}">
{{-- Autosaves — no submit button. Any `change` inside .bbk-checkout-main
(this form, plus the contact email/consent which sit outside it but
link via form="bbk-address-form") is debounced and POSTed as the whole
form; the shipping-method radios are excluded in scheduleSave(). --}}
<form
id="bbk-address-form"
method="POST"
action="{{ route('checkout.address.save', app()->getLocale()) }}"
data-bbk-checkout-form-target="form"
>
@csrf
{{-- Billing --}}
@@ -114,17 +131,13 @@ class="bbk-checkout-tab"
<x-checkout::field name="billing_postcode" label="{{ __('checkout.page.postcode') }}" :value="$billingAddress?->postcode" required />
</div>
<div class="bbk-field-row">
<x-checkout::field name="billing_state" label="{{ __('checkout.page.state') }}" :value="$billingAddress?->state" />
<x-checkout::select
name="billing_country_id"
label="{{ __('checkout.page.country') }}"
:options="$countries"
:value="$billingAddress?->country_id"
placeholder="{{ __('checkout.page.country_placeholder') }}"
required
/>
</div>
<x-checkout::region-country
prefix="billing"
:store-country="$storeCountry"
:regions="$regions"
:countries="$countries"
:address="$billingAddress"
/>
<x-checkout::field name="billing_contact_phone" label="{{ __('checkout.page.phone') }}" type="tel" :value="$billingAddress?->contact_phone" />
</section>
@@ -140,7 +153,7 @@ class="bbk-checkout-tab"
value="1"
data-bbk-checkout-form-target="sameAsBilling"
data-action="bbk-checkout-form#toggleSameAsBilling"
{{ old('same_as_billing', $shippingAddress === null || $shippingAddress?->is($billingAddress)) ? 'checked' : '' }}
@checked($shipToBilling)
>
{{ __('checkout.page.same_as_billing') }}
</label>
@@ -161,17 +174,13 @@ class="bbk-checkout-tab"
<x-checkout::field name="shipping_postcode" label="{{ __('checkout.page.postcode') }}" :value="$shippingAddress?->postcode" required />
</div>
<div class="bbk-field-row">
<x-checkout::field name="shipping_state" label="{{ __('checkout.page.state') }}" :value="$shippingAddress?->state" />
<x-checkout::select
name="shipping_country_id"
label="{{ __('checkout.page.country') }}"
:options="$countries"
:value="$shippingAddress?->country_id"
placeholder="{{ __('checkout.page.country_placeholder') }}"
required
/>
</div>
<x-checkout::region-country
prefix="shipping"
:store-country="$storeCountry"
:regions="$regions"
:countries="$countries"
:address="$shippingAddress"
/>
<x-checkout::field name="shipping_contact_phone" label="{{ __('checkout.page.phone') }}" type="tel" :value="$shippingAddress?->contact_phone" />
</div>
@@ -182,50 +191,28 @@ class="bbk-checkout-tab"
:value="$shippingAddress?->delivery_instructions"
/>
</section>
<button type="submit" class="bbk-checkout-continue">
{{ __('checkout.page.save_address') }}
</button>
</form>
{{-- Shipping method — only resolvable once a shipping address exists --}}
<p
class="bbk-checkout-status"
data-bbk-checkout-form-target="status"
role="status"
aria-live="polite"
hidden
></p>
{{-- Shipping method — resolves from the saved shipping address;
re-rendered as a fragment by bbk-checkout-form after each
autosave / option change. --}}
<section class="bbk-checkout-section">
<h2 class="bbk-checkout-section-heading">{{ __('checkout.page.shipping_method_heading') }}</h2>
@if ($shippingOptions->isEmpty())
<p class="bbk-checkout-note">{{ __('checkout.page.shipping_method_empty') }}</p>
@else
<form method="POST" action="{{ route('checkout.shipping-option.select', app()->getLocale()) }}">
@csrf
<div class="bbk-checkout-shipping-options">
@foreach ($shippingOptions as $option)
<label class="bbk-checkout-shipping-option">
<input
type="radio"
name="shipping_option"
value="{{ $option->identifier }}"
{{ old('shipping_option', $shippingAddress?->shipping_option) === $option->identifier ? 'checked' : '' }}
required
>
<span class="bbk-checkout-shipping-option-detail">
<span class="bbk-checkout-shipping-option-name">{{ $option->name }}</span>
@if ($option->description)
<span class="bbk-checkout-shipping-option-description">{{ $option->description }}</span>
@endif
</span>
<span class="bbk-checkout-shipping-option-price">{{ $option->price->formatted() }}</span>
</label>
@endforeach
</div>
@error('shipping_option')
<p class="bbk-field-error">{{ $message }}</p>
@enderror
<button type="submit" class="bbk-checkout-continue">
{{ __('checkout.page.select_shipping_method') }}
</button>
</form>
@endif
<div id="bbk-shipping-options" data-bbk-checkout-form-target="shippingOptions">
@include('checkout::partials.shipping-options', [
'shippingAddress' => $shippingAddress,
'shippingOptions' => $shippingOptions,
])
</div>
</section>
{{-- TODO: payment — next slice. boboko-core's Offline driver is the only
@@ -73,6 +73,11 @@ class="bbk-cart-coupon-input"
@endif
</div>
<div class="bbk-cart-summary-row">
<span>{{ __('checkout.cart.subtotal') }}</span>
<span>{{ $cart?->subTotal?->formatted() }}</span>
</div>
@if ($cart?->discountTotal?->value > 0)
<div class="bbk-cart-summary-row bbk-cart-summary-row--discount">
<span>{{ __('checkout.cart.discount') }}</span>
@@ -80,13 +85,29 @@ class="bbk-cart-coupon-input"
</div>
@endif
<div class="bbk-cart-summary-row">
<span>{{ __('checkout.cart.subtotal') }}</span>
<span>{{ $cart?->subTotal?->formatted() }}</span>
</div>
{{-- Shipping + tax appear once the shopper has a shipping address
(i.e. they're on the checkout page). In the drawer, where no
address is set yet, only subtotal + total show. --}}
@if ($cart?->shippingAddress)
<div class="bbk-cart-summary-row">
<span>{{ __('checkout.cart.shipping') }}</span>
@if ($cart->shippingAddress->shipping_option)
<span>{{ $cart->shippingTotal?->formatted() }}</span>
@else
<span class="bbk-cart-summary-pending">{{ __('checkout.cart.shipping_pending') }}</span>
@endif
</div>
@endif
{{-- Always shown, even with no discount — equals subtotal then,
diverges once one's applied. --}}
@if ($cart?->taxTotal?->value > 0)
<div class="bbk-cart-summary-row">
<span>{{ __('checkout.cart.tax') }}</span>
<span>{{ $cart->taxTotal->formatted() }}</span>
</div>
@endif
{{-- Always shown — equals subtotal with nothing else applied,
diverges as discount / shipping / tax come in. --}}
<div class="bbk-cart-summary-row bbk-cart-summary-row--total">
<span>{{ __('checkout.cart.total') }}</span>
<span>{{ $cart?->total?->formatted() }}</span>
@@ -0,0 +1,50 @@
{{--
Shipping methods for the checkout page. Rendered inline by page.blade.php on
load, and re-rendered as a fragment by CheckoutController after every
address save / option change (bbk-checkout-form swaps it in). Radios
autosave via bbk-checkout-form#selectShipping — no submit button. A single
resolved option is auto-selected server-side and shown as a fixed line.
$shippingAddress, $shippingOptions come from the controller / page scope.
--}}
@php($selected = $shippingAddress?->shipping_option)
{{-- Rate resolution needs country (always Greece here) + postcode; until a
postcode is saved there's nothing to quote against yet. --}}
@if (! $shippingAddress?->postcode)
<p class="bbk-checkout-note">{{ __('checkout.page.shipping_method_empty') }}</p>
@elseif ($shippingOptions->isEmpty())
<p class="bbk-checkout-note">{{ __('checkout.page.shipping_method_none') }}</p>
@elseif ($shippingOptions->count() === 1)
@php($only = $shippingOptions->first())
<div class="bbk-checkout-shipping-confirmed">
<span class="bbk-checkout-shipping-option-detail">
<span class="bbk-checkout-shipping-option-name">{{ $only->name }}</span>
@if ($only->description)
<span class="bbk-checkout-shipping-option-description">{{ strip_tags($only->description) }}</span>
@endif
</span>
<span class="bbk-checkout-shipping-option-price">{{ $only->price->formatted() }}</span>
</div>
@else
<div class="bbk-checkout-shipping-options">
@foreach ($shippingOptions as $option)
<label class="bbk-checkout-shipping-option">
<input
type="radio"
name="shipping_option"
value="{{ $option->identifier }}"
@checked($selected === $option->identifier)
data-action="change->bbk-checkout-form#selectShipping"
>
<span class="bbk-checkout-shipping-option-detail">
<span class="bbk-checkout-shipping-option-name">{{ $option->name }}</span>
@if ($option->description)
<span class="bbk-checkout-shipping-option-description">{{ strip_tags($option->description) }}</span>
@endif
</span>
<span class="bbk-checkout-shipping-option-price">{{ $option->price->formatted() }}</span>
</label>
@endforeach
</div>
@endif