setup for stoic settings, changes in legal pages to use core's store details and contact page to show contact info next to form

This commit is contained in:
elvira
2026-09-28 17:09:08 +03:00
parent 4634e14924
commit ca362f81c8
22 changed files with 489 additions and 102 deletions
+7 -1
View File
@@ -10,6 +10,7 @@
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\View\View;
use Modules\Core\Store\Services\StoreDetailsService;
use Throwable;
/**
@@ -27,7 +28,12 @@ class ContactController extends Controller
public function index(string $locale): View
{
return view('contact');
// Address and phone come from Store Details; the email is still
// CONTACT_EMAIL until Store Details has an email field.
return view('contact', [
'store' => app(StoreDetailsService::class)->current(),
'contactEmail' => config('services.contact.email'),
]);
}
public function send(string $locale, ContactRequest $request): RedirectResponse
+36 -1
View File
@@ -3,9 +3,26 @@
namespace App\Http\Controllers;
use App\Models\StoicPage;
use Modules\Core\Store\Services\StoreDetailsService;
class LegalPageController extends Controller
{
/**
* {placeholder} => StoreDetails column, usable in the Stoic legal page
* markdown.
*/
private const PLACEHOLDERS = [
'{store_name}' => 'name',
'{store_address}' => 'address',
'{store_phone}' => 'phone',
'{store_tax_identifier}' => 'tax_identifier',
'{store_registration_number}' => 'registration_number',
];
public function __construct(
private readonly StoreDetailsService $storeDetails,
) {}
public function terms(string $locale)
{
return $this->show('terms-and-conditions');
@@ -34,6 +51,24 @@ private function show(string $slug)
abort(404);
}
return view('legal', ['page' => $page]);
return view('legal', [
'page' => $page,
'content' => $this->fillPlaceholders($page->content ?? ''),
]);
}
/**
* Swapped in before the markdown is rendered, so values are escaped: the
* rendered markdown is printed unescaped. An empty field renders as
* nothing rather than leaving the raw placeholder on the page.
*/
private function fillPlaceholders(string $content): string
{
$details = $this->storeDetails->current();
return strtr($content, array_map(
fn (string $column) => e((string) ($details->translate($column) ?? '')),
self::PLACEHOLDERS,
));
}
}
+13
View File
@@ -4,6 +4,7 @@
use App\Models\Customer;
use App\Models\Staff;
use App\Services\StoicSettings;
use Illuminate\Database\Eloquent\Relations\Relation;
use Illuminate\Support\Facades\URL;
use Illuminate\Support\Facades\View;
@@ -17,6 +18,11 @@
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->scoped(StoicSettings::class);
}
public function boot(): void
{
Telemetry::optOut();
@@ -32,6 +38,13 @@ public function boot(): void
)->items());
});
// Site-wide settings edited in Stoic (settings.md): default meta
// description, social links for the footer, emails and schema.org.
View::composer(
['layouts.app', 'components.footer', 'emails.layout', 'home'],
fn (ViewInstance $view) => $view->with('stoicSettings', app(StoicSettings::class)),
);
if ($this->app->environment('production')) {
URL::forceScheme('https');
}
+58
View File
@@ -0,0 +1,58 @@
<?php
namespace App\Services;
use Spatie\YamlFrontMatter\YamlFrontMatter;
/**
* Site-wide, marketing-editable settings from Stoic's `settings` singleton
* (resources/stoic/content/settings.md). Read directly rather than through a
* Paper model: Paper models map to a folder of entries, and this is one file
* at the content root. Legal and transactional details (address, ΑΦΜ, phone)
* live in boboko-core's StoreDetails instead.
*
* Bound as scoped in AppServiceProvider, so the file is parsed once per
* request (or queued job) and edits in Stoic show up on the next one.
*/
class StoicSettings
{
private const SOCIALS = [
'facebook' => 'Facebook',
'instagram' => 'Instagram',
'tiktok' => 'TikTok',
];
private array $data;
public function __construct()
{
$path = resource_path('stoic/content/settings.md');
$this->data = is_file($path)
? YamlFrontMatter::parse(file_get_contents($path))->matter()
: [];
}
public function get(string $key, mixed $default = null): mixed
{
return filled($this->data[$key] ?? null) ? $this->data[$key] : $default;
}
/**
* Only the networks that have a URL set, in display order.
*
* @return list<array{icon: string, label: string, url: string}>
*/
public function socialLinks(): array
{
$links = [];
foreach (self::SOCIALS as $icon => $label) {
if ($url = $this->get("{$icon}_url")) {
$links[] = ['icon' => $icon, 'label' => $label, 'url' => $url];
}
}
return $links;
}
}