Initial commit - CMS Gov Bapenda Garut dengan EditorJS
This commit is contained in:
151
app/Views/admin/layout.php
Normal file
151
app/Views/admin/layout.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<meta name="csrf-token" content="<?= csrf_hash() ?>" />
|
||||
<meta name="csrf-header" content="<?= csrf_header() ?>" />
|
||||
<title><?= esc($title ?? 'Admin Dashboard') ?> - Bapenda Garut</title>
|
||||
<link rel="icon" type="image/png" href="<?= base_url('assets/images/favicon_1762970389090.png') ?>" />
|
||||
<link rel="shortcut icon" type="image/png" href="<?= base_url('assets/images/favicon_1762970389090.png') ?>" />
|
||||
<link rel="stylesheet" href="<?= base_url('assets/css/app.css') ?>">
|
||||
<style>
|
||||
/* Fix Editor.js toolbar z-index to stay below header */
|
||||
.ce-toolbar,
|
||||
.ce-inline-toolbar,
|
||||
.ce-popover,
|
||||
.ce-conversion-toolbar,
|
||||
.ce-settings,
|
||||
.ce-block-settings,
|
||||
.ce-toolbar__plus,
|
||||
.ce-toolbar__settings-btn,
|
||||
.ce-popover__item,
|
||||
.ce-popover__items,
|
||||
.ce-settings__button,
|
||||
.ce-toolbar__content,
|
||||
.ce-toolbar__actions {
|
||||
z-index: 10 !important;
|
||||
}
|
||||
header,
|
||||
header[class*="sticky"],
|
||||
header[class*="fixed"],
|
||||
header.sticky,
|
||||
header.fixed {
|
||||
z-index: 99999 !important;
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
|
||||
<script>
|
||||
// Apply dark mode immediately if stored (before Alpine loads)
|
||||
(function() {
|
||||
const darkMode = JSON.parse(localStorage.getItem('darkMode') || 'false');
|
||||
if (darkMode) {
|
||||
document.documentElement.classList.add('dark');
|
||||
}
|
||||
})();
|
||||
|
||||
// Initialize Alpine store for dark mode
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('darkMode', {
|
||||
enabled: JSON.parse(localStorage.getItem('darkMode') || 'false'),
|
||||
toggle() {
|
||||
this.enabled = !this.enabled;
|
||||
localStorage.setItem('darkMode', JSON.stringify(this.enabled));
|
||||
if (this.enabled) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body
|
||||
x-data="{ sidebarToggle: false }"
|
||||
:class="{'bg-gray-900': $store.darkMode.enabled}"
|
||||
>
|
||||
<!-- ===== Page Wrapper Start ===== -->
|
||||
<div class="flex h-screen overflow-hidden">
|
||||
<!-- ===== Sidebar Start ===== -->
|
||||
<?= $this->include('admin/partials/sidebar') ?>
|
||||
<!-- ===== Sidebar End ===== -->
|
||||
|
||||
<!-- ===== Content Area Start ===== -->
|
||||
<div class="relative flex flex-col flex-1 overflow-x-hidden overflow-y-auto">
|
||||
<!-- ===== Header Start ===== -->
|
||||
<?= $this->include('admin/partials/navbar') ?>
|
||||
<!-- ===== Header End ===== -->
|
||||
|
||||
<!-- ===== Main Content Start ===== -->
|
||||
<main>
|
||||
<div class="p-4 mx-auto max-w-7xl md:p-6">
|
||||
<?php if (session()->getFlashdata('success')): ?>
|
||||
<div class="mb-4 p-4 bg-green-50 border border-green-200 rounded-lg">
|
||||
<p class="text-sm text-green-800"><?= esc(session()->getFlashdata('success')) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (session()->getFlashdata('error')): ?>
|
||||
<div class="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p class="text-sm text-red-800"><?= esc(session()->getFlashdata('error')) ?></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?= $this->renderSection('content') ?>
|
||||
</div>
|
||||
</main>
|
||||
<!-- ===== Main Content End ===== -->
|
||||
</div>
|
||||
<!-- ===== Content Area End ===== -->
|
||||
</div>
|
||||
<!-- ===== Page Wrapper End ===== -->
|
||||
|
||||
<script src="<?= base_url('assets/js/app.js') ?>"></script>
|
||||
<script>
|
||||
// CSRF Helper for AJAX/Fetch requests
|
||||
function withCsrf(options = {}) {
|
||||
const csrfToken = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');
|
||||
const csrfHeader = document.querySelector('meta[name="csrf-header"]')?.getAttribute('content');
|
||||
|
||||
if (!csrfToken || !csrfHeader) {
|
||||
console.warn('CSRF token not found');
|
||||
return options;
|
||||
}
|
||||
|
||||
// Merge headers
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
[csrfHeader]: csrfToken,
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
// Override fetch to automatically include CSRF token
|
||||
const originalFetch = window.fetch;
|
||||
window.fetch = function(url, options = {}) {
|
||||
// Only add CSRF for same-origin POST/PUT/DELETE requests
|
||||
if (typeof url === 'string' && (url.startsWith('/') || url.startsWith(window.location.origin))) {
|
||||
const method = (options.method || 'GET').toUpperCase();
|
||||
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(method)) {
|
||||
options = withCsrf(options);
|
||||
}
|
||||
}
|
||||
return originalFetch(url, options);
|
||||
};
|
||||
|
||||
// Update CSRF token in meta tags after form submission
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Listen for form submissions and update CSRF token from response
|
||||
document.addEventListener('submit', function(e) {
|
||||
// After form submit, the new CSRF token will be in the response
|
||||
// We'll update it when the page reloads or via AJAX response
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= $this->renderSection('scripts') ?>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user