From e2faaa0c1d313c5ba00c24c4ece862f41f881eef Mon Sep 17 00:00:00 2001 From: isaacclad Date: Sun, 28 Jun 2026 12:03:49 +0000 Subject: [PATCH] Add mobile bottom nav and hide header avatar on small screens. Frontdesk now matches the Ladill mobile pattern: app name in the header, Home/Search/Notifications/Profile in the bottom bar with a profile sheet. Co-authored-by: Cursor --- app/Providers/AppServiceProvider.php | 4 + app/Support/MobileTopbar.php | 19 +++ app/Support/UserProfileMenu.php | 35 +++-- config/mobile-topbar.php | 5 + .../views/components/app-layout.blade.php | 27 ++++ .../partials/mobile-bottom-nav.blade.php | 129 ++++++++++++++++++ .../partials/mobile-topbar-title.blade.php | 6 + .../partials/topbar-desktop-widgets.blade.php | 6 +- resources/views/partials/topbar.blade.php | 4 +- 9 files changed, 218 insertions(+), 17 deletions(-) create mode 100644 app/Support/MobileTopbar.php create mode 100644 config/mobile-topbar.php create mode 100644 resources/views/partials/mobile-bottom-nav.blade.php create mode 100644 resources/views/partials/mobile-topbar-title.blade.php diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 940edc1..4d0d60f 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -48,5 +48,9 @@ class AppServiceProvider extends ServiceProvider $view->with('isPro', $isPro); }); + + View::composer(['partials.topbar'], function ($view) { + $view->with(\App\Support\MobileTopbar::resolve()); + }); } } diff --git a/app/Support/MobileTopbar.php b/app/Support/MobileTopbar.php new file mode 100644 index 0000000..cc91c71 --- /dev/null +++ b/app/Support/MobileTopbar.php @@ -0,0 +1,19 @@ + $title, + ]; + } +} diff --git a/app/Support/UserProfileMenu.php b/app/Support/UserProfileMenu.php index 268fcaf..9461caf 100644 --- a/app/Support/UserProfileMenu.php +++ b/app/Support/UserProfileMenu.php @@ -5,14 +5,14 @@ namespace App\Support; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Support\Facades\Route; -/** - * Standard signed-in profile links for Ladill product apps (identical across - * the suite). Points at the account/home portals, not app-local routes. - * - * @return list> - */ class UserProfileMenu { + /** + * Standard signed-in profile links for Ladill product apps. + * Not used by Ladill Mail (mail.ladill.com) — that app keeps a mailbox-specific menu. + * + * @return list> + */ public static function items(?Authenticatable $user = null): array { $user ??= auth()->user(); @@ -43,8 +43,6 @@ class UserProfileMenu ]; } - // Wallet balance peek — sits directly after Billing (only when this - // app exposes a balance endpoint). if (Route::has((string) config('billing.wallet_balance_route', 'wallet.balance'))) { $items[] = ['type' => 'wallet']; } @@ -79,10 +77,13 @@ class UserProfileMenu return ladill_home_url($path); } - $platform = (string) config('app.platform_domain', parse_url((string) config('app.url', ''), PHP_URL_HOST) ?: ''); - $domain = $platform !== '' ? 'home.'.$platform : (string) config('app.account_domain'); + $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($domain, $path); + return self::absoluteUrl((string) $domain, $path); } if (function_exists('ladill_account_url')) { @@ -92,7 +93,11 @@ class UserProfileMenu return self::absoluteUrl((string) config('app.account_domain'), $path); } - /** @param array{label?: string, path?: string, host?: string} $link */ + /** + * Hide a menu link when the signed-in user is already on that destination. + * + * @param array{label?: string, path?: string, host?: string} $link + */ private static function isCurrentMenuLink(array $link, string $href): bool { if (app()->runningInConsole() && ! app()->runningUnitTests()) { @@ -140,7 +145,11 @@ class UserProfileMenu { $base = 'https://'.trim($domain, '/'); - return $path === '' ? $base : $base.'/'.ltrim($path, '/'); + if ($path === '') { + return $base; + } + + return $base.'/'.ltrim($path, '/'); } private static function userIsAdmin(Authenticatable $user): bool diff --git a/config/mobile-topbar.php b/config/mobile-topbar.php new file mode 100644 index 0000000..4fba3b8 --- /dev/null +++ b/config/mobile-topbar.php @@ -0,0 +1,5 @@ + 'Frontdesk', +]; diff --git a/resources/views/components/app-layout.blade.php b/resources/views/components/app-layout.blade.php index 07d64d3..f8896c6 100644 --- a/resources/views/components/app-layout.blade.php +++ b/resources/views/components/app-layout.blade.php @@ -28,6 +28,33 @@ + @auth + @php + $navUser = auth()->user(); + $navInitials = collect(explode(' ', trim((string) $navUser?->name))) + ->filter()->take(2) + ->map(fn ($part) => strtoupper(substr($part, 0, 1))) + ->implode(''); + $navAvatarUrl = $navUser && method_exists($navUser, 'avatarUrl') + ? $navUser->avatarUrl() + : ($navUser?->avatar_url ?? null); + @endphp + @include('partials.mobile-bottom-nav', [ + 'homeUrl' => route('frontdesk.dashboard'), + 'homeActive' => request()->routeIs('frontdesk.dashboard'), + 'searchUrl' => route('frontdesk.visitors.index'), + 'searchActive' => request()->routeIs('frontdesk.visitors.*'), + 'notificationsUrl' => route('notifications.index'), + 'notificationsActive' => request()->routeIs('notifications.*'), + 'unreadUrl' => route('notifications.unread'), + 'profileActive' => false, + 'profileName' => $navUser?->name ?? '', + 'profileSubtitle' => $navUser?->email ?? '', + 'profileMenuItems' => \App\Support\UserProfileMenu::items($navUser), + 'avatarUrl' => $navAvatarUrl, + 'initials' => $navInitials !== '' ? $navInitials : 'U', + ]) + @endauth @include('partials.wallet-topup-modal', ['openOnLoad' => (bool) session('topup_required')]) @include('partials.afia') diff --git a/resources/views/partials/mobile-bottom-nav.blade.php b/resources/views/partials/mobile-bottom-nav.blade.php new file mode 100644 index 0000000..499483f --- /dev/null +++ b/resources/views/partials/mobile-bottom-nav.blade.php @@ -0,0 +1,129 @@ +@php + $showSearch = isset($searchUrl) && $searchUrl !== null && $searchUrl !== '#'; + $gridCols = match (true) { + ! empty($centerCompose) => $showSearch ? 'grid-cols-5' : 'grid-cols-4', + default => $showSearch ? 'grid-cols-4' : 'grid-cols-3', + }; + $avatarUrl = $avatarUrl ?? null; + $initials = $initials ?? 'U'; + $notificationsUrl = $notificationsUrl ?? '#'; + $unreadUrl = $unreadUrl ?? null; + $profileUrl = $profileUrl ?? '#'; + $profileName = trim((string) ($profileName ?? '')); + $profileSubtitle = trim((string) ($profileSubtitle ?? '')); + $profileMenuItems = $profileMenuItems ?? []; + if ($profileMenuItems === [] && $profileUrl !== '#') { + $profileMenuItems = [['type' => 'link', 'label' => 'Profile', 'href' => $profileUrl]]; + } + $navActive = fn (bool $active) => $active ? 'text-indigo-600' : 'text-slate-600'; +@endphp +
+ + + {{-- Profile menu bottom sheet (matches desktop avatar dropdown) --}} +
+
+ +
+
+ +
+ + @if ($profileName !== '' || $profileSubtitle !== '') +
+ @if ($avatarUrl) + + @else + {{ $initials }} + @endif +
+ @if ($profileName !== '') +

{{ $profileName }}

+ @endif + @if ($profileSubtitle !== '') +

{{ $profileSubtitle }}

+ @endif +
+
+ @endif + + @include('partials.user-profile-menu', [ + 'items' => $profileMenuItems, + 'variant' => 'sheet', + 'onNavigate' => 'profileOpen = false', + ]) +
+
+
diff --git a/resources/views/partials/mobile-topbar-title.blade.php b/resources/views/partials/mobile-topbar-title.blade.php new file mode 100644 index 0000000..ff10b4a --- /dev/null +++ b/resources/views/partials/mobile-topbar-title.blade.php @@ -0,0 +1,6 @@ +@php + $title = $mobileTopbarTitle ?? 'Ladill'; +@endphp +
+

{{ $title }}

+
diff --git a/resources/views/partials/topbar-desktop-widgets.blade.php b/resources/views/partials/topbar-desktop-widgets.blade.php index e6d69bd..5e00826 100644 --- a/resources/views/partials/topbar-desktop-widgets.blade.php +++ b/resources/views/partials/topbar-desktop-widgets.blade.php @@ -1,5 +1,7 @@ {{-- - Standard desktop top-right widgets: notifications → launcher → divider → avatar dropdown. + Top-right header widgets: + Mobile — Afia + launcher only (profile/notifications live in bottom nav). + Desktop — Afia → notifications → launcher → divider → avatar dropdown. --}} @php $topbarUser = $user ?? auth()->user(); @@ -21,7 +23,7 @@ -
+