Unify logged-in profile dropdown with platform account menu.
Deploy Ladill Hosting / deploy (push) Successful in 46s

Wire UserProfileMenu and shared Blade partial into topbar, mobile nav, and layouts so menu items match the account portal (Ladill Mail excluded).

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-06-19 09:33:27 +00:00
co-authored by Cursor
parent d85a7db057
commit 53f38ba794
6 changed files with 177 additions and 34 deletions
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace App\Support;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Route;
class UserProfileMenu
{
/** @return list<array<string, mixed>> */
public static function items(?Authenticatable $user = null): array
{
$user ??= auth()->user();
if (! $user) {
return [];
}
$items = [];
foreach ([
['label' => 'Home', 'path' => '', 'host' => 'home'],
['label' => 'Profile', 'path' => 'profile'],
['label' => 'Account Settings', 'path' => 'account-settings'],
['label' => 'Dashboard', 'path' => 'dashboard'],
['label' => 'Billing', 'path' => 'billing'],
] as $link) {
$items[] = [
'type' => 'link',
'label' => $link['label'],
'href' => self::platformUrl($link['host'] ?? 'account', $link['path']),
];
}
if (self::userIsAdmin($user)) {
$items[] = [
'type' => 'link',
'label' => 'Admin',
'href' => self::platformUrl('account', 'admin'),
];
}
if (Route::has('logout')) {
$items[] = [
'type' => 'logout',
'label' => 'Logout',
'action' => route('logout'),
];
}
return $items;
}
private static function platformUrl(string $host, string $path = ''): string
{
if ($host === 'home') {
if (function_exists('ladill_home_url')) {
return ladill_home_url($path);
}
$domain = config('app.home_domain');
if (! $domain) {
$platform = (string) config('app.platform_domain', parse_url((string) config('app.url', ''), PHP_URL_HOST) ?: '');
$domain = $platform !== '' ? 'home.'.$platform : (string) config('app.account_domain');
}
return self::absoluteUrl((string) $domain, $path);
}
if (function_exists('ladill_account_url')) {
return ladill_account_url($path);
}
return self::absoluteUrl((string) config('app.account_domain'), $path);
}
private static function absoluteUrl(string $domain, string $path = ''): string
{
$base = 'https://'.trim($domain, '/');
if ($path === '') {
return $base;
}
return $base.'/'.ltrim($path, '/');
}
private static function userIsAdmin(Authenticatable $user): bool
{
if (method_exists($user, 'isAdmin')) {
return $user->isAdmin();
}
return (bool) ($user->is_admin ?? false);
}
}
+1 -2
View File
@@ -46,8 +46,7 @@
'profileActive' => false, 'profileActive' => false,
'profileName' => $navUser?->name ?? '', 'profileName' => $navUser?->name ?? '',
'profileSubtitle' => $navUser?->email ?? '', 'profileSubtitle' => $navUser?->email ?? '',
'profileMenuItems' => [ 'profileMenuItems' => \App\Support\UserProfileMenu::items($navUser),
['type' => 'link', 'label' => 'Home', 'href' => ladill_home_url()],
['type' => 'link', 'label' => 'Profile', 'href' => ladill_account_url('profile')], ['type' => 'link', 'label' => 'Profile', 'href' => ladill_account_url('profile')],
['type' => 'link', 'label' => 'Account Settings', 'href' => ladill_account_url('account-settings')], ['type' => 'link', 'label' => 'Account Settings', 'href' => ladill_account_url('account-settings')],
['type' => 'link', 'label' => 'Dashboard', 'href' => route('email.dashboard')], ['type' => 'link', 'label' => 'Dashboard', 'href' => route('email.dashboard')],
+1 -2
View File
@@ -45,8 +45,7 @@
'profileActive' => false, 'profileActive' => false,
'profileName' => $navUser?->name ?? '', 'profileName' => $navUser?->name ?? '',
'profileSubtitle' => $navUser?->email ?? '', 'profileSubtitle' => $navUser?->email ?? '',
'profileMenuItems' => [ 'profileMenuItems' => \App\Support\UserProfileMenu::items($navUser),
['type' => 'link', 'label' => 'Home', 'href' => ladill_home_url()],
['type' => 'link', 'label' => 'Profile', 'href' => ladill_account_url('profile')], ['type' => 'link', 'label' => 'Profile', 'href' => ladill_account_url('profile')],
['type' => 'link', 'label' => 'Account Settings', 'href' => ladill_account_url('account-settings')], ['type' => 'link', 'label' => 'Account Settings', 'href' => ladill_account_url('account-settings')],
['type' => 'link', 'label' => 'Dashboard', 'href' => route('hosting.dashboard')], ['type' => 'link', 'label' => 'Dashboard', 'href' => route('hosting.dashboard')],
@@ -113,25 +113,11 @@
</div> </div>
@endif @endif
<div class="space-y-1 p-2 pb-4"> @include('partials.user-profile-menu', [
@foreach ($profileMenuItems as $item) 'items' => $profileMenuItems,
@if (($item['type'] ?? 'link') === 'link') 'variant' => 'sheet',
<a href="{{ $item['href'] }}" 'onNavigate' => 'profileOpen = false',
@click="profileOpen = false" ])
class="block rounded-xl px-4 py-3 text-sm font-medium text-slate-700 transition hover:bg-slate-100">
{{ $item['label'] }}
</a>
@elseif (($item['type'] ?? '') === 'logout')
<form method="POST" action="{{ $item['action'] }}" class="border-t border-slate-100 pt-2">
@csrf
<button type="submit"
class="w-full rounded-xl px-4 py-3 text-left text-sm font-medium text-rose-600 transition hover:bg-rose-50">
{{ $item['label'] }}
</button>
</form>
@endif
@endforeach
</div>
</div> </div>
</div> </div>
</div> </div>
+5 -10
View File
@@ -78,16 +78,11 @@
@endif @endif
<svg class="h-4 w-4 text-slate-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m19 9-7 7-7-7"/></svg> <svg class="h-4 w-4 text-slate-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="m19 9-7 7-7-7"/></svg>
</button> </button>
<div x-show="profileOpen" x-cloak x-transition class="absolute right-0 z-50 mt-2 w-52 rounded-xl border border-slate-200 bg-white p-1 shadow-lg"> <div x-show="profileOpen" x-cloak x-transition @click.outside="profileOpen = false"
<a href="{{ $home }}" class="block rounded-lg px-3 py-2 text-sm text-slate-700 hover:bg-slate-100">Home</a> class="absolute right-0 z-50 mt-2 w-56 rounded-xl border border-slate-200 bg-white shadow-lg">
<a href="{{ $acct }}/profile" class="block rounded-lg px-3 py-2 text-sm text-slate-700 hover:bg-slate-100">Profile</a> @include('partials.user-profile-menu', [
<a href="{{ $acct }}/account-settings" class="block rounded-lg px-3 py-2 text-sm text-slate-700 hover:bg-slate-100">Account Settings</a> 'items' => \App\Support\UserProfileMenu::items($user),
<a href="{{ route('hosting.dashboard') }}" class="block rounded-lg px-3 py-2 text-sm text-slate-700 hover:bg-slate-100">Dashboard</a> ])
<a href="{{ ladill_account_url('billing') }}" class="block rounded-lg px-3 py-2 text-sm text-slate-700 hover:bg-slate-100">Billing</a>
<div class="my-1 border-t border-slate-100"></div>
<form method="POST" action="{{ route('logout') }}">@csrf
<button type="submit" class="w-full rounded-lg px-3 py-2 text-left text-sm text-rose-600 hover:bg-rose-50">Logout</button>
</form>
</div> </div>
</div> </div>
@include('partials.afia-button', ['compact' => true]) @include('partials.afia-button', ['compact' => true])
@@ -0,0 +1,68 @@
@props([
'items' => [],
'user' => null,
'showUser' => false,
'variant' => 'dropdown',
'onNavigate' => null,
])
@php
$linkClass = match ($variant) {
'dark' => 'block rounded-md px-3 py-2 text-sm text-slate-200 hover:bg-slate-800',
'sheet' => 'block rounded-xl px-4 py-3 text-sm font-medium text-slate-700 transition hover:bg-slate-100',
default => 'block rounded-lg px-3 py-2 text-sm text-slate-700 hover:bg-slate-100',
};
$logoutClass = match ($variant) {
'dark' => 'w-full rounded-md px-3 py-2 text-left text-sm text-rose-300 hover:bg-rose-950/40',
'sheet' => 'w-full rounded-xl px-4 py-3 text-left text-sm font-medium text-rose-600 transition hover:bg-rose-50',
default => 'w-full rounded-lg px-3 py-2 text-left text-sm text-rose-600 hover:bg-rose-50',
};
$logoutWrapperClass = match ($variant) {
'sheet' => 'border-t border-slate-100 pt-2',
'dark' => '',
default => '',
};
$dividerClass = match ($variant) {
'dark' => 'my-1 border-t border-slate-800',
default => 'my-1 border-t border-slate-100',
};
$containerClass = match ($variant) {
'dark' => 'mt-2 rounded-lg border border-slate-800 bg-slate-900 p-1',
'sheet' => 'space-y-1 p-2 pb-4',
default => 'p-1',
};
@endphp
<div {{ $attributes->merge(['class' => $containerClass]) }}>
@if ($showUser && $user)
<div class="px-3 py-2">
<p class="truncate text-sm font-semibold text-slate-900">{{ $user->name ?? 'Your account' }}</p>
<p class="truncate text-xs text-slate-400">{{ $user->email }}</p>
</div>
<div class="{{ $dividerClass }}"></div>
@endif
@foreach ($items as $item)
@if (($item['type'] ?? 'link') === 'link')
<a href="{{ $item['href'] }}"
class="{{ $linkClass }}"
@if ($onNavigate) @click="{{ $onNavigate }}" @endif>
{{ $item['label'] }}
</a>
@elseif (($item['type'] ?? '') === 'logout')
@if ($variant !== 'sheet')
<div class="{{ $dividerClass }}"></div>
@endif
<form method="POST" action="{{ $item['action'] }}" class="{{ $logoutWrapperClass }}">
@csrf
<button type="submit" class="{{ $logoutClass }}">
{{ $item['label'] }}
</button>
</form>
@endif
@endforeach
</div>