generated from boboko/starter
Compare commits
2
Commits
86c645fb36
...
c7cd1138fe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7cd1138fe | ||
|
|
defe1dab12 |
@@ -2,27 +2,32 @@
|
|||||||
|
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Lunar\Models\Collection;
|
use Illuminate\Http\Response;
|
||||||
use Modules\Core\Product\DTOs\ProductFilters;
|
use Modules\Core\Catalog\DTOs\ProductFilters;
|
||||||
use Modules\Core\Product\Services\ProductService;
|
use Modules\Core\Catalog\Services\CollectionService;
|
||||||
|
use Modules\Core\Catalog\Services\ProductService;
|
||||||
|
|
||||||
class CategoryController extends Controller
|
class CategoryController extends Controller
|
||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly ProductService $products,
|
private readonly ProductService $products,
|
||||||
|
private readonly CollectionService $collections,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public function show(string $locale, Collection $collection)
|
public function show(string $locale, int $collection)
|
||||||
{
|
{
|
||||||
|
$collectionData = $this->collections->getById($collection);
|
||||||
|
abort_if($collectionData === null, Response::HTTP_NOT_FOUND);
|
||||||
|
|
||||||
$perPage = 12;
|
$perPage = 12;
|
||||||
$page = (int) request('page', 1);
|
$page = (int) request('page', 1);
|
||||||
|
|
||||||
// Listing/filtering reads from the Meilisearch index via ProductService,
|
// Listing/filtering reads from the Meilisearch index via ProductService,
|
||||||
// not Eloquent — see Modules\Core\Product\Services\ProductService. list() returns a
|
// not Eloquent — see Modules\Core\Catalog\Services\ProductService. list() returns a
|
||||||
// real LengthAwarePaginator of plain arrays (already localized/flattened),
|
// real LengthAwarePaginator of plain arrays (already localized/flattened),
|
||||||
// not Product models.
|
// not Product models.
|
||||||
$products = $this->products->list(
|
$products = $this->products->list(
|
||||||
filters: new ProductFilters(collectionId: $collection->id),
|
filters: new ProductFilters(collectionId: $collectionData['id']),
|
||||||
perPage: $perPage,
|
perPage: $perPage,
|
||||||
page: $page,
|
page: $page,
|
||||||
)->through(fn (array $product) => [
|
)->through(fn (array $product) => [
|
||||||
@@ -33,7 +38,7 @@ public function show(string $locale, Collection $collection)
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
return view('category.show', [
|
return view('category.show', [
|
||||||
'collection' => $collection,
|
'collection' => $collectionData,
|
||||||
'products' => $products,
|
'products' => $products,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@
|
|||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Lunar\Models\Collection;
|
use Modules\Core\Catalog\Services\ProductService;
|
||||||
use Modules\Core\Product\Services\ProductService;
|
|
||||||
|
|
||||||
class ProductController extends Controller
|
class ProductController extends Controller
|
||||||
{
|
{
|
||||||
@@ -13,11 +12,9 @@ public function __construct(private readonly ProductService $products) {}
|
|||||||
public function show(string $locale, int $id)
|
public function show(string $locale, int $id)
|
||||||
{
|
{
|
||||||
$product = $this->products->getById($id);
|
$product = $this->products->getById($id);
|
||||||
|
|
||||||
abort_if($product === null, Response::HTTP_NOT_FOUND);
|
abort_if($product === null, Response::HTTP_NOT_FOUND);
|
||||||
|
|
||||||
$collection = $product['collections'][0] ?? null;
|
$collection = $product['collections'][0] ?? null;
|
||||||
$collectionModel = $collection !== null ? Collection::find($collection) : null;
|
|
||||||
|
|
||||||
$variantsData = collect($product['variants'])
|
$variantsData = collect($product['variants'])
|
||||||
->map(fn (array $variant) => [
|
->map(fn (array $variant) => [
|
||||||
@@ -31,12 +28,8 @@ public function show(string $locale, int $id)
|
|||||||
$firstVariant = $product['variants'][0] ?? null;
|
$firstVariant = $product['variants'][0] ?? null;
|
||||||
$option = $firstVariant['options'][0]['option'] ?? null;
|
$option = $firstVariant['options'][0]['option'] ?? null;
|
||||||
|
|
||||||
// temp categories here
|
|
||||||
$categories = Collection::orderBy('_lft')->get();
|
|
||||||
|
|
||||||
return view('product.show', [
|
return view('product.show', [
|
||||||
'categories' => $categories,
|
'collection' => $collection,
|
||||||
'collection' => $collectionModel,
|
|
||||||
'product' => $product,
|
'product' => $product,
|
||||||
'option' => $option,
|
'option' => $option,
|
||||||
'variantsData' => $variantsData,
|
'variantsData' => $variantsData,
|
||||||
|
|||||||
@@ -6,9 +6,14 @@
|
|||||||
use App\Models\Staff;
|
use App\Models\Staff;
|
||||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||||
use Illuminate\Support\Facades\URL;
|
use Illuminate\Support\Facades\URL;
|
||||||
|
use Illuminate\Support\Facades\View;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\View\View as ViewInstance;
|
||||||
use Lunar\Facades\ModelManifest;
|
use Lunar\Facades\ModelManifest;
|
||||||
use Lunar\Facades\Telemetry;
|
use Lunar\Facades\Telemetry;
|
||||||
|
use Modules\Core\Catalog\DTOs\CollectionFilters;
|
||||||
|
use Modules\Core\Catalog\Enums\CollectionSort;
|
||||||
|
use Modules\Core\Catalog\Services\CollectionService;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -16,6 +21,17 @@ public function boot(): void
|
|||||||
{
|
{
|
||||||
Telemetry::optOut();
|
Telemetry::optOut();
|
||||||
|
|
||||||
|
// header.blade.php's category dropdown — root collections only, resolved
|
||||||
|
// per-request so the composer runs after `locale` middleware has already
|
||||||
|
// set App::getLocale(), which CollectionService's name resolution depends on.
|
||||||
|
View::composer('components.header', function (ViewInstance $view) {
|
||||||
|
$view->with('categories', app(CollectionService::class)->list(
|
||||||
|
filters: new CollectionFilters(rootOnly: true),
|
||||||
|
perPage: 100,
|
||||||
|
sort: CollectionSort::Position,
|
||||||
|
)->items());
|
||||||
|
});
|
||||||
|
|
||||||
if ($this->app->environment('production')) {
|
if ($this->app->environment('production')) {
|
||||||
URL::forceScheme('https');
|
URL::forceScheme('https');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,20 +16,4 @@
|
|||||||
|
|
||||||
'auto_create_customer_for_user' => true,
|
'auto_create_customer_for_user' => true,
|
||||||
|
|
||||||
/*
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
| Product Option Types
|
|
||||||
|--------------------------------------------------------------------------
|
|
||||||
|
|
|
||||||
| Enabled `Modules\Core\Product\Contracts\ProductOptionTypeInterface`
|
|
||||||
| implementations. An admin picks one per ProductOption from a dropdown
|
|
||||||
| on the option's own edit form — the selection is stored in
|
|
||||||
| ProductOption::meta, not tied to the option's handle.
|
|
||||||
|
|
|
||||||
*/
|
|
||||||
|
|
||||||
'product_option_types' => [
|
|
||||||
\Modules\Core\Product\OptionTypes\ColorOptionType::class,
|
|
||||||
],
|
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
*/
|
*/
|
||||||
'cart_lines' => [
|
'cart_lines' => [
|
||||||
Lunar\Pipelines\CartLine\GetUnitPrice::class,
|
Lunar\Pipelines\CartLine\GetUnitPrice::class,
|
||||||
|
Modules\Core\Cart\Pipelines\ZeroSavedForLaterPrice::class,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -44,5 +44,5 @@
|
|||||||
| Determines whether the cart sholud be soft deleted when the user logs out.
|
| Determines whether the cart sholud be soft deleted when the user logs out.
|
||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
'delete_on_forget' => true,
|
'delete_on_forget' => false,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -46,10 +46,10 @@
|
|||||||
|
|
||||||
'indexers' => [
|
'indexers' => [
|
||||||
Lunar\Models\Brand::class => Lunar\Search\BrandIndexer::class,
|
Lunar\Models\Brand::class => Lunar\Search\BrandIndexer::class,
|
||||||
Lunar\Models\Collection::class => Lunar\Search\CollectionIndexer::class,
|
Lunar\Models\Collection::class => Modules\Core\Catalog\Services\CollectionIndexer::class,
|
||||||
Lunar\Models\Customer::class => Lunar\Search\CustomerIndexer::class,
|
Lunar\Models\Customer::class => Lunar\Search\CustomerIndexer::class,
|
||||||
Lunar\Models\Order::class => Lunar\Search\OrderIndexer::class,
|
Lunar\Models\Order::class => Lunar\Search\OrderIndexer::class,
|
||||||
Lunar\Models\Product::class => Modules\Core\Product\Services\ProductIndexer::class,
|
Lunar\Models\Product::class => Modules\Core\Catalog\Services\ProductIndexer::class,
|
||||||
Lunar\Models\ProductOption::class => Lunar\Search\ProductOptionIndexer::class,
|
Lunar\Models\ProductOption::class => Lunar\Search\ProductOptionIndexer::class,
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
@extends('layouts.app')
|
@extends('layouts.app')
|
||||||
|
|
||||||
@section('title', $collection->translateAttribute('name') . ' — ' . config('app.name'))
|
@section('title', $collection['name'] . ' — ' . config('app.name'))
|
||||||
@section('description', strip_tags($collection->translateAttribute('description') ?? ''))
|
@section('description', strip_tags($collection['description'] ?? ''))
|
||||||
|
|
||||||
@section('content')
|
@section('content')
|
||||||
<div class="max-w-7xl mx-auto px-4 sm:px-8 py-12">
|
<div class="max-w-7xl mx-auto px-4 sm:px-8 py-12">
|
||||||
|
|
||||||
<div class="flex items-end justify-between gap-6 flex-wrap mb-10">
|
<div class="flex items-end justify-between gap-6 flex-wrap mb-10">
|
||||||
<h1 class="font-display font-extrabold text-h1">{{ $collection->translateAttribute('name') }}</h1>
|
<h1 class="font-display font-extrabold text-h1">{{ $collection['name'] }}</h1>
|
||||||
|
|
||||||
<x-breadcrumb :items="[
|
<x-breadcrumb :items="[
|
||||||
['label' => __('storefront.nav.home'), 'href' => route('home')],
|
['label' => __('storefront.nav.home'), 'href' => route('home')],
|
||||||
['label' => $collection->translateAttribute('name')],
|
['label' => $collection['name']],
|
||||||
]" />
|
]" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -18,11 +18,11 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<div class="nav-dropdown">
|
<div class="nav-dropdown">
|
||||||
{{-- @foreach($categories as $category)
|
@foreach($categories ?? [] as $category)
|
||||||
<a href="/category/{{ $category->id }}">
|
<a href="{{ route('category.show', ['id' => $category['id']]) }}">
|
||||||
{{ $category->translateAttribute('name') }}
|
{{ $category['name'] }}
|
||||||
</a>
|
</a>
|
||||||
@endforeach --}}
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{{-- $variants: array of Modules\Core\Product\Services\ProductIndexer's mapVariant()
|
{{-- $variants: array of Modules\Core\Catalog\Services\ProductIndexer's mapVariant()
|
||||||
shape (id, options: [{option, value, meta}], ...) — plain arrays, not Eloquent
|
shape (id, options: [{option, value, meta}], ...) — plain arrays, not Eloquent
|
||||||
models, since this is fed from Modules\Core\Product\Services\ProductService. --}}
|
models, since this is fed from Modules\Core\Catalog\Services\ProductService. --}}
|
||||||
@props(['variants', 'option' => null])
|
@props(['variants', 'option' => null])
|
||||||
|
|
||||||
<div {{ $attributes }}>
|
<div {{ $attributes }}>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<x-breadcrumb class="mb-14 justify-end" :items="[
|
<x-breadcrumb class="mb-14 justify-end" :items="[
|
||||||
$collection
|
$collection
|
||||||
? ['label' => $collection->translateAttribute('name'), 'href' => route('category.show', ['collection' => $collection])]
|
? ['label' => $collection['name'], 'href' => route('category.show', ['id' => $collection['id']])]
|
||||||
: ['label' => __('storefront.nav.products'), 'href' => '/products'],
|
: ['label' => __('storefront.nav.products'), 'href' => '/products'],
|
||||||
['label' => $product['name']],
|
['label' => $product['name']],
|
||||||
]" />
|
]" />
|
||||||
@@ -146,7 +146,7 @@ class="absolute bottom-6 right-8 text-white text-sm"
|
|||||||
{{ $product['name'] }}
|
{{ $product['name'] }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<x-reviews-stars :rating="$product['average_rating'] ?? 0" :count="$product['review_count']" :showCount="true" />
|
<x-reviews-stars :rating="$product['reviews']['average_rating'] ?? 0" :count="$product['reviews']['count']" :showCount="true" />
|
||||||
|
|
||||||
@if($product['price'] !== null)
|
@if($product['price'] !== null)
|
||||||
<p class="text-2xl font-bold" data-product-form-target="price">
|
<p class="text-2xl font-bold" data-product-form-target="price">
|
||||||
@@ -181,7 +181,7 @@ class="absolute bottom-6 right-8 text-white text-sm"
|
|||||||
|
|
||||||
<x-ui.tabs class="mt-16" size="lg" :tabs="[
|
<x-ui.tabs class="mt-16" size="lg" :tabs="[
|
||||||
['id' => 'description', 'label' => __('storefront.product.description')],
|
['id' => 'description', 'label' => __('storefront.product.description')],
|
||||||
['id' => 'reviews', 'label' => __('storefront.product.reviews') . ' (' . $product['review_count'] . ')'],
|
['id' => 'reviews', 'label' => __('storefront.product.reviews') . ' (' . $product['reviews']['count'] . ')'],
|
||||||
]">
|
]">
|
||||||
<x-slot name="description">
|
<x-slot name="description">
|
||||||
<div class="leading-7 [&_p]:mt-4">
|
<div class="leading-7 [&_p]:mt-4">
|
||||||
@@ -194,9 +194,9 @@ class="absolute bottom-6 right-8 text-white text-sm"
|
|||||||
</ul>
|
</ul>
|
||||||
</x-slot>
|
</x-slot>
|
||||||
<x-slot name="reviews">
|
<x-slot name="reviews">
|
||||||
@if(!empty($product['reviews']))
|
@if(!empty($product['reviews']['items']))
|
||||||
<div class="mb-10">
|
<div class="mb-10">
|
||||||
@foreach($product['reviews'] as $review)
|
@foreach($product['reviews']['items'] as $review)
|
||||||
<x-review-card :review="[
|
<x-review-card :review="[
|
||||||
'rating' => $review['rating'],
|
'rating' => $review['rating'],
|
||||||
'name' => $review['reviewer_name'],
|
'name' => $review['reviewer_name'],
|
||||||
@@ -211,7 +211,7 @@ class="absolute bottom-6 right-8 text-white text-sm"
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<h3 class="text-h4 font-bold">
|
<h3 class="text-h4 font-bold">
|
||||||
{{ $product['review_count'] > 0 ? __('storefront.review.write_new') : __('storefront.review.write_first') }}
|
{{ $product['reviews']['count'] > 0 ? __('storefront.review.write_new') : __('storefront.review.write_first') }}
|
||||||
{{ __('storefront.review.for_product', ['name' => $product['name']]) }}
|
{{ __('storefront.review.for_product', ['name' => $product['name']]) }}
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|||||||
+11
-1
@@ -7,6 +7,16 @@
|
|||||||
use App\Http\Controllers\ProductController;
|
use App\Http\Controllers\ProductController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
// Bare `/` has no {locale} segment to prefix-match against, so it's declared outside
|
||||||
|
// the group below purely to give `locale` middleware a route to run on — the group's
|
||||||
|
// `Route::prefix('{locale}')` requires a non-empty first segment, so `/` would
|
||||||
|
// otherwise 404 before the middleware (which already redirects an empty/unrecognized
|
||||||
|
// locale segment to the resolved default) ever gets a chance to run. Middleware runs
|
||||||
|
// before controller parameter binding, so this never actually reaches
|
||||||
|
// HomeController::index()'s required $locale argument — the middleware always
|
||||||
|
// redirects a request with no matching locale segment first.
|
||||||
|
Route::get('/', [HomeController::class, 'index'])->middleware('locale');
|
||||||
|
|
||||||
Route::prefix('{locale}')
|
Route::prefix('{locale}')
|
||||||
->middleware('locale')
|
->middleware('locale')
|
||||||
->group(function () {
|
->group(function () {
|
||||||
@@ -16,7 +26,7 @@
|
|||||||
'product.show',
|
'product.show',
|
||||||
);
|
);
|
||||||
|
|
||||||
Route::get('/category/{collection}', [CategoryController::class, 'show'])->name(
|
Route::get('/category/{id}', [CategoryController::class, 'show'])->name(
|
||||||
'category.show',
|
'category.show',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user