forked from UKSOURCE/cms.hailearning.edu.vn
Merge branch 'main' of https://gits.techvanguard.vn/UKSOURCE/cms.hailearning.edu.vn into hoanganh-03022026-appointment-contact-pricing
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -268,6 +268,11 @@
|
||||
tab.addEventListener('shown.bs.tab', function (event) {
|
||||
const targetId = event.target.getAttribute('href').substring(1);
|
||||
document.getElementById('activeTabInput').value = targetId;
|
||||
|
||||
// Update URL without reload to preserve tab state
|
||||
const url = new URL(window.location);
|
||||
url.searchParams.set('tab', targetId);
|
||||
window.history.replaceState({}, '', url);
|
||||
|
||||
// Only load Menu Tree if clicking on the menu tab
|
||||
if (targetId === 'menu') {
|
||||
@@ -365,6 +370,12 @@
|
||||
showNotification('All changes saved successfully', 'success');
|
||||
submitBtn.classList.remove('btn-primary');
|
||||
submitBtn.classList.add('btn-outline-primary');
|
||||
|
||||
// Reload to refresh data, preserve current tab
|
||||
const currentTab = document.getElementById('activeTabInput').value;
|
||||
setTimeout(() => {
|
||||
window.location.href = window.location.pathname + '?tab=' + currentTab;
|
||||
}, 1000);
|
||||
} else {
|
||||
const errorMsg = (!headerResult.success ? headerResult.message : '') || (!menuResult.success ? menuResult.message : '') || 'Unable to save some changes';
|
||||
showNotification('Error: ' + errorMsg, 'error');
|
||||
@@ -1113,19 +1124,29 @@
|
||||
console.log('Response:', response.data);
|
||||
|
||||
if (response.data.success || response.status === 200) {
|
||||
showToast('Success', 'Menu information has been updated', 'success');
|
||||
showNotification('Menu item saved successfully', 'success');
|
||||
|
||||
// Hide modal
|
||||
const modalElement = document.getElementById('modalAddMenu');
|
||||
const modal = bootstrap.Modal.getOrCreateInstance(modalElement);
|
||||
modal.hide();
|
||||
// Refresh data or reload tab
|
||||
setTimeout(() => window.location.reload(), 1000);
|
||||
|
||||
// Mark as changed so user needs to click Save Changes
|
||||
if (typeof window.markHeaderChanged === 'function') {
|
||||
window.markHeaderChanged();
|
||||
}
|
||||
|
||||
// Reload page to show updated menu structure, preserve current tab
|
||||
const currentTab = document.getElementById('activeTabInput').value;
|
||||
setTimeout(() => {
|
||||
window.location.href = window.location.pathname + '?tab=' + currentTab;
|
||||
}, 1000);
|
||||
} else {
|
||||
showToast('Error', response.data.message || 'Unable to save menu', 'error');
|
||||
showNotification(response.data.message || 'Unable to save menu', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('AJAX Error:', error);
|
||||
showToast('Error', 'Server connection error: ' + (error.response?.data?.message || error.message), 'error');
|
||||
showNotification('Server connection error: ' + (error.response?.data?.message || error.message), 'error');
|
||||
} finally {
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false;
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
<span class="badge bg-light text-dark border ms-2" style="font-size: 0.7rem;">External</span>
|
||||
<% } %>
|
||||
<% if (item.status === 'inactive') { %>
|
||||
<span class="badge bg-soft-secondary ms-2">Inactive</span>
|
||||
<span class="badge ms-2 bg-soft-danger text-danger">Inactive</span>
|
||||
<% } else { %>
|
||||
<span class="badge bg-soft-success ms-2">Active</span>
|
||||
<% } %>
|
||||
|
||||
@@ -88,15 +88,15 @@
|
||||
<div class="card-body">
|
||||
<div class="tab-content">
|
||||
<%- include('sections/hero') %>
|
||||
<%- include('sections/whyChooseUs') %>
|
||||
<%- include('sections/visaSolutions') %>
|
||||
<%- include('sections/visaCountries') %>
|
||||
<%- include('sections/testimonials') %>
|
||||
<%- include('sections/videoGallery') %>
|
||||
<%- include('sections/faq') %>
|
||||
<%- include('sections/achievements') %>
|
||||
<%- include('sections/partners') %>
|
||||
<%- include('sections/blogPreview') %>
|
||||
<%- include('sections/whyChooseUs') %>
|
||||
<%- include('sections/visaSolutions') %>
|
||||
<%- include('sections/visaCountries') %>
|
||||
<%- include('sections/testimonials') %>
|
||||
<%- include('sections/videoGallery') %>
|
||||
<%- include('sections/faq') %>
|
||||
<%- include('sections/achievements') %>
|
||||
<%- include('sections/partners') %>
|
||||
<%- include('sections/blogPreview') %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -136,9 +136,9 @@
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const form = document.querySelector("form");
|
||||
if (form) {
|
||||
form.addEventListener("submit", function(e) {
|
||||
form.addEventListener("submit", function (e) {
|
||||
console.log("Form submitting, collecting data from scrapers...");
|
||||
|
||||
|
||||
// Tự động thu gom dữ liệu từ các section đã đăng ký
|
||||
Object.keys(window.homeScrapers).forEach(section => {
|
||||
const input = document.getElementById(section + 'Json');
|
||||
@@ -164,7 +164,7 @@
|
||||
// --- UTILITIES (Dùng chung) ---
|
||||
|
||||
function initImageUploads() {
|
||||
document.addEventListener("click", function(e) {
|
||||
document.addEventListener("click", function (e) {
|
||||
const btn = e.target.closest(".btn-upload-image");
|
||||
if (btn) {
|
||||
document.getElementById("currentImageType").value = btn.dataset.imageType;
|
||||
|
||||
@@ -13,32 +13,18 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Heading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="faqHeading"
|
||||
value="<%= data.faq?.heading || '' %>"
|
||||
placeholder="e.g., Got Questions? We've Got Answers"
|
||||
/>
|
||||
<input type="text" class="form-control" id="faqHeading" value="<%= data.faq?.heading || '' %>"
|
||||
placeholder="e.g., Got Questions? We've Got Answers" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Subheading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="faqSubheading"
|
||||
value="<%= data.faq?.subheading || '' %>"
|
||||
placeholder="e.g., Visa FAQs"
|
||||
/>
|
||||
<input type="text" class="form-control" id="faqSubheading" value="<%= data.faq?.subheading || '' %>"
|
||||
placeholder="e.g., Visa FAQs" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Description</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="faqDescription"
|
||||
rows="3"
|
||||
placeholder="Enter description"
|
||||
><%= data.faq?.description || '' %></textarea>
|
||||
<textarea class="form-control" id="faqDescription" rows="3"
|
||||
placeholder="Enter description"><%= data.faq?.description || '' %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,16 +34,33 @@
|
||||
<!-- FAQ Items -->
|
||||
<div class="col-md-12">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-question-circle me-2"></i>FAQ Items
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% (data.faq?.items || []).forEach(function(item, index) { %>
|
||||
<div class="card mb-3 bg-light border">
|
||||
<div class="card-body" id="faqItemsContainer">
|
||||
<%
|
||||
const faqItems = (data.faq && Array.isArray(data.faq.items) && data.faq.items.length === 5)
|
||||
? data.faq.items
|
||||
: (data.faq && Array.isArray(data.faq.items) && data.faq.items.length > 0)
|
||||
? (function () {
|
||||
const clone = data.faq.items.slice(0, 5);
|
||||
while (clone.length < 5) clone.push({});
|
||||
return clone;
|
||||
})()
|
||||
: [{}, {}, {}, {}, {}];
|
||||
%>
|
||||
<% faqItems.forEach(function(item, index) { %>
|
||||
<div class="card mb-3 bg-light border faq-item" data-index="<%= index %>">
|
||||
<div class="card-header d-flex justify-content-between align-items-center bg-white">
|
||||
<h6 class="card-title fw-bold mb-0">FAQ
|
||||
<span class="faq-item-label">
|
||||
<%= index + 1 %>
|
||||
</span>
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h6 class="card-title fw-bold mb-3">FAQ <%= index + 1 %></h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Question</label>
|
||||
@@ -98,23 +101,13 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Label</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="faqCtaLabel"
|
||||
value="<%= data.faq?.ctaButton?.label || '' %>"
|
||||
placeholder="e.g., contact us"
|
||||
/>
|
||||
<input type="text" class="form-control" id="faqCtaLabel" value="<%= data.faq?.ctaButton?.label || '' %>"
|
||||
placeholder="e.g., contact us" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="faqCtaHref"
|
||||
value="<%= data.faq?.ctaButton?.href || '' %>"
|
||||
placeholder="/contact"
|
||||
/>
|
||||
<input type="text" class="form-control" id="faqCtaHref" value="<%= data.faq?.ctaButton?.href || '' %>"
|
||||
placeholder="/contact" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -122,3 +115,34 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Register scraper to collect FAQ data before form submit
|
||||
window.homeScrapers = window.homeScrapers || {};
|
||||
window.homeScrapers.faq = () => {
|
||||
const getVal = (id) => (document.getElementById(id)?.value || "").trim();
|
||||
|
||||
const items = [];
|
||||
document.querySelectorAll(".faq-item").forEach((el, idx) => {
|
||||
const index = el.getAttribute("data-index") || idx;
|
||||
|
||||
const question = getVal(`faqQuestion_${index}`);
|
||||
const answer = getVal(`faqAnswer_${index}`);
|
||||
|
||||
if (question || answer) {
|
||||
items.push({ question, answer });
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
heading: getVal("faqHeading"),
|
||||
subheading: getVal("faqSubheading"),
|
||||
description: getVal("faqDescription"),
|
||||
ctaButton: {
|
||||
label: getVal("faqCtaLabel"),
|
||||
href: getVal("faqCtaHref")
|
||||
},
|
||||
items
|
||||
};
|
||||
};
|
||||
</script>
|
||||
@@ -1,157 +1,312 @@
|
||||
<!-- Hero Tab -->
|
||||
<div class="tab-pane fade show active" id="hero" role="tabpanel">
|
||||
<div class="row g-4">
|
||||
<!-- Basic Info -->
|
||||
<!-- Background Image (section-level) -->
|
||||
<div class="col-md-12">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<div class="card border shadow-sm mb-3">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-info-circle me-2"></i>Basic Information
|
||||
<i class="fas fa-image me-2"></i>Hero Background
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroTitle"
|
||||
value="<%= data.hero?.title || '' %>"
|
||||
placeholder="e.g., From Application to Visa"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Subtitle</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroSubtitle"
|
||||
value="<%= data.hero?.subtitle || '' %>"
|
||||
placeholder="e.g., Global Education Simplified"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Description</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="heroDescription"
|
||||
rows="3"
|
||||
placeholder="Enter hero description"
|
||||
><%= data.hero?.description || '' %></textarea>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Background Image</label>
|
||||
<div class="input-group mb-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroBackgroundImage"
|
||||
value="<%= data.hero?.backgroundImage || '' %>"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="heroBackgroundImage"
|
||||
data-image-type="home"
|
||||
>
|
||||
<input type="text" class="form-control" id="heroBackgroundImage"
|
||||
value="<%= data.hero?.backgroundImage || '' %>" placeholder="/assets/img/home-1/hero/bg.jpg" />
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="heroBackgroundImage" data-image-type="home">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
<% if (data.hero?.backgroundImage) { %>
|
||||
<div class="mt-2">
|
||||
<img
|
||||
src="<%= data.hero.backgroundImage %>"
|
||||
class="img-thumbnail"
|
||||
style="height: 200px; width: 100%; object-fit: cover;"
|
||||
alt="Background preview"
|
||||
/>
|
||||
<div class="mt-2">
|
||||
<img src="<%= data.hero.backgroundImage %>" class="img-thumbnail"
|
||||
style="height: 200px; width: 100%; object-fit: cover;" alt="Background preview" />
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slides -->
|
||||
<div class="col-md-12">
|
||||
<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-sliders-h me-2"></i>Hero Slides
|
||||
</h6>
|
||||
<button type="button" class="btn btn-sm btn-primary" id="addHeroSlideBtn">
|
||||
<i class="fas fa-plus me-1"></i>Add Slide
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body" id="heroSlidesContainer">
|
||||
<% const existingSlides=(data.hero && Array.isArray(data.hero.slides) && data.hero.slides.length> 0)
|
||||
? data.hero.slides
|
||||
: [{
|
||||
title: data.hero?.title || '',
|
||||
subtitle: data.hero?.subtitle || '',
|
||||
description: data.hero?.description || '',
|
||||
heroImage: data.hero?.heroImage || '',
|
||||
videoUrl: data.hero?.videoUrl || '',
|
||||
primaryButton: data.hero?.primaryButton || {},
|
||||
secondaryButton: data.hero?.secondaryButton || {},
|
||||
}];
|
||||
%>
|
||||
|
||||
<% existingSlides.forEach(function(slide, index) { %>
|
||||
<div class="card mb-3 border hero-slide-item" data-index="<%= index %>">
|
||||
<div class="card-header bg-light d-flex justify-content-between align-items-center">
|
||||
<span class="fw-semibold">Slide
|
||||
<span class="hero-slide-label">
|
||||
<%= index + 1 %>
|
||||
</span>
|
||||
</span>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger btn-remove-hero-slide">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input type="text" class="form-control" id="heroSlide_<%= index %>_title"
|
||||
value="<%= slide.title || '' %>" placeholder="e.g., From Application to Visa" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Subtitle</label>
|
||||
<input type="text" class="form-control" id="heroSlide_<%= index %>_subtitle"
|
||||
value="<%= slide.subtitle || '' %>" placeholder="e.g., Global Education Simplified" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Description</label>
|
||||
<textarea class="form-control" id="heroSlide_<%= index %>_description" rows="3"
|
||||
placeholder="Enter hero description"><%= slide.description || '' %></textarea>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Hero Image</label>
|
||||
<small class="text-muted d-block mb-1">Recommended size: 893x848px</small>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" class="form-control" id="heroSlide_<%= index %>_heroImage"
|
||||
value="<%= slide.heroImage || '' %>" placeholder="/assets/img/home-1/hero/man.png" />
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="heroSlide_<%= index %>_heroImage" data-image-type="home">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
<% if (slide.heroImage) { %>
|
||||
<div class="mt-2">
|
||||
<img src="<%= slide.heroImage %>" class="img-thumbnail"
|
||||
style="height: 200px; width: 100%; object-fit: cover;" alt="Hero image preview" />
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Video URL</label>
|
||||
<input type="text" class="form-control" id="heroSlide_<%= index %>_videoUrl"
|
||||
value="<%= slide.videoUrl || '' %>" placeholder="https://www.youtube.com/watch?v=..." />
|
||||
</div>
|
||||
|
||||
<!-- Primary Button -->
|
||||
<div class="col-md-6">
|
||||
<div class="border rounded p-3 h-100">
|
||||
<h6 class="fw-semibold mb-3">
|
||||
<i class="fas fa-mouse-pointer me-2"></i>Primary Button
|
||||
</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Label</label>
|
||||
<input type="text" class="form-control" id="heroSlide_<%= index %>_primaryLabel"
|
||||
value="<%= slide.primaryButton?.label || '' %>" placeholder="e.g., Apply now" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input type="text" class="form-control" id="heroSlide_<%= index %>_primaryHref"
|
||||
value="<%= slide.primaryButton?.href || '' %>" placeholder="/contact" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Secondary Button -->
|
||||
<div class="col-md-6">
|
||||
<div class="border rounded p-3 h-100">
|
||||
<h6 class="fw-semibold mb-3">
|
||||
<i class="fas fa-mouse-pointer me-2"></i>Secondary Button
|
||||
</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Label</label>
|
||||
<input type="text" class="form-control" id="heroSlide_<%= index %>_secondaryLabel"
|
||||
value="<%= slide.secondaryButton?.label || '' %>"
|
||||
placeholder="e.g., Book Free Consultation" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input type="text" class="form-control" id="heroSlide_<%= index %>_secondaryHref"
|
||||
value="<%= slide.secondaryButton?.href || '' %>" placeholder="/contact" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Video URL</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroVideoUrl"
|
||||
value="<%= data.hero?.videoUrl || '' %>"
|
||||
placeholder="https://www.youtube.com/watch?v=..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Primary Button -->
|
||||
<div class="col-md-6">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-mouse-pointer me-2"></i>Primary Button
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Label</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroPrimaryButtonLabel"
|
||||
value="<%= data.hero?.primaryButton?.label || '' %>"
|
||||
placeholder="e.g., Apply now"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroPrimaryButtonHref"
|
||||
value="<%= data.hero?.primaryButton?.href || '' %>"
|
||||
placeholder="/contact"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Secondary Button -->
|
||||
<div class="col-md-6">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-mouse-pointer me-2"></i>Secondary Button
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Label</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroSecondaryButtonLabel"
|
||||
value="<%= data.hero?.secondaryButton?.label || '' %>"
|
||||
placeholder="e.g., Book Free Consultation"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="heroSecondaryButtonHref"
|
||||
value="<%= data.hero?.secondaryButton?.href || '' %>"
|
||||
placeholder="/contact"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Register scraper to collect Hero data before form submit
|
||||
window.homeScrapers = window.homeScrapers || {};
|
||||
window.homeScrapers.hero = () => {
|
||||
const getVal = (id) => (document.getElementById(id)?.value || "").trim();
|
||||
|
||||
const backgroundImage = getVal("heroBackgroundImage");
|
||||
|
||||
const slides = [];
|
||||
const slideEls = document.querySelectorAll(".hero-slide-item");
|
||||
slideEls.forEach((el, idx) => {
|
||||
const index = el.getAttribute("data-index") || idx;
|
||||
const prefix = `heroSlide_${index}_`;
|
||||
|
||||
const slide = {
|
||||
title: getVal(prefix + "title"),
|
||||
subtitle: getVal(prefix + "subtitle"),
|
||||
description: getVal(prefix + "description"),
|
||||
heroImage: getVal(prefix + "heroImage"),
|
||||
videoUrl: getVal(prefix + "videoUrl"),
|
||||
primaryButton: {
|
||||
label: getVal(prefix + "primaryLabel"),
|
||||
href: getVal(prefix + "primaryHref")
|
||||
},
|
||||
secondaryButton: {
|
||||
label: getVal(prefix + "secondaryLabel"),
|
||||
href: getVal(prefix + "secondaryHref")
|
||||
}
|
||||
};
|
||||
|
||||
// Bỏ qua slide trống hoàn toàn
|
||||
const hasContent = slide.title || slide.subtitle || slide.description || slide.heroImage || slide.videoUrl || slide.primaryButton.label || slide.secondaryButton.label;
|
||||
|
||||
if (hasContent) {
|
||||
slides.push(slide);
|
||||
}
|
||||
});
|
||||
|
||||
// Legacy single-slide fields: map từ slide đầu tiên (nếu có) để không vỡ FE cũ
|
||||
const first = slides[0] || {};
|
||||
|
||||
return {
|
||||
backgroundImage,
|
||||
slides,
|
||||
title: first.title || "",
|
||||
subtitle: first.subtitle || "",
|
||||
description: first.description || "",
|
||||
heroImage: first.heroImage || "",
|
||||
videoUrl: first.videoUrl || "",
|
||||
primaryButton: first.primaryButton || {
|
||||
label: "",
|
||||
href: ""
|
||||
},
|
||||
secondaryButton: first.secondaryButton || {
|
||||
label: "",
|
||||
href: ""
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Add / remove slides on the client
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const container = document.getElementById("heroSlidesContainer");
|
||||
const addBtn = document.getElementById("addHeroSlideBtn");
|
||||
|
||||
if (!container || !addBtn)
|
||||
return;
|
||||
|
||||
|
||||
// Re-index slides, update labels, IDs and upload target attributes
|
||||
const updateLabels = () => {
|
||||
container.querySelectorAll(".hero-slide-item").forEach((el, idx) => {
|
||||
el.setAttribute("data-index", String(idx));
|
||||
|
||||
const label = el.querySelector(".hero-slide-label");
|
||||
if (label)
|
||||
label.textContent = String(idx + 1);
|
||||
|
||||
|
||||
// Update input/textarea IDs to match new index
|
||||
el.querySelectorAll("input, textarea").forEach((input) => {
|
||||
if (!input.id)
|
||||
return;
|
||||
|
||||
const newId = input.id.replace(/heroSlide_\d+_/, `heroSlide_${idx}_`);
|
||||
input.id = newId;
|
||||
});
|
||||
|
||||
// Update upload button target-input so upload goes to correct slide
|
||||
el.querySelectorAll(".btn-upload-image").forEach((btn) => {
|
||||
const target = btn.getAttribute("data-target-input") || "";
|
||||
if (!target)
|
||||
return;
|
||||
|
||||
const newTarget = target.replace(/heroSlide_\d+_/, `heroSlide_${idx}_`);
|
||||
btn.setAttribute("data-target-input", newTarget);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
addBtn.addEventListener("click", () => {
|
||||
const template = container.querySelector(".hero-slide-item");
|
||||
if (!template)
|
||||
return;
|
||||
|
||||
|
||||
const clone = template.cloneNode(true);
|
||||
|
||||
// Clear values for the cloned slide (IDs will be fixed by updateLabels)
|
||||
clone.querySelectorAll("input, textarea").forEach((input) => {
|
||||
input.value = "";
|
||||
});
|
||||
|
||||
// Clear image previews
|
||||
clone.querySelectorAll("img").forEach((img) => {
|
||||
img.classList.add("d-none");
|
||||
img.removeAttribute("src");
|
||||
});
|
||||
|
||||
container.appendChild(clone);
|
||||
updateLabels();
|
||||
});
|
||||
|
||||
container.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest(".btn-remove-hero-slide");
|
||||
if (!btn)
|
||||
return;
|
||||
|
||||
|
||||
const card = btn.closest(".hero-slide-item");
|
||||
if (!card)
|
||||
return;
|
||||
|
||||
|
||||
const all = container.querySelectorAll(".hero-slide-item");
|
||||
if (all.length <= 1) { // Không cho xóa slide cuối cùng
|
||||
return;
|
||||
}
|
||||
|
||||
card.remove();
|
||||
updateLabels();
|
||||
});
|
||||
|
||||
// Initial normalization (in case indices rendered from server are not 0..n)
|
||||
updateLabels();
|
||||
});
|
||||
</script>
|
||||
@@ -13,49 +13,26 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Heading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="testimonialsHeading"
|
||||
value="<%= data.testimonials?.heading || '' %>"
|
||||
placeholder="e.g., Student Reviews & Testimonials"
|
||||
/>
|
||||
<input type="text" class="form-control" id="testimonialsHeading"
|
||||
value="<%= data.testimonials?.heading || '' %>" placeholder="e.g., Student Reviews & Testimonials" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Subheading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="testimonialsSubheading"
|
||||
value="<%= data.testimonials?.subheading || '' %>"
|
||||
placeholder="e.g., What Our Students Say"
|
||||
/>
|
||||
<input type="text" class="form-control" id="testimonialsSubheading"
|
||||
value="<%= data.testimonials?.subheading || '' %>" placeholder="e.g., What Our Students Say" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Video URL</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="testimonialsVideoUrl"
|
||||
value="<%= data.testimonials?.videoUrl || '' %>"
|
||||
placeholder="https://www.youtube.com/watch?v=..."
|
||||
/>
|
||||
<input type="text" class="form-control" id="testimonialsVideoUrl"
|
||||
value="<%= data.testimonials?.videoUrl || '' %>" placeholder="https://www.youtube.com/watch?v=..." />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Video Thumbnail</label>
|
||||
<div class="input-group mb-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="testimonialsVideoThumbnail"
|
||||
value="<%= data.testimonials?.videoThumbnail || '' %>"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="testimonialsVideoThumbnail"
|
||||
data-image-type="home"
|
||||
>
|
||||
<input type="text" class="form-control" id="testimonialsVideoThumbnail"
|
||||
value="<%= data.testimonials?.videoThumbnail || '' %>" />
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="testimonialsVideoThumbnail" data-image-type="home">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
@@ -68,92 +45,167 @@
|
||||
<!-- Testimonial Items -->
|
||||
<div class="col-md-12">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-comments me-2"></i>Testimonials
|
||||
</h6>
|
||||
<button type="button" class="btn btn-sm btn-primary" id="addTestimonialBtn">
|
||||
<i class="fas fa-plus me-1"></i>Add Testimonial
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body" id="testimonialsItemsContainer">
|
||||
<% (data.testimonials?.items || []).forEach(function(item, index) { %>
|
||||
<div class="card mb-3 bg-light border">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title fw-bold mb-3">Testimonial <%= index + 1 %></h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="testimonialsName_<%= index %>"
|
||||
value="<%= item.name || '' %>"
|
||||
placeholder="e.g., Sohel Tanvir"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Role</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="testimonialsRole_<%= index %>"
|
||||
value="<%= item.role || '' %>"
|
||||
placeholder="e.g., Student"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Country</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="testimonialsCountry_<%= index %>"
|
||||
value="<%= item.country || '' %>"
|
||||
placeholder="e.g., Canada"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Rating</label>
|
||||
<input
|
||||
type="number"
|
||||
class="form-control"
|
||||
id="testimonialsRating_<%= index %>"
|
||||
value="<%= item.rating || 5 %>"
|
||||
min="1"
|
||||
max="5"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Comment</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="testimonialsComment_<%= index %>"
|
||||
rows="3"
|
||||
placeholder="Enter testimonial comment"
|
||||
><%= item.comment || '' %></textarea>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Avatar</label>
|
||||
<div class="input-group mb-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="testimonialsAvatar_<%= index %>"
|
||||
value="<%= item.avatar || '' %>"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="testimonialsAvatar_<%= index %>"
|
||||
data-image-type="home"
|
||||
>
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
<div class="card mb-3 bg-light border testimonial-item" data-index="<%= index %>">
|
||||
<div class="card-header d-flex justify-content-between align-items-center bg-white">
|
||||
<h6 class="card-title fw-bold mb-0">Testimonial <span class="testimonial-label">
|
||||
<%= index + 1 %>
|
||||
</span></h6>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger btn-remove-testimonial">
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Name</label>
|
||||
<input type="text" class="form-control" id="testimonialsName_<%= index %>"
|
||||
value="<%= item.name || '' %>" placeholder="e.g., Sohel Tanvir" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Role</label>
|
||||
<input type="text" class="form-control" id="testimonialsRole_<%= index %>"
|
||||
value="<%= item.role || '' %>" placeholder="e.g., Student" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Country</label>
|
||||
<input type="text" class="form-control" id="testimonialsCountry_<%= index %>"
|
||||
value="<%= item.country || '' %>" placeholder="e.g., Canada" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Rating</label>
|
||||
<input type="number" class="form-control" id="testimonialsRating_<%= index %>"
|
||||
value="<%= item.rating || 5 %>" min="1" max="5" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Comment</label>
|
||||
<textarea class="form-control" id="testimonialsComment_<%= index %>" rows="3"
|
||||
placeholder="Enter testimonial comment"><%= item.comment || '' %></textarea>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Avatar</label>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" class="form-control" id="testimonialsAvatar_<%= index %>"
|
||||
value="<%= item.avatar || '' %>" />
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="testimonialsAvatar_<%= index %>" data-image-type="home">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Register scraper to collect Testimonials data before form submit
|
||||
window.homeScrapers = window.homeScrapers || {};
|
||||
window.homeScrapers.testimonials = () => {
|
||||
const getVal = (id) => (document.getElementById(id)?.value || "").trim();
|
||||
|
||||
const items = [];
|
||||
document.querySelectorAll(".testimonial-item").forEach((el, idx) => {
|
||||
const index = el.getAttribute("data-index") || idx;
|
||||
|
||||
const name = getVal(`testimonialsName_${index}`);
|
||||
const role = getVal(`testimonialsRole_${index}`);
|
||||
const country = getVal(`testimonialsCountry_${index}`);
|
||||
const ratingStr = getVal(`testimonialsRating_${index}`);
|
||||
const rating = ratingStr ? parseInt(ratingStr, 10) : 5;
|
||||
const comment = getVal(`testimonialsComment_${index}`);
|
||||
const avatar = getVal(`testimonialsAvatar_${index}`);
|
||||
|
||||
if (name || role || country || comment || avatar) {
|
||||
items.push({
|
||||
name,
|
||||
role,
|
||||
country,
|
||||
rating: isNaN(rating) ? 5 : Math.min(Math.max(rating, 1), 5),
|
||||
comment,
|
||||
avatar,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
heading: getVal("testimonialsHeading"),
|
||||
subheading: getVal("testimonialsSubheading"),
|
||||
videoUrl: getVal("testimonialsVideoUrl"),
|
||||
videoThumbnail: getVal("testimonialsVideoThumbnail"),
|
||||
items,
|
||||
};
|
||||
};
|
||||
|
||||
// Client-side add/remove for testimonials
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const container = document.getElementById("testimonialsItemsContainer");
|
||||
const addBtn = document.getElementById("addTestimonialBtn");
|
||||
|
||||
if (!container || !addBtn) return;
|
||||
|
||||
const updateLabels = () => {
|
||||
container.querySelectorAll(".testimonial-item").forEach((el, idx) => {
|
||||
el.setAttribute("data-index", String(idx));
|
||||
|
||||
const label = el.querySelector(".testimonial-label");
|
||||
if (label) label.textContent = String(idx + 1);
|
||||
|
||||
el.querySelectorAll("input, textarea").forEach((input) => {
|
||||
if (!input.id) return;
|
||||
const newId = input.id.replace(/testimonials\w+_\d+/, (match) => {
|
||||
const [prefix] = match.split("_");
|
||||
return `${prefix}_${idx}`;
|
||||
});
|
||||
input.id = newId;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
addBtn.addEventListener("click", () => {
|
||||
const template = container.querySelector(".testimonial-item");
|
||||
if (!template) return;
|
||||
|
||||
const clone = template.cloneNode(true);
|
||||
|
||||
clone.querySelectorAll("input, textarea").forEach((input) => {
|
||||
input.value = "";
|
||||
});
|
||||
|
||||
container.appendChild(clone);
|
||||
updateLabels();
|
||||
});
|
||||
|
||||
container.addEventListener("click", (e) => {
|
||||
const btn = e.target.closest(".btn-remove-testimonial");
|
||||
if (!btn) return;
|
||||
|
||||
const card = btn.closest(".testimonial-item");
|
||||
if (!card) return;
|
||||
|
||||
const all = container.querySelectorAll(".testimonial-item");
|
||||
if (all.length <= 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
card.remove();
|
||||
updateLabels();
|
||||
});
|
||||
|
||||
updateLabels();
|
||||
});
|
||||
</script>
|
||||
@@ -12,52 +12,30 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Heading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="videoGalleryHeading"
|
||||
value="<%= data.videoGallery?.heading || '' %>"
|
||||
placeholder="e.g., VIDEO PLAY GALLERY"
|
||||
/>
|
||||
<input type="text" class="form-control" id="videoGalleryHeading"
|
||||
value="<%= data.videoGallery?.heading || '' %>" placeholder="e.g., VIDEO PLAY GALLERY" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Video URL</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="videoGalleryVideoUrl"
|
||||
value="<%= data.videoGallery?.videoUrl || '' %>"
|
||||
placeholder="https://example.com/video.mp4"
|
||||
/>
|
||||
<input type="text" class="form-control" id="videoGalleryVideoUrl"
|
||||
value="<%= data.videoGallery?.videoUrl || '' %>" placeholder="https://example.com/video.mp4" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Thumbnail Image</label>
|
||||
<div class="input-group mb-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="videoGalleryThumbnail"
|
||||
value="<%= data.videoGallery?.thumbnail || '' %>"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="videoGalleryThumbnail"
|
||||
data-image-type="home"
|
||||
>
|
||||
<input type="text" class="form-control" id="videoGalleryThumbnail"
|
||||
value="<%= data.videoGallery?.thumbnail || '' %>" />
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="videoGalleryThumbnail" data-image-type="home">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
<% if (data.videoGallery?.thumbnail) { %>
|
||||
<div class="mt-2">
|
||||
<img
|
||||
src="<%= data.videoGallery.thumbnail %>"
|
||||
class="img-thumbnail"
|
||||
style="height: 200px; width: 100%; object-fit: cover;"
|
||||
alt="Thumbnail preview"
|
||||
/>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="mt-2">
|
||||
<img src="<%= data.videoGallery.thumbnail %>" class="img-thumbnail"
|
||||
style="height: 200px; width: 100%; object-fit: cover;" alt="Thumbnail preview" />
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,3 +43,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Register scraper to collect Video Gallery data before form submit
|
||||
window.homeScrapers = window.homeScrapers || {};
|
||||
window.homeScrapers.videoGallery = () => {
|
||||
const getVal = (id) => (document.getElementById(id)?.value || "").trim();
|
||||
|
||||
return {
|
||||
heading: getVal("videoGalleryHeading"),
|
||||
videoUrl: getVal("videoGalleryVideoUrl"),
|
||||
thumbnail: getVal("videoGalleryThumbnail"),
|
||||
};
|
||||
};
|
||||
</script>
|
||||
@@ -13,114 +13,76 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Heading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaCountriesHeading"
|
||||
value="<%= data.visaCountries?.heading || '' %>"
|
||||
placeholder="e.g., Visa & VISAWAY Services To UK"
|
||||
/>
|
||||
<input type="text" class="form-control" id="visaCountriesHeading"
|
||||
value="<%= data.visaCountries?.heading || '' %>" placeholder="e.g., Visa & VISAWAY Services To UK" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Subheading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaCountriesSubheading"
|
||||
value="<%= data.visaCountries?.subheading || '' %>"
|
||||
placeholder="e.g., UK. United Kingdom"
|
||||
/>
|
||||
<input type="text" class="form-control" id="visaCountriesSubheading"
|
||||
value="<%= data.visaCountries?.subheading || '' %>" placeholder="e.g., UK. United Kingdom" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Description</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="visaCountriesDescription"
|
||||
rows="3"
|
||||
placeholder="Enter description"
|
||||
><%= data.visaCountries?.description || '' %></textarea>
|
||||
<textarea class="form-control" id="visaCountriesDescription" rows="3"
|
||||
placeholder="Enter description"><%= data.visaCountries?.description || '' %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Countries -->
|
||||
<!-- Countries (Featured Country configuration) -->
|
||||
<div class="col-md-12">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-globe me-2"></i>Countries
|
||||
<i class="fas fa-globe me-2"></i>Featured Country
|
||||
</h6>
|
||||
<small class="text-muted">This country is used in the home page feature section.</small>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% (data.visaCountries?.countries || []).forEach(function(country, index) { %>
|
||||
<div class="card mb-3 bg-light border">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title fw-bold mb-3">Country <%= index + 1 %></h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Country Name</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaCountriesName_<%= index %>"
|
||||
value="<%= country.name || '' %>"
|
||||
placeholder="e.g., United Kingdom"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Country Code</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaCountriesCode_<%= index %>"
|
||||
value="<%= country.code || '' %>"
|
||||
placeholder="e.g., UK"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Flag Image</label>
|
||||
<div class="input-group mb-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaCountriesFlag_<%= index %>"
|
||||
value="<%= country.flag || '' %>"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="visaCountriesFlag_<%= index %>"
|
||||
data-image-type="home"
|
||||
>
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
<% const featured=(data.visaCountries && Array.isArray(data.visaCountries.countries) &&
|
||||
data.visaCountries.countries.length> 0)
|
||||
? data.visaCountries.countries[0]
|
||||
: {};
|
||||
%>
|
||||
<div class="card mb-3 bg-light border visa-country-item" data-index="0">
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Country Name</label>
|
||||
<input type="text" class="form-control" id="visaCountriesName_0" value="<%= featured.name || '' %>"
|
||||
placeholder="e.g., United Kingdom" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Country Code</label>
|
||||
<input type="text" class="form-control" id="visaCountriesCode_0" value="<%= featured.code || '' %>"
|
||||
placeholder="e.g., UK" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Flag / Illustration Image</label>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" class="form-control" id="visaCountriesFlag_0"
|
||||
value="<%= featured.flag || '' %>" placeholder="/assets/img/home-1/feature/shape.png" />
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="visaCountriesFlag_0" data-image-type="home">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input type="text" class="form-control" id="visaCountriesLink_0" value="<%= featured.link || '' %>"
|
||||
placeholder="/country-details/uk" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Visa Types (comma-separated)</label>
|
||||
<textarea class="form-control" id="visaCountriesVisaTypes_0" rows="2"
|
||||
placeholder="e.g., Student Visa, Work Visa, Tourist Visa"><%= (featured.visaTypes || []).join(', ') %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaCountriesLink_<%= index %>"
|
||||
value="<%= country.link || '' %>"
|
||||
placeholder="/country-details/uk"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Visa Types (comma-separated)</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="visaCountriesVisaTypes_<%= index %>"
|
||||
rows="2"
|
||||
placeholder="e.g., Student Visa, Work Visa, Tourist Visa"
|
||||
><%= (country.visaTypes || []).join(', ') %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -137,23 +99,13 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Label</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaCountriesCtaLabel"
|
||||
value="<%= data.visaCountries?.ctaButton?.label || '' %>"
|
||||
placeholder="e.g., Get Started"
|
||||
/>
|
||||
<input type="text" class="form-control" id="visaCountriesCtaLabel"
|
||||
value="<%= data.visaCountries?.ctaButton?.label || '' %>" placeholder="e.g., Get Started" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaCountriesCtaHref"
|
||||
value="<%= data.visaCountries?.ctaButton?.href || '' %>"
|
||||
placeholder="/contact"
|
||||
/>
|
||||
<input type="text" class="form-control" id="visaCountriesCtaHref"
|
||||
value="<%= data.visaCountries?.ctaButton?.href || '' %>" placeholder="/contact" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -161,3 +113,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Register scraper to collect Visa Countries data before form submit
|
||||
window.homeScrapers = window.homeScrapers || {};
|
||||
window.homeScrapers.visaCountries = () => {
|
||||
const getVal = (id) => (document.getElementById(id)?.value || "").trim();
|
||||
|
||||
const visaTypesRaw = getVal("visaCountriesVisaTypes_0");
|
||||
const visaTypes = visaTypesRaw ? visaTypesRaw.split(",").map((s) => s.trim()).filter(Boolean) : [];
|
||||
|
||||
const featuredCountry = {
|
||||
name: getVal("visaCountriesName_0"),
|
||||
code: getVal("visaCountriesCode_0"),
|
||||
flag: getVal("visaCountriesFlag_0"),
|
||||
link: getVal("visaCountriesLink_0"),
|
||||
visaTypes
|
||||
};
|
||||
|
||||
return {
|
||||
heading: getVal("visaCountriesHeading"),
|
||||
subheading: getVal("visaCountriesSubheading"),
|
||||
description: getVal("visaCountriesDescription"),
|
||||
countries: [featuredCountry],
|
||||
ctaButton: {
|
||||
label: getVal("visaCountriesCtaLabel"),
|
||||
href: getVal("visaCountriesCtaHref")
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
@@ -13,23 +13,13 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Heading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaSolutionsHeading"
|
||||
value="<%= data.visaSolutions?.heading || '' %>"
|
||||
placeholder="e.g., Comprehensive Visa Solutions"
|
||||
/>
|
||||
<input type="text" class="form-control" id="visaSolutionsHeading"
|
||||
value="<%= data.visaSolutions?.heading || '' %>" placeholder="e.g., Comprehensive Visa Solutions" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Subheading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaSolutionsSubheading"
|
||||
value="<%= data.visaSolutions?.subheading || '' %>"
|
||||
placeholder="e.g., Our Expert Services"
|
||||
/>
|
||||
<input type="text" class="form-control" id="visaSolutionsSubheading"
|
||||
value="<%= data.visaSolutions?.subheading || '' %>" placeholder="e.g., Our Expert Services" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,62 +29,109 @@
|
||||
<!-- Services Items -->
|
||||
<div class="col-md-12">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card-header bg-white">
|
||||
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-list-ul me-2"></i>Visa Solutions Items
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% (data.visaSolutions?.items || []).forEach(function(item, index) { %>
|
||||
<div class="card mb-3 bg-light border">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title fw-bold mb-3">Service <%= index + 1 %></h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-medium">Number</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaSolutionsNumber_<%= index %>"
|
||||
value="<%= item.number || '' %>"
|
||||
placeholder="e.g., 01"
|
||||
/>
|
||||
<div class="card-body" id="visaSolutionsItemsContainer">
|
||||
<% const vsItems=(data.visaSolutions && Array.isArray(data.visaSolutions.items) &&
|
||||
data.visaSolutions.items.length===4) ? data.visaSolutions.items : (data.visaSolutions &&
|
||||
Array.isArray(data.visaSolutions.items) && data.visaSolutions.items.length> 0)
|
||||
? (function () {
|
||||
const clone = data.visaSolutions.items.slice(0, 4);
|
||||
while (clone.length < 4) clone.push({}); return clone; })() : [{}, {}, {}, {}]; %>
|
||||
<% vsItems.forEach(function(item, index) { %>
|
||||
<div class="card mb-3 bg-light border visa-solution-item" data-index="<%= index %>">
|
||||
<div class="card-header d-flex justify-content-between align-items-center bg-white">
|
||||
<h6 class="card-title fw-bold mb-0">Service <span class="visa-solution-label">
|
||||
<%= index + 1 %>
|
||||
</span></h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-3">
|
||||
<label class="form-label fw-medium">Number</label>
|
||||
<input type="text" class="form-control" id="visaSolutionsNumber_<%= index %>"
|
||||
value="<%= item.number || '' %>" placeholder="e.g., 01" />
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input type="text" class="form-control" id="visaSolutionsTitle_<%= index %>"
|
||||
value="<%= item.title || '' %>" placeholder="e.g., Student Visa Guidance" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Description</label>
|
||||
<textarea class="form-control" id="visaSolutionsDescription_<%= index %>" rows="2"
|
||||
placeholder="Enter description"><%= item.description || '' %></textarea>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input type="text" class="form-control" id="visaSolutionsLink_<%= index %>"
|
||||
value="<%= item.link || '' %>" placeholder="/service-details" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaSolutionsTitle_<%= index %>"
|
||||
value="<%= item.title || '' %>"
|
||||
placeholder="e.g., Student Visa Guidance"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Description</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="visaSolutionsDescription_<%= index %>"
|
||||
rows="2"
|
||||
placeholder="Enter description"
|
||||
><%= item.description || '' %></textarea>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="visaSolutionsLink_<%= index %>"
|
||||
value="<%= item.link || '' %>"
|
||||
placeholder="/service-details"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Register scraper to collect Visa Solutions data before form submit
|
||||
window.homeScrapers = window.homeScrapers || {};
|
||||
window.homeScrapers.visaSolutions = () => {
|
||||
const getVal = (id) => (document.getElementById(id)?.value || "").trim();
|
||||
|
||||
const items = [];
|
||||
document.querySelectorAll(".visa-solution-item").forEach((el, idx) => {
|
||||
const index = el.getAttribute("data-index") || idx;
|
||||
|
||||
const number = getVal(`visaSolutionsNumber_${index}`);
|
||||
const title = getVal(`visaSolutionsTitle_${index}`);
|
||||
const description = getVal(`visaSolutionsDescription_${index}`);
|
||||
const link = getVal(`visaSolutionsLink_${index}`);
|
||||
|
||||
if (number || title || description || link) {
|
||||
items.push({ number, title, description, link });
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
heading: getVal("visaSolutionsHeading"),
|
||||
subheading: getVal("visaSolutionsSubheading"),
|
||||
items,
|
||||
};
|
||||
};
|
||||
|
||||
// Client-side add/remove for visa solutions
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
const container = document.getElementById("visaSolutionsItemsContainer");
|
||||
if (!container) return;
|
||||
|
||||
const updateLabels = () => {
|
||||
const items = container.querySelectorAll(".visa-solution-item");
|
||||
|
||||
items.forEach((el, idx) => {
|
||||
el.setAttribute("data-index", String(idx));
|
||||
|
||||
const label = el.querySelector(".visa-solution-label");
|
||||
if (label) label.textContent = String(idx + 1);
|
||||
|
||||
el.querySelectorAll("input, textarea").forEach((input) => {
|
||||
if (!input.id) return;
|
||||
const newId = input.id.replace(/visaSolutions\w+_\d+/, (match) => {
|
||||
const [prefix] = match.split("_");
|
||||
return `${prefix}_${idx}`;
|
||||
});
|
||||
input.id = newId;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
updateLabels();
|
||||
});
|
||||
</script>
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="row g-4">
|
||||
<!-- Basic Info -->
|
||||
<div class="col-md-12">
|
||||
<div class="card border shadow-sm">
|
||||
<div class="card border shadow-sm mb-3">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-info-circle me-2"></i>Basic Information
|
||||
@@ -13,32 +13,78 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Heading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="whyChooseUsHeading"
|
||||
<input type="text" class="form-control" id="whyChooseUsHeading"
|
||||
value="<%= data.whyChooseUs?.heading || '' %>"
|
||||
placeholder="e.g., Turning Study Abroad Dreams Into Reality"
|
||||
/>
|
||||
placeholder="e.g., Turning Study Abroad Dreams Into Reality" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Subheading</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="whyChooseUsSubheading"
|
||||
value="<%= data.whyChooseUs?.subheading || '' %>"
|
||||
placeholder="e.g., About Our Consultancy"
|
||||
/>
|
||||
<input type="text" class="form-control" id="whyChooseUsSubheading"
|
||||
value="<%= data.whyChooseUs?.subheading || '' %>" placeholder="e.g., About Our Consultancy" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Highlight Word (Optional)</label>
|
||||
<input type="text" class="form-control" id="whyChooseUsHighlightWord"
|
||||
value="<%= data.whyChooseUs?.highlightWord || '' %>" placeholder="e.g., Dreams" />
|
||||
<small class="text-muted">This word in the heading will be wrapped in a colored span.</small>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Description</label>
|
||||
<textarea
|
||||
class="form-control"
|
||||
id="whyChooseUsDescription"
|
||||
rows="3"
|
||||
placeholder="Enter description"
|
||||
><%= data.whyChooseUs?.description || '' %></textarea>
|
||||
<textarea class="form-control" id="whyChooseUsDescription" rows="3"
|
||||
placeholder="Enter description"><%= data.whyChooseUs?.description || '' %></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- About Images -->
|
||||
<div class="col-md-12">
|
||||
<div class="card border shadow-sm mb-3">
|
||||
<div class="card-header bg-white">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-images me-2"></i>About Images (Left side)
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Main Image</label>
|
||||
<small class="text-muted d-block mb-1">Recommended size: 375x419px</small>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" class="form-control" id="whyChooseUsMainImage"
|
||||
value="<%= data.whyChooseUs?.mainImage || '' %>" placeholder="/assets/img/home-1/about/about-1.jpg" />
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="whyChooseUsMainImage" data-image-type="home">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
<% if (data.whyChooseUs?.mainImage) { %>
|
||||
<div class="mt-2">
|
||||
<img src="<%= data.whyChooseUs.mainImage %>" class="img-thumbnail"
|
||||
style="height: 200px; width: 100%; object-fit: cover;" alt="Main about image preview" />
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Secondary Image</label>
|
||||
<small class="text-muted d-block mb-1">Recommended size: 376x394px</small>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" class="form-control" id="whyChooseUsSecondaryImage"
|
||||
value="<%= data.whyChooseUs?.secondaryImage || '' %>"
|
||||
placeholder="/assets/img/home-1/about/about-02.jpg" />
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="whyChooseUsSecondaryImage" data-image-type="home">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
<% if (data.whyChooseUs?.secondaryImage) { %>
|
||||
<div class="mt-2">
|
||||
<img src="<%= data.whyChooseUs.secondaryImage %>" class="img-thumbnail"
|
||||
style="height: 200px; width: 100%; object-fit: cover;" alt="Secondary about image preview" />
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -55,53 +101,36 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% (data.whyChooseUs?.items || []).forEach(function(item, index) { %>
|
||||
<div class="card mb-3 bg-light border">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title fw-bold mb-3">Item <%= index + 1 %></h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Icon URL</label>
|
||||
<div class="input-group mb-2">
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="whyChooseUsIcon_<%= index %>"
|
||||
value="<%= item.icon || '' %>"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="whyChooseUsIcon_<%= index %>"
|
||||
data-image-type="home"
|
||||
>
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
<div class="card mb-3 bg-light border">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title fw-bold mb-3">Item <%= index + 1 %>
|
||||
</h6>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Icon URL</label>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" class="form-control" id="whyChooseUsIcon_<%= index %>"
|
||||
value="<%= item.icon || '' %>" />
|
||||
<button type="button" class="btn btn-outline-primary btn-upload-image"
|
||||
data-target-input="whyChooseUsIcon_<%= index %>" data-image-type="home">
|
||||
<i class="fas fa-upload me-1"></i>Upload
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input type="text" class="form-control" id="whyChooseUsTitle_<%= index %>"
|
||||
value="<%= item.title || '' %>" placeholder="e.g., Global Reach" />
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Description</label>
|
||||
<input type="text" class="form-control" id="whyChooseUsItemDescription_<%= index %>"
|
||||
value="<%= item.description || '' %>" placeholder="e.g., Expanding Opportunities Worldwide" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Title</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="whyChooseUsTitle_<%= index %>"
|
||||
value="<%= item.title || '' %>"
|
||||
placeholder="e.g., Global Reach"
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Description</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="whyChooseUsItemDescription_<%= index %>"
|
||||
value="<%= item.description || '' %>"
|
||||
placeholder="e.g., Expanding Opportunities Worldwide"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }); %>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -116,17 +145,12 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<% (data.whyChooseUs?.features || []).forEach(function(feature, index) { %>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">Feature <%= index + 1 %></label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="whyChooseUsFeature_<%= index %>"
|
||||
value="<%= feature %>"
|
||||
placeholder="Enter feature"
|
||||
/>
|
||||
</div>
|
||||
<% }); %>
|
||||
<div class="mb-3">
|
||||
<label class="form-label fw-medium">Feature <%= index + 1 %></label>
|
||||
<input type="text" class="form-control" id="whyChooseUsFeature_<%= index %>" value="<%= feature %>"
|
||||
placeholder="Enter feature" />
|
||||
</div>
|
||||
<% }); %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -143,23 +167,13 @@
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Label</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="whyChooseUsCtaLabel"
|
||||
value="<%= data.whyChooseUs?.ctaButton?.label || '' %>"
|
||||
placeholder="e.g., Get Started"
|
||||
/>
|
||||
<input type="text" class="form-control" id="whyChooseUsCtaLabel"
|
||||
value="<%= data.whyChooseUs?.ctaButton?.label || '' %>" placeholder="e.g., Get Started" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label fw-medium">Link</label>
|
||||
<input
|
||||
type="text"
|
||||
class="form-control"
|
||||
id="whyChooseUsCtaHref"
|
||||
value="<%= data.whyChooseUs?.ctaButton?.href || '' %>"
|
||||
placeholder="/about"
|
||||
/>
|
||||
<input type="text" class="form-control" id="whyChooseUsCtaHref"
|
||||
value="<%= data.whyChooseUs?.ctaButton?.href || '' %>" placeholder="/about" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -167,3 +181,53 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Register scraper to collect Why Choose Us data before form submit
|
||||
window.homeScrapers = window.homeScrapers || {};
|
||||
window.homeScrapers.whyChooseUs = () => {
|
||||
const getVal = (id) => (document.getElementById(id)?.value || "").trim();
|
||||
|
||||
// Collect items
|
||||
const items = [];
|
||||
document.querySelectorAll('input[id^="whyChooseUsTitle_"]').forEach((titleInput) => {
|
||||
const id = titleInput.id || "";
|
||||
const match = id.match(/whyChooseUsTitle_(\d+)/);
|
||||
if (!match) return;
|
||||
const idx = match[1];
|
||||
|
||||
const title = (titleInput.value || "").trim();
|
||||
const icon = getVal(`whyChooseUsIcon_${idx}`);
|
||||
const description = getVal(`whyChooseUsItemDescription_${idx}`);
|
||||
|
||||
// Bỏ qua item trống hoàn toàn
|
||||
if (title || icon || description) {
|
||||
items.push({ icon, title, description });
|
||||
}
|
||||
});
|
||||
|
||||
// Collect features
|
||||
const features = [];
|
||||
document.querySelectorAll('input[id^="whyChooseUsFeature_"]').forEach((featureInput) => {
|
||||
const value = (featureInput.value || "").trim();
|
||||
if (value) {
|
||||
features.push(value);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
heading: getVal("whyChooseUsHeading"),
|
||||
subheading: getVal("whyChooseUsSubheading"),
|
||||
description: getVal("whyChooseUsDescription"),
|
||||
highlightWord: getVal("whyChooseUsHighlightWord"),
|
||||
mainImage: getVal("whyChooseUsMainImage"),
|
||||
secondaryImage: getVal("whyChooseUsSecondaryImage"),
|
||||
items,
|
||||
features,
|
||||
ctaButton: {
|
||||
label: getVal("whyChooseUsCtaLabel"),
|
||||
href: getVal("whyChooseUsCtaHref"),
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
@@ -362,6 +362,19 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Image Contact</label>
|
||||
<div class="input-group mb-2">
|
||||
<input type="text" id="contact_image_input" name="contact_image" class="form-control" placeholder="/uploads/visa/contact-image.jpg" required />
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="document.getElementById('fileContactImageInput').click()">
|
||||
<i class="fas fa-upload"></i>
|
||||
</button>
|
||||
</div>
|
||||
<input type="file" id="fileContactImageInput" style="display:none" accept="image/*"/>
|
||||
<div style="margin-top: 10px;">
|
||||
<img id="preview_contact_image" src="" alt="Contact Image Preview" style="max-width: 200px; max-height: 150px; object-fit: cover; display: none; border-radius: 4px; border: 1px solid #ddd;">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label fw-medium">Section Title</label>
|
||||
<input type="text" name="contact_sectionTitle" class="form-control" placeholder="e.g. Visa & Immigration" required/>
|
||||
@@ -452,18 +465,20 @@
|
||||
|
||||
// Image upload handler
|
||||
function setupImageUploadHandlers() {
|
||||
const imageInputs = ["fileFlagInput", "fileDetailMainInput", "fileGalleryInput1", "fileGalleryInput2"];
|
||||
const imageInputs = ["fileFlagInput", "fileDetailMainInput", "fileGalleryInput1", "fileGalleryInput2", "fileContactImageInput"];
|
||||
const previewMap = {
|
||||
fileFlagInput: "preview_icon",
|
||||
fileDetailMainInput: "preview_main_detail",
|
||||
fileGalleryInput1: "preview_bannerImageGallery1",
|
||||
fileGalleryInput2: "preview_bannerImageGallery2"
|
||||
fileGalleryInput2: "preview_bannerImageGallery2",
|
||||
fileContactImageInput: "preview_contact_image"
|
||||
};
|
||||
const inputMap = {
|
||||
fileFlagInput: "icon_input",
|
||||
fileDetailMainInput: "mainImage_detail",
|
||||
fileGalleryInput1: "bannerImageGallery1",
|
||||
fileGalleryInput2: "bannerImageGallery2"
|
||||
fileGalleryInput2: "bannerImageGallery2",
|
||||
fileContactImageInput: "contact_image_input"
|
||||
};
|
||||
|
||||
imageInputs.forEach((fileId) => {
|
||||
@@ -758,6 +773,17 @@
|
||||
|
||||
const contactInfo = country.detailedView?.contactInfo;
|
||||
if (contactInfo) {
|
||||
document.querySelector('input[name="contact_image"]').value = contactInfo.img || "";
|
||||
|
||||
// Update contact image preview
|
||||
if (contactInfo.img) {
|
||||
const contactPreview = document.getElementById("preview_contact_image");
|
||||
if (contactPreview) {
|
||||
contactPreview.src = contactInfo.img;
|
||||
contactPreview.style.display = "block";
|
||||
}
|
||||
}
|
||||
document.querySelector('input[name="contact_image"]').value = contactInfo.img || "";
|
||||
document.querySelector('input[name="contact_sectionTitle"]').value = contactInfo.sectionTitle || "";
|
||||
document.querySelector('input[name="contact_helpText"]').value = contactInfo.helpText || "";
|
||||
document.querySelector('input[name="contact_phone_label"]').value = contactInfo.phone?.label || "";
|
||||
@@ -766,6 +792,7 @@
|
||||
document.querySelector('input[name="contact_email"]').value = contactInfo.email?.value || "";
|
||||
document.querySelector('input[name="contact_location_label"]').value = contactInfo.location?.label || "";
|
||||
document.querySelector('input[name="contact_location"]').value = contactInfo.location?.address || "";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -843,6 +870,7 @@
|
||||
document.getElementById("preview_main_detail").style.display = "none";
|
||||
document.getElementById("preview_bannerImageGallery1").style.display = "none";
|
||||
document.getElementById("preview_bannerImageGallery2").style.display = "none";
|
||||
document.getElementById("preview_contact_image").style.display = "none";
|
||||
|
||||
// Clear related countries previews
|
||||
for (let i = 0; i < 7; i++) {
|
||||
@@ -858,12 +886,13 @@
|
||||
relatedNameInputs.forEach(input => input.value = "");
|
||||
relatedIconInputs.forEach(input => input.value = "");
|
||||
|
||||
const contactFields = ["contact_sectionTitle", "contact_helpText", "contact_phone_label", "contact_phone", "contact_email_label", "contact_email", "contact_location_label", "contact_location"];
|
||||
const contactFields = ["contact_image", "contact_sectionTitle", "contact_helpText", "contact_phone_label", "contact_phone", "contact_email_label", "contact_email", "contact_location_label", "contact_location"];
|
||||
contactFields.forEach(fieldName => {
|
||||
const field = document.querySelector(`input[name="${fieldName}"]`);
|
||||
if (field) field.value = "";
|
||||
});
|
||||
|
||||
|
||||
showFormView();
|
||||
});
|
||||
|
||||
@@ -954,6 +983,7 @@
|
||||
icon: document.getElementById(`related_url_${index}`)?.value || ""
|
||||
})),
|
||||
contactInfo: {
|
||||
img: document.querySelector('input[name="contact_image"]').value,
|
||||
sectionTitle: document.querySelector('input[name="contact_sectionTitle"]').value,
|
||||
helpText: document.querySelector('input[name="contact_helpText"]').value,
|
||||
phone: {
|
||||
@@ -971,6 +1001,7 @@
|
||||
}
|
||||
}
|
||||
};
|
||||
console.log('Payload:', payload);
|
||||
|
||||
try {
|
||||
btnSave.disabled = true;
|
||||
|
||||
@@ -60,9 +60,6 @@
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/footer">Footer</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/about">About</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/admin/about-us">About Us</a>
|
||||
</li>
|
||||
|
||||
Reference in New Issue
Block a user