feat: Improve home and contact CMS field guidance

This commit is contained in:
Tống Thành Đạt
2026-04-10 01:38:30 +07:00
parent ed09c7fa89
commit 7ce5921fe0
15 changed files with 529 additions and 230 deletions

View File

@@ -7,7 +7,7 @@
<p class="text-muted mb-0">Edit content displayed on Contact Us page</p>
</div>
<div>
<a href="<%= frontendUrl %>/contact-us/" class="btn btn-outline-primary" target="_blank">
<a href="<%= frontendUrl %>/contact" class="btn btn-outline-primary" target="_blank">
<i class="fas fa-external-link-alt me-2"></i>View Contact Us Page
</a>
</div>
@@ -66,7 +66,8 @@
<div class="input-group mb-2">
<input type="text" class="form-control" id="heroBackgroundImage"
name="heroBackgroundImage"
value="<%= data.hero?.backgroundImage || '' %>">
value="<%= data.hero?.backgroundImage || '' %>"
maxlength="255" data-maxlength="255">
<button type="button"
class="btn btn-outline-primary btn-upload-image"
data-target-input="heroBackgroundImage"
@@ -74,7 +75,7 @@
<i class="fas fa-upload me-1"></i>Upload
</button>
</div>
<small class="text-muted">Recommended size: 1920x1080px</small>
<small class="text-muted d-block mt-1">The contact hero currently renders at about 1496x544px on desktop. Recommended minimum upload: 1920x700px.</small>
</div>
<div class="col-md-7">
<div id="heroImagePreview" style="height: 300px;">
@@ -106,7 +107,9 @@
<div class="col-md-12">
<label class="form-label fw-medium">Title</label>
<input type="text" class="form-control" id="heroTitle" name="heroTitle"
value="<%= data.hero?.title || '' %>">
value="<%= data.hero?.title || '' %>"
maxlength="40" data-maxlength="40">
<small class="text-muted d-block mt-1">Keep the hero title short so the centered breadcrumb stays balanced on tablet and mobile.</small>
</div>
</div>
<!-- Hidden field for overlayColor - keep default value -->
@@ -176,7 +179,9 @@
<label class="form-label">Title</label>
<input type="text" class="form-control"
name="cardTitle_<%= index %>"
value="<%= card.title || '' %>">
value="<%= card.title || '' %>"
maxlength="40" data-maxlength="40">
<small class="text-muted d-block mt-1">Recommended maximum: 40 characters.</small>
</div>
<div class="col-md-12">
<label class="form-label">Icon Type</label>
@@ -324,7 +329,8 @@
class="form-control card-icon-image-input"
name="cardIconImage_<%= index %>"
value="<%= imageIconValue %>"
placeholder="/uploads/icon.png">
placeholder="/uploads/icon.png"
maxlength="255" data-maxlength="255">
<button type="button"
class="btn btn-outline-primary btn-upload-image"
data-target-input="cardIconImage_<%= index %>"
@@ -347,9 +353,7 @@
style="max-height: 100px; width: auto; display: none;"
alt="Icon preview">
<% } %>
<small class="text-muted">Upload
a custom icon image for this
contact card</small>
<small class="text-muted d-block mt-1">Custom icons render at about 28x28px inside a 64x64 circle. Use SVG or a square PNG/WebP at 64x64px or 128x128px.</small>
</div>
</div>
<div class="col-md-12">
@@ -357,9 +361,8 @@
line)</label>
<textarea class="form-control"
name="cardContent_<%= index %>"
rows="3"><%= (card.content || []).join('\n') %></textarea>
<small class="text-muted">Enter each content
item on a new line</small>
rows="3" maxlength="220" data-maxlength="220"><%= (card.content || []).join('\n') %></textarea>
<small class="text-muted d-block mt-1">Each line is shown inside a compact contact card. Keep it to 1-3 short lines.</small>
</div>
</div>
<button type="button"
@@ -386,23 +389,24 @@
<label class="form-label">Marker Title</label>
<input type="text" class="form-control" id="mapMarkerTitle"
value="<%= data.map?.markerTitle || '' %>"
placeholder="e.g., Our Office">
placeholder="e.g., Our Office"
maxlength="48" data-maxlength="48">
</div>
<div class="col-md-6">
<label class="form-label">Location</label>
<input type="text" class="form-control" id="mapLocation"
value="<%= data.map?.location || '' %>"
placeholder="e.g., 123 Main St, City, Country">
<small class="text-muted">Enter address - map will be automatically
shown</small>
placeholder="e.g., 123 Main St, City, Country"
maxlength="120" data-maxlength="120">
<small class="text-muted d-block mt-1">Enter a full address. This text is used for map lookup and should stay concise.</small>
</div>
<div class="col-md-12">
<label class="form-label">Google Map Embed URL</label>
<input type="text" class="form-control" id="mapEmbedUrl"
value="<%= data.map?.embedUrl || '' %>"
placeholder="https://www.google.com/maps/embed?...">
<small class="text-muted">Paste embed URL from Google Maps (Share ->
Embed a map)</small>
placeholder="https://www.google.com/maps/embed?..."
maxlength="1000" data-maxlength="1000">
<small class="text-muted d-block mt-1">Paste the Google Maps embed URL from Share -> Embed a map.</small>
</div>
<div class="col-md-12">
<div id="mapPreview"
@@ -468,22 +472,29 @@
<div class="col-md-6">
<label class="form-label">Section Label</label>
<input type="text" class="form-control" id="formSectionLabel"
value="<%= data.form?.sectionLabel || '' %>">
value="<%= data.form?.sectionLabel || '' %>"
maxlength="32" data-maxlength="32">
<small class="text-muted d-block mt-1">Legacy label. Keep it short if you still use it in future templates.</small>
</div>
<div class="col-md-6">
<label class="form-label">Submit Button Text</label>
<input type="text" class="form-control" id="formSubmitButtonText"
value="<%= data.form?.submitButton?.text || 'Send Message' %>">
value="<%= data.form?.submitButton?.text || 'Send Message' %>"
maxlength="24" data-maxlength="24">
<small class="text-muted d-block mt-1">Recommended maximum: 24 characters.</small>
</div>
<div class="col-md-12">
<label class="form-label">Heading</label>
<input type="text" class="form-control" id="formHeading"
value="<%= data.form?.heading || '' %>">
value="<%= data.form?.heading || '' %>"
maxlength="48" data-maxlength="48">
<small class="text-muted d-block mt-1">The form heading spans the full form width. Recommended maximum: 48 characters.</small>
</div>
<div class="col-md-12">
<label class="form-label">Description</label>
<textarea class="form-control" id="formDescription"
rows="2"><%= data.form?.description || '' %></textarea>
rows="2" maxlength="160" data-maxlength="160"><%= data.form?.description || '' %></textarea>
<small class="text-muted d-block mt-1">This line is centered under the form heading. Recommended maximum: 160 characters.</small>
</div>
<!-- Hidden fields for submitButton icon and buttonClass -->
<input type="hidden" id="formSubmitButtonIcon"
@@ -510,7 +521,9 @@
<input type="text" class="form-control"
name="fieldName_<%= index %>"
value="<%= field.name || '' %>"
placeholder="e.g., Your Name">
placeholder="e.g., Your Name"
maxlength="32" data-maxlength="32">
<small class="text-muted d-block mt-1">Keep field labels short for the stacked mobile form.</small>
</div>
<div class="col-md-3">
<label class="form-label">Field Type</label>
@@ -536,7 +549,8 @@
<label class="form-label">Placeholder</label>
<input type="text" class="form-control"
name="fieldPlaceholder_<%= index %>"
value="<%= field.placeholder || '' %>">
value="<%= field.placeholder || '' %>"
maxlength="72" data-maxlength="72">
</div>
<div class="col-md-2">
<label class="form-label">Required</label>
@@ -552,9 +566,9 @@
<input type="text" class="form-control"
name="fieldProgrammeName_<%= index %>"
value="<%= field.programmeName || '' %>"
placeholder="e.g., Summer Camp 2024">
<small class="text-muted">Internal name for the
programme</small>
placeholder="e.g., Summer Camp 2024"
maxlength="48" data-maxlength="48">
<small class="text-muted d-block mt-1">Internal programme reference only. Recommended maximum: 48 characters.</small>
</div>
<!-- Hidden fields for label and colClass -->
<input type="hidden" name="fieldLabel_<%= index %>"
@@ -775,6 +789,7 @@
updateAllJsonInputs(originalFormData);
initializeFormHandlers();
initContactCharacterCounters(document);
});
function applyDateFilter() {
@@ -1195,6 +1210,65 @@
});
}
function ensureContactCounter(input) {
if (!input || !input.dataset.maxlength) {
return null;
}
if (!input.id) {
input.id = `contactField_${Math.random().toString(36).slice(2, 10)}`;
}
const field = input.closest('.col-md-12, .col-md-7, .col-md-6, .col-md-5, .col-md-4, .col-md-3, .col-md-2, .col-12') || input.parentElement;
const anchor = input.closest('.input-group') || input;
const parent = anchor?.parentElement || field;
if (!field || !anchor || !parent) {
return null;
}
let hint = field.querySelector(`[data-counter-for="${input.id}"]`);
if (!hint) {
hint = document.createElement('small');
hint.className = 'form-text contact-limit-counter text-secondary';
hint.dataset.counterFor = input.id;
}
if (hint.previousElementSibling !== anchor) {
parent.insertBefore(hint, anchor.nextSibling);
}
return hint;
}
function updateContactCounter(input) {
const hint = ensureContactCounter(input);
if (!hint) {
return;
}
const max = Number(input.dataset.maxlength);
if (Number.isFinite(max) && max > 0 && (input.value || '').length > max) {
input.value = (input.value || '').slice(0, max);
}
const length = (input.value || '').length;
hint.textContent = `${length}/${max} characters`;
hint.classList.toggle('text-danger', length >= max);
}
function initContactCharacterCounters(scope = document) {
scope.querySelectorAll('input[data-maxlength], textarea[data-maxlength]').forEach((input) => {
updateContactCounter(input);
if (input.dataset.counterBound === 'true') {
return;
}
input.dataset.counterBound = 'true';
input.addEventListener('input', () => updateContactCounter(input));
});
}
function updateAllJsonInputs(data) {
document.getElementById('heroJson').value = JSON.stringify(data.hero || {});
document.getElementById('contactCardsJson').value = JSON.stringify(data.contactCards || []);
@@ -1252,7 +1326,8 @@
</div>
<div class="col-md-6">
<label class="form-label">Title</label>
<input type="text" class="form-control" name="cardTitle_${index}">
<input type="text" class="form-control" name="cardTitle_${index}" maxlength="40" data-maxlength="40">
<small class="text-muted d-block mt-1">Recommended maximum: 40 characters.</small>
</div>
<div class="col-md-12">
<label class="form-label">Icon Type</label>
@@ -1306,20 +1381,20 @@
<label class="form-label">Upload Icon Image</label>
<div class="input-group">
<input type="text" class="form-control card-icon-image-input" name="cardIconImage_${index}"
placeholder="/uploads/icon.png">
placeholder="/uploads/icon.png" maxlength="255" data-maxlength="255">
<button type="button" class="btn btn-outline-primary btn-upload-image"
data-target-input="cardIconImage_${index}" data-image-type="contact">
<i class="fas fa-upload me-1"></i>Upload Icon
</button>
</div>
<img src="" class="img-thumbnail mt-2 icon-image-preview" data-index="${index}" style="max-height: 100px; width: auto; display: none;" alt="Icon preview">
<small class="text-muted">Upload a custom icon image for this contact card</small>
<small class="text-muted d-block mt-1">Custom icons render at about 28x28px inside a 64x64 circle. Use SVG or a square PNG/WebP at 64x64px or 128x128px.</small>
</div>
</div>
<div class="col-md-12">
<label class="form-label">Content (one per line)</label>
<textarea class="form-control" name="cardContent_${index}" rows="3"></textarea>
<small class="text-muted">Enter each content item on a new line</small>
<textarea class="form-control" name="cardContent_${index}" rows="3" maxlength="220" data-maxlength="220"></textarea>
<small class="text-muted d-block mt-1">Each line is shown inside a compact contact card. Keep it to 1-3 short lines.</small>
</div>
</div>
<button type="button" class="btn btn-outline-danger btn-sm mt-3" onclick="removeContactCard(this)">
@@ -1340,6 +1415,8 @@
openImageUploader(targetInput, imageType);
});
}
initContactCharacterCounters(newCard);
}
function handleIconSourceChange(radio) {
@@ -1385,7 +1462,8 @@
<div class="row g-3">
<div class="col-md-3">
<label class="form-label">Label</label>
<input type="text" class="form-control" name="fieldName_${index}" placeholder="e.g., Your Name">
<input type="text" class="form-control" name="fieldName_${index}" placeholder="e.g., Your Name" maxlength="32" data-maxlength="32">
<small class="text-muted d-block mt-1">Keep field labels short for the stacked mobile form.</small>
</div>
<div class="col-md-3">
<label class="form-label">Field Type</label>
@@ -1399,7 +1477,7 @@
</div>
<div class="col-md-4">
<label class="form-label">Placeholder</label>
<input type="text" class="form-control" name="fieldPlaceholder_${index}">
<input type="text" class="form-control" name="fieldPlaceholder_${index}" maxlength="72" data-maxlength="72">
</div>
<div class="col-md-2">
<label class="form-label">Required</label>
@@ -1409,8 +1487,8 @@
</div>
<div class="col-md-3 programme-name-field" style="display: none;">
<label class="form-label">Programme Name</label>
<input type="text" class="form-control" name="fieldProgrammeName_${index}" placeholder="e.g., Summer Camp 2024">
<small class="text-muted">Internal name for the programme</small>
<input type="text" class="form-control" name="fieldProgrammeName_${index}" placeholder="e.g., Summer Camp 2024" maxlength="48" data-maxlength="48">
<small class="text-muted d-block mt-1">Internal programme reference only. Recommended maximum: 48 characters.</small>
</div>
<!-- Hidden fields for label and colClass -->
<input type="hidden" name="fieldLabel_${index}" value="">
@@ -1423,6 +1501,7 @@
</div>
`;
container.insertAdjacentHTML('beforeend', html);
initContactCharacterCounters(container.lastElementChild);
}
function removeFormField(button) {
@@ -1662,4 +1741,4 @@
}
});
}
</script>
</script>