Initial commit
This commit is contained in:
411
resources/views/layouts/app.blade.php
Normal file
411
resources/views/layouts/app.blade.php
Normal file
@@ -0,0 +1,411 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" data-theme="@themeId">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta content="width=device-width, initial-scale=1" 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
|
||||
|
||||
<title>@yield('title', $pageTitle) - {{ config('app.name') }}</title>
|
||||
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('favicon-32x32.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('favicon-16x16.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('apple-touch-icon.png') }}">
|
||||
|
||||
<!-- 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">
|
||||
|
||||
<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>
|
||||
|
||||
<!-- Forced Logout Modal -->
|
||||
@auth
|
||||
@livewire('forced-logout-modal')
|
||||
@livewire('account-info-modal')
|
||||
@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>
|
||||
17
resources/views/layouts/demo-app.blade.php
Normal file
17
resources/views/layouts/demo-app.blade.php
Normal file
@@ -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>
|
||||
108
resources/views/layouts/guest.blade.php
Normal file
108
resources/views/layouts/guest.blade.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<!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">
|
||||
@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
|
||||
|
||||
<title>@yield('title', $pageTitle) - {{ config('app.name') }}</title>
|
||||
<link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ asset('favicon-32x32.png') }}">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ asset('favicon-16x16.png') }}">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="{{ asset('apple-touch-icon.png') }}">
|
||||
{{-- <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', 'resources/js/app.js'])
|
||||
@livewireStyles
|
||||
@wirechatStyles
|
||||
|
||||
<!-- Dynamic Theme CSS Custom Properties -->
|
||||
<style>
|
||||
:root {
|
||||
{!! theme_css_vars() !!}
|
||||
}
|
||||
</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>
|
||||
|
||||
</head>
|
||||
<body class="font-sans antialiased flex flex-col min-h-screen">
|
||||
<x-jetstream.banner />
|
||||
<x-jetstream.toaster />
|
||||
<x-notifications position="bottom-end" />
|
||||
|
||||
<!-- Fixed navigation menu -->
|
||||
@include('components.navigation-menu-guest')
|
||||
|
||||
<!-- 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
|
||||
@wirechatAssets
|
||||
@stack('scripts')
|
||||
@yield('scripts_body')
|
||||
@yield('js')
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user