generated from boboko/starter
checkout process - start
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
{{--
|
||||
<x-checkout::field name="billing_first_name" label="First name" required />
|
||||
|
||||
Generic labelled text input with old-input repopulation and validation
|
||||
error display — the module's own equivalent of a host x-ui.field, used
|
||||
instead of it per the module's independence rule. All styling is .bbk-field*
|
||||
(resources/css/checkout.css); no host classes.
|
||||
--}}
|
||||
@props([
|
||||
'name',
|
||||
'label',
|
||||
'type' => 'text',
|
||||
'value' => null,
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
<div class="bbk-field">
|
||||
<label class="bbk-field-label" for="bbk-{{ $name }}">{{ $label }}</label>
|
||||
<input
|
||||
type="{{ $type }}"
|
||||
name="{{ $name }}"
|
||||
id="bbk-{{ $name }}"
|
||||
value="{{ old($name, $value) }}"
|
||||
@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
|
||||
</div>
|
||||
@@ -0,0 +1,38 @@
|
||||
{{--
|
||||
<x-checkout::select name="billing_country_id" label="Country" :options="$countries" required />
|
||||
|
||||
`options` is an iterable of {id, name} (a Collection of models works
|
||||
directly) — value/label pulled from those two keys.
|
||||
--}}
|
||||
@props([
|
||||
'name',
|
||||
'label',
|
||||
'options' => [],
|
||||
'value' => null,
|
||||
'placeholder' => null,
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
@php($selected = old($name, $value))
|
||||
|
||||
<div class="bbk-field">
|
||||
<label class="bbk-field-label" for="bbk-{{ $name }}">{{ $label }}</label>
|
||||
<select
|
||||
name="{{ $name }}"
|
||||
id="bbk-{{ $name }}"
|
||||
@if ($required) required @endif
|
||||
{{ $attributes->class(['bbk-field-input', 'bbk-field-input--error' => $errors->has($name)]) }}
|
||||
>
|
||||
@if ($placeholder)
|
||||
<option value="" @selected(! $selected)>{{ $placeholder }}</option>
|
||||
@endif
|
||||
@foreach ($options as $option)
|
||||
<option value="{{ $option->id }}" @selected((string) $selected === (string) $option->id)>
|
||||
{{ $option->name }}
|
||||
</option>
|
||||
@endforeach
|
||||
</select>
|
||||
@error($name)
|
||||
<p class="bbk-field-error">{{ $message }}</p>
|
||||
@enderror
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
@props([
|
||||
'name',
|
||||
'label',
|
||||
'value' => null,
|
||||
'required' => false,
|
||||
])
|
||||
|
||||
<div class="bbk-field">
|
||||
<label class="bbk-field-label" for="bbk-{{ $name }}">{{ $label }}</label>
|
||||
<textarea
|
||||
name="{{ $name }}"
|
||||
id="bbk-{{ $name }}"
|
||||
rows="3"
|
||||
@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
|
||||
</div>
|
||||
@@ -0,0 +1,250 @@
|
||||
{{--
|
||||
The checkout page. Two columns: left is contact + billing + shipping +
|
||||
shipping method, right is the order summary (the same cart-body partial the
|
||||
drawer uses, minus its own "Checkout" CTA — see .bbk-checkout-summary in
|
||||
checkout.css). Stops short of payment for this slice — see
|
||||
CheckoutController's class docblock.
|
||||
|
||||
$cart, $lines, $billingAddress, $shippingAddress, $shippingOptions,
|
||||
$countries come from CheckoutController::show().
|
||||
--}}
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('title', __('checkout.page.title') . ' — ' . config('app.name'))
|
||||
|
||||
@section('content')
|
||||
<div class="bbk-checkout-page">
|
||||
<h1 class="bbk-checkout-heading">{{ __('checkout.page.title') }}</h1>
|
||||
|
||||
<div class="bbk-checkout">
|
||||
<div class="bbk-checkout-main">
|
||||
|
||||
{{-- Contact --}}
|
||||
<section class="bbk-checkout-section">
|
||||
<div
|
||||
class="bbk-checkout-tabs"
|
||||
role="tablist"
|
||||
aria-label="{{ __('checkout.page.contact_heading') }}"
|
||||
data-controller="bbk-checkout-form"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="bbk-checkout-tab"
|
||||
role="tab"
|
||||
aria-selected="true"
|
||||
data-bbk-checkout-form-target="guestTab"
|
||||
data-action="bbk-checkout-form#showGuest"
|
||||
>{{ __('checkout.page.guest_tab') }}</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="bbk-checkout-tab"
|
||||
role="tab"
|
||||
aria-selected="false"
|
||||
data-bbk-checkout-form-target="loginTab"
|
||||
data-action="bbk-checkout-form#showLogin"
|
||||
>{{ __('checkout.page.login_tab') }}</button>
|
||||
|
||||
<div class="bbk-checkout-tab-panel" role="tabpanel" data-bbk-checkout-form-target="guestPanel">
|
||||
<x-checkout::field
|
||||
name="contact_email"
|
||||
label="{{ __('checkout.page.email_label') }}"
|
||||
type="email"
|
||||
:value="$shippingAddress?->contact_email ?? $billingAddress?->contact_email"
|
||||
required
|
||||
form="bbk-address-form"
|
||||
/>
|
||||
|
||||
{{-- One combined marketing opt-in (offers + news + unfinished-order
|
||||
reminders). Optional, unticked, never required — all of it is
|
||||
direct marketing under ePrivacy (GR L. 3471/2006 art. 11), so it
|
||||
needs an explicit opt-in and checkout can't be gated on it.
|
||||
Persistence + prefill from the cart is Task 2 (back-end);
|
||||
data_get() here already reads it once that lands. --}}
|
||||
<label class="bbk-checkbox bbk-checkbox--stacked">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="marketing_consent"
|
||||
value="1"
|
||||
form="bbk-address-form"
|
||||
{{ old('marketing_consent', data_get($cart, 'meta.marketing_consent')) ? 'checked' : '' }}
|
||||
>
|
||||
{{ __('checkout.page.marketing_consent') }}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{{-- Not wired yet — Modules\Core\Auth\Services\UserOtpService exists
|
||||
(email + one-time code, passwordless) but nothing in the storefront
|
||||
calls it yet. UI placeholder only; see project notes. --}}
|
||||
<div class="bbk-checkout-tab-panel" role="tabpanel" hidden data-bbk-checkout-form-target="loginPanel">
|
||||
<div class="bbk-field">
|
||||
<label class="bbk-field-label" for="bbk-login-email">{{ __('checkout.page.login_email_label') }}</label>
|
||||
<input type="email" id="bbk-login-email" class="bbk-field-input" disabled>
|
||||
</div>
|
||||
<button type="button" class="bbk-checkout-continue" disabled>
|
||||
{{ __('checkout.page.send_code') }}
|
||||
</button>
|
||||
<p class="bbk-checkout-note">{{ __('checkout.page.login_coming_soon') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<form id="bbk-address-form" method="POST" action="{{ route('checkout.address.save', app()->getLocale()) }}">
|
||||
@csrf
|
||||
|
||||
{{-- Billing --}}
|
||||
<section class="bbk-checkout-section">
|
||||
<h2 class="bbk-checkout-section-heading">{{ __('checkout.page.billing_heading') }}</h2>
|
||||
|
||||
<div class="bbk-field-row">
|
||||
<x-checkout::field name="billing_first_name" label="{{ __('checkout.page.first_name') }}" :value="$billingAddress?->first_name" required />
|
||||
<x-checkout::field name="billing_last_name" label="{{ __('checkout.page.last_name') }}" :value="$billingAddress?->last_name" required />
|
||||
</div>
|
||||
|
||||
<div class="bbk-field-row">
|
||||
<x-checkout::field name="billing_company_name" label="{{ __('checkout.page.company_name') }}" :value="$billingAddress?->company_name" />
|
||||
<x-checkout::field name="billing_tax_identifier" label="{{ __('checkout.page.tax_identifier') }}" :value="$billingAddress?->tax_identifier" />
|
||||
</div>
|
||||
|
||||
<x-checkout::field name="billing_line_one" label="{{ __('checkout.page.address_line_one') }}" :value="$billingAddress?->line_one" required />
|
||||
<x-checkout::field name="billing_line_two" label="{{ __('checkout.page.address_line_two') }}" :value="$billingAddress?->line_two" />
|
||||
|
||||
<div class="bbk-field-row">
|
||||
<x-checkout::field name="billing_city" label="{{ __('checkout.page.city') }}" :value="$billingAddress?->city" required />
|
||||
<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::field name="billing_contact_phone" label="{{ __('checkout.page.phone') }}" type="tel" :value="$billingAddress?->contact_phone" />
|
||||
</section>
|
||||
|
||||
{{-- Shipping --}}
|
||||
<section class="bbk-checkout-section">
|
||||
<h2 class="bbk-checkout-section-heading">{{ __('checkout.page.shipping_heading') }}</h2>
|
||||
|
||||
<label class="bbk-checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
name="same_as_billing"
|
||||
value="1"
|
||||
data-bbk-checkout-form-target="sameAsBilling"
|
||||
data-action="bbk-checkout-form#toggleSameAsBilling"
|
||||
{{ old('same_as_billing', $shippingAddress === null || $shippingAddress?->is($billingAddress)) ? 'checked' : '' }}
|
||||
>
|
||||
{{ __('checkout.page.same_as_billing') }}
|
||||
</label>
|
||||
|
||||
<div class="bbk-checkout-shipping-fields" data-bbk-checkout-form-target="shippingFields">
|
||||
<div class="bbk-field-row">
|
||||
<x-checkout::field name="shipping_first_name" label="{{ __('checkout.page.first_name') }}" :value="$shippingAddress?->first_name" required />
|
||||
<x-checkout::field name="shipping_last_name" label="{{ __('checkout.page.last_name') }}" :value="$shippingAddress?->last_name" required />
|
||||
</div>
|
||||
|
||||
<x-checkout::field name="shipping_company_name" label="{{ __('checkout.page.company_name') }}" :value="$shippingAddress?->company_name" />
|
||||
|
||||
<x-checkout::field name="shipping_line_one" label="{{ __('checkout.page.address_line_one') }}" :value="$shippingAddress?->line_one" required />
|
||||
<x-checkout::field name="shipping_line_two" label="{{ __('checkout.page.address_line_two') }}" :value="$shippingAddress?->line_two" />
|
||||
|
||||
<div class="bbk-field-row">
|
||||
<x-checkout::field name="shipping_city" label="{{ __('checkout.page.city') }}" :value="$shippingAddress?->city" required />
|
||||
<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::field name="shipping_contact_phone" label="{{ __('checkout.page.phone') }}" type="tel" :value="$shippingAddress?->contact_phone" />
|
||||
</div>
|
||||
|
||||
<x-checkout::textarea
|
||||
name="shipping_delivery_instructions"
|
||||
label="{{ __('checkout.page.delivery_instructions') }}"
|
||||
: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 --}}
|
||||
<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
|
||||
</section>
|
||||
|
||||
{{-- TODO: payment — next slice. boboko-core's Offline driver is the only
|
||||
one fully wired end-to-end today; Stripe needs its client_secret /
|
||||
3-D Secure continuation handled before it can drive a real form here. --}}
|
||||
<button type="button" class="bbk-checkout-continue" disabled>
|
||||
{{ __('checkout.page.continue_to_payment') }}
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<aside class="bbk-checkout-aside">
|
||||
<div class="bbk-checkout-summary" data-controller="bbk-cart">
|
||||
<h2 class="bbk-checkout-summary-heading">{{ __('checkout.page.order_summary_heading') }}</h2>
|
||||
<div data-bbk-cart-target="body" aria-live="polite">
|
||||
@include('checkout::partials.cart-body')
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -92,11 +92,9 @@ class="bbk-cart-coupon-input"
|
||||
<span>{{ $cart?->total?->formatted() }}</span>
|
||||
</div>
|
||||
|
||||
{{-- TODO: point at the checkout page once that slice exists —
|
||||
the drawer is the cart, there's no cart page for this to fall back to. --}}
|
||||
<button type="button" class="bbk-cart-checkout" disabled>
|
||||
<a class="bbk-cart-checkout" href="{{ route('checkout.show', app()->getLocale()) }}">
|
||||
{{ __('checkout.cart.checkout') }}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user