409 lines
12 KiB
HTML
409 lines
12 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>Menu Management - Sky Art Shop</title>
|
|
<link
|
|
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
|
|
rel="stylesheet"
|
|
/>
|
|
<link
|
|
rel="stylesheet"
|
|
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"
|
|
/>
|
|
<link rel="stylesheet" href="/admin/css/admin-style.css" />
|
|
<style>
|
|
.menu-item {
|
|
background: white;
|
|
border: 2px solid #e9ecef;
|
|
border-radius: 12px;
|
|
padding: 20px;
|
|
margin-bottom: 15px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
transition: all 0.3s ease;
|
|
cursor: move;
|
|
}
|
|
.menu-item:hover {
|
|
border-color: #667eea;
|
|
transform: translateX(5px);
|
|
}
|
|
.menu-item-content {
|
|
flex: 1;
|
|
}
|
|
.menu-item-actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
}
|
|
.drag-handle {
|
|
cursor: grab;
|
|
color: #6c757d;
|
|
margin-right: 15px;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="sidebar">
|
|
<div class="sidebar-brand">🛍️ Sky Art Shop</div>
|
|
<ul class="sidebar-menu">
|
|
<li>
|
|
<a href="/admin/dashboard"
|
|
><i class="bi bi-speedometer2"></i> Dashboard</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/homepage"
|
|
><i class="bi bi-house"></i> Homepage Editor</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/products"><i class="bi bi-box"></i> Products</a>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/portfolio"
|
|
><i class="bi bi-easel"></i> Portfolio</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/blog"><i class="bi bi-newspaper"></i> Blog</a>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/pages"
|
|
><i class="bi bi-file-text"></i> Custom Pages</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/media-library"
|
|
><i class="bi bi-images"></i> Media Library</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/menu" class="active"
|
|
><i class="bi bi-list"></i> Menu</a
|
|
>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/settings"><i class="bi bi-gear"></i> Settings</a>
|
|
</li>
|
|
<li>
|
|
<a href="/admin/users"><i class="bi bi-people"></i> Users</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div class="main-content">
|
|
<div class="top-bar">
|
|
<div>
|
|
<h3>Menu Management</h3>
|
|
<p class="mb-0 text-muted">Organize your website navigation</p>
|
|
</div>
|
|
<div>
|
|
<button class="btn-logout" onclick="logout()">
|
|
<i class="bi bi-box-arrow-right"></i> Logout
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="actions-bar">
|
|
<button class="btn btn-primary" onclick="showAddMenuItem()">
|
|
<i class="bi bi-plus-circle"></i> Add Menu Item
|
|
</button>
|
|
<button class="btn btn-success" onclick="saveMenuOrder()">
|
|
<i class="bi bi-save"></i> Save Order
|
|
</button>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="p-4">
|
|
<h5 class="mb-3">Main Navigation Menu</h5>
|
|
<small class="text-muted">Drag and drop to reorder menu items</small>
|
|
<div id="menuItems" class="mt-3">
|
|
<div class="text-center p-4">
|
|
<div class="loading-spinner"></div>
|
|
Loading menu items...
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Add/Edit Menu Item Modal -->
|
|
<div class="modal fade" id="menuModal" tabindex="-1">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="modalTitle">Add Menu Item</h5>
|
|
<button
|
|
type="button"
|
|
class="btn-close"
|
|
data-bs-dismiss="modal"
|
|
></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<form id="menuForm">
|
|
<input type="hidden" id="menuItemId" />
|
|
|
|
<div class="mb-3">
|
|
<label for="menuLabel" class="form-label">Label *</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
id="menuLabel"
|
|
required
|
|
placeholder="Home, Shop, About..."
|
|
/>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="menuUrl" class="form-label">URL *</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
id="menuUrl"
|
|
required
|
|
placeholder="/shop, /about, /contact"
|
|
/>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label for="menuIcon" class="form-label">Icon (optional)</label>
|
|
<input
|
|
type="text"
|
|
class="form-control"
|
|
id="menuIcon"
|
|
placeholder="bi-house, bi-shop, etc."
|
|
/>
|
|
<small class="text-muted">Bootstrap Icon name</small>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<div class="form-check form-switch">
|
|
<input
|
|
class="form-check-input"
|
|
type="checkbox"
|
|
id="menuVisible"
|
|
checked
|
|
/>
|
|
<label class="form-check-label" for="menuVisible">
|
|
Visible in menu
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button
|
|
type="button"
|
|
class="btn btn-secondary"
|
|
data-bs-dismiss="modal"
|
|
>
|
|
Cancel
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary"
|
|
onclick="saveMenuItem()"
|
|
>
|
|
<i class="bi bi-save"></i> Save
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
<script>
|
|
let menuItemsData = [];
|
|
let menuModal;
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
menuModal = new bootstrap.Modal(document.getElementById("menuModal"));
|
|
checkAuth().then((authenticated) => {
|
|
if (authenticated) {
|
|
loadMenuItems();
|
|
}
|
|
});
|
|
});
|
|
|
|
async function loadMenuItems() {
|
|
try {
|
|
const response = await fetch("/api/admin/menu", {
|
|
credentials: "include",
|
|
});
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
menuItemsData = data.items || [];
|
|
renderMenuItems();
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load menu items:", error);
|
|
menuItemsData = [];
|
|
renderMenuItems();
|
|
}
|
|
}
|
|
|
|
function renderMenuItems() {
|
|
const container = document.getElementById("menuItems");
|
|
if (menuItemsData.length === 0) {
|
|
container.innerHTML = `
|
|
<div class="text-center p-4">
|
|
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
|
|
<p class="mt-3 text-muted">No menu items yet</p>
|
|
<button class="btn btn-primary" onclick="showAddMenuItem()">
|
|
<i class="bi bi-plus-circle"></i> Add Your First Menu Item
|
|
</button>
|
|
</div>`;
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = menuItemsData
|
|
.map(
|
|
(item, index) => `
|
|
<div class="menu-item" draggable="true" data-index="${index}">
|
|
<div class="d-flex align-items-center flex-grow-1">
|
|
<i class="bi bi-grip-vertical drag-handle"></i>
|
|
<div class="menu-item-content">
|
|
<strong>${item.label}</strong>
|
|
<div class="text-muted small">${item.url}</div>
|
|
</div>
|
|
</div>
|
|
<div class="menu-item-actions">
|
|
<span class="badge ${
|
|
item.visible ? "badge-success" : "badge-danger"
|
|
}">
|
|
${item.visible ? "Visible" : "Hidden"}
|
|
</span>
|
|
<button class="btn btn-sm btn-info" onclick="editMenuItem(${index})">
|
|
<i class="bi bi-pencil"></i>
|
|
</button>
|
|
<button class="btn btn-sm btn-danger" onclick="deleteMenuItem(${index})">
|
|
<i class="bi bi-trash"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`
|
|
)
|
|
.join("");
|
|
|
|
// Add drag and drop functionality
|
|
addDragAndDrop();
|
|
}
|
|
|
|
function addDragAndDrop() {
|
|
const items = document.querySelectorAll(".menu-item");
|
|
items.forEach((item) => {
|
|
item.addEventListener("dragstart", handleDragStart);
|
|
item.addEventListener("dragover", handleDragOver);
|
|
item.addEventListener("drop", handleDrop);
|
|
item.addEventListener("dragend", handleDragEnd);
|
|
});
|
|
}
|
|
|
|
let draggedItem = null;
|
|
|
|
function handleDragStart(e) {
|
|
draggedItem = this;
|
|
e.dataTransfer.effectAllowed = "move";
|
|
}
|
|
|
|
function handleDragOver(e) {
|
|
if (e.preventDefault) e.preventDefault();
|
|
e.dataTransfer.dropEffect = "move";
|
|
return false;
|
|
}
|
|
|
|
function handleDrop(e) {
|
|
if (e.stopPropagation) e.stopPropagation();
|
|
if (draggedItem !== this) {
|
|
const fromIndex = parseInt(draggedItem.dataset.index);
|
|
const toIndex = parseInt(this.dataset.index);
|
|
const item = menuItemsData.splice(fromIndex, 1)[0];
|
|
menuItemsData.splice(toIndex, 0, item);
|
|
renderMenuItems();
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function handleDragEnd(e) {
|
|
draggedItem = null;
|
|
}
|
|
|
|
function showAddMenuItem() {
|
|
document.getElementById("modalTitle").textContent = "Add Menu Item";
|
|
document.getElementById("menuForm").reset();
|
|
document.getElementById("menuItemId").value = "";
|
|
document.getElementById("menuVisible").checked = true;
|
|
menuModal.show();
|
|
}
|
|
|
|
function editMenuItem(index) {
|
|
const item = menuItemsData[index];
|
|
document.getElementById("modalTitle").textContent = "Edit Menu Item";
|
|
document.getElementById("menuItemId").value = index;
|
|
document.getElementById("menuLabel").value = item.label;
|
|
document.getElementById("menuUrl").value = item.url;
|
|
document.getElementById("menuIcon").value = item.icon || "";
|
|
document.getElementById("menuVisible").checked = item.visible !== false;
|
|
menuModal.show();
|
|
}
|
|
|
|
function saveMenuItem() {
|
|
const index = document.getElementById("menuItemId").value;
|
|
const item = {
|
|
label: document.getElementById("menuLabel").value,
|
|
url: document.getElementById("menuUrl").value,
|
|
icon: document.getElementById("menuIcon").value,
|
|
visible: document.getElementById("menuVisible").checked,
|
|
};
|
|
|
|
if (!item.label || !item.url) {
|
|
alert("Label and URL are required");
|
|
return;
|
|
}
|
|
|
|
if (index === "") {
|
|
menuItemsData.push(item);
|
|
} else {
|
|
menuItemsData[parseInt(index)] = item;
|
|
}
|
|
|
|
menuModal.hide();
|
|
renderMenuItems();
|
|
saveMenuOrder();
|
|
}
|
|
|
|
function deleteMenuItem(index) {
|
|
if (confirm("Are you sure you want to delete this menu item?")) {
|
|
menuItemsData.splice(index, 1);
|
|
renderMenuItems();
|
|
saveMenuOrder();
|
|
}
|
|
}
|
|
|
|
async function saveMenuOrder() {
|
|
try {
|
|
const response = await fetch("/api/admin/menu", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
credentials: "include",
|
|
body: JSON.stringify({ items: menuItemsData }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
alert("Menu saved successfully!");
|
|
} else {
|
|
alert("Failed to save menu: " + (data.message || ""));
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to save menu:", error);
|
|
alert("Failed to save menu");
|
|
}
|
|
}
|
|
</script>
|
|
<script src="/admin/js/auth.js"></script>
|
|
</body>
|
|
</html>
|