diff --git a/app/Http/Controllers/Account/AccountController.php b/app/Http/Controllers/Account/AccountController.php
index 8467aac..bade0e6 100644
--- a/app/Http/Controllers/Account/AccountController.php
+++ b/app/Http/Controllers/Account/AccountController.php
@@ -10,6 +10,8 @@
use Illuminate\View\View;
use Lunar\Models\Country;
use Lunar\Models\State;
+use Modules\Core\Cart\Services\CartService;
+use Modules\Core\Checkout\Services\CheckoutService;
use Modules\Core\Customer\Services\CustomerAccountService;
use Modules\Core\Privacy\Services\PrivacyService;
@@ -68,6 +70,7 @@ public function update(string $locale, Request $request): RedirectResponse
Rule::exists((new State)->getTable(), 'name')->where('country_id', $country->id),
],
'contact_phone' => ['nullable', 'string', 'max:30'],
+ 'recovery_consent' => ['boolean'],
]);
$invoice = $request->boolean('invoice');
@@ -100,9 +103,37 @@ public function update(string $locale, Request $request): RedirectResponse
: $this->account->createAddress($user, $addressData);
}
+ $this->updateRecoveryConsent($customer, $request->boolean('recovery_consent'));
+
return redirect()->route('account')->with('status', __('storefront.account.saved'));
}
+ /**
+ * "Email me a reminder if I don't finish my order", as a standing choice.
+ * Stored on the customer in the same meta shape the checkout writes (see
+ * CheckoutController::rememberRecoveryConsent()), and applied to the
+ * current cart too, so opting out stops reminders for it right away.
+ */
+ private function updateRecoveryConsent($customer, bool $consent): void
+ {
+ if ((bool) data_get($customer, 'meta.recovery_consent') !== $consent) {
+ $customer->meta = [
+ ...($customer->meta?->toArray() ?? []),
+ 'recovery_consent' => $consent,
+ 'recovery_consent_at' => $consent ? now()->toIso8601String() : null,
+ 'recovery_consent_policy_version' => $consent ? config('legal.privacy_policy_version') : null,
+ ];
+ $customer->save();
+ }
+
+ // Only an existing cart; never create one just to record this.
+ $cart = app(CartService::class)->current();
+
+ if ($cart && (bool) data_get($cart, 'meta.recovery_consent') !== $consent) {
+ app(CheckoutService::class)->setRecoveryConsent($consent);
+ }
+ }
+
/**
* Self-service deletion: opens core's 30-day grace-period erasure request
* (which blocks the login right away) and logs out. Logging back in within
diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php
index 3517283..04420d8 100644
--- a/app/Http/Controllers/Auth/LoginController.php
+++ b/app/Http/Controllers/Auth/LoginController.php
@@ -25,8 +25,19 @@
*/
class LoginController extends Controller
{
- public function create(string $locale): View
+ /**
+ * `?redirect=/el/checkout` (e.g. from the checkout's login tab) becomes the
+ * intended URL that verify() returns to. Only a same-site path is accepted:
+ * no scheme, no protocol-relative `//host`, so it can't redirect off-site.
+ */
+ public function create(string $locale, Request $request): View
{
+ $redirect = (string) $request->query('redirect', '');
+
+ if (preg_match('#^/(?![/\\\\])#', $redirect)) {
+ $request->session()->put('url.intended', url($redirect));
+ }
+
return view('auth.login');
}
@@ -38,6 +49,9 @@ public function send(string $locale, Request $request, UserOtpService $otp): Red
$email = Str::lower(trim($validated['email']));
+ $userModel = config('auth.providers.users.model');
+ $isNewAccount = ! $userModel::where('email', $email)->exists();
+
try {
$otp->generateAndSend($email);
} catch (OtpThrottledException) {
@@ -46,6 +60,16 @@ public function send(string $locale, Request $request, UserOtpService $otp): Red
]);
}
+ // generateAndSend() just created the account: record that it happened
+ // under the login page's terms notice, and which versions it showed.
+ if ($isNewAccount) {
+ $userModel::where('email', $email)->update([
+ 'terms_accepted_at' => now(),
+ 'terms_version' => config('legal.terms_version'),
+ 'privacy_policy_version' => config('legal.privacy_policy_version'),
+ ]);
+ }
+
$request->session()->put('login.email', $email);
return redirect()->route('login.code');
diff --git a/app/Http/Controllers/Checkout/CheckoutController.php b/app/Http/Controllers/Checkout/CheckoutController.php
index c943ce0..46c3375 100644
--- a/app/Http/Controllers/Checkout/CheckoutController.php
+++ b/app/Http/Controllers/Checkout/CheckoutController.php
@@ -7,6 +7,7 @@
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
+use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
@@ -22,6 +23,7 @@
use Modules\Core\Checkout\Exceptions\TermsNotAcceptedException;
use Modules\Core\Checkout\Exceptions\UnknownPaymentTypeException;
use Modules\Core\Checkout\Services\CheckoutService;
+use Modules\Core\Customer\Services\CustomerAccountService;
use Modules\Core\Payment\Enums\PaymentResultStatus;
use Modules\Core\Payment\Models\PaymentMethod;
@@ -38,7 +40,11 @@
* lenient — nothing is rejected mid-typing; required-field enforcement is the
* job of the (not-yet-built) "Continue to payment" gate.
*
- * Guest-only for now — the Contact section's login tab is UI only.
+ * Guests type their email; the login tab links to the storefront's own
+ * `login` route and back. Logged in: the email is the account's (forced in
+ * saveAddress()), the first visit prefills addresses from the account
+ * (prefillFromAccount()), and Lunar's Login listener has already attached the
+ * cart, so the placed order lands in the account's history.
*
* Single-country store: STORE_COUNTRY_ISO3 fixes the country to Greece (hidden
* field, forced server-side). Set it to null for the full country picker (the
@@ -51,6 +57,7 @@ class CheckoutController extends Controller
public function __construct(
private readonly CartService $cart,
private readonly CheckoutService $checkout,
+ private readonly CustomerAccountService $account,
) {}
public function show(string $locale): View
@@ -61,10 +68,24 @@ public function show(string $locale): View
$shippingOptions = collect();
+ // Captured before prefillFromAccount(), which may recreate the address
+ // row (dropping its shipping_option) — same reason as in saveAddress().
+ $previousOption = $cart?->shippingAddress?->shipping_option;
+
+ if ($cart && Auth::check()) {
+ $cart = $this->prefillFromAccount($cart);
+
+ // Nothing chosen on this cart yet: carry over the account's standing
+ // opt-in (an explicit earlier choice, recorded with its own
+ // timestamp/policy version). Never opts anyone in by default.
+ if (! array_key_exists('recovery_consent', $cart->meta?->toArray() ?? [])
+ && data_get($this->account->customer(Auth::user()), 'meta.recovery_consent')) {
+ $cart = $this->checkout->setRecoveryConsent(true);
+ }
+ }
+
if ($cart?->shippingAddress) {
- // Nothing recreates the address row in this path — its own current
- // value is the correct "previous" to carry forward if still valid.
- $shippingOptions = $this->syncShipping($cart, $cart->shippingAddress->shipping_option);
+ $shippingOptions = $this->syncShipping($cart, $previousOption);
// Cart's CachesProperties::refresh() explicitly nulls total/
// subTotal/shippingTotal/etc. back to their defaults — every
@@ -96,6 +117,7 @@ public function show(string $locale): View
'shippingOptions' => $shippingOptions,
'paymentMethods' => $paymentMethods,
'shipToBilling' => (bool) data_get($cart, 'meta.ship_to_billing', true),
+ 'wantsInvoice' => (bool) data_get($cart, 'meta.wants_invoice', false),
'storeCountry' => $storeCountry,
'countries' => $storeCountry
? collect()
@@ -147,7 +169,6 @@ public function saveAddress(string $locale, Request $request): JsonResponse
'shipping_first_name' => ['nullable', 'string', 'max:255'],
'shipping_last_name' => ['nullable', 'string', 'max:255'],
- 'shipping_company_name' => ['nullable', 'string', 'max:255'],
'shipping_line_one' => ['nullable', 'string', 'max:255'],
'shipping_city' => ['nullable', 'string', 'max:255'],
'shipping_state' => $stateRule,
@@ -160,14 +181,24 @@ public function saveAddress(string $locale, Request $request): JsonResponse
$errors = $validator->errors()->toArray();
$data = $validator->valid();
+ // Logged in: the order email is always the account's. It isn't a field
+ // on the page then, and a submitted value isn't trusted.
+ if ($user = Auth::user()) {
+ $data['contact_email'] = $user->email;
+ }
+
$billingCountryId = $storeCountry?->id ?? ($data['billing_country_id'] ?? null);
$shippingCountryId = $storeCountry?->id ?? ($data['shipping_country_id'] ?? $billingCountryId);
+ // Company/ΑΦΜ only count when "I want an invoice" is ticked; the fields
+ // stay in the DOM (just hidden) when it isn't, so ignore what they send.
+ $wantsInvoice = $request->boolean('wants_invoice');
+
$billing = [
'first_name' => $data['billing_first_name'] ?? null,
'last_name' => $data['billing_last_name'] ?? null,
- 'company_name' => $data['billing_company_name'] ?? null,
- 'tax_identifier' => $data['billing_tax_identifier'] ?? null,
+ 'company_name' => $wantsInvoice ? ($data['billing_company_name'] ?? null) : null,
+ 'tax_identifier' => $wantsInvoice ? ($data['billing_tax_identifier'] ?? null) : null,
'line_one' => $data['billing_line_one'] ?? null,
'city' => $data['billing_city'] ?? null,
'state' => $data['billing_state'] ?? null,
@@ -178,11 +209,13 @@ public function saveAddress(string $locale, Request $request): JsonResponse
];
$shipping = $sameAsBilling
- ? [...$billing, 'delivery_instructions' => $data['shipping_delivery_instructions'] ?? null]
+ ? [
+ ...array_diff_key($billing, ['company_name' => 1, 'tax_identifier' => 1]),
+ 'delivery_instructions' => $data['shipping_delivery_instructions'] ?? null,
+ ]
: [
'first_name' => $data['shipping_first_name'] ?? null,
'last_name' => $data['shipping_last_name'] ?? null,
- 'company_name' => $data['shipping_company_name'] ?? null,
'line_one' => $data['shipping_line_one'] ?? null,
'city' => $data['shipping_city'] ?? null,
'state' => $data['shipping_state'] ?? null,
@@ -196,7 +229,11 @@ public function saveAddress(string $locale, Request $request): JsonResponse
$this->checkout->setBillingAddress($billing);
$cart = $this->checkout->setShippingAddress($shipping);
- $cart->meta = [...($cart->meta?->toArray() ?? []), 'ship_to_billing' => $sameAsBilling];
+ $cart->meta = [
+ ...($cart->meta?->toArray() ?? []),
+ 'ship_to_billing' => $sameAsBilling,
+ 'wants_invoice' => $wantsInvoice,
+ ];
$cart->save();
// Abandoned-cart-recovery opt-in — boboko-core owns the record (bool +
@@ -204,6 +241,10 @@ public function saveAddress(string $locale, Request $request): JsonResponse
// Deliberately its own scope, not merged with any future newsletter opt-in.
$this->checkout->setRecoveryConsent($request->boolean('recovery_consent'));
+ if (Auth::check()) {
+ $this->rememberRecoveryConsent($request->boolean('recovery_consent'));
+ }
+
$rateKeyAfter = $cart->shippingAddress?->only(['postcode', 'state', 'country_id']);
$rateChanged = $rateKeyAfter != $rateKeyBefore;
@@ -310,6 +351,15 @@ public function placeOrder(string $locale, Request $request): JsonResponse
], 422);
}
+ // Lenient autosave never requires these; this is the gate.
+ if (data_get($cart, 'meta.wants_invoice')
+ && (blank($cart->billingAddress?->company_name) || blank($cart->billingAddress?->tax_identifier))) {
+ return response()->json([
+ 'status' => 'invalid',
+ 'message' => __('checkout.page.invoice_required'),
+ ], 422);
+ }
+
// Same check Lunar's own ValidateCartForOrderCreation runs inside
// initiatePayment() (a product unpublished/deleted after it was
// added to the cart) — checked here first so the shopper is told
@@ -528,6 +578,116 @@ private function fragments(?Cart $cart, ?Collection $options, array $errors = []
]);
}
+ /**
+ * Logged-in shopper: fills any BLANK cart address field from the account
+ * (name, saved default address, phone, email), on every checkout load, so
+ * an account filled in after checkout started still shows up. Never
+ * overwrites anything already in the cart.
+ *
+ * Company/ΑΦΜ (and ticking "I want an invoice") only on the first pass
+ * (meta.account_prefilled): someone who then clears them or unticks the
+ * box for this order shouldn't get them back on the next reload.
+ *
+ * Writes only when something actually changes, so a normal reload costs
+ * nothing extra.
+ */
+ private function prefillFromAccount(Cart $cart): Cart
+ {
+ $user = Auth::user();
+ $customer = $this->account->customer($user);
+ $addresses = collect($this->account->addresses($user));
+ $saved = $addresses->firstWhere('shipping_default', true) ?? $addresses->first();
+ $firstPass = ! data_get($cart, 'meta.account_prefilled');
+
+ $fromAccount = array_filter([
+ 'first_name' => $customer?->first_name ?: $saved?->first_name,
+ 'last_name' => $customer?->last_name ?: $saved?->last_name,
+ 'line_one' => $saved?->line_one,
+ 'city' => $saved?->city,
+ 'state' => $saved?->state,
+ 'postcode' => $saved?->postcode,
+ 'country_id' => $this->storeCountry()?->id ?? $saved?->country_id,
+ 'contact_email' => $user->email,
+ 'contact_phone' => $saved?->contact_phone,
+ ], 'filled');
+
+ $invoice = $firstPass
+ ? array_filter([
+ 'company_name' => $customer?->company_name,
+ 'tax_identifier' => $customer?->tax_identifier,
+ ], 'filled')
+ : [];
+
+ $fields = ['first_name', 'last_name', 'company_name', 'tax_identifier', 'line_one', 'city',
+ 'state', 'postcode', 'country_id', 'contact_email', 'contact_phone'];
+
+ $fillBlanks = function (?array $current, array $values) {
+ $current ??= [];
+
+ foreach ($values as $key => $value) {
+ if (blank($current[$key] ?? null)) {
+ $current[$key] = $value;
+ }
+ }
+
+ return $current;
+ };
+
+ $billingBefore = $cart->billingAddress?->only($fields);
+ $billing = $fillBlanks($billingBefore, [...$fromAccount, ...$invoice]);
+
+ // Shipping has no company/ΑΦΜ (same shape saveAddress() writes).
+ $shipToBilling = (bool) data_get($cart, 'meta.ship_to_billing', true);
+ $shippingFields = [...array_diff($fields, ['company_name', 'tax_identifier']), 'delivery_instructions'];
+
+ $shippingBefore = $cart->shippingAddress?->only($shippingFields);
+ $shipping = $shipToBilling
+ ? [
+ ...array_diff_key($billing, ['company_name' => 1, 'tax_identifier' => 1]),
+ 'delivery_instructions' => $shippingBefore['delivery_instructions'] ?? null,
+ ]
+ : $fillBlanks($shippingBefore, $fromAccount);
+
+ if ($billing != ($billingBefore ?? []) || $shipping != ($shippingBefore ?? [])) {
+ $this->checkout->setBillingAddress($billing);
+ $cart = $this->checkout->setShippingAddress($shipping);
+ }
+
+ if ($firstPass) {
+ $cart->meta = [
+ ...($cart->meta?->toArray() ?? []),
+ 'account_prefilled' => true,
+ 'wants_invoice' => (bool) data_get($cart, 'meta.wants_invoice') || $invoice !== [],
+ ];
+ $cart->save();
+ }
+
+ return $cart;
+ }
+
+ /**
+ * The shopper's latest reminder choice, kept on their customer record
+ * (meta, same shape CheckoutService::setRecoveryConsent() writes on the
+ * cart) so their next checkout starts from it. The storefront's account
+ * page reads/writes the same keys. Candidate for a boboko-core method.
+ */
+ private function rememberRecoveryConsent(bool $consent): void
+ {
+ $customer = $this->account->customer(Auth::user());
+
+ if (! $customer || (bool) data_get($customer, 'meta.recovery_consent') === $consent) {
+ return;
+ }
+
+ $customer->meta = [
+ ...($customer->meta?->toArray() ?? []),
+ 'recovery_consent' => $consent,
+ 'recovery_consent_at' => $consent ? now()->toIso8601String() : null,
+ 'recovery_consent_policy_version' => $consent ? config('legal.privacy_policy_version') : null,
+ ];
+ $customer->save();
+ }
+
private function storeCountry(): ?Country
{
if (self::STORE_COUNTRY_ISO3 === null) {
diff --git a/app/Listeners/SaveAddressFromFirstOrder.php b/app/Listeners/SaveAddressFromFirstOrder.php
new file mode 100644
index 0000000..75b6b3a
--- /dev/null
+++ b/app/Listeners/SaveAddressFromFirstOrder.php
@@ -0,0 +1,57 @@
+order;
+ $user = $order->user;
+ $shipping = $order->shippingAddress;
+
+ if (! $user || ! $shipping || ! $shipping->line_one) {
+ return;
+ }
+
+ $customer = $this->account->customer($user);
+
+ if (! $customer) {
+ return;
+ }
+
+ if (! $customer->first_name && ! $customer->last_name) {
+ $this->account->updateProfile($user, [
+ 'first_name' => $shipping->first_name,
+ 'last_name' => $shipping->last_name,
+ ]);
+ }
+
+ if (collect($this->account->addresses($user))->isNotEmpty()) {
+ return;
+ }
+
+ $this->account->createAddress($user, [
+ ...$shipping->only(['first_name', 'last_name', 'line_one', 'city', 'state', 'postcode', 'country_id', 'contact_phone']),
+ 'contact_email' => $user->email,
+ 'shipping_default' => true,
+ 'billing_default' => true,
+ ]);
+ }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index f2d1d11..7fd65fe 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -46,6 +46,7 @@ protected function casts(): array
{
return [
'email_verified_at' => 'datetime',
+ 'terms_accepted_at' => 'datetime',
];
}
}
diff --git a/composer.lock b/composer.lock
index 700f0f5..e035eaa 100644
--- a/composer.lock
+++ b/composer.lock
@@ -935,16 +935,16 @@
},
{
"name": "composer/semver",
- "version": "3.4.4",
+ "version": "3.5.0",
"source": {
"type": "git",
"url": "https://github.com/composer/semver.git",
- "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95"
+ "reference": "f7a296f4c4cf8cb8bb83e35d6951a406bb11afa5"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95",
- "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95",
+ "url": "https://api.github.com/repos/composer/semver/zipball/f7a296f4c4cf8cb8bb83e35d6951a406bb11afa5",
+ "reference": "f7a296f4c4cf8cb8bb83e35d6951a406bb11afa5",
"shasum": ""
},
"require": {
@@ -986,7 +986,7 @@
"homepage": "http://robbast.nl"
}
],
- "description": "Semver library that offers utilities, version constraint parsing and validation.",
+ "description": "Version comparison library that offers utilities, version constraint parsing and validation.",
"keywords": [
"semantic",
"semver",
@@ -996,7 +996,7 @@
"support": {
"irc": "ircs://irc.libera.chat:6697/composer",
"issues": "https://github.com/composer/semver/issues",
- "source": "https://github.com/composer/semver/tree/3.4.4"
+ "source": "https://github.com/composer/semver/tree/3.5.0"
},
"funding": [
{
@@ -1008,7 +1008,7 @@
"type": "github"
}
],
- "time": "2025-08-20T19:15:30+00:00"
+ "time": "2026-09-24T14:38:51+00:00"
},
{
"name": "danharrin/date-format-converter",
diff --git a/config/checkout.php b/config/checkout.php
new file mode 100644
index 0000000..9758154
--- /dev/null
+++ b/config/checkout.php
@@ -0,0 +1,18 @@
+`, so the
+ * login page must send the shopper back there afterwards (3dealer's
+ * Auth\LoginController does). null: no login offered in checkout at all.
+ */
+ 'login_route' => 'login',
+
+];
diff --git a/database/migrations/2026_09_24_000002_add_terms_acceptance_to_users_table.php b/database/migrations/2026_09_24_000002_add_terms_acceptance_to_users_table.php
new file mode 100644
index 0000000..55eb641
--- /dev/null
+++ b/database/migrations/2026_09_24_000002_add_terms_acceptance_to_users_table.php
@@ -0,0 +1,29 @@
+timestamp('terms_accepted_at')->nullable();
+ $table->string('terms_version')->nullable();
+ $table->string('privacy_policy_version')->nullable();
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->dropColumn(['terms_accepted_at', 'terms_version', 'privacy_policy_version']);
+ });
+ }
+};
diff --git a/resources/css/checkout.css b/resources/css/checkout.css
index 444ba98..51a4648 100644
--- a/resources/css/checkout.css
+++ b/resources/css/checkout.css
@@ -504,35 +504,12 @@ .bbk-checkout-note {
font-size: 0.875rem;
}
-/* Contact tabs */
+/* Contact: "logged in as" line, or the guest login prompt */
-.bbk-checkout-tabs { display: flex; flex-direction: column; gap: 1rem; }
+.bbk-checkout-logged-in,
+.bbk-checkout-login-prompt { margin: 0; }
-.bbk-checkout-tab {
- display: inline-flex;
- width: fit-content;
- margin-right: 0.5rem;
- padding: 0.5rem 1rem;
- border: 1px solid var(--bbk-color-border);
- border-radius: var(--bbk-radius-sm);
- background: var(--bbk-color-bg);
- font: inherit;
- font-weight: 600;
- color: var(--bbk-color-muted);
- cursor: pointer;
- transition: background-color 0.15s ease, color 0.15s ease;
-}
-
-.bbk-checkout-tab[aria-selected="true"] {
- background: var(--bbk-color-text);
- border-color: var(--bbk-color-text);
- color: var(--bbk-color-bg);
-}
-
-.bbk-checkout-tab:focus-visible {
- outline: 2px solid var(--bbk-color-accent);
- outline-offset: 2px;
-}
+.bbk-checkout-login-prompt a { color: inherit; font-weight: 600; }
/* Fields */
@@ -602,6 +579,12 @@ .bbk-checkbox {
}
/* For a full-sentence label that can wrap — align the box to the first line. */
+/* "I want an invoice": company/ΑΦΜ only while ticked */
+
+.bbk-invoice { display: flex; flex-direction: column; gap: 1rem; }
+
+.bbk-invoice:not(:has(input[name="wants_invoice"]:checked)) .bbk-invoice-fields { display: none; }
+
.bbk-checkbox--stacked {
display: flex;
align-items: flex-start;
@@ -686,6 +669,8 @@ .bbk-checkout-continue {
font: inherit;
font-weight: 600;
text-align: center;
+ text-decoration: none;
+ box-sizing: border-box;
cursor: pointer;
transition: opacity 0.15s ease;
}
@@ -869,8 +854,3 @@ .bbk-address-lines {
font-size: 0.875rem;
color: var(--bbk-color-muted);
}
-
-.bbk-confirmation-continue {
- max-width: 280px;
- text-decoration: none;
-}
diff --git a/resources/js/checkout/bbk-checkout-form-controller.js b/resources/js/checkout/bbk-checkout-form-controller.js
index 7dfbe05..a8e720e 100644
--- a/resources/js/checkout/bbk-checkout-form-controller.js
+++ b/resources/js/checkout/bbk-checkout-form-controller.js
@@ -13,7 +13,6 @@ import { csrfToken } from './csrf'
// already uses). Shipping-method radios post to selectShippingUrl the same way.
export default class extends Controller {
static targets = [
- 'guestTab', 'loginTab', 'guestPanel', 'loginPanel',
'sameAsBilling', 'shippingFields',
'form', 'shippingOptions', 'status',
]
@@ -41,22 +40,6 @@ export default class extends Controller {
this.saveController?.abort()
}
- // ── Contact tabs ────────────────────────────────────────────────────
-
- showGuest() {
- this.guestPanelTarget.hidden = false
- this.loginPanelTarget.hidden = true
- this.guestTabTarget.setAttribute('aria-selected', 'true')
- this.loginTabTarget.setAttribute('aria-selected', 'false')
- }
-
- showLogin() {
- this.guestPanelTarget.hidden = true
- this.loginPanelTarget.hidden = false
- this.guestTabTarget.setAttribute('aria-selected', 'false')
- this.loginTabTarget.setAttribute('aria-selected', 'true')
- }
-
// ── Same as billing ────────────────────────────────────────────────
toggleSameAsBilling() {
@@ -93,7 +76,6 @@ export default class extends Controller {
// react to fields that actually belong to the address form.
const el = event.target
const belongsToForm = el.form?.id === 'bbk-address-form'
- || el.closest('[data-bbk-checkout-form-target="guestPanel"]')
if (!belongsToForm) return
// No status during the wait — it only shows once the request is in flight,
diff --git a/resources/views/account/show.blade.php b/resources/views/account/show.blade.php
index 4e8633d..abe75a0 100644
--- a/resources/views/account/show.blade.php
+++ b/resources/views/account/show.blade.php
@@ -133,6 +133,22 @@
+ {{-- Standing opt-in for abandoned-cart reminders (explicit, off by
+ default); checkout starts from it and can change it again. --}}
+ {{ __('storefront.account.emails_heading') }}
+
+
+ {!! __('storefront.auth.terms_notice', [ + 'terms' => route('legal.terms'), + 'privacy' => route('legal.privacy'), + ]) !!} +
+{{ __('checkout.page.confirmation_email_note') }}
+ {{-- Guests: logging in with the order's email attaches it to an account + (App\Listeners\ClaimGuestOrdersOnLogin), so it shows in their history. --}} + @guest + @if ($loginRoute = config('checkout.login_route')) ++ {{ __('checkout.page.confirmation_login_hint') }} + {{ __('checkout.page.login_link') }} +
+ @endif + @endguest ++ {{ __('checkout.page.logged_in_as') }} {{ auth()->user()->email }} +
+ @else + @if ($loginRoute) ++ {{ __('checkout.page.login_prompt') }} + {{ __('checkout.page.login_link') }} +
+ @endif - +{{ __('checkout.page.login_coming_soon') }}
-