Files
cms.techvanguard.vn/views/admin/footer/index.ejs
T

523 lines
27 KiB
Plaintext

<div class="container">
<div class="d-flex justify-content-between align-items-center mt-4 mb-4">
<div>
<h1 class="h3 mb-0" style="color: var(--primary-dark);">
<%= title %>
</h1>
<p class="text-muted mb-0">Edit content displayed in the site footer</p>
</div>
</div>
<div class="row">
<div class="col-12">
<form method="POST" id="footerForm" action="/admin/footer/update">
<!-- Hidden JSON inputs -->
<input type="hidden" name="brand" id="brandJson">
<input type="hidden" name="explore" id="exploreJson">
<input type="hidden" name="contact" id="contactJson">
<input type="hidden" name="newsletter" id="newsletterJson">
<input type="hidden" name="bottom" id="bottomJson">
<!-- Tabs -->
<div class="card shadow-sm border-0 mb-4">
<div class="card-header bg-white border-bottom">
<ul class="nav nav-tabs card-header-tabs" id="footerTabs" role="tablist">
<li class="nav-item" role="presentation">
<a class="nav-link active" data-bs-toggle="tab" href="#tab-brand-pane" role="tab">
<i class="fas fa-building me-2"></i>Brand
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-explore-pane" role="tab">
<i class="fas fa-compass me-2"></i>Explore
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-contact-pane" role="tab">
<i class="fas fa-address-card me-2"></i>Contact
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-newsletter-pane" role="tab">
<i class="fas fa-envelope me-2"></i>Newsletter
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link" data-bs-toggle="tab" href="#tab-bottom-pane" role="tab">
<i class="fas fa-grip-horizontal me-2"></i>Bottom Bar
</a>
</li>
</ul>
</div>
<div class="card-body">
<div class="tab-content">
<!-- ===== BRAND TAB ===== -->
<div class="tab-pane fade show active" id="tab-brand-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-building me-2"></i>Brand</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Logo Image URL</label>
<div class="input-group">
<input type="text" class="form-control" id="brandLogoImage"
value="<%= data.brand?.logo?.image || '' %>">
<button class="btn btn-outline-primary" type="button"
onclick="openImageUploader('brandLogoImage', 'footer')">
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
<% if (data.brand?.logo?.image) { %>
<img src="<%= data.brand.logo.image %>"
class="img-thumbnail uploaded-preview mt-2"
style="max-height:80px;">
<% } %>
</div>
<div class="col-md-6">
<label class="form-label">Logo Href</label>
<input type="text" class="form-control" id="brandLogoHref"
value="<%= data.brand?.logo?.href || '/' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="brandDescription"
rows="3"><%= data.brand?.description || '' %></textarea>
</div>
</div>
<div class="mt-4">
<div class="d-flex justify-content-between align-items-center mb-2">
<h6 class="mb-0">Social Links</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addSocialLink()">
<i class="fas fa-plus me-1"></i>Add Social
</button>
</div>
<div id="socialLinksContainer"></div>
</div>
</div>
</div>
</div>
<!-- ===== EXPLORE TAB ===== -->
<div class="tab-pane fade" id="tab-explore-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0"><i class="fas fa-compass me-2"></i>Explore</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addExploreLink()">
<i class="fas fa-plus me-1"></i>Add Link
</button>
</div>
<div class="card-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="exploreHeading"
value="<%= data.explore?.heading || '' %>">
</div>
</div>
<div id="exploreLinksContainer"></div>
</div>
</div>
</div>
<!-- ===== CONTACT TAB ===== -->
<div class="tab-pane fade" id="tab-contact-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-address-card me-2"></i>Contact</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="contactHeading"
value="<%= data.contact?.heading || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">Phone</label>
<input type="text" class="form-control" id="contactPhone"
value="<%= data.contact?.phone || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">Email</label>
<input type="text" class="form-control" id="contactEmail"
value="<%= data.contact?.email || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Address</label>
<input type="text" class="form-control" id="contactAddress"
value="<%= data.contact?.address || '' %>">
</div>
</div>
</div>
</div>
</div>
<!-- ===== NEWSLETTER TAB ===== -->
<div class="tab-pane fade" id="tab-newsletter-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h6 class="mb-0"><i class="fas fa-envelope me-2"></i>Newsletter</h6>
</div>
<div class="card-body p-4">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="newsletterHeading"
value="<%= data.newsletter?.heading || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">Button Text</label>
<input type="text" class="form-control" id="newsletterButtonText"
value="<%= data.newsletter?.buttonText || '' %>">
</div>
<div class="col-md-6">
<label class="form-label">Placeholder</label>
<input type="text" class="form-control" id="newsletterPlaceholder"
value="<%= data.newsletter?.placeholder || '' %>">
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="newsletterDescription"
rows="3"><%= data.newsletter?.description || '' %></textarea>
</div>
</div>
</div>
</div>
</div>
<!-- ===== BOTTOM BAR TAB ===== -->
<div class="tab-pane fade" id="tab-bottom-pane" role="tabpanel">
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0"><i class="fas fa-grip-horizontal me-2"></i>Bottom Bar</h6>
<button type="button" class="btn btn-outline-primary btn-sm"
onclick="addBottomLink()">
<i class="fas fa-plus me-1"></i>Add Link
</button>
</div>
<div class="card-body p-4">
<div class="row g-3 mb-4">
<div class="col-md-12">
<label class="form-label">Copyright Text</label>
<input type="text" class="form-control" id="bottomCopyright"
value="<%= data.bottom?.copyright || '' %>">
</div>
</div>
<div id="bottomLinksContainer"></div>
</div>
</div>
</div>
</div><!-- /.tab-content -->
</div><!-- /.card-body -->
<div class="card-footer bg-light d-flex justify-content-end py-3 gap-2">
<button type="button" class="btn btn-outline-secondary px-4" onclick="resetForm()">
<i class="fas fa-undo me-2"></i>Reset
</button>
<button type="submit" class="btn btn-outline-primary px-4" id="submitBtn">
<i class="fas fa-save me-2"></i>Save Changes
</button>
</div>
</div>
</form>
</div>
</div>
</div>
<script>
let originalFormData = null;
document.addEventListener('DOMContentLoaded', function () {
originalFormData = <%- JSON.stringify(data) %>;
populateAll(originalFormData);
document.getElementById('footerForm').addEventListener('submit', function (e) {
e.preventDefault();
serializeAll();
this.submit();
});
});
// ── Populate all sections ────────────────────────────────────────────────
function populateAll(data) {
if (!data) return;
// Brand
const b = data.brand || {};
setVal('brandLogoImage', b.logo?.image);
setVal('brandLogoHref', b.logo?.href);
setVal('brandDescription', b.description);
populateSocialLinks(b.social || []);
// Explore
const ex = data.explore || {};
setVal('exploreHeading', ex.heading);
populateExploreLinks(ex.links || []);
// Contact
const c = data.contact || {};
setVal('contactHeading', c.heading);
setVal('contactAddress', c.address);
setVal('contactPhone', c.phone);
setVal('contactEmail', c.email);
// Newsletter
const n = data.newsletter || {};
setVal('newsletterHeading', n.heading);
setVal('newsletterDescription', n.description);
setVal('newsletterPlaceholder', n.placeholder);
setVal('newsletterButtonText', n.buttonText);
// Bottom
const bt = data.bottom || {};
setVal('bottomCopyright', bt.copyright);
populateBottomLinks(bt.links || []);
}
function setVal(id, val) {
const el = document.getElementById(id);
if (el) el.value = val || '';
}
// ── Serialize all sections into hidden JSON inputs ───────────────────────
function serializeAll() {
// Brand
document.getElementById('brandJson').value = JSON.stringify({
logo: {
image: v('brandLogoImage'),
href: v('brandLogoHref'),
},
description: v('brandDescription'),
social: Array.from(document.querySelectorAll('.social-link-item')).map(row => ({
icon: row.querySelector('[data-field="icon"]').value.trim(),
href: row.querySelector('[data-field="href"]').value.trim(),
})),
});
// Explore
document.getElementById('exploreJson').value = JSON.stringify({
heading: v('exploreHeading'),
links: Array.from(document.querySelectorAll('.explore-link-item')).map(row => ({
label: row.querySelector('[data-field="label"]').value.trim(),
href: row.querySelector('[data-field="href"]').value.trim(),
})),
});
// Contact
document.getElementById('contactJson').value = JSON.stringify({
heading: v('contactHeading'),
address: v('contactAddress'),
phone: v('contactPhone'),
email: v('contactEmail'),
});
// Newsletter
document.getElementById('newsletterJson').value = JSON.stringify({
heading: v('newsletterHeading'),
description: v('newsletterDescription'),
placeholder: v('newsletterPlaceholder'),
buttonText: v('newsletterButtonText'),
});
// Bottom
document.getElementById('bottomJson').value = JSON.stringify({
copyright: v('bottomCopyright'),
links: Array.from(document.querySelectorAll('.bottom-link-item')).map(row => ({
label: row.querySelector('[data-field="label"]').value.trim(),
href: row.querySelector('[data-field="href"]').value.trim(),
})),
});
}
function v(id) {
const el = document.getElementById(id);
return el ? el.value.trim() : '';
}
// ── Social Links ─────────────────────────────────────────────────────────
function addSocialLink(item = {}) {
const c = document.getElementById('socialLinksContainer');
c.insertAdjacentHTML('beforeend', `
<div class="card mb-2 social-link-item">
<div class="card-body p-3">
<div class="row g-2 align-items-center">
<div class="col-md-4">
<label class="form-label small">Icon</label>
<div class="input-group">
<span class="input-group-text icon-preview-cell" style="min-width:38px">
${item.icon ? `<i class="${esc(item.icon)}"></i>` : ''}
</span>
<input type="text" class="form-control form-control-sm" data-field="icon"
value="${esc(item.icon)}" placeholder="Click to pick..." readonly
style="cursor:pointer;background:#fff"
onclick="IconPicker.open(this)">
</div>
</div>
<div class="col-md-6">
<label class="form-label small">Href</label>
<input type="text" class="form-control form-control-sm" data-field="href"
value="${esc(item.href)}" placeholder="https://...">
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="button" class="btn btn-link text-danger btn-sm p-0"
onclick="this.closest('.social-link-item').remove()">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>`);
}
function populateSocialLinks(items) {
document.getElementById('socialLinksContainer').innerHTML = '';
items.forEach(item => addSocialLink(item));
}
// ── Explore Links ────────────────────────────────────────────────────────
function addExploreLink(item = {}) {
const c = document.getElementById('exploreLinksContainer');
c.insertAdjacentHTML('beforeend', `
<div class="card mb-2 explore-link-item">
<div class="card-body p-3">
<div class="row g-2 align-items-center">
<div class="col-md-5">
<label class="form-label small">Label</label>
<input type="text" class="form-control form-control-sm" data-field="label"
value="${esc(item.label)}">
</div>
<div class="col-md-5">
<label class="form-label small">Href</label>
<input type="text" class="form-control form-control-sm" data-field="href"
value="${esc(item.href)}">
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="button" class="btn btn-link text-danger btn-sm p-0"
onclick="this.closest('.explore-link-item').remove()">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>`);
}
function populateExploreLinks(items) {
document.getElementById('exploreLinksContainer').innerHTML = '';
items.forEach(item => addExploreLink(item));
}
// ── Bottom Links ─────────────────────────────────────────────────────────
function addBottomLink(item = {}) {
const c = document.getElementById('bottomLinksContainer');
c.insertAdjacentHTML('beforeend', `
<div class="card mb-2 bottom-link-item">
<div class="card-body p-3">
<div class="row g-2 align-items-center">
<div class="col-md-5">
<label class="form-label small">Label</label>
<input type="text" class="form-control form-control-sm" data-field="label"
value="${esc(item.label)}">
</div>
<div class="col-md-5">
<label class="form-label small">Href</label>
<input type="text" class="form-control form-control-sm" data-field="href"
value="${esc(item.href)}">
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="button" class="btn btn-link text-danger btn-sm p-0"
onclick="this.closest('.bottom-link-item').remove()">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</div>`);
}
function populateBottomLinks(items) {
document.getElementById('bottomLinksContainer').innerHTML = '';
items.forEach(item => addBottomLink(item));
}
// ── Utilities ────────────────────────────────────────────────────────────
function esc(val) {
if (!val) return '';
return String(val).replace(/"/g, '&quot;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function resetForm() {
if (confirm('Reset all changes to last saved state?')) {
populateAll(originalFormData);
}
}
function openImageUploader(targetInput, imageType) {
let fileInput = document.getElementById('__globalFileInput');
if (!fileInput) {
fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.id = '__globalFileInput';
fileInput.style.cssText = 'position:fixed;top:-9999px;left:-9999px;opacity:0;width:1px;height:1px;';
document.body.appendChild(fileInput);
}
fileInput.value = '';
fileInput.onchange = async function (e) {
const file = e.target.files[0];
if (!file) return;
const uploadBtn = document.querySelector(`[onclick*="${targetInput}"]`);
if (uploadBtn) uploadBtn.disabled = true;
try {
const formData = new FormData();
formData.append('image', file);
const response = await fetch(`/admin/upload/image?imageType=${imageType}`, {
method: 'POST',
body: formData,
});
if (!response.ok) throw new Error('Upload failed');
const result = await response.json();
if (!result.success) throw new Error(result.error || 'Upload failed');
const input = document.getElementById(targetInput);
if (input) {
input.value = result.path;
const previewUrl = result.path.startsWith('http')
? result.path
: window.location.origin + result.path;
let preview = input.closest('.col-md-6, .col-md-12')?.querySelector('.uploaded-preview');
if (preview) {
preview.src = previewUrl;
} else {
const img = document.createElement('img');
img.src = previewUrl;
img.className = 'img-thumbnail uploaded-preview mt-2';
img.style.maxHeight = '80px';
input.closest('.input-group')?.insertAdjacentElement('afterend', img);
}
}
} catch (err) {
console.error('Upload error:', err);
alert('Upload failed: ' + err.message);
} finally {
if (uploadBtn) uploadBtn.disabled = false;
fileInput.value = '';
}
};
fileInput.click();
}
</script>