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
+1 -1
View File
@@ -58,8 +58,8 @@ MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
# Fallback only: the sender name comes from Store Details' "Mail from name" when set.
MAIL_FROM_NAME="${APP_NAME}"
CONTACT_EMAIL=
HCAPTCHA_SITEKEY=
HCAPTCHA_SECRET=
+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',
];
+1 -1
View File
@@ -8,7 +8,7 @@
use Illuminate\Mail\Mailables\Envelope;
/**
* A contact-form message, sent to the store's CONTACT_EMAIL. Reply-To is the
* A contact-form message, sent to Store Details' contact email. Reply-To is the
* sender, so replying from the inbox goes straight to them.
*/
class ContactMessageMail extends Mailable
+38 -5
View File
@@ -6,15 +6,20 @@
use App\Models\Staff;
use App\Services\StoicSettings;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Mail\Events\MessageSending;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
use Illuminate\View\View as ViewInstance;
use Lunar\Facades\ModelManifest;
use Lunar\Facades\Telemetry;
use Lunar\Models\CollectionGroup;
use Modules\Core\Catalog\DTOs\CollectionFilters;
use Modules\Core\Catalog\Enums\CollectionSort;
use Modules\Core\Catalog\Services\CollectionService;
use Modules\Core\Store\Services\StoreDetailsService;
use Symfony\Component\Mime\Address;
class AppServiceProvider extends ServiceProvider
{
@@ -27,12 +32,17 @@ public function boot(): void
{
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.
// header.blade.php's category dropdown — root collections of the catalog
// group only, so merchandising groups (e.g. "Homepage") stay out of the
// nav. Resolved per-request so the composer runs after `locale` middleware
// has already set App::getLocale(), which CollectionService's name
// resolution depends on. The group is looked up by handle, not id, since
// ids can differ between environments.
View::composer('components.header', function (ViewInstance $view) {
$view->with('categories', app(CollectionService::class)->list(
filters: new CollectionFilters(rootOnly: true),
$groupId = CollectionGroup::where('handle', 'shopify')->value('id');
$view->with('categories', $groupId === null ? [] : app(CollectionService::class)->list(
filters: new CollectionFilters(groupId: $groupId, rootOnly: true),
perPage: 100,
sort: CollectionSort::Position,
)->items());
@@ -45,6 +55,29 @@ public function boot(): void
fn (ViewInstance $view) => $view->with('stoicSettings', app(StoicSettings::class)),
);
// INTERIM, belongs in boboko-core's Store module: the sender name on
// outgoing emails comes from Store Details' "Mail from name", with
// MAIL_FROM_NAME as the fallback when it's empty. Done per message
// rather than by setting config at boot, so a long-running queue
// worker picks up a changed name without a restart. Only the default
// from address is renamed; a mailable with its own from is left alone.
Event::listen(MessageSending::class, function (MessageSending $event) {
$name = app(StoreDetailsService::class)->current()->mail_from_name;
if (blank($name)) {
return;
}
$defaultAddress = config('mail.from.address');
$event->message->from(...array_map(
fn (Address $from) => $from->getAddress() === $defaultAddress
? new Address($from->getAddress(), $name)
: $from,
$event->message->getFrom(),
));
});
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
-5
View File
@@ -35,11 +35,6 @@
],
],
// Where contact-form messages are sent (ContactController).
'contact' => [
'email' => env('CONTACT_EMAIL'),
],
// Bot check on guest forms (login, contact), verified by App\Rules\HCaptcha. For
// local dev use hCaptcha's test keys, the real ones reject localhost:
// sitekey 10000000-ffff-ffff-ffff-000000000001,
@@ -0,0 +1,13 @@
---
name: "Επικοινωνία"
slug: "contact"
meta_title: "Επικοινωνία"
meta_description: "Επικοινώνησε μαζί μας για οποιαδήποτε απορία ή ιδέα έχεις και θα σου απαντήσουμε το συντομότερο δυνατό."
og_image: ""
heading_first: "Επικοινώνησε"
heading_second: "μαζί μας"
heading_highlight: "άμεσα"
intro: "Έχεις απορία, ιδέα ή θες να μας πεις κάτι; Γράψε μας το μήνυμά σου και θα σου απαντήσουμε το συντομότερο δυνατό."
---
@@ -18,12 +18,12 @@ ## 1. Υπεύθυνος επεξεργασίας
Υπεύθυνος επεξεργασίας των προσωπικών δεδομένων που συλλέγονται μέσω του ηλεκτρονικού καταστήματος είναι:
**3DEALER**
**{store_legal_name}**
Πάρου 31, Κυψέλη, ΤΚ 11255, Αθήνα
ΑΦΜ: {store_tax_identifier}
ΓΕΜΗ: {store_registration_number}
Email: [print@3dealer.gr](mailto:print@3dealer.gr)
Τηλέφωνο: 6976 443140
Email: [{store_contact_email}](mailto:{store_contact_email})
Τηλέφωνο: {store_phone}
Η 3DEALER καθορίζει τους σκοπούς και τα μέσα επεξεργασίας των προσωπικών δεδομένων των πελατών και χρηστών του ηλεκτρονικού καταστήματος.
@@ -81,7 +81,7 @@ ## 6. Newsletter και εμπορική επικοινωνία
Η εγγραφή στο newsletter πραγματοποιείται με τη συγκατάθεσή σας.
Μπορείτε να ανακαλέσετε τη συγκατάθεσή σας οποιαδήποτε στιγμή, χρησιμοποιώντας τον σύνδεσμο απεγγραφής που περιλαμβάνεται στα emails ή επικοινωνώντας μαζί μας στο [print@3dealer.gr](mailto:print@3dealer.gr).
Μπορείτε να ανακαλέσετε τη συγκατάθεσή σας οποιαδήποτε στιγμή, χρησιμοποιώντας τον σύνδεσμο απεγγραφής που περιλαμβάνεται στα emails ή επικοινωνώντας μαζί μας στο [{store_contact_email}](mailto:{store_contact_email}).
Η ανάκληση της συγκατάθεσης δεν επηρεάζει τη νομιμότητα της επεξεργασίας που πραγματοποιήθηκε πριν από αυτήν.
@@ -186,8 +186,8 @@ ## 16. Τα δικαιώματά σας
Για την άσκηση των δικαιωμάτων σας μπορείτε να επικοινωνήσετε μαζί μας:
**Email:** [print@3dealer.gr](mailto:print@3dealer.gr)
**Τηλέφωνο:** 6976 443140
**Email:** [{store_contact_email}](mailto:{store_contact_email})
**Τηλέφωνο:** {store_phone}
Θα απαντήσουμε στο αίτημά σας χωρίς αδικαιολόγητη καθυστέρηση και, κατά κανόνα, εντός ενός (1) μήνα από την παραλαβή του.
@@ -45,7 +45,7 @@ ## 3. Χρόνος παράδοσης
Η 3DEALER δεν ευθύνεται για καθυστερήσεις που οφείλονται αποκλειστικά στην εταιρεία μεταφοράς, σε ακραία καιρικά φαινόμενα, απεργίες, προβλήματα στο δίκτυο μεταφορών ή άλλες περιστάσεις που βρίσκονται εκτός του εύλογου ελέγχου της.
Σε περίπτωση καθυστέρησης, μπορείτε να επικοινωνήσετε μαζί μας στο [print@3dealer.gr](mailto:print@3dealer.gr).
Σε περίπτωση καθυστέρησης, μπορείτε να επικοινωνήσετε μαζί μας στο [{store_contact_email}](mailto:{store_contact_email}).
## 4. Παραλαβή παραγγελίας
@@ -55,7 +55,7 @@ ## 4. Παραλαβή παραγγελίας
## 5. Προϊόν που παραδόθηκε κατεστραμμένο
Σε περίπτωση που το προϊόν παραδοθεί εμφανώς κατεστραμμένο, παρακαλούμε να επικοινωνήσετε μαζί μας το συντομότερο δυνατό στο [print@3dealer.gr](mailto:print@3dealer.gr) και να μας αποστείλετε φωτογραφίες του προϊόντος και της συσκευασίας.
Σε περίπτωση που το προϊόν παραδοθεί εμφανώς κατεστραμμένο, παρακαλούμε να επικοινωνήσετε μαζί μας το συντομότερο δυνατό στο [{store_contact_email}](mailto:{store_contact_email}) και να μας αποστείλετε φωτογραφίες του προϊόντος και της συσκευασίας.
Η 3DEALER θα εξετάσει το περιστατικό και, εφόσον διαπιστωθεί ζημιά που προέκυψε κατά τη μεταφορά ή άλλη έλλειψη συμμόρφωσης, θα προχωρήσει στην κατάλληλη λύση σύμφωνα με την ισχύουσα νομοθεσία.
@@ -63,7 +63,7 @@ ## 6. Δικαίωμα υπαναχώρησης
Για τις αγορές που εμπίπτουν στο πεδίο εφαρμογής του δικαιώματος υπαναχώρησης, ο καταναλωτής μπορεί να υπαναχωρήσει από τη σύμβαση εντός δεκατεσσάρων (14) ημερολογιακών ημερών από την ημέρα παραλαβής του προϊόντος.
Για την άσκηση του δικαιώματος υπαναχώρησης, ο πελάτης πρέπει να ενημερώσει την 3DEALER με σαφή δήλωση στο [print@3dealer.gr](mailto:print@3dealer.gr).
Για την άσκηση του δικαιώματος υπαναχώρησης, ο πελάτης πρέπει να ενημερώσει την 3DEALER με σαφή δήλωση στο [{store_contact_email}](mailto:{store_contact_email}).
Το προϊόν πρέπει να επιστραφεί χωρίς αδικαιολόγητη καθυστέρηση και, σε κάθε περίπτωση, εντός δεκατεσσάρων (14) ημερών από την ημέρα κατά την οποία ο πελάτης ενημέρωσε την 3DEALER για την απόφασή του να υπαναχωρήσει.
@@ -101,4 +101,4 @@ ## 10. Ελαττωματικά ή μη συμμορφούμενα προϊόν
Η ύπαρξη μικρών αισθητικών χαρακτηριστικών που είναι φυσιολογικά για την τρισδιάστατη εκτύπωση, όπως γραμμές στρώσεων ή μικρές διαφοροποιήσεις, δεν αποτελεί από μόνη της ελάττωμα.
Για οποιοδήποτε πρόβλημα με προϊόν, επικοινωνήστε μαζί μας στο [print@3dealer.gr](mailto:print@3dealer.gr), αναφέροντας τον αριθμό παραγγελίας και, όπου είναι δυνατόν, επισυνάπτοντας φωτογραφίες.
Για οποιοδήποτε πρόβλημα με προϊόν, επικοινωνήστε μαζί μας στο [{store_contact_email}](mailto:{store_contact_email}), αναφέροντας τον αριθμό παραγγελίας και, όπου είναι δυνατόν, επισυνάπτοντας φωτογραφίες.
@@ -9,15 +9,15 @@
## 1. Γενικά
Το ηλεκτρονικό κατάστημα 3DEALER (εφεξής «το Κατάστημα» ή «3DEALER») λειτουργεί από την επιχείρηση 3DEALER, με έδρα στην Πάρου 31, Κυψέλη, ΤΚ 11255, Αθήνα. test english
Το ηλεκτρονικό κατάστημα 3DEALER (εφεξής «το Κατάστημα» ή «3DEALER») λειτουργεί από την επιχείρηση {store_legal_name}, με έδρα στην Πάρου 31, Κυψέλη, ΤΚ 11255, Αθήνα.
**ΑΦΜ:** {store_tax_identifier}
**ΑΦΜ:** {store_tax_identifier}\
**ΓΕΜΗ:** {store_registration_number}
Για οποιαδήποτε πληροφορία ή επικοινωνία σχετικά με παραγγελίες, προϊόντα ή τη λειτουργία του Καταστήματος, μπορείτε να επικοινωνείτε μαζί μας:
**Email:** [print@3dealer.gr](mailto:print@3dealer.gr)
**Τηλέφωνο:** 6976 443140
**Email:** [{store_contact_email}](mailto:{store_contact_email})\
**Τηλέφωνο:** {store_phone}
Η χρήση του Καταστήματος και η πραγματοποίηση αγορών μέσω αυτού προϋποθέτουν την αποδοχή των παρόντων Όρων & Προϋποθέσεων.
@@ -0,0 +1,9 @@
---
name: "Contact"
meta_title: "Contact"
meta_description: "Get in touch with any question or idea you have and we'll get back to you as soon as we can."
heading_first: "Contact"
heading_second: "us"
heading_highlight: "today"
intro: "Got a question, an idea or something to tell us? Send us your message and we'll get back to you as soon as we can."
---
@@ -12,12 +12,12 @@ ## 1. Data controller
The controller of the personal data collected through the online store is:
**3DEALER**
**{store_legal_name}**
Parou 31, Kypseli, 11255 Athens, Greece
Tax ID (ΑΦΜ): {store_tax_identifier}
Company registration number (ΓΕΜΗ): {store_registration_number}
Email: [print@3dealer.gr](mailto:print@3dealer.gr)
Phone: +30 6976 443140
Email: [{store_contact_email}](mailto:{store_contact_email})
Phone: {store_phone}
3DEALER determines the purposes and means of processing the personal data of the online store's customers and users.
@@ -75,7 +75,7 @@ ## 6. Newsletter and marketing communication
Subscribing to the newsletter is based on your consent.
You can withdraw your consent at any time by using the unsubscribe link included in the emails or by contacting us at [print@3dealer.gr](mailto:print@3dealer.gr).
You can withdraw your consent at any time by using the unsubscribe link included in the emails or by contacting us at [{store_contact_email}](mailto:{store_contact_email}).
Withdrawing consent does not affect the lawfulness of processing carried out before the withdrawal.
@@ -180,8 +180,8 @@ ## 16. Your rights
To exercise your rights, you can contact us:
**Email:** [print@3dealer.gr](mailto:print@3dealer.gr)
**Phone:** +30 6976 443140
**Email:** [{store_contact_email}](mailto:{store_contact_email})
**Phone:** {store_phone}
We will respond to your request without undue delay and, as a rule, within one (1) month of receiving it.
@@ -40,7 +40,7 @@ ## 3. Delivery time
3DEALER is not responsible for delays caused solely by the carrier, extreme weather, strikes, problems in the transport network or other circumstances beyond its reasonable control.
In case of a delay, you can contact us at [print@3dealer.gr](mailto:print@3dealer.gr).
In case of a delay, you can contact us at [{store_contact_email}](mailto:{store_contact_email}).
## 4. Receiving your order
@@ -50,7 +50,7 @@ ## 4. Receiving your order
## 5. Product delivered damaged
If the product arrives visibly damaged, please contact us as soon as possible at [print@3dealer.gr](mailto:print@3dealer.gr) and send us photos of the product and the packaging.
If the product arrives visibly damaged, please contact us as soon as possible at [{store_contact_email}](mailto:{store_contact_email}) and send us photos of the product and the packaging.
3DEALER will look into the case and, if damage that occurred in transit or another non-conformity is found, will provide an appropriate remedy in accordance with applicable law.
@@ -58,7 +58,7 @@ ## 6. Right of withdrawal
For purchases that fall within the scope of the right of withdrawal, the consumer may withdraw from the contract within fourteen (14) calendar days of the day the product was received.
To exercise the right of withdrawal, the customer must inform 3DEALER with a clear statement at [print@3dealer.gr](mailto:print@3dealer.gr).
To exercise the right of withdrawal, the customer must inform 3DEALER with a clear statement at [{store_contact_email}](mailto:{store_contact_email}).
The product must be returned without undue delay and, in any case, within fourteen (14) days of the day the customer informed 3DEALER of their decision to withdraw.
@@ -96,4 +96,4 @@ ## 10. Defective or non-conforming products
Minor cosmetic characteristics that are normal for 3D printing, such as layer lines or small variations, do not in themselves constitute a defect.
For any problem with a product, contact us at [print@3dealer.gr](mailto:print@3dealer.gr), stating your order number and, where possible, attaching photos.
For any problem with a product, contact us at [{store_contact_email}](mailto:{store_contact_email}), stating your order number and, where possible, attaching photos.
@@ -6,15 +6,15 @@
## 1. General
The online store 3DEALER (hereinafter "the Store" or "3DEALER") is operated by the business 3DEALER, registered at Parou 31, Kypseli, 11255 Athens, Greece.
The online store 3DEALER (hereinafter "the Store" or "3DEALER") is operated by the business {store_legal_name}, registered at Parou 31, Kypseli, 11255 Athens, Greece.
**Tax ID (ΑΦΜ):** {store_tax_identifier}
**Tax ID (ΑΦΜ):** {store_tax_identifier}\
**Company registration number (ΓΕΜΗ):** {store_registration_number}
For any information or questions about orders, products or the operation of the Store, you can contact us:
**Email:** [print@3dealer.gr](mailto:print@3dealer.gr)
**Phone:** +30 6976 443140
**Email:** [{store_contact_email}](mailto:{store_contact_email})\
**Phone:** {store_phone}
Using the Store and making purchases through it requires acceptance of these Terms & Conditions.
+38
View File
@@ -37,6 +37,44 @@ schemas:
label: "Classics Section Title (favorites carousel heading)"
ui: string
translatable: true
- name: contact
label: Contact Page
file: pages/contact.md
fields:
- name: name
label: Name
ui: string
translatable: true
- name: slug
label: Slug
ui: slug
- name: meta_title
label: Meta Title
ui: string
translatable: true
- name: meta_description
label: Meta Description
ui: string
translatable: true
- name: og_image
label: Og Image
ui: image
- name: heading_first
label: "Heading, first line (animated)"
ui: string
translatable: true
- name: heading_second
label: "Heading, second line"
ui: string
translatable: true
- name: heading_highlight
label: "Heading, last word(s) of the second line (animated)"
ui: string
translatable: true
- name: intro
label: "Intro (paragraph above the form)"
ui: text
translatable: true
- name: terms-and-conditions
label: "Terms & Conditions Page"
file: pages/terms-and-conditions.md
@@ -1,16 +1,15 @@
{{--
Store address and phone from Lunar's Store Details (Modules\Core\Store),
email from CONTACT_EMAIL until Store Details has an email field too.
Each line is left out when its value is empty.
Store address, phone and email from Lunar's Store Details
(Modules\Core\Store). Each line is left out when its value is empty.
--}}
@props([
'store' => null,
'email' => null,
])
@php
$address = $store?->translate('address');
$phone = $store?->phone;
$email = $store?->contact_email;
@endphp
@if (filled($address) || filled($phone) || filled($email))
+8 -8
View File
@@ -1,7 +1,8 @@
@extends('layouts.app')
@section('title', __('storefront.nav.contact'))
@section('description', 'Επικοινώνησε μαζί μας για οποιαδήποτε απορία ή ιδέα έχεις και θα σου απαντήσουμε το συντομότερο δυνατό.')
@section('title', $page->meta_title ?: $page->name)
@section('description', $page->meta_description)
@section('og_image', $page->og_image)
{{-- Only on pages with a captcha, not in the global layout. --}}
@push('scripts')
@@ -21,13 +22,12 @@
<h1 class="font-display text-h1 font-semibold leading-tight"
data-controller="appear"
data-appear-visible-class="is-visible">
<span class="text-dance">Επικοινώνησε</span><br>
μαζί μας <span class="text-dance [--dance-delay:200ms]">άμεσα</span>
<span class="text-dance" data-stoic="pages/contact#heading_first">{{ $page->heading_first }}</span><br>
<span data-stoic="pages/contact#heading_second">{{ $page->heading_second }}</span>
<span class="text-dance [--dance-delay:200ms]" data-stoic="pages/contact#heading_highlight">{{ $page->heading_highlight }}</span>
</h1>
<p class="max-w-xl ">
Έχεις απορία, ιδέα ή θες να μας πεις κάτι; Γράψε μας το μήνυμά σου και θα σου απαντήσουμε το συντομότερο δυνατό.
</p>
<p class="max-w-xl" data-stoic="pages/contact#intro">{{ $page->intro }}</p>
<x-ui.status class="max-w-xl" />
@@ -76,7 +76,7 @@ class="h-captcha min-h-[78px]"
</div>
<div class="flex flex-col justify-center border-t border-black px-8 py-16 lg:border-t-0 lg:px-16">
<x-contact-details :store="$store" :email="$contactEmail" />
<x-contact-details :store="$store" />
</div>
</section>
+4
View File
@@ -0,0 +1,4 @@
@extends('errors.layout')
@section('error_title', __('storefront.errors.404_title'))
@section('error_text', __('storefront.errors.404_text'))
+4
View File
@@ -0,0 +1,4 @@
@extends('errors.layout')
@section('error_title', __('storefront.errors.4xx_title'))
@section('error_text', __('storefront.errors.4xx_text'))
+4
View File
@@ -0,0 +1,4 @@
@extends('errors.layout')
@section('error_title', __('storefront.errors.500_title'))
@section('error_text', __('storefront.errors.500_text'))
+4
View File
@@ -0,0 +1,4 @@
@extends('errors.layout')
@section('error_title', __('storefront.errors.5xx_title'))
@section('error_text', __('storefront.errors.5xx_text'))
+26
View File
@@ -0,0 +1,26 @@
{{-- Shared by every errors/{code} view: each one only sets title + text. --}}
@extends('layouts.app')
{{-- Block form: the inline @section('title', …) form would escape the
already-escaped error_title a second time. --}}
@section('title')
@yield('error_title')
@endsection
@push('seo')
<meta name="robots" content="noindex">
@endpush
@section('content')
<section class="flex flex-col items-center justify-center gap-8 px-8 py-24 text-center min-h-[calc(100vh-12rem)] sm:min-h-[calc(100vh-8rem)]">
<h1 class="font-display text-h1 font-semibold leading-tight">@yield('error_title')</h1>
<p class="max-w-xl">@yield('error_text')</p>
<x-ui.button :href="route('home')" class="mt-4">{{ __('storefront.errors.back_home') }}</x-ui.button>
</section>
@endsection
+6
View File
@@ -131,4 +131,10 @@
->name('account.email.resend');
});
// Unmatched URLs would otherwise 404 during routing, before `web` and
// `locale` middleware run, so errors/404 would render with no session,
// no App::setLocale() and no URL::defaults() — the header's route()
// calls would then throw on the missing {locale}. Catching them here
// renders the 404 inside the locale group like any other page.
Route::fallback(fn () => abort(404));
});