From ee042b479bd5c5ccded31f3ea5b043ddadcf502f Mon Sep 17 00:00:00 2001 From: Jade Geels Date: Tue, 12 Nov 2024 13:18:06 +0000 Subject: [PATCH 01/31] PHPStan level 1 (#620) --- .github/workflows/analyse.yml | 42 ++++++++++++++++++++++++++ composer.json | 3 ++ config/rapidez/models.php | 1 + phpstan.neon | 12 ++++++++ src/Actions/DecodeJwt.php | 3 +- src/Auth/MagentoCartTokenGuard.php | 2 +- src/Auth/MagentoCustomerTokenGuard.php | 2 +- src/Casts/QuoteItems.php | 0 src/ContentVariables/Widget.php | 4 ++- src/Facades/Rapidez.php | 1 + src/Models/Customer.php | 2 +- src/Models/Product.php | 2 +- src/Models/ProductReviewSummary.php | 2 +- src/Rapidez.php | 1 + tests/DuskTestCaseSetup.php | 3 +- 15 files changed, 72 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/analyse.yml create mode 100644 phpstan.neon delete mode 100644 src/Casts/QuoteItems.php diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml new file mode 100644 index 000000000..34298a876 --- /dev/null +++ b/.github/workflows/analyse.yml @@ -0,0 +1,42 @@ +name: analyse + +on: + push: + branches: + - master + - '*.x' + pull_request: + +jobs: + analyse: + runs-on: ubuntu-latest + strategy: + fail-fast: true + matrix: + php: [8.3] + stability: [prefer-stable] + include: + - laravel: 10.* + testbench: 8.* + - laravel: 11.* + testbench: 9.* + + steps: + - uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.RAPIDEZ_ACTIONS_ACCOUNT_PAT }} + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo + coverage: none + + - name: Install dependencies + run: | + composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update + composer update --${{ matrix.stability }} --prefer-dist --no-interaction + - name: Analyse + run: composer analyse diff --git a/composer.json b/composer.json index b8e7d5e0b..be1cca24b 100644 --- a/composer.json +++ b/composer.json @@ -29,9 +29,11 @@ "tormjens/eventy": "^0.8" }, "require-dev": { + "larastan/larastan": "^2.9", "laravel/dusk": "^8.2", "orchestra/testbench": "^9.4", "orchestra/testbench-dusk": "^9.7", + "phpstan/phpstan": "^1.12", "phpunit/phpunit": "^10.5.34|^11.3.5" }, "autoload": { @@ -61,6 +63,7 @@ } }, "scripts": { + "analyse": "phpstan --memory-limit=256M", "dusk:prepare": [ "./vendor/bin/dusk-updater detect --auto-update", "@php -r \"file_exists('phpunit.dusk.xml') || copy('phpunit.dusk.xml.dist', 'phpunit.dusk.xml'); \"" diff --git a/config/rapidez/models.php b/config/rapidez/models.php index 4782a6300..864efc62c 100644 --- a/config/rapidez/models.php +++ b/config/rapidez/models.php @@ -6,6 +6,7 @@ 'attribute' => Rapidez\Core\Models\Attribute::class, 'product' => Rapidez\Core\Models\Product::class, 'category' => Rapidez\Core\Models\Category::class, + 'customer_group' => Rapidez\Core\Models\CustomerGroup::class, 'category_product' => Rapidez\Core\Models\CategoryProduct::class, 'customer' => Rapidez\Core\Models\Customer::class, 'config' => Rapidez\Core\Models\Config::class, diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 000000000..1b0abc9c6 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,12 @@ +includes: + - ./vendor/larastan/larastan/extension.neon + +parameters: + paths: + - src/ + - tests/ + - routes/ + ignoreErrors: + - '#^Result of static method TorMorten\\Eventy\\Events::#' + - "#Access to an undefined property#" + level: 1 diff --git a/src/Actions/DecodeJwt.php b/src/Actions/DecodeJwt.php index 24037cb4c..28a9d1e02 100644 --- a/src/Actions/DecodeJwt.php +++ b/src/Actions/DecodeJwt.php @@ -15,6 +15,7 @@ use Lcobucci\JWT\Validation\Constraint\SignedWith; use Lcobucci\JWT\Validation\ConstraintViolation; use Lcobucci\JWT\Validation\RequiredConstraintsViolated; +use Rapidez\Core\Exceptions\DecryptionException; class DecodeJwt { @@ -39,7 +40,7 @@ public static function decode(string $jwt): UnencryptedToken } } - throw $exception; + throw $exception ?? new DecryptionException('No crypt keys defined.'); } /** diff --git a/src/Auth/MagentoCartTokenGuard.php b/src/Auth/MagentoCartTokenGuard.php index a866cd67f..db7facb9e 100644 --- a/src/Auth/MagentoCartTokenGuard.php +++ b/src/Auth/MagentoCartTokenGuard.php @@ -16,7 +16,7 @@ public static function register() }); auth()->extend('magento-cart', function (Application $app, string $name, array $config) { - return new static(auth()->createUserProvider($config['provider']), request(), 'mask', 'mask'); + return new self(auth()->createUserProvider($config['provider']), request(), 'mask', 'mask'); }); config([ diff --git a/src/Auth/MagentoCustomerTokenGuard.php b/src/Auth/MagentoCustomerTokenGuard.php index cb6fc47f7..d1afe3ca5 100644 --- a/src/Auth/MagentoCustomerTokenGuard.php +++ b/src/Auth/MagentoCustomerTokenGuard.php @@ -17,7 +17,7 @@ public static function register() }); auth()->extend('magento-customer', function (Application $app, string $name, array $config) { - return new static(auth()->createUserProvider($config['provider']), request(), 'token', 'token'); + return new self(auth()->createUserProvider($config['provider']), request(), 'token', 'token'); }); config([ diff --git a/src/Casts/QuoteItems.php b/src/Casts/QuoteItems.php deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/ContentVariables/Widget.php b/src/ContentVariables/Widget.php index 7df511ddf..a9f76c25c 100644 --- a/src/ContentVariables/Widget.php +++ b/src/ContentVariables/Widget.php @@ -9,12 +9,14 @@ public function __invoke($content) return preg_replace_callback('/{{widget type="(.*?)" (.*?)}}/ms', function ($matches) { [$full, $type, $parameters] = $matches; preg_match_all('/(.*?)="(.*?)"/ms', $parameters, $parameters, PREG_SET_ORDER); + + $options = []; foreach ($parameters as $parameter) { [$full, $parameter, $value] = $parameter; $options[trim($parameter)] = trim($value); } - if (! isset($type)) { + if (! $type) { return ''; } diff --git a/src/Facades/Rapidez.php b/src/Facades/Rapidez.php index 274472877..a605ff5e9 100644 --- a/src/Facades/Rapidez.php +++ b/src/Facades/Rapidez.php @@ -14,6 +14,7 @@ * @method static array getStores($storeId = null) * @method static array getStore($storeId) * @method static void setStore($store) + * @method static mixed withStore(\Rapidez\Core\Models\Store|array|callable|int|string $store, callable $callback, mixed ...$args) * * @see \Rapidez\Core\Rapidez */ diff --git a/src/Models/Customer.php b/src/Models/Customer.php index 418302db8..e1210f607 100644 --- a/src/Models/Customer.php +++ b/src/Models/Customer.php @@ -29,7 +29,7 @@ public function oauthTokens() public function group() { - return $this->belongsTo(CustomerGroup::class, 'group_id'); + return $this->belongsTo(config('rapidez.models.customer_group'), 'group_id'); } public function getRememberTokenName() diff --git a/src/Models/Product.php b/src/Models/Product.php index a5b3971fb..d37d64a3c 100644 --- a/src/Models/Product.php +++ b/src/Models/Product.php @@ -122,7 +122,7 @@ public function categoryProducts(): HasMany public function reviewSummary(): HasOne { return $this->hasOne( - config('rapidez.models.product_review_summary', Rapidez\Core\Models\ProductReviewSummary::class), + config('rapidez.models.product_review_summary', \Rapidez\Core\Models\ProductReviewSummary::class), 'entity_pk_value' ); } diff --git a/src/Models/ProductReviewSummary.php b/src/Models/ProductReviewSummary.php index 88ef9dc69..e1a0491dd 100644 --- a/src/Models/ProductReviewSummary.php +++ b/src/Models/ProductReviewSummary.php @@ -27,7 +27,7 @@ protected static function booting(): void public function product(): BelongsTo { return $this->belongsTo( - config('rapidez.models.product', Rapidez\Core\Models\Product::class), + config('rapidez.models.product', \Rapidez\Core\Models\Product::class), 'entity_pk_value' ); } diff --git a/src/Rapidez.php b/src/Rapidez.php index 911e8f6b7..dc1024ecc 100644 --- a/src/Rapidez.php +++ b/src/Rapidez.php @@ -8,6 +8,7 @@ use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Event; +use Rapidez\Core\Exceptions\StoreNotFoundException; use Rapidez\Core\Models\Store; class Rapidez diff --git a/tests/DuskTestCaseSetup.php b/tests/DuskTestCaseSetup.php index 39e59f011..1556d074e 100644 --- a/tests/DuskTestCaseSetup.php +++ b/tests/DuskTestCaseSetup.php @@ -39,7 +39,7 @@ protected function setUp(): void Browser::macro('waitUntilIdle', function ($timeout = 120) { /** @var Browser $this */ - $this->waitUntilTrueForDuration('window.app?.$data?.loading !== true && await new Promise((resolve, reject) => window.requestIdleCallback((deadline) => resolve(!deadline.didTimeout), {timeout: 5}))', $timeout); + $this->waitUntilTrueForDuration('window.app?.$data?.loading !== true && await new Promise((resolve, reject) => window.requestIdleCallback((deadline) => resolve(!deadline.didTimeout), {timeout: 5}))', $timeout); // @phpstan-ignore-line return $this; }); @@ -63,6 +63,7 @@ protected function setUp(): void ->visit($productUrl); } + // @phpstan-ignore-next-line $this ->waitUntilIdle() ->pressAndWaitFor('@add-to-cart', 60) From 2a5672afd999e5ac51c2b9290a507b6c3158e384 Mon Sep 17 00:00:00 2001 From: Bob Wezelman Date: Tue, 12 Nov 2024 14:51:16 +0100 Subject: [PATCH 02/31] Fix error of undefined when extensions is not set (#635) --- resources/js/callbacks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/callbacks.js b/resources/js/callbacks.js index 1b5c03530..1f2f6c0d0 100644 --- a/resources/js/callbacks.js +++ b/resources/js/callbacks.js @@ -81,7 +81,7 @@ Vue.prototype.checkResponseForExpiredCart = async function (variables, response) if ( response?.errors?.some( (error) => - error.extensions.category === 'graphql-no-such-entity' && + error.extensions?.category === 'graphql-no-such-entity' && // Untested, but something like this is maybe a better idea as // we're using a lot of different mutations in the checkout. error.path.some((path) => path.toLowerCase().includes('cart')), From 9ad38717b339b18c544bec137f2900beebf37244 Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:40:25 +0100 Subject: [PATCH 03/31] Extracted login to a loginByToken function (#636) --- resources/js/stores/useUser.js | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/resources/js/stores/useUser.js b/resources/js/stores/useUser.js index ed9b53cf1..8a338f4d1 100644 --- a/resources/js/stores/useUser.js +++ b/resources/js/stores/useUser.js @@ -131,29 +131,25 @@ export const login = async function (email, password) { password: password, }, ) - // Set Auth Token .then(async (response) => { - token.value = response.data.generateCustomerToken.token - - return response - }) - // Link or Fetch Cart - .then(async (response) => { - if (mask.value) { - await linkUserToCart() - } else { - await fetchCustomerCart() - } - return response - }) - // Fire logged in event - .then(async (response) => { - window.app.$emit('logged-in') + await loginByToken(response.data.generateCustomerToken.token) return response }) ) } +export const loginByToken = async function (customerToken) { + token.value = customerToken; + + if (mask.value) { + await linkUserToCart() + } else { + await fetchCustomerCart() + } + + window.app.$emit('logged-in') +} + export const logout = async function () { await magentoGraphQL('mutation { revokeCustomerToken { result } }', {}, { notifyOnError: false, redirectOnExpiration: false }).finally( async () => { From a67c7abb7866b023d96a3e2fa749f8c20d6e4242 Mon Sep 17 00:00:00 2001 From: royduin Date: Wed, 13 Nov 2024 15:41:26 +0000 Subject: [PATCH 04/31] Apply fixes from Prettier --- resources/js/stores/useUser.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/resources/js/stores/useUser.js b/resources/js/stores/useUser.js index 8a338f4d1..5fd2f6e04 100644 --- a/resources/js/stores/useUser.js +++ b/resources/js/stores/useUser.js @@ -123,23 +123,20 @@ export const register = async function (email, firstname, lastname, password, in } export const login = async function (email, password) { - return ( - magentoGraphQL( - 'mutation generateCustomerToken ($email: String!, $password: String!) { generateCustomerToken (email: $email, password: $password) { token } }', - { - email: email, - password: password, - }, - ) - .then(async (response) => { - await loginByToken(response.data.generateCustomerToken.token) - return response - }) - ) + return magentoGraphQL( + 'mutation generateCustomerToken ($email: String!, $password: String!) { generateCustomerToken (email: $email, password: $password) { token } }', + { + email: email, + password: password, + }, + ).then(async (response) => { + await loginByToken(response.data.generateCustomerToken.token) + return response + }) } export const loginByToken = async function (customerToken) { - token.value = customerToken; + token.value = customerToken if (mask.value) { await linkUserToCart() From 07bb07767a192d63f404422b30c00a47adf83cb2 Mon Sep 17 00:00:00 2001 From: Jade Geels Date: Wed, 13 Nov 2024 15:50:46 +0000 Subject: [PATCH 05/31] Revert #626 (#639) --- .../views/checkout/steps/login.blade.php | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/resources/views/checkout/steps/login.blade.php b/resources/views/checkout/steps/login.blade.php index 18f80470e..2ac6e1f26 100644 --- a/resources/views/checkout/steps/login.blade.php +++ b/resources/views/checkout/steps/login.blade.php @@ -8,24 +8,13 @@ v-bind:disabled="loggedIn" required /> - + @if (App::providerIsLoaded('Rapidez\Account\AccountServiceProvider')) @lang('Forgot your password?') From 53f93a175104206845a9e64bc278ad919bde0055 Mon Sep 17 00:00:00 2001 From: Jade Geels Date: Thu, 14 Nov 2024 09:30:33 +0000 Subject: [PATCH 06/31] Fix dusk tests (#640) --- resources/views/checkout/steps/login.blade.php | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/resources/views/checkout/steps/login.blade.php b/resources/views/checkout/steps/login.blade.php index 2ac6e1f26..9b46d1014 100644 --- a/resources/views/checkout/steps/login.blade.php +++ b/resources/views/checkout/steps/login.blade.php @@ -8,13 +8,15 @@ v-bind:disabled="loggedIn" required /> - + @if (App::providerIsLoaded('Rapidez\Account\AccountServiceProvider')) @lang('Forgot your password?') From ac44a0922c008a618f18dd50fe0c72710238df7f Mon Sep 17 00:00:00 2001 From: Jade Geels Date: Thu, 14 Nov 2024 13:43:36 +0000 Subject: [PATCH 07/31] Image switching length fix (#642) --- resources/js/components/Product/Images.vue | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/resources/js/components/Product/Images.vue b/resources/js/components/Product/Images.vue index 353254504..969ee0e6d 100644 --- a/resources/js/components/Product/Images.vue +++ b/resources/js/components/Product/Images.vue @@ -22,6 +22,7 @@ export default { Object.values(window.config.product.super_attributes).filter((attribute) => attribute.update_image).length ) { self.images = simpleProduct.images + self.active = Math.min(self.active, self.images.length - 1) } }) }) @@ -40,10 +41,10 @@ export default { }, keyPressed(e) { - if (e.key == 'ArrowLeft' && this.active) { + if (e.key == 'ArrowLeft' && this.active > 0) { // left this.active-- - } else if (e.key == 'ArrowRight' && this.active != this.images.length - 1) { + } else if (e.key == 'ArrowRight' && this.active < this.images.length - 1) { // right this.active++ } else if (e.key == 'Escape') { From 4a7f190e4473bf2a8606571542ea1687448983eb Mon Sep 17 00:00:00 2001 From: Bob Wezelman Date: Tue, 19 Nov 2024 10:58:42 +0100 Subject: [PATCH 08/31] Product page and listing images fix (#649) --- resources/views/listing/partials/item.blade.php | 2 +- resources/views/product/partials/gallery/slider.blade.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/views/listing/partials/item.blade.php b/resources/views/listing/partials/item.blade.php index 43b25e47f..4a6a62e62 100644 --- a/resources/views/listing/partials/item.blade.php +++ b/resources/views/listing/partials/item.blade.php @@ -9,7 +9,7 @@ {{ $product->name }} Date: Tue, 19 Nov 2024 10:04:50 +0000 Subject: [PATCH 09/31] Update CHANGELOG --- CHANGELOG.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb3af9953..ec92ed890 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,17 @@ # Changelog -[Unreleased changes](https://github.com/rapidez/core/compare/2.14.0...2.14.0) -## [2.14.0](https://github.com/rapidez/core/releases/tag/2.14.0) - 2024-10-17 +[Unreleased changes](https://github.com/rapidez/core/compare/2.16.0...2.16.0) +## [2.16.0](https://github.com/rapidez/core/releases/tag/2.16.0) - 2024-11-19 ### Changed -- Updated the frontend dependencies including Vite 5 (#584) -- Drop support for Magento 2.4.5 (#589) +- Resize path helper (#633, #648) +- Extracted login to a loginByToken function (#637) ### Fixed -- Filter out double slashes from API endpoints (#559) -- Fix casts in quote model (#542) -- Correct error message when placing an order fails (#599) +- Image switching length fix (#641) +- Fix error of undefined when extensions is not set (#634) ## [2.13.0](https://github.com/rapidez/core/releases/tag/2.13.0) - 2024-09-27 From a0940c6bcbd7296f28ec5645160069cde512229c Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:11:48 +0100 Subject: [PATCH 10/31] Apply middlewares from fallback routes (#617) --- src/Http/Controllers/FallbackController.php | 34 +++++++++++++++++---- src/Rapidez.php | 27 +++++++++++++--- 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/Http/Controllers/FallbackController.php b/src/Http/Controllers/FallbackController.php index 28c2e9df3..107303046 100644 --- a/src/Http/Controllers/FallbackController.php +++ b/src/Http/Controllers/FallbackController.php @@ -3,28 +3,43 @@ namespace Rapidez\Core\Http\Controllers; use Exception; +use Illuminate\Container\Container; use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\RecordsNotFoundException; use Illuminate\Http\Request; +use Symfony\Component\HttpFoundation\Response; +use Illuminate\Routing\Controller; use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException; +use Illuminate\Routing\Pipeline; +use Illuminate\Routing\Route; +use Illuminate\Routing\Router; use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; use Rapidez\Core\Facades\Rapidez; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Exception\RouteNotFoundException; -class FallbackController +class FallbackController extends Controller { + protected $container; + + public function __construct( + ?Container $container = null, + protected Router $router + ) { + $this->container = $container ?: new Container; + } + public function __invoke(Request $request) { $cacheKey = 'fallbackroute-' . md5($request->url()); $route = Cache::get($cacheKey); - if ($route && $response = $this->tryRoute($route)) { + if ($route && $response = $this->tryRoute($route['route'], $request)) { return $response; } foreach (Rapidez::getAllFallbackRoutes() as $route) { - if (! ($response = $this->tryRoute($route))) { + if (! ($response = $this->tryRoute($route['route'], $request))) { continue; } @@ -41,13 +56,20 @@ public function __invoke(Request $request) abort(404); } - protected function tryRoute($route) + protected function tryRoute(Route $route, $request): ?Response { try { - $response = App::call($route['action']['uses']); + $middleware = $this->router->gatherRouteMiddleware($route->setContainer($this->container)); + /** @var Response $response */ + $response = (new Pipeline($this->container)) + ->send($request) + ->through($middleware) + ->then(fn ($request) => $this->router->prepareResponse( + $request, $route->bind($request)->run() + )); // Null response is equal to no response or 404. - if ($response === null) { + if (!$response->getContent() || $response->isNotFound()) { abort(404); } diff --git a/src/Rapidez.php b/src/Rapidez.php index dc1024ecc..2958c461b 100644 --- a/src/Rapidez.php +++ b/src/Rapidez.php @@ -2,7 +2,9 @@ namespace Rapidez\Core; +use Illuminate\Routing\Route; use Illuminate\Routing\RouteAction; +use Illuminate\Routing\Router; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Illuminate\Support\Facades\App; @@ -10,6 +12,7 @@ use Illuminate\Support\Facades\Event; use Rapidez\Core\Exceptions\StoreNotFoundException; use Rapidez\Core\Models\Store; +use ReflectionClass; class Rapidez { @@ -17,20 +20,34 @@ class Rapidez public function __construct(protected Collection $routes) {} - public function addFallbackRoute($action, $position = 9999) + public function addFallbackRoute(Route|array|string $action, $position = 9999) { $this->routes->push([ - 'action' => RouteAction::parse('', $action), + 'route' => $this->actionToRoute($action)->fallback(), 'position' => $position, ]); return $this; } - public function removeFallbackRoute($action) + private function actionToRoute(Route|array|string $action): Route { - $action = RouteAction::parse('', $action); - $this->routes = $this->routes->reject(fn ($route) => $route['action'] === $action); + if($action instanceof Route) + { + return $action; + } + + $router = new ReflectionClass(Router::class); + $createRoute = $router->getMethod('createRoute'); + $createRoute->setAccessible(true); + + return $createRoute->invoke(app(Router::class), ['GET'], '', $action); + } + + public function removeFallbackRoute(Route|array|string $action) + { + $action = $this->actionToRoute($action); + $this->routes = $this->routes->reject(fn ($route) => $route['route']->action === $action->action); return $this; } From 5181586c5b6db50791f5e18b0789b05b4b709216 Mon Sep 17 00:00:00 2001 From: royduin Date: Wed, 20 Nov 2024 09:12:10 +0000 Subject: [PATCH 11/31] Apply fixes from Duster --- src/Http/Controllers/FallbackController.php | 7 +++---- src/Rapidez.php | 4 +--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/Http/Controllers/FallbackController.php b/src/Http/Controllers/FallbackController.php index 107303046..3872f7e53 100644 --- a/src/Http/Controllers/FallbackController.php +++ b/src/Http/Controllers/FallbackController.php @@ -7,15 +7,14 @@ use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\RecordsNotFoundException; use Illuminate\Http\Request; -use Symfony\Component\HttpFoundation\Response; use Illuminate\Routing\Controller; use Illuminate\Routing\Exceptions\BackedEnumCaseNotFoundException; use Illuminate\Routing\Pipeline; use Illuminate\Routing\Route; use Illuminate\Routing\Router; -use Illuminate\Support\Facades\App; use Illuminate\Support\Facades\Cache; use Rapidez\Core\Facades\Rapidez; +use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Routing\Exception\RouteNotFoundException; @@ -24,7 +23,7 @@ class FallbackController extends Controller protected $container; public function __construct( - ?Container $container = null, + ?Container $container, protected Router $router ) { $this->container = $container ?: new Container; @@ -69,7 +68,7 @@ protected function tryRoute(Route $route, $request): ?Response )); // Null response is equal to no response or 404. - if (!$response->getContent() || $response->isNotFound()) { + if (! $response->getContent() || $response->isNotFound()) { abort(404); } diff --git a/src/Rapidez.php b/src/Rapidez.php index 2958c461b..94a205ef2 100644 --- a/src/Rapidez.php +++ b/src/Rapidez.php @@ -3,7 +3,6 @@ namespace Rapidez\Core; use Illuminate\Routing\Route; -use Illuminate\Routing\RouteAction; use Illuminate\Routing\Router; use Illuminate\Support\Arr; use Illuminate\Support\Collection; @@ -32,8 +31,7 @@ public function addFallbackRoute(Route|array|string $action, $position = 9999) private function actionToRoute(Route|array|string $action): Route { - if($action instanceof Route) - { + if ($action instanceof Route) { return $action; } From 49b9af529769cf874f3dacba41147f1d5af6ac40 Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Wed, 20 Nov 2024 10:51:53 +0100 Subject: [PATCH 12/31] HTTP/3 Early Hints support (#644) --- composer.json | 3 ++- config/rapidez/routing.php | 5 +++++ src/RapidezServiceProvider.php | 10 ++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index be1cca24b..ab03dc250 100644 --- a/composer.json +++ b/composer.json @@ -18,13 +18,14 @@ "require": { "php": "^8.1|^8.2|^8.3", "blade-ui-kit/blade-heroicons": "^2.4", - "mailerlite/laravel-elasticsearch": "^11.1", "illuminate/database": "^11.0", "illuminate/events": "^11.0", "illuminate/queue": "^11.0", "illuminate/support": "^11.0", + "justbetter/laravel-http3earlyhints": "*", "lcobucci/clock": "^2.0|^3.2", "lcobucci/jwt": "^4.0|^5.3", + "mailerlite/laravel-elasticsearch": "^11.1", "rapidez/blade-directives": "^0.6", "tormjens/eventy": "^0.8" }, diff --git a/config/rapidez/routing.php b/config/rapidez/routing.php index 3b75cab58..fb59c3153 100644 --- a/config/rapidez/routing.php +++ b/config/rapidez/routing.php @@ -23,4 +23,9 @@ // This does not cache the response, it caches the controller used for that page. 'cache_duration' => 3600, ], + + // See: https://docs.rapidez.io/3.x/configuration.html#early-hints + 'earlyhints' => [ + 'enabled' => env('EARLY_HINTS_ENABLED', true), + ] ]; diff --git a/src/RapidezServiceProvider.php b/src/RapidezServiceProvider.php index 5af9c583c..ac33fe4a6 100644 --- a/src/RapidezServiceProvider.php +++ b/src/RapidezServiceProvider.php @@ -146,6 +146,16 @@ protected function bootRoutes(): self RapidezFacade::addFallbackRoute(CmsPageController::class, 10); RapidezFacade::addFallbackRoute(LegacyFallbackController::class, 99999); + if (!app()->runningInConsole() && config('rapidez.routing.earlyhints.enabled', true)) { + $this->app->call(function (\Illuminate\Contracts\Http\Kernel $kernel) { + /** @var \Illuminate\Foundation\Http\Kernel $kernel */ + $middlewares = $kernel->getGlobalMiddleware(); + $middlewares[] = \JustBetter\Http3EarlyHints\Middleware\AddHttp3EarlyHints::class; + + $kernel->setGlobalMiddleware($middlewares); + }); + } + return $this; } From 87757e72713c5b29e707e1382d71bc4ca4508c96 Mon Sep 17 00:00:00 2001 From: royduin Date: Wed, 20 Nov 2024 09:52:16 +0000 Subject: [PATCH 13/31] Apply fixes from Duster --- config/rapidez/routing.php | 2 +- src/RapidezServiceProvider.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/rapidez/routing.php b/config/rapidez/routing.php index fb59c3153..e1568e4c9 100644 --- a/config/rapidez/routing.php +++ b/config/rapidez/routing.php @@ -27,5 +27,5 @@ // See: https://docs.rapidez.io/3.x/configuration.html#early-hints 'earlyhints' => [ 'enabled' => env('EARLY_HINTS_ENABLED', true), - ] + ], ]; diff --git a/src/RapidezServiceProvider.php b/src/RapidezServiceProvider.php index ac33fe4a6..06bcb1ce4 100644 --- a/src/RapidezServiceProvider.php +++ b/src/RapidezServiceProvider.php @@ -146,7 +146,7 @@ protected function bootRoutes(): self RapidezFacade::addFallbackRoute(CmsPageController::class, 10); RapidezFacade::addFallbackRoute(LegacyFallbackController::class, 99999); - if (!app()->runningInConsole() && config('rapidez.routing.earlyhints.enabled', true)) { + if (! app()->runningInConsole() && config('rapidez.routing.earlyhints.enabled', true)) { $this->app->call(function (\Illuminate\Contracts\Http\Kernel $kernel) { /** @var \Illuminate\Foundation\Http\Kernel $kernel */ $middlewares = $kernel->getGlobalMiddleware(); From 1a7dd98c8cbfecc51c1055ee40c8a7fc5e55351b Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Wed, 20 Nov 2024 14:59:22 +0100 Subject: [PATCH 14/31] Fix merge issues, remove todos (#647) --- .prettierignore | 9 +-- composer.json | 2 +- resources/js/callbacks.js | 75 +------------------ .../views/cart/queries/fragments/cart.graphql | 1 - .../views/checkout/partials/sidebar.blade.php | 5 +- .../views/checkout/steps/agreements.blade.php | 4 +- .../checkout/steps/place_order.blade.php | 1 - src/Http/Controllers/CheckoutController.php | 3 - 8 files changed, 7 insertions(+), 93 deletions(-) diff --git a/.prettierignore b/.prettierignore index aa7b4a0c2..62558ff4a 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,11 +4,6 @@ .git **/*.php !**/*.blade.php -/resources/views/cart/queries/*.graphql -/resources/views/cart/queries/**/*.graphql -/resources/views/checkout/queries/*.graphql -/resources/views/checkout/queries/**/*.graphql -/resources/views/customer/queries/*.graphql -/resources/views/customer/queries/**/*.graphql -composer.lock +/resources/views/*.graphql +/resources/views/**/*.graphql CHANGELOG.md diff --git a/composer.json b/composer.json index ab03dc250..c557b4985 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "lcobucci/clock": "^2.0|^3.2", "lcobucci/jwt": "^4.0|^5.3", "mailerlite/laravel-elasticsearch": "^11.1", - "rapidez/blade-directives": "^0.6", + "rapidez/blade-directives": "^0.7", "tormjens/eventy": "^0.8" }, "require-dev": { diff --git a/resources/js/callbacks.js b/resources/js/callbacks.js index 1f2f6c0d0..337eede7b 100644 --- a/resources/js/callbacks.js +++ b/resources/js/callbacks.js @@ -1,4 +1,4 @@ -import { cart, clear as clearCart, getAttributeValues } from './stores/useCart' +import { cart, clear as clearCart } from './stores/useCart' import { fillFromGraphqlResponse as updateOrder, order } from './stores/useOrder' import { runAfterPlaceOrderHandlers, runBeforePaymentMethodHandlers, runBeforePlaceOrderHandlers } from './stores/usePaymentHandlers' import { refresh as refreshUser, token } from './stores/useUser' @@ -35,48 +35,6 @@ Vue.prototype.submitPartials = async function (form) { return await Promise.all(promises) } -Vue.prototype.updateCart = async function (variables, response) { - cart.value = 'cart' in Object.values(response.data)[0] ? Object.values(response.data)[0].cart : Object.values(response.data)[0] - - getAttributeValues().then((response) => { - if (!response?.data?.customAttributeMetadata?.items) { - return - } - - const mapping = Object.fromEntries( - response.data.customAttributeMetadata.items.map((attribute) => [ - attribute.attribute_code, - Object.fromEntries(attribute.attribute_options.map((value) => [value.value, value.label])), - ]), - ) - - cart.value.items = cart.value.items.map((cartItem) => { - cartItem.product.attribute_values = {} - - for (const key in mapping) { - cartItem.product.attribute_values[key] = cartItem.product[key] - if (cartItem.product.attribute_values[key] === null) { - continue - } - - if (typeof cartItem.product.attribute_values[key] === 'string') { - cartItem.product.attribute_values[key] = cartItem.product.attribute_values[key].split(',') - } - - if (typeof cartItem.product.attribute_values[key] !== 'object') { - cartItem.product.attribute_values[key] = [cartItem.product.attribute_values[key]] - } - - cartItem.product.attribute_values[key] = cartItem.product.attribute_values[key].map((value) => mapping[key][value] || value) - } - - return cartItem - }) - }) - - return response.data -} - Vue.prototype.checkResponseForExpiredCart = async function (variables, response) { if ( response?.errors?.some( @@ -115,37 +73,6 @@ Vue.prototype.updateOrder = async function (data, response) { return response.data } -Vue.prototype.checkResponseForExpiredCart = async function (variables, response) { - if ( - response?.errors?.some( - (error) => - error.extensions.category === 'graphql-no-such-entity' && - error.path.some((path) => - [ - 'cart', - 'customerCart', - 'assignCustomerToGuestCart', - 'mergeCarts', - 'addProductsToCart', - 'removeItemFromCart', - 'updateCartItems', - ].includes(path), - ), - ) - ) { - Notify(window.config.translations.errors.cart_expired, 'error') - clearCart() - if (token.value !== undefined) { - // If the cart has expired, check if the session is not expired - refreshUser() - } - - return true - } - - return false -} - Vue.prototype.handleBeforePaymentMethodHandlers = runBeforePaymentMethodHandlers Vue.prototype.handleBeforePlaceOrderHandlers = runBeforePlaceOrderHandlers diff --git a/resources/views/cart/queries/fragments/cart.graphql b/resources/views/cart/queries/fragments/cart.graphql index 7665ec3da..338fb0c14 100644 --- a/resources/views/cart/queries/fragments/cart.graphql +++ b/resources/views/cart/queries/fragments/cart.graphql @@ -1,6 +1,5 @@ fragment cart on Cart { id - # TODO: Check if virtual orders are working is_virtual total_quantity items { diff --git a/resources/views/checkout/partials/sidebar.blade.php b/resources/views/checkout/partials/sidebar.blade.php index e918bdda0..4a4448368 100644 --- a/resources/views/checkout/partials/sidebar.blade.php +++ b/resources/views/checkout/partials/sidebar.blade.php @@ -37,8 +37,7 @@

- {{-- TODO: Check this compare as uid might not what we want but there is not anything else in the cart response --}} - +

    @@ -49,7 +48,7 @@
  • @{{ cart.shipping_addresses[0]?.telephone }}
-
+

@lang('Billing address')

  • @{{ cart.billing_address.company }}
  • diff --git a/resources/views/checkout/steps/agreements.blade.php b/resources/views/checkout/steps/agreements.blade.php index 4016b8da8..418aa5f87 100644 --- a/resources/views/checkout/steps/agreements.blade.php +++ b/resources/views/checkout/steps/agreements.blade.php @@ -1,7 +1,5 @@ @php($checkoutAgreements ??= \Rapidez\Core\Models\CheckoutAgreement::all()) -@if (!$checkoutAgreements->count()) - @return -@endif +@return(!$checkoutAgreements->count()) @foreach ($checkoutAgreements as $agreement) diff --git a/resources/views/checkout/steps/place_order.blade.php b/resources/views/checkout/steps/place_order.blade.php index a7c01c2bf..eadace8aa 100644 --- a/resources/views/checkout/steps/place_order.blade.php +++ b/resources/views/checkout/steps/place_order.blade.php @@ -5,7 +5,6 @@ :callback="handlePlaceOrder" mutate-event="placeOrder" redirect="{{ route('checkout.success') }}" - {{-- :error-callback="alert" --}} v-slot="{ mutate, variables }" >
    diff --git a/src/Http/Controllers/CheckoutController.php b/src/Http/Controllers/CheckoutController.php index c1a18a380..fb75f6304 100644 --- a/src/Http/Controllers/CheckoutController.php +++ b/src/Http/Controllers/CheckoutController.php @@ -17,9 +17,6 @@ public function __invoke(Request $request, ?string $step = null) abort_if(! in_array($step, $checkoutSteps), 404); - // TODO: Is there a way to check if we should be able to go to this step? - // For example; without a quote we could be redirected to the homepage. - return view('rapidez::checkout.pages.' . $step, [ 'checkoutSteps' => $checkoutSteps, 'currentStep' => $step, From 5b5a23af53a24857996c4636e675def8070fd9ad Mon Sep 17 00:00:00 2001 From: Bob Wezelman Date: Wed, 20 Nov 2024 17:17:40 +0100 Subject: [PATCH 15/31] Disable checkout button and add notice if not in stock (#628) --- lang/nl.json | 2 ++ resources/js/package.js | 5 +++++ resources/js/stores/useCart.js | 22 +++++++++++++++++++ resources/views/cart/item.blade.php | 3 +++ .../cart/queries/fragments/product.graphql | 1 + resources/views/cart/sidebar.blade.php | 13 ++++++++--- .../partials/header/minicart.blade.php | 16 ++++++++++---- 7 files changed, 55 insertions(+), 7 deletions(-) diff --git a/lang/nl.json b/lang/nl.json index 2fae16ff3..d3b3a8726 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -71,6 +71,8 @@ "Shipping method": "Verzendmethode", "Shipping": "Verzending", "Show cart": "Bekijk winkelwagen", + "This product is out of stock, remove it to continue your order.": "Dit product is niet op voorraad, verwijder het om verder te gaan met je bestelling.", + "Out of stock": "Niet op voorraad", "Show results": "Bekijk resultaten", "Sign up for our newsletter to stay up to date.": "Meld je aan voor onze nieuwsbrief om op de hoogte te blijven.", "Sorry! No image": "Sorry! Geen afbeelding", diff --git a/resources/js/package.js b/resources/js/package.js index 20f3a0f36..a438eb2ad 100644 --- a/resources/js/package.js +++ b/resources/js/package.js @@ -92,6 +92,7 @@ function init() { this.scrollLock = bool } }, + resizedPath(imagePath, size, store = null) { if (!store) { store = window.config.store @@ -116,6 +117,10 @@ function init() { hasCart() { return this.cart?.id && this.cart.items.length }, + + canOrder() { + return this.cart.items.every((item) => item.is_available) + }, }, watch: { loadingCount: function (count) { diff --git a/resources/js/stores/useCart.js b/resources/js/stores/useCart.js index e534ec55a..edfbd2af7 100644 --- a/resources/js/stores/useCart.js +++ b/resources/js/stores/useCart.js @@ -163,6 +163,21 @@ function addCustomerAddressId(address) { return address } +export const checkAvailability = function (cartItem) { + // Here we polyfill the is_available field. We need to do this + // because the default is_available field supported by Magento + // always returns true, even when a product is out of stock. This + // should be fixed in the next Magento release, reference: https://github.com/magento/magento2/blame/2.4-develop/app/code/Magento/QuoteGraphQl/Model/CartItem/ProductStock.php#L61 + if ('stock_item' in cartItem.product && 'in_stock' in cartItem.product.stock_item && cartItem.product.stock_item.in_stock !== null) { + return cartItem.product.stock_item.in_stock + } + + // Without the use of compadre the in stock check can't be + // done. We will need to always allow users to go on to + // the checkout. + return true +} + export const cart = computed({ get() { if (!cartStorage.value?.id && mask.value) { @@ -190,6 +205,12 @@ export const cart = computed({ getAttributeValues() .then((response) => { if (!response?.data?.customAttributeMetadata?.items) { + value.items = value.items.map((item) => { + item.is_available = checkAvailability(item) + + return item + }) + return } @@ -201,6 +222,7 @@ export const cart = computed({ ) value.items = value.items.map((cartItem) => { + cartItem.is_available = checkAvailability(item) cartItem.product.attribute_values = {} for (const key in mapping) { diff --git a/resources/views/cart/item.blade.php b/resources/views/cart/item.blade.php index b6a9711e1..7385aa5d6 100644 --- a/resources/views/cart/item.blade.php +++ b/resources/views/cart/item.blade.php @@ -15,6 +15,9 @@ class="mx-auto"
    @{{ item.product.name }} +
    + @lang('This product is out of stock, remove it to continue your order.') +
    @{{ option.option_label }}: @{{ option.value_label }} diff --git a/resources/views/cart/queries/fragments/product.graphql b/resources/views/cart/queries/fragments/product.graphql index cf04677a7..459462edc 100644 --- a/resources/views/cart/queries/fragments/product.graphql +++ b/resources/views/cart/queries/fragments/product.graphql @@ -17,6 +17,7 @@ fragment product on ProductInterface { } @if (Rapidez::checkCompadreVersion('0.0.1')) stock_item { + in_stock max_sale_qty min_sale_qty qty_increments diff --git a/resources/views/cart/sidebar.blade.php b/resources/views/cart/sidebar.blade.php index 9de1d416a..6ad59e0f2 100644 --- a/resources/views/cart/sidebar.blade.php +++ b/resources/views/cart/sidebar.blade.php @@ -42,6 +42,13 @@
    - - @lang('Checkout') - +
    + + @lang('Checkout') + +
    \ No newline at end of file diff --git a/resources/views/layouts/partials/header/minicart.blade.php b/resources/views/layouts/partials/header/minicart.blade.php index 9cd2a5461..50b356105 100644 --- a/resources/views/layouts/partials/header/minicart.blade.php +++ b/resources/views/layouts/partials/header/minicart.blade.php @@ -7,7 +7,12 @@
    - + @@ -20,9 +25,12 @@ @lang('Show cart') - - @lang('Checkout') - + +
    + + @lang('Checkout') + +
    From f5114d1eaa4895c02adbbe27912a90df8c3ff091 Mon Sep 17 00:00:00 2001 From: Roene Verbeek <85165259+Roene-JustBetter@users.noreply.github.com> Date: Thu, 21 Nov 2024 08:47:02 +0100 Subject: [PATCH 16/31] Dutch empty cart translation (#652) --- lang/nl.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/nl.json b/lang/nl.json index d3b3a8726..aabf3fc80 100644 --- a/lang/nl.json +++ b/lang/nl.json @@ -99,7 +99,7 @@ "We will send a confirmation of your order to": "Wij zullen een confirmatie sturen naar", "Wishlist": "Wensenlijst", "Yes": "Ja", - "You don't have anything in your cart.": "Deze winkelwagen is leeg.", + "You don't have anything in your cart.": "Je hebt geen producten in je winkelwagen.", "You have filtered for:": "Je hebt gefilterd op:", "Your order is currently:": "Je bestelling is op dit moment:" } From e743d7cd7dac22575b9cc29565ee43bc595be7b2 Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:49:47 +0100 Subject: [PATCH 17/31] Reduce forced reflow with the slider (#655) --- resources/js/components/Elements/Slider.vue | 58 ++++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/resources/js/components/Elements/Slider.vue b/resources/js/components/Elements/Slider.vue index c5dc09670..b4dfc92d1 100644 --- a/resources/js/components/Elements/Slider.vue +++ b/resources/js/components/Elements/Slider.vue @@ -67,7 +67,7 @@ export default { if (this.loop) { this.initLoop() } - this.slider.dispatchEvent(new CustomEvent('scroll')) + setTimeout(() => this.slider.dispatchEvent(new CustomEvent('scroll'))) this.mounted = true this.initAutoPlay() @@ -94,17 +94,19 @@ export default { } let firstChild = this.container.firstChild - for (let slide of slides) { - let startClone = this.container.insertBefore(slide.cloneNode(true), firstChild) - startClone.dataset.clone = true - startClone.dataset.position = 'start' + requestAnimationFrame(() => { + for (let slide of slides) { + let startClone = this.container.insertBefore(slide.cloneNode(true), firstChild) + startClone.dataset.clone = true + startClone.dataset.position = 'start' - let endClone = this.container.appendChild(slide.cloneNode(true)) - endClone.dataset.clone = true - endClone.dataset.position = 'end' - } + let endClone = this.container.appendChild(slide.cloneNode(true)) + endClone.dataset.clone = true + endClone.dataset.position = 'end' + } - this.slider.dispatchEvent(new CustomEvent('scrollend')) + this.slider.dispatchEvent(new CustomEvent('scrollend')) + }) }, initAutoPlay() { if (!this.autoplay) { @@ -156,27 +158,31 @@ export default { : this.slider.scrollTo({ left: this.container.children[index]?.offsetLeft, behavior: behavior }) }, handleLoop() { - if (this.currentSlide + 1 === this.slidesTotal - 1) { - Array.from(this.chunk.children).forEach((child) => { - this.container.appendChild(child.cloneNode(true)) - }) - } - if (this.currentSlide < 1) { - Array.from(this.chunk.children).forEach((child) => { - this.container.insertBefore(child.cloneNode(true), this.container.firstChild) - }) - } + requestAnimationFrame(() => { + if (this.currentSlide + 1 === this.slidesTotal - 1) { + Array.from(this.chunk.children).forEach((child) => { + this.container.appendChild(child.cloneNode(true)) + }) + } + if (this.currentSlide < 1) { + Array.from(this.chunk.children).forEach((child) => { + this.container.insertBefore(child.cloneNode(true), this.container.firstChild) + }) + } + }) }, updateSpan() { - let slide = this.childSpan == 0 ? 0 : this.currentSlide + setTimeout(() => { + let slide = this.childSpan == 0 ? 0 : this.currentSlide - this.childSpan = this.vertical - ? (this.container.children[0]?.offsetHeight ?? this.container.offsetHeight) - : (this.container.children[0]?.offsetWidth ?? this.container.offsetWidth) + this.childSpan = this.vertical + ? (this.container.children[0]?.offsetHeight ?? this.container.offsetHeight) + : (this.container.children[0]?.offsetWidth ?? this.container.offsetWidth) - this.navigate(slide, 'instant') + this.navigate(slide, 'instant') - this.sliderSpan = this.vertical ? this.container.offsetHeight : this.container.offsetWidth + this.sliderSpan = this.vertical ? this.container.offsetHeight : this.container.offsetWidth + }) }, }, watch: { From ec8d64e77779fea9f6234d31ea0da1e03f4c08f3 Mon Sep 17 00:00:00 2001 From: royduin Date: Tue, 26 Nov 2024 10:31:33 +0000 Subject: [PATCH 18/31] Apply fixes from Prettier --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 660826705..b63117652 100644 --- a/README.md +++ b/README.md @@ -10,10 +10,10 @@ Just run `./vendor/bin/phpunit` ### Browser tests -- Make sure you've got a Magento install running -- Prepare Laravel Dusk with `composer run dusk:prepare` -- Compile the assets with: `composer run dusk:assets` and re-run this when the assets are changed -- Tests can be started with: `composer run dusk:test` +- Make sure you've got a Magento install running +- Prepare Laravel Dusk with `composer run dusk:prepare` +- Compile the assets with: `composer run dusk:assets` and re-run this when the assets are changed +- Tests can be started with: `composer run dusk:test` ## License From c01f532eef2c5ee578d172ae8fe2a45b71834ac1 Mon Sep 17 00:00:00 2001 From: indykoning <15870933+indykoning@users.noreply.github.com> Date: Tue, 26 Nov 2024 14:16:06 +0100 Subject: [PATCH 19/31] Improved Vue/Turbo loading speed (#646) --- resources/js/package.js | 218 ++++++++++-------- .../views/components/productlist.blade.php | 2 +- tests/Browser/CartTest.php | 3 + tests/Browser/CheckoutTest.php | 2 + tests/Browser/DialogTest.php | 1 + tests/Browser/HomepageTest.php | 2 +- tests/Browser/NewsletterTest.php | 1 + tests/DuskTestCaseSetup.php | 19 +- 8 files changed, 146 insertions(+), 102 deletions(-) diff --git a/resources/js/package.js b/resources/js/package.js index a438eb2ad..d39aa9752 100644 --- a/resources/js/package.js +++ b/resources/js/package.js @@ -15,16 +15,47 @@ import useMask from './stores/useMask' import { swatches, clear as clearSwatches } from './stores/useSwatches' import { clear as clearAttributes } from './stores/useAttributes.js' import './vue' -import { computed } from 'vue' import './fetch' import './filters' import './mixins' -import './turbolinks' +(() => import('./turbolinks'))() import './cookies' import './callbacks' import './vue-components' +if (import.meta.env.VITE_DEBUG === 'true') { + document.addEventListener('vue:loaded', () => { + window.app.$on('notification-message', function (message, type, params, link) { + switch (type) { + case 'error': + console.error(...arguments) + break + case 'warning': + console.warn(...arguments) + break + case 'success': + case 'info': + default: + console.log(...arguments) + } + }) + }) +} + +document.addEventListener('vue:loaded', () => { + const lastStoreCode = useLocalStorage('last_store_code', window.config.store_code) + if (lastStoreCode.value !== window.config.store_code) { + clearAttributes() + clearSwatches() + lastStoreCode.value = window.config.store_code + } +}) + function init() { + if (document.body.contains(window.app.$el)) { + return; + } + // https://vuejs.org/api/application.html#app-config-performance Vue.config.performance = import.meta.env.VITE_PERFORMANCE == 'true' Vue.prototype.window = window @@ -59,101 +90,94 @@ function init() { custom_attributes: [], } - window.app = new Vue({ - el: '#app', - data: { - custom: {}, - config: window.config, - loadingCount: 0, - loading: false, - loadAutocomplete: false, - csrfToken: document.querySelector('[name=csrf-token]').content, - cart: useCart(), - order: useOrder(), - user: useUser(), - mask: useMask(), - showTax: window.config.show_tax, - swatches: swatches, - scrollLock: useScrollLock(document.body), - }, - methods: { - search(value) { - if (value.length) { - Turbo.visit(window.url('/search?q=' + encodeURIComponent(value))) - } - }, - setSearchParams(url) { - window.history.pushState(window.history.state, '', new URL(url)) - }, - toggleScroll(bool = null) { - if (bool === null) { - this.scrollLock = !this.scrollLock - } else { - this.scrollLock = bool - } - }, - - resizedPath(imagePath, size, store = null) { - if (!store) { - store = window.config.store + requestAnimationFrame( + () => { + window.app = new Vue({ + el: '#app', + data: { + custom: {}, + config: window.config, + loadingCount: 0, + loading: false, + loadAutocomplete: false, + csrfToken: document.querySelector('[name=csrf-token]').content, + cart: useCart(), + order: useOrder(), + user: useUser(), + mask: useMask(), + showTax: window.config.show_tax, + swatches: swatches, + scrollLock: useScrollLock(document.body), + }, + methods: { + search(value) { + if (value.length) { + Turbo.visit(window.url('/search?q=' + encodeURIComponent(value))) + } + }, + + setSearchParams(url) { + window.history.pushState(window.history.state, '', new URL(url)) + }, + + toggleScroll(bool = null) { + if (bool === null) { + this.scrollLock = !this.scrollLock + } else { + this.scrollLock = bool + } + }, + + resizedPath(imagePath, size, store = null) { + if (!store) { + store = window.config.store + } + + let url = new URL(imagePath) + url = url.pathname.replace('/media', '') + + return `/storage/${store}/resizes/${size}/magento${url}` + }, + }, + computed: { + // Wrap the local storage in getter and setter functions so you do not have to interact using .value + guestEmail: wrapValue( + useLocalStorage('email', window.debug ? 'wayne@enterprises.com' : '', { serializer: StorageSerializers.string }), + ), + + loggedIn() { + return this.user?.is_logged_in + }, + + hasCart() { + return this.cart?.id && this.cart.items.length + }, + + + canOrder() { + return this.cart.items.every((item) => item.is_available) + }, + }, + watch: { + loadingCount: function (count) { + window.app.$data.loading = count > 0 + }, + }, + mounted() { + setTimeout(() => { + const event = new CustomEvent('vue:mounted', { detail: { vue: window.app } }) + document.dispatchEvent(event) + }) } - - let url = new URL(imagePath) - url = url.pathname.replace('/media', '') - - return `/storage/${store}/resizes/${size}/magento${url}` - }, - }, - computed: { - // Wrap the local storage in getter and setter functions so you do not have to interact using .value - guestEmail: wrapValue( - useLocalStorage('email', window.debug ? 'wayne@enterprises.com' : '', { serializer: StorageSerializers.string }), - ), - - loggedIn() { - return this.user?.is_logged_in - }, - - hasCart() { - return this.cart?.id && this.cart.items.length - }, - - canOrder() { - return this.cart.items.every((item) => item.is_available) - }, - }, - watch: { - loadingCount: function (count) { - window.app.$data.loading = count > 0 - }, - }, - }) - - const lastStoreCode = useLocalStorage('last_store_code', window.config.store_code) - if (lastStoreCode.value !== window.config.store_code) { - clearAttributes() - clearSwatches() - lastStoreCode.value = window.config.store_code - } - - if (window.debug) { - window.app.$on('notification-message', function (message, type, params, link) { - switch (type) { - case 'error': - console.error(...arguments) - break - case 'warning': - console.warn(...arguments) - break - case 'success': - case 'info': - default: - console.log(...arguments) - } - }) - } - - const event = new CustomEvent('vue:loaded', { detail: { vue: window.app } }) - document.dispatchEvent(event) + }) + + setTimeout(() => { + const event = new CustomEvent('vue:loaded', { detail: { vue: window.app } }) + document.dispatchEvent(event) + }) + } + ) } + document.addEventListener('turbo:load', init) +setTimeout(init) diff --git a/resources/views/components/productlist.blade.php b/resources/views/components/productlist.blade.php index 341a264f0..7e64018e6 100644 --- a/resources/views/components/productlist.blade.php +++ b/resources/views/components/productlist.blade.php @@ -24,7 +24,7 @@
    -
    +
    diff --git a/tests/Browser/CartTest.php b/tests/Browser/CartTest.php index 604b38a9d..44b51a5fa 100644 --- a/tests/Browser/CartTest.php +++ b/tests/Browser/CartTest.php @@ -15,6 +15,7 @@ public function addSimpleProduct() $this->browse(function (Browser $browser) { $browser->addProductToCart($this->testProduct->url) ->visit('/cart') + ->waitUntilVueLoaded() ->waitUntilIdle() ->waitFor('@cart-content', 15) ->waitUntilIdle() @@ -43,6 +44,7 @@ public function changeProductQty() { $this->browse(function (Browser $browser) { $browser->visit('/cart') + ->waitUntilVueLoaded() ->waitUntilIdle() ->waitFor('@cart-content', 15) ->waitUntilIdle() @@ -60,6 +62,7 @@ public function removeProduct() { $this->browse(function (Browser $browser) { $browser->visit('/cart') + ->waitUntilVueLoaded() ->waitUntilIdle() ->waitFor('@cart-content', 15) ->waitUntilIdle() diff --git a/tests/Browser/CheckoutTest.php b/tests/Browser/CheckoutTest.php index 4784c3988..ad83f049b 100644 --- a/tests/Browser/CheckoutTest.php +++ b/tests/Browser/CheckoutTest.php @@ -34,6 +34,7 @@ public function checkoutAsUser() // Go through checkout as guest and log in. $this->browse(function (Browser $browser) use ($email) { $browser->waitForReload(fn ($browser) => $browser->visit('/'), 4) + ->waitUntilVueLoaded() ->waitUntilIdle() ->waitFor('@account_menu') ->click('@account_menu') @@ -71,6 +72,7 @@ public function doCheckoutLogin(Browser $browser, $email = false, $password = fa { $browser ->visit('/checkout') + ->waitUntilVueLoaded() ->waitUntilIdle() ->type('@email', $email ?: 'wayne@enterprises.com') ->waitUntilIdle(); diff --git a/tests/Browser/DialogTest.php b/tests/Browser/DialogTest.php index a4e0ab516..53190b94a 100644 --- a/tests/Browser/DialogTest.php +++ b/tests/Browser/DialogTest.php @@ -14,6 +14,7 @@ public function test() { $this->browse(function (Browser $browser) { $browser->visit('/?show-cookie-notice') + ->waitUntilVueLoaded() ->waitUntilIdle() ->assertSee('Accept cookies') ->waitForReload(function (Browser $browser) { diff --git a/tests/Browser/HomepageTest.php b/tests/Browser/HomepageTest.php index 32df98ba8..d9d2b8582 100644 --- a/tests/Browser/HomepageTest.php +++ b/tests/Browser/HomepageTest.php @@ -13,7 +13,7 @@ class HomepageTest extends DuskTestCase public function homepage() { $this->browse(function (Browser $browser) { - $browser->visit('/')->assertSee('All rights reserved.'); + $browser->visit('/')->waitUntilVueLoaded()->assertSee('All rights reserved.'); }); } } diff --git a/tests/Browser/NewsletterTest.php b/tests/Browser/NewsletterTest.php index 54a466b73..0dffd043b 100644 --- a/tests/Browser/NewsletterTest.php +++ b/tests/Browser/NewsletterTest.php @@ -18,6 +18,7 @@ public function test() $this->browse(function (Browser $browser) { $email = $this->faker->email; $browser->visit('/') + ->waitUntilVueLoaded() ->scrollIntoView('@newsletter') ->waitUntilIdle() ->type('@newsletter-email', $email) diff --git a/tests/DuskTestCaseSetup.php b/tests/DuskTestCaseSetup.php index 1556d074e..6ae47bdc8 100644 --- a/tests/DuskTestCaseSetup.php +++ b/tests/DuskTestCaseSetup.php @@ -44,6 +44,16 @@ protected function setUp(): void return $this; }); + Browser::macro('waitUntilVueLoaded', function () { + /** @var Browser $this */ + $this + ->waitUntilIdle() + ->waitUntilTrueForDuration('document.body.contains(window.app?.$el) && window.app?._isMounted && console.log("mounted") === undefined', 10, 1) + ->waitUntilIdle(); + + return $this; + }); + Browser::macro('assertFormValid', function ($selector) { /** @var Browser $this */ $fullSelector = $this->resolver->format($selector); @@ -60,14 +70,17 @@ protected function setUp(): void /** @var Browser $this */ if ($productUrl) { $this - ->visit($productUrl); + ->visit($productUrl) + ->waitUntilVueLoaded() + ->waitUntilIdle(); } // @phpstan-ignore-next-line $this ->waitUntilIdle() - ->pressAndWaitFor('@add-to-cart', 60) - ->waitForText('Added', 60) + ->waitUntilEnabled('@add-to-cart', 200) + ->pressAndWaitFor('@add-to-cart', 120) + ->waitForText(__('Added'), 120) ->waitUntilIdle(); return $this; From 441ece9184b8e886472eedc24c391511b6ff1ce0 Mon Sep 17 00:00:00 2001 From: royduin Date: Tue, 26 Nov 2024 13:17:06 +0000 Subject: [PATCH 20/31] Apply fixes from Prettier --- resources/js/package.js | 159 ++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 81 deletions(-) diff --git a/resources/js/package.js b/resources/js/package.js index d39aa9752..9329e7197 100644 --- a/resources/js/package.js +++ b/resources/js/package.js @@ -18,7 +18,7 @@ import './vue' import './fetch' import './filters' import './mixins' -(() => import('./turbolinks'))() +;(() => import('./turbolinks'))() import './cookies' import './callbacks' import './vue-components' @@ -53,7 +53,7 @@ document.addEventListener('vue:loaded', () => { function init() { if (document.body.contains(window.app.$el)) { - return; + return } // https://vuejs.org/api/application.html#app-config-performance @@ -90,93 +90,90 @@ function init() { custom_attributes: [], } - requestAnimationFrame( - () => { - window.app = new Vue({ - el: '#app', - data: { - custom: {}, - config: window.config, - loadingCount: 0, - loading: false, - loadAutocomplete: false, - csrfToken: document.querySelector('[name=csrf-token]').content, - cart: useCart(), - order: useOrder(), - user: useUser(), - mask: useMask(), - showTax: window.config.show_tax, - swatches: swatches, - scrollLock: useScrollLock(document.body), + requestAnimationFrame(() => { + window.app = new Vue({ + el: '#app', + data: { + custom: {}, + config: window.config, + loadingCount: 0, + loading: false, + loadAutocomplete: false, + csrfToken: document.querySelector('[name=csrf-token]').content, + cart: useCart(), + order: useOrder(), + user: useUser(), + mask: useMask(), + showTax: window.config.show_tax, + swatches: swatches, + scrollLock: useScrollLock(document.body), + }, + methods: { + search(value) { + if (value.length) { + Turbo.visit(window.url('/search?q=' + encodeURIComponent(value))) + } }, - methods: { - search(value) { - if (value.length) { - Turbo.visit(window.url('/search?q=' + encodeURIComponent(value))) - } - }, - - setSearchParams(url) { - window.history.pushState(window.history.state, '', new URL(url)) - }, - - toggleScroll(bool = null) { - if (bool === null) { - this.scrollLock = !this.scrollLock - } else { - this.scrollLock = bool - } - }, - - resizedPath(imagePath, size, store = null) { - if (!store) { - store = window.config.store - } - - let url = new URL(imagePath) - url = url.pathname.replace('/media', '') - - return `/storage/${store}/resizes/${size}/magento${url}` - }, + + setSearchParams(url) { + window.history.pushState(window.history.state, '', new URL(url)) + }, + + toggleScroll(bool = null) { + if (bool === null) { + this.scrollLock = !this.scrollLock + } else { + this.scrollLock = bool + } }, - computed: { - // Wrap the local storage in getter and setter functions so you do not have to interact using .value - guestEmail: wrapValue( - useLocalStorage('email', window.debug ? 'wayne@enterprises.com' : '', { serializer: StorageSerializers.string }), - ), - loggedIn() { - return this.user?.is_logged_in - }, + resizedPath(imagePath, size, store = null) { + if (!store) { + store = window.config.store + } - hasCart() { - return this.cart?.id && this.cart.items.length - }, + let url = new URL(imagePath) + url = url.pathname.replace('/media', '') + return `/storage/${store}/resizes/${size}/magento${url}` + }, + }, + computed: { + // Wrap the local storage in getter and setter functions so you do not have to interact using .value + guestEmail: wrapValue( + useLocalStorage('email', window.debug ? 'wayne@enterprises.com' : '', { serializer: StorageSerializers.string }), + ), + + loggedIn() { + return this.user?.is_logged_in + }, - canOrder() { - return this.cart.items.every((item) => item.is_available) - }, + hasCart() { + return this.cart?.id && this.cart.items.length }, - watch: { - loadingCount: function (count) { - window.app.$data.loading = count > 0 - }, + + canOrder() { + return this.cart.items.every((item) => item.is_available) }, - mounted() { - setTimeout(() => { - const event = new CustomEvent('vue:mounted', { detail: { vue: window.app } }) - document.dispatchEvent(event) - }) - } - }) - - setTimeout(() => { - const event = new CustomEvent('vue:loaded', { detail: { vue: window.app } }) - document.dispatchEvent(event) - }) - } - ) + }, + watch: { + loadingCount: function (count) { + window.app.$data.loading = count > 0 + }, + }, + mounted() { + setTimeout(() => { + const event = new CustomEvent('vue:mounted', { detail: { vue: window.app } }) + document.dispatchEvent(event) + }) + }, + }) + + setTimeout(() => { + const event = new CustomEvent('vue:loaded', { detail: { vue: window.app } }) + document.dispatchEvent(event) + }) + }) } document.addEventListener('turbo:load', init) From e692ae445f81713db22eea83ca8f38e4357c4ec2 Mon Sep 17 00:00:00 2001 From: Jade Geels Date: Wed, 27 Nov 2024 10:38:19 +0100 Subject: [PATCH 21/31] Typo (#659) --- resources/js/stores/useCart.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/js/stores/useCart.js b/resources/js/stores/useCart.js index edfbd2af7..6fa88c523 100644 --- a/resources/js/stores/useCart.js +++ b/resources/js/stores/useCart.js @@ -222,7 +222,7 @@ export const cart = computed({ ) value.items = value.items.map((cartItem) => { - cartItem.is_available = checkAvailability(item) + cartItem.is_available = checkAvailability(cartItem) cartItem.product.attribute_values = {} for (const key in mapping) { From c91cc04435a80b33d21f9009acecf74dab085503 Mon Sep 17 00:00:00 2001 From: royduin Date: Wed, 27 Nov 2024 09:39:18 +0000 Subject: [PATCH 22/31] Update CHANGELOG --- CHANGELOG.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec92ed890..fff5381ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,11 @@ # Changelog -[Unreleased changes](https://github.com/rapidez/core/compare/2.16.0...2.16.0) -## [2.16.0](https://github.com/rapidez/core/releases/tag/2.16.0) - 2024-11-19 +[Unreleased changes](https://github.com/rapidez/core/compare/2.18.1...2.18.1) +## [2.18.1](https://github.com/rapidez/core/releases/tag/2.18.1) - 2024-11-26 -### Changed - -- Resize path helper (#633, #648) -- Extracted login to a loginByToken function (#637) - ### Fixed -- Image switching length fix (#641) -- Fix error of undefined when extensions is not set (#634) +- Typo (#658) ## [2.13.0](https://github.com/rapidez/core/releases/tag/2.13.0) - 2024-09-27 From f0ced0186f241f3edf0113054f16aec2e3d03599 Mon Sep 17 00:00:00 2001 From: Jade Geels Date: Wed, 27 Nov 2024 11:20:24 +0100 Subject: [PATCH 23/31] Ignore PHPStan error (#660) --- phpstan.neon | 1 + tests/DuskTestCaseSetup.php | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index 1b0abc9c6..2fc0fab3a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -9,4 +9,5 @@ parameters: ignoreErrors: - '#^Result of static method TorMorten\\Eventy\\Events::#' - "#Access to an undefined property#" + - "#::waitUntilIdle#" level: 1 diff --git a/tests/DuskTestCaseSetup.php b/tests/DuskTestCaseSetup.php index 6ae47bdc8..748764ed4 100644 --- a/tests/DuskTestCaseSetup.php +++ b/tests/DuskTestCaseSetup.php @@ -75,7 +75,6 @@ protected function setUp(): void ->waitUntilIdle(); } - // @phpstan-ignore-next-line $this ->waitUntilIdle() ->waitUntilEnabled('@add-to-cart', 200) From 1bb00aa967cb538c44370dd25bef9a78632159b1 Mon Sep 17 00:00:00 2001 From: Roy Duineveld Date: Wed, 27 Nov 2024 16:14:42 +0100 Subject: [PATCH 24/31] Version bump of rapidez/blade-directives --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c557b4985..f7c8b0abd 100644 --- a/composer.json +++ b/composer.json @@ -26,7 +26,7 @@ "lcobucci/clock": "^2.0|^3.2", "lcobucci/jwt": "^4.0|^5.3", "mailerlite/laravel-elasticsearch": "^11.1", - "rapidez/blade-directives": "^0.7", + "rapidez/blade-directives": "^1.0", "tormjens/eventy": "^0.8" }, "require-dev": { From 6f08f70d1af0c5cd2a26c82edf70e4dd16c8e3e6 Mon Sep 17 00:00:00 2001 From: Roy Duineveld Date: Wed, 27 Nov 2024 16:20:34 +0100 Subject: [PATCH 25/31] Prefixed the tag Blade component --- resources/views/components/button/base.blade.php | 4 ++-- resources/views/components/slideover/index.blade.php | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/views/components/button/base.blade.php b/resources/views/components/button/base.blade.php index 8949cbea4..fdcf3752e 100644 --- a/resources/views/components/button/base.blade.php +++ b/resources/views/components/button/base.blade.php @@ -5,11 +5,11 @@ $tag = $attributes->has('for') ? 'label' : $tag; @endphp -merge([ 'class' => 'flex items-center justify-center font-semibold py-2 px-4 border rounded disabled:opacity-50 disabled:cursor-not-allowed hover:opacity-75 whitespace-nowrap transition', ':disabled' => $attributes->has('href') || $attributes->has(':href') || !$disableWhenLoading ? null : '$root.loading' ]) }} > {{ $slot }} - + diff --git a/resources/views/components/slideover/index.blade.php b/resources/views/components/slideover/index.blade.php index 51943796a..64e7aad32 100644 --- a/resources/views/components/slideover/index.blade.php +++ b/resources/views/components/slideover/index.blade.php @@ -24,7 +24,7 @@ $closeId = $isInForm ? 'close-' . $id : $id; @endphp - + @if (!$hasParent) @@ -45,4 +45,4 @@ class="pointer-events-none fixed inset-0 z-40 cursor-pointer bg-neutral/50 opaci {{ $slot }}
    - + From df60ab82c76ac11b8d089fb6a05fa4b05f807389 Mon Sep 17 00:00:00 2001 From: Roy Duineveld Date: Wed, 27 Nov 2024 16:44:39 +0100 Subject: [PATCH 26/31] Thumbnail image src fix --- resources/views/product/partials/gallery/thumbnails.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/product/partials/gallery/thumbnails.blade.php b/resources/views/product/partials/gallery/thumbnails.blade.php index fb74e65bb..c2ea459b5 100644 --- a/resources/views/product/partials/gallery/thumbnails.blade.php +++ b/resources/views/product/partials/gallery/thumbnails.blade.php @@ -14,7 +14,7 @@ class="flex items-center justify-center bg-white border rounded p-1.5 aspect-squ @click="change(imageId)" > {{ $product->name }} Date: Wed, 27 Nov 2024 16:59:35 +0100 Subject: [PATCH 27/31] Improved Vue/Turbo loading speed fix (#661) --- resources/js/package.js | 7 +++++-- tests/DuskTestCaseSetup.php | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/resources/js/package.js b/resources/js/package.js index 9329e7197..f8308dcf8 100644 --- a/resources/js/package.js +++ b/resources/js/package.js @@ -18,10 +18,10 @@ import './vue' import './fetch' import './filters' import './mixins' -;(() => import('./turbolinks'))() import './cookies' import './callbacks' import './vue-components' +import './turbolinks' if (import.meta.env.VITE_DEBUG === 'true') { document.addEventListener('vue:loaded', () => { @@ -51,10 +51,12 @@ document.addEventListener('vue:loaded', () => { } }) +let booting = false function init() { - if (document.body.contains(window.app.$el)) { + if (booting || document.body.contains(window.app.$el)) { return } + booting = true // https://vuejs.org/api/application.html#app-config-performance Vue.config.performance = import.meta.env.VITE_PERFORMANCE == 'true' @@ -170,6 +172,7 @@ function init() { }) setTimeout(() => { + booting = false const event = new CustomEvent('vue:loaded', { detail: { vue: window.app } }) document.dispatchEvent(event) }) diff --git a/tests/DuskTestCaseSetup.php b/tests/DuskTestCaseSetup.php index 748764ed4..c0bc68bd1 100644 --- a/tests/DuskTestCaseSetup.php +++ b/tests/DuskTestCaseSetup.php @@ -48,7 +48,7 @@ protected function setUp(): void /** @var Browser $this */ $this ->waitUntilIdle() - ->waitUntilTrueForDuration('document.body.contains(window.app?.$el) && window.app?._isMounted && console.log("mounted") === undefined', 10, 1) + ->waitUntilTrueForDuration('document.body.contains(window.app?.$el) && window.app?._isMounted', 60, 2) ->waitUntilIdle(); return $this; @@ -78,7 +78,7 @@ protected function setUp(): void $this ->waitUntilIdle() ->waitUntilEnabled('@add-to-cart', 200) - ->pressAndWaitFor('@add-to-cart', 120) + ->press('@add-to-cart', 120) ->waitForText(__('Added'), 120) ->waitUntilIdle(); From ae5e0d7e929e8da3d296e3bba5b7f474588b6301 Mon Sep 17 00:00:00 2001 From: royduin Date: Wed, 27 Nov 2024 16:00:47 +0000 Subject: [PATCH 28/31] Update CHANGELOG --- CHANGELOG.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fff5381ea..d8a18c6da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,76 @@ # Changelog -[Unreleased changes](https://github.com/rapidez/core/compare/2.18.1...2.18.1) -## [2.18.1](https://github.com/rapidez/core/releases/tag/2.18.1) - 2024-11-26 +[Unreleased changes](https://github.com/rapidez/core/compare/2.18.2...2.18.2) +## [2.18.2](https://github.com/rapidez/core/releases/tag/2.18.2) - 2024-11-27 + +### Fixed + +- Improved Vue/Turbo loading speed fix (#662) + +## [2.18.1](https://github.com/rapidez/core/releases/tag/2.18.1) - 2024-11-27 ### Fixed - Typo (#658) +## [2.18.0](https://github.com/rapidez/core/releases/tag/2.18.0) - 2024-11-26 + +### Changed + +- Improved Vue/Turbo loading speed (#653) + +## [2.17.1](https://github.com/rapidez/core/releases/tag/2.17.1) - 2024-11-26 + +### Fixed + +- Reduce forced reflow with the slider (#609) +- Clone address to remove reactivity (#656) + +## [2.17.0](https://github.com/rapidez/core/releases/tag/2.17.0) - 2024-11-20 + +### Added + +- Disable checkout button and add notice if not in stock (#627) + +### Changed + +- Apply middlewares from fallback routes (#643) + +## [2.16.0](https://github.com/rapidez/core/releases/tag/2.16.0) - 2024-11-19 + +### Changed + +- Resize path helper (#633, #648) +- Extracted login to a loginByToken function (#637) + +### Fixed + +- Image switching length fix (#641) +- Fix error of undefined when extensions is not set (#634) + +## [2.15.0](https://github.com/rapidez/core/releases/tag/2.15.0) - 2024-10-23 + +### Added + +- VAT change event (#606) + +### Fixed + +- Use correct object structure in PostCSS config (#611) + +## [2.14.0](https://github.com/rapidez/core/releases/tag/2.14.0) - 2024-10-17 + +### Changed + +- Updated the frontend dependencies including Vite 5 (#584) +- Drop support for Magento 2.4.5 (#589) + +### Fixed + +- Filter out double slashes from API endpoints (#559) +- Fix casts in quote model (#542) +- Correct error message when placing an order fails (#599) + ## [2.13.0](https://github.com/rapidez/core/releases/tag/2.13.0) - 2024-09-27 ### Changed From dc71685a56a161f0d78353c40f3c59d107a3aea8 Mon Sep 17 00:00:00 2001 From: Jade Geels Date: Thu, 28 Nov 2024 13:58:48 +0100 Subject: [PATCH 29/31] Use synonym graph instead of regular synonyms (#664) --- src/Commands/ElasticsearchIndexer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Commands/ElasticsearchIndexer.php b/src/Commands/ElasticsearchIndexer.php index f699216f5..7556110b3 100644 --- a/src/Commands/ElasticsearchIndexer.php +++ b/src/Commands/ElasticsearchIndexer.php @@ -81,7 +81,7 @@ public function prepare(string $indexName, array $mapping = [], array $settings ->map(fn ($synonym) => $synonym->synonyms) ->toArray(); - data_set($settings, 'index.analysis.filter.synonym', ['type' => 'synonym', 'synonyms' => $synonyms]); + data_set($settings, 'index.analysis.filter.synonym', ['type' => 'synonym_graph', 'synonyms' => $synonyms]); data_set($settings, 'index.analysis.analyzer.synonym', [ 'filter' => ['lowercase', 'asciifolding', 'synonym'], 'tokenizer' => 'standard', From 55cf69287fef81eb81c0ed79dda586ac75a30a08 Mon Sep 17 00:00:00 2001 From: Jade Geels Date: Fri, 29 Nov 2024 11:34:07 +0100 Subject: [PATCH 30/31] Require rapidez/blade-components (#665) --- composer.json | 1 + tests/DuskTestCase.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/composer.json b/composer.json index f7c8b0abd..96f6b8d96 100644 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "lcobucci/clock": "^2.0|^3.2", "lcobucci/jwt": "^4.0|^5.3", "mailerlite/laravel-elasticsearch": "^11.1", + "rapidez/blade-components": "^1.0", "rapidez/blade-directives": "^1.0", "tormjens/eventy": "^0.8" }, diff --git a/tests/DuskTestCase.php b/tests/DuskTestCase.php index 70913ce36..bc3b12c56 100644 --- a/tests/DuskTestCase.php +++ b/tests/DuskTestCase.php @@ -5,6 +5,7 @@ use BladeUI\Heroicons\BladeHeroiconsServiceProvider; use BladeUI\Icons\BladeIconsServiceProvider; use Orchestra\Testbench\Dusk\TestCase as BaseTestCase; +use Rapidez\BladeComponents\BladeComponentsServiceProvider; use Rapidez\BladeDirectives\BladeDirectivesServiceProvider; use Rapidez\Core\Facades\Rapidez; use Rapidez\Core\RapidezServiceProvider; @@ -21,6 +22,7 @@ protected function getPackageProviders($app) RapidezServiceProvider::class, BladeIconsServiceProvider::class, BladeHeroiconsServiceProvider::class, + BladeComponentsServiceProvider::class, BladeDirectivesServiceProvider::class, ]; } From 7baec26bd15d025e1c3818b47623e4afdbba72f5 Mon Sep 17 00:00:00 2001 From: Roy Duineveld Date: Tue, 3 Dec 2024 09:45:32 +0100 Subject: [PATCH 31/31] Color names refactor (#622) --- resources/css/app.css | 6 +- resources/css/components/pagination.css | 2 +- resources/css/components/price-slider.css | 8 +-- resources/css/theme-variables.css | 10 --- resources/views/cart/coupon/add.blade.php | 5 +- resources/views/cart/item/quantity.blade.php | 7 +- resources/views/cart/sidebar.blade.php | 8 +-- .../checkout/pages/credentials.blade.php | 6 +- .../views/checkout/pages/login.blade.php | 4 +- .../views/checkout/pages/onestep.blade.php | 2 +- .../views/checkout/pages/payment.blade.php | 2 +- .../checkout/partials/progressbar.blade.php | 8 +-- .../views/checkout/partials/sidebar.blade.php | 4 +- .../checkout/steps/credentials.blade.php | 37 ---------- .../views/checkout/steps/payment.blade.php | 68 ------------------- .../checkout/steps/place_order.blade.php | 4 +- .../views/checkout/steps/success.blade.php | 18 ++--- .../views/components/breadcrumb.blade.php | 4 +- .../views/components/button/base.blade.php | 2 +- .../views/components/button/cart.blade.php | 4 +- .../components/button/conversion.blade.php | 3 + .../views/components/button/index.blade.php | 2 +- .../views/components/button/outline.blade.php | 2 +- .../components/button/secondary.blade.php | 3 + .../views/components/button/slider.blade.php | 2 +- resources/views/components/checkbox.blade.php | 4 +- .../views/components/filter/heading.blade.php | 4 +- resources/views/components/input.blade.php | 2 +- resources/views/components/label.blade.php | 2 +- .../views/components/productlist.blade.php | 2 +- resources/views/components/radio.blade.php | 4 +- resources/views/components/select.blade.php | 2 +- .../components/slideover/index.blade.php | 4 +- resources/views/components/textarea.blade.php | 4 +- resources/views/layouts/app.blade.php | 2 +- .../partials/footer/copyright.blade.php | 2 +- .../partials/footer/navigation.blade.php | 2 +- .../partials/footer/newsletter.blade.php | 14 ++-- .../layouts/partials/footer/social.blade.php | 6 +- .../layouts/partials/header/account.blade.php | 8 +-- .../partials/header/autocomplete.blade.php | 2 +- .../partials/header/minicart.blade.php | 8 +-- resources/views/listing/filters.blade.php | 8 +-- .../listing/partials/filter/boolean.blade.php | 5 +- .../listing/partials/filter/select.blade.php | 11 +-- .../partials/filter/selected.blade.php | 8 +-- .../listing/partials/filter/swatch.blade.php | 6 +- .../views/listing/partials/item.blade.php | 2 +- .../views/listing/partials/stats.blade.php | 2 +- resources/views/listing/products.blade.php | 2 +- resources/views/product/overview.blade.php | 6 +- .../product/partials/addtocart.blade.php | 4 +- tailwind.config.js | 58 ++++++++++++---- 53 files changed, 167 insertions(+), 238 deletions(-) delete mode 100644 resources/css/theme-variables.css delete mode 100644 resources/views/checkout/steps/credentials.blade.php delete mode 100644 resources/views/checkout/steps/payment.blade.php create mode 100644 resources/views/components/button/conversion.blade.php create mode 100644 resources/views/components/button/secondary.blade.php diff --git a/resources/css/app.css b/resources/css/app.css index 4bcf8b5cc..0f5030156 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,7 +1,6 @@ @import 'components/vue-slider'; @import 'components/price-slider'; -@import './theme-variables.css'; -@import './components/pagination.css'; +@import 'components/pagination'; @tailwind base; @tailwind components; @@ -10,12 +9,15 @@ [v-cloak] { display: none; } + [v-blur] { @apply animate-pulse blur; } + * { -webkit-tap-highlight-color: transparant; } + listing { /* Reset browser added styling causing unexpected behavior */ display: contents; diff --git a/resources/css/components/pagination.css b/resources/css/components/pagination.css index 3a3803a46..1027377b5 100644 --- a/resources/css/components/pagination.css +++ b/resources/css/components/pagination.css @@ -8,7 +8,7 @@ } .pagination-button { - @apply !font-semibold !font-sans !border !border-border !rounded !bg-white !text-neutral !shadow; + @apply !font-semibold !font-sans !border !border-border !rounded !bg-white !text !shadow; } .pagination-button.active { diff --git a/resources/css/components/price-slider.css b/resources/css/components/price-slider.css index 80bcc1364..c4fb6d506 100644 --- a/resources/css/components/price-slider.css +++ b/resources/css/components/price-slider.css @@ -4,11 +4,11 @@ div.vue-slider { div.vue-slider-process, div.vue-slider-rail { - @apply h-2 bg-neutral !important; + @apply h-2 bg !important; } div.vue-slider-rail { - @apply bg-highlight !important; + @apply bg-emphasis !important; } div.vue-slider .vue-slider-dot { @@ -16,11 +16,11 @@ div.vue-slider .vue-slider-dot { } div.vue-slider-dot-tooltip-inner { - @apply bg-white text-neutral border-border border px-1.5 !important; + @apply bg-white text border border-default px-1.5 !important; } span.vue-slider-dot-tooltip-text { - @apply font-medium font-sans text-neutral; + @apply font-medium font-sans text; } div.vue-slider-dot-tooltip::before { diff --git a/resources/css/theme-variables.css b/resources/css/theme-variables.css deleted file mode 100644 index 1a80dceac..000000000 --- a/resources/css/theme-variables.css +++ /dev/null @@ -1,10 +0,0 @@ -:root { - --primary: 47 188 133; - --primary-text: 255 255 255; - --secondary: 249 115 22; - --secondary-text: 255 255 255; - --neutral: 51 65 85; - --inactive: 100 116 139; - --highlight: 241 245 249; - --border: 231 235 239; -} diff --git a/resources/views/cart/coupon/add.blade.php b/resources/views/cart/coupon/add.blade.php index 7bbfa45e9..e5d69cb26 100644 --- a/resources/views/cart/coupon/add.blade.php +++ b/resources/views/cart/coupon/add.blade.php @@ -15,10 +15,11 @@ placeholder="Coupon code" v-model="variables.coupon_code" v-bind:disabled="$root.loading" + wrapperClass="flex flex-1" required /> - + @lang('Apply') - + diff --git a/resources/views/cart/item/quantity.blade.php b/resources/views/cart/item/quantity.blade.php index 08943fecd..79ec0209e 100644 --- a/resources/views/cart/item/quantity.blade.php +++ b/resources/views/cart/item/quantity.blade.php @@ -5,13 +5,14 @@ :error-callback="checkResponseForExpiredCart" v-slot="{ mutate, variables }" > -
    + - + - + diff --git a/resources/views/cart/sidebar.blade.php b/resources/views/cart/sidebar.blade.php index 6ad59e0f2..8eb87eb08 100644 --- a/resources/views/cart/sidebar.blade.php +++ b/resources/views/cart/sidebar.blade.php @@ -43,12 +43,12 @@
    - @lang('Checkout') - -
    \ No newline at end of file + +
    diff --git a/resources/views/checkout/pages/credentials.blade.php b/resources/views/checkout/pages/credentials.blade.php index bbb5b3cb7..22819ce94 100644 --- a/resources/views/checkout/pages/credentials.blade.php +++ b/resources/views/checkout/pages/credentials.blade.php @@ -9,7 +9,7 @@ @include('rapidez::checkout.partials.progressbar')
    -
    +
    - + @lang('Next') - +
    diff --git a/resources/views/checkout/pages/login.blade.php b/resources/views/checkout/pages/login.blade.php index f588b8556..dfd31999a 100644 --- a/resources/views/checkout/pages/login.blade.php +++ b/resources/views/checkout/pages/login.blade.php @@ -20,9 +20,9 @@ class="max-w-md mx-auto" > @include('rapidez::checkout.steps.login') - + @lang('Next') - +
    @endsection diff --git a/resources/views/checkout/pages/onestep.blade.php b/resources/views/checkout/pages/onestep.blade.php index fb20feaa7..def44c67d 100644 --- a/resources/views/checkout/pages/onestep.blade.php +++ b/resources/views/checkout/pages/onestep.blade.php @@ -7,7 +7,7 @@ @section('content')
    -
    +
    - @if (!$loop->last) -
    +
    @endif $currentStepKey < $checkoutStepKey, - 'bg-neutral text-white' => $checkoutStepKey <= $currentStepKey, + 'bg-emphasis' => $checkoutStepKey <= $currentStepKey, 'bg-white' => $checkoutStepKey > $currentStepKey, - 'bg-neutral text-white shadow-neutral pointer-events-none cursor-default' => $checkoutStepKey === $currentStepKey + 'bg-emphasis shadow-md pointer-events-none cursor-default' => $checkoutStepKey === $currentStepKey ])> {{ $checkoutStepKey + 1 }} diff --git a/resources/views/checkout/partials/sidebar.blade.php b/resources/views/checkout/partials/sidebar.blade.php index 4a4448368..09d3ddf92 100644 --- a/resources/views/checkout/partials/sidebar.blade.php +++ b/resources/views/checkout/partials/sidebar.blade.php @@ -36,7 +36,7 @@
    -

    +

    @@ -49,7 +49,7 @@
    -

    @lang('Billing address')

    +

    @lang('Billing address')

    • @{{ cart.billing_address.company }}
    • @{{ cart.billing_address.prefix }} @{{ cart.billing_address.firstname }} @{{ cart.billing_address.middlename }} @{{ cart.billing_address.lastname }} @{{ cart.billing_address.suffix }}
    • diff --git a/resources/views/checkout/steps/credentials.blade.php b/resources/views/checkout/steps/credentials.blade.php deleted file mode 100644 index 2f4736d9f..000000000 --- a/resources/views/checkout/steps/credentials.blade.php +++ /dev/null @@ -1,37 +0,0 @@ -

      @lang('Credentials')

      - - -
      -
      -

      - @lang('Shipping address') -

      - @include('rapidez::checkout.partials.address', ['type' => 'shipping']) -
      -
      - - @lang('My billing and shipping address are the same') - -
      -
      -

      - @lang('Billing address') -

      - @include('rapidez::checkout.partials.address', ['type' => 'billing']) -
      -
      - -
      -

      - @lang('Shipping method') -

      - -
      - - @lang('Continue') - - diff --git a/resources/views/checkout/steps/payment.blade.php b/resources/views/checkout/steps/payment.blade.php deleted file mode 100644 index 6760f058f..000000000 --- a/resources/views/checkout/steps/payment.blade.php +++ /dev/null @@ -1,68 +0,0 @@ -@php($checkoutAgreements = \Rapidez\Core\Models\CheckoutAgreement::all()) - -

      @lang('Payment method')

      -
      -
      - -
      - @{{ method.title }} - -
      -
      -
      - - @if (count($checkoutAgreements)) -
      - @foreach ($checkoutAgreements as $agreement) - - - {{ $agreement->name }} - - - @if ($agreement->is_html) -
      - {!! $agreement->content !!} -
      - @else -
      - {{ $agreement->content }} -
      - @endif -
      - - @if ($agreement->mode == 'AUTO') - - @else -
      - - - -
      - @endif - @endforeach -
      - @endif - - - @lang('Place order') - - diff --git a/resources/views/checkout/steps/place_order.blade.php b/resources/views/checkout/steps/place_order.blade.php index eadace8aa..fdc38b2ba 100644 --- a/resources/views/checkout/steps/place_order.blade.php +++ b/resources/views/checkout/steps/place_order.blade.php @@ -8,8 +8,8 @@ v-slot="{ mutate, variables }" >
      - + @lang('Place order') - +
      diff --git a/resources/views/checkout/steps/success.blade.php b/resources/views/checkout/steps/success.blade.php index 816dabdef..16a774091 100644 --- a/resources/views/checkout/steps/success.blade.php +++ b/resources/views/checkout/steps/success.blade.php @@ -2,7 +2,7 @@ - + @lang('Show results') - +
    - + @lang('Filters') - + diff --git a/resources/views/listing/partials/filter/boolean.blade.php b/resources/views/listing/partials/filter/boolean.blade.php index af036861e..84e39c7dc 100644 --- a/resources/views/listing/partials/filter/boolean.blade.php +++ b/resources/views/listing/partials/filter/boolean.blade.php @@ -19,7 +19,10 @@ class="relative pb-4" v-bind:checked="value[item.key]" v-on:change="handleChange(item.key)" > -
    +
    (@{{ item.doc_count }}) diff --git a/resources/views/listing/partials/filter/select.blade.php b/resources/views/listing/partials/filter/select.blade.php index 35b3b2d9e..02380e950 100644 --- a/resources/views/listing/partials/filter/select.blade.php +++ b/resources/views/listing/partials/filter/select.blade.php @@ -3,9 +3,9 @@ :component-id="filter.code" :data-field="filter.code+'.keyword'" :inner-class="{ - count: 'text-inactive', + count: 'text-muted', list: '!max-h-full [&>li]:!h-auto', - label: 'text-inactive before:shrink-0' + label: 'text-muted before:shrink-0' }" :react="{and: filter.input == 'multiselect' ? reactiveFilters : reactiveFilters.filter(item => item != filter.code) }" :query-format="filter.input == 'multiselect' ? 'and' : 'or'" @@ -20,14 +20,17 @@ v-for="item, index in data" v-if="index < 6 || isOpen" :key="item._id" - class="flex justify-between text-base text-inactive" + class="flex justify-between text-base text-muted" >
    -
    +
    @{{ item.key }} (@{{ item.doc_count }})
    diff --git a/resources/views/listing/partials/filter/selected.blade.php b/resources/views/listing/partials/filter/selected.blade.php index bbb3ac473..e22895bcf 100644 --- a/resources/views/listing/partials/filter/selected.blade.php +++ b/resources/views/listing/partials/filter/selected.blade.php @@ -9,19 +9,19 @@
    -
    +
    @lang('You have filtered for:')
    -
    - + @{{ filter.value }} - +
    diff --git a/resources/views/listing/partials/filter/swatch.blade.php b/resources/views/listing/partials/filter/swatch.blade.php index a28c05c56..ac63a08f4 100644 --- a/resources/views/listing/partials/filter/swatch.blade.php +++ b/resources/views/listing/partials/filter/swatch.blade.php @@ -3,7 +3,7 @@ :component-id="filter.code" :data-field="filter.super ? 'super_' + filter.code : (filter.visual_swatch ? 'visual_' + filter.code : filter.code)" :inner-class="{ - title: 'capitalize text-sm font-medium text-gray-900', + title: 'capitalize text-sm font-medium text', }" :react="{and: filter.input == 'multiselect' ? reactiveFilters : reactiveFilters.filter(item => item != filter.code) }" :query-format="filter.input == 'multiselect' ? 'and' : 'or'" @@ -25,8 +25,8 @@ class="size-6 ring-black/5 ring-1 ring-inset cursor-pointer flex items-center ju
    @{{ item.product.name }} + @{{ item.product.name }} +
    + @lang('Out of stock') +
    +
    @{{ item.quantity }} @{{ item.prices.row_total.value | price }}