Snapshot of main at 9f39a6e1e (2026-11-02)
This commit is contained in:
commit
6ccf1792d7
2580 files changed
+1040202
No files matched your search
@@ -0,0 +1,498 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" data-theme="@themeId">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1, viewport-fit=cover" name="viewport">
|
||||
<meta content="{{ csrf_token() }}" name="csrf-token">
|
||||
<meta content="{{config('app.name')}}" name="title">
|
||||
@unless(timebank_config('seo.allow_indexing_auth'))
|
||||
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
|
||||
@endunless
|
||||
|
||||
@auth
|
||||
<meta name="user-id" content="{{ auth()->id() }}">
|
||||
<meta name="user-guard" content="{{ config('auth.defaults.guard') }}">
|
||||
@endauth
|
||||
|
||||
@php
|
||||
// Determine page title: explicit title > header content > fallback
|
||||
$pageTitle = null;
|
||||
|
||||
// Check if header slot is set and extract text from it
|
||||
if (isset($header) && $header) {
|
||||
$headerContent = (string) $header;
|
||||
$pageTitle = trim(strip_tags($headerContent));
|
||||
}
|
||||
|
||||
// Fall back to StringHelper if no valid header content
|
||||
if (empty($pageTitle)) {
|
||||
$pageTitle = \App\Helpers\StringHelper::getPageTitle();
|
||||
}
|
||||
@endphp
|
||||
|
||||
@include('layouts.partials.brand-head', ['pageTitle' => $pageTitle])
|
||||
|
||||
<!-- Scripts head -->
|
||||
<script src="{{ route('lang.js') }}"></script>
|
||||
<!-- Fonts -->
|
||||
{{-- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap"> --}}
|
||||
|
||||
<!-- Styles -->
|
||||
<link href="{{ asset('css/tagify.css') }}" rel="stylesheet">
|
||||
|
||||
<!-- Dynamic Theme CSS Custom Properties -->
|
||||
<style>
|
||||
:root {
|
||||
{!! theme_css_vars() !!}
|
||||
}
|
||||
</style>
|
||||
|
||||
@vite(['resources/css/app.css', 'resources/css/fonts.css', 'resources/sass/custom_timebank.css'])
|
||||
<link href="{{ asset('css/custom_tagify.css') }}" rel="stylesheet">
|
||||
@livewireStyles
|
||||
@wirechatStyles
|
||||
|
||||
<!-- Flatpickr Styles -->
|
||||
<x-flatpickr::style />
|
||||
|
||||
|
||||
<style>
|
||||
/* Theme-aware typography */
|
||||
body {
|
||||
font-family: var(--font-family-body, 'Poppins', sans-serif) !important;
|
||||
}
|
||||
|
||||
/* Theme-aware heading styles */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-family-heading, 'Oswald', sans-serif) !important;
|
||||
text-transform: var(--heading-transform, uppercase) !important;
|
||||
}
|
||||
|
||||
/* Apply theme-specific CSS custom properties */
|
||||
:root {
|
||||
@themeCssVars
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Quill editor (loaded locally via Vite) -->
|
||||
@vite('resources/js/quill.js')
|
||||
<style>
|
||||
.ql-editor {
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Scripts (wireui moved to body to fix Firefox Alpine init order) -->
|
||||
|
||||
|
||||
{{-- TODO: Move styles below to separate css file, that can be compiled --}}
|
||||
<style>
|
||||
input[type=file]::file-selector-button {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
color: #fff !important;
|
||||
padding-left: 1.0rem;
|
||||
padding-top: 0.25rem;
|
||||
padding-right: 1.0rem;
|
||||
padding-bottom: 0.25rem;
|
||||
border-radius: 0.25rem;
|
||||
-webkit-appearance: button;
|
||||
font-weight: 700;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
input[type=file]::file-selector-button:hover {
|
||||
cursor: pointer;
|
||||
--tw-text-opacity: 1;
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(107 114 128 / var(--tw-bg-opacity));
|
||||
}
|
||||
|
||||
progress {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
border: none;
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background-color: #9ae6b4;
|
||||
}
|
||||
|
||||
progress::-moz-progress-bar {
|
||||
background-color: #9ae6b4;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="font-sans antialiased flex flex-col min-h-screen" data-active-guard="{{ session('active_guard', 'web') }}">
|
||||
|
||||
<x-jetstream.banner />
|
||||
<x-jetstream.toaster />
|
||||
|
||||
<div class="flex-grow md:bg-theme-surface">
|
||||
@livewire('navigation-menu')
|
||||
<x-notifications position="bottom-end" />
|
||||
|
||||
|
||||
<header class="bg-theme-brand text-xl font-semibold text-theme-background shadow sm:mt-16">
|
||||
<!-- System Anounnucement -->
|
||||
@livewire('system-announcement', ['type' => 'SiteContents\SystemAnnouncement' ?? null, 'limit' => 1])
|
||||
<!-- Maintenance Banner -->
|
||||
@livewire('admin.maintenance-banner')
|
||||
<!-- Header --->
|
||||
@if (isset($header))
|
||||
<div class="max-w-7xl mx-auto py-2 px-4 sm:px-6 lg:px-8">
|
||||
{{ $header }}
|
||||
</div>
|
||||
@endif
|
||||
</header>
|
||||
|
||||
<!-- Page Content -->
|
||||
<main>
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="mt-auto w-full">
|
||||
<x-footer />
|
||||
</div>
|
||||
|
||||
<!-- Profile switch notification -->
|
||||
@php
|
||||
$switchNotification = null;
|
||||
|
||||
// Check for query parameter first (Livewire navigate case)
|
||||
if (request()->has('profile-switch-banner')) {
|
||||
try {
|
||||
$encoded = request('profile-switch-banner');
|
||||
$switchNotification = json_decode(base64_decode($encoded), true);
|
||||
// Convert photo path to URL if it exists
|
||||
if ($switchNotification && !empty($switchNotification['photo'])) {
|
||||
$switchNotification['photo'] = Storage::url($switchNotification['photo']);
|
||||
}
|
||||
// Translate role label
|
||||
if ($switchNotification && !empty($switchNotification['roleLabel'])) {
|
||||
$switchNotification['roleLabel'] = __($switchNotification['roleLabel']);
|
||||
}
|
||||
\Log::info('BANNER: Found notification in query param', ['name' => $switchNotification['name'] ?? 'unknown']);
|
||||
} catch (\Exception $e) {
|
||||
\Log::warning('BANNER: Failed to decode query param', ['error' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to session (for non-Livewire navigate cases like direct login)
|
||||
if (!$switchNotification && session('profile-switched-notification')) {
|
||||
$switchNotification = [
|
||||
'name' => session('activeProfileName'),
|
||||
'photo' => session('activeProfilePhoto') ? Storage::url(session('activeProfilePhoto')) : null,
|
||||
'roleLabel' => session('activeProfileRoleLabel') ? __(session('activeProfileRoleLabel')) : null,
|
||||
];
|
||||
\Log::info('BANNER: Found notification in session', ['name' => $switchNotification['name']]);
|
||||
session()->forget('profile-switched-notification');
|
||||
}
|
||||
|
||||
if (!$switchNotification) {
|
||||
\Log::info('BANNER: No notification found (query or session)');
|
||||
}
|
||||
@endphp
|
||||
|
||||
@if($switchNotification)
|
||||
<meta name="profile-switch-data" content="{{ base64_encode(json_encode($switchNotification)) }}">
|
||||
@endif
|
||||
|
||||
<div wire:ignore>
|
||||
<div id="profile-switch-banner"
|
||||
style="display:none;position:fixed;top:0;left:0;right:0;bottom:0;z-index:9999;align-items:center;justify-content:center;background:rgba(0,0,0,0.45);">
|
||||
<div style="background:#fff;border-radius:12px;padding:24px 28px;max-width:360px;width:90%;box-shadow:0 8px 32px rgba(0,0,0,0.25);">
|
||||
<img id="profile-switch-banner-photo" src="" alt="" style="display:none;width:72px;height:72px;border-radius:50%;object-fit:cover;margin:0 auto 12px;box-shadow:0 2px 8px rgba(0,0,0,0.15);">
|
||||
<div style="color:#6b7280;font-size:13px;margin-bottom:4px;text-align:left;">{{ __('You are now acting as') }}</div>
|
||||
<div id="profile-switch-banner-name" style="font-size:18px;font-weight:700;color:#111827;margin-bottom:6px;text-align:left;"></div>
|
||||
<div id="profile-switch-banner-role" style="font-size:12px;color:#6b7280;text-align:left;display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Always-run script: checks meta tag for notification data on every page load --}}
|
||||
<script>
|
||||
(function() {
|
||||
function checkAndShowNotification() {
|
||||
var metaTag = document.querySelector('meta[name="profile-switch-data"]');
|
||||
if (!metaTag) return;
|
||||
|
||||
try {
|
||||
var encoded = metaTag.getAttribute('content');
|
||||
var data = JSON.parse(atob(encoded));
|
||||
|
||||
function show() {
|
||||
if (window.__showProfileSwitchBanner) {
|
||||
window.__showProfileSwitchBanner(data);
|
||||
// Remove meta tag after processing so it doesn't show again
|
||||
metaTag.remove();
|
||||
} else {
|
||||
setTimeout(show, 50);
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', show);
|
||||
} else {
|
||||
show();
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('Profile switch notification error:', e);
|
||||
}
|
||||
}
|
||||
|
||||
checkAndShowNotification();
|
||||
})();
|
||||
</script>
|
||||
|
||||
<!-- Forced Logout Modal -->
|
||||
@auth
|
||||
@livewire('forced-logout-modal')
|
||||
@livewire('account-info-modal')
|
||||
<div class="hidden">@livewire('unread-indicator')</div>
|
||||
@endauth
|
||||
|
||||
<!-- Scripts body-->
|
||||
<!-- Be careful with changing the loading order! -->
|
||||
<wireui:scripts />
|
||||
@livewireScripts
|
||||
@vite('resources/js/app.js')
|
||||
@wirechatAssets
|
||||
|
||||
<!-- Session Expiration Detection -->
|
||||
<script>
|
||||
// Detect session expiration and auto-reload
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
let sessionExpired = false;
|
||||
let isLoggingOut = false;
|
||||
|
||||
// Mark when logout is initiated
|
||||
document.addEventListener('click', function(e) {
|
||||
const logoutButton = e.target.closest('form[action*="logout"]') ||
|
||||
e.target.closest('button[wire\\:click*="logout"]') ||
|
||||
e.target.closest('a[href*="logout"]');
|
||||
if (logoutButton) {
|
||||
isLoggingOut = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Detect Livewire 401/419 errors (session expired)
|
||||
window.addEventListener('livewire:request', ({ detail }) => {
|
||||
detail.fail(({ status, content, preventDefault }) => {
|
||||
if (status === 401 || status === 419) {
|
||||
preventDefault(); // Always prevent default to avoid "Page expired" dialog
|
||||
|
||||
if (!sessionExpired) {
|
||||
sessionExpired = true;
|
||||
|
||||
// Small delay to let other pending requests complete
|
||||
setTimeout(() => {
|
||||
// If logging out, redirect to home instead of reloading
|
||||
if (isLoggingOut) {
|
||||
window.location.href = '{{ LaravelLocalization::localizeUrl('/') }}';
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Detect Echo connection errors (session expired)
|
||||
if (window.Echo && window.Echo.connector && window.Echo.connector.pusher) {
|
||||
window.Echo.connector.pusher.connection.bind('error', function(err) {
|
||||
// Check if it's an authentication error
|
||||
if (err && (err.type === 'AuthError' || err.error?.data?.code === 4009)) {
|
||||
if (!sessionExpired) {
|
||||
sessionExpired = true;
|
||||
// Delay reload slightly to avoid rapid reloads
|
||||
setTimeout(() => window.location.reload(), 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Catch "Handler does not exist" errors globally
|
||||
window.addEventListener('error', function(event) {
|
||||
const message = event.message || '';
|
||||
if (message.includes('Handler for event') && message.includes('does not exist')) {
|
||||
// This means session expired and component is stale
|
||||
if (!sessionExpired) {
|
||||
sessionExpired = true;
|
||||
window.location.reload();
|
||||
}
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}, true);
|
||||
|
||||
// Suppress the specific Echo error message in console
|
||||
const originalConsoleError = console.error;
|
||||
console.error = function(...args) {
|
||||
const message = args[0]?.toString() || '';
|
||||
// Don't log Echo handler errors if we're already reloading
|
||||
if (sessionExpired && message.includes('Handler for event')) {
|
||||
return;
|
||||
}
|
||||
// Suppress handler errors and trigger reload
|
||||
if (message.includes('Handler for event') && message.includes('does not exist')) {
|
||||
if (!sessionExpired) {
|
||||
sessionExpired = true;
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
}
|
||||
return;
|
||||
}
|
||||
originalConsoleError.apply(console, args);
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Flatpickr Scripts -->
|
||||
<x-flatpickr::script />
|
||||
|
||||
<!-- Flatpickr Error Handling Override -->
|
||||
<script>
|
||||
// Wrap the original Flatpickr initialization with error handling
|
||||
if (window.LaravelFlatpickr) {
|
||||
const originalInit = window.LaravelFlatpickr.initializeFlatpickr;
|
||||
window.LaravelFlatpickr.initializeFlatpickr = function(e) {
|
||||
try {
|
||||
// Check if element and required methods exist
|
||||
if (!e || !e.getAttribute) return;
|
||||
|
||||
const targetId = e.getAttribute("data-selector-id");
|
||||
if (!targetId) return;
|
||||
|
||||
const target = document.getElementById(targetId);
|
||||
if (!target) return;
|
||||
|
||||
// Call original initialization
|
||||
originalInit.call(this, e);
|
||||
} catch (error) {
|
||||
// Silently fail to prevent console spam
|
||||
// console.warn('Flatpickr initialization skipped:', error);
|
||||
}
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Suppress non-critical Alpine.js errors during Livewire morphing -->
|
||||
<script>
|
||||
// Suppress WireUI select component errors during Livewire morphing
|
||||
// These errors occur when Alpine tries to evaluate expressions before the component data is initialized
|
||||
// They don't affect functionality as the components initialize correctly after morphing completes
|
||||
|
||||
// Store original console methods
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
|
||||
// List of error patterns to suppress
|
||||
const suppressedPatterns = [
|
||||
/positionable is not defined/,
|
||||
/getSelectedValue is not defined/,
|
||||
/isEmpty is not defined/,
|
||||
/getPlaceholder is not defined/,
|
||||
/config is not defined/,
|
||||
/getSelectedDisplayText is not defined/,
|
||||
/asyncData is not defined/,
|
||||
/displayOptions is not defined/,
|
||||
/isNotEmpty is not defined/,
|
||||
/selectedOptions is not defined/,
|
||||
/wireui_select/,
|
||||
/Alpine Expression Error/,
|
||||
/ReferenceError.*is not defined/
|
||||
];
|
||||
|
||||
function shouldSuppressMessage(args) {
|
||||
const message = args.map(arg => {
|
||||
if (typeof arg === 'string') return arg;
|
||||
if (arg instanceof Error) return arg.message;
|
||||
return String(arg);
|
||||
}).join(' ');
|
||||
|
||||
return suppressedPatterns.some(pattern => pattern.test(message));
|
||||
}
|
||||
|
||||
// Override console.error
|
||||
console.error = function(...args) {
|
||||
if (!shouldSuppressMessage(args)) {
|
||||
originalError.apply(console, args);
|
||||
}
|
||||
};
|
||||
|
||||
// Override console.warn (some frameworks log errors as warnings)
|
||||
console.warn = function(...args) {
|
||||
if (!shouldSuppressMessage(args)) {
|
||||
originalWarn.apply(console, args);
|
||||
}
|
||||
};
|
||||
|
||||
// Catch uncaught errors in promises and event handlers
|
||||
window.addEventListener('error', function(event) {
|
||||
if (shouldSuppressMessage([event.message || event.error?.message || ''])) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
}, true);
|
||||
|
||||
window.addEventListener('unhandledrejection', function(event) {
|
||||
if (shouldSuppressMessage([event.reason?.message || event.reason || ''])) {
|
||||
event.preventDefault();
|
||||
return false;
|
||||
}
|
||||
}, true);
|
||||
</script>
|
||||
|
||||
@stack('scripts')
|
||||
@yield('scripts_body')
|
||||
@yield('js')
|
||||
@stack('modals')
|
||||
|
||||
<!-- Listen for forced logout event -->
|
||||
@auth
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (window.Echo) {
|
||||
window.Echo.private('user.logout.{{ auth()->id() }}')
|
||||
.listen('.forced-logout', (e) => {
|
||||
// Get current locale from HTML lang attribute
|
||||
const locale = document.documentElement.lang || 'en';
|
||||
|
||||
// Translate message in user's locale using window.i18n
|
||||
const messageKey = e.message_key || 'For security and maintenance, a system administrator has logged you out of your account. Sorry for this inconvenience and thanks for your patience.';
|
||||
let message = messageKey;
|
||||
if (window.i18n && window.i18n[locale] && window.i18n[locale][messageKey]) {
|
||||
message = window.i18n[locale][messageKey];
|
||||
}
|
||||
|
||||
// Show alert and immediately redirect to login page
|
||||
// This avoids CSRF errors from deleted sessions
|
||||
alert(message);
|
||||
|
||||
// Redirect to login page with a maintenance mode flag
|
||||
window.location.href = '{{ route("login") }}?logged_out=maintenance';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
@endauth
|
||||
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="title" content="@yield('title', config('messenger-ui.site_name'))">
|
||||
|
||||
<title>Demo CKeditor</title>
|
||||
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body>
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,111 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" data-theme="@themeId">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||
@unless(timebank_config('seo.allow_indexing_guest'))
|
||||
<meta name="robots" content="noindex, nofollow, noarchive, nosnippet">
|
||||
@endunless
|
||||
|
||||
@php
|
||||
// Determine page title: explicit title > header content > fallback
|
||||
$pageTitle = null;
|
||||
|
||||
// Check if header slot is set and extract text from it
|
||||
if (isset($header) && $header) {
|
||||
$headerContent = (string) $header;
|
||||
$pageTitle = trim(strip_tags($headerContent));
|
||||
}
|
||||
|
||||
// Fall back to StringHelper if no valid header content
|
||||
if (empty($pageTitle)) {
|
||||
$pageTitle = \App\Helpers\StringHelper::getPageTitle();
|
||||
}
|
||||
@endphp
|
||||
|
||||
@include('layouts.partials.brand-head', ['pageTitle' => $pageTitle])
|
||||
{{-- <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Nunito:wght@400;600;700&display=swap"> --}}
|
||||
|
||||
<!-- Scripts -->
|
||||
<wireui:scripts />
|
||||
{{-- <script src="{{ mix('js/app.js') }}" defer></script> --}}
|
||||
<!-- Styles -->
|
||||
@vite(['resources/css/app.css', 'resources/css/fonts.css', 'resources/sass/custom_timebank.css'])
|
||||
@livewireStyles
|
||||
@wirechatStyles
|
||||
|
||||
<!-- Dynamic Theme CSS Custom Properties -->
|
||||
<style>
|
||||
:root {
|
||||
{!! theme_css_vars() !!}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
/* Safari iOS fix: prevent 100vw overflow issues */
|
||||
html, body {
|
||||
overflow-x: clip;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Theme-aware typography */
|
||||
body {
|
||||
font-family: var(--font-family-body, 'Poppins', sans-serif) !important;
|
||||
}
|
||||
|
||||
/* Theme-aware heading styles */
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: var(--font-family-heading, 'Oswald', sans-serif) !important;
|
||||
text-transform: var(--heading-transform, uppercase) !important;
|
||||
}
|
||||
|
||||
/* Apply theme-specific CSS custom properties */
|
||||
:root {
|
||||
@themeCssVars
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body class="font-sans antialiased min-h-screen" style="display: flex; flex-direction: column;">
|
||||
<!-- Fixed navigation menu - BEFORE banner/toaster -->
|
||||
@livewire('navigation-menu-guest')
|
||||
|
||||
<x-jetstream.banner />
|
||||
<x-jetstream.toaster />
|
||||
<x-notifications position="bottom-end" />
|
||||
|
||||
<!-- Main content with top padding to account for fixed navigation -->
|
||||
<div class="flex-grow bg-theme-surface pt-16">
|
||||
<x-notifications position="bottom-end" />
|
||||
|
||||
<!-- System Announcement - positioned below fixed navigation -->
|
||||
@livewire('system-announcement', ['type' => 'SiteContents\SystemAnnouncement' ?? null, 'limit' => 1])
|
||||
|
||||
<header class="bg-theme-brand text-theme-surface shadow">
|
||||
<!-- Header --->
|
||||
@if (isset($header))
|
||||
<div class="max-w-7xl mx-auto pt-1 pb-2 px-4 sm:px-6 lg:px-8">
|
||||
{{ $header }}
|
||||
</div>
|
||||
@endif
|
||||
</header>
|
||||
<main>
|
||||
{{ $slot }}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="mt-auto w-full">
|
||||
<x-footer />
|
||||
</div>
|
||||
|
||||
<!-- Scripts body-->
|
||||
<!-- Be careful with changing the loading order! -->
|
||||
@livewireScripts
|
||||
@vite('resources/js/app.js')
|
||||
@wirechatAssets
|
||||
@stack('scripts')
|
||||
@yield('scripts_body')
|
||||
@yield('js')
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,32 @@
|
||||
{{-- Icons and the preview for shared links, for every layout. Expects $pageTitle (the page's fallback title).
|
||||
Optional files (favicon.svg, favicon-96x96.png, site.webmanifest, the preview image) are linked only when
|
||||
they exist: in the platform folder, the theme's favicon_dir or public/. --}}
|
||||
@php
|
||||
// yieldContent() escapes a section given as a string and the default itself; only the app name is added here
|
||||
$brandTitle = trim($__env->yieldContent('title', $pageTitle ?? '')) . ' - ' . e(config('app.name'));
|
||||
@endphp
|
||||
<title>{!! $brandTitle !!}</title>
|
||||
<link rel="icon" type="image/x-icon" href="{{ theme_favicon('favicon.ico') }}">
|
||||
@if($svgIcon = theme_favicon_optional('favicon.svg'))
|
||||
<link rel="icon" type="image/svg+xml" href="{{ $svgIcon }}">
|
||||
@endif
|
||||
@if($icon96 = theme_favicon_optional('favicon-96x96.png'))
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="{{ $icon96 }}">
|
||||
@endif
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ theme_favicon('favicon-32x32.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ theme_favicon('favicon-16x16.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ theme_favicon('apple-touch-icon.png') }}">
|
||||
@if($manifest = theme_favicon_optional('site.webmanifest'))
|
||||
<link rel="manifest" href="{{ $manifest }}">
|
||||
@endif
|
||||
|
||||
{{-- Open Graph: property=, not name= (Facebook and LinkedIn only read property) --}}
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:site_name" content="{{ config('app.name') }}">
|
||||
<meta property="og:title" content="{!! $brandTitle !!}">
|
||||
<meta property="og:url" content="{{ url()->current() }}">
|
||||
<meta property="og:locale" content="{{ str_replace('-', '_', app()->getLocale()) }}">
|
||||
@if($ogImage = theme_og_image())
|
||||
<meta property="og:image" content="{{ $ogImage }}">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
@endif
|
||||
Reference in new issue
Block a user