breakign changes

This commit is contained in:
Mark Gregory
2026-07-24 15:16:10 +01:00
parent 157450cf44
commit 4988cb85bd
15 changed files with 457 additions and 76 deletions
@@ -1 +1,27 @@
<x-layouts.app :title="__('Blog')"><div class="mx-auto max-w-5xl"><h1 class="mb-8 text-4xl font-bold">Blog</h1><div class="grid gap-6 md:grid-cols-2">@foreach($records as $record)<article class="rounded-xl border p-6"><h2 class="text-xl font-semibold"><a href="{{ route('blog.public.show',$record->slug) }}">{{ $record->title }}</a></h2><p class="mt-2 text-zinc-600">{{ $record->excerpt }}</p></article>@endforeach</div><div class="mt-8">{{ $records->links() }}</div></div></x-layouts.app>
<x-stylist-theme-saasland::layouts.app
title="Blog"
description="Ideas, guides and updates from our team."
eyebrow="Insights"
:breadcrumbs="['Blog' => null]"
>
<section class="content-section">
<div class="wrap">
<div class="content-grid">
@forelse($records as $record)
<article class="post-card">
<div class="post-card-accent"></div>
<div class="post-card-body">
<span class="post-meta">{{ optional($record->published_at)->format('j F Y') ?: 'Latest article' }}</span>
<h2><a href="{{ route('blog.public.show', $record->slug) }}">{{ $record->title }}</a></h2>
@if($record->excerpt)<p>{{ $record->excerpt }}</p>@endif
<a class="read-more" href="{{ route('blog.public.show', $record->slug) }}">Read article →</a>
</div>
</article>
@empty
<div class="empty-state">No blog posts have been published yet.</div>
@endforelse
</div>
<div class="theme-pagination">{{ $records->links() }}</div>
</div>
</section>
</x-stylist-theme-saasland::layouts.app>
@@ -1 +1,19 @@
<x-layouts.app :title="$record->title"><article class="mx-auto max-w-3xl"><h1 class="text-4xl font-bold">{{ $record->title }}</h1><p class="mt-3 text-zinc-500">{{ optional($record->published_at)->format('j F Y') }}</p><div class="prose mt-8 max-w-none">{!! nl2br(e($record->content)) !!}</div></article></x-layouts.app>
<x-stylist-theme-saasland::layouts.app
:title="$record->title"
:description="$record->excerpt"
eyebrow="Blog"
:breadcrumbs="['Blog' => route('blog.public.index'), $record->title => null]"
>
<section class="content-section">
<div class="wrap content-layout">
<article class="content-card prose-content">
<div class="article-meta">
@if($record->author)<span>{{ $record->author->name }}</span>@endif
@if($record->published_at)<time datetime="{{ $record->published_at->toDateString() }}">{{ $record->published_at->format('j F Y') }}</time>@endif
</div>
@if($record->excerpt)<p class="content-lead">{{ $record->excerpt }}</p>@endif
<div class="cms-content">{!! nl2br(e($record->content)) !!}</div>
</article>
</div>
</section>
</x-stylist-theme-saasland::layouts.app>
@@ -1 +1,27 @@
<x-layouts.app :title="__('News')"><div class="mx-auto max-w-5xl"><h1 class="mb-8 text-4xl font-bold">News</h1><div class="grid gap-6 md:grid-cols-2">@foreach($records as $record)<article class="rounded-xl border p-6"><h2 class="text-xl font-semibold"><a href="{{ route('news.public.show',$record->slug) }}">{{ $record->title }}</a></h2><p class="mt-2 text-zinc-600">{{ $record->excerpt }}</p></article>@endforeach</div><div class="mt-8">{{ $records->links() }}</div></div></x-layouts.app>
<x-stylist-theme-saasland::layouts.app
title="News"
description="The latest announcements, releases and company updates."
eyebrow="Updates"
:breadcrumbs="['News' => null]"
>
<section class="content-section">
<div class="wrap">
<div class="content-grid">
@forelse($records as $record)
<article class="post-card">
<div class="post-card-accent"></div>
<div class="post-card-body">
<span class="post-meta">{{ optional($record->published_at)->format('j F Y') ?: 'Latest update' }}</span>
<h2><a href="{{ route('news.public.show', $record->slug) }}">{{ $record->title }}</a></h2>
@if($record->excerpt)<p>{{ $record->excerpt }}</p>@endif
<a class="read-more" href="{{ route('news.public.show', $record->slug) }}">Read update →</a>
</div>
</article>
@empty
<div class="empty-state">No news has been published yet.</div>
@endforelse
</div>
<div class="theme-pagination">{{ $records->links() }}</div>
</div>
</section>
</x-stylist-theme-saasland::layouts.app>
@@ -1 +1,19 @@
<x-layouts.app :title="$record->title"><article class="mx-auto max-w-3xl"><h1 class="text-4xl font-bold">{{ $record->title }}</h1><p class="mt-3 text-zinc-500">{{ optional($record->published_at)->format('j F Y') }}</p><div class="prose mt-8 max-w-none">{!! nl2br(e($record->content)) !!}</div></article></x-layouts.app>
<x-stylist-theme-saasland::layouts.app
:title="$record->title"
:description="$record->excerpt"
eyebrow="News"
:breadcrumbs="['News' => route('news.public.index'), $record->title => null]"
>
<section class="content-section">
<div class="wrap content-layout">
<article class="content-card prose-content">
<div class="article-meta">
@if($record->author)<span>{{ $record->author->name }}</span>@endif
@if($record->published_at)<time datetime="{{ $record->published_at->toDateString() }}">{{ $record->published_at->format('j F Y') }}</time>@endif
</div>
@if($record->excerpt)<p class="content-lead">{{ $record->excerpt }}</p>@endif
<div class="cms-content">{!! nl2br(e($record->content)) !!}</div>
</article>
</div>
</section>
</x-stylist-theme-saasland::layouts.app>
@@ -1 +1,18 @@
<x-layouts.app :title="$page->title"><article class="prose mx-auto max-w-4xl py-10 dark:prose-invert"><h1>{{ $page->title }}</h1>@if($page->summary)<p class="lead">{{ $page->summary }}</p>@endif{!! $page->content !!}</article></x-layouts.app>
<x-stylist-theme-saasland::layouts.app
:title="$page->title"
:description="$page->summary"
eyebrow="Page"
:breadcrumbs="[$page->title => null]"
>
<section class="content-section">
<div class="wrap content-layout">
<article class="content-card prose-content">
@if ($page->summary)
<p class="content-lead">{{ $page->summary }}</p>
@endif
<div class="cms-content">{!! $page->content !!}</div>
</article>
</div>
</section>
</x-stylist-theme-saasland::layouts.app>
@@ -13,7 +13,10 @@ use Modules\Stylist\Console\ThemeDoctorCommand;
use Modules\Stylist\Console\ValidateThemeCommand;
use Modules\Stylist\Html\ThemeHtmlBuilder;
use Modules\Stylist\Html\ThemeSeoBuilder;
use Modules\Stylist\Support\FrontendNavigation;
use Modules\Stylist\Support\ModuleInfo;
use Modules\Stylist\Services\ThemeSettings;
use Modules\Stylist\Theme\Loader;
use Modules\Stylist\Theme\Stylist;
final class StylistServiceProvider extends ServiceProvider
@@ -62,6 +65,24 @@ final class StylistServiceProvider extends ServiceProvider
$view->with('activeTheme', $this->app->make(Stylist::class)->current());
});
View::composer('stylist-theme-saasland::*', function ($view): void {
$theme = $this->app->make(Loader::class)->find('saasland');
$settings = $this->app->make(ThemeSettings::class)->get($theme);
$navigation = $this->app->make(FrontendNavigation::class);
$view->with([
'siteTitle' => filled($settings['site_title'] ?? null) ? $settings['site_title'] : config('app.name', 'Saasland'),
'siteDescription' => filled($settings['site_description'] ?? null) ? $settings['site_description'] : 'Modern software for modern companies.',
'logoUrl' => $settings['logo_url'] ?? null,
'facebookUrl' => $settings['facebook_url'] ?? null,
'xUrl' => $settings['x_url'] ?? null,
'linkedinUrl' => $settings['linkedin_url'] ?? null,
'primaryNavigation' => $navigation->primary(),
'footerNavigation' => $navigation->footer(),
]);
});
}
private function registerViews(): void
@@ -0,0 +1,121 @@
<?php
namespace Modules\Stylist\Support;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Schema;
use Modules\Menus\Models\Menu;
use Throwable;
final class FrontendNavigation
{
/** @return array<int, array<string, mixed>> */
public function primary(): array
{
return $this->forLocations(['primary', 'header', 'main'], $this->fallbackPrimary());
}
/** @return array<int, array<string, mixed>> */
public function footer(): array
{
return $this->forLocations(['footer'], $this->fallbackFooter());
}
/** @param array<int, string> $locations
* @param array<int, array<string, mixed>> $fallback
* @return array<int, array<string, mixed>>
*/
private function forLocations(array $locations, array $fallback): array
{
try {
if (! Schema::hasTable('menus')) {
return $fallback;
}
$menu = Menu::query()
->where('enabled', true)
->whereIn('location', $locations)
->orderByRaw('CASE location '.collect($locations)->map(fn (string $location, int $index) => "WHEN ? THEN {$index}")->implode(' ').' ELSE 999 END', $locations)
->first();
if (! $menu || ! is_array($menu->items) || $menu->items === []) {
return $fallback;
}
$items = $this->normaliseItems($menu->items);
return $items === [] ? $fallback : $items;
} catch (Throwable) {
return $fallback;
}
}
/** @param array<int, mixed> $items
* @return array<int, array<string, mixed>>
*/
private function normaliseItems(array $items): array
{
return collect($items)
->filter(fn (mixed $item): bool => is_array($item) && ($item['enabled'] ?? true) !== false)
->map(function (array $item): ?array {
$label = trim((string) ($item['label'] ?? $item['title'] ?? $item['name'] ?? ''));
if ($label === '') {
return null;
}
$url = $this->resolveUrl($item);
if ($url === null) {
return null;
}
return [
'label' => $label,
'url' => $url,
'target' => ($item['target'] ?? null) === '_blank' ? '_blank' : null,
'children' => $this->normaliseItems((array) ($item['children'] ?? $item['items'] ?? [])),
];
})
->filter()
->values()
->all();
}
/** @param array<string, mixed> $item */
private function resolveUrl(array $item): ?string
{
$routeName = trim((string) ($item['route'] ?? $item['route_name'] ?? ''));
if ($routeName !== '' && Route::has($routeName)) {
return route($routeName, (array) ($item['parameters'] ?? []));
}
$pageSlug = trim((string) ($item['page_slug'] ?? $item['slug'] ?? ''));
if ($pageSlug !== '' && Route::has('pages.public.show')) {
return route('pages.public.show', $pageSlug);
}
$url = trim((string) ($item['url'] ?? $item['href'] ?? ''));
return $url !== '' ? $url : null;
}
/** @return array<int, array<string, mixed>> */
private function fallbackPrimary(): array
{
$items = [['label' => 'Home', 'url' => url('/'), 'target' => null, 'children' => []]];
if (Route::has('blog.public.index')) {
$items[] = ['label' => 'Blog', 'url' => route('blog.public.index'), 'target' => null, 'children' => []];
}
if (Route::has('news.public.index')) {
$items[] = ['label' => 'News', 'url' => route('news.public.index'), 'target' => null, 'children' => []];
}
return $items;
}
/** @return array<int, array<string, mixed>> */
private function fallbackFooter(): array
{
return $this->fallbackPrimary();
}
}