Enhance application structure: Add runtime configuration for environment variables, integrate new dependencies for Cloudinary and UI components, and refactor member management features including improved forms and member dashboard. Update styles and layout for better user experience.

This commit is contained in:
Jennie Robinson Faber 2025-08-27 16:49:51 +01:00
parent 6e7e27ac4e
commit e4a0a9ab0f
61 changed files with 7902 additions and 950 deletions

View file

@ -0,0 +1,66 @@
import mongoose from 'mongoose'
import Event from '../server/models/event.js'
import { connectDB } from '../server/utils/mongoose.js'
// Generate slug from title
function generateSlug(title) {
return title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
}
async function migrateEventSlugs() {
try {
// Connect to database
await connectDB()
console.log('Connected to database')
// Find all events without slugs
const eventsWithoutSlugs = await Event.find({
$or: [
{ slug: { $exists: false } },
{ slug: null },
{ slug: '' }
]
})
console.log(`Found ${eventsWithoutSlugs.length} events without slugs`)
if (eventsWithoutSlugs.length === 0) {
console.log('All events already have slugs!')
return
}
// Generate and assign unique slugs
for (const event of eventsWithoutSlugs) {
let baseSlug = generateSlug(event.title)
let slug = baseSlug
let counter = 1
// Ensure slug is unique
while (await Event.findOne({ slug, _id: { $ne: event._id } })) {
slug = `${baseSlug}-${counter}`
counter++
}
event.slug = slug
await event.save({ validateBeforeSave: false }) // Skip validation to avoid pre-save hook
console.log(`✓ Generated slug "${slug}" for event "${event.title}"`)
}
console.log(`Successfully migrated ${eventsWithoutSlugs.length} events!`)
} catch (error) {
console.error('Error migrating event slugs:', error)
} finally {
await mongoose.connection.close()
console.log('Database connection closed')
}
}
// Run migration if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
migrateEventSlugs()
}
export default migrateEventSlugs

44
scripts/seed-all.js Normal file
View file

@ -0,0 +1,44 @@
import mongoose from 'mongoose'
import { connectDB } from '../server/utils/mongoose.js'
import dotenv from 'dotenv'
// Load environment variables
dotenv.config()
// Import seed functions
import { execSync } from 'child_process'
async function seedAll() {
try {
console.log('🌱 Starting database seeding...\n')
// Seed members
console.log('👥 Seeding members...')
execSync('node scripts/seed-members.js', { stdio: 'inherit' })
console.log('\n🎉 Seeding events...')
execSync('node scripts/seed-events.js', { stdio: 'inherit' })
console.log('\n✅ All data seeded successfully!')
console.log('\n📊 Database Summary:')
// Connect and show final counts
await connectDB()
const Member = (await import('../server/models/member.js')).default
const Event = (await import('../server/models/event.js')).default
const memberCount = await Member.countDocuments()
const eventCount = await Event.countDocuments()
console.log(` Members: ${memberCount}`)
console.log(` Events: ${eventCount}`)
process.exit(0)
} catch (error) {
console.error('❌ Error seeding database:', error)
process.exit(1)
}
}
seedAll()

282
scripts/seed-events.js Normal file
View file

@ -0,0 +1,282 @@
import mongoose from 'mongoose'
import Event from '../server/models/event.js'
import { connectDB } from '../server/utils/mongoose.js'
import dotenv from 'dotenv'
// Load environment variables
dotenv.config()
// Generate future dates relative to today
const today = new Date()
const nextMonth = new Date(today)
nextMonth.setMonth(nextMonth.getMonth() + 1)
// Generate slug from title
function generateSlug(title) {
return title
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
}
const sampleEvents = [
{
title: 'Monthly Community Meetup',
tagline: 'Connect, share, and learn with fellow cooperative game developers',
description: 'Join us for our monthly community gathering where developers share experiences, discuss cooperative models, and network with fellow members.',
featureImage: {
url: 'https://images.unsplash.com/photo-1556761175-b413da4baf72?w=1200&h=400&fit=crop',
publicId: 'samples/community-meetup',
alt: 'Developers collaborating at a community meetup'
},
content: 'This informal meetup is perfect for connecting with other developers interested in cooperative business models. We\'ll have brief presentations, open discussions, and time for networking.\n\nAgenda:\n- Welcome & introductions\n- Member spotlight presentations\n- Open discussion on cooperative challenges and successes\n- Networking and social time',
startDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7, 19, 0),
endDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 7, 21, 0),
eventType: 'community',
location: '#general',
isOnline: true,
membersOnly: false,
isVisible: true,
isCancelled: false,
targetCircles: ['community', 'founder'],
maxAttendees: 50,
registrationRequired: true,
registrationDeadline: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 5, 23, 59),
agenda: [
'Welcome & introductions (15 min)',
'Member spotlight presentations (30 min)',
'Open discussion on cooperative challenges and successes (45 min)',
'Networking and social time (30 min)'
],
createdBy: 'admin@ghostguild.org'
},
{
title: 'Cooperative Business Structures Workshop',
tagline: 'Learn how to structure and run a successful game development cooperative',
description: 'An in-depth workshop covering the legal and practical aspects of forming and operating a cooperative business.',
featureImage: {
url: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=1200&h=400&fit=crop',
publicId: 'samples/business-workshop',
alt: 'Business planning workshop with charts and collaboration'
},
content: 'Learn the fundamentals of cooperative business structures, including legal requirements, governance models, and financial considerations.\n\nTopics covered:\n- Types of cooperative structures\n- Legal requirements and incorporation process\n- Democratic governance and decision-making\n- Profit sharing and member equity\n- Case studies from successful game development cooperatives',
startDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 14, 14, 0),
endDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 14, 17, 0),
eventType: 'workshop',
location: 'https://zoom.us/j/123456789',
isOnline: true,
membersOnly: false,
isVisible: true,
isCancelled: false,
targetCircles: ['founder', 'practitioner'],
maxAttendees: 30,
agenda: [
'Introduction to Cooperative Business Models (30 min)',
'Legal Structures and Formation (45 min)',
'Democratic Governance in Creative Teams (45 min)',
'Break (15 min)',
'Financial Management and Profit Sharing (45 min)',
'Case Studies: Successful Game Co-ops (30 min)',
'Q&A and Networking (30 min)'
],
speakers: [
{
name: 'Alex Rivera',
role: 'Co-founder, Pixel Collective',
bio: '10+ years running a successful game development co-op'
},
{
name: 'Sam Chen',
role: 'Legal Advisor',
bio: 'Specializes in cooperative business law and formation'
}
],
registrationRequired: true,
registrationDeadline: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 12, 23, 59),
createdBy: 'admin@ghostguild.org'
},
{
title: 'Game Development Co-op Showcase',
tagline: 'See what our member studios are creating',
description: 'Member studios present their latest projects and share insights about developing games in a cooperative environment.',
featureImage: {
url: 'https://images.unsplash.com/photo-1511512578047-dfb367046420?w=1200&h=400&fit=crop',
publicId: 'samples/game-showcase',
alt: 'Game development showcase with screens displaying games'
},
content: 'Our quarterly showcase featuring presentations from Ghost Guild member studios. Learn about ongoing projects, cooperative development processes, and the unique challenges and benefits of collaborative game creation.\n\nFeatured presentations:\n- "Collaborative Level Design in Practice"\n- "Democratic Decision Making in Creative Projects"\n- "Balancing Individual Creativity with Group Consensus"\n- Q&A with presenting studios',
startDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 21, 18, 30),
endDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 21, 21, 0),
eventType: 'showcase',
location: '#showcase',
isOnline: true,
membersOnly: true,
isVisible: true,
isCancelled: false,
targetCircles: ['founder', 'practitioner'],
maxAttendees: 75,
agenda: [
'Welcome and introductions (15 min)',
'Studio presentation: Collaborative Level Design in Practice (20 min)',
'Studio presentation: Democratic Decision Making in Creative Projects (20 min)',
'Studio presentation: Balancing Individual Creativity with Group Consensus (20 min)',
'Panel Q&A with presenting studios (30 min)',
'Networking and demos (45 min)'
],
registrationRequired: true,
registrationDeadline: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 19, 23, 59),
createdBy: 'admin@ghostguild.org'
},
{
title: 'New Year Social Mixer',
tagline: 'Celebrate and connect with the Ghost Guild community',
description: 'Celebrate the new year with fellow Ghost Guild members in a relaxed social atmosphere.',
content: 'Join us for a casual evening of celebration, networking, and community building. Perfect for new members to meet the community and for existing members to catch up.\n\nActivities:\n- Welcome reception\n- Casual networking\n- Community achievements celebration\n- Light refreshments provided\n- Optional lightning talks (5 min, informal)',
startDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 3, 18, 0),
endDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 3, 21, 0),
eventType: 'social',
location: '#social',
isOnline: true,
membersOnly: true,
isVisible: true,
isCancelled: false,
targetCircles: ['community', 'founder', 'practitioner'],
registrationRequired: false,
createdBy: 'admin@ghostguild.org'
},
{
title: 'Funding Your Game Co-op Panel',
tagline: 'Explore funding strategies for cooperative game studios',
description: 'Panel discussion with successful co-op founders and supporting investors about funding strategies for cooperative game studios.',
content: 'Learn about different funding approaches for cooperative game development studios from those who have successfully navigated the process.\n\nPanelists:\n- Founder of successful indie co-op studio\n- Impact investor specializing in cooperative businesses\n- Grant specialist for creative cooperatives\n- Community development financial institution representative\n\nTopics:\n- Traditional vs. alternative funding models\n- Grant opportunities for cooperatives\n- Community-supported development\n- Investor relations in cooperative structures',
startDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 28, 19, 0),
endDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 28, 21, 0),
eventType: 'workshop',
location: 'https://meet.google.com/abc-defg-hij',
isOnline: true,
membersOnly: false,
isVisible: true,
isCancelled: false,
targetCircles: ['founder', 'practitioner'],
maxAttendees: 100,
speakers: [
{
name: 'Maria Garcia',
role: 'Founder, Collective Games Studio',
bio: 'Successfully raised $500K for her cooperative game studio'
},
{
name: 'David Park',
role: 'Impact Investor',
bio: 'Specializes in funding cooperative and social enterprises'
},
{
name: 'Jennifer Wu',
role: 'Grant Specialist',
bio: 'Helped secure over $2M in grants for creative cooperatives'
}
],
registrationRequired: true,
registrationDeadline: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 26, 23, 59),
createdBy: 'admin@ghostguild.org'
},
{
title: 'Intro to Game Cooperatives',
tagline: 'Learn the basics of cooperative game development',
description: 'A beginner-friendly introduction to cooperative business models in game development.',
content: 'Perfect for developers who are curious about cooperatives but don\'t know where to start. We\'ll cover the basics of what makes a cooperative different, the benefits and challenges, and how to get started.\n\nTopics covered:\n- What is a cooperative?\n- Benefits of the cooperative model\n- Common challenges and solutions\n- Resources for learning more\n- Q&A session',
startDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 10, 18, 0),
endDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 10, 20, 0),
eventType: 'workshop',
location: 'https://teams.microsoft.com/meet/123456',
isOnline: true,
membersOnly: false,
isVisible: true,
isCancelled: false,
targetCircles: ['community'],
maxAttendees: 50,
registrationRequired: true,
registrationDeadline: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 8, 23, 59),
agenda: [
'Welcome and introductions (10 min)',
'What is a cooperative? (20 min)',
'Benefits of the cooperative model (20 min)',
'Common challenges and solutions (20 min)',
'Getting started with your co-op (20 min)',
'Resources and next steps (15 min)',
'Q&A session (15 min)'
],
createdBy: 'admin@ghostguild.org'
},
{
title: 'Advanced Co-op Leadership Workshop',
tagline: 'Deep dive into cooperative leadership principles',
description: 'An advanced workshop for experienced cooperative practitioners exploring leadership models.',
content: 'This workshop is designed for practitioners who have experience with cooperative models and want to develop their leadership skills.',
startDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 35, 14, 0),
endDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 35, 17, 0),
eventType: 'workshop',
location: '#private-workshops',
isOnline: true,
membersOnly: true,
isVisible: false, // Hidden from public calendar
isCancelled: false,
targetCircles: ['practitioner'],
maxAttendees: 15,
registrationRequired: true,
registrationDeadline: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 33, 23, 59),
createdBy: 'admin@ghostguild.org'
},
{
title: 'Game Development Co-op Meetup - February',
tagline: 'CANCELLED - Will be rescheduled',
description: 'Monthly community meetup - this session has been cancelled due to scheduling conflicts.',
content: 'Our February meetup has been cancelled but will be rescheduled soon. Stay tuned for updates!',
startDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 42, 19, 0),
endDate: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 42, 21, 0),
eventType: 'community',
location: '#general',
isOnline: true,
membersOnly: false,
isVisible: true,
isCancelled: true,
cancellationMessage: 'This meetup has been cancelled due to speaker availability conflicts. We are working to reschedule it for early March with our originally planned speakers. All registered participants will be notified as soon as the new date is confirmed.',
targetCircles: ['community', 'founder'],
maxAttendees: 50,
registrationRequired: true,
registrationDeadline: new Date(today.getFullYear(), today.getMonth(), today.getDate() + 40, 23, 59),
createdBy: 'admin@ghostguild.org'
}
]
async function seedEvents() {
try {
await connectDB()
// Clear existing events
await Event.deleteMany({})
console.log('Cleared existing events')
// Insert sample events one by one with generated slugs
for (let eventData of sampleEvents) {
// Add slug if not present
if (!eventData.slug) {
eventData.slug = generateSlug(eventData.title)
}
const event = new Event(eventData)
await event.save()
}
console.log(`Added ${sampleEvents.length} sample events`)
// Verify insertion
const count = await Event.countDocuments()
console.log(`Total events in database: ${count}`)
process.exit(0)
} catch (error) {
console.error('Error seeding events:', error)
process.exit(1)
}
}
seedEvents()

199
scripts/seed-members.js Normal file
View file

@ -0,0 +1,199 @@
import mongoose from 'mongoose'
import Member from '../server/models/member.js'
import { connectDB } from '../server/utils/mongoose.js'
import dotenv from 'dotenv'
// Load environment variables
dotenv.config()
const sampleMembers = [
{
email: 'alex.rivera@pixelcollective.coop',
name: 'Alex Rivera',
circle: 'founder',
contributionTier: '50',
slackInvited: true,
createdAt: new Date('2024-01-15'),
lastLogin: new Date('2025-08-20')
},
{
email: 'sam.chen@legalcoop.com',
name: 'Sam Chen',
circle: 'practitioner',
contributionTier: '30',
slackInvited: true,
createdAt: new Date('2024-02-03'),
lastLogin: new Date('2025-08-18')
},
{
email: 'maria.garcia@collectivegames.coop',
name: 'Maria Garcia',
circle: 'founder',
contributionTier: '50',
helcimCustomerId: 'cust_12345',
helcimSubscriptionId: 'sub_67890',
slackInvited: true,
createdAt: new Date('2024-03-10'),
lastLogin: new Date('2025-08-25')
},
{
email: 'david.park@impactinvest.org',
name: 'David Park',
circle: 'practitioner',
contributionTier: '30',
slackInvited: true,
createdAt: new Date('2024-04-12'),
lastLogin: new Date('2025-08-22')
},
{
email: 'jennifer.wu@grantspecialist.org',
name: 'Jennifer Wu',
circle: 'practitioner',
contributionTier: '15',
slackInvited: true,
createdAt: new Date('2024-05-08'),
lastLogin: new Date('2025-08-19')
},
{
email: 'jordan.lee@indiedev.com',
name: 'Jordan Lee',
circle: 'community',
contributionTier: '15',
slackInvited: false,
createdAt: new Date('2024-06-20'),
lastLogin: new Date('2025-08-15')
},
{
email: 'taylor.smith@gamemaker.studio',
name: 'Taylor Smith',
circle: 'community',
contributionTier: '5',
slackInvited: true,
createdAt: new Date('2024-07-15'),
lastLogin: new Date('2025-08-10')
},
{
email: 'casey.wong@studiocoop.dev',
name: 'Casey Wong',
circle: 'founder',
contributionTier: '30',
helcimCustomerId: 'cust_54321',
slackInvited: true,
createdAt: new Date('2024-08-01'),
lastLogin: new Date('2025-08-24')
},
{
email: 'riley.johnson@cooperativedev.org',
name: 'Riley Johnson',
circle: 'community',
contributionTier: '0',
slackInvited: false,
createdAt: new Date('2024-08-15'),
lastLogin: new Date('2025-08-12')
},
{
email: 'morgan.davis@gamecollective.coop',
name: 'Morgan Davis',
circle: 'founder',
contributionTier: '50',
helcimCustomerId: 'cust_98765',
helcimSubscriptionId: 'sub_13579',
slackInvited: true,
createdAt: new Date('2024-09-01'),
lastLogin: new Date('2025-08-26')
},
{
email: 'avery.brown@newdevstudio.com',
name: 'Avery Brown',
circle: 'community',
contributionTier: '5',
slackInvited: false,
createdAt: new Date('2024-10-10'),
lastLogin: new Date('2025-08-14')
},
{
email: 'phoenix.martinez@coopgames.dev',
name: 'Phoenix Martinez',
circle: 'practitioner',
contributionTier: '15',
slackInvited: true,
createdAt: new Date('2024-11-05'),
lastLogin: new Date('2025-08-21')
},
{
email: 'sage.anderson@collaborativestudio.org',
name: 'Sage Anderson',
circle: 'community',
contributionTier: '15',
slackInvited: true,
createdAt: new Date('2024-12-01'),
lastLogin: new Date('2025-08-16')
},
{
email: 'dakota.wilson@indieguildstudio.com',
name: 'Dakota Wilson',
circle: 'founder',
contributionTier: '30',
slackInvited: true,
createdAt: new Date('2025-01-10'),
lastLogin: new Date('2025-08-23')
},
{
email: 'charlie.thompson@gamecooperative.net',
name: 'Charlie Thompson',
circle: 'practitioner',
contributionTier: '50',
helcimCustomerId: 'cust_11111',
helcimSubscriptionId: 'sub_22222',
slackInvited: true,
createdAt: new Date('2025-02-14'),
lastLogin: new Date('2025-08-25')
}
]
async function seedMembers() {
try {
await connectDB()
// Clear existing members
await Member.deleteMany({})
console.log('Cleared existing members')
// Insert sample members
await Member.insertMany(sampleMembers)
console.log(`Added ${sampleMembers.length} sample members`)
// Verify insertion and show summary
const count = await Member.countDocuments()
console.log(`Total members in database: ${count}`)
// Show breakdown by circle
const circleBreakdown = await Member.aggregate([
{ $group: { _id: '$circle', count: { $sum: 1 } } },
{ $sort: { _id: 1 } }
])
console.log('\nBreakdown by circle:')
circleBreakdown.forEach(circle => {
console.log(` ${circle._id}: ${circle.count} members`)
})
// Show breakdown by contribution tier
const tierBreakdown = await Member.aggregate([
{ $group: { _id: '$contributionTier', count: { $sum: 1 } } },
{ $sort: { _id: 1 } }
])
console.log('\nBreakdown by contribution tier:')
tierBreakdown.forEach(tier => {
console.log(` $${tier._id}: ${tier.count} members`)
})
process.exit(0)
} catch (error) {
console.error('Error seeding members:', error)
process.exit(1)
}
}
seedMembers()