Merge pull request 'fea/thanh-05022026-home' (#28) from fea/thanh-05022026-home into main

Reviewed-on: UKSOURCE/cms.hailearning.edu.vn#28
This commit is contained in:
2026-02-05 14:23:11 +00:00
11 changed files with 951 additions and 602 deletions

View File

@@ -7,8 +7,28 @@ const getHomeDoc = async () => Home.findOne().sort({ updatedAt: -1 });
const getHomeData = async () => (await getHomeDoc())?.toObject() || {}; const getHomeData = async () => (await getHomeDoc())?.toObject() || {};
const getDefaultHomeData = () => ({ const getDefaultHomeData = () => ({
hero: { title: "", subtitle: "", description: "", backgroundImage: "", videoUrl: "", primaryButton: {}, secondaryButton: {} }, hero: {
whyChooseUs: { heading: "", subheading: "", description: "", items: [], features: [], ctaButton: {} }, backgroundImage: "",
slides: [],
title: "",
subtitle: "",
description: "",
heroImage: "",
videoUrl: "",
primaryButton: {},
secondaryButton: {},
},
whyChooseUs: {
heading: "",
subheading: "",
description: "",
highlightWord: "",
mainImage: "",
secondaryImage: "",
items: [],
features: [],
ctaButton: {},
},
visaSolutions: { heading: "", subheading: "", items: [] }, visaSolutions: { heading: "", subheading: "", items: [] },
visaCountries: { heading: "", subheading: "", description: "", countries: [], ctaButton: {} }, visaCountries: { heading: "", subheading: "", description: "", countries: [], ctaButton: {} },
testimonials: { heading: "", subheading: "", videoUrl: "", videoThumbnail: "", items: [] }, testimonials: { heading: "", subheading: "", videoUrl: "", videoThumbnail: "", items: [] },

View File

@@ -1,7 +1,6 @@
{ {
"hero": { "hero": {
"title": "From Application to Visa We've Got You Covered", "title": "From Application to Visa Weve Got You Covered",
"subtitle": "Global Education Simplified", "subtitle": "Global Education Simplified",
"description": "We guide you through every step of the education visa process, from initial application to final approval, ensuring a smooth, hassle-free journey.", "description": "We guide you through every step of the education visa process, from initial application to final approval, ensuring a smooth, hassle-free journey.",
"primaryButton": { "primaryButton": {
@@ -48,25 +47,25 @@
"number": "01", "number": "01",
"title": "Student Visa Guidance", "title": "Student Visa Guidance",
"description": "Assistance with admission, documentation, and visa application.Assistance", "description": "Assistance with admission, documentation, and visa application.Assistance",
"link": "/service-details" "link": "/services/student-visa"
}, },
{ {
"number": "02", "number": "02",
"title": "PTE Exam Preparation", "title": "PTE Exam Preparation",
"description": "We provide expert guidance and personalized support throughout the education visa process,", "description": "We provide expert guidance and personalized support throughout the education visa process,",
"link": "/service-details" "link": "/services/pte-exam"
}, },
{ {
"number": "03", "number": "03",
"title": "University Selection Assistance", "title": "University Selection Assistance",
"description": "We provide expert guidance and personalized support throughout the education visa process,", "description": "We provide expert guidance and personalized support throughout the education visa process,",
"link": "/service-details" "link": "/services/university-selection"
}, },
{ {
"number": "04", "number": "04",
"title": "IELTS Exam Preparation", "title": "IELTS Exam Preparation",
"description": "We provide expert guidance and personalized support throughout the education visa process,", "description": "We provide expert guidance and personalized support throughout the education visa process,",
"link": "/service-details" "link": "/services/ielts-exam"
} }
] ]
}, },

View File

@@ -11,14 +11,35 @@ const LinkSchema = new Schema(
{ _id: false }, { _id: false },
); );
const HeroSchema = new Schema( // Hero slide (for multiple hero items in slider)
const HeroSlideSchema = new Schema(
{ {
title: { type: String, default: "" }, title: { type: String, default: "" },
subtitle: { type: String, default: "" }, subtitle: { type: String, default: "" },
description: { type: String, default: "" }, description: { type: String, default: "" },
primaryButton: { type: LinkSchema, default: () => ({}) }, primaryButton: { type: LinkSchema, default: () => ({}) },
secondaryButton: { type: LinkSchema, default: () => ({}) }, secondaryButton: { type: LinkSchema, default: () => ({}) },
heroImage: { type: String, default: "" },
videoUrl: { type: String, default: "" },
},
{ _id: false },
);
const HeroSchema = new Schema(
{
// Background for whole hero section
backgroundImage: { type: String, default: "" }, backgroundImage: { type: String, default: "" },
// Multiple slides
slides: { type: [HeroSlideSchema], default: [] },
// Legacy single-slide fields (backward compatible)
title: { type: String, default: "" },
subtitle: { type: String, default: "" },
description: { type: String, default: "" },
primaryButton: { type: LinkSchema, default: () => ({}) },
secondaryButton: { type: LinkSchema, default: () => ({}) },
heroImage: { type: String, default: "" },
videoUrl: { type: String, default: "" }, videoUrl: { type: String, default: "" },
}, },
{ _id: false }, { _id: false },
@@ -38,6 +59,9 @@ const WhyChooseUsSchema = new Schema(
heading: { type: String, default: "" }, heading: { type: String, default: "" },
subheading: { type: String, default: "" }, subheading: { type: String, default: "" },
description: { type: String, default: "" }, description: { type: String, default: "" },
highlightWord: { type: String, default: "" },
mainImage: { type: String, default: "" },
secondaryImage: { type: String, default: "" },
items: { type: [WhyChooseUsItemSchema], default: [] }, items: { type: [WhyChooseUsItemSchema], default: [] },
features: { type: [String], default: [] }, features: { type: [String], default: [] },
ctaButton: { type: LinkSchema, default: () => ({}) }, ctaButton: { type: LinkSchema, default: () => ({}) },

View File

@@ -88,15 +88,15 @@
<div class="card-body"> <div class="card-body">
<div class="tab-content"> <div class="tab-content">
<%- include('sections/hero') %> <%- include('sections/hero') %>
<%- include('sections/whyChooseUs') %> <%- include('sections/whyChooseUs') %>
<%- include('sections/visaSolutions') %> <%- include('sections/visaSolutions') %>
<%- include('sections/visaCountries') %> <%- include('sections/visaCountries') %>
<%- include('sections/testimonials') %> <%- include('sections/testimonials') %>
<%- include('sections/videoGallery') %> <%- include('sections/videoGallery') %>
<%- include('sections/faq') %> <%- include('sections/faq') %>
<%- include('sections/achievements') %> <%- include('sections/achievements') %>
<%- include('sections/partners') %> <%- include('sections/partners') %>
<%- include('sections/blogPreview') %> <%- include('sections/blogPreview') %>
</div> </div>
</div> </div>
</div> </div>
@@ -136,7 +136,7 @@
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const form = document.querySelector("form"); const form = document.querySelector("form");
if (form) { if (form) {
form.addEventListener("submit", function(e) { form.addEventListener("submit", function (e) {
console.log("Form submitting, collecting data from scrapers..."); console.log("Form submitting, collecting data from scrapers...");
// Tự động thu gom dữ liệu từ các section đã đăng ký // Tự động thu gom dữ liệu từ các section đã đăng ký
@@ -164,7 +164,7 @@
// --- UTILITIES (Dùng chung) --- // --- UTILITIES (Dùng chung) ---
function initImageUploads() { function initImageUploads() {
document.addEventListener("click", function(e) { document.addEventListener("click", function (e) {
const btn = e.target.closest(".btn-upload-image"); const btn = e.target.closest(".btn-upload-image");
if (btn) { if (btn) {
document.getElementById("currentImageType").value = btn.dataset.imageType; document.getElementById("currentImageType").value = btn.dataset.imageType;

View File

@@ -13,32 +13,18 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Heading</label> <label class="form-label fw-medium">Heading</label>
<input <input type="text" class="form-control" id="faqHeading" value="<%= data.faq?.heading || '' %>"
type="text" placeholder="e.g., Got Questions? We've Got Answers" />
class="form-control"
id="faqHeading"
value="<%= data.faq?.heading || '' %>"
placeholder="e.g., Got Questions? We've Got Answers"
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Subheading</label> <label class="form-label fw-medium">Subheading</label>
<input <input type="text" class="form-control" id="faqSubheading" value="<%= data.faq?.subheading || '' %>"
type="text" placeholder="e.g., Visa FAQs" />
class="form-control"
id="faqSubheading"
value="<%= data.faq?.subheading || '' %>"
placeholder="e.g., Visa FAQs"
/>
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<label class="form-label fw-medium">Description</label> <label class="form-label fw-medium">Description</label>
<textarea <textarea class="form-control" id="faqDescription" rows="3"
class="form-control" placeholder="Enter description"><%= data.faq?.description || '' %></textarea>
id="faqDescription"
rows="3"
placeholder="Enter description"
><%= data.faq?.description || '' %></textarea>
</div> </div>
</div> </div>
</div> </div>
@@ -48,16 +34,33 @@
<!-- FAQ Items --> <!-- FAQ Items -->
<div class="col-md-12"> <div class="col-md-12">
<div class="card border shadow-sm"> <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"> <h6 class="mb-0">
<i class="fas fa-question-circle me-2"></i>FAQ Items <i class="fas fa-question-circle me-2"></i>FAQ Items
</h6> </h6>
</div> </div>
<div class="card-body"> <div class="card-body" id="faqItemsContainer">
<% (data.faq?.items || []).forEach(function(item, index) { %> <%
<div class="card mb-3 bg-light border"> 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"> <div class="card-body">
<h6 class="card-title fw-bold mb-3">FAQ <%= index + 1 %></h6>
<div class="row g-3"> <div class="row g-3">
<div class="col-md-12"> <div class="col-md-12">
<label class="form-label fw-medium">Question</label> <label class="form-label fw-medium">Question</label>
@@ -98,23 +101,13 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Label</label> <label class="form-label fw-medium">Label</label>
<input <input type="text" class="form-control" id="faqCtaLabel" value="<%= data.faq?.ctaButton?.label || '' %>"
type="text" placeholder="e.g., contact us" />
class="form-control"
id="faqCtaLabel"
value="<%= data.faq?.ctaButton?.label || '' %>"
placeholder="e.g., contact us"
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Link</label> <label class="form-label fw-medium">Link</label>
<input <input type="text" class="form-control" id="faqCtaHref" value="<%= data.faq?.ctaButton?.href || '' %>"
type="text" placeholder="/contact" />
class="form-control"
id="faqCtaHref"
value="<%= data.faq?.ctaButton?.href || '' %>"
placeholder="/contact"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -122,3 +115,34 @@
</div> </div>
</div> </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>

View File

@@ -1,157 +1,312 @@
<!-- Hero Tab --> <!-- Hero Tab -->
<div class="tab-pane fade show active" id="hero" role="tabpanel"> <div class="tab-pane fade show active" id="hero" role="tabpanel">
<div class="row g-4"> <div class="row g-4">
<!-- Basic Info --> <!-- Background Image (section-level) -->
<div class="col-md-12"> <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"> <div class="card-header bg-white d-flex justify-content-between align-items-center">
<h6 class="mb-0"> <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> </h6>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row g-3"> <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"> <div class="col-md-6">
<label class="form-label fw-medium">Background Image</label> <label class="form-label fw-medium">Background Image</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input <input type="text" class="form-control" id="heroBackgroundImage"
type="text" value="<%= data.hero?.backgroundImage || '' %>" placeholder="/assets/img/home-1/hero/bg.jpg" />
class="form-control" <button type="button" class="btn btn-outline-primary btn-upload-image"
id="heroBackgroundImage" data-target-input="heroBackgroundImage" data-image-type="home">
value="<%= data.hero?.backgroundImage || '' %>"
/>
<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 <i class="fas fa-upload me-1"></i>Upload
</button> </button>
</div> </div>
<% if (data.hero?.backgroundImage) { %> <% if (data.hero?.backgroundImage) { %>
<div class="mt-2"> <div class="mt-2">
<img <img src="<%= data.hero.backgroundImage %>" class="img-thumbnail"
src="<%= data.hero.backgroundImage %>" style="height: 200px; width: 100%; object-fit: cover;" alt="Background preview" />
class="img-thumbnail" </div>
style="height: 200px; width: 100%; object-fit: cover;" <% } %>
alt="Background preview" </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>
<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> </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>

View File

@@ -13,49 +13,26 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Heading</label> <label class="form-label fw-medium">Heading</label>
<input <input type="text" class="form-control" id="testimonialsHeading"
type="text" value="<%= data.testimonials?.heading || '' %>" placeholder="e.g., Student Reviews & Testimonials" />
class="form-control"
id="testimonialsHeading"
value="<%= data.testimonials?.heading || '' %>"
placeholder="e.g., Student Reviews & Testimonials"
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Subheading</label> <label class="form-label fw-medium">Subheading</label>
<input <input type="text" class="form-control" id="testimonialsSubheading"
type="text" value="<%= data.testimonials?.subheading || '' %>" placeholder="e.g., What Our Students Say" />
class="form-control"
id="testimonialsSubheading"
value="<%= data.testimonials?.subheading || '' %>"
placeholder="e.g., What Our Students Say"
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Video URL</label> <label class="form-label fw-medium">Video URL</label>
<input <input type="text" class="form-control" id="testimonialsVideoUrl"
type="text" value="<%= data.testimonials?.videoUrl || '' %>" placeholder="https://www.youtube.com/watch?v=..." />
class="form-control"
id="testimonialsVideoUrl"
value="<%= data.testimonials?.videoUrl || '' %>"
placeholder="https://www.youtube.com/watch?v=..."
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Video Thumbnail</label> <label class="form-label fw-medium">Video Thumbnail</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input <input type="text" class="form-control" id="testimonialsVideoThumbnail"
type="text" value="<%= data.testimonials?.videoThumbnail || '' %>" />
class="form-control" <button type="button" class="btn btn-outline-primary btn-upload-image"
id="testimonialsVideoThumbnail" data-target-input="testimonialsVideoThumbnail" data-image-type="home">
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 <i class="fas fa-upload me-1"></i>Upload
</button> </button>
</div> </div>
@@ -68,92 +45,167 @@
<!-- Testimonial Items --> <!-- Testimonial Items -->
<div class="col-md-12"> <div class="col-md-12">
<div class="card border shadow-sm"> <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"> <h6 class="mb-0">
<i class="fas fa-comments me-2"></i>Testimonials <i class="fas fa-comments me-2"></i>Testimonials
</h6> </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>
<div class="card-body"> <div class="card-body" id="testimonialsItemsContainer">
<% (data.testimonials?.items || []).forEach(function(item, index) { %> <% (data.testimonials?.items || []).forEach(function(item, index) { %>
<div class="card mb-3 bg-light border"> <div class="card mb-3 bg-light border testimonial-item" data-index="<%= index %>">
<div class="card-body"> <div class="card-header d-flex justify-content-between align-items-center bg-white">
<h6 class="card-title fw-bold mb-3">Testimonial <%= index + 1 %></h6> <h6 class="card-title fw-bold mb-0">Testimonial <span class="testimonial-label">
<div class="row g-3"> <%= index + 1 %>
<div class="col-md-6"> </span></h6>
<label class="form-label fw-medium">Name</label> <button type="button" class="btn btn-sm btn-outline-danger btn-remove-testimonial">
<input <i class="fas fa-trash-alt"></i>
type="text" </button>
class="form-control" </div>
id="testimonialsName_<%= index %>" <div class="card-body">
value="<%= item.name || '' %>" <div class="row g-3">
placeholder="e.g., Sohel Tanvir" <div class="col-md-6">
/> <label class="form-label fw-medium">Name</label>
</div> <input type="text" class="form-control" id="testimonialsName_<%= index %>"
<div class="col-md-6"> value="<%= item.name || '' %>" placeholder="e.g., Sohel Tanvir" />
<label class="form-label fw-medium">Role</label> </div>
<input <div class="col-md-6">
type="text" <label class="form-label fw-medium">Role</label>
class="form-control" <input type="text" class="form-control" id="testimonialsRole_<%= index %>"
id="testimonialsRole_<%= index %>" value="<%= item.role || '' %>" placeholder="e.g., Student" />
value="<%= item.role || '' %>" </div>
placeholder="e.g., Student" <div class="col-md-6">
/> <label class="form-label fw-medium">Country</label>
</div> <input type="text" class="form-control" id="testimonialsCountry_<%= index %>"
<div class="col-md-6"> value="<%= item.country || '' %>" placeholder="e.g., Canada" />
<label class="form-label fw-medium">Country</label> </div>
<input <div class="col-md-6">
type="text" <label class="form-label fw-medium">Rating</label>
class="form-control" <input type="number" class="form-control" id="testimonialsRating_<%= index %>"
id="testimonialsCountry_<%= index %>" value="<%= item.rating || 5 %>" min="1" max="5" />
value="<%= item.country || '' %>" </div>
placeholder="e.g., Canada" <div class="col-md-12">
/> <label class="form-label fw-medium">Comment</label>
</div> <textarea class="form-control" id="testimonialsComment_<%= index %>" rows="3"
<div class="col-md-6"> placeholder="Enter testimonial comment"><%= item.comment || '' %></textarea>
<label class="form-label fw-medium">Rating</label> </div>
<input <div class="col-md-12">
type="number" <label class="form-label fw-medium">Avatar</label>
class="form-control" <div class="input-group mb-2">
id="testimonialsRating_<%= index %>" <input type="text" class="form-control" id="testimonialsAvatar_<%= index %>"
value="<%= item.rating || 5 %>" value="<%= item.avatar || '' %>" />
min="1" <button type="button" class="btn btn-outline-primary btn-upload-image"
max="5" data-target-input="testimonialsAvatar_<%= index %>" data-image-type="home">
/> <i class="fas fa-upload me-1"></i>Upload
</div> </button>
<div class="col-md-12"> </div>
<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>
</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>

View File

@@ -12,52 +12,30 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-12"> <div class="col-md-12">
<label class="form-label fw-medium">Heading</label> <label class="form-label fw-medium">Heading</label>
<input <input type="text" class="form-control" id="videoGalleryHeading"
type="text" value="<%= data.videoGallery?.heading || '' %>" placeholder="e.g., VIDEO PLAY GALLERY" />
class="form-control"
id="videoGalleryHeading"
value="<%= data.videoGallery?.heading || '' %>"
placeholder="e.g., VIDEO PLAY GALLERY"
/>
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<label class="form-label fw-medium">Video URL</label> <label class="form-label fw-medium">Video URL</label>
<input <input type="text" class="form-control" id="videoGalleryVideoUrl"
type="text" value="<%= data.videoGallery?.videoUrl || '' %>" placeholder="https://example.com/video.mp4" />
class="form-control"
id="videoGalleryVideoUrl"
value="<%= data.videoGallery?.videoUrl || '' %>"
placeholder="https://example.com/video.mp4"
/>
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<label class="form-label fw-medium">Thumbnail Image</label> <label class="form-label fw-medium">Thumbnail Image</label>
<div class="input-group mb-2"> <div class="input-group mb-2">
<input <input type="text" class="form-control" id="videoGalleryThumbnail"
type="text" value="<%= data.videoGallery?.thumbnail || '' %>" />
class="form-control" <button type="button" class="btn btn-outline-primary btn-upload-image"
id="videoGalleryThumbnail" data-target-input="videoGalleryThumbnail" data-image-type="home">
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 <i class="fas fa-upload me-1"></i>Upload
</button> </button>
</div> </div>
<% if (data.videoGallery?.thumbnail) { %> <% if (data.videoGallery?.thumbnail) { %>
<div class="mt-2"> <div class="mt-2">
<img <img src="<%= data.videoGallery.thumbnail %>" class="img-thumbnail"
src="<%= data.videoGallery.thumbnail %>" style="height: 200px; width: 100%; object-fit: cover;" alt="Thumbnail preview" />
class="img-thumbnail" </div>
style="height: 200px; width: 100%; object-fit: cover;" <% } %>
alt="Thumbnail preview"
/>
</div>
<% } %>
</div> </div>
</div> </div>
</div> </div>
@@ -65,3 +43,17 @@
</div> </div>
</div> </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>

View File

@@ -13,114 +13,76 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Heading</label> <label class="form-label fw-medium">Heading</label>
<input <input type="text" class="form-control" id="visaCountriesHeading"
type="text" value="<%= data.visaCountries?.heading || '' %>" placeholder="e.g., Visa & VISAWAY Services To UK" />
class="form-control"
id="visaCountriesHeading"
value="<%= data.visaCountries?.heading || '' %>"
placeholder="e.g., Visa & VISAWAY Services To UK"
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Subheading</label> <label class="form-label fw-medium">Subheading</label>
<input <input type="text" class="form-control" id="visaCountriesSubheading"
type="text" value="<%= data.visaCountries?.subheading || '' %>" placeholder="e.g., UK. United Kingdom" />
class="form-control"
id="visaCountriesSubheading"
value="<%= data.visaCountries?.subheading || '' %>"
placeholder="e.g., UK. United Kingdom"
/>
</div> </div>
<div class="col-md-12"> <div class="col-md-12">
<label class="form-label fw-medium">Description</label> <label class="form-label fw-medium">Description</label>
<textarea <textarea class="form-control" id="visaCountriesDescription" rows="3"
class="form-control" placeholder="Enter description"><%= data.visaCountries?.description || '' %></textarea>
id="visaCountriesDescription"
rows="3"
placeholder="Enter description"
><%= data.visaCountries?.description || '' %></textarea>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Countries --> <!-- Countries (Featured Country configuration) -->
<div class="col-md-12"> <div class="col-md-12">
<div class="card border shadow-sm"> <div class="card border shadow-sm">
<div class="card-header bg-white"> <div class="card-header bg-white">
<h6 class="mb-0"> <h6 class="mb-0">
<i class="fas fa-globe me-2"></i>Countries <i class="fas fa-globe me-2"></i>Featured Country
</h6> </h6>
<small class="text-muted">This country is used in the home page feature section.</small>
</div> </div>
<div class="card-body"> <div class="card-body">
<% (data.visaCountries?.countries || []).forEach(function(country, index) { %> <% const featured=(data.visaCountries && Array.isArray(data.visaCountries.countries) &&
<div class="card mb-3 bg-light border"> data.visaCountries.countries.length> 0)
<div class="card-body"> ? data.visaCountries.countries[0]
<h6 class="card-title fw-bold mb-3">Country <%= index + 1 %></h6> : {};
<div class="row g-3"> %>
<div class="col-md-6"> <div class="card mb-3 bg-light border visa-country-item" data-index="0">
<label class="form-label fw-medium">Country Name</label> <div class="card-body">
<input <div class="row g-3">
type="text" <div class="col-md-6">
class="form-control" <label class="form-label fw-medium">Country Name</label>
id="visaCountriesName_<%= index %>" <input type="text" class="form-control" id="visaCountriesName_0" value="<%= featured.name || '' %>"
value="<%= country.name || '' %>" placeholder="e.g., United Kingdom" />
placeholder="e.g., United Kingdom" </div>
/> <div class="col-md-6">
</div> <label class="form-label fw-medium">Country Code</label>
<div class="col-md-6"> <input type="text" class="form-control" id="visaCountriesCode_0" value="<%= featured.code || '' %>"
<label class="form-label fw-medium">Country Code</label> placeholder="e.g., UK" />
<input </div>
type="text" <div class="col-md-6">
class="form-control" <label class="form-label fw-medium">Flag / Illustration Image</label>
id="visaCountriesCode_<%= index %>" <div class="input-group mb-2">
value="<%= country.code || '' %>" <input type="text" class="form-control" id="visaCountriesFlag_0"
placeholder="e.g., UK" value="<%= featured.flag || '' %>" placeholder="/assets/img/home-1/feature/shape.png" />
/> <button type="button" class="btn btn-outline-primary btn-upload-image"
</div> data-target-input="visaCountriesFlag_0" data-image-type="home">
<div class="col-md-6"> <i class="fas fa-upload me-1"></i>Upload
<label class="form-label fw-medium">Flag Image</label> </button>
<div class="input-group mb-2"> </div>
<input </div>
type="text" <div class="col-md-6">
class="form-control" <label class="form-label fw-medium">Link</label>
id="visaCountriesFlag_<%= index %>" <input type="text" class="form-control" id="visaCountriesLink_0" value="<%= featured.link || '' %>"
value="<%= country.flag || '' %>" placeholder="/country-details/uk" />
/> </div>
<button <div class="col-md-12">
type="button" <label class="form-label fw-medium">Visa Types (comma-separated)</label>
class="btn btn-outline-primary btn-upload-image" <textarea class="form-control" id="visaCountriesVisaTypes_0" rows="2"
data-target-input="visaCountriesFlag_<%= index %>" placeholder="e.g., Student Visa, Work Visa, Tourist Visa"><%= (featured.visaTypes || []).join(', ') %></textarea>
data-image-type="home"
>
<i class="fas fa-upload me-1"></i>Upload
</button>
</div> </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>
<% }); %>
</div> </div>
</div> </div>
</div> </div>
@@ -137,23 +99,13 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Label</label> <label class="form-label fw-medium">Label</label>
<input <input type="text" class="form-control" id="visaCountriesCtaLabel"
type="text" value="<%= data.visaCountries?.ctaButton?.label || '' %>" placeholder="e.g., Get Started" />
class="form-control"
id="visaCountriesCtaLabel"
value="<%= data.visaCountries?.ctaButton?.label || '' %>"
placeholder="e.g., Get Started"
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Link</label> <label class="form-label fw-medium">Link</label>
<input <input type="text" class="form-control" id="visaCountriesCtaHref"
type="text" value="<%= data.visaCountries?.ctaButton?.href || '' %>" placeholder="/contact" />
class="form-control"
id="visaCountriesCtaHref"
value="<%= data.visaCountries?.ctaButton?.href || '' %>"
placeholder="/contact"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -161,3 +113,33 @@
</div> </div>
</div> </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>

View File

@@ -13,23 +13,13 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Heading</label> <label class="form-label fw-medium">Heading</label>
<input <input type="text" class="form-control" id="visaSolutionsHeading"
type="text" value="<%= data.visaSolutions?.heading || '' %>" placeholder="e.g., Comprehensive Visa Solutions" />
class="form-control"
id="visaSolutionsHeading"
value="<%= data.visaSolutions?.heading || '' %>"
placeholder="e.g., Comprehensive Visa Solutions"
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Subheading</label> <label class="form-label fw-medium">Subheading</label>
<input <input type="text" class="form-control" id="visaSolutionsSubheading"
type="text" value="<%= data.visaSolutions?.subheading || '' %>" placeholder="e.g., Our Expert Services" />
class="form-control"
id="visaSolutionsSubheading"
value="<%= data.visaSolutions?.subheading || '' %>"
placeholder="e.g., Our Expert Services"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -39,62 +29,109 @@
<!-- Services Items --> <!-- Services Items -->
<div class="col-md-12"> <div class="col-md-12">
<div class="card border shadow-sm"> <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"> <h6 class="mb-0">
<i class="fas fa-list-ul me-2"></i>Visa Solutions Items <i class="fas fa-list-ul me-2"></i>Visa Solutions Items
</h6> </h6>
</div> </div>
<div class="card-body"> <div class="card-body" id="visaSolutionsItemsContainer">
<% (data.visaSolutions?.items || []).forEach(function(item, index) { %> <% const vsItems=(data.visaSolutions && Array.isArray(data.visaSolutions.items) &&
<div class="card mb-3 bg-light border"> data.visaSolutions.items.length===4) ? data.visaSolutions.items : (data.visaSolutions &&
<div class="card-body"> Array.isArray(data.visaSolutions.items) && data.visaSolutions.items.length> 0)
<h6 class="card-title fw-bold mb-3">Service <%= index + 1 %></h6> ? (function () {
<div class="row g-3"> const clone = data.visaSolutions.items.slice(0, 4);
<div class="col-md-3"> while (clone.length < 4) clone.push({}); return clone; })() : [{}, {}, {}, {}]; %>
<label class="form-label fw-medium">Number</label> <% vsItems.forEach(function(item, index) { %>
<input <div class="card mb-3 bg-light border visa-solution-item" data-index="<%= index %>">
type="text" <div class="card-header d-flex justify-content-between align-items-center bg-white">
class="form-control" <h6 class="card-title fw-bold mb-0">Service <span class="visa-solution-label">
id="visaSolutionsNumber_<%= index %>" <%= index + 1 %>
value="<%= item.number || '' %>" </span></h6>
placeholder="e.g., 01" </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>
<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> </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>

View File

@@ -3,7 +3,7 @@
<div class="row g-4"> <div class="row g-4">
<!-- Basic Info --> <!-- Basic Info -->
<div class="col-md-12"> <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"> <div class="card-header bg-white">
<h6 class="mb-0"> <h6 class="mb-0">
<i class="fas fa-info-circle me-2"></i>Basic Information <i class="fas fa-info-circle me-2"></i>Basic Information
@@ -13,32 +13,78 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Heading</label> <label class="form-label fw-medium">Heading</label>
<input <input type="text" class="form-control" id="whyChooseUsHeading"
type="text"
class="form-control"
id="whyChooseUsHeading"
value="<%= data.whyChooseUs?.heading || '' %>" value="<%= data.whyChooseUs?.heading || '' %>"
placeholder="e.g., Turning Study Abroad Dreams Into Reality" placeholder="e.g., Turning Study Abroad Dreams Into Reality" />
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Subheading</label> <label class="form-label fw-medium">Subheading</label>
<input <input type="text" class="form-control" id="whyChooseUsSubheading"
type="text" value="<%= data.whyChooseUs?.subheading || '' %>" placeholder="e.g., About Our Consultancy" />
class="form-control" </div>
id="whyChooseUsSubheading" <div class="col-md-6">
value="<%= data.whyChooseUs?.subheading || '' %>" <label class="form-label fw-medium">Highlight Word (Optional)</label>
placeholder="e.g., About Our Consultancy" <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>
<div class="col-md-12"> <div class="col-md-12">
<label class="form-label fw-medium">Description</label> <label class="form-label fw-medium">Description</label>
<textarea <textarea class="form-control" id="whyChooseUsDescription" rows="3"
class="form-control" placeholder="Enter description"><%= data.whyChooseUs?.description || '' %></textarea>
id="whyChooseUsDescription" </div>
rows="3" </div>
placeholder="Enter description" </div>
><%= data.whyChooseUs?.description || '' %></textarea> </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> </div>
</div> </div>
@@ -55,53 +101,36 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<% (data.whyChooseUs?.items || []).forEach(function(item, index) { %> <% (data.whyChooseUs?.items || []).forEach(function(item, index) { %>
<div class="card mb-3 bg-light border"> <div class="card mb-3 bg-light border">
<div class="card-body"> <div class="card-body">
<h6 class="card-title fw-bold mb-3">Item <%= index + 1 %></h6> <h6 class="card-title fw-bold mb-3">Item <%= index + 1 %>
<div class="row g-3"> </h6>
<div class="col-md-6"> <div class="row g-3">
<label class="form-label fw-medium">Icon URL</label> <div class="col-md-6">
<div class="input-group mb-2"> <label class="form-label fw-medium">Icon URL</label>
<input <div class="input-group mb-2">
type="text" <input type="text" class="form-control" id="whyChooseUsIcon_<%= index %>"
class="form-control" value="<%= item.icon || '' %>" />
id="whyChooseUsIcon_<%= index %>" <button type="button" class="btn btn-outline-primary btn-upload-image"
value="<%= item.icon || '' %>" data-target-input="whyChooseUsIcon_<%= index %>" data-image-type="home">
/> <i class="fas fa-upload me-1"></i>Upload
<button </button>
type="button" </div>
class="btn btn-outline-primary btn-upload-image" </div>
data-target-input="whyChooseUsIcon_<%= index %>" <div class="col-md-6">
data-image-type="home" <label class="form-label fw-medium">Title</label>
> <input type="text" class="form-control" id="whyChooseUsTitle_<%= index %>"
<i class="fas fa-upload me-1"></i>Upload value="<%= item.title || '' %>" placeholder="e.g., Global Reach" />
</button> </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 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> <% }); %>
<% }); %>
</div> </div>
</div> </div>
</div> </div>
@@ -116,17 +145,12 @@
</div> </div>
<div class="card-body"> <div class="card-body">
<% (data.whyChooseUs?.features || []).forEach(function(feature, index) { %> <% (data.whyChooseUs?.features || []).forEach(function(feature, index) { %>
<div class="mb-3"> <div class="mb-3">
<label class="form-label fw-medium">Feature <%= index + 1 %></label> <label class="form-label fw-medium">Feature <%= index + 1 %></label>
<input <input type="text" class="form-control" id="whyChooseUsFeature_<%= index %>" value="<%= feature %>"
type="text" placeholder="Enter feature" />
class="form-control" </div>
id="whyChooseUsFeature_<%= index %>" <% }); %>
value="<%= feature %>"
placeholder="Enter feature"
/>
</div>
<% }); %>
</div> </div>
</div> </div>
</div> </div>
@@ -143,23 +167,13 @@
<div class="row g-3"> <div class="row g-3">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Label</label> <label class="form-label fw-medium">Label</label>
<input <input type="text" class="form-control" id="whyChooseUsCtaLabel"
type="text" value="<%= data.whyChooseUs?.ctaButton?.label || '' %>" placeholder="e.g., Get Started" />
class="form-control"
id="whyChooseUsCtaLabel"
value="<%= data.whyChooseUs?.ctaButton?.label || '' %>"
placeholder="e.g., Get Started"
/>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label fw-medium">Link</label> <label class="form-label fw-medium">Link</label>
<input <input type="text" class="form-control" id="whyChooseUsCtaHref"
type="text" value="<%= data.whyChooseUs?.ctaButton?.href || '' %>" placeholder="/about" />
class="form-control"
id="whyChooseUsCtaHref"
value="<%= data.whyChooseUs?.ctaButton?.href || '' %>"
placeholder="/about"
/>
</div> </div>
</div> </div>
</div> </div>
@@ -167,3 +181,53 @@
</div> </div>
</div> </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>