mirror of
https://github.com/myronblair/tomsjavajive
synced 2026-06-30 17:50:32 -05:00
293 lines
14 KiB
PHP
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')">×</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 | <i class="fas fa-level-down-alt fa-rotate-90"></i> Single line break = <br></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,'&').replace(/</g,'<').replace(/>/g,'>').replace(/<br>/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'; ?>
|