Files
2026-05-22 12:52:44 +00:00

293 lines
14 KiB
PHP

<?php
ob_start();
$pageTitle = 'About Us Content';
$currentPage = 'about-us';
require_once __DIR__ . '/includes/header.php';
/* ── Actions ─────────────────────────────────────── */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$action = $_POST['action'] ?? '';
$sectionId = $_POST['section_id'] ?? '';
if ($action === 'create') {
$body = trim($_POST['body'] ?? '');
if ($body) {
db()->insert('about_us_sections', [
'section_id' => generateId('sec_'),
'heading' => trim($_POST['heading'] ?? '') ?: null,
'body' => $body,
'sort_order' => intval($_POST['sort_order'] ?? 0),
'is_active' => 1,
]);
setFlash('success', 'Section added');
}
}
if ($action === 'update' && $sectionId) {
$body = trim($_POST['body'] ?? '');
if ($body) {
db()->update('about_us_sections', [
'heading' => trim($_POST['heading'] ?? '') ?: null,
'body' => $body,
'sort_order' => intval($_POST['sort_order'] ?? 0),
'is_active' => isset($_POST['is_active']) ? 1 : 0,
], 'section_id = :id', ['id' => $sectionId]);
setFlash('success', 'Section updated');
}
}
if ($action === 'delete' && $sectionId) {
db()->delete('about_us_sections', 'section_id = :id', ['id' => $sectionId]);
setFlash('success', 'Section deleted');
}
if ($action === 'reorder') {
$ids = json_decode($_POST['order'] ?? '[]', true);
foreach ($ids as $pos => $sid) {
db()->update('about_us_sections', ['sort_order' => $pos + 1],
'section_id = :id', ['id' => $sid]);
}
echo json_encode(['ok' => true]); exit;
}
header('Location: /admin/about-us.php'); exit;
}
$sections = db()->fetchAll(
"SELECT * FROM about_us_sections ORDER BY sort_order ASC, id ASC"
);
?>
<div class="page-header">
<h1 class="page-title">About Us Content</h1>
<button class="btn btn-primary" onclick="openModal()">
<i class="fas fa-plus"></i> Add Section
</button>
</div>
<?php if (hasFlash('success')): ?>
<div class="alert alert-success"><i class="fas fa-check-circle"></i> <?= getFlash('success') ?></div>
<?php endif; ?>
<div class="admin-card" style="margin-bottom:1.5rem">
<div class="admin-card-body" style="padding:.85rem 1.5rem">
<p class="text-muted" style="margin:0">
<i class="fas fa-info-circle"></i>
These sections appear on the homepage between <strong>Our Story</strong> and the <strong>Explore Our Coffee</strong> button.
Drag rows to reorder. Blank lines in the text become paragraph breaks.
</p>
</div>
</div>
<!-- Live Preview -->
<div class="admin-card" style="margin-bottom:1.5rem">
<div class="admin-card-header">
<h3><i class="fas fa-eye"></i> Homepage Preview</h3>
<a href="/" target="_blank" class="btn btn-sm btn-secondary"><i class="fas fa-external-link-alt"></i> View Live</a>
</div>
<div class="admin-card-body" style="background:var(--admin-bg);border-radius:var(--radius-md);padding:2rem">
<h2 style="font-family:Georgia,serif;font-size:1.75rem;margin:0 0 1rem">Our Story</h2>
<div id="livePreview" style="font-size:.9375rem;color:var(--admin-text-muted);line-height:1.7">
<?php foreach ($sections as $sec): if (!$sec['is_active']) continue; ?>
<?php if (!empty($sec['heading'])): ?>
<h3 style="font-size:1.1rem;font-weight:600;margin:.5rem 0 .4rem"><?= htmlspecialchars($sec['heading']) ?></h3>
<?php endif; ?>
<?php foreach (array_filter(array_map('trim', preg_split('/\n{2,}/', $sec['body']))) as $para): ?>
<p style="margin:0 0 1rem"><?= nl2br(htmlspecialchars($para)) ?></p>
<?php endforeach; ?>
<?php endforeach; ?>
</div>
<a href="#" class="btn btn-primary" style="margin-top:.5rem;pointer-events:none;opacity:.8">Explore Our Coffee →</a>
</div>
</div>
<!-- Section List -->
<div class="admin-card">
<div class="admin-card-body" style="padding:0">
<?php if (empty($sections)): ?>
<div class="text-center text-muted" style="padding:3rem">No sections yet. Click <strong>Add Section</strong> to get started.</div>
<?php else: ?>
<table class="admin-table">
<thead>
<tr>
<th style="width:30px"></th>
<th>Heading</th>
<th>Body Text</th>
<th style="width:60px">Order</th>
<th style="width:80px">Status</th>
<th style="width:100px">Actions</th>
</tr>
</thead>
<tbody id="sectionTbody">
<?php foreach ($sections as $sec): ?>
<tr data-id="<?= $sec['section_id'] ?>">
<td style="color:var(--admin-text-muted);text-align:center;cursor:grab"><i class="fas fa-grip-vertical"></i></td>
<td style="font-weight:600;min-width:120px"><?= htmlspecialchars($sec['heading'] ?? '—') ?></td>
<td style="color:var(--admin-text-muted);font-size:.875rem">
<div style="max-width:420px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">
<?= htmlspecialchars($sec['body']) ?>
</div>
</td>
<td class="sort-cell"><?= $sec['sort_order'] ?></td>
<td>
<?= $sec['is_active']
? '<span class="badge badge-success">Active</span>'
: '<span class="badge badge-error">Hidden</span>' ?>
</td>
<td>
<button class="btn btn-sm btn-secondary" onclick='openModal(<?= json_encode($sec, JSON_HEX_APOS | JSON_HEX_QUOT) ?>)' title="Edit">
<i class="fas fa-edit"></i>
</button>
<form method="POST" style="display:inline">
<input type="hidden" name="action" value="delete">
<input type="hidden" name="section_id" value="<?= $sec['section_id'] ?>">
<button type="submit" class="btn btn-sm btn-danger" data-confirm="Delete this section?">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<!-- ── Modal ──────────────────────────────────────── -->
<div class="modal-overlay" id="sectionModal">
<div class="modal" style="max-width:680px;width:95vw">
<div class="modal-header">
<h3 class="modal-title" id="modalTitle">Add Section</h3>
<button type="button" class="modal-close" onclick="Modal.close('sectionModal')">&times;</button>
</div>
<form method="POST" id="sectionForm">
<div class="modal-body">
<input type="hidden" name="action" id="formAction" value="create">
<input type="hidden" name="section_id" id="formSectionId">
<div class="form-group">
<label class="form-label">
Heading <span class="text-muted" style="font-weight:400">(optional sub-heading above this block)</span>
</label>
<input type="text" name="heading" id="formHeading" class="form-input"
placeholder="e.g. Our Mission, From Farm to Cup…">
</div>
<div class="form-group">
<label class="form-label">Body Text *</label>
<textarea name="body" id="formBody" class="form-input" rows="10"
style="resize:vertical;font-size:.9375rem;line-height:1.6"
placeholder="Type your text here. Leave a blank line between paragraphs to create separate paragraph blocks."
required oninput="updatePreview()"></textarea>
<small class="text-muted"><i class="fas fa-paragraph"></i> Blank line = new paragraph &nbsp;|&nbsp; <i class="fas fa-level-down-alt fa-rotate-90"></i> Single line break = &lt;br&gt;</small>
</div>
<!-- Inline preview inside modal -->
<div class="form-group" style="margin-bottom:0">
<label class="form-label" style="display:flex;justify-content:space-between">
<span>Text Preview</span>
<span class="text-muted" style="font-weight:400;font-size:.8rem">Updates as you type</span>
</label>
<div id="inlinePreview"
style="border:1px solid var(--color-border);border-radius:var(--radius-md);padding:1rem 1.25rem;
background:var(--admin-bg);min-height:80px;font-size:.9375rem;color:var(--admin-text-muted);line-height:1.7">
<em style="opacity:.4">Preview will appear here…</em>
</div>
</div>
</div>
<div class="modal-footer">
<div id="statusWrap" style="display:none;margin-right:auto">
<label style="display:flex;align-items:center;gap:.5rem;cursor:pointer">
<input type="checkbox" name="is_active" id="formActive" checked> Active
</label>
</div>
<div class="form-group mb-0" style="margin-right:auto">
<label class="form-label" style="display:inline;margin-right:.5rem">Order</label>
<input type="number" name="sort_order" id="formOrder" class="form-input"
value="0" min="0" style="width:70px;display:inline-block">
</div>
<button type="button" class="btn btn-secondary" onclick="Modal.close('sectionModal')">Cancel</button>
<button type="submit" class="btn btn-primary" id="formSubmitBtn">Add Section</button>
</div>
</form>
</div>
</div>
<style>
#sectionTbody tr { cursor: grab; }
#sectionTbody tr.drag-over { background: rgba(255,94,26,.06); }
#formBody:focus { border-color: var(--admin-primary); }
</style>
<script>
/* ── Modal ───────────────────────────────────────── */
function openModal(sec) {
var isEdit = !!sec;
document.getElementById('modalTitle').textContent = isEdit ? 'Edit Section' : 'Add Section';
document.getElementById('formSubmitBtn').textContent = isEdit ? 'Save Changes' : 'Add Section';
document.getElementById('formAction').value = isEdit ? 'update' : 'create';
document.getElementById('formSectionId').value = isEdit ? sec.section_id : '';
document.getElementById('formHeading').value = isEdit ? (sec.heading || '') : '';
document.getElementById('formBody').value = isEdit ? sec.body : '';
document.getElementById('formOrder').value = isEdit ? sec.sort_order : 0;
document.getElementById('formActive').checked = isEdit ? !!parseInt(sec.is_active) : true;
document.getElementById('statusWrap').style.display = isEdit ? '' : 'none';
updatePreview();
Modal.open('sectionModal');
// Focus textarea after modal opens
setTimeout(function() { document.getElementById('formBody').focus(); }, 150);
}
/* ── Inline text preview ─────────────────────────── */
function updatePreview() {
var raw = document.getElementById('formBody').value;
var box = document.getElementById('inlinePreview');
if (!raw.trim()) {
box.innerHTML = '<em style="opacity:.4">Preview will appear here…</em>';
return;
}
var paras = raw.split(/\n{2,}/).map(function(p) { return p.trim(); }).filter(Boolean);
box.innerHTML = paras.map(function(p) {
return '<p style="margin:0 0 .75rem">' + p.replace(/\n/g, '<br>').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/&lt;br&gt;/g,'<br>') + '</p>';
}).join('');
}
/* ── Drag-to-reorder ─────────────────────────────── */
(function() {
var tbody = document.getElementById('sectionTbody');
if (!tbody) return;
var dragging = null;
tbody.querySelectorAll('tr').forEach(function(row) {
row.draggable = true;
row.addEventListener('dragstart', function() { dragging = this; this.style.opacity = '.4'; });
row.addEventListener('dragend', function() { this.style.opacity = ''; dragging = null; saveOrder(); });
row.addEventListener('dragover', function(e) { e.preventDefault(); this.classList.add('drag-over'); });
row.addEventListener('dragleave', function() { this.classList.remove('drag-over'); });
row.addEventListener('drop', function(e) {
e.preventDefault(); this.classList.remove('drag-over');
if (dragging && dragging !== this) {
if (Array.from(tbody.querySelectorAll('tr')).indexOf(dragging) <
Array.from(tbody.querySelectorAll('tr')).indexOf(this)) this.after(dragging);
else this.before(dragging);
}
});
});
function saveOrder() {
var ids = Array.from(tbody.querySelectorAll('tr')).map(function(r) { return r.dataset.id; });
fetch('/admin/about-us.php', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: 'action=reorder&order=' + encodeURIComponent(JSON.stringify(ids))
});
tbody.querySelectorAll('tr').forEach(function(r, i) {
r.querySelector('.sort-cell').textContent = i + 1;
});
}
})();
</script>
<?php require_once __DIR__ . '/includes/footer.php'; ?>