feat: Improve home and contact CMS field guidance

This commit is contained in:
Tống Thành Đạt
2026-04-10 01:38:30 +07:00
parent ed09c7fa89
commit 7ce5921fe0
15 changed files with 529 additions and 230 deletions

View File

@@ -7,11 +7,13 @@ const heroSchema = new mongoose.Schema(
type: String,
required: true,
trim: true,
maxlength: 40,
},
backgroundImage: {
type: String,
trim: true,
default: "",
maxlength: 255,
},
overlayColor: {
type: String,
@@ -62,9 +64,10 @@ const contactCardSchema = new mongoose.Schema(
type: String,
required: true,
trim: true,
maxlength: 40,
},
content: {
type: [String],
type: [{ type: String, maxlength: 96 }],
default: [],
},
iconType: {
@@ -72,6 +75,7 @@ const contactCardSchema = new mongoose.Schema(
required: false,
trim: true,
default: "",
maxlength: 255,
},
iconSource: {
type: String,
@@ -139,16 +143,19 @@ const mapSchema = new mongoose.Schema(
type: String,
required: true,
trim: true,
maxlength: 120,
},
markerTitle: {
type: String,
trim: true,
default: "",
maxlength: 48,
},
embedUrl: {
type: String,
trim: true,
default: "",
maxlength: 1000,
},
tileLayer: {
type: tileLayerSchema,
@@ -165,11 +172,13 @@ const formFieldSchema = new mongoose.Schema(
type: String,
required: true,
trim: true,
maxlength: 32,
},
label: {
type: String,
trim: true,
default: "",
maxlength: 32,
},
type: {
type: String,
@@ -181,6 +190,7 @@ const formFieldSchema = new mongoose.Schema(
type: String,
trim: true,
default: "",
maxlength: 72,
},
required: {
type: Boolean,
@@ -195,6 +205,7 @@ const formFieldSchema = new mongoose.Schema(
type: String,
trim: true,
default: "",
maxlength: 48,
},
},
{ _id: false }
@@ -207,6 +218,7 @@ const submitButtonSchema = new mongoose.Schema(
type: String,
required: true,
trim: true,
maxlength: 24,
},
icon: {
type: String,
@@ -229,16 +241,19 @@ const formSchema = new mongoose.Schema(
type: String,
trim: true,
default: "",
maxlength: 32,
},
heading: {
type: String,
trim: true,
default: "",
maxlength: 48,
},
description: {
type: String,
trim: true,
default: "",
maxlength: 160,
},
fields: {
type: [formFieldSchema],

View File

@@ -5,15 +5,15 @@ const { Schema } = mongoose;
// Reusable small schemas
const LinkSchema = new Schema(
{
label: { type: String, default: "" },
href: { type: String, default: "" },
label: { type: String, default: "", maxlength: 32 },
href: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const FloatingContactBrandSchema = new Schema(
{
imageSrc: { type: String, default: "" },
imageSrc: { type: String, default: "", maxlength: 255 },
imageAlt: { type: String, default: "", maxlength: 60 },
},
{ _id: false },
@@ -21,8 +21,8 @@ const FloatingContactBrandSchema = new Schema(
const FloatingContactTriggerSchema = new Schema(
{
imageSrc: { type: String, default: "" },
icon: { type: String, default: "fa-comments" },
imageSrc: { type: String, default: "", maxlength: 255 },
icon: { type: String, default: "fa-comments", maxlength: 64 },
},
{ _id: false },
);
@@ -34,10 +34,10 @@ const FloatingContactActionSchema = new Schema(
enabled: { type: Boolean, default: true },
label: { type: String, default: "", maxlength: 48 },
subtitle: { type: String, default: "", maxlength: 48 },
href: { type: String, default: "" },
iconImage: { type: String, default: "" },
href: { type: String, default: "", maxlength: 255 },
iconImage: { type: String, default: "", maxlength: 255 },
iconType: { type: String, default: "iconClass" },
iconClass: { type: String, default: "" },
iconClass: { type: String, default: "", maxlength: 120 },
iconText: { type: String, default: "", maxlength: 12 },
order: { type: Number, default: 0 },
},
@@ -59,13 +59,13 @@ const FloatingContactSchema = 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: "" },
title: { type: String, default: "", maxlength: 72 },
subtitle: { type: String, default: "", maxlength: 48 },
description: { type: String, default: "", maxlength: 220 },
primaryButton: { type: LinkSchema, default: () => ({}) },
secondaryButton: { type: LinkSchema, default: () => ({}) },
heroImage: { type: String, default: "" },
videoUrl: { type: String, default: "" },
heroImage: { type: String, default: "", maxlength: 255 },
videoUrl: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
@@ -73,42 +73,42 @@ const HeroSlideSchema = new Schema(
const HeroSchema = new Schema(
{
// Background for whole hero section
backgroundImage: { type: String, default: "" },
backgroundImage: { type: String, default: "", maxlength: 255 },
// Multiple slides
slides: { type: [HeroSlideSchema], default: [] },
// Legacy single-slide fields (backward compatible)
title: { type: String, default: "" },
subtitle: { type: String, default: "" },
description: { type: String, default: "" },
title: { type: String, default: "", maxlength: 72 },
subtitle: { type: String, default: "", maxlength: 48 },
description: { type: String, default: "", maxlength: 220 },
primaryButton: { type: LinkSchema, default: () => ({}) },
secondaryButton: { type: LinkSchema, default: () => ({}) },
heroImage: { type: String, default: "" },
videoUrl: { type: String, default: "" },
heroImage: { type: String, default: "", maxlength: 255 },
videoUrl: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const WhyChooseUsItemSchema = new Schema(
{
icon: { type: String, default: "" },
title: { type: String, default: "" },
description: { type: String, default: "" },
icon: { type: String, default: "", maxlength: 255 },
title: { type: String, default: "", maxlength: 40 },
description: { type: String, default: "", maxlength: 72 },
},
{ _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: "" },
heading: { type: String, default: "", maxlength: 72 },
subheading: { type: String, default: "", maxlength: 48 },
description: { type: String, default: "", maxlength: 260 },
highlightWord: { type: String, default: "", maxlength: 24 },
mainImage: { type: String, default: "", maxlength: 255 },
secondaryImage: { type: String, default: "", maxlength: 255 },
items: { type: [WhyChooseUsItemSchema], default: [] },
features: { type: [String], default: [] },
features: { type: [{ type: String, maxlength: 96 }], default: [] },
ctaButton: { type: LinkSchema, default: () => ({}) },
},
{ _id: false },
@@ -116,18 +116,18 @@ const WhyChooseUsSchema = new Schema(
const VisaSolutionItemSchema = new Schema(
{
number: { type: String, default: "" },
title: { type: String, default: "" },
description: { type: String, default: "" },
link: { type: String, default: "" },
number: { type: String, default: "", maxlength: 4 },
title: { type: String, default: "", maxlength: 56 },
description: { type: String, default: "", maxlength: 180 },
link: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const VisaSolutionsSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
heading: { type: String, default: "", maxlength: 64 },
subheading: { type: String, default: "", maxlength: 40 },
items: { type: [VisaSolutionItemSchema], default: [] },
},
{ _id: false },
@@ -135,20 +135,20 @@ const VisaSolutionsSchema = new Schema(
const VisaCountrySchema = new Schema(
{
name: { type: String, default: "" },
code: { type: String, default: "" },
flag: { type: String, default: "" },
link: { type: String, default: "" },
visaTypes: { type: [String], default: [] },
name: { type: String, default: "", maxlength: 40 },
code: { type: String, default: "", maxlength: 12 },
flag: { type: String, default: "", maxlength: 255 },
link: { type: String, default: "", maxlength: 255 },
visaTypes: { type: [{ type: String, maxlength: 48 }], default: [] },
},
{ _id: false },
);
const VisaCountriesSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
description: { type: String, default: "" },
heading: { type: String, default: "", maxlength: 88 },
subheading: { type: String, default: "", maxlength: 56 },
description: { type: String, default: "", maxlength: 240 },
countries: { type: [VisaCountrySchema], default: [] },
ctaButton: { type: LinkSchema, default: () => ({}) },
},
@@ -157,22 +157,22 @@ const VisaCountriesSchema = new Schema(
const TestimonialSchema = new Schema(
{
name: { type: String, default: "" },
role: { type: String, default: "" },
country: { type: String, default: "" },
name: { type: String, default: "", maxlength: 48 },
role: { type: String, default: "", maxlength: 48 },
country: { type: String, default: "", maxlength: 48 },
rating: { type: Number, default: 5 },
comment: { type: String, default: "" },
avatar: { type: String, default: "" },
comment: { type: String, default: "", maxlength: 280 },
avatar: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const TestimonialsSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
videoUrl: { type: String, default: "" },
videoThumbnail: { type: String, default: "" },
heading: { type: String, default: "", maxlength: 64 },
subheading: { type: String, default: "", maxlength: 40 },
videoUrl: { type: String, default: "", maxlength: 255 },
videoThumbnail: { type: String, default: "", maxlength: 255 },
items: { type: [TestimonialSchema], default: [] },
},
{ _id: false },
@@ -180,26 +180,40 @@ const TestimonialsSchema = new Schema(
const VideoGallerySchema = new Schema(
{
heading: { type: String, default: "" },
videoUrl: { type: String, default: "" },
thumbnail: { type: String, default: "" },
heading: {
type: String,
default: "",
maxlength: 32,
validate: {
validator(value) {
const words = String(value || "")
.trim()
.split(/\s+/)
.filter(Boolean);
return words.length <= 4;
},
message: "Video gallery heading must be 4 words or fewer.",
},
},
videoUrl: { type: String, default: "", maxlength: 255 },
thumbnail: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
const FaqItemSchema = new Schema(
{
question: { type: String, default: "" },
answer: { type: String, default: "" },
question: { type: String, default: "", maxlength: 120 },
answer: { type: String, default: "", maxlength: 320 },
},
{ _id: false },
);
const FaqSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
description: { type: String, default: "" },
heading: { type: String, default: "", maxlength: 64 },
subheading: { type: String, default: "", maxlength: 40 },
description: { type: String, default: "", maxlength: 220 },
ctaButton: { type: LinkSchema, default: () => ({}) },
items: { type: [FaqItemSchema], default: [] },
},
@@ -208,18 +222,18 @@ const FaqSchema = new Schema(
const AchievementItemSchema = new Schema(
{
value: { type: String, default: "" },
suffix: { type: String, default: "" },
label: { type: String, default: "" },
description: { type: String, default: "" },
value: { type: String, default: "", maxlength: 6 },
suffix: { type: String, default: "", maxlength: 4 },
label: { type: String, default: "", maxlength: 40 },
description: { type: String, default: "", maxlength: 120 },
},
{ _id: false },
);
const AchievementsSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
heading: { type: String, default: "", maxlength: 56 },
subheading: { type: String, default: "", maxlength: 32 },
items: { type: [AchievementItemSchema], default: [] },
},
{ _id: false },
@@ -227,9 +241,9 @@ const AchievementsSchema = new Schema(
const VisaConsultancyItemSchema = new Schema(
{
name: { type: String, default: "" },
icon: { type: String, default: "" },
year: { type: String, default: "" },
name: { type: String, default: "", maxlength: 48 },
icon: { type: String, default: "", maxlength: 255 },
year: { type: String, default: "", maxlength: 8 },
},
{ _id: false },
);
@@ -243,7 +257,7 @@ const VisaConsultancySchema = new Schema(
const BrandItemSchema = new Schema(
{
logo: { type: String, default: "" },
logo: { type: String, default: "", maxlength: 255 },
},
{ _id: false },
);
@@ -265,16 +279,16 @@ const PartnersSchema = new Schema(
const BlogPreviewItemSchema = new Schema(
{
title: { type: String, default: "" },
excerpt: { type: String, default: "" },
category: { type: String, default: "" },
title: { type: String, default: "", maxlength: 120 },
excerpt: { type: String, default: "", maxlength: 280 },
category: { type: String, default: "", maxlength: 48 },
date: { type: String, default: "" }, // keep as string for easy JSON compatibility (e.g. "2025-08-20")
author: {
name: { type: String, default: "" },
name: { type: String, default: "", maxlength: 48 },
avatar: { type: String, default: "" },
},
comments: { type: Number, default: 0 },
link: { type: String, default: "" },
link: { type: String, default: "", maxlength: 255 },
thumbnail: { type: String, default: "" },
},
{ _id: false },
@@ -282,8 +296,8 @@ const BlogPreviewItemSchema = new Schema(
const BlogPreviewSchema = new Schema(
{
heading: { type: String, default: "" },
subheading: { type: String, default: "" },
heading: { type: String, default: "", maxlength: 64 },
subheading: { type: String, default: "", maxlength: 40 },
ctaButton: { type: LinkSchema, default: () => ({}) },
items: { type: [BlogPreviewItemSchema], default: [] },
selectedBlogIds: [{ type: Schema.Types.ObjectId, ref: 'Blog' }],