Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e45b84636 | ||
|
|
437cbf2460 | ||
|
|
5425a0396f | ||
|
|
4d0e326cb9 | ||
|
|
d4f9766940 |
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||||
|
|
||||||
|
## [0.16.2] - 2026-09-09
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- `Lunar\Base\ShippingManifest` is a request-lifetime singleton whose `getOptions()` re-runs the
|
||||||
|
shipping modifier pipeline without ever clearing its `$options` collection first, and whose
|
||||||
|
`addOption()` keeps the first entry per `getIdentifier()` and silently drops any later one. In
|
||||||
|
practice, an option resolved for an earlier shipping address (or cart state) shadowed the
|
||||||
|
correct one after the address/region changed within the same request — e.g. a carrier priced
|
||||||
|
differently across two zones that both match an address would keep quoting the stale zone's
|
||||||
|
price, and `ApplyShipping` would price the cart total off that same stale option. Renamed
|
||||||
|
`Modules\Core\Shipping\Listeners\FlushLivePricingCache` to
|
||||||
|
`Modules\Core\Shipping\Listeners\InvalidateShippingOptions` and had it additionally call
|
||||||
|
`ShippingManifest::clearOptions()`, merged in because both invalidations fire on the exact same
|
||||||
|
event set (`CartLineAdded`, `CartLineUpdated`, `CartLineRemoved`, `CartCleared`,
|
||||||
|
`ShippingAddressSet`) — the only inputs the shipping modifier pipeline depends on.
|
||||||
|
|
||||||
|
## [0.16.1] - 2026-09-09
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- OTP login page (`resources/views/auth/filament/pages/login.blade.php`) had no visible spacing
|
||||||
|
between the email/OTP input, error text, and buttons following the Filament v3 → v4 upgrade.
|
||||||
|
The view relied on a bare `grid gap-y-4` Tailwind utility class, but since this view ships from
|
||||||
|
the `boboko-core` package rather than a consuming app, that class was never present in any
|
||||||
|
host app's compiled Tailwind output. Replaced with an inline `style` (flex column, `row-gap:
|
||||||
|
1rem`) so the layout no longer depends on the consuming app's Tailwind content scanning.
|
||||||
|
|
||||||
## [0.16.0] - 2026-09-08
|
## [0.16.0] - 2026-09-08
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
"name": "boboko/core",
|
"name": "boboko/core",
|
||||||
"description": "Core module — authentication and shared panel behaviour",
|
"description": "Core module — authentication and shared panel behaviour",
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"version": "0.16.0",
|
"version": "0.16.2",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Modules\\Core\\": "src/"
|
"Modules\\Core\\": "src/"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
@if (! $otpSent)
|
@if (! $otpSent)
|
||||||
<form wire:submit="requestOtp">
|
<form wire:submit="requestOtp">
|
||||||
<div class="grid gap-y-4">
|
<div style="display: flex; flex-direction: column; row-gap: 1rem;">
|
||||||
<x-filament::input.wrapper>
|
<x-filament::input.wrapper>
|
||||||
<x-filament::input
|
<x-filament::input
|
||||||
type="email"
|
type="email"
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
</form>
|
</form>
|
||||||
@else
|
@else
|
||||||
<form wire:submit="authenticate">
|
<form wire:submit="authenticate">
|
||||||
<div class="grid gap-y-4">
|
<div style="display: flex; flex-direction: column; row-gap: 1rem;">
|
||||||
<p class="text-sm text-gray-500">
|
<p class="text-sm text-gray-500">
|
||||||
A login code was sent to <strong>{{ $email }}</strong>.
|
A login code was sent to <strong>{{ $email }}</strong>.
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class StorefrontLabels
|
|||||||
'nav.account' => ['en' => 'Account', 'el' => 'Λογαριασμός'],
|
'nav.account' => ['en' => 'Account', 'el' => 'Λογαριασμός'],
|
||||||
'nav.back' => ['en' => 'Back', 'el' => 'Πίσω'],
|
'nav.back' => ['en' => 'Back', 'el' => 'Πίσω'],
|
||||||
'nav.contact' => ['en' => 'Contact', 'el' => 'Επικοινωνία'],
|
'nav.contact' => ['en' => 'Contact', 'el' => 'Επικοινωνία'],
|
||||||
|
'nav.close' => ['en' => 'Close', 'el' => 'Κλείσιμο'],
|
||||||
'cart.empty' => ['en' => 'Your cart is empty', 'el' => 'Το καλάθι σας είναι άδειο'],
|
'cart.empty' => ['en' => 'Your cart is empty', 'el' => 'Το καλάθι σας είναι άδειο'],
|
||||||
'cart.checkout' => ['en' => 'Checkout', 'el' => 'Ολοκλήρωση Παραγγελίας'],
|
'cart.checkout' => ['en' => 'Checkout', 'el' => 'Ολοκλήρωση Παραγγελίας'],
|
||||||
'cart.total' => ['en' => 'Total', 'el' => 'Σύνολο'],
|
'cart.total' => ['en' => 'Total', 'el' => 'Σύνολο'],
|
||||||
@@ -67,6 +68,7 @@ class StorefrontLabels
|
|||||||
'en' => '{0} No products found|{1} Showing :first–:last of :total result|[2,*] Showing :first–:last of :total results',
|
'en' => '{0} No products found|{1} Showing :first–:last of :total result|[2,*] Showing :first–:last of :total results',
|
||||||
'el' => '{0} Δεν βρέθηκαν προϊόντα|{1} Εμφάνιση :first–:last από :total αποτέλεσμα|[2,*] Εμφάνιση :first–:last από :total αποτελέσματα',
|
'el' => '{0} Δεν βρέθηκαν προϊόντα|{1} Εμφάνιση :first–:last από :total αποτέλεσμα|[2,*] Εμφάνιση :first–:last από :total αποτελέσματα',
|
||||||
],
|
],
|
||||||
|
'shop.all_products' => ['en' => 'All Products', 'el' => 'Όλα τα Προϊόντα'],
|
||||||
'shop.sort_label' => ['en' => 'Sort products', 'el' => 'Ταξινόμηση προϊόντων'],
|
'shop.sort_label' => ['en' => 'Sort products', 'el' => 'Ταξινόμηση προϊόντων'],
|
||||||
'shop.sort_default' => ['en' => 'Default sorting', 'el' => 'Προεπιλεγμένη ταξινόμηση'],
|
'shop.sort_default' => ['en' => 'Default sorting', 'el' => 'Προεπιλεγμένη ταξινόμηση'],
|
||||||
'shop.sort_popularity' => ['en' => 'Popularity', 'el' => 'Δημοφιλή'],
|
'shop.sort_popularity' => ['en' => 'Popularity', 'el' => 'Δημοφιλή'],
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ use Modules\Core\Shipping\Carriers\BoxNow\BoxNowRateDriver;
|
|||||||
use Modules\Core\Shipping\Contracts\CarrierFulfillmentInterface;
|
use Modules\Core\Shipping\Contracts\CarrierFulfillmentInterface;
|
||||||
use Modules\Core\Shipping\Filament\Pages\ManageShippingRates;
|
use Modules\Core\Shipping\Filament\Pages\ManageShippingRates;
|
||||||
use Modules\Core\Shipping\Jobs\PollShipmentTrackingJob;
|
use Modules\Core\Shipping\Jobs\PollShipmentTrackingJob;
|
||||||
use Modules\Core\Shipping\Listeners\FlushLivePricingCache;
|
use Modules\Core\Shipping\Listeners\InvalidateShippingOptions;
|
||||||
use Modules\Core\Shipping\Models\Shipment;
|
use Modules\Core\Shipping\Models\Shipment;
|
||||||
|
|
||||||
class ShippingServiceProvider extends ServiceProvider
|
class ShippingServiceProvider extends ServiceProvider
|
||||||
@@ -70,7 +70,7 @@ class ShippingServiceProvider extends ServiceProvider
|
|||||||
});
|
});
|
||||||
|
|
||||||
foreach ([CartLineAdded::class, CartLineUpdated::class, CartLineRemoved::class, CartCleared::class, ShippingAddressSet::class] as $event) {
|
foreach ([CartLineAdded::class, CartLineUpdated::class, CartLineRemoved::class, CartCleared::class, ShippingAddressSet::class] as $event) {
|
||||||
Event::listen($event, [FlushLivePricingCache::class, 'handle']);
|
Event::listen($event, [InvalidateShippingOptions::class, 'handle']);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deferred: the Shipping facade resolves a binding registered in
|
// Deferred: the Shipping facade resolves a binding registered in
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use Lunar\Shipping\Models\ShippingRate;
|
|||||||
* across carts: the quote depends on cart-specific weight/quantity/
|
* across carts: the quote depends on cart-specific weight/quantity/
|
||||||
* destination (see docs/checkout.md).
|
* destination (see docs/checkout.md).
|
||||||
*
|
*
|
||||||
* Invalidated by Modules\Core\Shipping\Listeners\FlushLivePricingCache on
|
* Invalidated by Modules\Core\Shipping\Listeners\InvalidateShippingOptions on
|
||||||
* the only two things that can change what this cart's quote should be: a
|
* the only two things that can change what this cart's quote should be: a
|
||||||
* cart line changing (add/update/remove/clear) or the shipping address
|
* cart line changing (add/update/remove/clear) or the shipping address
|
||||||
* changing. Deliberately NOT invalidated on order placement — the price
|
* changing. Deliberately NOT invalidated on order placement — the price
|
||||||
|
|||||||
+23
-8
@@ -3,6 +3,7 @@
|
|||||||
namespace Modules\Core\Shipping\Listeners;
|
namespace Modules\Core\Shipping\Listeners;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Cache;
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
use Lunar\Facades\ShippingManifest;
|
||||||
use Lunar\Shipping\Facades\Shipping;
|
use Lunar\Shipping\Facades\Shipping;
|
||||||
use Lunar\Shipping\Models\ShippingRate;
|
use Lunar\Shipping\Models\ShippingRate;
|
||||||
use Modules\Core\Cart\Events\CartCleared;
|
use Modules\Core\Cart\Events\CartCleared;
|
||||||
@@ -13,17 +14,29 @@ use Modules\Core\Checkout\Events\ShippingAddressSet;
|
|||||||
use Modules\Core\Shipping\Contracts\SupportsLivePricing;
|
use Modules\Core\Shipping\Contracts\SupportsLivePricing;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flushes Modules\Core\Shipping\Concerns\CachesLivePricing's cached quotes
|
* Invalidates everything that caches or memoises resolved shipping options
|
||||||
* for a cart on the only two things that can change what they should be: a
|
* for a cart, on the only two things that can change what they should be: a
|
||||||
* cart line changing (weight/quantity) or the shipping address changing
|
* cart line changing (weight/quantity) or the shipping address changing
|
||||||
* (destination). See that trait's docblock for why order placement is
|
* (destination).
|
||||||
* deliberately not a trigger here.
|
|
||||||
*
|
*
|
||||||
* Only rates whose method's driver implements SupportsLivePricing are ever
|
* Two things need clearing here, both stale for the same reason:
|
||||||
* cached by CachesLivePricing, so only their ids need a forget() call —
|
*
|
||||||
* no need to touch every ShippingRate row on every cart change.
|
* - Modules\Core\Shipping\Concerns\CachesLivePricing's per-rate cache (see
|
||||||
|
* that trait's docblock for why order placement is deliberately not a
|
||||||
|
* trigger). Only rates whose method's driver implements
|
||||||
|
* SupportsLivePricing are ever cached by it, so only their ids need a
|
||||||
|
* forget() call — no need to touch every ShippingRate row on every cart
|
||||||
|
* change.
|
||||||
|
* - Lunar\Base\ShippingManifest's $options collection, which is a
|
||||||
|
* request-lifetime singleton: ShippingManifest::getOptions() re-runs the
|
||||||
|
* modifier pipeline on every call but never clears $options first, and
|
||||||
|
* addOption() keeps the first entry per identifier and silently drops any
|
||||||
|
* later one — so an option resolved for an earlier address/cart state
|
||||||
|
* shadows the correct one after that state changes, within the same
|
||||||
|
* request. clearOptions() forces the next getOptions() call to resolve
|
||||||
|
* fresh.
|
||||||
*/
|
*/
|
||||||
class FlushLivePricingCache
|
class InvalidateShippingOptions
|
||||||
{
|
{
|
||||||
public function handle(CartLineAdded|CartLineUpdated|CartLineRemoved|CartCleared|ShippingAddressSet $event): void
|
public function handle(CartLineAdded|CartLineUpdated|CartLineRemoved|CartCleared|ShippingAddressSet $event): void
|
||||||
{
|
{
|
||||||
@@ -32,6 +45,8 @@ class FlushLivePricingCache
|
|||||||
foreach ($this->livePricingRateIds() as $rateId) {
|
foreach ($this->livePricingRateIds() as $rateId) {
|
||||||
Cache::forget("shipping.live_price.{$rateId}.{$cart->id}");
|
Cache::forget("shipping.live_price.{$rateId}.{$cart->id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ShippingManifest::clearOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Reference in New Issue
Block a user