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;
|
||||
|
||||
$variantsData = $this->products->variantSummaries($product);
|
||||
|
||||
$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));
|
||||
[$productOptions, $variantsData] = $this->buildOptionPicker($id);
|
||||
|
||||
return view('product.show', [
|
||||
'collection' => $collection,
|
||||
'product' => $product,
|
||||
'option' => $option,
|
||||
'optionIsColor' => $optionIsColor,
|
||||
'productOptions' => $productOptions,
|
||||
'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
|
||||
* product page's "Add to cart" calls this first and only submits to the
|
||||
|
||||
Generated
+94
-240
@@ -515,11 +515,11 @@
|
||||
},
|
||||
{
|
||||
"name": "boboko/core",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://code.radical-elements.com/boboko/core.git",
|
||||
"reference": "2cc6f5e5f0d43102001d65b60810423a35e83e1f"
|
||||
"reference": "0437057e5d604e3c05479089071fcbe79ad69bb7"
|
||||
},
|
||||
"require": {
|
||||
"laravel/framework": "^12.0",
|
||||
@@ -558,7 +558,8 @@
|
||||
"Modules\\Core\\Providers\\CartServiceProvider",
|
||||
"Modules\\Core\\Providers\\ReviewServiceProvider",
|
||||
"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",
|
||||
"time": "2026-09-15T21:06:04+00:00"
|
||||
"time": "2026-09-17T22:30:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -1062,16 +1063,16 @@
|
||||
},
|
||||
{
|
||||
"name": "danharrin/livewire-rate-limiting",
|
||||
"version": "v2.2.1",
|
||||
"version": "v2.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/danharrin/livewire-rate-limiting.git",
|
||||
"reference": "69436717dc70e30f80d7f8fd02504c22992a9ad5"
|
||||
"reference": "46c4ceaf7997c713c79afe7c507a39649f6c9d6f"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/69436717dc70e30f80d7f8fd02504c22992a9ad5",
|
||||
"reference": "69436717dc70e30f80d7f8fd02504c22992a9ad5",
|
||||
"url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/46c4ceaf7997c713c79afe7c507a39649f6c9d6f",
|
||||
"reference": "46c4ceaf7997c713c79afe7c507a39649f6c9d6f",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1112,7 +1113,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-08-06T08:41:51+00:00"
|
||||
"time": "2026-09-18T07:38:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dflydev/dot-access-data",
|
||||
@@ -1329,27 +1330,25 @@
|
||||
},
|
||||
{
|
||||
"name": "doctrine/lexer",
|
||||
"version": "3.0.1",
|
||||
"version": "3.0.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/lexer.git",
|
||||
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd"
|
||||
"reference": "e96fe45e92a54233726014a7cc7340abf29bb14c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
|
||||
"reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd",
|
||||
"url": "https://api.github.com/repos/doctrine/lexer/zipball/e96fe45e92a54233726014a7cc7340abf29bb14c",
|
||||
"reference": "e96fe45e92a54233726014a7cc7340abf29bb14c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.1"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"phpunit/phpunit": "^10.5",
|
||||
"psalm/plugin-phpunit": "^0.18.3",
|
||||
"vimeo/psalm": "^5.21"
|
||||
"doctrine/coding-standard": "^14",
|
||||
"phpstan/phpstan": "^2",
|
||||
"phpunit/phpunit": "^10.5.58 || ^12.5.4"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
@@ -1386,7 +1385,7 @@
|
||||
],
|
||||
"support": {
|
||||
"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": [
|
||||
{
|
||||
@@ -1402,7 +1401,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-02-05T11:56:58+00:00"
|
||||
"time": "2026-06-14T20:44:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/dompdf",
|
||||
@@ -1692,7 +1691,7 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/actions",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/actions.git",
|
||||
@@ -1741,16 +1740,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/filament",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/panels.git",
|
||||
"reference": "ff848c7750b49f0bb532d44c6768f0e601fb09d2"
|
||||
"reference": "1215d7d79b5609f66c1e432933610eefa0ab3ad6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/ff848c7750b49f0bb532d44c6768f0e601fb09d2",
|
||||
"reference": "ff848c7750b49f0bb532d44c6768f0e601fb09d2",
|
||||
"url": "https://api.github.com/repos/filamentphp/panels/zipball/1215d7d79b5609f66c1e432933610eefa0ab3ad6",
|
||||
"reference": "1215d7d79b5609f66c1e432933610eefa0ab3ad6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1795,20 +1794,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"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",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/forms.git",
|
||||
"reference": "18e3382115ed3062a074736c9e80cfcb3ffe2221"
|
||||
"reference": "10d7ba430544ae706a406713105ba37b18f52008"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/18e3382115ed3062a074736c9e80cfcb3ffe2221",
|
||||
"reference": "18e3382115ed3062a074736c9e80cfcb3ffe2221",
|
||||
"url": "https://api.github.com/repos/filamentphp/forms/zipball/10d7ba430544ae706a406713105ba37b18f52008",
|
||||
"reference": "10d7ba430544ae706a406713105ba37b18f52008",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1845,20 +1844,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"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",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/infolists.git",
|
||||
"reference": "b05093c8ff726d28eb75df29fa21bdd836386ded"
|
||||
"reference": "1979591422ff823543622b87e0276cdca3f863e8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/b05093c8ff726d28eb75df29fa21bdd836386ded",
|
||||
"reference": "b05093c8ff726d28eb75df29fa21bdd836386ded",
|
||||
"url": "https://api.github.com/repos/filamentphp/infolists/zipball/1979591422ff823543622b87e0276cdca3f863e8",
|
||||
"reference": "1979591422ff823543622b87e0276cdca3f863e8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1890,20 +1889,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"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",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/notifications.git",
|
||||
"reference": "f481bcfb705ad650f96336e7496814419907639c"
|
||||
"reference": "40989f93ec3dc36f5582024ca41f99333a4c0ea7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/notifications/zipball/f481bcfb705ad650f96336e7496814419907639c",
|
||||
"reference": "f481bcfb705ad650f96336e7496814419907639c",
|
||||
"url": "https://api.github.com/repos/filamentphp/notifications/zipball/40989f93ec3dc36f5582024ca41f99333a4c0ea7",
|
||||
"reference": "40989f93ec3dc36f5582024ca41f99333a4c0ea7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1937,20 +1936,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"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",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/query-builder.git",
|
||||
"reference": "f7c1eb9eeaded573838c1f3b8d0a63f55434331a"
|
||||
"reference": "0428645f4cd0a232b6bc446437cbfafa366e56c0"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/query-builder/zipball/f7c1eb9eeaded573838c1f3b8d0a63f55434331a",
|
||||
"reference": "f7c1eb9eeaded573838c1f3b8d0a63f55434331a",
|
||||
"url": "https://api.github.com/repos/filamentphp/query-builder/zipball/0428645f4cd0a232b6bc446437cbfafa366e56c0",
|
||||
"reference": "0428645f4cd0a232b6bc446437cbfafa366e56c0",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -1983,20 +1982,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"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",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/schemas.git",
|
||||
"reference": "fec2747e8cf1e6ca76d6c883c7f1019da994f771"
|
||||
"reference": "dc4eeb57671b104b9e60f6d3f435f803c705e6df"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/schemas/zipball/fec2747e8cf1e6ca76d6c883c7f1019da994f771",
|
||||
"reference": "fec2747e8cf1e6ca76d6c883c7f1019da994f771",
|
||||
"url": "https://api.github.com/repos/filamentphp/schemas/zipball/dc4eeb57671b104b9e60f6d3f435f803c705e6df",
|
||||
"reference": "dc4eeb57671b104b9e60f6d3f435f803c705e6df",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2028,11 +2027,11 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"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",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/spatie-laravel-media-library-plugin.git",
|
||||
@@ -2069,16 +2068,16 @@
|
||||
},
|
||||
{
|
||||
"name": "filament/support",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/support.git",
|
||||
"reference": "6ff50570ff1f2c6c1caf6ed1e7a2ce146b901106"
|
||||
"reference": "6bd7bdb5f5ff3b1a70cac6dabe28f5d996a36bcc"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/6ff50570ff1f2c6c1caf6ed1e7a2ce146b901106",
|
||||
"reference": "6ff50570ff1f2c6c1caf6ed1e7a2ce146b901106",
|
||||
"url": "https://api.github.com/repos/filamentphp/support/zipball/6bd7bdb5f5ff3b1a70cac6dabe28f5d996a36bcc",
|
||||
"reference": "6bd7bdb5f5ff3b1a70cac6dabe28f5d996a36bcc",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2124,20 +2123,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"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",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/tables.git",
|
||||
"reference": "51f2f2d19eddae424b8b4a08ab9a14770e2acc9d"
|
||||
"reference": "8a0c6634d752e0b71ed0871f92d533c0d03d7601"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/51f2f2d19eddae424b8b4a08ab9a14770e2acc9d",
|
||||
"reference": "51f2f2d19eddae424b8b4a08ab9a14770e2acc9d",
|
||||
"url": "https://api.github.com/repos/filamentphp/tables/zipball/8a0c6634d752e0b71ed0871f92d533c0d03d7601",
|
||||
"reference": "8a0c6634d752e0b71ed0871f92d533c0d03d7601",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2170,20 +2169,20 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"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",
|
||||
"version": "v4.13.2",
|
||||
"version": "v4.13.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/filamentphp/widgets.git",
|
||||
"reference": "3d5a4dde469ad9f8e6cb2d5928e4882f6a92afe1"
|
||||
"reference": "5ec044730102c3444d019232d8edc3d8922876c6"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/filamentphp/widgets/zipball/3d5a4dde469ad9f8e6cb2d5928e4882f6a92afe1",
|
||||
"reference": "3d5a4dde469ad9f8e6cb2d5928e4882f6a92afe1",
|
||||
"url": "https://api.github.com/repos/filamentphp/widgets/zipball/5ec044730102c3444d019232d8edc3d8922876c6",
|
||||
"reference": "5ec044730102c3444d019232d8edc3d8922876c6",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2214,7 +2213,7 @@
|
||||
"issues": "https://github.com/filamentphp/filament/issues",
|
||||
"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",
|
||||
@@ -7844,20 +7843,20 @@
|
||||
},
|
||||
{
|
||||
"name": "ramsey/uuid",
|
||||
"version": "4.9.3",
|
||||
"version": "4.9.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ramsey/uuid.git",
|
||||
"reference": "1df15849d00943a67d677dc9cfd80795f038c9f8"
|
||||
"reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/1df15849d00943a67d677dc9cfd80795f038c9f8",
|
||||
"reference": "1df15849d00943a67d677dc9cfd80795f038c9f8",
|
||||
"url": "https://api.github.com/repos/ramsey/uuid/zipball/75d73f48d02797c2c285a7e9f348fadc0102ffe2",
|
||||
"reference": "75d73f48d02797c2c285a7e9f348fadc0102ffe2",
|
||||
"shasum": ""
|
||||
},
|
||||
"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",
|
||||
"ramsey/collection": "^1.2 || ^2.0"
|
||||
},
|
||||
@@ -7916,9 +7915,9 @@
|
||||
],
|
||||
"support": {
|
||||
"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",
|
||||
@@ -8000,35 +7999,33 @@
|
||||
},
|
||||
{
|
||||
"name": "sabberworm/php-css-parser",
|
||||
"version": "v9.4.0",
|
||||
"version": "v9.5.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/MyIntervals/PHP-CSS-Parser.git",
|
||||
"reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f"
|
||||
"reference": "f284e63b6e891e0c28631e54ba06c3ed102a9ef3"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
|
||||
"reference": "fd3bf9fb173e0df649bc4e3e0d088a1b2417c08f",
|
||||
"url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/f284e63b6e891e0c28631e54ba06c3ed102a9ef3",
|
||||
"reference": "f284e63b6e891e0c28631e54ba06c3ed102a9ef3",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0",
|
||||
"thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4"
|
||||
"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"
|
||||
},
|
||||
"require-dev": {
|
||||
"php-parallel-lint/php-parallel-lint": "1.4.0",
|
||||
"phpstan/extension-installer": "1.4.3",
|
||||
"phpstan/phpstan": "1.12.33 || 2.2.2",
|
||||
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.16",
|
||||
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.11",
|
||||
"phpunit/phpunit": "8.5.52",
|
||||
"phpstan/phpstan": "1.12.33 || 2.2.9",
|
||||
"phpstan/phpstan-phpunit": "1.4.2 || 2.0.18",
|
||||
"phpstan/phpstan-strict-rules": "1.6.2 || 2.0.12",
|
||||
"phpunit/phpunit": "8.5.54",
|
||||
"rawr/phpunit-data-provider": "3.3.1",
|
||||
"rector/rector": "1.2.10 || 2.4.6",
|
||||
"rector/type-perfect": "1.0.0 || 2.1.3",
|
||||
"squizlabs/php_codesniffer": "4.0.1",
|
||||
"thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.3"
|
||||
"rector/rector": "1.2.10 || 2.6.2",
|
||||
"rector/type-perfect": "1.0.0 || 2.1.4",
|
||||
"squizlabs/php_codesniffer": "4.0.4"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-mbstring": "for parsing UTF-8 CSS"
|
||||
@@ -8036,7 +8033,7 @@
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "9.5.x-dev"
|
||||
"dev-main": "9.6.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
@@ -8074,9 +8071,9 @@
|
||||
],
|
||||
"support": {
|
||||
"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",
|
||||
@@ -12603,16 +12600,16 @@
|
||||
},
|
||||
{
|
||||
"name": "technikermathe/blade-lucide-icons",
|
||||
"version": "v3.180.0",
|
||||
"version": "v3.181.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/PascaleBeier/blade-lucide-icons.git",
|
||||
"reference": "adac9da3a65910fedc271129eea5340093910203"
|
||||
"reference": "64ccac4ecfe1b833e9a5cf1ebe7216acbe3cac98"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/PascaleBeier/blade-lucide-icons/zipball/adac9da3a65910fedc271129eea5340093910203",
|
||||
"reference": "adac9da3a65910fedc271129eea5340093910203",
|
||||
"url": "https://api.github.com/repos/PascaleBeier/blade-lucide-icons/zipball/64ccac4ecfe1b833e9a5cf1ebe7216acbe3cac98",
|
||||
"reference": "64ccac4ecfe1b833e9a5cf1ebe7216acbe3cac98",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -12662,152 +12659,9 @@
|
||||
],
|
||||
"support": {
|
||||
"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"
|
||||
},
|
||||
{
|
||||
"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"
|
||||
"time": "2026-09-18T02:09:04+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tijsverkoyen/css-to-inline-styles",
|
||||
|
||||
Regular → Executable
Regular → Executable
@@ -16,13 +16,17 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
connect() {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const urlId = parseInt(params.get('variant'))
|
||||
const defaultId = this.variantsValue[0]?.id
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
const urlId = parseInt(params.get('variant'))
|
||||
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)
|
||||
? urlId
|
||||
: defaultId
|
||||
// A product can have several independent options (e.g. size + style
|
||||
// + person-count) — this tracks the currently-picked value id per
|
||||
// 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
|
||||
// of the checkout module's add-to-cart <form>) — runs BEFORE that
|
||||
@@ -108,11 +112,24 @@ export default class extends Controller {
|
||||
}
|
||||
|
||||
selectVariant(event) {
|
||||
const id = parseInt(event.currentTarget.dataset.variantId)
|
||||
this.selectedValue = id
|
||||
const option = event.currentTarget.dataset.option
|
||||
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)
|
||||
url.searchParams.set('variant', id)
|
||||
url.searchParams.set('variant', match.id)
|
||||
window.history.pushState({}, '', url)
|
||||
}
|
||||
|
||||
@@ -140,12 +157,16 @@ export default class extends Controller {
|
||||
if (purchasableInput) purchasableInput.value = id
|
||||
|
||||
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.setAttribute('aria-pressed', String(isSelected))
|
||||
|
||||
if (isSelected && this.hasColorNameTarget) {
|
||||
this.colorNameTarget.textContent = swatch.getAttribute('aria-label')
|
||||
if (isSelected) {
|
||||
// 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()
|
||||
shape (id, options: [{option, value, meta}], ...) — plain arrays, not Eloquent
|
||||
models, since this is fed from Modules\Core\Catalog\Services\ProductService. --}}
|
||||
@props(['variants', 'option' => null])
|
||||
{{-- $values: one product option's de-duplicated, ordered values, as built by
|
||||
ProductController::buildOptionPicker() — [{id, label, hex}]. --}}
|
||||
@props(['values', 'option' => null, 'optionHandle' => null])
|
||||
|
||||
<div {{ $attributes }}>
|
||||
@if($option)
|
||||
<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>
|
||||
@endif
|
||||
|
||||
@@ -15,22 +14,17 @@ class="flex flex-wrap gap-2"
|
||||
role="group"
|
||||
aria-label="{{ $option ?? 'Color' }}"
|
||||
>
|
||||
@foreach($variants as $variant)
|
||||
@php
|
||||
$value = $variant['options'][0] ?? null;
|
||||
$label = $value['value'] ?? '';
|
||||
$bg = $value['meta']['hex'] ?? '#cccccc';
|
||||
@endphp
|
||||
@foreach($values as $value)
|
||||
<button
|
||||
type="button"
|
||||
class="color-swatch"
|
||||
data-product-form-target="swatch"
|
||||
data-action="click->product-form#selectVariant"
|
||||
data-variant-id="{{ $variant['id'] }}"
|
||||
style="background-color: {{ $bg }};"
|
||||
aria-label="{{ $label }}"
|
||||
data-option="{{ $optionHandle }}"
|
||||
data-value-id="{{ $value['id'] }}"
|
||||
style="background-color: {{ $value['hex'] ?? '#cccccc' }};"
|
||||
aria-label="{{ $value['label'] }}"
|
||||
aria-pressed="false"
|
||||
title="{{ $label }}"
|
||||
></button>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
{{-- $variants: array of Modules\Core\Catalog\Services\ProductIndexer's mapVariant()
|
||||
shape (id, options: [{option, value, meta}], ...) — plain arrays, not Eloquent
|
||||
models, since this is fed from Modules\Core\Catalog\Services\ProductService.
|
||||
Use this instead of <x-ui.color-swatch> for options that aren't a color
|
||||
(no meta.hex), where a swatch dot has nothing meaningful to show. --}}
|
||||
@props(['variants', 'option' => null])
|
||||
{{-- $values: one product option's de-duplicated, ordered values, as built by
|
||||
ProductController::buildOptionPicker() — [{id, label, hex}]. Use this
|
||||
instead of <x-ui.color-swatch> for options that aren't a color (no hex),
|
||||
where a swatch dot has nothing meaningful to show. --}}
|
||||
@props(['values', 'option' => null, 'optionHandle' => null])
|
||||
|
||||
<div {{ $attributes }}>
|
||||
@if($option)
|
||||
@@ -17,20 +16,17 @@ class="flex flex-wrap gap-2"
|
||||
role="group"
|
||||
aria-label="{{ $option ?? 'Option' }}"
|
||||
>
|
||||
@foreach($variants as $variant)
|
||||
@php
|
||||
$value = $variant['options'][0] ?? null;
|
||||
$label = $value['value'] ?? '';
|
||||
@endphp
|
||||
@foreach($values as $value)
|
||||
<x-ui.button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
data-product-form-target="swatch"
|
||||
data-action="click->product-form#selectVariant"
|
||||
data-variant-id="{{ $variant['id'] }}"
|
||||
aria-label="{{ $label }}"
|
||||
data-option="{{ $optionHandle }}"
|
||||
data-value-id="{{ $value['id'] }}"
|
||||
aria-label="{{ $value['label'] }}"
|
||||
aria-pressed="false"
|
||||
>{{ $label }}</x-ui.button>
|
||||
>{{ $value['label'] }}</x-ui.button>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,9 +60,16 @@ class="w-full h-full object-cover"
|
||||
@endif
|
||||
</a>
|
||||
|
||||
<x-ui.button size="md" position="absolute" class="opacity-0 group-hover:opacity-100 transition-opacity duration-100">
|
||||
{{ __('storefront.product.add_to_cart') }}
|
||||
</x-ui.button>
|
||||
@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') }}
|
||||
</x-ui.button>
|
||||
</x-checkout::add-to-cart>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<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
|
||||
</div>
|
||||
|
||||
@if($option && !empty($product['variants']))
|
||||
@if($optionIsColor)
|
||||
<x-ui.color-swatch :variants="$product['variants']" :option="$option" />
|
||||
@foreach($productOptions as $productOption)
|
||||
@if($productOption['isColor'])
|
||||
<x-ui.color-swatch :values="$productOption['values']" :option="$productOption['label']" :option-handle="$productOption['handle']" />
|
||||
@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
|
||||
@endforeach
|
||||
|
||||
<x-checkout::add-to-cart
|
||||
: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