mirror of
https://github.com/myronblair/tomsjavajive
synced 2026-06-30 17:50:32 -05:00
411 lines
21 KiB
PHP
411 lines
21 KiB
PHP
<?php
|
|
ob_start();
|
|
$pageTitle = 'Splash Box';
|
|
$currentPage = 'splashes';
|
|
require_once __DIR__ . '/includes/header.php';
|
|
|
|
/* ── Actions ─────────────────────────────────────── */
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = $_POST['action'] ?? '';
|
|
$splashId = $_POST['splash_id'] ?? '';
|
|
|
|
if ($action === 'create') {
|
|
$title = trim($_POST['title'] ?? '');
|
|
if ($title) {
|
|
db()->insert('homepage_splashes', [
|
|
'splash_id' => generateId('spl_'),
|
|
'icon' => trim($_POST['icon'] ?? 'fas fa-star'),
|
|
'image_url' => trim($_POST['image_url'] ?? '') ?: null,
|
|
'title' => $title,
|
|
'description' => trim($_POST['description'] ?? '') ?: null,
|
|
'sort_order' => intval($_POST['sort_order'] ?? 0),
|
|
'is_active' => 1,
|
|
]);
|
|
setFlash('success', 'Splash block created');
|
|
}
|
|
}
|
|
|
|
if ($action === 'update' && $splashId) {
|
|
$title = trim($_POST['title'] ?? '');
|
|
if ($title) {
|
|
db()->update('homepage_splashes', [
|
|
'icon' => trim($_POST['icon'] ?? 'fas fa-star'),
|
|
'image_url' => trim($_POST['image_url'] ?? '') ?: null,
|
|
'title' => $title,
|
|
'description' => trim($_POST['description'] ?? '') ?: null,
|
|
'sort_order' => intval($_POST['sort_order'] ?? 0),
|
|
'is_active' => isset($_POST['is_active']) ? 1 : 0,
|
|
], 'splash_id = :id', ['id' => $splashId]);
|
|
setFlash('success', 'Splash block updated');
|
|
}
|
|
}
|
|
|
|
if ($action === 'delete' && $splashId) {
|
|
db()->delete('homepage_splashes', 'splash_id = :id', ['id' => $splashId]);
|
|
setFlash('success', 'Splash block deleted');
|
|
}
|
|
|
|
if ($action === 'reorder') {
|
|
$ids = json_decode($_POST['order'] ?? '[]', true);
|
|
foreach ($ids as $pos => $sid) {
|
|
db()->update('homepage_splashes', ['sort_order' => $pos + 1],
|
|
'splash_id = :id', ['id' => $sid]);
|
|
}
|
|
echo json_encode(['ok' => true]); exit;
|
|
}
|
|
|
|
header('Location: /admin/splashes.php'); exit;
|
|
}
|
|
|
|
$splashes = db()->fetchAll(
|
|
"SELECT * FROM homepage_splashes ORDER BY sort_order ASC, id ASC"
|
|
);
|
|
|
|
$iconOptions = [
|
|
'fas fa-leaf' => 'Leaf',
|
|
'fas fa-fire' => 'Fire',
|
|
'fas fa-truck' => 'Truck',
|
|
'fas fa-heart' => 'Heart',
|
|
'fas fa-star' => 'Star',
|
|
'fas fa-coffee' => 'Coffee',
|
|
'fas fa-mug-hot' => 'Mug Hot',
|
|
'fas fa-seedling' => 'Seedling',
|
|
'fas fa-shield-alt' => 'Shield',
|
|
'fas fa-check-circle' => 'Check',
|
|
'fas fa-gift' => 'Gift',
|
|
'fas fa-globe' => 'Globe',
|
|
'fas fa-award' => 'Award',
|
|
'fas fa-smile' => 'Smile',
|
|
'fas fa-bolt' => 'Bolt',
|
|
'fas fa-recycle' => 'Recycle',
|
|
'fas fa-hand-holding-heart'=> 'Care',
|
|
'fas fa-crown' => 'Crown',
|
|
'fas fa-gem' => 'Gem',
|
|
'fas fa-thumbs-up' => 'Thumbs Up',
|
|
];
|
|
?>
|
|
|
|
<div class="page-header">
|
|
<h1 class="page-title">Splash Box</h1>
|
|
<button class="btn btn-primary" onclick="openSplashModal()">
|
|
<i class="fas fa-plus"></i> Add Splash
|
|
</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>
|
|
Drag rows to reorder — saves automatically.
|
|
<?= count($splashes) ?> block<?= count($splashes) !== 1 ? 's' : '' ?> total.
|
|
Homepage scrolls horizontally when more than 4 are active.
|
|
Each block can show an icon <em>or</em> a custom image.</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></div>
|
|
<div class="admin-card-body" style="background:var(--admin-bg);border-radius:var(--radius-md);padding:0;overflow:hidden">
|
|
<div id="splashPreview" style="display:flex;gap:1.5rem;overflow-x:auto;padding:2rem;scrollbar-width:thin">
|
|
<?php foreach ($splashes as $sp): if (!$sp['is_active']) continue; ?>
|
|
<div style="min-width:190px;text-align:center;padding:1.5rem 1rem;background:var(--admin-card);border-radius:var(--radius-lg);flex-shrink:0;box-shadow:0 1px 4px rgba(0,0,0,.08)">
|
|
<div style="width:56px;height:56px;background:linear-gradient(135deg,var(--admin-primary),#c4420f);border-radius:12px;display:flex;align-items:center;justify-content:center;margin:0 auto .75rem;color:#fff;font-size:1.35rem;overflow:hidden">
|
|
<?php if (!empty($sp['image_url'])): ?>
|
|
<img src="<?= htmlspecialchars($sp['image_url']) ?>" style="width:100%;height:100%;object-fit:cover" alt="">
|
|
<?php else: ?>
|
|
<i class="<?= htmlspecialchars($sp['icon']) ?>"></i>
|
|
<?php endif; ?>
|
|
</div>
|
|
<div style="font-weight:600;font-size:.9rem;margin-bottom:.35rem"><?= htmlspecialchars($sp['title']) ?></div>
|
|
<div style="font-size:.78rem;color:var(--admin-text-muted);line-height:1.4"><?= htmlspecialchars($sp['description'] ?? '') ?></div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
<div class="admin-card">
|
|
<div class="admin-card-body" style="padding:0">
|
|
<?php if (empty($splashes)): ?>
|
|
<div class="text-center text-muted" style="padding:3rem">No splash blocks yet. Click <strong>Add Splash</strong> to get started.</div>
|
|
<?php else: ?>
|
|
<table class="admin-table">
|
|
<thead>
|
|
<tr>
|
|
<th style="width:30px"></th>
|
|
<th style="width:60px">Visual</th>
|
|
<th>Title</th>
|
|
<th>Description</th>
|
|
<th style="width:60px">Order</th>
|
|
<th style="width:80px">Status</th>
|
|
<th style="width:100px">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="splashTbody">
|
|
<?php foreach ($splashes as $sp): ?>
|
|
<tr data-id="<?= $sp['splash_id'] ?>">
|
|
<td style="color:var(--admin-text-muted);text-align:center;cursor:grab"><i class="fas fa-grip-vertical"></i></td>
|
|
<td>
|
|
<div style="width:42px;height:42px;background:linear-gradient(135deg,var(--admin-primary),#c4420f);border-radius:8px;display:flex;align-items:center;justify-content:center;color:#fff;overflow:hidden">
|
|
<?php if (!empty($sp['image_url'])): ?>
|
|
<img src="<?= htmlspecialchars($sp['image_url']) ?>" style="width:100%;height:100%;object-fit:cover" alt="">
|
|
<?php else: ?>
|
|
<i class="<?= htmlspecialchars($sp['icon']) ?>"></i>
|
|
<?php endif; ?>
|
|
</div>
|
|
</td>
|
|
<td><strong><?= htmlspecialchars($sp['title']) ?></strong></td>
|
|
<td style="max-width:260px;color:var(--admin-text-muted);font-size:.875rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">
|
|
<?= htmlspecialchars($sp['description'] ?? '') ?>
|
|
</td>
|
|
<td class="sort-cell"><?= $sp['sort_order'] ?></td>
|
|
<td>
|
|
<?= $sp['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='openSplashModal(<?= json_encode($sp) ?>)' 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="splash_id" value="<?= $sp['splash_id'] ?>">
|
|
<button type="submit" class="btn btn-sm btn-danger" data-confirm="Delete this splash block?">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</form>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<?php endif; ?>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ── Modal ──────────────────────────────────────── -->
|
|
<div class="modal-overlay" id="splashModal">
|
|
<div class="modal" style="max-width:620px;width:95vw">
|
|
<div class="modal-header">
|
|
<h3 class="modal-title" id="splashModalTitle">Add Splash Block</h3>
|
|
<button type="button" class="modal-close" onclick="Modal.close('splashModal')">×</button>
|
|
</div>
|
|
<form method="POST" id="splashForm">
|
|
<div class="modal-body">
|
|
<input type="hidden" name="action" id="splashAction" value="create">
|
|
<input type="hidden" name="splash_id" id="splashId">
|
|
<input type="hidden" name="image_url" id="splashImageUrl">
|
|
|
|
<!-- Image upload zone -->
|
|
<div class="form-group">
|
|
<label class="form-label">Image <span class="text-muted" style="font-weight:400">(optional — overrides icon when set)</span></label>
|
|
<div id="dropZone" style="border:2px dashed var(--color-border);border-radius:var(--radius-md);padding:1.5rem;text-align:center;cursor:pointer;transition:all .2s;position:relative">
|
|
<div id="dropPlaceholder">
|
|
<i class="fas fa-cloud-upload-alt" style="font-size:2rem;color:var(--admin-text-muted);display:block;margin-bottom:.5rem"></i>
|
|
<div style="font-size:.875rem;color:var(--admin-text-muted)">Drag & drop an image here, or <span style="color:var(--admin-primary);font-weight:600">browse</span></div>
|
|
<div style="font-size:.75rem;color:var(--admin-text-muted);margin-top:.25rem">JPG, PNG, WebP, GIF · Max 5 MB</div>
|
|
</div>
|
|
<div id="dropPreview" style="display:none">
|
|
<img id="dropPreviewImg" src="" alt="" style="max-height:120px;max-width:100%;border-radius:var(--radius-md);margin-bottom:.5rem">
|
|
<div>
|
|
<button type="button" class="btn btn-sm btn-danger" onclick="clearImage(event)">
|
|
<i class="fas fa-times"></i> Remove image
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<input type="file" id="dropInput" accept="image/*" style="position:absolute;inset:0;opacity:0;cursor:pointer">
|
|
<div id="dropUploading" style="display:none;color:var(--admin-text-muted)"><i class="fas fa-spinner fa-spin"></i> Uploading…</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Icon picker -->
|
|
<div class="form-group" id="iconGroup">
|
|
<label class="form-label">Icon <span class="text-muted" style="font-weight:400">(used when no image is set)</span></label>
|
|
<div style="display:flex;flex-wrap:wrap;gap:.4rem;margin-bottom:.6rem">
|
|
<?php foreach ($iconOptions as $cls => $label): ?>
|
|
<button type="button" class="icon-opt btn btn-secondary" data-icon="<?= $cls ?>" title="<?= $label ?>"
|
|
style="width:40px;height:40px;padding:0;display:flex;align-items:center;justify-content:center"
|
|
onclick="pickIcon('<?= $cls ?>')">
|
|
<i class="<?= $cls ?>"></i>
|
|
</button>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<input type="text" name="icon" id="splashIcon" class="form-input" placeholder="Custom FA class e.g. fas fa-mug-hot" value="fas fa-star">
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Title *</label>
|
|
<input type="text" name="title" id="splashTitle" class="form-input" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="form-label">Description</label>
|
|
<textarea name="description" id="splashDesc" class="form-input" rows="3" style="resize:vertical"></textarea>
|
|
</div>
|
|
|
|
<div style="display:flex;gap:1rem">
|
|
<div class="form-group" style="flex:1">
|
|
<label class="form-label">Sort Order</label>
|
|
<input type="number" name="sort_order" id="splashOrder" class="form-input" value="0" min="0">
|
|
</div>
|
|
<div class="form-group" id="splashStatusGroup" style="display:none;flex:1">
|
|
<label class="form-label">Status</label>
|
|
<label style="display:flex;align-items:center;gap:.5rem;margin-top:.6rem;cursor:pointer">
|
|
<input type="checkbox" name="is_active" id="splashActive" checked> Active
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" onclick="Modal.close('splashModal')">Cancel</button>
|
|
<button type="submit" class="btn btn-primary" id="splashSubmitBtn">Add Splash</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.icon-opt.selected { background:var(--admin-primary)!important;color:#fff!important;border-color:var(--admin-primary)!important; }
|
|
#dropZone.drag-active { border-color:var(--admin-primary);background:rgba(255,94,26,.04); }
|
|
#splashTbody tr { cursor:grab; }
|
|
#splashTbody tr.drag-over { background:rgba(255,94,26,.06); }
|
|
</style>
|
|
|
|
<script>
|
|
/* ── Icon picker ─────────────────────────────────── */
|
|
function pickIcon(cls) {
|
|
document.getElementById('splashIcon').value = cls;
|
|
document.querySelectorAll('.icon-opt').forEach(function(b) {
|
|
b.classList.toggle('selected', b.dataset.icon === cls);
|
|
});
|
|
}
|
|
document.getElementById('splashIcon').addEventListener('input', function() {
|
|
document.querySelectorAll('.icon-opt').forEach(function(b) {
|
|
b.classList.toggle('selected', b.dataset.icon === this.value);
|
|
}.bind(this));
|
|
});
|
|
|
|
/* ── Image drop zone ─────────────────────────────── */
|
|
var dropZone = document.getElementById('dropZone');
|
|
var dropInput = document.getElementById('dropInput');
|
|
|
|
['dragenter','dragover'].forEach(function(ev) {
|
|
dropZone.addEventListener(ev, function(e) { e.preventDefault(); dropZone.classList.add('drag-active'); });
|
|
});
|
|
['dragleave','drop'].forEach(function(ev) {
|
|
dropZone.addEventListener(ev, function(e) { e.preventDefault(); dropZone.classList.remove('drag-active'); });
|
|
});
|
|
dropZone.addEventListener('drop', function(e) { handleFile(e.dataTransfer.files[0]); });
|
|
dropInput.addEventListener('change', function() { if (this.files[0]) handleFile(this.files[0]); });
|
|
|
|
function handleFile(file) {
|
|
if (!file) return;
|
|
var allowed = ['image/jpeg','image/png','image/gif','image/webp'];
|
|
if (!allowed.includes(file.type)) { alert('Please use JPG, PNG, WebP or GIF.'); return; }
|
|
if (file.size > 5 * 1024 * 1024) { alert('File too large (max 5 MB).'); return; }
|
|
|
|
document.getElementById('dropPlaceholder').style.display = 'none';
|
|
document.getElementById('dropPreview').style.display = 'none';
|
|
document.getElementById('dropUploading').style.display = 'block';
|
|
|
|
var fd = new FormData();
|
|
fd.append('image', file);
|
|
fetch('/admin/api/upload-splash.php', { method: 'POST', body: fd })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
document.getElementById('dropUploading').style.display = 'none';
|
|
if (data.error) { alert(data.error); showDropPlaceholder(); return; }
|
|
document.getElementById('splashImageUrl').value = data.url;
|
|
document.getElementById('dropPreviewImg').src = data.url;
|
|
document.getElementById('dropPreview').style.display = 'block';
|
|
})
|
|
.catch(function() { document.getElementById('dropUploading').style.display='none'; showDropPlaceholder(); });
|
|
}
|
|
|
|
function showDropPlaceholder() {
|
|
document.getElementById('dropPlaceholder').style.display = 'block';
|
|
document.getElementById('dropPreview').style.display = 'none';
|
|
}
|
|
|
|
function clearImage(e) {
|
|
e.stopPropagation();
|
|
document.getElementById('splashImageUrl').value = '';
|
|
document.getElementById('dropInput').value = '';
|
|
showDropPlaceholder();
|
|
}
|
|
|
|
/* ── Open modal ──────────────────────────────────── */
|
|
function openSplashModal(sp) {
|
|
var isEdit = !!sp;
|
|
document.getElementById('splashModalTitle').textContent = isEdit ? 'Edit Splash Block' : 'Add Splash Block';
|
|
document.getElementById('splashSubmitBtn').textContent = isEdit ? 'Save Changes' : 'Add Splash';
|
|
document.getElementById('splashAction').value = isEdit ? 'update' : 'create';
|
|
document.getElementById('splashId').value = isEdit ? sp.splash_id : '';
|
|
document.getElementById('splashTitle').value = isEdit ? sp.title : '';
|
|
document.getElementById('splashDesc').value = isEdit ? (sp.description || '') : '';
|
|
document.getElementById('splashOrder').value = isEdit ? sp.sort_order : 0;
|
|
document.getElementById('splashActive').checked = isEdit ? !!parseInt(sp.is_active) : true;
|
|
document.getElementById('splashStatusGroup').style.display = isEdit ? '' : 'none';
|
|
pickIcon(isEdit && sp.icon ? sp.icon : 'fas fa-star');
|
|
|
|
// image
|
|
var imgUrl = isEdit ? (sp.image_url || '') : '';
|
|
document.getElementById('splashImageUrl').value = imgUrl;
|
|
document.getElementById('dropInput').value = '';
|
|
if (imgUrl) {
|
|
document.getElementById('dropPreviewImg').src = imgUrl;
|
|
document.getElementById('dropPreview').style.display = 'block';
|
|
document.getElementById('dropPlaceholder').style.display = 'none';
|
|
document.getElementById('dropUploading').style.display = 'none';
|
|
} else {
|
|
showDropPlaceholder();
|
|
document.getElementById('dropUploading').style.display = 'none';
|
|
}
|
|
|
|
Modal.open('splashModal');
|
|
}
|
|
|
|
/* ── Drag-to-reorder rows ────────────────────────── */
|
|
(function() {
|
|
var tbody = document.getElementById('splashTbody');
|
|
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) {
|
|
var rows = Array.from(tbody.querySelectorAll('tr'));
|
|
if (rows.indexOf(dragging) < rows.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/splashes.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'; ?>
|