diff --git a/controllers/aboutUsController.js b/controllers/aboutUsController.js index cf94d30..862b45a 100644 --- a/controllers/aboutUsController.js +++ b/controllers/aboutUsController.js @@ -1,5 +1,6 @@ const { addBaseUrlToImages } = require("../utils/imageHelper"); const AboutUs = require("../models/aboutUs"); +const Blog = require("../models/blog"); const jsonHelper = require("../utils/jsonHelper"); /** @@ -9,22 +10,67 @@ const jsonHelper = require("../utils/jsonHelper"); exports.getAbout = async (req, res) => { try { // Force no-cache headers - res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); - res.setHeader('Pragma', 'no-cache'); - res.setHeader('Expires', '0'); + res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + res.setHeader("Pragma", "no-cache"); + res.setHeader("Expires", "0"); const data = await AboutUs.getSingle(); const rawData = data.toObject(); - const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get('host')}`; + // === Dynamic Blog News Section === + const news = rawData.news || {}; + let blogs = []; + + // Nếu có chọn blog cụ thể + if (news.selectedBlogIds && news.selectedBlogIds.length > 0) { + blogs = await Blog.find({ + _id: { $in: news.selectedBlogIds }, + status: "published", + }).lean(); + + // Sắp xếp theo thứ tự đã chọn trong selectedBlogIds + blogs.sort((a, b) => { + return news.selectedBlogIds.indexOf(a._id.toString()) - news.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 + 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 + news.items = blogs.map((blog) => ({ + title: blog.title, + category: blog.category && blog.category[0] ? blog.category[0] : "Visa", + date: + blog.publishedAt || + new Date(blog.createdAt).toLocaleDateString("en-GB", { + day: "numeric", + month: "long", + year: "numeric", + }), + comments: blog.commentsCount || 0, + author: { + name: blog.author || "Admin", + avatar: "/assets/img/home-1/news/client.png", // Default avatar + }, + link: `/blog/${blog.slug}`, + thumbnail: blog.featuredImage, + })); + + rawData.news = news; + // =============================== + + const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get("host")}`; const processedData = addBaseUrlToImages(rawData, baseUrl); - + res.json(processedData); } catch (error) { console.error("Error getting about data:", error); res.status(500).json({ success: false, - error: "Failed to get about data" + error: "Failed to get about data", }); } }; @@ -44,7 +90,7 @@ exports.updateAbout = async (req, res) => { } catch (e) { return res.status(400).json({ success: false, - message: "Invalid JSON in aboutJson" + message: "Invalid JSON in aboutJson", }); } } @@ -55,9 +101,7 @@ exports.updateAbout = async (req, res) => { await doc.save(); // Fetch fresh data for syncing and returning - const finalData = await AboutUs.findOne() - .select('-_id -__v -createdAt -updatedAt') - .lean(); + const finalData = await AboutUs.findOne().select("-_id -__v -createdAt -updatedAt").lean(); // Update about.json file to keep it in sync jsonHelper.writeJsonFile("about", finalData); @@ -65,13 +109,13 @@ exports.updateAbout = async (req, res) => { res.json({ success: true, message: "About Us updated successfully", - data: finalData + data: finalData, }); } catch (error) { console.error("Error updating about data:", error); res.status(500).json({ success: false, - error: "Failed to update about data: " + error.message + error: "Failed to update about data: " + error.message, }); } }; @@ -84,15 +128,20 @@ exports.index = async (req, res) => { const data = await AboutUs.getSingle(); const rawData = data.toObject(); + // Lấy tất cả blog để chọn trong CMS + const allBlogs = await Blog.find({ status: "published" }).sort({ createdAt: -1 }).lean(); + const activeTab = req.query.activeTab || "hero"; res.render("admin/aboutUs/index", { layout: "layouts/main", title: "About Us Management", data: rawData, + allBlogs, activeTab, user: req.session.user, currentPath: req.path, - frontendUrl: process.env.FRONTEND_URL || 'http://localhost:3000' + frontendUrl: process.env.FRONTEND_URL || "http://localhost:3000", + backendUrl: process.env.BACKEND_URL || "http://localhost:3001", }); } catch (err) { console.error("Error in about index:", err); @@ -120,9 +169,7 @@ exports.update = async (req, res) => { doc.set(updateData); await doc.save(); - const finalData = await AboutUs.findOne() - .select('-_id -__v -createdAt -updatedAt') - .lean(); + const finalData = await AboutUs.findOne().select("-_id -__v -createdAt -updatedAt").lean(); jsonHelper.writeJsonFile("about", finalData); req.flash("success_msg", "About Us updated successfully"); diff --git a/data/about.json b/data/about.json index ac1e658..246a6c7 100644 --- a/data/about.json +++ b/data/about.json @@ -11,15 +11,15 @@ "subheading": "Company Intro", "heading": "Building Pathways to Your Immigration Success", "description": "We provide expert guidance, personalized solutions, and transparent processes to help you achieve your immigration goals. Our dedicated team ensures a smooth journey, building pathways to your international success.", - "image": "http://localhost:3001/uploads/about/intro.jpg" + "image": "/uploads/about/businessman.jpg" }, "mission": { "subheading": "About Our Consultancy", "heading": "Turning Study Abroad Dreams Into Reality", "description": "We guide students with expert visa consulting, ensuring a smooth process from application to approval, turning study abroad aspirations into life-changing opportunities for a brighter future.", "images": { - "main": "/assets/img/home-1/about/about-1.jpg", - "secondary": "/assets/img/home-1/about/about-02.jpg", + "main": "/uploads/about/375x419.jpg", + "secondary": "/uploads/about/375x419.jpg", "bgShape": "/assets/img/home-1/about/Vector.png", "planeShape": "/assets/img/home-1/about/plane.png", "topShape": "/assets/img/home-1/about/shape.png", @@ -51,7 +51,7 @@ "subheading": "Your Travel Made Easy", "heading": "Smooth Visa Journey Guaranteed", "description": "We provide expert guidance for every visa application, ensuring smooth processing, personalized support, and reliable assistance", - "image": "/assets/img/home-2/feature/02.png", + "image": "/uploads/about/686x906.jpg", "items": [ { "icon": "/assets/img/home-2/icon/01.png", @@ -81,43 +81,11 @@ "label": "view all articles", "href": "/blog" }, - "items": [ - { - "title": "Step-by-Step Guide to Applying for a Student Visa", - "category": "Student Visa", - "date": "20 August ,2025", - "comments": 8, - "author": { - "name": "Sohel", - "avatar": "/assets/img/home-1/news/client.png" - }, - "link": "/blog/step-by-step-guide-student-visa", - "thumbnail": "/assets/img/home-1/news/news-1.jpg" - }, - { - "title": "Tips to Prepare Financial Documents for Visa Approval", - "category": "IELTS / TOEFL", - "date": "20 August ,2025", - "comments": 8, - "author": { - "name": "Sohel", - "avatar": "/assets/img/home-1/news/client.png" - }, - "link": "/blog/financial-documents-visa-approval", - "thumbnail": "/assets/img/home-1/news/news-2.jpg" - }, - { - "title": "Post-Arrival Guide What Every Student Should Know", - "category": "Study Abroad", - "date": "20 August ,2025", - "comments": 8, - "author": { - "name": "Sohel", - "avatar": "/assets/img/home-1/news/client.png" - }, - "link": "/blog/post-arrival-guide-students", - "thumbnail": "/assets/img/home-1/news/news-3.jpg" - } - ] + "selectedBlogIds": [ + "69857d6c6d04fed459107944", + "69857d6c6d04fed459107942", + "69857d6c6d04fed459107940" + ], + "items": [] } } \ No newline at end of file diff --git a/models/aboutUs.js b/models/aboutUs.js index 1c0bfda..d72dc07 100644 --- a/models/aboutUs.js +++ b/models/aboutUs.js @@ -1,106 +1,108 @@ const mongoose = require("mongoose"); const aboutUsSchema = new mongoose.Schema( - { - hero: { - title: String, - breadcrumb: [String], - backgroundImage: String, - }, - intro: { - subheading: String, - heading: String, - description: String, - image: String, - }, - mission: { - subheading: String, - heading: String, - description: String, - images: { - main: String, - secondary: String, - bgShape: String, - planeShape: String, - topShape: String, - globeShape: String, - }, - items: [ - new mongoose.Schema( - { - icon: String, - label: String, - description: String, - }, - { _id: false } - ), - ], - features: [String], - ctaButton: { - label: String, - href: String, - }, - }, - features: { - backgroundImage: String, - subheading: String, - heading: String, - description: String, - image: String, - items: [ - new mongoose.Schema( - { - icon: String, + { + hero: { title: String, + breadcrumb: [String], + backgroundImage: String, + }, + intro: { + subheading: String, + heading: String, description: String, - }, - { _id: false } - ), - ], - ctaButton: { - label: String, - href: String, - }, - }, - news: { - subheading: String, - heading: String, - ctaButton: { - label: String, - href: String, - }, - items: [ - new mongoose.Schema( - { - title: String, - category: String, - date: String, - comments: Number, - author: { - name: String, - avatar: String, + image: String, + }, + mission: { + subheading: String, + heading: String, + description: String, + images: { + main: String, + secondary: String, + bgShape: String, + planeShape: String, + topShape: String, + globeShape: String, }, - link: String, - thumbnail: String, - }, - { _id: false } - ), - ], + items: [ + new mongoose.Schema( + { + icon: String, + label: String, + description: String, + }, + { _id: false }, + ), + ], + features: [String], + ctaButton: { + label: String, + href: String, + }, + }, + features: { + backgroundImage: String, + subheading: String, + heading: String, + description: String, + image: String, + items: [ + new mongoose.Schema( + { + icon: String, + title: String, + description: String, + }, + { _id: false }, + ), + ], + ctaButton: { + label: String, + href: String, + }, + }, + news: { + subheading: String, + heading: String, + ctaButton: { + label: String, + href: String, + }, + selectedBlogIds: [{ type: mongoose.Schema.Types.ObjectId, ref: "Blog" }], + // Deprecated: items field kept for backward compatibility during migration + items: [ + new mongoose.Schema( + { + title: String, + category: String, + date: String, + comments: Number, + author: { + name: String, + avatar: String, + }, + link: String, + thumbnail: String, + }, + { _id: false }, + ), + ], + }, + }, + { + timestamps: true, + collection: "aboutus", }, - }, - { - timestamps: true, - collection: "aboutus", - } ); // Static method để đảm bảo luôn chỉ có 1 bản ghi duy nhất (Singleton) aboutUsSchema.statics.getSingle = async function () { - let doc = await this.findOne(); - if (!doc) { - doc = await this.create({}); - } - return doc; + let doc = await this.findOne(); + if (!doc) { + doc = await this.create({}); + } + return doc; }; module.exports = mongoose.model("AboutUs", aboutUsSchema); diff --git a/public/img/home-1/686x906.jpg b/public/img/home-1/686x906.jpg new file mode 100644 index 0000000..0f75a17 Binary files /dev/null and b/public/img/home-1/686x906.jpg differ diff --git a/public/img/home-1/about/375x419.jpg b/public/img/home-1/about/375x419.jpg new file mode 100644 index 0000000..1a73623 Binary files /dev/null and b/public/img/home-1/about/375x419.jpg differ diff --git a/public/img/home-1/about/businessman.jpg b/public/img/home-1/about/businessman.jpg new file mode 100644 index 0000000..f092493 Binary files /dev/null and b/public/img/home-1/about/businessman.jpg differ diff --git a/public/img/home-1/employee-testimonial-questions.jpeg b/public/img/home-1/employee-testimonial-questions.jpeg new file mode 100644 index 0000000..c7d4ba5 Binary files /dev/null and b/public/img/home-1/employee-testimonial-questions.jpeg differ diff --git a/public/img/home-1/news/connector-852x400.png b/public/img/home-1/news/connector-852x400.png new file mode 100644 index 0000000..0e9e71f Binary files /dev/null and b/public/img/home-1/news/connector-852x400.png differ diff --git a/public/uploads/about/01.png b/public/uploads/about/01.png new file mode 100644 index 0000000..cb8e5e7 Binary files /dev/null and b/public/uploads/about/01.png differ diff --git a/public/uploads/about/01.svg b/public/uploads/about/01.svg new file mode 100644 index 0000000..87ac71c --- /dev/null +++ b/public/uploads/about/01.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/uploads/about/02.png b/public/uploads/about/02.png new file mode 100644 index 0000000..cc69b4b Binary files /dev/null and b/public/uploads/about/02.png differ diff --git a/public/uploads/about/375x419.jpg b/public/uploads/about/375x419.jpg new file mode 100644 index 0000000..1a73623 Binary files /dev/null and b/public/uploads/about/375x419.jpg differ diff --git a/public/uploads/about/686x906.jpg b/public/uploads/about/686x906.jpg new file mode 100644 index 0000000..0f75a17 Binary files /dev/null and b/public/uploads/about/686x906.jpg differ diff --git a/public/uploads/about/Vector.png b/public/uploads/about/Vector.png new file mode 100644 index 0000000..e69d461 Binary files /dev/null and b/public/uploads/about/Vector.png differ diff --git a/public/uploads/about/about-02.jpg b/public/uploads/about/about-02.jpg new file mode 100644 index 0000000..d9399ee Binary files /dev/null and b/public/uploads/about/about-02.jpg differ diff --git a/public/uploads/about/about-1.jpg b/public/uploads/about/about-1.jpg new file mode 100644 index 0000000..7e0555e Binary files /dev/null and b/public/uploads/about/about-1.jpg differ diff --git a/public/uploads/about/bg-shape.png b/public/uploads/about/bg-shape.png new file mode 100644 index 0000000..b30ddb9 Binary files /dev/null and b/public/uploads/about/bg-shape.png differ diff --git a/public/uploads/about/breadcrumb.jpg b/public/uploads/about/breadcrumb.jpg new file mode 100644 index 0000000..a6d73a3 Binary files /dev/null and b/public/uploads/about/breadcrumb.jpg differ diff --git a/public/uploads/about/businessman.jpg b/public/uploads/about/businessman.jpg new file mode 100644 index 0000000..f092493 Binary files /dev/null and b/public/uploads/about/businessman.jpg differ diff --git a/public/uploads/about/client.png b/public/uploads/about/client.png new file mode 100644 index 0000000..398078e Binary files /dev/null and b/public/uploads/about/client.png differ diff --git a/public/uploads/about/details-1.jpg b/public/uploads/about/details-1.jpg new file mode 100644 index 0000000..fb9d1ba Binary files /dev/null and b/public/uploads/about/details-1.jpg differ diff --git a/public/uploads/about/footer-bg.jpg b/public/uploads/about/footer-bg.jpg new file mode 100644 index 0000000..1c8a33b Binary files /dev/null and b/public/uploads/about/footer-bg.jpg differ diff --git a/public/uploads/about/globe.png b/public/uploads/about/globe.png new file mode 100644 index 0000000..f593f8d Binary files /dev/null and b/public/uploads/about/globe.png differ diff --git a/public/uploads/about/intro.jpg b/public/uploads/about/intro.jpg new file mode 100644 index 0000000..2540cd4 Binary files /dev/null and b/public/uploads/about/intro.jpg differ diff --git a/public/uploads/about/news-1.jpg b/public/uploads/about/news-1.jpg new file mode 100644 index 0000000..199f457 Binary files /dev/null and b/public/uploads/about/news-1.jpg differ diff --git a/public/uploads/about/news-2.jpg b/public/uploads/about/news-2.jpg new file mode 100644 index 0000000..199f457 Binary files /dev/null and b/public/uploads/about/news-2.jpg differ diff --git a/public/uploads/about/news-3.jpg b/public/uploads/about/news-3.jpg new file mode 100644 index 0000000..199f457 Binary files /dev/null and b/public/uploads/about/news-3.jpg differ diff --git a/public/uploads/about/plane.png b/public/uploads/about/plane.png new file mode 100644 index 0000000..391ca8c Binary files /dev/null and b/public/uploads/about/plane.png differ diff --git a/public/uploads/about/shape.png b/public/uploads/about/shape.png new file mode 100644 index 0000000..bacb7da Binary files /dev/null and b/public/uploads/about/shape.png differ diff --git a/public/uploads/blog/connector-852x400.png b/public/uploads/blog/connector-852x400.png new file mode 100644 index 0000000..0e9e71f Binary files /dev/null and b/public/uploads/blog/connector-852x400.png differ diff --git a/public/uploads/footer/black-logo.svg b/public/uploads/footer/black-logo.svg new file mode 100644 index 0000000..ec35168 --- /dev/null +++ b/public/uploads/footer/black-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/uploads/footer/footer-bg.jpg b/public/uploads/footer/footer-bg.jpg new file mode 100644 index 0000000..1c8a33b Binary files /dev/null and b/public/uploads/footer/footer-bg.jpg differ diff --git a/public/uploads/footer/white-logo.svg b/public/uploads/footer/white-logo.svg new file mode 100644 index 0000000..486d3ee --- /dev/null +++ b/public/uploads/footer/white-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/public/uploads/header/footer-bg.jpg b/public/uploads/header/footer-bg.jpg new file mode 100644 index 0000000..1c8a33b Binary files /dev/null and b/public/uploads/header/footer-bg.jpg differ diff --git a/public/uploads/layout/white-logo.svg b/public/uploads/layout/white-logo.svg new file mode 100644 index 0000000..486d3ee --- /dev/null +++ b/public/uploads/layout/white-logo.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/scripts/2026_02_07_migrate_about_news_to_blog.js b/scripts/2026_02_07_migrate_about_news_to_blog.js new file mode 100644 index 0000000..7cce458 --- /dev/null +++ b/scripts/2026_02_07_migrate_about_news_to_blog.js @@ -0,0 +1,99 @@ +/** + * Migration: Convert About News static items to dynamic Blog selection + * Date: 2026-02-07 + * + * This migration: + * 1. Adds selectedBlogIds field to About news section + * 2. Keeps existing items for backward compatibility + * 3. Does NOT delete old data (safe migration) + */ + +const mongoose = require("mongoose"); +require("dotenv").config(); + +const MONGODB_URI = process.env.MONGODB_URI || "mongodb://localhost:27017/SIMS"; + +async function up() { + try { + await mongoose.connect(MONGODB_URI); + console.log("✓ Connected to MongoDB"); + + const AboutUs = mongoose.model("AboutUs", new mongoose.Schema({}, { strict: false })); + + const doc = await AboutUs.findOne(); + + if (!doc) { + console.log("⚠ No About Us document found. Skipping migration."); + return; + } + + // Check if already migrated + if (doc.news && doc.news.selectedBlogIds !== undefined) { + console.log("✓ Migration already applied. Skipping."); + return; + } + + // Add selectedBlogIds field (empty array by default) + if (!doc.news) { + doc.news = {}; + } + + doc.news.selectedBlogIds = []; + + // Keep existing items for backward compatibility + // Admin can manually select blogs after migration + + await doc.save(); + + console.log("✓ Migration completed successfully"); + console.log(" - Added selectedBlogIds field to About news section"); + console.log(" - Existing items preserved for backward compatibility"); + console.log(" - Admin can now select blogs from Blog Management"); + } catch (error) { + console.error("✗ Migration failed:", error); + throw error; + } +} + +async function down() { + try { + await mongoose.connect(MONGODB_URI); + console.log("✓ Connected to MongoDB"); + + const AboutUs = mongoose.model("AboutUs", new mongoose.Schema({}, { strict: false })); + + const doc = await AboutUs.findOne(); + + if (!doc || !doc.news) { + console.log("⚠ No About Us document found. Skipping rollback."); + return; + } + + // Remove selectedBlogIds field + if (doc.news.selectedBlogIds !== undefined) { + delete doc.news.selectedBlogIds; + await doc.save(); + console.log("✓ Rollback completed - selectedBlogIds removed"); + } else { + console.log("✓ Nothing to rollback"); + } + } catch (error) { + console.error("✗ Rollback failed:", error); + throw error; + } +} + +// Run migration +if (require.main === module) { + up() + .then(() => { + console.log("\n✓ Migration script completed"); + process.exit(0); + }) + .catch((error) => { + console.error("\n✗ Migration script failed:", error); + process.exit(1); + }); +} + +module.exports = { up, down }; diff --git a/views/admin/aboutUs/index.ejs b/views/admin/aboutUs/index.ejs index 34770b7..556b9cd 100644 --- a/views/admin/aboutUs/index.ejs +++ b/views/admin/aboutUs/index.ejs @@ -281,36 +281,65 @@
-
-
News Section
+
+
News Section (Blog Preview)
+ System will automatically fetch the 3 latest posts if no specific blog is selected.
- - + +
- - + +
- - + +
- - + +
-
-
News Articles
- +
+ +

Select blog posts to display on About page. If none are selected, the system will use the 3 latest posts.

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

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

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

No published blogs found. Please create some blogs first.

+ Create Blog +
+ <% } %> +
-
@@ -638,7 +667,16 @@ document.getElementById('newsHeading').value = news.heading || ''; document.getElementById('newsCtaLabel').value = news.ctaButton?.label || ''; document.getElementById('newsCtaHref').value = news.ctaButton?.href || ''; - populateNewsItems(news.items || []); + + // Update blog selection checkboxes + document.querySelectorAll('.about-blog-checkbox').forEach(cb => { + const isSelected = news.selectedBlogIds && news.selectedBlogIds.some(id => id.toString() === cb.value); + cb.checked = isSelected; + const card = cb.closest('.blog-select-card'); + if (card) { + handleAboutCheckboxUpdate(card, isSelected); + } + }); } function updateImagePreview(inputId, imagePath) { @@ -760,77 +798,42 @@ }); } - function addNewsItem() { - const container = document.getElementById('newsItemsContainer'); - const idx = container.children.length; - const html = ` -
-
-
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- -
- - -
-
-
- - -
-
- -
- - -
-
-
- -
-
`; - container.insertAdjacentHTML('beforeend', html); + // Blog selection functions for About News section + function toggleAboutBlogSelection(card, blogId) { + const checkbox = card.querySelector('.about-blog-checkbox'); + const isChecking = !checkbox.checked; + + if (isChecking) { + const checkedCount = document.querySelectorAll('.about-blog-checkbox:checked').length; + if (checkedCount >= 3) { + alert('You can only select up to 3 blogs.'); + return; + } + } + + checkbox.checked = isChecking; + handleAboutCheckboxUpdate(card, checkbox.checked); } - function populateNewsItems(items) { - const container = document.getElementById('newsItemsContainer'); - container.innerHTML = ''; - items.forEach((item, i) => { - addNewsItem(); - const last = container.lastElementChild; - last.querySelector(`[name="newsItemTitle_${i}"]`).value = item.title || ''; - last.querySelector(`[name="newsItemCategory_${i}"]`).value = item.category || ''; - last.querySelector(`[name="newsItemDate_${i}"]`).value = item.date || ''; - last.querySelector(`[name="newsItemComments_${i}"]`).value = item.comments || 0; - last.querySelector(`[name="newsItemAuthorName_${i}"]`).value = item.author?.name || ''; - last.querySelector(`[name="newsItemAuthorAvatar_${i}"]`).value = item.author?.avatar || ''; - last.querySelector(`[name="newsItemLink_${i}"]`).value = item.link || ''; - last.querySelector(`[name="newsItemThumbnail_${i}"]`).value = item.thumbnail || ''; - }); + function handleAboutCheckboxChange(checkbox) { + if (checkbox.checked) { + const checkedCount = document.querySelectorAll('.about-blog-checkbox:checked').length; + if (checkedCount > 3) { + checkbox.checked = false; + alert('You can only select up to 3 blogs.'); + return; + } + } + const card = checkbox.closest('.blog-select-card'); + handleAboutCheckboxUpdate(card, checkbox.checked); + } + + function handleAboutCheckboxUpdate(card, isChecked) { + if (isChecked) { + card.classList.add('border-primary', 'bg-light'); + } else { + card.classList.remove('border-primary', 'bg-light'); + } } @@ -926,6 +929,11 @@ document.getElementById('featuresJson').value = JSON.stringify(featuresData); // News + const selectedIds = []; + document.querySelectorAll('.about-blog-checkbox:checked').forEach(cb => { + selectedIds.push(cb.value); + }); + const newsData = { subheading: document.getElementById('newsSubheading').value.trim(), heading: document.getElementById('newsHeading').value.trim(), @@ -933,18 +941,8 @@ label: document.getElementById('newsCtaLabel').value.trim(), href: document.getElementById('newsCtaHref').value.trim() }, - items: Array.from(document.querySelectorAll('.news-item-row')).map(item => ({ - title: item.querySelector('[name^="newsItemTitle_"]').value.trim(), - category: item.querySelector('[name^="newsItemCategory_"]').value.trim(), - date: item.querySelector('[name^="newsItemDate_"]').value.trim(), - comments: parseInt(item.querySelector('[name^="newsItemComments_"]').value) || 0, - author: { - name: item.querySelector('[name^="newsItemAuthorName_"]').value.trim(), - avatar: item.querySelector('[name^="newsItemAuthorAvatar_"]').value.trim() - }, - link: item.querySelector('[name^="newsItemLink_"]').value.trim(), - thumbnail: item.querySelector('[name^="newsItemThumbnail_"]').value.trim() - })).filter(i => i.title !== '') + selectedBlogIds: selectedIds, + items: [] // Server will populate this from selectedBlogIds }; document.getElementById('newsJson').value = JSON.stringify(newsData); @@ -955,4 +953,15 @@ } - \ No newline at end of file + + + \ No newline at end of file