Add landing page
This commit is contained in:
parent
3fea484585
commit
bce86ee840
47 changed files with 7119 additions and 439 deletions
|
|
@ -1,35 +1,173 @@
|
|||
import mongoose from 'mongoose'
|
||||
import mongoose from "mongoose";
|
||||
|
||||
const seriesSchema = new mongoose.Schema({
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
validate: {
|
||||
validator: function(v) {
|
||||
return /^[a-z0-9-]+$/.test(v);
|
||||
},
|
||||
message: 'Series ID must contain only lowercase letters, numbers, and dashes'
|
||||
}
|
||||
},
|
||||
id: { type: String, required: true, unique: true }, // Simple string ID (e.g., "coop-game-dev-2025")
|
||||
title: { type: String, required: true },
|
||||
description: { type: String, required: true },
|
||||
slug: { type: String, unique: true }, // Auto-generated in pre-save hook
|
||||
description: String,
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['workshop_series', 'recurring_meetup', 'multi_day', 'course', 'tournament'],
|
||||
default: 'workshop_series'
|
||||
enum: [
|
||||
"workshop_series",
|
||||
"recurring_meetup",
|
||||
"multi_day",
|
||||
"course",
|
||||
"tournament",
|
||||
],
|
||||
default: "workshop_series",
|
||||
},
|
||||
totalEvents: Number,
|
||||
// Visibility and status
|
||||
isVisible: { type: Boolean, default: true },
|
||||
isActive: { type: Boolean, default: true },
|
||||
// Date range (calculated from events or set manually)
|
||||
startDate: Date,
|
||||
endDate: Date,
|
||||
// Series ticketing configuration
|
||||
tickets: {
|
||||
enabled: { type: Boolean, default: false },
|
||||
requiresSeriesTicket: { type: Boolean, default: false }, // If true, must buy series pass
|
||||
allowIndividualEventTickets: { type: Boolean, default: true }, // Allow drop-in for individual events
|
||||
currency: { type: String, default: "CAD" },
|
||||
// Member series pass configuration
|
||||
member: {
|
||||
available: { type: Boolean, default: true },
|
||||
isFree: { type: Boolean, default: true },
|
||||
price: { type: Number, default: 0 },
|
||||
name: { type: String, default: "Member Series Pass" },
|
||||
description: String,
|
||||
// Circle-specific overrides
|
||||
circleOverrides: {
|
||||
community: {
|
||||
isFree: { type: Boolean },
|
||||
price: { type: Number },
|
||||
},
|
||||
founder: {
|
||||
isFree: { type: Boolean },
|
||||
price: { type: Number },
|
||||
},
|
||||
practitioner: {
|
||||
isFree: { type: Boolean },
|
||||
price: { type: Number },
|
||||
},
|
||||
},
|
||||
},
|
||||
// Public (non-member) series pass configuration
|
||||
public: {
|
||||
available: { type: Boolean, default: false },
|
||||
name: { type: String, default: "Series Pass" },
|
||||
description: String,
|
||||
price: { type: Number, default: 0 },
|
||||
quantity: Number, // null/undefined = unlimited
|
||||
sold: { type: Number, default: 0 },
|
||||
reserved: { type: Number, default: 0 },
|
||||
earlyBirdPrice: Number,
|
||||
earlyBirdDeadline: Date,
|
||||
},
|
||||
// Series-wide capacity
|
||||
capacity: {
|
||||
total: Number, // null/undefined = unlimited
|
||||
reserved: { type: Number, default: 0 },
|
||||
},
|
||||
// Waitlist configuration
|
||||
waitlist: {
|
||||
enabled: { type: Boolean, default: false },
|
||||
maxSize: Number,
|
||||
entries: [
|
||||
{
|
||||
name: String,
|
||||
email: String,
|
||||
membershipLevel: String,
|
||||
addedAt: { type: Date, default: Date.now },
|
||||
notified: { type: Boolean, default: false },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
// Series pass purchases (registrations)
|
||||
registrations: [
|
||||
{
|
||||
memberId: { type: mongoose.Schema.Types.ObjectId, ref: "Member" },
|
||||
name: String,
|
||||
email: String,
|
||||
membershipLevel: String,
|
||||
isMember: { type: Boolean, default: false },
|
||||
// Ticket information
|
||||
ticketType: {
|
||||
type: String,
|
||||
enum: ["member", "public", "guest"],
|
||||
default: "guest",
|
||||
},
|
||||
ticketPrice: { type: Number, default: 0 },
|
||||
// Payment information
|
||||
paymentStatus: {
|
||||
type: String,
|
||||
enum: ["pending", "completed", "failed", "refunded", "not_required"],
|
||||
default: "not_required",
|
||||
},
|
||||
paymentId: String, // Helcim transaction ID
|
||||
amountPaid: { type: Number, default: 0 },
|
||||
// Metadata
|
||||
registeredAt: { type: Date, default: Date.now },
|
||||
cancelledAt: Date,
|
||||
refundedAt: Date,
|
||||
refundAmount: Number,
|
||||
// Events they've been registered for (references)
|
||||
eventRegistrations: [
|
||||
{
|
||||
eventId: { type: mongoose.Schema.Types.ObjectId, ref: "Event" },
|
||||
registrationId: mongoose.Schema.Types.ObjectId, // ID of registration in event.registrations
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
// Target circles
|
||||
targetCircles: [
|
||||
{
|
||||
type: String,
|
||||
enum: ["community", "founder", "practitioner"],
|
||||
},
|
||||
],
|
||||
// Metadata
|
||||
totalEvents: { type: Number, default: 0 }, // Number of events in this series
|
||||
createdBy: { type: String, required: true },
|
||||
createdAt: { type: Date, default: Date.now },
|
||||
updatedAt: { type: Date, default: Date.now }
|
||||
})
|
||||
updatedAt: { type: Date, default: Date.now },
|
||||
});
|
||||
|
||||
// Update the updatedAt field on save
|
||||
seriesSchema.pre('save', function(next) {
|
||||
this.updatedAt = new Date()
|
||||
next()
|
||||
})
|
||||
// Generate slug from title
|
||||
function generateSlug(title) {
|
||||
return title
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
export default mongoose.models.Series || mongoose.model('Series', seriesSchema)
|
||||
// Pre-save hook to generate slug
|
||||
seriesSchema.pre("save", async function (next) {
|
||||
try {
|
||||
// Always generate slug if it doesn't exist or if title has changed
|
||||
if (!this.slug || this.isNew || this.isModified("title")) {
|
||||
let baseSlug = generateSlug(this.title);
|
||||
let slug = baseSlug;
|
||||
let counter = 1;
|
||||
|
||||
// Ensure slug is unique
|
||||
while (await this.constructor.findOne({ slug, _id: { $ne: this._id } })) {
|
||||
slug = `${baseSlug}-${counter}`;
|
||||
counter++;
|
||||
}
|
||||
|
||||
this.slug = slug;
|
||||
}
|
||||
|
||||
// Update timestamps
|
||||
this.updatedAt = new Date();
|
||||
|
||||
next();
|
||||
} catch (error) {
|
||||
console.error("Error in pre-save hook:", error);
|
||||
next(error);
|
||||
}
|
||||
});
|
||||
|
||||
export default mongoose.models.Series || mongoose.model("Series", seriesSchema);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue