Files
timebank-cc-public/resources/views/navigation-menu.blade.php
T

813 lines
47 KiB
PHP

@php
$navGuard = session('active_guard', 'web');
$navUser = auth($navGuard)->user();
$activeProfileUnreadCount = 0;
if ($navUser) {
$activeProfileUnreadCount = \Illuminate\Support\Facades\DB::table('wirechat_participants as p')
->join('wirechat_conversations as c', 'p.conversation_id', '=', 'c.id')
->where('p.participantable_id', $navUser->id)
->where('p.participantable_type', $navUser->getMorphClass())
->whereNull('p.deleted_at')
->where(function ($q) {
$q->whereNull('p.conversation_read_at')
->orWhereColumn('c.updated_at', '>', 'p.conversation_read_at');
})
->whereExists(function ($q) use ($navUser) {
$q->select(\Illuminate\Support\Facades\DB::raw(1))
->from('wirechat_messages as m')
->whereColumn('m.conversation_id', 'p.conversation_id')
->whereNull('m.deleted_at')
->where(function ($q2) use ($navUser) {
$q2->where('m.sendable_id', '!=', $navUser->id)
->orWhere('m.sendable_type', '!=', $navUser->getMorphClass());
})
->where(function ($q3) {
$q3->whereNull('p.conversation_read_at')
->orWhereColumn('m.created_at', '>', 'p.conversation_read_at');
});
})
->count();
}
// Check if any non-active profile (org/bank/admin) has unread messages
// Used to initialize the hamburger dot before Livewire mounts SwitchProfileUnreadIndicator
$switchProfileHasUnread = false;
$webUser = auth('web')->user();
if ($webUser) {
$activeProfileType = session('activeProfileType');
$activeProfileId = session('activeProfileId');
$userWithRelations = \App\Models\User::with(['organizations', 'banksManaged', 'admins'])->find($webUser->id);
if ($userWithRelations) {
$profilePairs = [];
foreach ($userWithRelations->organizations->filter(fn($o) => !$o->deleted_at || $o->deleted_at > now()) as $org) {
if (!($activeProfileType === 'App\\Models\\Organization' && $activeProfileId == $org->id)) {
$profilePairs[] = ['type' => 'App\Models\Organization', 'id' => $org->id];
}
}
foreach ($userWithRelations->banksManaged->filter(fn($b) => !$b->deleted_at || $b->deleted_at > now()) as $bank) {
if (!($activeProfileType === 'App\\Models\\Bank' && $activeProfileId == $bank->id)) {
$profilePairs[] = ['type' => 'App\Models\Bank', 'id' => $bank->id];
}
}
foreach ($userWithRelations->admins->filter(fn($a) => !$a->deleted_at || $a->deleted_at > now()) as $admin) {
if (!($activeProfileType === 'App\\Models\\Admin' && $activeProfileId == $admin->id)) {
$profilePairs[] = ['type' => 'App\Models\Admin', 'id' => $admin->id];
}
}
if (!empty($profilePairs)) {
$typeIds = collect($profilePairs)->groupBy('type')->map(fn($g) => $g->pluck('id')->toArray())->toArray();
foreach ($typeIds as $modelClass => $ids) {
$found = \Illuminate\Support\Facades\DB::table('wirechat_participants as p')
->join('wirechat_conversations as c', 'p.conversation_id', '=', 'c.id')
->where('p.participantable_type', $modelClass)
->whereIn('p.participantable_id', $ids)
->whereNull('p.deleted_at')
->whereExists(function ($q) {
$q->select(\Illuminate\Support\Facades\DB::raw(1))
->from('wirechat_messages as m')
->whereColumn('m.conversation_id', 'p.conversation_id')
->whereNull('m.deleted_at')
->where(fn($q2) => $q2->whereNull('p.conversation_read_at')->orWhereColumn('m.created_at', '>', 'p.conversation_read_at'))
->where(fn($q3) => $q3->where('m.sendable_id', '!=', \Illuminate\Support\Facades\DB::raw('p.participantable_id'))->orWhere('m.sendable_type', '!=', \Illuminate\Support\Facades\DB::raw('p.participantable_type')));
})
->exists();
if ($found) {
$switchProfileHasUnread = true;
break;
}
}
}
}
}
@endphp
@php
// Admin management links. Each keeps its own visibility check; for Admin profiles they are
// grouped into the Editorial and Administration dropdowns, other profiles keep plain links.
$navWebUser = Auth::guard('web')->user();
$navIsAdmin = strtolower(getActiveProfileType() ?? '') === 'admin';
$navCan = fn (string $permission) => (bool) $navWebUser?->can($permission);
$navShowProfiles = ($navIsAdmin && $navCan('access profiles'))
|| (getActiveProfileType() === 'Bank' && optional(getActiveProfile())->level === 0)
|| $navCan('bank manage accounts');
$navLink = fn (string $label, string $route, bool $visible) => ['label' => __($label), 'route' => $route, 'visible' => $visible];
$navGroups = $navIsAdmin ? [
__('Editorial') => [
$navLink('Posts', 'posts.manage', $navCan('access posts')),
$navLink('Polls', 'polls.manage', $navCan('access polls')),
$navLink('Calls', 'calls.manage', true),
$navLink('Mailings', 'mailings.index', $navCan('access mailings')),
$navLink('Categories', 'categories.manage', $navCan('access categories')),
$navLink('Tags', 'tags.manage', $navCan('access tags')),
],
__('Administration') => [
$navLink('Profiles', 'profiles.manage', $navShowProfiles),
$navLink('Roles', 'roles.manage', $navCan('access roles')),
],
] : [];
// Drop hidden links, and groups left empty
$navGroups = array_filter(array_map(fn ($links) => array_values(array_filter($links, fn ($link) => $link['visible'])), $navGroups));
@endphp
<nav id="navigation-menu"
class="shadow-md border-b border-theme-primary bg-theme-background fixed top-0 left-0 right-0 z-[80] w-full"
x-data="{ open: false }"
wire:ignore.self>
@auth
<!-- Primary Navigation Menu -->
<div class="mx-auto max-w-7xl px-4 lg:px-8 w-full">
<div class="flex h-0 md:h-16 items-center justify-between min-w-0">
<!-- Logo -->
<div class="hidden md:flex shrink-0 items-center mr-4">
<a href="{{ route('main') }}">
<x-jetstream.application-mark class="block h-7 lg:h-9 w-auto" style="max-width: 80px;" logoTitle="Home"/>
</a>
</div>
<!-- Navigation Links -->
<div class="hidden items-center space-x-4 lg:space-x-8 md:-my-px md:ml-6 md:flex min-w-0 flex-shrink overflow-hidden">
<!-- Only if activeProfile has the accounts relation and has any accounts -->
@if (getActiveProfile()
&& method_exists(getActiveProfile(), 'accounts') &&
getActiveProfile()->accounts()->notRemoved()->count() > 0)
<!-- Only if activeProfile has active accounts -->
@if (getActiveProfile()->accounts()->active()->count() > 0)
@if (canActiveProfileCreatePayments())
<x-jetstream.nav-link :active="request()->routeIs('pay')" href="{{ route('pay') }}">
{{ __('Pay') }}
</x-jetstream.nav-link>
@else
<span class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-400 cursor-not-allowed select-none">
{{ __('Pay') }}
</span>
@endif
@endif
<x-jetstream.nav-link :active="request()->routeIs('transactions')" href="{{ route('transactions') }}">
{{ __('Transactions') }}
</x-jetstream.nav-link>
<x-jetstream.nav-link :active="request()->routeIs('reports')" href="{{ route('reports') }}">
{{ __('Reports') }}
</x-jetstream.nav-link>
<x-jetstream.nav-link :active="request()->routeIs('contacts')" href="{{ route('contacts') }}">
{{ __('Contacts') }}
</x-jetstream.nav-link>
@endif
@if ($navIsAdmin)
@foreach ($navGroups as $groupLabel => $groupLinks)
<x-nav-dropdown :label="$groupLabel" :active="collect($groupLinks)->contains(fn ($link) => request()->routeIs($link['route']))">
@foreach ($groupLinks as $link)
<x-jetstream.dropdown-link href="{{ route($link['route']) }}" role="menuitem"
@class(['font-semibold bg-theme-surface' => request()->routeIs($link['route'])])>
{{ $link['label'] }}
</x-jetstream.dropdown-link>
@endforeach
</x-nav-dropdown>
@endforeach
@else
@if ($navShowProfiles)
<x-jetstream.nav-link :active="request()->routeIs('profiles.manage')" href="{{ route('profiles.manage') }}">
{{ __('Profiles') }}
</x-jetstream.nav-link>
@endif
<x-jetstream.nav-link :active="request()->routeIs('calls.manage')" href="{{ route('calls.manage') }}">
{{ __('Calls') }}
</x-jetstream.nav-link>
@endif
{{-- Platform statistics: Admin and Bank profiles holding the permission --}}
@if (\Illuminate\Support\Facades\Gate::allows('access stats') || \Illuminate\Support\Facades\Gate::allows('org stats'))
<x-jetstream.nav-link :active="request()->routeIs('stats')" href="{{ route('stats') }}">
{{ __('Statistics') }}
</x-jetstream.nav-link>
@endif
{{-- @usercan('manage permissions')
@profile('admin')
<x-jetstream.nav-link :active="request()->routeIs('permissions.manage')" href="{{ route('permissions.manage') }}">
{{ __('Permissions') }}
</x-jetstream.nav-link>
@endprofile
@endusercan --}}
<!-- Main Search Bar -->
@livewire('main-search-bar')
</div>
<div class="hidden md:ml-6 md:flex md:items-center flex-shrink-0 gap-1">
<div class="items-end ml-3 flex min-w-0 flex-shrink">
<!-- Wirechat Messenger -->
@php
$messengerType = session('activeProfileType');
$showMessenger = match(true) {
// The chats component needs an active profile (aborts without one)
$messengerType === null => false,
$messengerType === 'App\Models\Organization' || $messengerType === 'App\Models\Bank' => Gate::check('org messenger'),
$messengerType === 'App\Models\Admin' => Gate::check('admin messenger'),
default => true,
};
@endphp
@if ($showMessenger)
<x-jetstream.dropdown align="right" width="60">
<x-slot name="trigger">
<span class="inline-flex rounded-md">
<button id="messenger-nav-button" class="relative inline-flex items-center rounded-md bg-theme-background pr-2 lg:pr-3 py-2 text-sm font-medium leading-4 text-theme-primary hover:text-theme-secondary focus:outline-none active:bg-theme-surface">
<x-icon name="chat-bubble-bottom-center-text" class="w-5 h-5" outline />
<div class="ml-3 hidden lg:inline">
{{ __('Messenger') }}
</div>
<span id="nav-unread-dot-main"
class="absolute top-1 right-0 h-2 w-2 rounded-full bg-red-700"
style="display: none;">
</span>
</button>
</span>
</x-slot>
<!-- Wirechat chats dropdown -->
<x-slot name="content">
{{-- Capped at 3/4 of the screen height. As a flex column the chat list
shrinks to fit, and its own <main> (overflow-y-auto) scrolls. --}}
<div id="messenger-dropdown-content" class="w-96 z-50 max-h-[75vh] flex flex-col">
<livewire:wirechat.chats
:title="''"
:showHomeRouteButton="false"
/>
</div>
</x-slot>
</x-jetstream.dropdown>
@endif
<!-- Language selector ---->
<!-- This changes the session('locale') and by the Middleware StoreUserLangPreference this locale
is stored as the lang_preference in the user table -->
@php
$languages = Illuminate\Support\Facades\DB::table('languages')->orderBy('lang_code', 'asc')->get();
$supportedLocales = LaravelLocalization::getSupportedLocales();
// Sort the supported locales alphabetically by the language name
uasort($supportedLocales, function ($a, $b) {
return strcmp($a['native'], $b['native']);
});
@endphp
<x-jetstream.dropdown align="right" width="48">
<x-slot name="trigger">
<span class="inline-flex rounded-md">
<button class="inline-flex items-center rounded-md bg-theme-background px-2 lg:px-3 py-2 text-sm font-medium leading-4 text-theme-primary transition hover:text-theme-secondary focus:outline-none active:bg-theme-surface"
type="button">
<x-icon name="globe-alt" class="w-6 h-5" outline />
</button>
</span>
</x-slot>
<!-- Language select options -->
<x-slot name="content">
<div class="w-60">
@php
$profileTypeBaseKey = null;
if (request()->route() && request()->route()->getName() === 'profile.show_by_type_and_id') {
$currentType = request()->route('type');
foreach (['user', 'organization', 'bank'] as $baseKey) {
if (strtolower($currentType) === strtolower(__($baseKey))) {
$profileTypeBaseKey = $baseKey;
break;
}
}
}
@endphp
@foreach ($supportedLocales as $localeCode => $properties)
@php
$language = $languages->firstWhere('lang_code', strtolower($localeCode));
if ($profileTypeBaseKey) {
$translatedType = Lang::get($profileTypeBaseKey, [], $localeCode);
$localizedUrl = LaravelLocalization::getURLFromRouteNameTranslated($localeCode, 'routes.profile.show', ['type' => $translatedType, 'id' => request()->route('id')]);
} else {
$localizedUrl = LaravelLocalization::getLocalizedURL($localeCode, null, [], true);
}
@endphp
@if ($language)
@php $isActive = strtolower($localeCode) === strtolower(app()->getLocale()); @endphp
<a class="block pl-3 pr-4 py-2 border-l-4 text-sm leading-5 text-theme-primary hover:bg-theme-surface focus:outline-none focus:bg-theme-surface transition {{ $isActive ? 'border-black bg-theme-surface font-semibold' : 'border-transparent' }}"
href="{{ $localizedUrl }}"
hreflang="{{ $localeCode }}" rel="alternate">
{{ $language->flag }}
<span class="ml-3 text-theme-primary">{{ Lang::get($language->name, [], $localeCode) }}</span>
</a>
@endif
@endforeach
</div>
</x-slot>
</x-jetstream.dropdown>
</div>
<!-- Settings / Profile Dropdown -->
<div class="relative ml-2 shrink-0">
<x-jetstream.dropdown align="right" width="48">
<x-slot name="trigger">
@if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
<button
class="border-none hover:border-none focus:border-grey-900 flex items-center rounded-full text-sm transition focus:border-none relative">
<a class="hidden lg:inline items-center pr-2 pt-1 border-theme-primary text-sm font-medium leading-5 text-theme-primary hover:text-theme-secondary focus:outline-none focus:border-theme-primary transition"
href="#">
{{ \Illuminate\Support\Str::limit(Session('activeProfileName'), 25, '...') }}
</a>
<div class="relative">
<img alt="" class="h-9 w-9 aspect-square rounded-full profile-photo object-cover outline outline-1 outline-offset-1 outline-theme-secondary shrink-0 hover:opacity-80"
src="{{ Storage::url(Session('activeProfilePhoto')) }}" />
@livewire('switch-profile-unread-indicator')
</div>
</button>
@else
<span class="inline-flex rounded-md">
<button class="inline-flex items-center rounded-md border border-transparent bg-theme-background px-3 py-2 text-sm font-medium leading-4 text-theme-secondary transition hover:text-theme-primary focus:outline-none"
type="button">
{{ \Illuminate\Support\Str::limit(Session('activeProfileName'), 25, '...') }}
<svg class="-mr-0.5 ml-2 h-4 w-4" fill="currentColor" viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path clip-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
fill-rule="evenodd" />
</svg>
</button>
</span>
@endif
<span class="badge-pill badge-danger mr-n2 badge" id="nav_mobile_total_count"></span>
</x-slot>
<x-slot name="content">
<!---- Balance Info --->
@if (session('activeProfileType') !== 'App\Models\Admin')
<x-jetstream.dropdown-link href="#" wire:click.prevent="$dispatch('openAccountInfoModal')">
{{ __('Account info') }}
</x-jetstream.dropdown-link>
<div class="border-t border-theme-primary"></div>
@endif
<!---- Switch Profile --->
@if (auth()->guard('web')->user()?->hasOtherProfiles())
<livewire:switch-profile>
@endif
<!---- Edit profile info--->
@if (session('activeProfileType') !== 'App\Models\Admin')
@php $isOrgOrBank = in_array(session('activeProfileType'), ['App\Models\Organization', 'App\Models\Bank']); @endphp
@if (!$isOrgOrBank || Gate::check('org edit-profile'))
<x-jetstream.dropdown-link :active="request()->routeIs('profile.edit')" href="{{ route('profile.edit') }}">
{{ __('Edit profile') }}
</x-jetstream.dropdown-link>
@endif
<!---- Show profile --->
<x-jetstream.dropdown-link href="{{ route('profile.show_active') }}">
{{ __('Show profile') }}
</x-jetstream.dropdown-link>
@else
{{-- Disabled link --}}
<div class="block cursor-default px-4 py-2 text-sm leading-5 text-theme-light">
{{ __('Edit profile') }}
</div>
{{-- Disabled link --}}
<div class="block cursor-default px-4 py-2 text-sm leading-5 text-theme-light">
{{ __('Show profile') }}
</div>
@endif
<!---- Settings --->
@php
$activeType = session('activeProfileType');
$showSettings = match(true) {
$activeType === 'App\Models\Organization' || $activeType === 'App\Models\Bank' => Gate::check('org settings'),
$activeType === 'App\Models\Admin' => Gate::check('admin settings'),
default => true,
};
@endphp
@if ($showSettings)
<x-jetstream.dropdown-link href="{{ route('profile.settings') }}">
{{ __('Settings') }}
</x-jetstream.dropdown-link>
@endif
@if (Laravel\Jetstream\Jetstream::hasApiFeatures())
<x-jetstream.dropdown-link href="{{ route('api-tokens.index') }}">
{{ __('API Tokens') }}
</x-jetstream.dropdown-link>
@endif
<div class="border-t border-theme-primary"></div>
<!-- Log out -->
<form action="{{ route('logout') }}" method="POST" x-data>
@csrf
<x-jetstream.dropdown-link @click.prevent="$root.submit();" href="{{ route('logout') }}">
{{ __('Log out') }}
</x-jetstream.dropdown-link>
</form>
</x-slot>
</x-jetstream.dropdown>
</div>
</div>
</div>
<!-- Hamburger -->
<div class="fixed top-4 right-4 z-50 md:hidden" style="position: fixed !important;">
<button @click="open = ! open"
id="hamburger-menu-button"
class="inline-flex items-center justify-center rounded-md p-2 text-theme-primary transition border border-1 border-theme-primary hover:bg-theme-surface focus:bg-theme-surface focus:text-theme-primary focus:outline-none bg-theme-background shadow-lg relative" style="position: relative !important;">
<svg class="h-6 w-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path :class="{ 'hidden': open, 'inline-flex': !open }" class="inline-flex"
d="M4 6h16M4 12h16M4 18h16" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" />
<path :class="{ 'hidden': !open, 'inline-flex': open }" class="hidden" d="M6 18L18 6M6 6l12 12"
stroke-linecap="round" stroke-linejoin="round" stroke-width="2" />
</svg>
<span id="nav-unread-dot-hamburger"
class="absolute top-1 right-1 h-2 w-2 rounded-full bg-red-700"
style="display: none;">
</span>
</button>
</div>
</div>
<!-- Responsive Navigation Menu -->
<style>
@media (max-width: 374px) {
.responsive-nav-menu {
width: calc(100% - 4px) !important;
max-width: calc(100% - 4px) !important;
right: auto !important;
left: 2px !important;
}
.responsive-nav-menu img {
width: 20px !important;
height: 20px !important;
}
.responsive-nav-menu button {
padding-left: 0 !important;
padding-right: 0.25rem !important;
}
.responsive-nav-menu .switch-profile-container div[x-show] {
left: -150px !important;
right: auto !important;
max-width: calc(100% - 4px) !important;
}
/* Language dropdown - constrain width */
.responsive-nav-menu .w-60 {
width: calc(100% - 20px) !important;
max-width: calc(100% - 20px) !important;
overflow-x: auto !important;
}
}
</style>
<div x-show="open"
x-transition:enter="transition ease-out duration-100"
x-transition:enter-start="opacity-0 scale-0 origin-top-right"
x-transition:enter-end="opacity-100 scale-100 origin-top-right"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 scale-100 origin-top-right"
x-transition:leave-end="opacity-0 scale-0 origin-top-right"
class="md:hidden w-[300px] fixed top-16 right-4 z-40 shadow-xl border border-theme-border rounded-lg bg-theme-background max-h-[calc(100vh-5rem)] overflow-y-auto origin-top-right responsive-nav-menu"
style="display: none;"
@click.away="open = false">
<div class="space-y-1 pt-2">
<x-jetstream.responsive-nav-link :active="request()->routeIs('main')" href="{{ route('main') }}">
{{ __('Main page') }}
</x-jetstream.responsive-nav-link>
<!-- Only if activeProfile has the accounts relation and has any accounts -->
@if (getActiveProfile() && method_exists(getActiveProfile(), 'accounts') && getActiveProfile()->accounts()->notRemoved()->count() > 0)
<!-- Only if activeProfile has active accounts -->
@if (getActiveProfile()->accounts()->active()->count() > 0)
@if (canActiveProfileCreatePayments())
<x-jetstream.responsive-nav-link :active="request()->routeIs('pay')" href="{{ route('pay') }}">
{{ __('Pay') }}
</x-jetstream.responsive-nav-link>
@else
<span class="block pl-3 pr-4 py-2 text-base font-medium text-gray-400 cursor-not-allowed select-none">
{{ __('Pay') }}
</span>
@endif
@endif
<x-jetstream.responsive-nav-link :active="request()->routeIs('transactions')" href="{{ route('transactions') }}">
{{ __('Transactions') }}
</x-jetstream.responsive-nav-link>
<x-jetstream.responsive-nav-link :active="request()->routeIs('reports')" href="{{ route('reports') }}">
{{ __('Reports') }}
</x-jetstream.responsive-nav-link>
<x-jetstream.responsive-nav-link :active="request()->routeIs('contacts')" href="{{ route('contacts') }}">
{{ __('Contacts') }}
</x-jetstream.responsive-nav-link>
@endif
@if ($navIsAdmin)
@foreach ($navGroups as $groupLabel => $groupLinks)
<div class="pt-3 pb-1 pl-4 text-xs font-semibold uppercase tracking-wider text-theme-secondary">{{ $groupLabel }}</div>
@foreach ($groupLinks as $link)
<x-jetstream.responsive-nav-link :active="request()->routeIs($link['route'])" href="{{ route($link['route']) }}">
{{ $link['label'] }}
</x-jetstream.responsive-nav-link>
@endforeach
@endforeach
@else
@if ($navShowProfiles)
<x-jetstream.responsive-nav-link :active="request()->routeIs('profiles.manage')" href="{{ route('profiles.manage') }}">
{{ __('Profiles') }}
</x-jetstream.responsive-nav-link>
@endif
<x-jetstream.responsive-nav-link :active="request()->routeIs('calls.manage')" href="{{ route('calls.manage') }}">
{{ __('Calls') }}
</x-jetstream.responsive-nav-link>
@endif
{{-- Platform statistics: Admin and Bank profiles holding the permission --}}
@if (\Illuminate\Support\Facades\Gate::allows('access stats') || \Illuminate\Support\Facades\Gate::allows('org stats'))
<x-jetstream.responsive-nav-link :active="request()->routeIs('stats')" href="{{ route('stats') }}">
{{ __('Statistics') }}
</x-jetstream.responsive-nav-link>
@endif
<!-- Messenger Link -->
@php
$messengerTypeResp = session('activeProfileType');
$showMessengerResp = match(true) {
$messengerTypeResp === null => false,
$messengerTypeResp === 'App\Models\Organization' || $messengerTypeResp === 'App\Models\Bank' => Gate::check('org messenger'),
$messengerTypeResp === 'App\Models\Admin' => Gate::check('admin messenger'),
default => true,
};
@endphp
@if ($showMessengerResp)
<x-jetstream.responsive-nav-link
id="messenger-nav-button-responsive"
class="relative"
href="{{ route('chats') }}"
:active="request()->routeIs('chats')">
<span class="flex items-center">
{{ __('Messenger') }}
<span id="nav-unread-dot-responsive"
class="ml-4 h-2 w-2 rounded-full bg-red-700"
style="display: none;"></span>
</span>
</x-jetstream.responsive-nav-link>
@endif
</div>
<div class="w-full px-2 lg:px-3 pt-2">
@livewire('main-search-bar')
</div>
<!-- Responsive Settings Options -->
<div class="border-t border-theme-primary mt-4 w-full"></div>
<div class="pb-4 pt-4">
<div class="flex items-center px-4">
@if (Laravel\Jetstream\Jetstream::managesProfilePhotos())
<div class="mr-3 shrink-0">
<img alt="" class="h-10 w-10 aspect-square rounded-full profile-photo object-cover outline outline-1 outline-offset-1 outline-theme-secondary"
src="{{ Storage::url(Session('activeProfilePhoto')) }}" />
</div>
@endif
<div x-on:click.capture="window.dispatchEvent(new CustomEvent('nav-globe-close'))">
@if (auth()->guard('web')->user()?->hasOtherProfiles())
<livewire:switch-profile>
@else
<div class="text-base font-medium text-theme-primary">{{ Auth::user()->name }}</div>
@endif
</div>
<!---- Language Selector ---->
<div class="flex ml-auto items-center">
<div class="relative" x-data="{ open: false }" @click.away="open = false" @nav-globe-close.window="open = false">
<div @click="open = ! open">
<span class="inline-flex rounded-md">
<button class="inline-flex items-center rounded-md bg-theme-background px-3 py-2 text-sm font-medium leading-4 text-theme-primary transition hover:bg-theme-surface hover:text-theme-secondary focus:bg-theme-surface focus:outline-none active:bg-theme-surface"
type="button">
<x-icon name="globe-alt" class="w-6 h-5" outline />
</button>
</span>
</div>
<!-- Language select options -->
<div x-show="open"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="transform opacity-0 scale-95"
x-transition:enter-end="transform opacity-100 scale-100"
x-transition:leave="transition ease-in duration-75"
x-transition:leave-start="transform opacity-100 scale-100"
x-transition:leave-end="transform opacity-0 scale-95"
class="absolute z-50 mt-2 w-48 max-w-[calc(100vw-1rem)] rounded-md shadow-lg origin-top-right right-0"
style="display: none;"
@click="open = false">
<div class="rounded-md ring-1 ring-black ring-opacity-5 py-1 bg-theme-background">
<div class="w-60">
@foreach ($supportedLocales as $localeCode => $properties)
@php
$language = $languages->firstWhere('lang_code', strtolower($localeCode));
if ($profileTypeBaseKey) {
$translatedType = Lang::get($profileTypeBaseKey, [], $localeCode);
$localizedUrl = LaravelLocalization::getURLFromRouteNameTranslated($localeCode, 'routes.profile.show', ['type' => $translatedType, 'id' => request()->route('id')]);
} else {
$localizedUrl = LaravelLocalization::getLocalizedURL($localeCode, null, [], true);
}
@endphp
@if ($language)
@php $isActive = strtolower($localeCode) === strtolower(app()->getLocale()); @endphp
<a class="block pl-3 pr-4 py-2 border-l-4 text-sm leading-5 text-theme-primary hover:bg-theme-surface focus:outline-none focus:bg-theme-surface transition {{ $isActive ? 'border-black bg-theme-surface font-semibold' : 'border-transparent' }}"
href="{{ $localizedUrl }}"
hreflang="{{ $localeCode }}" rel="alternate">
{{ $language->flag }}
<span class="ml-3 text-theme-primary">{{ Lang::get($language->name, [], $localeCode) }}</span>
</a>
@endif
@endforeach
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-3 space-y-1">
<!-- Account Management -->
<!-- Balance Info -->
@if (session('activeProfileType') !== 'App\Models\Admin')
<div class="border-t border-theme-primary"></div>
<x-jetstream.responsive-nav-link href="#" wire:click.prevent="$dispatch('openAccountInfoModal')">
{{ __('Account info') }}
</x-jetstream.responsive-nav-link>
<div class="border-t border-theme-primary"></div>
@endif
<!-- Edit profile info -->
@if (session('activeProfileType') !== 'App\Models\Admin')
@php $isOrgOrBankResp = in_array(session('activeProfileType'), ['App\Models\Organization', 'App\Models\Bank']); @endphp
@if (!$isOrgOrBankResp || Gate::check('org edit-profile'))
<x-jetstream.responsive-nav-link href="{{ route('profile.edit') }}" :active="request()->routeIs('profile.edit')">
{{ __('Edit profile') }}
</x-jetstream.responsive-nav-link>
@endif
<!-- Show Profile -->
<x-jetstream.responsive-nav-link href="{{ route('profile.show_active') }}">
{{ __('Show profile') }}
</x-jetstream.responsive-nav-link>
@else
<div class="block cursor-default px-4 py-2 text-sm leading-5 text-theme-light">
{{ __('Edit profile') }}
</div>
{{-- Disabled link --}}
<div class="block cursor-default px-4 py-2 text-sm leading-5 text-theme-light">
{{ __('Show profile') }}
</div>
@endif
<!-- Settings -->
@php
$activeTypeResp = session('activeProfileType');
$showSettingsResp = match(true) {
$activeTypeResp === 'App\Models\Organization' || $activeTypeResp === 'App\Models\Bank' => Gate::check('org settings'),
$activeTypeResp === 'App\Models\Admin' => Gate::check('admin settings'),
default => true,
};
@endphp
@if ($showSettingsResp)
<x-jetstream.responsive-nav-link href="{{ route('profile.settings') }}">
{{ __('Settings') }}
</x-jetstream.responsive-nav-link>
@endif
@if (Laravel\Jetstream\Jetstream::hasApiFeatures())
<x-jetstream.responsive-nav-link href="{{ route('api-tokens.index') }}" :active="request()->routeIs('api-tokens.index')">
{{ __('API Tokens') }}
</x-jetstream.responsive-nav-link>
@endif
</div>
<div class="border-t border-theme-primary mt-3 w-full"></div>
<div class="pt-2">
<!-- Authentication -->
<form method="POST" action="{{ route('logout') }}" x-data>
@csrf
<x-jetstream.responsive-nav-link href="{{ route('logout') }}"
@click.prevent="$root.submit();">
{{ __('Log out') }}
</x-jetstream.responsive-nav-link>
</form>
</div>
</div>
</div>
{{-- <script>
window.Pusher.logToConsole = true;
window.Echo.private('switchProfile')
.listen('ProfileSwitchEvent', (e) => {
window.location.reload();
});
</script> --}}
<script>
document.addEventListener('DOMContentLoaded', () => {
const unreadDotMain = document.getElementById('nav-unread-dot-main');
const unreadDotResponsive = document.getElementById('nav-unread-dot-responsive');
const unreadDotHamburger = document.getElementById('nav-unread-dot-hamburger');
const dropdownContent = document.getElementById('messenger-dropdown-content');
const hamburgerMenuButton = document.getElementById('hamburger-menu-button');
if (!dropdownContent) return;
// Initialize both values from PHP on page load so dots show immediately
// without waiting for Livewire components to mount and dispatch events
let hasSwitchProfileUnread = {{ $switchProfileHasUnread ? 'true' : 'false' }};
let activeProfileUnreadCount = {{ (int) $activeProfileUnreadCount ?? 0 }};
function updateUnreadDots() {
// Check DOM-based unread dots in the chat list (live chat updates)
const unreadDots = dropdownContent.querySelectorAll('[dusk="unreadMessagesDot"]');
let hasVisibleUnread = activeProfileUnreadCount > 0;
unreadDots.forEach(dot => {
const computedStyle = window.getComputedStyle(dot);
if (computedStyle.display !== 'none') {
hasVisibleUnread = true;
}
});
const isMenuOpen = hamburgerMenuButton && hamburgerMenuButton.__x && hamburgerMenuButton.__x.$data.open;
// Main Messenger button dot (only show for active profile messages)
if (unreadDotMain) {
unreadDotMain.style.display = hasVisibleUnread ? 'block' : 'none';
}
// Responsive nav dot (only show for active profile messages)
if (unreadDotResponsive) {
unreadDotResponsive.style.display = hasVisibleUnread ? 'block' : 'none';
}
// Hamburger menu dot (show for EITHER active profile messages OR other profile messages)
if (unreadDotHamburger) {
const shouldShowHamburgerDot = (hasVisibleUnread || hasSwitchProfileUnread) && !isMenuOpen;
unreadDotHamburger.style.display = shouldShowHamburgerDot ? 'block' : 'none';
}
}
const observer = new MutationObserver(() => {
// Slight delay to let Alpine finish its display updates
setTimeout(updateUnreadDots, 50);
});
observer.observe(dropdownContent, {
childList: true,
subtree: true,
attributes: true, // Also watch for attribute changes (Alpine modifies style attributes)
attributeFilter: ['style', 'x-show']
});
// Initial check
updateUnreadDots();
// Listen for menu open/close changes (Alpine)
document.addEventListener('click', () => {
setTimeout(updateUnreadDots, 50);
});
// Listen for Livewire updates
window.addEventListener('livewire:update', () => {
setTimeout(updateUnreadDots, 100);
});
// Listen for custom WireChat events
window.addEventListener('chat-opened', updateUnreadDots);
window.addEventListener('chat-closed', updateUnreadDots);
// Listen for unread count updates from UnreadIndicator component
window.addEventListener('unread-count-updated', (event) => {
activeProfileUnreadCount = event.detail?.count ?? 0;
updateUnreadDots();
});
Livewire.on('unread-count-updated', (data) => {
activeProfileUnreadCount = Array.isArray(data) ? (data[0]?.count ?? 0) : (data?.count ?? 0);
updateUnreadDots();
});
// Listen for switch profile unread updates (non-active profiles)
window.addEventListener('switch-profile-unread-updated', (event) => {
hasSwitchProfileUnread = event.detail.hasUnread;
setTimeout(updateUnreadDots, 50);
});
// Also listen via Livewire
Livewire.on('switch-profile-unread-updated', (data) => {
hasSwitchProfileUnread = data.hasUnread;
setTimeout(updateUnreadDots, 50);
});
});
</script>
@endauth
</nav>