{{-- Client portal shell (B2C). The carries two orthogonal seeds: the per-panel --brand (so primary-* re-themes per reseller) AND data-template (aurora | vibrant | midnight), which app.css turns into a full skin with zero recompilation — see STYLE-GUIDE-CLIENT-TEMPLATES §1. The chosen template also decides the SHELL: 'topnav' includes the classic horizontal bar; 'sidebar' includes the brand-filled / dark sidebar variant (§1.4). --}} @php $user = auth()->user(); // The wallet/ledger are ALWAYS the base currency; the client SEES the balance // converted to their chosen currency (display-only) via client_money(). $baseBalance = $user ? app(\App\Services\Wallet\WalletService::class)->balanceOf($user) : '0'; $balance = client_money($baseBalance); $template = panel_client_template(); $layout = client_template_layout(); // 'topnav' | 'sidebar' $defaultTheme = client_template_meta($template)['default_theme'] ?? 'dark'; // Vibrant gets its own dedicated IMPULSA shell (purple sidebar with the // "biting tab" nav); midnight keeps the shared sidebar, aurora the topnav. $shell = $template === 'vibrant' ? 'vibrant' : $layout; // Unread news count shown as a badge on the News nav item. $newsUnread = 0; if ($user) { $readIds = \App\Models\NewsRead::where('user_id', $user->id)->pluck('news_id'); $newsUnread = \App\Models\News::where('status', true)->whereNotIn('id', $readIds)->count(); } // Horizontal/vertical nav model shared by both shells. `children` renders a // dropdown (topnav) or a subsection (sidebar); a null route is a group parent. $navDripfeed = module_enabled('dripfeed'); $navTickets = module_enabled('tickets'); $navApi = module_enabled('api'); $navOrderChildren = array_values(array_filter([ ['label' => __('Order Logs'), 'icon' => 'list', 'route' => 'app.orders'], $navDripfeed ? ['label' => __('Drip-feed'), 'icon' => 'droplets', 'route' => 'app.dripfeed'] : null, ['label' => __('Subscriptions'), 'icon' => 'repeat', 'route' => 'app.subscriptions'], ])); $navOrderMatch = array_values(array_filter([ 'app.orders', $navDripfeed ? 'app.dripfeed' : null, 'app.subscriptions', ])); // Finance group — keeps the topnav from overflowing with 10 flat items. $navFinanceChildren = [ ['label' => __('Add Funds'), 'icon' => 'wallet', 'route' => 'app.funds'], ['label' => __('Transactions'), 'icon' => 'receipt', 'route' => 'app.transactions'], ]; $navFinanceMatch = ['app.funds', 'app.transactions']; // "More" overflow group: News (with unread badge), API, Terms & Conditions. $navMoreChildren = array_values(array_filter([ ['label' => __('News'), 'icon' => 'megaphone', 'route' => 'app.news', 'badge' => $newsUnread], $navApi ? ['label' => __('API'), 'icon' => 'code', 'route' => 'app.api'] : null, ['label' => __('Terms & Conditions'), 'icon' => 'file-text', 'route' => 'app.terms'], ])); $navMoreMatch = array_values(array_filter([ 'app.news', $navApi ? 'app.api' : null, 'app.terms', ])); $nav = array_values(array_filter([ ['label' => __('Statistics'), 'icon' => 'layout-dashboard', 'route' => 'app.dashboard'], ['label' => __('New Order'), 'icon' => 'plus-circle', 'route' => 'app.orders.create'], ['label' => __('Orders'), 'icon' => 'shopping-cart', 'route' => null, 'match' => $navOrderMatch, 'children' => $navOrderChildren], ['label' => __('Services'), 'icon' => 'layers', 'route' => 'app.services'], $navTickets ? ['label' => __('Support'), 'icon' => 'life-buoy', 'route' => null, 'match' => ['app.tickets'], 'children' => [ ['label' => __('Tickets'), 'icon' => 'ticket', 'route' => 'app.tickets'], ]] : null, ['label' => __('Finance'), 'icon' => 'circle-dollar-sign', 'route' => null, 'match' => $navFinanceMatch, 'children' => $navFinanceChildren], ['label' => __('More'), 'icon' => 'more-horizontal', 'route' => null, 'match' => $navMoreMatch, 'children' => $navMoreChildren, 'badge' => $newsUnread], ])); $navActive = fn ($item) => ($item['route'] && request()->routeIs($item['route'])) || (isset($item['match']) && collect($item['match'])->contains(fn ($m) => request()->routeIs($m.'*'))); // The network-tiles row (§5.4) only reads as intended in the sidebar skins, // and only on the two screens the guide calls out. $showNetRow = $layout === 'sidebar' && request()->routeIs(['app.dashboard', 'app.services']); @endphp {{ $title ?? __('Dashboard') }} — {{ \App\Support\Brand::name() }} @php $rfFavicon = panel_favicon_url(); @endphp @if ($rfFavicon) @else @endif {{-- Apply the theme before paint. The default follows the template's mother theme (§1.5) until the user has toggled one (persisted in rf-theme). Midnight is always dark — user can toggle Aurora/Vibrant. --}} {{-- Per-panel brand seed — the primary scale derives from this at runtime. --}} @vite(['resources/css/app.css', 'resources/js/app.js']) @livewireStyles @stack('styles') {{-- Monochrome network-glyph sprite, referenced by the tiles (self-hosted). --}} @include('partials.social-icons') {{-- Impersonation banner — shown while an admin views the panel as a client. --}} @if (session('impersonator_id'))
{{ __('You are viewing the panel as :name.', ['name' => $user->display_name ?? $user->name]) }} {{ (session('impersonate_return_url') || str_starts_with((string) session('impersonate_return'), 'panel.')) ? __('Return to my panel') : __('Return to admin') }}
@endif {{-- Read-only skin preview banner (admin opened /app?preview_template=X). --}} @if (request()->query('preview_template') && staff_may_preview_templates())
{{ __('Preview mode — :template template (not saved).', ['template' => client_template_meta($template)['label'] ?? $template]) }}
@endif @include('components.client.shell-'.$shell) {{-- Global notification host (top-right), shared by every shell. --}} @include('partials.toast-host') {{-- A toast flashed to the session (e.g. after a currency change forces a full reload) is replayed as the browser `toast` event once the page is ready. --}} @if (session('toast')) @endif {{-- The one confirmation dialog every destructive control routes through. --}} @livewireScripts @stack('scripts')