CluPilotCloud/resources/views/layouts/portal-app.blade.php

155 lines
8.8 KiB
PHP

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="csrf-token" content="{{ csrf_token() }}">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="apple-touch-icon" href="/favicon.svg">
<title>{{ $title ?? config('app.name') }}</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
@livewireStyles
</head>
<body class="min-h-full bg-bg text-body antialiased" x-data="{ nav: false }">
@php
$maintenanceWindows = collect();
if (auth()->check()) {
$portalCustomer = \App\Models\Customer::query()->where('user_id', auth()->id())->first()
?? \App\Models\Customer::query()->where('email', auth()->user()->email)->first();
if ($portalCustomer) {
$maintenanceWindows = \App\Models\MaintenanceWindow::bannerFor($portalCustomer);
}
}
@endphp
@if (auth()->check() && ! ($portalCustomer ?? null))
{{-- No customer behind this login (e.g. an operator account): say so, or
every customer action on these pages looks like a dead button. --}}
<div class="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 border-b border-warning-border bg-warning-bg px-4 py-2 text-center text-sm text-ink">
<x-ui.icon name="alert-triangle" class="size-4 shrink-0 text-warning" />
<span class="font-medium">{{ __('dashboard.no_customer_title') }}</span>
<span class="text-muted">{{ __('dashboard.no_customer_hint') }}</span>
@if (auth()->user()->can('console.view'))
<a href="{{ route('admin.customers') }}" class="font-semibold text-accent-text hover:underline">{{ __('dashboard.no_customer_cta') }}</a>
@endif
</div>
@endif
@foreach ($maintenanceWindows as $mw)
@php $isActive = now()->greaterThanOrEqualTo($mw->starts_at) && now()->lessThanOrEqualTo($mw->ends_at); @endphp
<div class="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 border-b {{ $isActive ? 'border-warning-border bg-warning-bg' : 'border-info-border bg-info-bg' }} px-4 py-2 text-center text-sm text-ink">
<x-ui.icon name="alert-triangle" class="size-4 shrink-0 {{ $isActive ? 'text-warning' : 'text-info' }}" />
<span class="font-medium">{{ $mw->title }}</span>
<span class="text-muted">
{{ $isActive
? __('maintenance.banner_active', ['end' => $mw->ends_at->isoFormat('LT')])
: __('maintenance.banner_upcoming', ['start' => $mw->starts_at->isoFormat('LLL'), 'end' => $mw->ends_at->isoFormat('LT')]) }}
</span>
</div>
@endforeach
@if (session('impersonator_id'))
{{-- Admin impersonation banner visible on every portal page until the admin returns. --}}
<div class="flex flex-wrap items-center justify-center gap-x-3 gap-y-1 border-b border-warning-border bg-warning-bg px-4 py-2 text-center text-sm font-medium text-ink">
<span>{{ __('impersonate.banner', ['name' => auth()->user()->name]) }}</span>
<form method="POST" action="{{ route('impersonate.leave') }}" class="inline">
@csrf
<button type="submit" class="inline-flex items-center gap-1 rounded-pill border border-warning-border px-3 py-0.5 font-semibold text-warning underline-offset-2 hover:underline">
<x-ui.icon name="log-out" class="size-4" />
{{ __('impersonate.leave') }}
</button>
</form>
</div>
@endif
<div class="flex min-h-screen lg:h-screen lg:overflow-hidden">
{{-- Sidebar: fixed drawer on small screens, fixed full-height column on large (R7). --}}
<aside
class="fixed inset-y-0 left-0 z-40 flex w-60 flex-col overflow-y-auto border-r border-line bg-surface px-3 py-4 transition-transform lg:static lg:h-screen lg:translate-x-0"
:class="nav ? 'translate-x-0' : '-translate-x-full'"
>
<div class="flex items-center justify-between px-3 pb-4">
<span class="flex items-center gap-2">
<x-ui.logo class="size-7" />
<span class="font-mono text-lg font-semibold tracking-tight text-ink">CluPilot</span>
</span>
<button type="button" class="lg:hidden text-muted" @click="nav = false" aria-label="{{ __('dashboard.close_nav') }}">
<x-ui.icon name="chevron-down" class="size-5 rotate-90" />
</button>
</div>
<nav class="space-y-1">
@foreach ([
['dashboard', 'gauge', 'overview'],
['cloud', 'cloud', 'cloud'],
['users', 'users', 'users'],
['backups', 'database', 'backups'],
['invoices', 'receipt', 'invoices'],
['billing', 'box', 'billing'],
['settings', 'settings', 'settings'],
['support', 'life-buoy', 'support'],
] as [$route, $icon, $key])
<x-ui.nav-item :href="route($route)" :active="request()->routeIs($route)">
<x-slot:icon><x-ui.icon :name="$icon" /></x-slot:icon>
{{ __('dashboard.nav.'.$key) }}
</x-ui.nav-item>
@endforeach
</nav>
</aside>
{{-- Mobile backdrop --}}
<div x-show="nav" x-cloak @click="nav = false" class="fixed inset-0 z-30 bg-ink/20 lg:hidden"></div>
<div class="flex min-w-0 flex-1 flex-col lg:h-screen lg:overflow-hidden">
<header class="flex h-14 shrink-0 items-center justify-between border-b border-line bg-surface px-4">
<button type="button" class="lg:hidden inline-flex min-h-11 min-w-11 items-center justify-center rounded text-muted hover:bg-surface-hover" @click="nav = true" aria-label="{{ __('dashboard.open_nav') }}">
<x-ui.icon name="menu" />
</button>
<div class="flex-1"></div>
@auth
<div class="relative" x-data="{ open: false }" @keydown.escape="open = false">
<button type="button" class="flex items-center gap-2 rounded px-2 py-1.5 hover:bg-surface-hover" @click="open = !open" :aria-expanded="open" aria-haspopup="menu">
<span class="flex size-8 items-center justify-center rounded-pill bg-accent-subtle text-sm font-semibold text-accent-text">{{ \Illuminate\Support\Str::upper(\Illuminate\Support\Str::substr(auth()->user()->name, 0, 1)) }}</span>
<span class="hidden text-sm text-body sm:block">{{ auth()->user()->name }}</span>
<x-ui.icon name="chevron-down" class="size-4 text-muted" />
</button>
<div x-show="open" x-cloak @click.outside="open = false" class="absolute right-0 mt-1 w-48 rounded-lg border border-line bg-surface py-1 shadow-md">
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit" class="flex w-full items-center gap-2 px-3 py-2 text-left text-sm text-body hover:bg-surface-hover">
<x-ui.icon name="log-out" class="size-4" />
{{ __('dashboard.logout') }}
</button>
</form>
</div>
</div>
@endauth
</header>
<main class="mx-auto w-full max-w-[1200px] flex-1 p-6 lg:overflow-y-auto lg:p-8">
{{ $slot }}
</main>
</div>
</div>
{{-- Global toast any element can trigger it with $dispatch('notify', { message }) --}}
<div
x-data="{ show: false, msg: '', timer: null }"
@notify.window="msg = $event.detail.message; show = true; clearTimeout(timer); timer = setTimeout(() => show = false, 3200)"
x-show="show"
x-cloak
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 translate-y-12"
x-transition:enter-end="opacity-100 translate-y-0"
x-transition:leave="transition ease-in duration-300"
x-transition:leave-start="opacity-100 translate-y-0"
x-transition:leave-end="opacity-0 translate-y-12"
role="status"
aria-live="polite"
class="fixed bottom-6 left-1/2 z-[80] -translate-x-1/2 rounded-pill bg-ink px-5 py-3 text-sm font-medium text-on-accent shadow-md"
>
<span x-text="msg"></span>
</div>
@livewire('wire-elements-modal')
@livewireScripts
</body>
</html>