generated from boboko/starter
product options layout and add to cart button in homepage
This commit is contained in:
@@ -40,26 +40,78 @@ public function show(string $locale, int $id)
|
|||||||
|
|
||||||
$collection = $product['collections'][0] ?? null;
|
$collection = $product['collections'][0] ?? null;
|
||||||
|
|
||||||
$variantsData = $this->products->variantSummaries($product);
|
[$productOptions, $variantsData] = $this->buildOptionPicker($id);
|
||||||
|
|
||||||
$firstVariant = $product['variants'][0] ?? null;
|
|
||||||
$option = $firstVariant['options'][0]['option'] ?? null;
|
|
||||||
|
|
||||||
// The "Color" option type is the only one that writes a hex code into
|
|
||||||
// meta (see boboko/core's ColorOptionType) — its presence is how we
|
|
||||||
// tell a color option (swatches) from any other option (buttons).
|
|
||||||
$optionIsColor = collect($product['variants'])
|
|
||||||
->contains(fn (array $variant) => !empty($variant['options'][0]['meta']['hex'] ?? null));
|
|
||||||
|
|
||||||
return view('product.show', [
|
return view('product.show', [
|
||||||
'collection' => $collection,
|
'collection' => $collection,
|
||||||
'product' => $product,
|
'product' => $product,
|
||||||
'option' => $option,
|
'productOptions' => $productOptions,
|
||||||
'optionIsColor' => $optionIsColor,
|
|
||||||
'variantsData' => $variantsData,
|
'variantsData' => $variantsData,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One button/swatch group per product option (a product can have several
|
||||||
|
* — e.g. a custom-photo product with size + style + person-count, each
|
||||||
|
* combination resolved client-side to one exact ProductVariant, see
|
||||||
|
* product-form-controller.js) plus the per-variant data the picker
|
||||||
|
* resolves a selection against.
|
||||||
|
*
|
||||||
|
* Reads live Eloquent rather than the Meilisearch-indexed $product array
|
||||||
|
* the rest of the page uses (same reasoning as checkStock() below): the
|
||||||
|
* index has no concept of option/value display order (Lunar's
|
||||||
|
* `position` column) or a stable value id, both of which the picker
|
||||||
|
* needs — to render values in the merchant's intended order, and to
|
||||||
|
* match a combination back to one exact variant without relying on
|
||||||
|
* translated label strings staying unique.
|
||||||
|
*
|
||||||
|
* @return array{0: array, 1: array}
|
||||||
|
*/
|
||||||
|
private function buildOptionPicker(int $productId): array
|
||||||
|
{
|
||||||
|
$variants = ProductVariant::query()
|
||||||
|
->where('product_id', $productId)
|
||||||
|
->with(['values.option', 'prices', 'images'])
|
||||||
|
->get();
|
||||||
|
|
||||||
|
$productOptions = $variants
|
||||||
|
->flatMap(fn (ProductVariant $variant) => $variant->values)
|
||||||
|
->unique('id')
|
||||||
|
->groupBy(fn ($value) => $value->option->handle)
|
||||||
|
->map(function ($values, $handle) {
|
||||||
|
$sorted = $values->sortBy([['position', 'asc'], ['id', 'asc']]);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'handle' => $handle,
|
||||||
|
'label' => $sorted->first()->option->translate('name'),
|
||||||
|
// The "Color" option type is the only one that writes a
|
||||||
|
// hex code into meta (see boboko/core's ColorOptionType)
|
||||||
|
// — its presence is how we tell a color option (swatches)
|
||||||
|
// from any other option (buttons).
|
||||||
|
'isColor' => $sorted->contains(fn ($v) => !empty($v->meta['hex'] ?? null)),
|
||||||
|
'values' => $sorted->map(fn ($v) => [
|
||||||
|
'id' => $v->id,
|
||||||
|
'label' => $v->translate('name'),
|
||||||
|
'hex' => $v->meta['hex'] ?? null,
|
||||||
|
])->values()->all(),
|
||||||
|
];
|
||||||
|
})
|
||||||
|
->values()
|
||||||
|
->all();
|
||||||
|
|
||||||
|
$variantsData = $variants->map(fn (ProductVariant $variant) => [
|
||||||
|
'id' => $variant->id,
|
||||||
|
'price' => $variant->prices->first()?->price?->decimal(),
|
||||||
|
'image' => $variant->images->first()?->getUrl(),
|
||||||
|
// handle => selected value id, for matching a combination of
|
||||||
|
// selections back to this variant — see selectVariant() in
|
||||||
|
// product-form-controller.js.
|
||||||
|
'options' => $variant->values->mapWithKeys(fn ($v) => [$v->option->handle => $v->id])->all(),
|
||||||
|
])->values()->all();
|
||||||
|
|
||||||
|
return [$productOptions, $variantsData];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A storefront-owned, checkout-module-independent stock check — the
|
* A storefront-owned, checkout-module-independent stock check — the
|
||||||
* product page's "Add to cart" calls this first and only submits to the
|
* product page's "Add to cart" calls this first and only submits to the
|
||||||
|
|||||||
Generated
+94
-240
@@ -515,11 +515,11 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "boboko/core",
|
"name": "boboko/core",
|
||||||
"version": "0.18.0",
|
"version": "0.19.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://code.radical-elements.com/boboko/core.git",
|
"url": "https://code.radical-elements.com/boboko/core.git",
|
||||||
"reference": "2cc6f5e5f0d43102001d65b60810423a35e83e1f"
|
"reference": "0437057e5d604e3c05479089071fcbe79ad69bb7"
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
@@ -558,7 +558,8 @@
|
|||||||
"Modules\\Core\\Providers\\CartServiceProvider",
|
"Modules\\Core\\Providers\\CartServiceProvider",
|
||||||
"Modules\\Core\\Providers\\ReviewServiceProvider",
|
"Modules\\Core\\Providers\\ReviewServiceProvider",
|
||||||
"Modules\\Core\\Providers\\ShippingServiceProvider",
|
"Modules\\Core\\Providers\\ShippingServiceProvider",
|
||||||
"Modules\\Core\\Providers\\OrderServiceProvider"
|
"Modules\\Core\\Providers\\OrderServiceProvider",
|
||||||
|
"Modules\\Core\\Providers\\PrivacyServiceProvider"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -568,7 +569,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "Core module — authentication and shared panel behaviour",
|
"description": "Core module — authentication and shared panel behaviour",
|
||||||
"time": "2026-09-15T21:06:04+00:00"
|
"time": "2026-09-17T22:30:37+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@@ -1062,16 +1063,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "danharrin/livewire-rate-limiting",
|
"name": "danharrin/livewire-rate-limiting",
|
||||||
"version": "v2.2.1",
|
"version": "v2.3.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/danharrin/livewire-rate-limiting.git",
|
"url": "https://github.com/danharrin/livewire-rate-limiting.git",
|
||||||
"reference": "69436717dc70e30f80d7f8fd02504c22992a9ad5"
|
"reference": "46c4ceaf7997c713c79afe7c507a39649f6c9d6f"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/69436717dc70e30f80d7f8fd02504c22992a9ad5",
|
"url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/46c4ceaf7997c713c79afe7c507a39649f6c9d6f",
|
||||||
"reference": "69436717dc70e30f80d7f8fd02504c22992a9ad5",
|
"reference": "46c4ceaf7997c713c79afe7c507a39649f6c9d6f",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1112,7 +1113,7 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2026-08-06T08:41:51+00:00"
|
"time": "2026-09-18T07:38:01+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dflydev/dot-access-data",
|
"name": "dflydev/dot-access-data",
|
||||||
@@ -1329,27 +1330,25 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "doctrine/lexer",
|
"name": "doctrine/lexer",
|
||||||
"version": "3.0.1",
|
"version": "3.0.2",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/doctrine/lexer.git",
|
"url": "https://github.com/doctrine/lexer.git",
|
||||||
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd"
|
"reference": "e96fe45e92a54233726014a7cc7340abf29bb14c"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
|
"url": "https://api.github.com/repos/doctrine/lexer/zipball/e96fe45e92a54233726014a7cc7340abf29bb14c",
|
||||||
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
|
"reference": "e96fe45e92a54233726014a7cc7340abf29bb14c",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"php": "^8.1"
|
"php": "^8.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"doctrine/coding-standard": "^12",
|
"doctrine/coding-standard": "^14",
|
||||||
"phpstan/phpstan": "^1.10",
|
"phpstan/phpstan": "^2",
|
||||||
"phpunit/phpunit": "^10.5",
|
"phpunit/phpunit": "^10.5.58 || ^12.5.4"
|
||||||
"psalm/plugin-phpunit": "^0.18.3",
|
|
||||||
"vimeo/psalm": "^5.21"
|
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -1386,7 +1385,7 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/doctrine/lexer/issues",
|
"issues": "https://github.com/doctrine/lexer/issues",
|
||||||
"source": "https://github.com/doctrine/lexer/tree/3.0.1"
|
"source": "https://github.com/doctrine/lexer/tree/3.0.2"
|
||||||
},
|
},
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -1402,7 +1401,7 @@
|
|||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-02-05T11:56:58+00:00"
|
"time": "2026-06-14T20:44:06+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dompdf/dompdf",
|
"name": "dompdf/dompdf",
|
||||||
@@ -1692,7 +1691,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/actions",
|
"name": "filament/actions",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/actions.git",
|
"url": "https://github.com/filamentphp/actions.git",
|
||||||
@@ -1741,16 +1740,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/filament",
|
"name": "filament/filament",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/panels.git",
|
"url": "https://github.com/filamentphp/panels.git",
|
||||||
"reference": "ff848c7750b49f0bb532d44c6768f0e601fb09d2"
|
"reference": "1215d7d79b5609f66c1e432933610eefa0ab3ad6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/ff848c7750b49f0bb532d44c6768f0e601fb09d2",
|
"url": "https://api.github.com/repos/filamentphp/panels/zipball/1215d7d79b5609f66c1e432933610eefa0ab3ad6",
|
||||||
"reference": "ff848c7750b49f0bb532d44c6768f0e601fb09d2",
|
"reference": "1215d7d79b5609f66c1e432933610eefa0ab3ad6",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1795,20 +1794,20 @@
|
|||||||
"issues": "https://github.com/filamentphp/filament/issues",
|
"issues": "https://github.com/filamentphp/filament/issues",
|
||||||
"source": "https://github.com/filamentphp/filament"
|
"source": "https://github.com/filamentphp/filament"
|
||||||
},
|
},
|
||||||
"time": "2026-09-15T16:56:27+00:00"
|
"time": "2026-09-20T19:15:07+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/forms",
|
"name": "filament/forms",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/forms.git",
|
"url": "https://github.com/filamentphp/forms.git",
|
||||||
"reference": "18e3382115ed3062a074736c9e80cfcb3ffe2221"
|
"reference": "10d7ba430544ae706a406713105ba37b18f52008"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/18e3382115ed3062a074736c9e80cfcb3ffe2221",
|
"url": "https://api.github.com/repos/filamentphp/forms/zipball/10d7ba430544ae706a406713105ba37b18f52008",
|
||||||
"reference": "18e3382115ed3062a074736c9e80cfcb3ffe2221",
|
"reference": "10d7ba430544ae706a406713105ba37b18f52008",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1845,20 +1844,20 @@
|
|||||||
"issues": "https://github.com/filamentphp/filament/issues",
|
"issues": "https://github.com/filamentphp/filament/issues",
|
||||||
"source": "https://github.com/filamentphp/filament"
|
"source": "https://github.com/filamentphp/filament"
|
||||||
},
|
},
|
||||||
"time": "2026-09-15T17:00:54+00:00"
|
"time": "2026-09-20T18:45:30+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/infolists",
|
"name": "filament/infolists",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/infolists.git",
|
"url": "https://github.com/filamentphp/infolists.git",
|
||||||
"reference": "b05093c8ff726d28eb75df29fa21bdd836386ded"
|
"reference": "1979591422ff823543622b87e0276cdca3f863e8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/b05093c8ff726d28eb75df29fa21bdd836386ded",
|
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/1979591422ff823543622b87e0276cdca3f863e8",
|
||||||
"reference": "b05093c8ff726d28eb75df29fa21bdd836386ded",
|
"reference": "1979591422ff823543622b87e0276cdca3f863e8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1890,20 +1889,20 @@
|
|||||||
"issues": "https://github.com/filamentphp/filament/issues",
|
"issues": "https://github.com/filamentphp/filament/issues",
|
||||||
"source": "https://github.com/filamentphp/filament"
|
"source": "https://github.com/filamentphp/filament"
|
||||||
},
|
},
|
||||||
"time": "2026-09-15T16:59:31+00:00"
|
"time": "2026-09-20T18:45:56+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/notifications",
|
"name": "filament/notifications",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/notifications.git",
|
"url": "https://github.com/filamentphp/notifications.git",
|
||||||
"reference": "f481bcfb705ad650f96336e7496814419907639c"
|
"reference": "40989f93ec3dc36f5582024ca41f99333a4c0ea7"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filamentphp/notifications/zipball/f481bcfb705ad650f96336e7496814419907639c",
|
"url": "https://api.github.com/repos/filamentphp/notifications/zipball/40989f93ec3dc36f5582024ca41f99333a4c0ea7",
|
||||||
"reference": "f481bcfb705ad650f96336e7496814419907639c",
|
"reference": "40989f93ec3dc36f5582024ca41f99333a4c0ea7",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1937,20 +1936,20 @@
|
|||||||
"issues": "https://github.com/filamentphp/filament/issues",
|
"issues": "https://github.com/filamentphp/filament/issues",
|
||||||
"source": "https://github.com/filamentphp/filament"
|
"source": "https://github.com/filamentphp/filament"
|
||||||
},
|
},
|
||||||
"time": "2026-09-15T16:55:37+00:00"
|
"time": "2026-09-20T18:48:58+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/query-builder",
|
"name": "filament/query-builder",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/query-builder.git",
|
"url": "https://github.com/filamentphp/query-builder.git",
|
||||||
"reference": "f7c1eb9eeaded573838c1f3b8d0a63f55434331a"
|
"reference": "0428645f4cd0a232b6bc446437cbfafa366e56c0"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filamentphp/query-builder/zipball/f7c1eb9eeaded573838c1f3b8d0a63f55434331a",
|
"url": "https://api.github.com/repos/filamentphp/query-builder/zipball/0428645f4cd0a232b6bc446437cbfafa366e56c0",
|
||||||
"reference": "f7c1eb9eeaded573838c1f3b8d0a63f55434331a",
|
"reference": "0428645f4cd0a232b6bc446437cbfafa366e56c0",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -1983,20 +1982,20 @@
|
|||||||
"issues": "https://github.com/filamentphp/filament/issues",
|
"issues": "https://github.com/filamentphp/filament/issues",
|
||||||
"source": "https://github.com/filamentphp/filament"
|
"source": "https://github.com/filamentphp/filament"
|
||||||
},
|
},
|
||||||
"time": "2026-09-15T16:55:48+00:00"
|
"time": "2026-09-20T18:53:32+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/schemas",
|
"name": "filament/schemas",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/schemas.git",
|
"url": "https://github.com/filamentphp/schemas.git",
|
||||||
"reference": "fec2747e8cf1e6ca76d6c883c7f1019da994f771"
|
"reference": "dc4eeb57671b104b9e60f6d3f435f803c705e6df"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filamentphp/schemas/zipball/fec2747e8cf1e6ca76d6c883c7f1019da994f771",
|
"url": "https://api.github.com/repos/filamentphp/schemas/zipball/dc4eeb57671b104b9e60f6d3f435f803c705e6df",
|
||||||
"reference": "fec2747e8cf1e6ca76d6c883c7f1019da994f771",
|
"reference": "dc4eeb57671b104b9e60f6d3f435f803c705e6df",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2028,11 +2027,11 @@
|
|||||||
"issues": "https://github.com/filamentphp/filament/issues",
|
"issues": "https://github.com/filamentphp/filament/issues",
|
||||||
"source": "https://github.com/filamentphp/filament"
|
"source": "https://github.com/filamentphp/filament"
|
||||||
},
|
},
|
||||||
"time": "2026-09-08T10:20:09+00:00"
|
"time": "2026-09-20T18:49:21+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/spatie-laravel-media-library-plugin",
|
"name": "filament/spatie-laravel-media-library-plugin",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/spatie-laravel-media-library-plugin.git",
|
"url": "https://github.com/filamentphp/spatie-laravel-media-library-plugin.git",
|
||||||
@@ -2069,16 +2068,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/support",
|
"name": "filament/support",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/support.git",
|
"url": "https://github.com/filamentphp/support.git",
|
||||||
"reference": "6ff50570ff1f2c6c1caf6ed1e7a2ce146b901106"
|
"reference": "6bd7bdb5f5ff3b1a70cac6dabe28f5d996a36bcc"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/6ff50570ff1f2c6c1caf6ed1e7a2ce146b901106",
|
"url": "https://api.github.com/repos/filamentphp/support/zipball/6bd7bdb5f5ff3b1a70cac6dabe28f5d996a36bcc",
|
||||||
"reference": "6ff50570ff1f2c6c1caf6ed1e7a2ce146b901106",
|
"reference": "6bd7bdb5f5ff3b1a70cac6dabe28f5d996a36bcc",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2124,20 +2123,20 @@
|
|||||||
"issues": "https://github.com/filamentphp/filament/issues",
|
"issues": "https://github.com/filamentphp/filament/issues",
|
||||||
"source": "https://github.com/filamentphp/filament"
|
"source": "https://github.com/filamentphp/filament"
|
||||||
},
|
},
|
||||||
"time": "2026-09-15T16:59:53+00:00"
|
"time": "2026-09-20T19:14:52+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/tables",
|
"name": "filament/tables",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/tables.git",
|
"url": "https://github.com/filamentphp/tables.git",
|
||||||
"reference": "51f2f2d19eddae424b8b4a08ab9a14770e2acc9d"
|
"reference": "8a0c6634d752e0b71ed0871f92d533c0d03d7601"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/51f2f2d19eddae424b8b4a08ab9a14770e2acc9d",
|
"url": "https://api.github.com/repos/filamentphp/tables/zipball/8a0c6634d752e0b71ed0871f92d533c0d03d7601",
|
||||||
"reference": "51f2f2d19eddae424b8b4a08ab9a14770e2acc9d",
|
"reference": "8a0c6634d752e0b71ed0871f92d533c0d03d7601",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2170,20 +2169,20 @@
|
|||||||
"issues": "https://github.com/filamentphp/filament/issues",
|
"issues": "https://github.com/filamentphp/filament/issues",
|
||||||
"source": "https://github.com/filamentphp/filament"
|
"source": "https://github.com/filamentphp/filament"
|
||||||
},
|
},
|
||||||
"time": "2026-09-15T16:56:17+00:00"
|
"time": "2026-09-20T18:53:09+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "filament/widgets",
|
"name": "filament/widgets",
|
||||||
"version": "v4.13.2",
|
"version": "v4.13.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/filamentphp/widgets.git",
|
"url": "https://github.com/filamentphp/widgets.git",
|
||||||
"reference": "3d5a4dde469ad9f8e6cb2d5928e4882f6a92afe1"
|
"reference": "5ec044730102c3444d019232d8edc3d8922876c6"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/filamentphp/widgets/zipball/3d5a4dde469ad9f8e6cb2d5928e4882f6a92afe1",
|
"url": "https://api.github.com/repos/filamentphp/widgets/zipball/5ec044730102c3444d019232d8edc3d8922876c6",
|
||||||
"reference": "3d5a4dde469ad9f8e6cb2d5928e4882f6a92afe1",
|
"reference": "5ec044730102c3444d019232d8edc3d8922876c6",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -2214,7 +2213,7 @@
|
|||||||
"issues": "https://github.com/filamentphp/filament/issues",
|
"issues": "https://github.com/filamentphp/filament/issues",
|
||||||
"source": "https://github.com/filamentphp/filament"
|
"source": "https://github.com/filamentphp/filament"
|
||||||
},
|
},
|
||||||
"time": "2026-09-07T18:36:33+00:00"
|
"time": "2026-09-20T18:53:38+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "fruitcake/php-cors",
|
"name": "fruitcake/php-cors",
|
||||||
@@ -7844,20 +7843,20 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ramsey/uuid",
|
"name": "ramsey/uuid",
|
||||||
"version": "4.9.3",
|
"version": "4.9.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/ramsey/uuid.git",
|
"url": "https://github.com/ramsey/uuid.git",
|
||||||
"reference": "1df15849d00943a67d677dc9cfd80795f038c9f8"
|
"reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8",
|
"url": "https://api.github.com/repos/ramsey/uuid/zipball/75d73f48d02797c2c285a7e9f348fadc0102ffe2",
|
||||||
"reference": "1df15849d00943a67d677dc9cfd80795f038c9f8",
|
"reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"brick/math": ">=0.8.16 <=0.18",
|
"brick/math": "^0.8.16 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13 || ^0.14 || ^0.15 || ^0.16 || ^0.17 || ^0.18 || ^0.19 || ^0.20 || ^1.0",
|
||||||
"php": "^8.0",
|
"php": "^8.0",
|
||||||
"ramsey/collection": "^1.2 || ^2.0"
|
"ramsey/collection": "^1.2 || ^2.0"
|
||||||
},
|
},
|
||||||
@@ -7916,9 +7915,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/ramsey/uuid/issues",
|
"issues": "https://github.com/ramsey/uuid/issues",
|
||||||
"source": "https://github.com/ramsey/uuid/tree/4.9.3"
|
"source": "https://github.com/ramsey/uuid/tree/4.9.4"
|
||||||
},
|
},
|
||||||
"time": "2026-06-18T03:57:49+00:00"
|
"time": "2026-09-16T11:39:30+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "ryangjchandler/blade-capture-directive",
|
"name": "ryangjchandler/blade-capture-directive",
|
||||||
@@ -8000,35 +7999,33 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sabberworm/php-css-parser",
|
"name": "sabberworm/php-css-parser",
|
||||||
"version": "v9.4.0",
|
"version": "v9.5.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
|
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
|
||||||
"reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f"
|
"reference": "f284e63b6e891e0c28631e54ba06c3ed102a9ef3"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
|
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f284e63b6e891e0c28631e54ba06c3ed102a9ef3",
|
||||||
"reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
|
"reference": "f284e63b6e891e0c28631e54ba06c3ed102a9ef3",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-iconv": "*",
|
"ext-iconv": "*",
|
||||||
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0"
|
||||||
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
|
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"php-parallel-lint/php-parallel-lint": "1.4.0",
|
"php-parallel-lint/php-parallel-lint": "1.4.0",
|
||||||
"phpstan/extension-installer": "1.4.3",
|
"phpstan/extension-installer": "1.4.3",
|
||||||
"phpstan/phpstan": "1.12.33 || 2.2.2",
|
"phpstan/phpstan": "1.12.33 || 2.2.9",
|
||||||
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.16",
|
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.18",
|
||||||
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.11",
|
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.12",
|
||||||
"phpunit/phpunit": "8.5.52",
|
"phpunit/phpunit": "8.5.54",
|
||||||
"rawr/phpunit-data-provider": "3.3.1",
|
"rawr/phpunit-data-provider": "3.3.1",
|
||||||
"rector/rector": "1.2.10 || 2.4.6",
|
"rector/rector": "1.2.10 || 2.6.2",
|
||||||
"rector/type-perfect": "1.0.0 || 2.1.3",
|
"rector/type-perfect": "1.0.0 || 2.1.4",
|
||||||
"squizlabs/php_codesniffer": "4.0.1",
|
"squizlabs/php_codesniffer": "4.0.4"
|
||||||
"thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.3"
|
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-mbstring": "for parsing UTF-8 CSS"
|
"ext-mbstring": "for parsing UTF-8 CSS"
|
||||||
@@ -8036,7 +8033,7 @@
|
|||||||
"type": "library",
|
"type": "library",
|
||||||
"extra": {
|
"extra": {
|
||||||
"branch-alias": {
|
"branch-alias": {
|
||||||
"dev-main": "9.5.x-dev"
|
"dev-main": "9.6.x-dev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -8074,9 +8071,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
|
"issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues",
|
||||||
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.4.0"
|
"source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.5.0"
|
||||||
},
|
},
|
||||||
"time": "2026-06-18T15:10:53+00:00"
|
"time": "2026-09-20T15:02:00+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "scrivo/highlight.php",
|
"name": "scrivo/highlight.php",
|
||||||
@@ -12603,16 +12600,16 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "technikermathe/blade-lucide-icons",
|
"name": "technikermathe/blade-lucide-icons",
|
||||||
"version": "v3.180.0",
|
"version": "v3.181.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/PascaleBeier/blade-lucide-icons.git",
|
"url": "https://github.com/PascaleBeier/blade-lucide-icons.git",
|
||||||
"reference": "adac9da3a65910fedc271129eea5340093910203"
|
"reference": "64ccac4ecfe1b833e9a5cf1ebe7216acbe3cac98"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/PascaleBeier/blade-lucide-icons/zipball/adac9da3a65910fedc271129eea5340093910203",
|
"url": "https://api.github.com/repos/PascaleBeier/blade-lucide-icons/zipball/64ccac4ecfe1b833e9a5cf1ebe7216acbe3cac98",
|
||||||
"reference": "adac9da3a65910fedc271129eea5340093910203",
|
"reference": "64ccac4ecfe1b833e9a5cf1ebe7216acbe3cac98",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
@@ -12662,152 +12659,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/PascaleBeier/blade-lucide-icons/issues",
|
"issues": "https://github.com/PascaleBeier/blade-lucide-icons/issues",
|
||||||
"source": "https://github.com/PascaleBeier/blade-lucide-icons/tree/v3.180.0"
|
"source": "https://github.com/PascaleBeier/blade-lucide-icons/tree/v3.181.0"
|
||||||
},
|
},
|
||||||
"time": "2026-09-15T02:25:56+00:00"
|
"time": "2026-09-18T02:09:04+00:00"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "thecodingmachine/safe",
|
|
||||||
"version": "v3.4.0",
|
|
||||||
"source": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/thecodingmachine/safe.git",
|
|
||||||
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19"
|
|
||||||
},
|
|
||||||
"dist": {
|
|
||||||
"type": "zip",
|
|
||||||
"url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19",
|
|
||||||
"reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19",
|
|
||||||
"shasum": ""
|
|
||||||
},
|
|
||||||
"require": {
|
|
||||||
"php": "^8.1"
|
|
||||||
},
|
|
||||||
"require-dev": {
|
|
||||||
"php-parallel-lint/php-parallel-lint": "^1.4",
|
|
||||||
"phpstan/phpstan": "^2",
|
|
||||||
"phpunit/phpunit": "^10",
|
|
||||||
"squizlabs/php_codesniffer": "^3.2"
|
|
||||||
},
|
|
||||||
"type": "library",
|
|
||||||
"autoload": {
|
|
||||||
"files": [
|
|
||||||
"lib/special_cases.php",
|
|
||||||
"generated/apache.php",
|
|
||||||
"generated/apcu.php",
|
|
||||||
"generated/array.php",
|
|
||||||
"generated/bzip2.php",
|
|
||||||
"generated/calendar.php",
|
|
||||||
"generated/classobj.php",
|
|
||||||
"generated/com.php",
|
|
||||||
"generated/cubrid.php",
|
|
||||||
"generated/curl.php",
|
|
||||||
"generated/datetime.php",
|
|
||||||
"generated/dir.php",
|
|
||||||
"generated/eio.php",
|
|
||||||
"generated/errorfunc.php",
|
|
||||||
"generated/exec.php",
|
|
||||||
"generated/fileinfo.php",
|
|
||||||
"generated/filesystem.php",
|
|
||||||
"generated/filter.php",
|
|
||||||
"generated/fpm.php",
|
|
||||||
"generated/ftp.php",
|
|
||||||
"generated/funchand.php",
|
|
||||||
"generated/gettext.php",
|
|
||||||
"generated/gmp.php",
|
|
||||||
"generated/gnupg.php",
|
|
||||||
"generated/hash.php",
|
|
||||||
"generated/ibase.php",
|
|
||||||
"generated/ibmDb2.php",
|
|
||||||
"generated/iconv.php",
|
|
||||||
"generated/image.php",
|
|
||||||
"generated/imap.php",
|
|
||||||
"generated/info.php",
|
|
||||||
"generated/inotify.php",
|
|
||||||
"generated/json.php",
|
|
||||||
"generated/ldap.php",
|
|
||||||
"generated/libxml.php",
|
|
||||||
"generated/lzf.php",
|
|
||||||
"generated/mailparse.php",
|
|
||||||
"generated/mbstring.php",
|
|
||||||
"generated/misc.php",
|
|
||||||
"generated/mysql.php",
|
|
||||||
"generated/mysqli.php",
|
|
||||||
"generated/network.php",
|
|
||||||
"generated/oci8.php",
|
|
||||||
"generated/opcache.php",
|
|
||||||
"generated/openssl.php",
|
|
||||||
"generated/outcontrol.php",
|
|
||||||
"generated/pcntl.php",
|
|
||||||
"generated/pcre.php",
|
|
||||||
"generated/pgsql.php",
|
|
||||||
"generated/posix.php",
|
|
||||||
"generated/ps.php",
|
|
||||||
"generated/pspell.php",
|
|
||||||
"generated/readline.php",
|
|
||||||
"generated/rnp.php",
|
|
||||||
"generated/rpminfo.php",
|
|
||||||
"generated/rrd.php",
|
|
||||||
"generated/sem.php",
|
|
||||||
"generated/session.php",
|
|
||||||
"generated/shmop.php",
|
|
||||||
"generated/sockets.php",
|
|
||||||
"generated/sodium.php",
|
|
||||||
"generated/solr.php",
|
|
||||||
"generated/spl.php",
|
|
||||||
"generated/sqlsrv.php",
|
|
||||||
"generated/ssdeep.php",
|
|
||||||
"generated/ssh2.php",
|
|
||||||
"generated/stream.php",
|
|
||||||
"generated/strings.php",
|
|
||||||
"generated/swoole.php",
|
|
||||||
"generated/uodbc.php",
|
|
||||||
"generated/uopz.php",
|
|
||||||
"generated/url.php",
|
|
||||||
"generated/var.php",
|
|
||||||
"generated/xdiff.php",
|
|
||||||
"generated/xml.php",
|
|
||||||
"generated/xmlrpc.php",
|
|
||||||
"generated/yaml.php",
|
|
||||||
"generated/yaz.php",
|
|
||||||
"generated/zip.php",
|
|
||||||
"generated/zlib.php"
|
|
||||||
],
|
|
||||||
"classmap": [
|
|
||||||
"lib/DateTime.php",
|
|
||||||
"lib/DateTimeImmutable.php",
|
|
||||||
"lib/Exceptions/",
|
|
||||||
"generated/Exceptions/"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"notification-url": "https://packagist.org/downloads/",
|
|
||||||
"license": [
|
|
||||||
"MIT"
|
|
||||||
],
|
|
||||||
"description": "PHP core functions that throw exceptions instead of returning FALSE on error",
|
|
||||||
"support": {
|
|
||||||
"issues": "https://github.com/thecodingmachine/safe/issues",
|
|
||||||
"source": "https://github.com/thecodingmachine/safe/tree/v3.4.0"
|
|
||||||
},
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"url": "https://github.com/OskarStark",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/shish",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/silasjoisten",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"url": "https://github.com/staabm",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"time": "2026-02-04T18:08:13+00:00"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "tijsverkoyen/css-to-inline-styles",
|
"name": "tijsverkoyen/css-to-inline-styles",
|
||||||
|
|||||||
Regular → Executable
Regular → Executable
@@ -18,11 +18,15 @@ export default class extends Controller {
|
|||||||
connect() {
|
connect() {
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
const urlId = parseInt(params.get('variant'))
|
const urlId = parseInt(params.get('variant'))
|
||||||
const defaultId = this.variantsValue[0]?.id
|
const urlVariant = this.variantsValue.find(v => v.id === urlId)
|
||||||
|
const initial = urlVariant ?? this.variantsValue[0]
|
||||||
|
|
||||||
this.selectedValue = urlId && this.variantsValue.find(v => v.id === urlId)
|
// A product can have several independent options (e.g. size + style
|
||||||
? urlId
|
// + person-count) — this tracks the currently-picked value id per
|
||||||
: defaultId
|
// option handle, and selectVariant() below resolves the full
|
||||||
|
// combination back to one exact variant on every change.
|
||||||
|
this.selections = { ...initial?.options }
|
||||||
|
this.selectedValue = initial?.id
|
||||||
|
|
||||||
// Capture phase, on this controller's own root element (an ancestor
|
// Capture phase, on this controller's own root element (an ancestor
|
||||||
// of the checkout module's add-to-cart <form>) — runs BEFORE that
|
// of the checkout module's add-to-cart <form>) — runs BEFORE that
|
||||||
@@ -108,11 +112,24 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
selectVariant(event) {
|
selectVariant(event) {
|
||||||
const id = parseInt(event.currentTarget.dataset.variantId)
|
const option = event.currentTarget.dataset.option
|
||||||
this.selectedValue = id
|
const valueId = parseInt(event.currentTarget.dataset.valueId)
|
||||||
|
this.selections = { ...this.selections, [option]: valueId }
|
||||||
|
|
||||||
|
const match = this.variantsValue.find(variant =>
|
||||||
|
Object.keys(this.selections).every(key => variant.options?.[key] === this.selections[key])
|
||||||
|
)
|
||||||
|
|
||||||
|
// No variant exists for this combination (e.g. an option value that
|
||||||
|
// isn't offered together with another currently-selected value) —
|
||||||
|
// leave the previous selection in place rather than pointing the
|
||||||
|
// add-to-cart form at nothing.
|
||||||
|
if (!match) return
|
||||||
|
|
||||||
|
this.selectedValue = match.id
|
||||||
|
|
||||||
const url = new URL(window.location)
|
const url = new URL(window.location)
|
||||||
url.searchParams.set('variant', id)
|
url.searchParams.set('variant', match.id)
|
||||||
window.history.pushState({}, '', url)
|
window.history.pushState({}, '', url)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,12 +157,16 @@ export default class extends Controller {
|
|||||||
if (purchasableInput) purchasableInput.value = id
|
if (purchasableInput) purchasableInput.value = id
|
||||||
|
|
||||||
this.swatchTargets.forEach(swatch => {
|
this.swatchTargets.forEach(swatch => {
|
||||||
const isSelected = parseInt(swatch.dataset.variantId) === id
|
const isSelected = this.selections[swatch.dataset.option] === parseInt(swatch.dataset.valueId)
|
||||||
swatch.classList.toggle('is-selected', isSelected)
|
swatch.classList.toggle('is-selected', isSelected)
|
||||||
swatch.setAttribute('aria-pressed', String(isSelected))
|
swatch.setAttribute('aria-pressed', String(isSelected))
|
||||||
|
|
||||||
if (isSelected && this.hasColorNameTarget) {
|
if (isSelected) {
|
||||||
this.colorNameTarget.textContent = swatch.getAttribute('aria-label')
|
// Each color-option group has its own colorName echo (see
|
||||||
|
// x-ui.color-swatch) — matched by option handle so a swatch
|
||||||
|
// in one group never overwrites another group's label.
|
||||||
|
const colorName = this.colorNameTargets.find(target => target.dataset.option === swatch.dataset.option)
|
||||||
|
if (colorName) colorName.textContent = swatch.getAttribute('aria-label')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
{{-- $variants: array of Modules\Core\Catalog\Services\ProductIndexer's mapVariant()
|
{{-- $values: one product option's de-duplicated, ordered values, as built by
|
||||||
shape (id, options: [{option, value, meta}], ...) — plain arrays, not Eloquent
|
ProductController::buildOptionPicker() — [{id, label, hex}]. --}}
|
||||||
models, since this is fed from Modules\Core\Catalog\Services\ProductService. --}}
|
@props(['values', 'option' => null, 'optionHandle' => null])
|
||||||
@props(['variants', 'option' => null])
|
|
||||||
|
|
||||||
<div {{ $attributes }}>
|
<div {{ $attributes }}>
|
||||||
@if($option)
|
@if($option)
|
||||||
<p class="font-bold mb-3 text-sm uppercase tracking-wide">
|
<p class="font-bold mb-3 text-sm uppercase tracking-wide">
|
||||||
{{ $option }}: <span data-product-form-target="colorName" class="font-normal normal-case"></span>
|
{{ $option }}: <span data-product-form-target="colorName" data-option="{{ $optionHandle }}" class="font-normal normal-case"></span>
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@@ -15,22 +14,17 @@ class="flex flex-wrap gap-2"
|
|||||||
role="group"
|
role="group"
|
||||||
aria-label="{{ $option ?? 'Color' }}"
|
aria-label="{{ $option ?? 'Color' }}"
|
||||||
>
|
>
|
||||||
@foreach($variants as $variant)
|
@foreach($values as $value)
|
||||||
@php
|
|
||||||
$value = $variant['options'][0] ?? null;
|
|
||||||
$label = $value['value'] ?? '';
|
|
||||||
$bg = $value['meta']['hex'] ?? '#cccccc';
|
|
||||||
@endphp
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="color-swatch"
|
class="color-swatch"
|
||||||
data-product-form-target="swatch"
|
data-product-form-target="swatch"
|
||||||
data-action="click->product-form#selectVariant"
|
data-action="click->product-form#selectVariant"
|
||||||
data-variant-id="{{ $variant['id'] }}"
|
data-option="{{ $optionHandle }}"
|
||||||
style="background-color: {{ $bg }};"
|
data-value-id="{{ $value['id'] }}"
|
||||||
aria-label="{{ $label }}"
|
style="background-color: {{ $value['hex'] ?? '#cccccc' }};"
|
||||||
|
aria-label="{{ $value['label'] }}"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
title="{{ $label }}"
|
|
||||||
></button>
|
></button>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
{{-- $variants: array of Modules\Core\Catalog\Services\ProductIndexer's mapVariant()
|
{{-- $values: one product option's de-duplicated, ordered values, as built by
|
||||||
shape (id, options: [{option, value, meta}], ...) — plain arrays, not Eloquent
|
ProductController::buildOptionPicker() — [{id, label, hex}]. Use this
|
||||||
models, since this is fed from Modules\Core\Catalog\Services\ProductService.
|
instead of <x-ui.color-swatch> for options that aren't a color (no hex),
|
||||||
Use this instead of <x-ui.color-swatch> for options that aren't a color
|
where a swatch dot has nothing meaningful to show. --}}
|
||||||
(no meta.hex), where a swatch dot has nothing meaningful to show. --}}
|
@props(['values', 'option' => null, 'optionHandle' => null])
|
||||||
@props(['variants', 'option' => null])
|
|
||||||
|
|
||||||
<div {{ $attributes }}>
|
<div {{ $attributes }}>
|
||||||
@if($option)
|
@if($option)
|
||||||
@@ -17,20 +16,17 @@ class="flex flex-wrap gap-2"
|
|||||||
role="group"
|
role="group"
|
||||||
aria-label="{{ $option ?? 'Option' }}"
|
aria-label="{{ $option ?? 'Option' }}"
|
||||||
>
|
>
|
||||||
@foreach($variants as $variant)
|
@foreach($values as $value)
|
||||||
@php
|
|
||||||
$value = $variant['options'][0] ?? null;
|
|
||||||
$label = $value['value'] ?? '';
|
|
||||||
@endphp
|
|
||||||
<x-ui.button
|
<x-ui.button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
size="sm"
|
size="sm"
|
||||||
data-product-form-target="swatch"
|
data-product-form-target="swatch"
|
||||||
data-action="click->product-form#selectVariant"
|
data-action="click->product-form#selectVariant"
|
||||||
data-variant-id="{{ $variant['id'] }}"
|
data-option="{{ $optionHandle }}"
|
||||||
aria-label="{{ $label }}"
|
data-value-id="{{ $value['id'] }}"
|
||||||
|
aria-label="{{ $value['label'] }}"
|
||||||
aria-pressed="false"
|
aria-pressed="false"
|
||||||
>{{ $label }}</x-ui.button>
|
>{{ $value['label'] }}</x-ui.button>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -60,9 +60,16 @@ class="w-full h-full object-cover"
|
|||||||
@endif
|
@endif
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<x-ui.button size="md" position="absolute" class="opacity-0 group-hover:opacity-100 transition-opacity duration-100">
|
@if ($product['variantId'] ?? null)
|
||||||
|
<x-checkout::add-to-cart
|
||||||
|
:purchasable="$product['variantId']"
|
||||||
|
class="absolute opacity-0 group-hover:opacity-100 has-[.bbk-add-to-cart-error:not([hidden])]:opacity-100 transition-opacity duration-100"
|
||||||
|
>
|
||||||
|
<x-ui.button type="submit" size="md">
|
||||||
{{ __('storefront.product.add_to_cart') }}
|
{{ __('storefront.product.add_to_cart') }}
|
||||||
</x-ui.button>
|
</x-ui.button>
|
||||||
|
</x-checkout::add-to-cart>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex items-baseline justify-between gap-4 absolute bottom-0 left-0 w-full">
|
<div class="flex items-baseline justify-between gap-4 absolute bottom-0 left-0 w-full">
|
||||||
|
|||||||
@@ -169,13 +169,13 @@ class="absolute bottom-6 right-8 text-white text-sm"
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if($option && !empty($product['variants']))
|
@foreach($productOptions as $productOption)
|
||||||
@if($optionIsColor)
|
@if($productOption['isColor'])
|
||||||
<x-ui.color-swatch :variants="$product['variants']" :option="$option" />
|
<x-ui.color-swatch :values="$productOption['values']" :option="$productOption['label']" :option-handle="$productOption['handle']" />
|
||||||
@else
|
@else
|
||||||
<x-ui.option-buttons :variants="$product['variants']" :option="$option" />
|
<x-ui.option-buttons :values="$productOption['values']" :option="$productOption['label']" :option-handle="$productOption['handle']" />
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
|
@endforeach
|
||||||
|
|
||||||
<x-checkout::add-to-cart
|
<x-checkout::add-to-cart
|
||||||
:purchasable="$variantsData[0]['id'] ?? null"
|
:purchasable="$variantsData[0]['id'] ?? null"
|
||||||
|
|||||||
@@ -1,236 +0,0 @@
|
|||||||
@extends('layouts.app')
|
|
||||||
|
|
||||||
@section('title', $product->translateAttribute('name') . ' — ' . config('app.name'))
|
|
||||||
@section('description', $product->translateAttribute('description'))
|
|
||||||
|
|
||||||
@section('content')
|
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-8 py-12">
|
|
||||||
|
|
||||||
@php $collection = $product->collections->first(); @endphp
|
|
||||||
|
|
||||||
<x-breadcrumb class="mb-14 justify-end" :items="[
|
|
||||||
$collection
|
|
||||||
? ['label' => $collection->translateAttribute('name'), 'href' => '/category/' . $collection->id]
|
|
||||||
: ['label' => 'Products', 'href' => '/products'],
|
|
||||||
['label' => $product->translateAttribute('name')],
|
|
||||||
]" />
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="grid grid-cols-1 md:grid-cols-2 gap-12"
|
|
||||||
data-controller="product-form"
|
|
||||||
data-product-form-variants-value="{{ json_encode($variantsData) }}"
|
|
||||||
>
|
|
||||||
|
|
||||||
{{-- Image --}}
|
|
||||||
<div
|
|
||||||
data-controller="product-gallery"
|
|
||||||
class="flex gap-4 items-start"
|
|
||||||
>
|
|
||||||
@if($product->media->isNotEmpty())
|
|
||||||
{{-- Thumbnails --}}
|
|
||||||
<div class="flex flex-col items-center gap-1 w-[116px] shrink-0 -mt-12.5">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-action="click->product-gallery#scrollUp"
|
|
||||||
class="gallery-arrow w-full flex items-center justify-center py-2"
|
|
||||||
aria-label="Scroll thumbnails up"
|
|
||||||
><x-ui.icon name="arrow-up" :size="30" /></button>
|
|
||||||
|
|
||||||
<div
|
|
||||||
data-product-gallery-target="track"
|
|
||||||
class="flex flex-col gap-4 overflow-hidden"
|
|
||||||
style="max-height: var(--gallery-height, 600px)"
|
|
||||||
>
|
|
||||||
@foreach($product->media as $i => $media)
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-action="click->product-gallery#select"
|
|
||||||
data-product-gallery-target="thumb"
|
|
||||||
data-src="{{ $media->getUrl() }}"
|
|
||||||
data-alt="{{ $product->translateAttribute('name') }}"
|
|
||||||
class="block w-full shrink-0 border border-black overflow-hidden "
|
|
||||||
aria-label="View image {{ $i + 1 }}"
|
|
||||||
aria-pressed="{{ $i === 0 ? 'true' : 'false' }}"
|
|
||||||
>
|
|
||||||
|
|
||||||
<!-- opacity-50 transition-opacity {{ $i === 0 ? 'opacity-100' : '' }}" -->
|
|
||||||
<img src="{{ $media->getUrl() }}" alt="" class="w-full h-auto object-cover" aria-hidden="true">
|
|
||||||
</button>
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-action="click->product-gallery#scrollDown"
|
|
||||||
class="gallery-arrow w-full flex items-center justify-center py-2"
|
|
||||||
aria-label="Scroll thumbnails down"
|
|
||||||
><x-ui.icon name="arrow-down" :size="30" /></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Main image --}}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex-1 border border-black bg-white cursor-zoom-in block p-0"
|
|
||||||
data-action="click->product-gallery#openLightbox"
|
|
||||||
aria-label="View full size image"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
data-product-form-target="image"
|
|
||||||
data-product-gallery-target="main"
|
|
||||||
src="{{ $product->media->first()->getUrl() }}"
|
|
||||||
alt="{{ $product->translateAttribute('name') }}"
|
|
||||||
class="w-full h-auto block"
|
|
||||||
>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
{{-- Lightbox --}}
|
|
||||||
<div
|
|
||||||
id="product-lightbox"
|
|
||||||
popover
|
|
||||||
class="product-lightbox"
|
|
||||||
data-product-gallery-target="lightbox"
|
|
||||||
data-action="click->product-gallery#closeLightboxOnBackdrop"
|
|
||||||
role="dialog"
|
|
||||||
aria-label="Product image gallery"
|
|
||||||
aria-modal="true"
|
|
||||||
>
|
|
||||||
{{-- Prev --}}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-action="click->product-gallery#prevImage click->product-gallery#noop"
|
|
||||||
class="lightbox-arrow absolute left-8 top-1/2 -translate-y-1/2"
|
|
||||||
aria-label="Previous image"
|
|
||||||
><x-ui.icon name="arrow-left" :size="72" color="white" /></button>
|
|
||||||
|
|
||||||
{{-- Image + close button as a unit --}}
|
|
||||||
<div class="relative" data-action="click->product-gallery#noop">
|
|
||||||
<img
|
|
||||||
data-product-gallery-target="lightboxImage"
|
|
||||||
src=""
|
|
||||||
alt=""
|
|
||||||
class="max-h-[90vh] max-w-[80vw] object-contain block"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
popovertarget="product-lightbox"
|
|
||||||
popovertargetaction="hide"
|
|
||||||
class="lightbox-close absolute -top-10 -right-10"
|
|
||||||
aria-label="Close gallery"
|
|
||||||
><x-ui.icon name="close" :size="36" color="white" /></button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Next --}}
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
data-action="click->product-gallery#nextImage click->product-gallery#noop"
|
|
||||||
class="lightbox-arrow absolute right-8 top-1/2 -translate-y-1/2"
|
|
||||||
aria-label="Next image"
|
|
||||||
><x-ui.icon name="arrow-right" :size="72" color="white" /></button>
|
|
||||||
|
|
||||||
{{-- Counter --}}
|
|
||||||
<p
|
|
||||||
data-product-gallery-target="lightboxCounter"
|
|
||||||
class="absolute bottom-6 right-8 text-white text-sm"
|
|
||||||
aria-live="polite"
|
|
||||||
></p>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div class="w-full bg-neutral-300 flex items-center justify-center text-neutral-500 min-h-64">
|
|
||||||
No image
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{{-- Info --}}
|
|
||||||
<div class="flex flex-col gap-6">
|
|
||||||
|
|
||||||
<h1 class="font-display font-medium text-4xl lg:text-[54px]">
|
|
||||||
{{ $product->translateAttribute('name') }}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<x-reviews-stars :rating="3" :count="24" :showCount="true" />
|
|
||||||
|
|
||||||
@if($product->variants->first()?->prices->isNotEmpty())
|
|
||||||
<p class="text-2xl font-bold" data-product-form-target="price">
|
|
||||||
<x-ui.price :amount="$product->variants->first()->prices->first()->price->decimal" />
|
|
||||||
</p>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@php
|
|
||||||
$desc = strip_tags($product->translateAttribute('description') ?? '');
|
|
||||||
$descTruncated = Str::limit($desc, 137);
|
|
||||||
$descNeedsMore = mb_strlen($desc) > mb_strlen(rtrim($descTruncated, '.'));
|
|
||||||
@endphp
|
|
||||||
<div class="text-base leading-relaxed">
|
|
||||||
{{ $descTruncated }}
|
|
||||||
@if($descNeedsMore)
|
|
||||||
<a href="#tab-panel-description" class="underline-slide font-semibold whitespace-nowrap">Περισσότερα</a>
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if($option && $product->variants->count() >= 1)
|
|
||||||
<x-ui.color-swatch :variants="$product->variants" :option="$option" />
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<div class="flex items-stretch gap-10">
|
|
||||||
<x-ui.quantity name="quantity" />
|
|
||||||
<x-ui.button class="flex-1">Προσθήκη στο καλάθι</x-ui.button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<x-ui.tabs class="mt-16" size="lg" :tabs="[
|
|
||||||
['id' => 'description', 'label' => 'Περιγραφή'],
|
|
||||||
// ['id' => 'reviews', 'label' => 'Αξιολογήσεις (' . count($reviews) . ')'],
|
|
||||||
['id' => 'reviews', 'label' => 'Αξιολογήσεις (3)'],
|
|
||||||
]">
|
|
||||||
<x-slot name="description">
|
|
||||||
<div class="leading-7 [&_p]:mt-4">
|
|
||||||
{!! $product->translateAttribute('description') !!}
|
|
||||||
</div>
|
|
||||||
@if($product->translateAttribute('details'))
|
|
||||||
<div class="mt-6">{!! $product->translateAttribute('details') !!}</div>
|
|
||||||
@endif
|
|
||||||
<ul class="mt-8 flex flex-col gap-3 text-neutral-600 list-disc list-outside pl-5">
|
|
||||||
<li>Όλα τα προϊόντα εκτυπώνονται και προετοιμάζονται κατά παραγγελία. Ο χρόνος προετοιμασίας κυμαίνεται μεταξύ 2 και 7 εργάσιμων ημερών.</li>
|
|
||||||
<li>Όλα τα προϊόντα κατασκευάζονται με τρισδιάστατη εκτύπωση σε ειδικούς εκτυπωτές πλαστικού υλικού. Πιθανώς να έχουν εμφανείς γραμμές ένωσης, στρώσεις εκτύπωσης υλικού και μικρές ατέλειες. Είναι φυσιολογικό για το αποτέλεσμα αυτής της δημιουργικής διαδικασίας.</li>
|
|
||||||
<li>Όλα τα προϊόντα είναι σχεδιασμένα και κατασκευασμένα είτε για διακόσμηση είτε για χρήση σε λογικά, ρεαλιστικά πλαίσια. Υπερβολική ισχύς, υψηλότατες θερμοκρασίες και αποσυναρμολόγηση μπορεί να προκαλέσουν ζημιά στο προϊόν για την οποία δεν ευθύνεται το 3Dealer.</li>
|
|
||||||
</ul>
|
|
||||||
</x-slot>
|
|
||||||
<x-slot name="reviews">
|
|
||||||
{{-- @if(count($reviews) > 0)
|
|
||||||
<div class="mb-10">
|
|
||||||
@foreach($reviews as $review)
|
|
||||||
<x-review-card :review="$review" />
|
|
||||||
@endforeach
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<p class="text-neutral-500 mb-10">Δεν υπάρχουν αξιολογήσεις ακόμα.</p>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
<h3 class="text-h4 font-bold">
|
|
||||||
{{ count($reviews) > 0 ? 'Πρόσθεσε μια' : 'Γράψε την πρώτη' }} αξιολόγηση για το «{{ $product->translateAttribute('name') }}»
|
|
||||||
</h3> --}}
|
|
||||||
|
|
||||||
<x-review-form :product="$product" />
|
|
||||||
</x-slot>
|
|
||||||
</x-ui.tabs>
|
|
||||||
|
|
||||||
<x-product-grid
|
|
||||||
class="mt-20"
|
|
||||||
title="Σχετικά προϊόντα"
|
|
||||||
:products="[
|
|
||||||
['name' => 'Camper', 'price' => '30', 'image' => null, 'href' => '#'],
|
|
||||||
['name' => 'Ponderer IPA','price' => '25', 'image' => null, 'href' => '#'],
|
|
||||||
['name' => 'Squish Red', 'price' => '35', 'image' => null, 'href' => '#'],
|
|
||||||
['name' => 'Red Light', 'price' => '25', 'image' => null, 'href' => '#'],
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<x-newsletter class="py-20" />
|
|
||||||
|
|
||||||
@endsection
|
|
||||||
Reference in New Issue
Block a user