forked from UKSOURCE/cms.hailearning.edu.vn
Merge branch 'fea/dat-07042026-float-social-buttons' of https://gits.techvanguard.vn/UKSOURCE/cms.hailearning.edu.vn into fea/nhat-06042026-menu-management
This commit is contained in:
@@ -3,87 +3,87 @@ const mongoose = require("mongoose");
|
||||
const aboutUsSchema = new mongoose.Schema(
|
||||
{
|
||||
hero: {
|
||||
title: String,
|
||||
breadcrumb: [String],
|
||||
backgroundImage: String,
|
||||
title: { type: String, trim: true, maxlength: 80 },
|
||||
breadcrumb: [{ type: String, trim: true, maxlength: 80 }],
|
||||
backgroundImage: { type: String, trim: true, maxlength: 255 },
|
||||
},
|
||||
intro: {
|
||||
subheading: String,
|
||||
heading: String,
|
||||
description: String,
|
||||
image: String,
|
||||
subheading: { type: String, trim: true, maxlength: 80 },
|
||||
heading: { type: String, trim: true, maxlength: 120 },
|
||||
description: { type: String, trim: true, maxlength: 1000 },
|
||||
image: { type: String, trim: true, maxlength: 255 },
|
||||
},
|
||||
mission: {
|
||||
subheading: String,
|
||||
heading: String,
|
||||
description: String,
|
||||
subheading: { type: String, trim: true, maxlength: 80 },
|
||||
heading: { type: String, trim: true, maxlength: 120 },
|
||||
description: { type: String, trim: true, maxlength: 1000 },
|
||||
images: {
|
||||
main: String,
|
||||
secondary: String,
|
||||
bgShape: String,
|
||||
planeShape: String,
|
||||
topShape: String,
|
||||
globeShape: String,
|
||||
main: { type: String, trim: true, maxlength: 255 },
|
||||
secondary: { type: String, trim: true, maxlength: 255 },
|
||||
bgShape: { type: String, trim: true, maxlength: 255 },
|
||||
planeShape: { type: String, trim: true, maxlength: 255 },
|
||||
topShape: { type: String, trim: true, maxlength: 255 },
|
||||
globeShape: { type: String, trim: true, maxlength: 255 },
|
||||
},
|
||||
items: [
|
||||
new mongoose.Schema(
|
||||
{
|
||||
icon: String,
|
||||
label: String,
|
||||
description: String,
|
||||
icon: { type: String, trim: true, maxlength: 255 },
|
||||
label: { type: String, trim: true, maxlength: 80 },
|
||||
description: { type: String, trim: true, maxlength: 240 },
|
||||
},
|
||||
{ _id: false },
|
||||
),
|
||||
],
|
||||
features: [String],
|
||||
features: [{ type: String, trim: true, maxlength: 80 }],
|
||||
ctaButton: {
|
||||
label: String,
|
||||
href: String,
|
||||
label: { type: String, trim: true, maxlength: 64 },
|
||||
href: { type: String, trim: true, maxlength: 255 },
|
||||
},
|
||||
},
|
||||
features: {
|
||||
backgroundImage: String,
|
||||
subheading: String,
|
||||
heading: String,
|
||||
description: String,
|
||||
image: String,
|
||||
backgroundImage: { type: String, trim: true, maxlength: 255 },
|
||||
subheading: { type: String, trim: true, maxlength: 80 },
|
||||
heading: { type: String, trim: true, maxlength: 120 },
|
||||
description: { type: String, trim: true, maxlength: 1000 },
|
||||
image: { type: String, trim: true, maxlength: 255 },
|
||||
items: [
|
||||
new mongoose.Schema(
|
||||
{
|
||||
icon: String,
|
||||
title: String,
|
||||
description: String,
|
||||
icon: { type: String, trim: true, maxlength: 255 },
|
||||
title: { type: String, trim: true, maxlength: 80 },
|
||||
description: { type: String, trim: true, maxlength: 240 },
|
||||
},
|
||||
{ _id: false },
|
||||
),
|
||||
],
|
||||
ctaButton: {
|
||||
label: String,
|
||||
href: String,
|
||||
label: { type: String, trim: true, maxlength: 64 },
|
||||
href: { type: String, trim: true, maxlength: 255 },
|
||||
},
|
||||
},
|
||||
news: {
|
||||
subheading: String,
|
||||
heading: String,
|
||||
subheading: { type: String, trim: true, maxlength: 80 },
|
||||
heading: { type: String, trim: true, maxlength: 120 },
|
||||
ctaButton: {
|
||||
label: String,
|
||||
href: String,
|
||||
label: { type: String, trim: true, maxlength: 64 },
|
||||
href: { type: String, trim: true, maxlength: 255 },
|
||||
},
|
||||
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,
|
||||
title: { type: String, trim: true, maxlength: 120 },
|
||||
category: { type: String, trim: true, maxlength: 48 },
|
||||
date: { type: String, trim: true, maxlength: 32 },
|
||||
comments: Number,
|
||||
author: {
|
||||
name: String,
|
||||
avatar: String,
|
||||
name: { type: String, trim: true, maxlength: 48 },
|
||||
avatar: { type: String, trim: true, maxlength: 255 },
|
||||
},
|
||||
link: String,
|
||||
thumbnail: String,
|
||||
link: { type: String, trim: true, maxlength: 255 },
|
||||
thumbnail: { type: String, trim: true, maxlength: 255 },
|
||||
},
|
||||
{ _id: false },
|
||||
),
|
||||
|
||||
@@ -7,28 +7,33 @@ const activitySchema = new mongoose.Schema(
|
||||
titleActivities: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: ''
|
||||
default: "",
|
||||
maxlength: 80,
|
||||
},
|
||||
titleBooking: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: ''
|
||||
default: "",
|
||||
maxlength: 80,
|
||||
},
|
||||
bannerImageActivities: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: ''
|
||||
default: "",
|
||||
maxlength: 255,
|
||||
},
|
||||
bannerImageBooking: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: ''
|
||||
default: "",
|
||||
maxlength: 255,
|
||||
},
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
trim: true,
|
||||
maxlength: 120,
|
||||
},
|
||||
price: {
|
||||
type: Number,
|
||||
@@ -38,6 +43,7 @@ const activitySchema = new mongoose.Schema(
|
||||
priceText: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 32,
|
||||
},
|
||||
season: [
|
||||
{
|
||||
@@ -58,25 +64,28 @@ const activitySchema = new mongoose.Schema(
|
||||
{
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 80,
|
||||
},
|
||||
],
|
||||
image: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 255,
|
||||
},
|
||||
link: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 255,
|
||||
},
|
||||
// Global filters document (single document in Activity collection)
|
||||
filters: [
|
||||
{
|
||||
label: { type: String, required: true, trim: true },
|
||||
value: { type: String, required: true, trim: true },
|
||||
label: { type: String, required: true, trim: true, maxlength: 64 },
|
||||
value: { type: String, required: true, trim: true, maxlength: 64 },
|
||||
items: [
|
||||
{
|
||||
value: { type: String, required: true },
|
||||
label: { type: String, required: true },
|
||||
value: { type: String, required: true, maxlength: 64 },
|
||||
label: { type: String, required: true, maxlength: 64 },
|
||||
},
|
||||
],
|
||||
order: { type: Number, default: 0 },
|
||||
@@ -85,6 +94,7 @@ const activitySchema = new mongoose.Schema(
|
||||
program: {
|
||||
type: String,
|
||||
trim: true,
|
||||
maxlength: 80,
|
||||
},
|
||||
rating: {
|
||||
type: Number,
|
||||
@@ -113,7 +123,7 @@ const activitySchema = new mongoose.Schema(
|
||||
// Booking sessions - các đợt booking với thông số riêng
|
||||
bookingSessions: [
|
||||
{
|
||||
sessionId: { type: String, required: true },
|
||||
sessionId: { type: String, required: true, maxlength: 80 },
|
||||
startDate: { type: Date, required: true },
|
||||
endDate: { type: Date, required: true },
|
||||
overnightStays: { type: Number, required: true, default: 14 },
|
||||
@@ -127,11 +137,11 @@ const activitySchema = new mongoose.Schema(
|
||||
// Danh sách booking cho session này
|
||||
bookingList: [
|
||||
{
|
||||
address: { type: String, required: true },
|
||||
address: { type: String, required: true, maxlength: 255 },
|
||||
agreeNewsletter: { type: Boolean, default: false },
|
||||
agreeTerms: { type: Boolean, required: true },
|
||||
city: { type: String, required: true },
|
||||
country: { type: String, required: true },
|
||||
city: { type: String, required: true, maxlength: 80 },
|
||||
country: { type: String, required: true, maxlength: 80 },
|
||||
dietaryRestrictions: {
|
||||
type: String,
|
||||
enum: ['none', 'vegetarian', 'vegan', 'halal', 'kosher', 'gluten-free', 'other'],
|
||||
@@ -141,26 +151,27 @@ const activitySchema = new mongoose.Schema(
|
||||
type: String,
|
||||
required: true,
|
||||
lowercase: true,
|
||||
trim: true
|
||||
trim: true,
|
||||
maxlength: 120
|
||||
},
|
||||
emergencyContact: { type: String, required: true },
|
||||
emergencyPhone: { type: String, required: true },
|
||||
medicalConditions: { type: String, default: '' },
|
||||
emergencyContact: { type: String, required: true, maxlength: 80 },
|
||||
emergencyPhone: { type: String, required: true, maxlength: 40 },
|
||||
medicalConditions: { type: String, default: '', maxlength: 500 },
|
||||
numberOfParticipants: { type: Number, required: true, min: 1 },
|
||||
parentFirstName: { type: String, required: true, trim: true },
|
||||
parentLastName: { type: String, required: true, trim: true },
|
||||
parentFirstName: { type: String, required: true, trim: true, maxlength: 80 },
|
||||
parentLastName: { type: String, required: true, trim: true, maxlength: 80 },
|
||||
participantBirthDate: { type: Date, required: true },
|
||||
participantFirstName: { type: String, required: true, trim: true },
|
||||
participantFirstName: { type: String, required: true, trim: true, maxlength: 80 },
|
||||
participantGender: {
|
||||
type: String,
|
||||
enum: ['male', 'female', 'other'],
|
||||
required: true
|
||||
},
|
||||
participantLastName: { type: String, required: true, trim: true },
|
||||
phone: { type: String, required: true },
|
||||
postalCode: { type: String, required: true },
|
||||
sessionDate: { type: String, required: true }, // sessionId reference
|
||||
specialRequests: { type: String, default: '' },
|
||||
participantLastName: { type: String, required: true, trim: true, maxlength: 80 },
|
||||
phone: { type: String, required: true, maxlength: 40 },
|
||||
postalCode: { type: String, required: true, maxlength: 20 },
|
||||
sessionDate: { type: String, required: true, maxlength: 80 }, // sessionId reference
|
||||
specialRequests: { type: String, default: '', maxlength: 500 },
|
||||
// Thêm các trường quản lý
|
||||
bookingStatus: {
|
||||
type: String,
|
||||
@@ -175,8 +186,8 @@ const activitySchema = new mongoose.Schema(
|
||||
totalAmount: { type: Number, default: 0 },
|
||||
paidAmount: { type: Number, default: 0 },
|
||||
bookingDate: { type: Date, default: Date.now },
|
||||
confirmationCode: { type: String, unique: true },
|
||||
adminNotes: { type: String, default: '' }
|
||||
confirmationCode: { type: String, unique: true, maxlength: 32 },
|
||||
adminNotes: { type: String, default: '', maxlength: 1000 }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -11,70 +11,70 @@ if (mongoose.connection.models.Booking) {
|
||||
const bookingSchema = new mongoose.Schema(
|
||||
{
|
||||
hero: {
|
||||
title: String,
|
||||
backgroundImage: String,
|
||||
title: { type: String, trim: true, maxlength: 80 },
|
||||
backgroundImage: { type: String, trim: true, maxlength: 255 },
|
||||
},
|
||||
|
||||
searchBar: {
|
||||
locationLabel: String,
|
||||
holidaySeasonLabel: String,
|
||||
searchButtonText: String,
|
||||
locationLabel: { type: String, trim: true, maxlength: 64 },
|
||||
holidaySeasonLabel: { type: String, trim: true, maxlength: 64 },
|
||||
searchButtonText: { type: String, trim: true, maxlength: 64 },
|
||||
},
|
||||
|
||||
filterPanel: {
|
||||
title: String,
|
||||
priceTitle: String,
|
||||
priceLabel: String,
|
||||
pricePlaceholder: String,
|
||||
title: { type: String, trim: true, maxlength: 80 },
|
||||
priceTitle: { type: String, trim: true, maxlength: 64 },
|
||||
priceLabel: { type: String, trim: true, maxlength: 64 },
|
||||
pricePlaceholder: { type: String, trim: true, maxlength: 64 },
|
||||
priceMin: Number,
|
||||
priceMax: Number,
|
||||
activitiesTitle: String,
|
||||
ageTitle: String,
|
||||
ageSelectPlaceholder: String,
|
||||
activitiesTitle: { type: String, trim: true, maxlength: 64 },
|
||||
ageTitle: { type: String, trim: true, maxlength: 64 },
|
||||
ageSelectPlaceholder: { type: String, trim: true, maxlength: 64 },
|
||||
ageMin: Number,
|
||||
ageMax: Number,
|
||||
ratingTitle: String,
|
||||
ratingTitle: { type: String, trim: true, maxlength: 64 },
|
||||
ratingOptions: [
|
||||
{
|
||||
value: String,
|
||||
label: String,
|
||||
value: { type: String, trim: true, maxlength: 48 },
|
||||
label: { type: String, trim: true, maxlength: 64 },
|
||||
},
|
||||
],
|
||||
resetButtonText: String,
|
||||
resetButtonText: { type: String, trim: true, maxlength: 64 },
|
||||
},
|
||||
|
||||
programs: [
|
||||
{
|
||||
value: String,
|
||||
label: String,
|
||||
value: { type: String, trim: true, maxlength: 64 },
|
||||
label: { type: String, trim: true, maxlength: 64 },
|
||||
},
|
||||
],
|
||||
|
||||
holidays: [
|
||||
{
|
||||
value: String,
|
||||
label: String,
|
||||
value: { type: String, trim: true, maxlength: 64 },
|
||||
label: { type: String, trim: true, maxlength: 64 },
|
||||
},
|
||||
],
|
||||
|
||||
locations: [
|
||||
{
|
||||
value: String,
|
||||
label: String,
|
||||
value: { type: String, trim: true, maxlength: 64 },
|
||||
label: { type: String, trim: true, maxlength: 64 },
|
||||
},
|
||||
],
|
||||
|
||||
camps: [
|
||||
{
|
||||
name: String,
|
||||
name: { type: String, trim: true, maxlength: 120 },
|
||||
price: Number,
|
||||
priceText: String,
|
||||
priceText: { type: String, trim: true, maxlength: 32 },
|
||||
season: [String],
|
||||
age: [Number],
|
||||
locations: [String],
|
||||
image: String,
|
||||
link: String,
|
||||
program: String,
|
||||
image: { type: String, trim: true, maxlength: 255 },
|
||||
link: { type: String, trim: true, maxlength: 255 },
|
||||
program: { type: String, trim: true, maxlength: 80 },
|
||||
rating: Number,
|
||||
},
|
||||
],
|
||||
@@ -103,4 +103,4 @@ const bookingSchema = new mongoose.Schema(
|
||||
}
|
||||
);
|
||||
|
||||
module.exports = mongoose.model("Booking", bookingSchema);
|
||||
module.exports = mongoose.model("Booking", bookingSchema);
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -31,6 +31,10 @@ const HeaderMenuSchema = new mongoose.Schema({
|
||||
enum: ['active', 'inactive'],
|
||||
default: 'active'
|
||||
},
|
||||
is_maintainance: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['internal', 'external'],
|
||||
@@ -43,6 +47,7 @@ const HeaderMenuSchema = new mongoose.Schema({
|
||||
// Indexes for optimization
|
||||
HeaderMenuSchema.index({ order: 1 });
|
||||
HeaderMenuSchema.index({ status: 1 });
|
||||
HeaderMenuSchema.index({ is_maintainance: 1 });
|
||||
HeaderMenuSchema.index({ parentId: 1, order: 1 });
|
||||
|
||||
module.exports = mongoose.model('HeaderMenu', HeaderMenuSchema);
|
||||
|
||||
138
models/home.js
138
models/home.js
@@ -5,8 +5,53 @@ 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: "", maxlength: 255 },
|
||||
imageAlt: { type: String, default: "", maxlength: 60 },
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const FloatingContactTriggerSchema = new Schema(
|
||||
{
|
||||
imageSrc: { type: String, default: "", maxlength: 255 },
|
||||
icon: { type: String, default: "fa-comments", maxlength: 64 },
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const FloatingContactActionSchema = new Schema(
|
||||
{
|
||||
id: { type: String, default: "" },
|
||||
platform: { type: String, default: "" },
|
||||
enabled: { type: Boolean, default: true },
|
||||
label: { type: String, default: "", maxlength: 48 },
|
||||
subtitle: { type: String, default: "", maxlength: 48 },
|
||||
href: { type: String, default: "", maxlength: 255 },
|
||||
iconImage: { type: String, default: "", maxlength: 255 },
|
||||
iconType: { type: String, default: "iconClass" },
|
||||
iconClass: { type: String, default: "", maxlength: 120 },
|
||||
iconText: { type: String, default: "", maxlength: 12 },
|
||||
order: { type: Number, default: 0 },
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
|
||||
const FloatingContactSchema = new Schema(
|
||||
{
|
||||
enabled: { type: Boolean, default: true },
|
||||
position: { type: String, default: "bottom-right" },
|
||||
panelTitle: { type: String, default: "", maxlength: 72 },
|
||||
brand: { type: FloatingContactBrandSchema, default: () => ({}) },
|
||||
trigger: { type: FloatingContactTriggerSchema, default: () => ({}) },
|
||||
actions: { type: [FloatingContactActionSchema], default: [] },
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
@@ -14,13 +59,13 @@ const LinkSchema = 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 },
|
||||
);
|
||||
@@ -31,28 +76,28 @@ const HeroSchema = new Schema(
|
||||
enabled: { type: Boolean, default: true },
|
||||
|
||||
// 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 },
|
||||
);
|
||||
@@ -69,7 +114,7 @@ const WhyChooseUsSchema = new Schema(
|
||||
mainImage: { type: String, default: "" },
|
||||
secondaryImage: { type: String, default: "" },
|
||||
items: { type: [WhyChooseUsItemSchema], default: [] },
|
||||
features: { type: [String], default: [] },
|
||||
features: { type: [{ type: String, maxlength: 96 }], default: [] },
|
||||
ctaButton: { type: LinkSchema, default: () => ({}) },
|
||||
},
|
||||
{ _id: false },
|
||||
@@ -77,10 +122,10 @@ 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 },
|
||||
);
|
||||
@@ -99,11 +144,11 @@ 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 },
|
||||
);
|
||||
@@ -124,12 +169,12 @@ 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 },
|
||||
);
|
||||
@@ -162,8 +207,8 @@ const VideoGallerySchema = new Schema(
|
||||
|
||||
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 },
|
||||
);
|
||||
@@ -184,10 +229,10 @@ 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 },
|
||||
);
|
||||
@@ -206,9 +251,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 },
|
||||
);
|
||||
@@ -222,7 +267,7 @@ const VisaConsultancySchema = new Schema(
|
||||
|
||||
const BrandItemSchema = new Schema(
|
||||
{
|
||||
logo: { type: String, default: "" },
|
||||
logo: { type: String, default: "", maxlength: 255 },
|
||||
},
|
||||
{ _id: false },
|
||||
);
|
||||
@@ -247,16 +292,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 },
|
||||
@@ -296,6 +341,7 @@ const HomeSchema = new Schema(
|
||||
achievements: { type: AchievementsSchema, default: () => ({}) },
|
||||
partners: { type: PartnersSchema, default: () => ({}) },
|
||||
blogPreview: { type: BlogPreviewSchema, default: () => ({}) },
|
||||
floatingContact: { type: FloatingContactSchema, default: () => ({}) },
|
||||
},
|
||||
{
|
||||
timestamps: true,
|
||||
|
||||
@@ -15,11 +15,13 @@ const breadcrumbItemSchema = new mongoose.Schema(
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "",
|
||||
maxlength: 40,
|
||||
},
|
||||
link: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "",
|
||||
maxlength: 255,
|
||||
},
|
||||
},
|
||||
{ _id: false }
|
||||
@@ -32,16 +34,19 @@ const heroSchema = new mongoose.Schema(
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "Pricing Plan",
|
||||
maxlength: 60,
|
||||
},
|
||||
backgroundImage: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "/assets/img/inner-page/breadcrumb.jpg",
|
||||
maxlength: 255,
|
||||
},
|
||||
shapeImage: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "/assets/img/inner-page/shape.png",
|
||||
maxlength: 255,
|
||||
},
|
||||
breadcrumb: {
|
||||
type: [breadcrumbItemSchema],
|
||||
@@ -58,16 +63,19 @@ const pricingSectionSchema = new mongoose.Schema(
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "pricing plan",
|
||||
maxlength: 64,
|
||||
},
|
||||
heading: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "Flexible Plans to Suit Every Traveler",
|
||||
maxlength: 120,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "",
|
||||
maxlength: 500,
|
||||
},
|
||||
},
|
||||
{ _id: false }
|
||||
@@ -80,36 +88,43 @@ const planSchema = new mongoose.Schema(
|
||||
type: String,
|
||||
trim: true,
|
||||
required: true,
|
||||
maxlength: 64,
|
||||
},
|
||||
price: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "0",
|
||||
maxlength: 32,
|
||||
},
|
||||
period: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "mo",
|
||||
maxlength: 8,
|
||||
},
|
||||
currency: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "$",
|
||||
maxlength: 8,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "Get Started Today",
|
||||
maxlength: 64,
|
||||
},
|
||||
buttonLink: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "/pricing",
|
||||
maxlength: 255,
|
||||
},
|
||||
buttonIcon: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "fa-solid fa-arrow-right",
|
||||
maxlength: 64,
|
||||
},
|
||||
style: {
|
||||
type: String,
|
||||
@@ -118,7 +133,7 @@ const planSchema = new mongoose.Schema(
|
||||
default: "default",
|
||||
},
|
||||
features: {
|
||||
type: [String],
|
||||
type: [{ type: String, maxlength: 96 }],
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
@@ -147,11 +162,13 @@ const testimonialItemSchema = new mongoose.Schema(
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "",
|
||||
maxlength: 64,
|
||||
},
|
||||
role: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "",
|
||||
maxlength: 64,
|
||||
},
|
||||
rating: {
|
||||
type: Number,
|
||||
@@ -163,6 +180,7 @@ const testimonialItemSchema = new mongoose.Schema(
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "",
|
||||
maxlength: 400,
|
||||
},
|
||||
},
|
||||
{ _id: false }
|
||||
@@ -175,31 +193,37 @@ const testimonialsSchema = new mongoose.Schema(
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "What Our Clients Say",
|
||||
maxlength: 64,
|
||||
},
|
||||
heading: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "Immigration Success Stories",
|
||||
maxlength: 120,
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "View All Review",
|
||||
maxlength: 64,
|
||||
},
|
||||
buttonLink: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "/contact",
|
||||
maxlength: 255,
|
||||
},
|
||||
buttonIcon: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "fa-solid fa-arrow-right",
|
||||
maxlength: 64,
|
||||
},
|
||||
image: {
|
||||
type: String,
|
||||
trim: true,
|
||||
default: "",
|
||||
maxlength: 255,
|
||||
},
|
||||
items: {
|
||||
type: [testimonialItemSchema],
|
||||
|
||||
Reference in New Issue
Block a user