multilingual content, change in how we show header categories and homepage hero products

This commit is contained in:
elvira
2026-09-28 19:42:35 +03:00
parent 60ae6cf7a6
commit b522d60954
24 changed files with 250 additions and 63 deletions
+11 -6
View File
@@ -5,6 +5,7 @@
use App\Http\Requests\ContactRequest;
use App\Mail\ContactConfirmationMail;
use App\Mail\ContactMessageMail;
use App\Models\StoicPage;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
@@ -15,7 +16,7 @@
/**
* Contact form. Nothing is stored: the message is emailed to the store
* (CONTACT_EMAIL) and the sender gets a generic confirmation. Both are sent
* (Store Details' contact email) and the sender gets a generic confirmation. Both are sent
* synchronously so a failed store email can be reported back on the form.
*
* Limited per IP in here rather than with throttle middleware, so the limit
@@ -28,11 +29,15 @@ class ContactController extends Controller
public function index(string $locale): View
{
// Address and phone come from Store Details; the email is still
// CONTACT_EMAIL until Store Details has an email field.
$page = StoicPage::localized('contact');
if (! $page) {
abort(404);
}
return view('contact', [
'page' => $page,
'store' => app(StoreDetailsService::class)->current(),
'contactEmail' => config('services.contact.email'),
]);
}
@@ -47,11 +52,11 @@ public function send(string $locale, ContactRequest $request): RedirectResponse
RateLimiter::hit($key, self::SEND_DECAY_SECONDS);
$data = $request->validated();
$to = config('services.contact.email');
$to = app(StoreDetailsService::class)->current()->contact_email;
try {
if (blank($to)) {
throw new \RuntimeException('CONTACT_EMAIL is not set.');
throw new \RuntimeException('Store Details has no contact email.');
}
Mail::to($to)->send(new ContactMessageMail(
+50 -5
View File
@@ -4,11 +4,23 @@
use App\Catalog\ProductCard;
use App\Models\StoicPage;
use Modules\Core\Catalog\DTOs\ProductFilters;
use Modules\Core\Catalog\Services\CollectionService;
use Modules\Core\Catalog\Services\ProductService;
class HomeController extends Controller
{
public function __construct(private readonly ProductService $products) {}
/** Slug of the hero collection in the "Homepage" collection group. */
private const HERO_COLLECTION_SLUG = 'hero';
private const HERO_FALLBACK_COUNT = 5;
private const CLASSICS_COUNT = 8;
public function __construct(
private readonly ProductService $products,
private readonly CollectionService $collections,
) {}
public function index(string $locale)
{
@@ -18,13 +30,46 @@ public function index(string $locale)
abort(404);
}
$products = collect($this->products->random(13))
->map(fn (array $product) => ProductCard::fromIndexed($product));
// Random pool for the classics carousel, and for the hero too when the
// hero collection is missing or empty. Oversized so hero products can be
// excluded from it and still leave enough.
$random = collect($this->products->random(self::HERO_FALLBACK_COUNT + self::CLASSICS_COUNT));
$hero = collect($this->heroCollectionProducts());
if ($hero->isEmpty()) {
$hero = $random->take(self::HERO_FALLBACK_COUNT);
}
$heroIds = $hero->pluck('id')->all();
$classics = $random
->reject(fn (array $product) => in_array($product['id'], $heroIds))
->take(self::CLASSICS_COUNT);
return view('home', [
'page' => $page,
'heroProducts' => $products->take(5)->values(),
'classicsProducts' => $products->slice(5)->values(),
'heroProducts' => $hero->map(fn (array $product) => ProductCard::fromIndexed($product))->values(),
'classicsProducts' => $classics->map(fn (array $product) => ProductCard::fromIndexed($product))->values(),
]);
}
/**
* Products the store owner picked for the hero, via the Lunar collection
* with slug "hero". Not in the owner's drag order yet: the product index
* doesn't carry per-collection position (pending a boboko-core task).
*/
private function heroCollectionProducts(): array
{
$collection = $this->collections->getBySlug(self::HERO_COLLECTION_SLUG);
if ($collection === null) {
return [];
}
return $this->products
->list(new ProductFilters(collectionId: $collection['id']))
->products
->items();
}
}
@@ -15,6 +15,8 @@ class LegalPageController extends Controller
'{store_name}' => 'name',
'{store_address}' => 'address',
'{store_phone}' => 'phone',
'{store_contact_email}' => 'contact_email',
'{store_legal_name}' => 'legal_name',
'{store_tax_identifier}' => 'tax_identifier',
'{store_registration_number}' => 'registration_number',
];