Files
ares/views/admin/layout.html
Beyhan Oğur 4d92991817 first commit
2026-04-26 21:30:42 +03:00

442 lines
20 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="tr" data-bs-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Paneli</title>
<!-- Bootstrap CSS -->
<link href="/assets/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Admin Theme CSS -->
<link href="/admin/css/theme.css" rel="stylesheet">
<!-- HTMX -->
<script src="/assets/htmx/htmx.js"></script>
<!-- jQuery -->
<script src="/assets/jquery/dist/jquery.min.js"></script>
<!-- Alpine.js -->
<script defer src="/assets/alpinejs/dist/cdn.min.js"></script>
<!-- SweetAlert2 -->
<link rel="stylesheet" href="/assets/sweetalert2/dist/sweetalert2.min.css">
<script src="/assets/sweetalert2/dist/sweetalert2.all.min.js"></script>
<!-- Main JS -->
<script src="/admin/js/main.js"></script>
</head>
<body x-data>
<!-- Sidebar -->
<nav id="sidebar" :class="{ 'show': $store.sidebar.open }">
<div class="sidebar-header">
<h5 class="mb-0">AdminPanel</h5>
</div>
<div class="sidebar-nav list-group list-group-flush mt-3">
<a href="#" class="nav-link active" hx-get="/admin/content/dashboard" hx-target="#main-content"
hx-push-url="true">
<i class="bi bi-speedometer2 me-2"></i> Dashboard
</a>
<a href="#" class="nav-link" hx-get="/admin/content/users" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-people me-2"></i> Kullanıcılar
</a>
<div class="nav-item">
<a class="nav-link collapsed" data-bs-toggle="collapse" href="#blogMenu" role="button"
aria-expanded="false" aria-controls="blogMenu">
<i class="bi bi-journal-text me-2"></i> Blog
</a>
<div class="collapse" id="blogMenu">
<div class="list-group list-group-flush ms-3">
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/posts"
hx-target="#main-content" hx-push-url="true">Yazılar</a>
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/categories"
hx-target="#main-content" hx-push-url="true">Kategoriler</a>
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/tags"
hx-target="#main-content" hx-push-url="true">Taglar</a>
<a href="#" class="list-group-item list-group-item-action"
hx-get="/admin/content/category-views" hx-target="#main-content" hx-push-url="true">Kategori
Görünümleri</a>
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/comments"
hx-target="#main-content" hx-push-url="true">Yorumlar</a>
</div>
</div>
</div>
<div class="nav-item">
<a class="nav-link collapsed" data-bs-toggle="collapse" href="#productMenu" role="button"
aria-expanded="false" aria-controls="productMenu">
<i class="bi bi-box me-2"></i> Ürünler
</a>
<div class="collapse" id="productMenu">
<div class="list-group list-group-flush ms-3">
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/products"
hx-target="#main-content" hx-push-url="true">Ürünler</a>
<a href="#" class="list-group-item list-group-item-action"
hx-get="/admin/content/product-categories" hx-target="#main-content"
hx-push-url="true">Kategoriler</a>
<a href="#" class="list-group-item list-group-item-action" hx-get="/admin/content/product-tags"
hx-target="#main-content" hx-push-url="true">Etiketler</a>
<!-- Sepetler (Carts) -->
<a href="#" class="list-group-item list-group-item-action border-top mt-2"
hx-get="/admin/content/carts" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-cart3 me-1"></i> Sepetler
</a>
<!-- Ürün İçgörüleri -->
<a href="#" class="list-group-item list-group-item-action"
hx-get="/admin/content/product-comments" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-chat-left-text me-1"></i> Yorumlar
</a>
<a href="#" class="list-group-item list-group-item-action"
hx-get="/admin/content/product-category-views" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-eye me-1"></i> Görüntülenmeler
</a>
</div>
</div>
</div>
<a href="#" class="nav-link" hx-get="/admin/content/settings" hx-target="#main-content" hx-push-url="true">
<i class="bi bi-gear me-2"></i> Ayarlar
</a>
<a href="/logout" class="nav-link text-danger mt-5">
<i class="bi bi-box-arrow-right me-2"></i> Çıkış
</a>
</div>
</nav>
<!-- Header -->
<header id="main-header">
<button class="btn btn-link link-body-emphasis d-lg-none me-3" @click="$store.sidebar.toggle()">
<i class="bi bi-list fs-4"></i>
</button>
<h5 class="mb-0 d-none d-lg-block">Dashboard</h5>
<div class="ms-auto d-flex align-items-center gap-3">
<!-- Theme Toggle -->
<button class="btn btn-link nav-link" @click="$store.theme.toggle()">
<i class="bi" :class="$store.theme.mode === 'light' ? 'bi-moon-stars' : 'bi-sun'"></i>
<span class="d-none d-md-inline ms-1"
x-text="$store.theme.mode === 'light' ? 'Koyu Mod' : 'Aydınlık Mod'"></span>
</button>
<!-- User Dropdown (Optional) -->
<div class="dropdown">
<a href="#" class="d-flex align-items-center link-body-emphasis text-decoration-none dropdown-toggle"
data-bs-toggle="dropdown" aria-expanded="false">
<img id="adminAvatar" src="https://github.com/mdo.png" alt="" width="32" height="32"
class="rounded-circle me-2">
<strong id="adminName">Admin</strong>
</a>
<ul class="dropdown-menu dropdown-menu-end text-small shadow">
<li><a class="dropdown-item" href="#">Profil</a></li>
<li>
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item" href="/logout">Çıkış Yap</a></li>
</ul>
</div>
</div>
</header>
<!-- Main Content -->
<div id="main-content-wrapper">
<main id="main-content" class="p-4 fade-in">
{{embed}}
</main>
</div>
<!-- Global Modal Container for HTMX -->
<div class="modal fade" id="userModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content" id="modal-area">
<!-- Modal Content will be loaded here via HTMX -->
</div>
</div>
</div>
<!-- Bootstrap Bundle with Popper -->
<script src="/assets/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('htmx:afterSwap', function (event) {
// Only handle modal area swaps
if (event.detail.target.id === 'modal-area') {
// If the response is not empty (i.e., form content or error), show the modal
if (event.detail.target.innerHTML.trim() !== "") {
const el = document.getElementById('userModal');
let modal = bootstrap.Modal.getInstance(el);
if (!modal) {
modal = new bootstrap.Modal(el);
}
modal.show();
}
}
});
// Initialize Quill if injected via HTMX into any target
document.addEventListener('htmx:afterSwap', function (event) {
try {
var target = event.detail.target;
if (!target || !target.querySelector) return;
var editorEl = target.querySelector('#quillEditor');
if (!editorEl) return;
function doInit() {
if (editorEl.__quillInitialized) return;
var textarea = target.querySelector('#contentInput');
var quill = new Quill(editorEl, {
theme: 'snow',
modules: {
toolbar: {
container: [['bold', 'italic', 'underline'], [{ 'list': 'ordered' }, { 'list': 'bullet' }], ['link', 'image'], ['code-block'], ['clean']],
handlers: {
image: function () {
try { window.showImageModal(quill); } catch (e) { console.error('open image modal failed', e); }
}
}
}
}
});
editorEl.__quillInitialized = true;
if (textarea && textarea.value && textarea.value.trim() !== '') {
quill.clipboard.dangerouslyPasteHTML(textarea.value);
}
if (textarea && textarea.form) {
textarea.form.addEventListener('submit', function () { textarea.value = quill.root.innerHTML; });
}
}
if (window.Quill) {
doInit();
} else {
// load CSS if missing
if (!document.querySelector('link[href="/assets/quill/dist/quill.snow.css"]')) {
var l = document.createElement('link'); l.rel = 'stylesheet'; l.href = '/assets/quill/dist/quill.snow.css'; document.head.appendChild(l);
}
if (!document.querySelector('script[src="/assets/quill/dist/quill.js"]')) {
var s = document.createElement('script'); s.src = '/assets/quill/dist/quill.js'; s.onload = doInit; document.body.appendChild(s);
} else {
// script already present, try init shortly
setTimeout(doInit, 50);
}
}
} catch (e) {
console.error('Quill HTMX init error', e);
}
});
// Global SweetAlert2 Helpers
const Toast = Swal.mixin({
toast: true,
position: 'top-end',
showConfirmButton: false,
timer: 3000,
timerProgressBar: true,
didOpen: (toast) => {
toast.addEventListener('mouseenter', Swal.stopTimer)
toast.addEventListener('mouseleave', Swal.resumeTimer)
}
});
function showToast(icon, title) {
Toast.fire({
icon: icon,
title: title
});
}
function confirmDelete(event, form) {
event.preventDefault();
Swal.fire({
title: 'Emin misiniz?',
text: "Bu işlem geri alınamaz!",
icon: 'warning',
showCancelButton: true,
confirmButtonColor: '#d33',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Evet, Sil!',
cancelButtonText: 'İptal'
}).then((result) => {
if (result.isConfirmed) {
form.submit();
}
});
return false;
}
function confirmRestore(event, form) {
event.preventDefault();
Swal.fire({
title: 'Geri Yükle?',
text: "Bu kullanıcı tekrar aktif edilecek.",
icon: 'question',
showCancelButton: true,
confirmButtonColor: '#28a745',
cancelButtonColor: '#3085d6',
confirmButtonText: 'Evet, Geri Yükle!',
cancelButtonText: 'İptal'
}).then((result) => {
if (result.isConfirmed) {
form.submit();
}
});
return false;
}
// Check for success/error parameters in URL
document.addEventListener('DOMContentLoaded', () => {
const urlParams = new URLSearchParams(window.location.search);
const successMsg = urlParams.get('success');
const errorMsg = urlParams.get('error');
if (successMsg) {
showToast('success', successMsg);
// Clean URL
window.history.replaceState({}, document.title, window.location.pathname);
}
if (errorMsg) {
showToast('error', errorMsg);
window.history.replaceState({}, document.title, window.location.pathname);
}
});
// Listen for the closeModal event triggered by HX-Trigger
document.addEventListener('closeModal', function () {
const el = document.getElementById('userModal');
const modal = bootstrap.Modal.getInstance(el);
if (modal) {
modal.hide();
// Clear content after hiding
setTimeout(() => {
document.getElementById('modal-area').innerHTML = "";
}, 500);
}
});
</script>
<script>
// Populate admin dropdown with real name/email/avatar
document.addEventListener('DOMContentLoaded', function () {
fetch('/admin/me', { credentials: 'same-origin' })
.then(function (res) { if (!res.ok) throw new Error('no'); return res.json(); })
.then(function (data) {
if (!data) return;
try {
var nameEl = document.getElementById('adminName');
var avatarEl = document.getElementById('adminAvatar');
if (nameEl) nameEl.textContent = data.name || data.email || 'Admin';
if (avatarEl && data.avatar) avatarEl.src = data.avatar;
} catch (e) { }
}).catch(function () { /* ignore */ });
});
</script>
<style>
/* Ensure editor images behave */
.ql-figure img {
display: block;
margin: auto;
max-width: 100%;
height: auto;
}
</style>
<!-- Image Modal (global for admin) -->
<div class="modal fade" id="imageModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Resim Ekle</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Kapat"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<label class="form-label">Resim URL</label>
<input id="imageUrlInput" class="form-control" placeholder="https://...">
</div>
<div class="mb-2 row g-2">
<div class="col-6"><input id="imageWidthInput" class="form-control" placeholder="Genişlik (px)">
</div>
<div class="col-6"><input id="imageHeightInput" class="form-control"
placeholder="Yükseklik (px)"></div>
</div>
<div class="mb-2">
<img id="imagePreviewModal" src="" alt="preview"
style="display:none;max-width:100%;border:1px solid #ddd;padding:4px;">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">İptal</button>
<button id="imageInsertBtn" type="button" class="btn btn-primary">Ekle</button>
</div>
</div>
</div>
</div>
<script>
// Register a block embed blot to hold images so code-block doesn't swallow them
(function registerFigureBlot() {
if (!window.Quill) return;
try {
var BlockEmbed = Quill.import('blots/block/embed');
class FigureBlot extends BlockEmbed {
static blotName = 'figure';
static tagName = 'figure';
static className = 'ql-figure';
static create(value) {
let node = super.create();
let img = document.createElement('img');
img.setAttribute('src', value.src);
if (value.style) img.style.cssText = value.style;
node.appendChild(img);
return node;
}
static value(node) {
let img = node.querySelector('img');
return { src: img.getAttribute('src'), style: img.style.cssText };
}
}
Quill.register(FigureBlot);
} catch (e) {
// ignore if already registered or Quill not ready
console.debug('FigureBlot register failed', e);
}
})();
// Show image modal and insert into provided quill instance
window.showImageModal = function (quill) {
if (!quill) return;
const modalEl = document.getElementById('imageModal');
const urlInput = document.getElementById('imageUrlInput');
const wInput = document.getElementById('imageWidthInput');
const hInput = document.getElementById('imageHeightInput');
const preview = document.getElementById('imagePreviewModal');
urlInput.value = '';
wInput.value = '';
hInput.value = '';
preview.style.display = 'none';
urlInput.addEventListener('input', function onChange() {
const v = urlInput.value.trim();
if (v) { preview.src = v; preview.style.display = 'block'; } else { preview.style.display = 'none'; }
});
var bsModal = new bootstrap.Modal(modalEl);
bsModal.show();
const insertBtn = document.getElementById('imageInsertBtn');
const handler = function () {
let src = urlInput.value.trim();
if (!src) return alert('URL girin');
if (src.charAt(0) === '/') src = window.location.origin + src;
let w = parseInt(wInput.value) || 0;
let h = parseInt(hInput.value) || 0;
let style = 'max-width:100%;height:auto;';
if (w) style = 'width:' + w + 'px;' + (h ? 'height:' + h + 'px;' : 'height:auto;');
if (h && !w) style = 'height:' + h + 'px;max-width:100%;';
var range = quill.getSelection(true) || { index: quill.getLength() };
quill.insertEmbed(range.index, 'figure', { src: src, style: style });
bsModal.hide();
insertBtn.removeEventListener('click', handler);
};
insertBtn.addEventListener('click', handler);
};
</script>
</body>
</html>