diff --git a/controllers/homeController.js b/controllers/homeController.js index d4eac73..fbc55e8 100644 --- a/controllers/homeController.js +++ b/controllers/homeController.js @@ -7,8 +7,28 @@ const getHomeDoc = async () => Home.findOne().sort({ updatedAt: -1 }); const getHomeData = async () => (await getHomeDoc())?.toObject() || {}; const getDefaultHomeData = () => ({ - hero: { title: "", subtitle: "", description: "", backgroundImage: "", videoUrl: "", primaryButton: {}, secondaryButton: {} }, - whyChooseUs: { heading: "", subheading: "", description: "", items: [], features: [], ctaButton: {} }, + hero: { + backgroundImage: "", + slides: [], + title: "", + subtitle: "", + description: "", + heroImage: "", + videoUrl: "", + primaryButton: {}, + secondaryButton: {}, + }, + whyChooseUs: { + heading: "", + subheading: "", + description: "", + highlightWord: "", + mainImage: "", + secondaryImage: "", + items: [], + features: [], + ctaButton: {}, + }, visaSolutions: { heading: "", subheading: "", items: [] }, visaCountries: { heading: "", subheading: "", description: "", countries: [], ctaButton: {} }, testimonials: { heading: "", subheading: "", videoUrl: "", videoThumbnail: "", items: [] }, @@ -16,10 +36,10 @@ const getDefaultHomeData = () => ({ faq: { heading: "", subheading: "", description: "", ctaButton: {}, items: [] }, achievements: { heading: "", subheading: "", items: [] }, partners: { visaConsultancy: { items: [] }, brands: { items: [] } }, - blogPreview: { - heading: "Latest Insights & Updates", - subheading: "Visa Tips & Guides", - ctaButton: { label: "View All Articles", href: "/blog" }, + blogPreview: { + heading: "Latest Insights & Updates", + subheading: "Visa Tips & Guides", + ctaButton: { label: "View All Articles", href: "/blog" }, items: [], selectedBlogIds: [] // Array of manually selected blog IDs }, @@ -30,7 +50,7 @@ exports.index = async (req, res) => { try { let data = await getHomeData(); const defaults = getDefaultHomeData(); - + // Merge dữ liệu mặc định cho tất cả các phần const sections = Object.keys(defaults); sections.forEach(s => { @@ -39,7 +59,7 @@ exports.index = async (req, res) => { const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; const backendUrl = process.env.BACKEND_URL || "http://localhost:3001"; - + // Lấy tất cả blog để chọn trong CMS const allBlogs = await Blog.find({ status: "published" }).sort({ createdAt: -1 }).lean(); @@ -65,8 +85,8 @@ exports.index = async (req, res) => { exports.update = async (req, res) => { try { const sections = [ - "hero", "whyChooseUs", "visaSolutions", "visaCountries", - "testimonials", "videoGallery", "faq", "achievements", + "hero", "whyChooseUs", "visaSolutions", "visaCountries", + "testimonials", "videoGallery", "faq", "achievements", "partners", "blogPreview" ]; @@ -122,20 +142,20 @@ exports.api = async (req, res) => { // === Xử lý Blog Preview động === const blogPreview = data.blogPreview || {}; let blogs = []; - + // Nếu có chọn blog cụ thể if (blogPreview.selectedBlogIds && blogPreview.selectedBlogIds.length > 0) { - blogs = await Blog.find({ + blogs = await Blog.find({ _id: { $in: blogPreview.selectedBlogIds }, - status: "published" + status: "published" }).lean(); - + // Sắp xếp theo thứ tự đã chọn trong selectedBlogIds blogs.sort((a, b) => { return blogPreview.selectedBlogIds.indexOf(a._id.toString()) - blogPreview.selectedBlogIds.indexOf(b._id.toString()); }); - } - + } + // Nếu không chọn hoặc chọn nhưng không đủ, lấy thêm 3 bài mới nhất (hoặc bù vào) if (blogs.length === 0) { blogs = await Blog.find({ status: "published" }) diff --git a/data/home.json b/data/home.json index 92bd9ab..0d4df4a 100644 --- a/data/home.json +++ b/data/home.json @@ -1,7 +1,6 @@ { - "hero": { - "title": "From Application to Visa – We've Got You Covered", + "title": "From Application to Visa – We’ve Got You Covered", "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.", "primaryButton": { @@ -48,25 +47,25 @@ "number": "01", "title": "Student Visa Guidance", "description": "Assistance with admission, documentation, and visa application.Assistance", - "link": "/service-details" + "link": "/services/student-visa" }, { "number": "02", "title": "PTE Exam Preparation", "description": "We provide expert guidance and personalized support throughout the education visa process,", - "link": "/service-details" + "link": "/services/pte-exam" }, { "number": "03", "title": "University Selection Assistance", "description": "We provide expert guidance and personalized support throughout the education visa process,", - "link": "/service-details" + "link": "/services/university-selection" }, { "number": "04", "title": "IELTS Exam Preparation", "description": "We provide expert guidance and personalized support throughout the education visa process,", - "link": "/service-details" + "link": "/services/ielts-exam" } ] }, diff --git a/models/home.js b/models/home.js index 429d3be..09d3d52 100644 --- a/models/home.js +++ b/models/home.js @@ -11,14 +11,35 @@ const LinkSchema = new Schema( { _id: false }, ); -const HeroSchema = new Schema( +// Hero slide (for multiple hero items in slider) +const HeroSlideSchema = new Schema( { 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: "" }, + }, + { _id: false }, +); + +const HeroSchema = new Schema( + { + // Background for whole hero section 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: "" }, }, { _id: false }, @@ -38,6 +59,9 @@ const WhyChooseUsSchema = new Schema( heading: { type: String, default: "" }, subheading: { type: String, default: "" }, description: { type: String, default: "" }, + highlightWord: { type: String, default: "" }, + mainImage: { type: String, default: "" }, + secondaryImage: { type: String, default: "" }, items: { type: [WhyChooseUsItemSchema], default: [] }, features: { type: [String], default: [] }, ctaButton: { type: LinkSchema, default: () => ({}) }, diff --git a/views/admin/home/index.ejs b/views/admin/home/index.ejs index 4296913..f094b65 100644 --- a/views/admin/home/index.ejs +++ b/views/admin/home/index.ejs @@ -88,15 +88,15 @@