Add mobile topbar titles and show Afia on all mobile headers.
Deploy Ladill Servers / deploy (push) Successful in 41s

Route-based subtitle and page titles replace plain app labels in mobile topbars, and the Afia AI button is visible on mobile across topbars and mobile-page-header screens.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-07 16:31:00 +00:00
co-authored by Cursor
parent fc58c9d40d
commit ee5be04d11
7 changed files with 90 additions and 9 deletions
+6
View File
@@ -6,6 +6,8 @@ use App\Models\HostingAccount;
use App\Policies\HostingAccountPolicy;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\ServiceProvider;
use App\Support\MobileTopbar;
use Illuminate\Support\Facades\View;
class AppServiceProvider extends ServiceProvider
{
@@ -17,5 +19,9 @@ class AppServiceProvider extends ServiceProvider
public function boot(): void
{
Gate::policy(HostingAccount::class, HostingAccountPolicy::class);
View::composer(['partials.topbar'], function ($view) {
$view->with(MobileTopbar::resolve());
});
}
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Support;
class MobileTopbar
{
public static function resolve(): array
{
$config = config('mobile-topbar', []);
$subtitle = $config['app_name'] ?? 'Ladill';
$title = $config['default_title'] ?? 'Overview';
foreach ($config['patterns'] ?? [] as $pattern => $patternTitle) {
if (request()->routeIs($pattern)) {
$title = is_callable($patternTitle) ? $patternTitle() : $patternTitle;
break;
}
}
return [
'mobileTopbarSubtitle' => $subtitle,
'mobileTopbarTitle' => $title,
];
}
}
+19
View File
@@ -0,0 +1,19 @@
<?php
return [
'app_name' => 'Servers',
'default_title' => 'Dashboard',
'patterns' => [
'servers.dashboard' => 'Dashboard',
'servers.search' => 'Search',
'servers.vps' => 'VPS',
'servers.dedicated' => 'Dedicated',
'servers.orders.*' => 'Orders',
'hosting.server-panel.*' => 'Server panel',
'account.wallet' => 'Wallet',
'account.billing' => 'Billing',
'account.team*' => 'Team',
'account.settings' => 'Settings',
'notifications.*' => 'Notifications',
],
];
@@ -3,6 +3,7 @@
'subtitle' => null,
'backUrl' => null,
'badge' => null,
'hideAfia' => false,
])
<div class="sticky top-0 z-20 border-b border-slate-200 bg-white/95 backdrop-blur lg:hidden">
@@ -22,6 +23,9 @@
@if ($badge)
<span class="rounded-full bg-slate-100 px-3 py-1 text-xs font-medium text-slate-600">{{ $badge }}</span>
@endif
@unless ($hideAfia)
@include('partials.afia-button', ['compact' => true])
@endunless
{{ $actions ?? '' }}
</div>
{{ $slot }}
@@ -0,0 +1,24 @@
@props([
'compact' => false,
'handler' => 'dispatch',
])
@auth
<button type="button"
@if ($handler === 'openAfia')
@click="openAfia()"
@else
@click="$dispatch('afia-open')"
@endif
@class([
'inline-flex shrink-0 items-center justify-center gap-2 rounded-xl bg-gradient-to-r from-indigo-700 via-blue-600 to-emerald-400 text-sm font-semibold text-white shadow-sm transition hover:opacity-95',
'h-9 w-9 px-0 lg:h-auto lg:w-auto lg:px-4 lg:py-2' => $compact,
'px-3 py-2 lg:px-4' => ! $compact,
])
aria-label="Open Afia AI assistant">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M12 2.25c.414 0 .75.336.75.75a5.25 5.25 0 0 0 5.25 5.25.75.75 0 0 1 0 1.5A5.25 5.25 0 0 0 12.75 15a.75.75 0 0 1-1.5 0A5.25 5.25 0 0 0 6 9.75a.75.75 0 0 1 0-1.5A5.25 5.25 0 0 0 11.25 3a.75.75 0 0 1 .75-.75Zm6.75 11.25a.75.75 0 0 1 .75.75A2.25 2.25 0 0 0 21.75 16.5a.75.75 0 0 1 0 1.5A2.25 2.25 0 0 0 19.5 20.25a.75.75 0 0 1-1.5 0A2.25 2.25 0 0 0 15.75 18a.75.75 0 0 1 0-1.5A2.25 2.25 0 0 0 18 14.25a.75.75 0 0 1 .75-.75ZM5.25 14.25a.75.75 0 0 1 .75.75 3 3 0 0 0 3 3 .75.75 0 0 1 0 1.5 3 3 0 0 0-3 3 .75.75 0 0 1-1.5 0 3 3 0 0 0-3-3 .75.75 0 0 1 0-1.5 3 3 0 0 0 3-3 .75.75 0 0 1 .75-.75Z"/>
</svg>
<span @class(['sr-only lg:not-sr-only lg:inline' => $compact, 'hidden sm:inline' => ! $compact])>AI</span>
</button>
@endauth
@@ -0,0 +1,10 @@
@php
$subtitle = $mobileTopbarSubtitle ?? null;
$title = $mobileTopbarTitle ?? 'Overview';
@endphp
<div class="min-w-0 flex-1 lg:hidden">
@if ($subtitle)
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-slate-400">{{ $subtitle }}</p>
@endif
<h1 class="truncate text-base font-semibold text-slate-900">{{ $title }}</h1>
</div>
+2 -9
View File
@@ -4,6 +4,7 @@
<button @click="sidebarOpen = !sidebarOpen" class="lg:hidden shrink-0 text-slate-500 hover:text-slate-700">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/></svg>
</button>
@include('partials.mobile-topbar-title')
{{-- Search hosting accounts, domains, orders --}}
<div class="relative hidden w-full max-w-md lg:block" x-data="topbarSearch({ searchUrl: @js(route('servers.search')) })" @click.outside="open = false" @keydown.escape.window="open = false">
<div class="relative">
@@ -81,15 +82,7 @@
</form>
</div>
</div>
{{-- Afia (AI) opens the in-app assistant. Matches the platform AI button. --}}
<button type="button" @click="$dispatch('afia-open')"
class="inline-flex items-center gap-2 rounded-xl bg-gradient-to-r from-indigo-700 via-blue-600 to-emerald-400 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:opacity-95">
<svg class="h-4 w-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2.25c.414 0 .75.336.75.75a5.25 5.25 0 0 0 5.25 5.25.75.75 0 0 1 0 1.5A5.25 5.25 0 0 0 12.75 15a.75.75 0 0 1-1.5 0A5.25 5.25 0 0 0 6 9.75a.75.75 0 0 1 0-1.5A5.25 5.25 0 0 0 11.25 3a.75.75 0 0 1 .75-.75Zm6.75 11.25a.75.75 0 0 1 .75.75A2.25 2.25 0 0 0 21.75 16.5a.75.75 0 0 1 0 1.5A2.25 2.25 0 0 0 19.5 20.25a.75.75 0 0 1-1.5 0A2.25 2.25 0 0 0 15.75 18a.75.75 0 0 1 0-1.5A2.25 2.25 0 0 0 18 14.25a.75.75 0 0 1 .75-.75ZM5.25 14.25a.75.75 0 0 1 .75.75 3 3 0 0 0 3 3 .75.75 0 0 1 0 1.5 3 3 0 0 0-3 3 .75.75 0 0 1-1.5 0 3 3 0 0 0-3-3 .75.75 0 0 1 0-1.5 3 3 0 0 0 3-3 .75.75 0 0 1 .75-.75Z"/>
</svg>
<span>AI</span>
</button>
@include('partials.afia-button', ['compact' => true])
@else
<a href="{{ route('login') }}" class="rounded-lg bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-700">Sign in</a>
@endauth