Restructure: move all files from Public HTML/ to root for Hostinger deployment
This commit is contained in:
Executable
+512
@@ -0,0 +1,512 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow, noarchive">
|
||||
<title>Pages Management - MSPE Admin</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha384-t1nt8BQoYMLFN5p42tRAtuAAFQaCQODekUVeKKZrEnEyp4H2R0RHFz0KWpmj7i8g" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/admin/css/admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="admin-wrapper">
|
||||
<!-- Sidebar -->
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<a href="/admin/dashboard.html" class="sidebar-logo">
|
||||
<i class="fas fa-cube"></i>
|
||||
<span>MSPE</span>
|
||||
</a>
|
||||
<button class="sidebar-toggle" id="sidebar-toggle">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav">
|
||||
<div class="nav-section">
|
||||
<span class="nav-section-title">Main</span>
|
||||
<ul class="nav-menu">
|
||||
<li class="nav-item">
|
||||
<a href="/admin/dashboard.html" class="nav-link">
|
||||
<i class="fas fa-th-large"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<span class="nav-section-title">Content</span>
|
||||
<ul class="nav-menu">
|
||||
<li class="nav-item active">
|
||||
<a href="/admin/pages.html" class="nav-link">
|
||||
<i class="fas fa-file-alt"></i>
|
||||
<span>Pages</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/services.html" class="nav-link">
|
||||
<i class="fas fa-concierge-bell"></i>
|
||||
<span>Services</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/portfolio.html" class="nav-link">
|
||||
<i class="fas fa-briefcase"></i>
|
||||
<span>Portfolio</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/news.html" class="nav-link">
|
||||
<i class="fas fa-newspaper"></i>
|
||||
<span>News & Events</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/testimonials.html" class="nav-link">
|
||||
<i class="fas fa-quote-right"></i>
|
||||
<span>Testimonials</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/team.html" class="nav-link">
|
||||
<i class="fas fa-users"></i>
|
||||
<span>Team</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<span class="nav-section-title">Media</span>
|
||||
<ul class="nav-menu">
|
||||
<li class="nav-item">
|
||||
<a href="/admin/media.html" class="nav-link">
|
||||
<i class="fas fa-images"></i>
|
||||
<span>Media Library</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<span class="nav-section-title">Inquiries</span>
|
||||
<ul class="nav-menu">
|
||||
<li class="nav-item">
|
||||
<a href="/admin/messages.html" class="nav-link">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<span>Messages</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/subscribers.html" class="nav-link">
|
||||
<i class="fas fa-bell"></i>
|
||||
<span>Subscribers</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/bookings.html" class="nav-link">
|
||||
<i class="fas fa-calendar-check"></i>
|
||||
<span>Bookings</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<span class="nav-section-title">Settings</span>
|
||||
<ul class="nav-menu">
|
||||
<li class="nav-item">
|
||||
<a href="/admin/settings.html" class="nav-link">
|
||||
<i class="fas fa-cog"></i>
|
||||
<span>Site Settings</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a href="/admin/users.html" class="nav-link">
|
||||
<i class="fas fa-user-shield"></i>
|
||||
<span>Admin Users</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<a href="/index.html" class="view-site-btn" target="_blank">
|
||||
<i class="fas fa-external-link-alt"></i>
|
||||
<span>View Website</span>
|
||||
</a>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="main-content">
|
||||
<!-- Header -->
|
||||
<header class="admin-header">
|
||||
<div class="header-left">
|
||||
<button class="mobile-toggle" id="mobile-toggle">
|
||||
<i class="fas fa-bars"></i>
|
||||
</button>
|
||||
<h1 class="page-title">Pages Management</h1>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<div class="header-user">
|
||||
<div class="user-avatar">
|
||||
<img src="https://ui-avatars.com/api/?name=Admin&background=0ea5e9&color=fff" alt="Admin">
|
||||
</div>
|
||||
<div class="user-info">
|
||||
<span class="user-name">Admin</span>
|
||||
<span class="user-role">Administrator</span>
|
||||
</div>
|
||||
<div class="user-dropdown">
|
||||
<button class="dropdown-toggle">
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/admin/settings.html"><i class="fas fa-cog"></i> Settings</a></li>
|
||||
<li class="divider"></li>
|
||||
<li><a href="#" id="logout-btn"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Content -->
|
||||
<div class="content-wrapper">
|
||||
<div class="content-header">
|
||||
<p class="content-description">Manage your website pages and their content sections</p>
|
||||
</div>
|
||||
|
||||
<!-- Pages Grid (loaded dynamically) -->
|
||||
<div class="pages-grid" id="pages-grid">
|
||||
<div style="grid-column:1/-1;text-align:center;padding:3rem;color:var(--gray-400);">
|
||||
<i class="fas fa-spinner fa-spin fa-2x"></i>
|
||||
<p style="margin-top:1rem;">Loading pages...</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Global Sections -->
|
||||
<div class="card" style="margin-top: 2rem;">
|
||||
<div class="card-header">
|
||||
<h3><i class="fas fa-layer-group"></i> Global Sections</h3>
|
||||
<p class="card-subtitle">These sections appear across multiple pages</p>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="global-sections-grid" id="global-sections-grid">
|
||||
<div style="text-align:center;padding:2rem;color:var(--gray-400);grid-column:1/-1;">
|
||||
<i class="fas fa-spinner fa-spin"></i> Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- Page Editor Modal -->
|
||||
<div class="modal" id="page-modal">
|
||||
<div class="modal-overlay" onclick="closePageModal()"></div>
|
||||
<div class="modal-content" style="max-width:700px;">
|
||||
<div class="modal-header">
|
||||
<h3 id="page-modal-title">Edit Page</h3>
|
||||
<button class="modal-close" onclick="closePageModal()"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<div class="modal-body" style="max-height:70vh;overflow-y:auto;">
|
||||
<form id="page-form">
|
||||
<input type="hidden" id="page-slug">
|
||||
|
||||
<!-- SEO & Meta -->
|
||||
<div style="margin-bottom:1.5rem;">
|
||||
<h4 style="font-size:.875rem;font-weight:600;color:var(--dark);margin-bottom:.75rem;display:flex;align-items:center;gap:.5rem;">
|
||||
<i class="fas fa-search" style="color:var(--primary);"></i> SEO & Metadata
|
||||
</h4>
|
||||
<div class="form-group" style="margin-bottom:.75rem;">
|
||||
<label>Meta Title</label>
|
||||
<input type="text" class="form-control" id="page-meta-title" placeholder="Page title for search engines">
|
||||
<small style="color:var(--gray-400);font-size:.75rem;">Recommended: 50–60 characters</small>
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom:.75rem;">
|
||||
<label>Meta Description</label>
|
||||
<textarea class="form-control" id="page-meta-desc" rows="2" placeholder="Brief description for search results"></textarea>
|
||||
<small style="color:var(--gray-400);font-size:.75rem;">Recommended: 150–160 characters</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Status</label>
|
||||
<select class="form-control" id="page-status">
|
||||
<option value="published">Published</option>
|
||||
<option value="draft">Draft</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sections -->
|
||||
<div id="page-sections-editor"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-outline" onclick="closePageModal()">Cancel</button>
|
||||
<button class="btn btn-primary" onclick="savePage()"><i class="fas fa-save"></i> Save Changes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Global Section Editor Modal -->
|
||||
<div class="modal" id="global-modal">
|
||||
<div class="modal-overlay" onclick="closeGlobalModal()"></div>
|
||||
<div class="modal-content" style="max-width:550px;">
|
||||
<div class="modal-header">
|
||||
<h3 id="global-modal-title">Edit Section</h3>
|
||||
<button class="modal-close" onclick="closeGlobalModal()"><i class="fas fa-times"></i></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="global-form">
|
||||
<input type="hidden" id="global-slug">
|
||||
<div id="global-fields-editor"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-outline" onclick="closeGlobalModal()">Cancel</button>
|
||||
<button class="btn btn-primary" onclick="saveGlobalSection()"><i class="fas fa-save"></i> Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/admin/js/admin.js"></script>
|
||||
<script>
|
||||
let allPages = [];
|
||||
let globalSections = [];
|
||||
let currentPageData = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadPages();
|
||||
loadGlobalSections();
|
||||
});
|
||||
|
||||
// ── Load & Render ────────────────────────────────────
|
||||
|
||||
async function loadPages() {
|
||||
try {
|
||||
const res = await MSPE.API.get('pages.php');
|
||||
if (res && res.success) {
|
||||
allPages = res.data;
|
||||
renderPages(allPages);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading pages:', err);
|
||||
document.getElementById('pages-grid').innerHTML =
|
||||
'<div class="empty-state" style="grid-column:1/-1;"><i class="fas fa-exclamation-circle"></i><p>Failed to load pages</p></div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderPages(pages) {
|
||||
const grid = document.getElementById('pages-grid');
|
||||
if (!pages.length) {
|
||||
grid.innerHTML = '<div class="empty-state" style="grid-column:1/-1;"><i class="fas fa-file-alt"></i><p>No pages found</p></div>';
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = pages.map(page => {
|
||||
const sectionTags = page.sections.map(s => `<span class="section-tag">${s.label || s}</span>`).join('');
|
||||
const statusClass = page.status === 'draft' ? 'draft' : 'published';
|
||||
const statusLabel = page.status === 'draft' ? 'Draft' : 'Published';
|
||||
const lastSaved = page.updated_at ? `<span style="font-size:.7rem;color:var(--gray-400);display:block;margin-top:.25rem;">Last saved: ${MSPE.formatDate(page.updated_at)}</span>` : '';
|
||||
return `
|
||||
<div class="page-admin-card">
|
||||
<div class="page-admin-header">
|
||||
<div class="page-icon"><i class="fas ${page.icon || 'fa-file-alt'}"></i></div>
|
||||
<div class="page-status ${statusClass}"><i class="fas fa-circle"></i> ${statusLabel}</div>
|
||||
</div>
|
||||
<h3>${page.title}</h3>
|
||||
<p>${page.description || ''}${lastSaved}</p>
|
||||
<div class="page-sections">${sectionTags}</div>
|
||||
<div class="page-admin-actions">
|
||||
<a href="${page.url || '#'}" target="_blank" class="btn btn-sm btn-outline">
|
||||
<i class="fas fa-eye"></i> View
|
||||
</a>
|
||||
<button class="btn btn-sm btn-primary" onclick="editPage('${page.slug}')">
|
||||
<i class="fas fa-edit"></i> Edit
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function loadGlobalSections() {
|
||||
try {
|
||||
const res = await MSPE.API.get('pages.php?type=global');
|
||||
if (res && res.success) {
|
||||
globalSections = res.data;
|
||||
renderGlobalSections(globalSections);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error loading global sections:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function renderGlobalSections(sections) {
|
||||
const grid = document.getElementById('global-sections-grid');
|
||||
grid.innerHTML = sections.map(s => `
|
||||
<div class="global-section-item">
|
||||
<i class="fas ${s.icon}"></i>
|
||||
<div>
|
||||
<h4>${s.label}</h4>
|
||||
<p>${s.description}</p>
|
||||
</div>
|
||||
<button class="btn btn-sm btn-outline" onclick="editGlobalSection('${s.slug}')">Edit</button>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// ── Page Editor ──────────────────────────────────────
|
||||
|
||||
async function editPage(slug) {
|
||||
try {
|
||||
const res = await MSPE.API.get('pages.php?id=' + slug);
|
||||
if (!res || !res.success) {
|
||||
MSPE.showNotification('Failed to load page data', 'error');
|
||||
return;
|
||||
}
|
||||
currentPageData = res.data;
|
||||
document.getElementById('page-modal-title').textContent = 'Edit: ' + currentPageData.title;
|
||||
document.getElementById('page-slug').value = currentPageData.slug;
|
||||
document.getElementById('page-meta-title').value = currentPageData.meta_title || '';
|
||||
document.getElementById('page-meta-desc').value = currentPageData.meta_description || '';
|
||||
document.getElementById('page-status').value = currentPageData.status || 'published';
|
||||
|
||||
// Build section editors
|
||||
const container = document.getElementById('page-sections-editor');
|
||||
container.innerHTML = currentPageData.sections.map((section, si) => {
|
||||
const fields = section.fields.map((f, fi) => {
|
||||
const inputId = 'sect-' + section.key + '-' + f.name;
|
||||
if (f.type === 'textarea') {
|
||||
return `<div class="form-group" style="margin-bottom:.75rem;">
|
||||
<label>${f.label}</label>
|
||||
<textarea class="form-control section-field" id="${inputId}" data-section="${section.key}" data-field="${f.name}" rows="2" placeholder="${f.label}">${f.value || ''}</textarea>
|
||||
</div>`;
|
||||
}
|
||||
return `<div class="form-group" style="margin-bottom:.75rem;">
|
||||
<label>${f.label}</label>
|
||||
<input type="text" class="form-control section-field" id="${inputId}" data-section="${section.key}" data-field="${f.name}" value="${(f.value || '').replace(/"/g, '"')}" placeholder="${f.label}">
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
return `<div style="margin-bottom:1.5rem;border:1px solid var(--gray-200);border-radius:var(--radius-sm);overflow:hidden;">
|
||||
<div style="background:var(--gray-100);padding:.625rem 1rem;font-size:.8rem;font-weight:600;color:var(--dark);display:flex;align-items:center;gap:.5rem;cursor:pointer;" onclick="this.parentElement.querySelector('.sect-body').classList.toggle('collapsed')">
|
||||
<i class="fas fa-puzzle-piece" style="color:var(--primary);"></i> ${section.label}
|
||||
<i class="fas fa-chevron-down" style="margin-left:auto;font-size:.65rem;color:var(--gray-400);"></i>
|
||||
</div>
|
||||
<div class="sect-body" style="padding:1rem;">
|
||||
${fields.length ? fields : '<p style="color:var(--gray-400);font-size:.8rem;">No editable fields for this section.</p>'}
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
document.getElementById('page-modal').classList.add('active');
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
MSPE.showNotification('Error loading page', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function closePageModal() {
|
||||
document.getElementById('page-modal').classList.remove('active');
|
||||
currentPageData = null;
|
||||
}
|
||||
|
||||
async function savePage() {
|
||||
const slug = document.getElementById('page-slug').value;
|
||||
if (!slug) return;
|
||||
|
||||
// Gather section field values
|
||||
const sectionsData = {};
|
||||
document.querySelectorAll('.section-field').forEach(el => {
|
||||
const sKey = el.dataset.section;
|
||||
const fName = el.dataset.field;
|
||||
if (!sectionsData[sKey]) sectionsData[sKey] = {};
|
||||
sectionsData[sKey][fName] = el.value;
|
||||
});
|
||||
|
||||
const payload = {
|
||||
slug,
|
||||
meta_title: document.getElementById('page-meta-title').value,
|
||||
meta_description: document.getElementById('page-meta-desc').value,
|
||||
status: document.getElementById('page-status').value,
|
||||
sections_data: sectionsData,
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await MSPE.API.post('pages.php', payload);
|
||||
if (res && res.success) {
|
||||
MSPE.showNotification('Page saved!', 'success');
|
||||
closePageModal();
|
||||
loadPages();
|
||||
} else {
|
||||
MSPE.showNotification(res?.message || 'Save failed', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
MSPE.showNotification('Error saving page', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Global Section Editor ────────────────────────────
|
||||
|
||||
function editGlobalSection(slug) {
|
||||
const section = globalSections.find(s => s.slug === slug);
|
||||
if (!section) return;
|
||||
|
||||
document.getElementById('global-modal-title').textContent = 'Edit: ' + section.label;
|
||||
document.getElementById('global-slug').value = slug;
|
||||
|
||||
const container = document.getElementById('global-fields-editor');
|
||||
container.innerHTML = section.fields.map(f => {
|
||||
const inputId = 'gf-' + slug + '-' + f.name;
|
||||
if (f.type === 'textarea') {
|
||||
return `<div class="form-group" style="margin-bottom:.75rem;">
|
||||
<label>${f.label}</label>
|
||||
<textarea class="form-control global-field" id="${inputId}" data-field="${f.name}" rows="2" placeholder="${f.label}">${f.value || ''}</textarea>
|
||||
</div>`;
|
||||
}
|
||||
return `<div class="form-group" style="margin-bottom:.75rem;">
|
||||
<label>${f.label}</label>
|
||||
<input type="text" class="form-control global-field" id="${inputId}" data-field="${f.name}" value="${(f.value || '').replace(/"/g, '"')}" placeholder="${f.label}">
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
document.getElementById('global-modal').classList.add('active');
|
||||
}
|
||||
|
||||
function closeGlobalModal() {
|
||||
document.getElementById('global-modal').classList.remove('active');
|
||||
}
|
||||
|
||||
async function saveGlobalSection() {
|
||||
const slug = document.getElementById('global-slug').value;
|
||||
if (!slug) return;
|
||||
|
||||
const fields = {};
|
||||
document.querySelectorAll('.global-field').forEach(el => {
|
||||
fields[el.dataset.field] = el.value;
|
||||
});
|
||||
|
||||
try {
|
||||
const res = await MSPE.API.post('pages.php?type=global', { section_slug: slug, fields });
|
||||
if (res && res.success) {
|
||||
MSPE.showNotification('Section saved!', 'success');
|
||||
closeGlobalModal();
|
||||
loadGlobalSections();
|
||||
} else {
|
||||
MSPE.showNotification(res?.message || 'Save failed', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
MSPE.showNotification('Error saving section', 'error');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user