Refactor Homepage, About, Footer (Controller,Model, View, Data), Update: Dashboard, Delete old file

This commit is contained in:
2026-04-22 15:30:09 +07:00
parent cb5e4b0ceb
commit 3ede974215
67 changed files with 3003 additions and 14780 deletions
+61 -220
View File
@@ -3,49 +3,41 @@ const mongoose = require("mongoose");
const { Schema } = mongoose;
// Reusable small schemas
const LinkSchema = new Schema(
const FloatingBadgeSchema = new Schema(
{
icon: { type: String, default: "" },
value: { type: String, default: "" },
label: { type: String, default: "" },
href: { type: String, default: "" },
},
{ _id: false },
);
// 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)
badge: { type: String, default: "" },
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: "" },
searchPlaceholder: { type: String, default: "" },
buttonLabel: { type: String, default: "" },
image: { type: String, default: "" },
imageAlt: { type: String, default: "" },
floatingBadge: { type: FloatingBadgeSchema, default: () => ({}) },
},
{ _id: false },
);
const WhyChooseUsItemSchema = new Schema(
const QuickLinkItemSchema = new Schema(
{
icon: { type: String, default: "" },
title: { type: String, default: "" },
description: { type: String, default: "" },
linkText: { type: String, default: "" },
href: { type: String, default: "" },
},
{ _id: false },
);
const ValuePropFeatureSchema = new Schema(
{
icon: { type: String, default: "" },
title: { type: String, default: "" },
@@ -54,218 +46,68 @@ const WhyChooseUsItemSchema = new Schema(
{ _id: false },
);
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: () => ({}) },
},
{ _id: false },
);
const VisaSolutionItemSchema = new Schema(
{
number: { type: String, default: "" },
title: { type: String, default: "" },
description: { type: String, default: "" },
link: { type: String, default: "" },
},
{ _id: false },
);
const VisaSolutionsSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
items: { type: [VisaSolutionItemSchema], default: [] },
},
{ _id: false },
);
const VisaCountrySchema = new Schema(
{
name: { type: String, default: "" },
code: { type: String, default: "" },
flag: { type: String, default: "" },
link: { type: String, default: "" },
visaTypes: { type: [String], default: [] },
},
{ _id: false },
);
const VisaCountriesSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
description: { type: String, default: "" },
countries: { type: [VisaCountrySchema], default: [] },
ctaButton: { type: LinkSchema, default: () => ({}) },
},
{ _id: false },
);
const TestimonialSchema = new Schema(
{
name: { type: String, default: "" },
role: { type: String, default: "" },
country: { type: String, default: "" },
rating: { type: Number, default: 5 },
comment: { type: String, default: "" },
avatar: { type: String, default: "" },
},
{ _id: false },
);
const TestimonialsSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
videoUrl: { type: String, default: "" },
videoThumbnail: { type: String, default: "" },
items: { type: [TestimonialSchema], default: [] },
},
{ _id: false },
);
const VideoGallerySchema = new Schema(
{
heading: { type: String, default: "" },
videoUrl: { type: String, default: "" },
thumbnail: { type: String, default: "" },
},
{ _id: false },
);
const FaqItemSchema = new Schema(
{
question: { type: String, default: "" },
answer: { type: String, default: "" },
},
{ _id: false },
);
const FaqSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
description: { type: String, default: "" },
ctaButton: { type: LinkSchema, default: () => ({}) },
items: { type: [FaqItemSchema], default: [] },
},
{ _id: false },
);
const AchievementItemSchema = new Schema(
const ValuePropStatSchema = new Schema(
{
value: { type: String, default: "" },
suffix: { type: String, default: "" },
label: { type: String, default: "" },
description: { type: String, default: "" },
image: { type: String, default: "" },
imageAlt: { type: String, default: "" },
},
{ _id: false },
);
const AchievementsSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
items: { type: [AchievementItemSchema], default: [] },
},
{ _id: false },
);
const VisaConsultancyItemSchema = new Schema(
{
name: { 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(
{
visaConsultancy: { type: VisaConsultancySchema, default: () => ({}) },
brands: { type: BrandsSchema, default: () => ({}) },
},
{ _id: false },
);
const BlogPreviewItemSchema = new Schema(
const ValuePropSchema = new Schema(
{
badge: { type: String, default: "" },
title: { type: String, default: "" },
excerpt: { type: String, default: "" },
category: { type: String, default: "" },
date: { type: String, default: "" }, // keep as string for easy JSON compatibility (e.g. "2025-08-20")
author: {
name: { type: String, default: "" },
avatar: { type: String, default: "" },
},
comments: { type: Number, default: 0 },
link: { type: String, default: "" },
thumbnail: { type: String, default: "" },
description: { type: String, default: "" },
features: { type: [ValuePropFeatureSchema], default: [] },
stats: { type: [ValuePropStatSchema], default: [] },
},
{ _id: false },
);
const BlogPreviewSchema = new Schema(
const ProgramItemSchema = new Schema(
{
category: { type: String, default: "" },
image: { type: String, default: "" },
duration: { type: String, default: "" },
rating: { type: String, default: "" },
title: { type: String, default: "" },
description: { type: String, default: "" },
studentCount: { type: String, default: "" },
href: { type: String, default: "" },
},
{ _id: false },
);
const ProgramsSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
ctaButton: { type: LinkSchema, default: () => ({}) },
items: { type: [BlogPreviewItemSchema], default: [] },
selectedBlogIds: [{ type: Schema.Types.ObjectId, ref: 'Blog' }],
description: { type: String, default: "" },
items: { type: [ProgramItemSchema], default: [] },
},
{ _id: false },
);
const RequestInfoSchema = new Schema(
{
heading: { type: String, default: "" },
description: { type: String, default: "" },
phone: { type: String, default: "" },
email: { type: String, default: "" },
programs: { type: [String], default: [] },
},
{ _id: false },
);
/**
* Home page content model
*
* NOTE:
* - This schema is based on `hailearning.edu.vn/app/home.json`.
* - `strict: false` keeps backward compatibility with any existing CMS-only sections
* (e.g. about/missionVision/programs/newsletter/latestPosts...) that the admin UI might still send.
*/
const HomeSchema = new Schema(
{
hero: { type: HeroSchema, default: () => ({}) },
whyChooseUs: { type: WhyChooseUsSchema, default: () => ({}) },
visaSolutions: { type: VisaSolutionsSchema, default: () => ({}) },
visaCountries: { type: VisaCountriesSchema, default: () => ({}) },
testimonials: { type: TestimonialsSchema, default: () => ({}) },
videoGallery: { type: VideoGallerySchema, default: () => ({}) },
faq: { type: FaqSchema, default: () => ({}) },
achievements: { type: AchievementsSchema, default: () => ({}) },
partners: { type: PartnersSchema, default: () => ({}) },
blogPreview: { type: BlogPreviewSchema, default: () => ({}) },
quickLinks: { type: [QuickLinkItemSchema], default: [] },
valueProp: { type: ValuePropSchema, default: () => ({}) },
programs: { type: ProgramsSchema, default: () => ({}) },
requestInfo: { type: RequestInfoSchema, default: () => ({}) },
},
{
timestamps: true,
@@ -274,4 +116,3 @@ const HomeSchema = new Schema(
);
module.exports = mongoose.model("Home", HomeSchema);