diff --git a/controllers/homeController.js b/controllers/homeController.js index e651588..d4eac73 100644 --- a/controllers/homeController.js +++ b/controllers/homeController.js @@ -1,138 +1,56 @@ -const { addBaseUrlToImages } = require("../utils/imageHelper"); +const { addBaseUrlToImages, getFullImageUrl } = require("../utils/imageHelper"); const Home = require("../models/home"); +const Blog = require("../models/blog"); -// -------------------- Helpers -------------------- +// Các hàm hỗ trợ +const getHomeDoc = async () => Home.findOne().sort({ updatedAt: -1 }); +const getHomeData = async () => (await getHomeDoc())?.toObject() || {}; -const getHomeDoc = async () => { - // Keep newest document as the source of truth - return await Home.findOne().sort({ updatedAt: -1 }); -}; - -const getHomeData = async () => { - const doc = await Home.findOne().sort({ updatedAt: -1 }).lean(); - return doc || {}; -}; - -/** - * Default structure used by the current CMS Home admin UI (`views/admin/home/index.ejs`). - * This is intentionally permissive; the Home model itself also supports the Next.js - * structure from `hailearning.edu.vn/app/home.json`. - */ const getDefaultHomeData = () => ({ - hero: { - title: "", - description: "", - backgroundImage: "", - button: { label: "Book Your Adventure", href: "/booking" }, - contactBox: { - welcomeText: "", - phone: { label: "Call us", number: "", href: "" }, - email: { label: "Email", address: "", href: "" }, - workingHours: { label: "Working Hours", hours: "" }, - }, - }, - about: { - title: "", - subtitle: "", - description: "", - images: { mainImage1: "", mainImage2: "", avatars: [] }, - features: [], - quote: "", - button: { label: "", href: "" }, - stats: { customerCount: 0, customerLabel: "" }, - }, - missionVision: { - title: "", - subtitle: "", - backgroundImage: "", - cards: [], - }, - whyChooseUs: { - title: "", - subtitle: "", - description: "", - button: { label: "", href: "" }, - features: [], - tags: [], - cta: { text: "", linkText: "", linkHref: "" }, - }, - activities: { cards: [] }, - faq: { - title: "", - subtitle: "", - description: "", - image: "", - contact: { title: "", info: "" }, - questions: [], - }, - partners: { - title: "", - subtitle: "", - backgroundImage: "", - logos: [], - cta: { badge: "", text: "", linkText: "", linkHref: "" }, - }, - programs: { - title: "", - subtitle: "", - button: { label: "", href: "" }, - card: { - pricePrefix: "from", - priceSuffix: "USD", - buttonLabel: "Camp Detail", - buttonHref: "/camp-profiles", - }, + hero: { title: "", subtitle: "", description: "", backgroundImage: "", videoUrl: "", primaryButton: {}, secondaryButton: {} }, + whyChooseUs: { heading: "", subheading: "", description: "", items: [], features: [], ctaButton: {} }, + visaSolutions: { heading: "", subheading: "", items: [] }, + visaCountries: { heading: "", subheading: "", description: "", countries: [], ctaButton: {} }, + testimonials: { heading: "", subheading: "", videoUrl: "", videoThumbnail: "", items: [] }, + videoGallery: { heading: "", videoUrl: "", thumbnail: "" }, + 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" }, items: [], - }, - newsletter: { - title: "", - subtitle: "", - description: "", - image: "", - decorativeImage: "", - button: { label: "", placeholder: "", href: "" }, - }, - latestPosts: { - title: "", - subtitle: "", - searchPlaceholder: "", - sidebarTitle: "", - blogPosts: [], - sidebarPosts: [], - featuredCard: { image: "", title: "", description: "" }, + selectedBlogIds: [] // Array of manually selected blog IDs }, }); -// -------------------- Admin -------------------- - +// Admin: Xem trang quản lý exports.index = async (req, res) => { try { let data = await getHomeData(); - - if (!data || Object.keys(data).length === 0) { - data = getDefaultHomeData(); - } else { - // Merge minimal defaults to keep the view safe - const defaults = getDefaultHomeData(); - data.hero = data.hero || defaults.hero; - data.about = data.about || defaults.about; - data.missionVision = data.missionVision || defaults.missionVision; - data.whyChooseUs = data.whyChooseUs || defaults.whyChooseUs; - data.activities = data.activities || defaults.activities; - data.faq = data.faq || defaults.faq; - data.partners = data.partners || defaults.partners; - data.programs = data.programs || defaults.programs; - data.newsletter = data.newsletter || defaults.newsletter; - data.latestPosts = data.latestPosts || defaults.latestPosts; - } + 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 => { + data[s] = data[s] || defaults[s]; + }); 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(); return res.render("admin/home/index", { layout: "layouts/main", title: "Home Management", data, + allBlogs, frontendUrl, + backendUrl, + getFullImageUrl, currentPath: req.path, user: req.session.user, }); @@ -143,73 +61,32 @@ exports.index = async (req, res) => { } }; +// Admin: Cập nhật dữ liệu (tập trung vào achievements, partners; các phần khác giữ nguyên nếu không có dữ liệu mới) exports.update = async (req, res) => { try { - const currentDoc = await getHomeDoc(); - const currentData = currentDoc ? currentDoc.toObject() : {}; - const updatedData = { ...currentData }; - - // Quick fields (Hero) from classic form fields - if (req.body.heroTitle || req.body.heroDescription || req.body.heroBackgroundImage) { - updatedData.hero = { - title: req.body.heroTitle || "", - description: req.body.heroDescription || "", - backgroundImage: req.body.heroBackgroundImage || "", - button: { - label: req.body.heroButtonLabel || "Book Your Adventure", - href: req.body.heroButtonHref || "/booking", - }, - contactBox: { - welcomeText: req.body.heroContactWelcome || "", - phone: { - label: "Call us", - number: req.body.heroContactPhone || "", - href: req.body.heroContactPhone ? `tel:${req.body.heroContactPhone}` : "", - }, - email: { - label: "Email", - address: req.body.heroContactEmail || "", - href: req.body.heroContactEmail ? `mailto:${req.body.heroContactEmail}` : "", - }, - workingHours: { - label: "Working Hours", - hours: req.body.heroContactHours || "", - }, - }, - }; - } - - // Handle sections sent as JSON payloads const sections = [ - "hero", - "about", - "missionVision", - "whyChooseUs", - "activities", - "faq", - "partners", - "programs", - "newsletter", - "latestPosts", + "hero", "whyChooseUs", "visaSolutions", "visaCountries", + "testimonials", "videoGallery", "faq", "achievements", + "partners", "blogPreview" ]; + let doc = await getHomeDoc(); + if (!doc) { + doc = new Home({}); + } + let hasChanges = false; - for (const section of sections) { - if (!req.body[section]) continue; - - try { - const newSectionData = JSON.parse(req.body[section]); - const currentSectionData = currentData?.[section]; - - if (JSON.stringify(newSectionData) !== JSON.stringify(currentSectionData)) { - updatedData[section] = newSectionData; + if (req.body[section]) { + try { + const payload = JSON.parse(req.body[section]); + // Gán trực tiếp vào doc, Mongoose sẽ tự check schema + doc[section] = payload; + doc.markModified(section); hasChanges = true; + } catch (e) { + console.error(`Invalid JSON for ${section}:`, e); } - } catch (e) { - console.error(`Error processing section "${section}":`, e); - req.flash("error_msg", `Invalid JSON for section "${section}": ${e.message}`); - return req.session.save(() => res.redirect("/admin/home")); } } @@ -218,29 +95,75 @@ exports.update = async (req, res) => { return req.session.save(() => res.redirect("/admin/home")); } - if (currentDoc?._id) { - await Home.findByIdAndUpdate(currentDoc._id, updatedData, { new: true }); - } else { - await Home.create(updatedData); - } - - req.flash("success_msg", "Home data updated successfully"); + await doc.save(); + req.flash("success_msg", "Home page configuration has been updated!"); return req.session.save(() => res.redirect("/admin/home")); } catch (err) { console.error("Home update error:", err); - req.flash("error_msg", `Update error: ${err.message || "Unknown"}`); + req.flash("error_msg", `Update error: ${err.message}`); return req.session.save(() => res.redirect("/admin/home")); } }; -// -------------------- Public API -------------------- - +// Public API// API lấy danh sách blog cho CMS +exports.apiGetBlogs = async (req, res) => { + try { + const blogs = await Blog.find({ status: "published" }).sort({ createdAt: -1 }).select("title slug featuredImage author publishedAt").lean(); + res.json(blogs); + } catch (err) { + res.status(500).json({ error: err.message }); + } +}; exports.api = async (req, res) => { try { - const homeData = await getHomeData(); + let data = await getHomeData(); const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get("host")}`; - const processedData = addBaseUrlToImages(homeData, baseUrl); - return res.json(processedData); + + // === 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({ + _id: { $in: blogPreview.selectedBlogIds }, + 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" }) + .sort({ createdAt: -1 }) + .limit(3) + .lean(); + } + + // Map dữ liệu blog sang format mà frontend mong đợi + blogPreview.items = blogs.map(blog => ({ + title: blog.title, + excerpt: blog.excerpt, + category: blog.category && blog.category[0] ? blog.category[0] : "Visa", + date: blog.publishedAt || blog.createdAt, + author: { + name: blog.author || "Admin", + avatar: "" // Frontend đang tự xử lý hoặc dùng logo hệ thống + }, + comments: blog.commentsCount || 0, + link: `/blog/${blog.slug}`, + thumbnail: blog.featuredImage + })); + + data.blogPreview = blogPreview; + // =============================== + + const processed = addBaseUrlToImages(data, baseUrl); + return res.json(processed); } catch (err) { console.error("Home API error:", err); return res.status(500).json({ error: "Error loading home data" }); diff --git a/data/header-menu.json b/data/header-menu.json index 496291b..af22c79 100644 --- a/data/header-menu.json +++ b/data/header-menu.json @@ -132,36 +132,11 @@ { "label": "Blog", "slug": "blog", - "href": "#", + "href": "/blog", "type": "internal", "order": 5, "isActive": true, - "children": [ - { - "label": "Blog Grid", - "slug": "blog-grid", - "href": "/blog-grid", - "type": "internal", - "order": 1, - "isActive": true - }, - { - "label": "Blog Standard", - "slug": "blog-standard", - "href": "/blog", - "type": "internal", - "order": 2, - "isActive": true - }, - { - "label": "Blog Details", - "slug": "blog-details", - "href": "/blog-details", - "type": "internal", - "order": 3, - "isActive": true - } - ] + "children": [] }, { "label": "Contact Us", diff --git a/data/home.json b/data/home.json index 4ecd14f..92bd9ab 100644 --- a/data/home.json +++ b/data/home.json @@ -238,54 +238,50 @@ ] }, "partners": { - "heading": "Our Trusted Partners", - "items": [ - { - "name": "Best Visa Consultancy", - "logo": "/assets/img/home-1/feature/icon-1.png", - "year": "2025" - }, - { - "name": "Visa Success Award", - "logo": "/assets/img/home-1/feature/icon-2.png", - "year": "2025" - }, - { - "name": "Innovation Award", - "logo": "/assets/img/home-1/feature/icon-3.png", - "year": "2025" - }, - { - "name": "Global Education Partner", - "logo": "/assets/img/home-1/feature/icon-4.png", - "year": "2025" - }, - { - "name": "University Partner 1", - "logo": "/assets/img/home-1/brand/01.png", - "year": "2025" - }, - { - "name": "University Partner 2", - "logo": "/assets/img/home-1/brand/02.png", - "year": "2025" - }, - { - "name": "University Partner 3", - "logo": "/assets/img/home-1/brand/03.png", - "year": "2025" - }, - { - "name": "University Partner 4", - "logo": "/assets/img/home-1/brand/04.png", - "year": "2025" - }, - { - "name": "University Partner 5", - "logo": "/assets/img/home-1/brand/05.png", - "year": "2025" - } - ] + "visaConsultancy": { + "heading": "Our Achievements & Awards", + "items": [ + { + "name": "Best Visa Consultancy", + "icon": "/assets/img/home-1/feature/icon-1.png", + "year": "2025" + }, + { + "name": "Visa Success Award", + "icon": "/assets/img/home-1/feature/icon-2.png", + "year": "2025" + }, + { + "name": "Innovation Award", + "icon": "/assets/img/home-1/feature/icon-3.png", + "year": "2025" + }, + { + "name": "Global Education Partner", + "icon": "/assets/img/home-1/feature/icon-4.png", + "year": "2025" + } + ] + }, + "brands": { + "items": [ + { + "logo": "/assets/img/home-1/brand/01.png" + }, + { + "logo": "/assets/img/home-1/brand/02.png" + }, + { + "logo": "/assets/img/home-1/brand/03.png" + }, + { + "logo": "/assets/img/home-1/brand/04.png" + }, + { + "logo": "/assets/img/home-1/brand/05.png" + } + ] + } }, "blogPreview": { "heading": "Latest Insights & Updates", diff --git a/models/home.js b/models/home.js index cc2f48d..429d3be 100644 --- a/models/home.js +++ b/models/home.js @@ -156,19 +156,40 @@ const AchievementsSchema = new Schema( { _id: false }, ); -const PartnerItemSchema = new Schema( +const VisaConsultancyItemSchema = new Schema( { name: { type: String, default: "" }, - logo: { type: String, default: "" }, + icon: { type: String, default: "" }, year: { type: String, default: "" }, }, { _id: false }, ); +const VisaConsultancySchema = new Schema( + { + items: { type: [VisaConsultancyItemSchema], default: [] }, + }, + { _id: false }, +); + +const BrandItemSchema = new Schema( + { + logo: { type: String, default: "" }, + }, + { _id: false }, +); + +const BrandsSchema = new Schema( + { + items: { type: [BrandItemSchema], default: [] }, + }, + { _id: false }, +); + const PartnersSchema = new Schema( { - heading: { type: String, default: "" }, - items: { type: [PartnerItemSchema], default: [] }, + visaConsultancy: { type: VisaConsultancySchema, default: () => ({}) }, + brands: { type: BrandsSchema, default: () => ({}) }, }, { _id: false }, ); @@ -196,6 +217,7 @@ const BlogPreviewSchema = new Schema( subheading: { type: String, default: "" }, ctaButton: { type: LinkSchema, default: () => ({}) }, items: { type: [BlogPreviewItemSchema], default: [] }, + selectedBlogIds: [{ type: Schema.Types.ObjectId, ref: 'Blog' }], }, { _id: false }, ); diff --git a/routes/admin.js b/routes/admin.js index 6a565dc..49218f0 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -40,6 +40,7 @@ router.get("/dashboard", ensureAuthenticated, dashboardController.getDashboard); // Home router.get("/home", ensureAuthenticated, homeController.index); router.post("/home/update", ensureAuthenticated, homeController.update); +router.get("/home/api/blogs", ensureAuthenticated, homeController.apiGetBlogs); // Middleware chuẩn hóa code router.param("code", (req, res, next, code) => { diff --git a/views/admin/home/index.ejs b/views/admin/home/index.ejs index dbf860d..4296913 100644 --- a/views/admin/home/index.ejs +++ b/views/admin/home/index.ejs @@ -7,11 +7,7 @@

Edit content displayed on homepage

- + View Homepage
@@ -19,75 +15,51 @@
-
+ - - - + + + + + - - - +
@@ -140,2093 +87,16 @@
- -
-
- -
-
-
-
- Basic - Information -
-
-
-
-
- - -
-
- -
- - -
- <% if (data.hero?.backgroundImage) { %> -
- Background preview -
- <% } %> -
-
- - -
-
-
-
-
- - -
-
-
-
- Button - Configuration -
-
-
-
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Contact Box -
-
-
-
-
- - -
-
- - - Will auto-generate tel: link -
-
- - - Will auto-generate mailto: link -
-
- - -
-
-
-
-
-
-
- - -
-
- -
-
-
-
- Basic - Information -
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Main Images -
-
-
-
-
- -
- - -
- <% if (data.about?.images?.mainImage1) { %> -
- Main Image 1 -
- <% } %> -
-
- -
- - -
- <% if (data.about?.images?.mainImage2) { %> -
- Main Image 2 -
- <% } %> -
-
-
-
-
- - -
-
-
-
- Team Avatars -
-
-
- <% (data.about?.images?.avatars || []).forEach((avatar, - index) => { %> -
-
- -
- - -
-
-
- <% if (avatar) { %> - Avatar <%= index + 1 %> - <% } %> -
-
- <% }); %> -
-
-
- - -
-
-
-
- Features -
-
-
- <% (data.about?.features || - []).forEach(function(feature, index) { %> -
- - -
- <% }); %> -
-
-
- - -
-
-
-
- Button & Statistics -
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
-
-
- - -
-
- -
-
-
-
- General - Information -
-
-
-
-
- - -
-
- - -
-
- -
- - -
- <% if (data.missionVision?.backgroundImage) { %> -
- Mission Vision Background -
- <% } %> -
-
-
-
-
- - -
-
-
-
- Mission & Vision - Cards -
-
-
- <% (data.missionVision?.cards || - []).forEach(function(card, index) { %> -
-
-
-
- Card <%= index + 1 %> -
-
-
-
- - -
-
- - -
-
-
-
- <% }); %> <% if (!data.missionVision?.cards || - data.missionVision.cards.length === 0) { %> -
- No cards available. To add cards, please update the - database or run migration. -
- <% } %> -
-
-
-
-
- - -
-
- -
-
-
-
- General - Information -
-
-
-
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Main Button -
-
-
-
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Features -
-
-
- <% (data.whyChooseUs?.features || - []).forEach(function(feature, index) { %> -
-
-
- Feature <%= index + 1 %> -
-
-
- - -
-
- - -
-
-
-
- <% }); %> <% if (!data.whyChooseUs?.features || - data.whyChooseUs.features.length === 0) { %> -
- No features available. Add via database or migration. -
- <% } %> -
-
-
- - -
-
-
-
- Tags -
-
-
-
- - -
- Enter each tag on a new line. -
-
-
-
-
- - -
-
-
-
- Call to Action - (CTA) -
-
-
-
-
- - -
-
- - -
-
- - -
-
-
-
-
-
-
- - -
-
-
-
-
-
- Activity Cards -
-
-
- <% (data.activities?.cards || []).forEach(function(card, - index) { %> -
-
-
- Activity <%= index + 1 %> -
-
-
- - -
-
- -
- - -
- <% if (card.image) { %> -
- Activity Image -
- <% } %> -
-
- - -
-
-
-
- <% }); %> <% if (!data.activities?.cards || - data.activities.cards.length === 0) { %> -
- No activities found. Add them via database or - migration. -
- <% } %> -
-
-
-
-
- - -
-
- -
-
-
-
- General - Information -
-
-
-
-
- - -
-
- - -
-
- - -
-
- -
- - -
- <% if (data.faq?.image) { %> -
- FAQ Image -
- <% } %> -
-
-
-
-
- - -
-
-
-
- Sidebar Contact -
-
-
-
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Questions & - Answers -
-
-
- <% (data.faq?.questions || []).forEach(function(item, - index) { %> -
-
-
- Q&A <%= index + 1 %> -
-
-
- - -
-
- - -
-
-
-
- <% }); %> <% if (!data.faq?.questions || - data.faq.questions.length === 0) { %> -
- No questions available. Add via database or migration. -
- <% } %> -
-
-
-
-
- - -
-
- -
-
-
-
- General - Information -
-
-
-
-
- - -
-
- - -
-
- -
- - -
- <% if (data.partners?.backgroundImage) { %> -
- Partners Background -
- <% } %> -
-
-
-
-
- - -
-
-
-
- Partner Logos -
-
-
- <% (data.partners?.logos || []).forEach(function(logo, - index) { %> -
-
- -
- - -
- <% if (logo) { %> -
- Logo <%= index + 1 %> -
- <% } %> -
-
- <% }); %> <% if (!data.partners?.logos || - data.partners.logos.length === 0) { %> -
- No logos available. Add via database or migration. -
- <% } %> -
-
-
- - -
-
-
-
- CTA Section -
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
-
-
- - -
-
- -
-
-
-
- General - Information -
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Card Display - Settings -
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Program Items - <%= (data.programs?.items || []).length %> - items -
-
-
- <% (data.programs?.items || []).forEach(function(item, - index) { %> -
-
-
-
- Program <%= index + 1 %>: <%= item.title || - 'Untitled' %> -
- <%= item.id || 'No ID' %> -
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- Enter seasons separated by commas -
-
-
- -
- - -
- <% if (item.image) { %> -
- Program Image -
- <% } %> -
-
-
-
- <% }); %> <% if (!data.programs?.items || - data.programs.items.length === 0) { %> -
- - No program items available. Add them via database or - migration. -
- <% } %> -
-
-
-
-
- - -
-
-
-
-
-
- Newsletter - Configuration -
-
-
-
-
- - -
-
- - -
-
- - -
- - -
- -
- - -
- <% if (data.newsletter?.image) { %> -
- Newsletter Image -
- <% } %> -
-
- -
- - -
- <% if (data.newsletter?.decorativeImage) { %> -
- Decorative Image -
- <% } %> -
- - -
- - -
-
- - -
-
- - -
-
-
-
-
-
-
- - -
-
- -
-
-
-
- General Settings -
-
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Featured Card - (Sidebar) -
-
-
-
-
- -
- - -
- <% if (data.latestPosts?.featuredCard?.image) { %> -
- Featured -
- <% } %> -
-
- - -
-
- - -
-
-
-
-
- - -
-
-
-
- Main Blog Posts -
-
-
- <% (data.latestPosts?.blogPosts || - []).forEach(function(post, index) { %> -
-
-
- Post <%= index + 1 %> -
-
-
- - -
-
- - -
-
- - -
-
- -
- - -
-
-
- - -
-
-
-
- <% }); %> <% if (!data.latestPosts?.blogPosts || - data.latestPosts.blogPosts.length === 0) { %> -
- No blog posts found. -
- <% } %> -
-
-
- - -
-
-
-
- Sidebar Posts -
-
-
- <% (data.latestPosts?.sidebarPosts || - []).forEach(function(post, index) { %> -
-
-
- Sidebar Post <%= index + 1 %> -
-
-
- - -
-
- -
- - -
-
-
- - -
-
-
-
- <% }); %> <% if (!data.latestPosts?.sidebarPosts || - data.latestPosts.sidebarPosts.length === 0) { %> -
- No sidebar posts found. -
- <% } %> -
-
-
-
-
+ <%- 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') %>
@@ -2253,43 +123,64 @@ + \ No newline at end of file diff --git a/views/admin/home/sections/achievements.ejs b/views/admin/home/sections/achievements.ejs new file mode 100644 index 0000000..28f4bd5 --- /dev/null +++ b/views/admin/home/sections/achievements.ejs @@ -0,0 +1,122 @@ + +
+
+ +
+
+
+
+ General Information +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ Achievement Items (Fixed 4 Items) +
+
+
+ <% for(let i=0; i<4; i++) { + const item = (data.achievements?.items && data.achievements.items[i]) || {}; + %> +
+
+
+
Achievement #<%= i + 1 %>
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ <% } %> +
+
+
+
+
+ + diff --git a/views/admin/home/sections/blogPreview.ejs b/views/admin/home/sections/blogPreview.ejs new file mode 100644 index 0000000..342fef7 --- /dev/null +++ b/views/admin/home/sections/blogPreview.ejs @@ -0,0 +1,175 @@ + +
+
+ +
+
+
+
+ Basic Information & Blog Selection +
+ CMS will automatically fetch the 3 latest posts if no specific blog is + selected. +
+
+
+
+ + +
+
+ + +
+ +
+ +

Select blog posts to prioritize on the home page. If none are selected, + the system will use the 3 latest posts.

+
+ <% if (allBlogs && allBlogs.length> 0) { %> + <% allBlogs.forEach(blog=> { + const isSelected = data.blogPreview?.selectedBlogIds && data.blogPreview.selectedBlogIds.some(id => + id.toString() === blog._id.toString()); + %> +
+
+
+
+ onclick="event.stopPropagation(); + handleCheckboxChange(this)"> +
+
+ +
+
+ <%= blog.title %> +
+

+ <%= blog.publishedAt ? new Date(blog.publishedAt).toLocaleDateString('vi-VN') : '' %> +

+
+
+
+ <% }) %> + <% } else { %> +
+

No published blogs found. Please create some blogs first.

+ Create Blog +
+ <% } %> +
+
+
+
+
+
+ + +
+
+
+
+ CTA Button +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/views/admin/home/sections/faq.ejs b/views/admin/home/sections/faq.ejs new file mode 100644 index 0000000..021c06a --- /dev/null +++ b/views/admin/home/sections/faq.ejs @@ -0,0 +1,124 @@ + +
+
+ +
+
+
+
+ Basic Information +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ FAQ Items +
+
+
+ <% (data.faq?.items || []).forEach(function(item, index) { %> +
+
+
FAQ <%= index + 1 %>
+
+
+ + +
+
+ + +
+
+
+
+ <% }); %> +
+
+
+ + +
+
+
+
+ CTA Button +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
diff --git a/views/admin/home/sections/hero.ejs b/views/admin/home/sections/hero.ejs new file mode 100644 index 0000000..c1f333f --- /dev/null +++ b/views/admin/home/sections/hero.ejs @@ -0,0 +1,157 @@ + +
+
+ +
+
+
+
+ Basic Information +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+ <% if (data.hero?.backgroundImage) { %> +
+ Background preview +
+ <% } %> +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ Primary Button +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ Secondary Button +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
diff --git a/views/admin/home/sections/partners.ejs b/views/admin/home/sections/partners.ejs new file mode 100644 index 0000000..1536ade --- /dev/null +++ b/views/admin/home/sections/partners.ejs @@ -0,0 +1,150 @@ + +
+
+ +
+
+
+
+ Awards & Certifications (Fixed 4 Items) +
+
+
+
+ <% for(let i=0; i<4; i++) { + const item = (data.partners?.visaConsultancy?.items && data.partners.visaConsultancy.items[i]) || {}; + %> +
+
+
+
Award #<%= i + 1 %>
+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ +
+
+
+
+
+ <% } %> +
+
+
+
+ + +
+
+
+
+ Brand Partner Logos (Slider) +
+ +
+
+
+ <% (data.partners?.brands?.items || []).forEach(function(item, index) { %> +
+
+
+
+ Brand Logo + +
+
+ + +
+
+ +
+
+
+
+ <% }); %> +
+
+
+
+
+
+ + \ No newline at end of file diff --git a/views/admin/home/sections/testimonials.ejs b/views/admin/home/sections/testimonials.ejs new file mode 100644 index 0000000..55f91ac --- /dev/null +++ b/views/admin/home/sections/testimonials.ejs @@ -0,0 +1,159 @@ + +
+
+ +
+
+
+
+ Basic Information +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+
+
+ + +
+
+
+
+ Testimonials +
+
+
+ <% (data.testimonials?.items || []).forEach(function(item, index) { %> +
+
+
Testimonial <%= index + 1 %>
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+
+
+ <% }); %> +
+
+
+
+
diff --git a/views/admin/home/sections/videoGallery.ejs b/views/admin/home/sections/videoGallery.ejs new file mode 100644 index 0000000..4398814 --- /dev/null +++ b/views/admin/home/sections/videoGallery.ejs @@ -0,0 +1,67 @@ + +
+
+
+
+
+
+ Video Gallery +
+
+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ <% if (data.videoGallery?.thumbnail) { %> +
+ Thumbnail preview +
+ <% } %> +
+
+
+
+
+
+
diff --git a/views/admin/home/sections/visaCountries.ejs b/views/admin/home/sections/visaCountries.ejs new file mode 100644 index 0000000..73472f8 --- /dev/null +++ b/views/admin/home/sections/visaCountries.ejs @@ -0,0 +1,163 @@ + +
+
+ +
+
+
+
+ Basic Information +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ Countries +
+
+
+ <% (data.visaCountries?.countries || []).forEach(function(country, index) { %> +
+
+
Country <%= index + 1 %>
+
+
+ + +
+
+ + +
+
+ +
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ <% }); %> +
+
+
+ + +
+
+
+
+ CTA Button +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
diff --git a/views/admin/home/sections/visaSolutions.ejs b/views/admin/home/sections/visaSolutions.ejs new file mode 100644 index 0000000..bfad6f1 --- /dev/null +++ b/views/admin/home/sections/visaSolutions.ejs @@ -0,0 +1,100 @@ + +
+
+ +
+
+
+
+ Basic Information +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ Visa Solutions Items +
+
+
+ <% (data.visaSolutions?.items || []).forEach(function(item, index) { %> +
+
+
Service <%= index + 1 %>
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ <% }); %> +
+
+
+
+
diff --git a/views/admin/home/sections/whyChooseUs.ejs b/views/admin/home/sections/whyChooseUs.ejs new file mode 100644 index 0000000..2a1a24b --- /dev/null +++ b/views/admin/home/sections/whyChooseUs.ejs @@ -0,0 +1,169 @@ + +
+
+ +
+
+
+
+ Basic Information +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ + +
+
+
+
+ Why Choose Us Items +
+
+
+ <% (data.whyChooseUs?.items || []).forEach(function(item, index) { %> +
+
+
Item <%= index + 1 %>
+
+
+ +
+ + +
+
+
+ + +
+
+ + +
+
+
+
+ <% }); %> +
+
+
+ + +
+
+
+
+ Features +
+
+
+ <% (data.whyChooseUs?.features || []).forEach(function(feature, index) { %> +
+ + +
+ <% }); %> +
+
+
+ + +
+
+
+
+ CTA Button +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
diff --git a/views/auth/login.ejs b/views/auth/login.ejs index 8469dec..bc14e19 100644 --- a/views/auth/login.ejs +++ b/views/auth/login.ejs @@ -178,8 +178,8 @@
-

CMS Management System

-

Welcome to Content Management System

+

CMS Management System

+

Welcome to Content Management System