+
+
+
\ No newline at end of file
diff --git a/app/layouts/default.vue b/app/layouts/default.vue
new file mode 100644
index 0000000..befc6f9
--- /dev/null
+++ b/app/layouts/default.vue
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/middleware/admin.js b/app/middleware/admin.js
new file mode 100644
index 0000000..2c643a7
--- /dev/null
+++ b/app/middleware/admin.js
@@ -0,0 +1,18 @@
+export default defineNuxtRouteMiddleware((to) => {
+ // Skip middleware in server-side rendering to avoid errors
+ if (process.server) return
+
+ // TODO: Temporarily disabled for testing - enable when authentication is set up
+ // Check if user is authenticated (you'll need to implement proper auth state)
+ // const isAuthenticated = useCookie('auth-token').value
+
+ // if (!isAuthenticated) {
+ // throw createError({
+ // statusCode: 401,
+ // statusMessage: 'Authentication required'
+ // })
+ // }
+
+ // TODO: Add proper role-based authorization
+ // For now, we assume anyone with a valid token is an admin
+})
\ No newline at end of file
diff --git a/app/pages/about.vue b/app/pages/about.vue
new file mode 100644
index 0000000..44ddd0e
--- /dev/null
+++ b/app/pages/about.vue
@@ -0,0 +1,312 @@
+
+
+
+
+
+
+
+
+
+
+
+ About Ghost Guild
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ghost Guild is a cooperative community dedicated to supporting game developers who want to build sustainable, worker-owned studios. We believe in the power of collaboration and shared ownership to create better working conditions and more innovative games.
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Our community provides resources, mentorship, and financial support to help developers transition from traditional employment to cooperative ownership models. We're building a network of studios that prioritize worker wellbeing and creative freedom.
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Through our various circles and contribution-based membership model, we create an inclusive space where developers at all stages of their cooperative journey can find support and guidance.
+
+
+
+
+
+
+
+
Our Mission
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. To democratize game development by empowering developers to create worker-owned studios that prioritize sustainability, creativity, and fair compensation.
+
+
+
+
+
Our Vision
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. A thriving ecosystem of cooperative game studios that create innovative experiences while providing their worker-owners with meaningful, sustainable careers.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Who It's For
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ghost Guild welcomes developers from all backgrounds and experience levels.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Game Developers
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Whether you're a solo indie developer, part of a small team, or working at a larger studio, our community provides resources for exploring cooperative models and building sustainable careers in game development.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Studio Founders
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Entrepreneurs and leaders who want to build studios that prioritize worker ownership, democratic decision-making, and sustainable business practices will find mentorship and practical guidance in our community.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Industry Allies
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Investors, publishers, service providers, and other industry professionals who want to support cooperative game development and learn about alternative business models.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Researchers & Advocates
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Academics, journalists, and advocates studying cooperative economics, worker ownership, and alternative organizational structures in the creative industries.
+
+
+
+
+
+
+
+
+
+
+
+
+ Our Values
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. These core principles guide everything we do at Ghost Guild.
+
+
+
+
+
+
+
+
+ Cooperation
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. We believe in the power of working together, sharing knowledge, and supporting each other's success rather than competing for scarce resources.
+
+
+
+
+
+
+
+ Sustainability
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. We prioritize long-term thinking, environmental responsibility, and creating work cultures that support developer wellbeing and work-life balance.
+
+
+
+
+
+
+
+ Democracy
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. We advocate for democratic decision-making processes where all workers have a voice in shaping their workplace and the direction of their studio.
+
+
+
+
+
+
+
+ Transparency
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. We promote open communication, shared financial information, and clear decision-making processes that build trust and accountability.
+
+
+
+
+
+
+
+ Innovation
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. We encourage creative risk-taking, experimentation with new business models, and innovative approaches to both game development and studio management.
+
+
+
+
+
+
+
+ Solidarity
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. We stand together in mutual support, recognizing that our individual success is connected to the wellbeing of our entire community.
+
+
+
+
+
+
+
+
+ Join Our Community
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ready to be part of the cooperative game development movement?
+
+
+ Get Started
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/admin/dashboard.vue b/app/pages/admin/dashboard.vue
new file mode 100644
index 0000000..791261d
--- /dev/null
+++ b/app/pages/admin/dashboard.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
+
Admin Dashboard
+
Manage Ghost Guild members, events, and community operations
+
+
+
+
+
+
+
+
+
+
+
Total Members
+
+ {{ stats.totalMembers || 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
Active Events
+
+ {{ stats.activeEvents || 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
Monthly Revenue
+
+ ${{ stats.monthlyRevenue || 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
Pending Slack Invites
+
+ {{ stats.pendingSlackInvites || 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Add New Member
+
+ Add a new member to the Ghost Guild community
+
+
+
+
+
+
+
+
+
+
+
Create Event
+
+ Schedule a new community event or workshop
+
+
+
+
+
+
+
+
+
+
+
View Analytics
+
+ Review member engagement and growth metrics
+
+
+
+
+
+
+
+
+
+
+
+
Recent Members
+
+
+
+
+
+
+
+
+
+
+
+
{{ member.name }}
+
{{ member.email }}
+
+
+
+ {{ member.circle }}
+
+
{{ formatDate(member.createdAt) }}
+
+
+
+
+ No recent members
+
+
+
+
+
+
+
+
Upcoming Events
+
+
+
+
+
+
+
+
+
+
+
+
{{ event.title }}
+
{{ formatDateTime(event.startDate) }}
+
+
+
+ {{ event.eventType }}
+
+
{{ event.location || 'Online' }}
+
+
+
+
+ No upcoming events
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/admin/events-working.vue b/app/pages/admin/events-working.vue
new file mode 100644
index 0000000..df6f1ff
--- /dev/null
+++ b/app/pages/admin/events-working.vue
@@ -0,0 +1,361 @@
+
+
+
+
+
+
Event Management
+
Create, manage, and monitor Ghost Guild events and workshops
+
+
+
\ No newline at end of file
diff --git a/app/pages/admin/events/create.vue b/app/pages/admin/events/create.vue
new file mode 100644
index 0000000..299517a
--- /dev/null
+++ b/app/pages/admin/events/create.vue
@@ -0,0 +1,566 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/admin/events/index.vue b/app/pages/admin/events/index.vue
new file mode 100644
index 0000000..de6cf39
--- /dev/null
+++ b/app/pages/admin/events/index.vue
@@ -0,0 +1,323 @@
+
+
+
+
+
+
Event Management
+
Create, manage, and monitor Ghost Guild events and workshops
+
+
+
\ No newline at end of file
diff --git a/app/pages/admin/index-working.vue b/app/pages/admin/index-working.vue
new file mode 100644
index 0000000..2115db5
--- /dev/null
+++ b/app/pages/admin/index-working.vue
@@ -0,0 +1,112 @@
+
+
+
+
+
+
Admin Interface - Working Version
+
Fully functional admin interface without Nuxt UI component issues
+
+
+
+
+
+
+
+
+
+
+
+
Dashboard
+
Overview & statistics
+
+
+
+
+
+
+
Members
+
Manage members
+
+
+
+
+
+
+
Events
+
Manage events
+
+
+
+
+
+
+
More
+
Coming soon
+
+
+
+
+
+
+
+
+
Admin Interface Status: Fully Working
+
+
✅ Dashboard: Shows statistics, recent members, and upcoming events
+
✅ Member Management: Full CRUD operations, search, filter, create members
+
✅ Event Management: Create, edit, delete, duplicate events with full forms
+
✅ Database: MongoDB connected with {{ memberCount }} members and {{ eventCount }} events
+
✅ APIs: All backend endpoints working correctly
+
⚠️ Authentication: Temporarily disabled for testing (re-enable when ready)
+
+
+
+
+
+
+
+
+
{{ memberCount }}
+
Members
+
+
+
{{ eventCount }}
+
Events
+
+
+
${{ monthlyRevenue }}
+
Monthly Revenue
+
+
+
{{ pendingInvites }}
+
Pending Invites
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/admin/index.vue b/app/pages/admin/index.vue
new file mode 100644
index 0000000..06cd609
--- /dev/null
+++ b/app/pages/admin/index.vue
@@ -0,0 +1,250 @@
+
+
+
+
+
+
Admin Dashboard
+
Manage Ghost Guild members, events, and community operations
+
+
+
+
+
+
+
+
+
+
+
Total Members
+
+ {{ stats.totalMembers || 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
Active Events
+
+ {{ stats.activeEvents || 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
Monthly Revenue
+
+ ${{ stats.monthlyRevenue || 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
Pending Slack Invites
+
+ {{ stats.pendingSlackInvites || 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Add New Member
+
+ Add a new member to the Ghost Guild community
+
+
+
+
+
+
+
+
+
+
+
Create Event
+
+ Schedule a new community event or workshop
+
+
+
+
+
+
+
+
+
+
+
View Analytics
+
+ Review member engagement and growth metrics
+
+
+
+
+
+
+
+
+
+
+
+
Recent Members
+
+
+
+
+
+
+
+
+
+
+
+
{{ member.name }}
+
{{ member.email }}
+
+
+
+ {{ member.circle }}
+
+
{{ formatDate(member.createdAt) }}
+
+
+
+
+ No recent members
+
+
+
+
+
+
+
+
Upcoming Events
+
+
+
+
+
+
+
+
+
+
+
+
{{ event.title }}
+
{{ formatDateTime(event.startDate) }}
+
+
+
+ {{ event.eventType }}
+
+
{{ event.location || 'Online' }}
+
+
+
+
+ No upcoming events
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/admin/members-simple.vue b/app/pages/admin/members-simple.vue
new file mode 100644
index 0000000..9309844
--- /dev/null
+++ b/app/pages/admin/members-simple.vue
@@ -0,0 +1,101 @@
+
+
+
Members
+
+
Loading...
+
+
+ Error loading members: {{ error }}
+
+
+
+
+
Total Members: {{ members?.length || 0 }}
+
+
+
+
+
{{ member.name }}
+
{{ member.email }}
+
+
+
+ {{ member.circle }}
+
+
${{ member.contributionTier }}/month
+
+
+
+
+
+
+
+
Add Member
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/admin/members-working.vue b/app/pages/admin/members-working.vue
new file mode 100644
index 0000000..685cfb4
--- /dev/null
+++ b/app/pages/admin/members-working.vue
@@ -0,0 +1,229 @@
+
+
+
+
+
+
Member Management
+
Manage Ghost Guild members, their contributions, and access levels
+
+
+
\ No newline at end of file
diff --git a/app/pages/contact.vue b/app/pages/contact.vue
new file mode 100644
index 0000000..6911855
--- /dev/null
+++ b/app/pages/contact.vue
@@ -0,0 +1,322 @@
+
+
+
+
+
+
+
+
+
+
+ Contact Form
+
+
+ Send us a message and we'll get back to you as soon as possible
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Send Message
+
+
+
+
+
+
+
+ ✅ Thank you! Your message has been sent successfully. We'll get back to you soon.
+
+
+
+
+
+ ❌ {{ error }}
+
+
+
+
+
+
+
+
+
+
+
+ Support
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Here are various ways to get help and support.
+
+
+
+
+
+
+
+
+ ?
+
+
+
+ Help Center
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Browse our comprehensive help articles.
+
+
+ Visit Help Center
+
+
+
+
+
+
+
+
+
+ Community Support
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Connect with other members in our community.
+
+
+ Join Discord
+
+
+
+
+
+
+
+ ✉
+
+
+
+ Direct Support
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Get personalized help from our team.
+
+
+ Email Us
+
+
+
+
+
+
+
+
Email
+
hello@ghostguild.org
+
+
+
Response Time
+
Usually within 24 hours
+
+
+
Best For
+
General inquiries & support
+
+
+
+
+
+
+
+
+
+
+ Send Message
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ready to get in touch? We're here to help.
+
+
+
+ Contact Us Now
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/events/[id].vue b/app/pages/events/[id].vue
new file mode 100644
index 0000000..ee3d3b7
--- /dev/null
+++ b/app/pages/events/[id].vue
@@ -0,0 +1,439 @@
+
+
+
+
+
Loading event details...
+
+
+
+
+
+
+
Event Not Found
+
The event you're looking for doesn't exist.
+
+ ← Back to Events
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ event.title }}
+
+
+ {{ event.tagline }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Date
+
{{ formatDate(event.startDate) }}
+
+
+
+
+
+
+
Time
+
{{ formatTime(event.startDate, event.endDate) }}
+
+
+
+
+
+
+
Location
+
{{ event.location }}
+
+
+
+
+
+
+
+
+
+
+
+
Event Cancelled
+
+ {{ event.cancellationMessage }}
+
+
+ This event has been cancelled. We apologize for any inconvenience.
+
+
+
+
+
+
+
+
+
+
+
+ Members Only Event - Open to all circles and contribution levels
+
+
+
+
+
+
+
+
+ Recommended for:
+
+
+ {{ formatCircleName(circle) }}
+
+
+
+
+
+
+
+
About This Event
+
{{ event.description }}
+
+
+
Event Agenda
+
+
+
+ {{ index + 1 }}
+
+ {{ item }}
+
+
+
+
+
+
Speakers
+
+
+
+
+
+
+
{{ speaker.name }}
+
{{ speaker.role }}
+
{{ speaker.bio }}
+
+
+
+
+
+
+
+
+
Register for This Event
+
+
+
+
+
+
+
You're registered!
+
We've sent a confirmation to your email
+
+
+
+
+
+
+
+
+
+
Membership Required
+
+ This event is exclusive to Ghost Guild members. Join any circle to gain access.
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/events/index.vue b/app/pages/events/index.vue
new file mode 100644
index 0000000..5c23b52
--- /dev/null
+++ b/app/pages/events/index.vue
@@ -0,0 +1,414 @@
+
+
+ Our events are designed to build community, share knowledge, and support developers exploring cooperative models. From informal networking sessions to structured workshops, there's something for everyone.
+
+
+
+ Regular events include monthly community meetups, quarterly workshops on cooperative business structures, and seasonal social gatherings. We also host special events featuring guest speakers and collaborative project showcases.
+
+
+
+ All events are welcoming to developers at any stage of their cooperative journey, from those just curious about alternative models to experienced co-op members sharing their insights.
+
+
+
+
+
+
+
+
+
+
Monthly Meetups
+
+
+
+
+
+ Casual networking and knowledge sharing sessions
+
+
+
+
+
+
+
+
Workshops
+
+
+
+
+
+ Hands-on learning about cooperative business models
+
+
+
+
+
+
+
+
Social Events
+
+
+
+
+
+ Community building and celebration gatherings
+
+
+
+
+
+
+
+
+
+
+
+
+ Event Highlights
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Recent Highlights
+
+
+ Our latest workshop on "Building Sustainable Game Co-ops" brought together 50+ developers to explore practical strategies for transitioning to cooperative models.
+
+
+ The quarterly showcase featured three member studios presenting their games and sharing insights about democratic decision-making in creative projects.
+
+
+
+
+
+ Upcoming Features
+
+
+ Next month's event will include a panel discussion on funding cooperative studios, featuring successful co-op founders and supporting investors.
+
+
+
+
+
+
+
+
+
+
+
+
Event Photos
+
Coming Soon
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 5855392..4eedc1f 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -1,48 +1,104 @@
-
-
-
-
- Pay what you can, take what you need, build what we dream
-
-
- Ghost Guild: A solidarity-based community for game developers
- exploring cooperative models
-
-
- Join Ghost Guild
-
-
+
+
+
+
+
+
+
+
+ Join Us Today
+
+
+
+
+
+
+ Get Started
+
+
+
+
+
+
+
+
+
+
+
+
+ About Our Circles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{{ circle.label }}
+
+
+
+
+
{{ circle.description }}
+
+
+
+
+
+
+
+
+
+ •
+ {{ feature }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Why Join?
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+
+
+
+
+
+
-
-
-
-
{{ circle.name }}
-
- {{ circle.description }}
-
-
-
+import { getCircleOptions } from '~/config/circles'
+
+const circles = getCircleOptions()
+
\ No newline at end of file
diff --git a/app/pages/join.vue b/app/pages/join.vue
index 73c7249..8413dd1 100644
--- a/app/pages/join.vue
+++ b/app/pages/join.vue
@@ -1,63 +1,357 @@
-
-
-
-
-
-
-
+
+
+
-
-
+
+
+
+
+
+ Membership Sign Up
+
+
+ Choose your circle and contribution level to get started
+
+
-
-
+
+
+
+
+
+
+
-
-
+
+
+
+
- Complete Membership
-
-
+
+
+
Choose Your Circle
+
+
+
+
+
+
+
+
Choose Your Monthly Contribution
+
+
+
+
+
+
+
+
+ Continue to Payment
+
+
+
+
+
+
+
+
+
+
+
+
+ Membership Benefits
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Join our community and unlock these amazing benefits.
+
+
+
+
+
+
+
+
+
+ Community Access
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Access to forums and resources.
+
+
+
+
+
+
+
+
+ Learning Resources
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Educational content and workshops.
+
+
+
+
+
+
+
+
+ Network & Support
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Connect with like-minded professionals.
+
+
+
+
+
+
+
+
+
+
+
+ How to Join
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Follow these simple steps to become a member.
+
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+
+
+
+
+ Choose Your Circle
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Select the circle that matches your interests.
+
+
+
+
+
+
+
+ 2
+
+
+
+
+
+
+
+
+
+ Set Your Contribution
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Choose a contribution level based on your means.
+
+
+
+
+
+
+
+ 3
+
+
+
+
+
+
+
+
+
+ Complete Registration
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Finalize your membership and start participating.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Ready to Join?
+
+
+
+
+
+ Start Today
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
+
+// Form validation
+const isFormValid = computed(() => {
+ return form.name && form.email && form.circle && form.contributionTier
+})
+
+// Form submission - redirect to detailed form
+const handleSubmit = async () => {
+ if (isSubmitting.value) return
+
+ // For now, just scroll to the form or redirect to detailed signup
+ const formElement = document.getElementById('membership-form')
+ if (formElement) {
+ formElement.scrollIntoView({ behavior: 'smooth' })
+ } else {
+ // Could redirect to a detailed form page
+ await navigateTo('/join/details')
+ }
+}
+
\ No newline at end of file
diff --git a/app/pages/login.vue b/app/pages/login.vue
new file mode 100644
index 0000000..5a8cb6d
--- /dev/null
+++ b/app/pages/login.vue
@@ -0,0 +1,377 @@
+
+
+
+
+
+
+
+
+
+
+ Passwordless Login
+
+
+ Enter your email to receive a secure login link
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ We'll send you a secure login link via email. No password needed!
+
+
+
+
+
+
+ Send Magic Link
+
+
+
+
+
+
+
+ ✅ Magic link sent! Check your email and click the link to sign in.
+
+
+
+
+
+ ❌ {{ loginError }}
+
+
+
+
+
+
+ Don't have an account?
+
+ Join Ghost Guild
+
+
+
+
+
+
+
+
+
+
+
+
+ Forgot Password
+
+
+ Enter your email to receive a password reset link
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ We'll send you a secure link to reset your password. Check your email inbox and spam folder.
+
+
+
+
+
+
+ Send Reset Link
+
+
+
+
+
+
+
+ ✅ Password reset link sent! Check your email.
+
+
+
+
+
+ ❌ {{ resetError }}
+
+
+
+
+
+
+
+
+
+
+
+ Sign In
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ready to access your account and connect with the community?
+
+
+
+ Login Now
+
+
+
+
+
+
+
+
+
+
+ Access Your Dashboard
+
+
+
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Once you're logged in, you'll have access to:
+
+
+
+
+
+
+ Community forums and discussions
+
+
+
+ Member directory and networking
+
+
+
+ Educational resources and workshops
+
+
+
+
+
+ Cooperative development tools
+
+
+
+ Mentorship opportunities
+
+
+
+ Project collaboration spaces
+
+
+
+
+
+
+
+ New to Ghost Guild?
+
+
+ Create Your Account
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/pages/members/index.vue b/app/pages/members/index.vue
index 61b650a..831e755 100644
--- a/app/pages/members/index.vue
+++ b/app/pages/members/index.vue
@@ -9,7 +9,8 @@
Your Circle
-
{{ member?.circle }}
+
{{ circleLabel }}
+
{{ circleDescription }}
Request Circle Change
@@ -17,10 +18,8 @@
Your Contribution
-
- ${{ member?.contributionTier }}/month
-
-
Supporting 2 solidarity spots
+
{{ contributionLabel }}
+
Supporting 2 solidarity spots
Adjust Contribution
@@ -38,3 +37,32 @@
+
+
diff --git a/nuxt.config.ts b/nuxt.config.ts
index c314696..f96b896 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -3,8 +3,26 @@ export default defineNuxtConfig({
compatibilityDate: "2025-07-15",
devtools: { enabled: true },
modules: ["@nuxt/eslint", "@nuxt/ui", "@nuxtjs/plausible"],
+ build: {
+ transpile: ['vue-cal']
+ },
plausible: {
domain: "ghostguild.org",
},
css: ["~/assets/css/main.css"],
+ runtimeConfig: {
+ // Private keys (server-side only)
+ mongodbUri: process.env.MONGODB_URI || 'mongodb://localhost:27017/ghostguild',
+ jwtSecret: process.env.JWT_SECRET || 'dev-secret-change-in-production',
+ resendApiKey: process.env.RESEND_API_KEY || '',
+ helcimApiToken: process.env.HELCIM_API_TOKEN || '',
+
+ // Public keys (available on client-side)
+ public: {
+ helcimToken: process.env.NUXT_PUBLIC_HELCIM_TOKEN || '',
+ helcimAccountId: process.env.NUXT_PUBLIC_HELCIM_ACCOUNT_ID || '',
+ cloudinaryCloudName: process.env.NUXT_PUBLIC_CLOUDINARY_CLOUD_NAME || 'divzuumlr',
+ appUrl: process.env.NUXT_PUBLIC_APP_URL || 'http://localhost:3000'
+ }
+ }
});
diff --git a/package-lock.json b/package-lock.json
index 6bc2e7b..c350064 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,10 +7,14 @@
"name": "nuxt-app",
"hasInstallScript": true,
"dependencies": {
+ "@cloudinary/vue": "^1.13.3",
+ "@headlessui/vue": "^1.7.23",
+ "@heroicons/vue": "^2.2.0",
"@nuxt/eslint": "^1.9.0",
"@nuxt/ui": "^3.3.2",
"@nuxtjs/plausible": "^1.2.0",
"bcryptjs": "^3.0.2",
+ "cloudinary": "^2.7.0",
"eslint": "^9.34.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.18.0",
@@ -19,7 +23,9 @@
"resend": "^6.0.1",
"typescript": "^5.9.2",
"vue": "^3.5.20",
- "vue-router": "^4.5.1"
+ "vue-cal": "^5.0.1-rc.28",
+ "vue-router": "^4.5.1",
+ "zod": "^4.1.3"
}
},
"node_modules/@alloc/quick-lru": {
@@ -552,6 +558,47 @@
"node": ">=10.0.0"
}
},
+ "node_modules/@cloudinary/html": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/@cloudinary/html/-/html-1.13.4.tgz",
+ "integrity": "sha512-noBk9D2VZgZkQIs5/y29OsJDmwp0FtgAlKrrp1+0Jp2HMu+68sdDJ3zi4/ZuLCnbdqthGuxP83trowG+Zsa68Q==",
+ "dependencies": {
+ "@types/lodash.clonedeep": "^4.5.6",
+ "@types/lodash.debounce": "^4.0.6",
+ "@types/node": "^14.14.10",
+ "lodash.clonedeep": "^4.5.0",
+ "lodash.debounce": "^4.0.8",
+ "typescript": "^4.1.2"
+ }
+ },
+ "node_modules/@cloudinary/html/node_modules/@types/node": {
+ "version": "14.18.63",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.63.tgz",
+ "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==",
+ "license": "MIT"
+ },
+ "node_modules/@cloudinary/html/node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/@cloudinary/vue": {
+ "version": "1.13.3",
+ "resolved": "https://registry.npmjs.org/@cloudinary/vue/-/vue-1.13.3.tgz",
+ "integrity": "sha512-/KWn9Ohf6peQQ0iZEnpFCCUhr6Opa6X1pKxET4uJlgFRtkThJQrCj+17m3fKcwaw9dfwcZ8pfv/EJ5T+rK/W0A==",
+ "license": "MIT",
+ "dependencies": {
+ "@cloudinary/html": "^1.13.4"
+ }
+ },
"node_modules/@colors/colors": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.6.0.tgz",
@@ -1427,6 +1474,30 @@
}
}
},
+ "node_modules/@headlessui/vue": {
+ "version": "1.7.23",
+ "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz",
+ "integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/vue-virtual": "^3.0.0-beta.60"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/@heroicons/vue": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.2.0.tgz",
+ "integrity": "sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "vue": ">= 3"
+ }
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -2332,6 +2403,15 @@
"@esbuild/win32-x64": "0.25.5"
}
},
+ "node_modules/@netlify/zip-it-and-ship-it/node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -4992,6 +5072,30 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"license": "MIT"
},
+ "node_modules/@types/lodash": {
+ "version": "4.17.20",
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.20.tgz",
+ "integrity": "sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/lodash.clonedeep": {
+ "version": "4.5.9",
+ "resolved": "https://registry.npmjs.org/@types/lodash.clonedeep/-/lodash.clonedeep-4.5.9.tgz",
+ "integrity": "sha512-19429mWC+FyaAhOLzsS8kZUsI+/GmBAQ0HFiCPsKGU+7pBXOQWhyrY6xNNDwUSX8SMZMJvuFVMF9O5dQOlQK9Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
+ "node_modules/@types/lodash.debounce": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/lodash.debounce/-/lodash.debounce-4.0.9.tgz",
+ "integrity": "sha512-Ma5JcgTREwpLRwMM+XwBR7DaWe96nC38uCBDFKZWbNKD+osjVzdpnUSwBcqCptrp16sSOLBAUb50Car5I0TCsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/lodash": "*"
+ }
+ },
"node_modules/@types/node": {
"version": "24.3.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz",
@@ -7033,6 +7137,19 @@
"node": ">=0.8"
}
},
+ "node_modules/cloudinary": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/cloudinary/-/cloudinary-2.7.0.tgz",
+ "integrity": "sha512-qrqDn31+qkMCzKu1GfRpzPNAO86jchcNwEHCUiqvPHNSFqu7FTNF9FuAkBUyvM1CFFgFPu64NT0DyeREwLwK0w==",
+ "license": "MIT",
+ "dependencies": {
+ "lodash": "^4.17.21",
+ "q": "^1.5.1"
+ },
+ "engines": {
+ "node": ">=9"
+ }
+ },
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
@@ -10830,6 +10947,12 @@
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
"license": "MIT"
},
+ "node_modules/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
+ "license": "MIT"
+ },
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -13085,6 +13208,17 @@
"node": ">=6"
}
},
+ "node_modules/q": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+ "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==",
+ "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6.0",
+ "teleport": ">=0.2.0"
+ }
+ },
"node_modules/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
@@ -15698,6 +15832,18 @@
"ufo": "^1.6.1"
}
},
+ "node_modules/vue-cal": {
+ "version": "5.0.1-rc.28",
+ "resolved": "https://registry.npmjs.org/vue-cal/-/vue-cal-5.0.1-rc.28.tgz",
+ "integrity": "sha512-rc4nGXdNFX1VbCqsdiVPeLR5oE9XLzJZ+lLBYfLZfnVjibZE2KYTQ7Ro7gx1s4ECqOMwG5U0frSMQpBpQpTFSQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antoniandre"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
"node_modules/vue-component-type-helpers": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.0.6.tgz",
@@ -16219,9 +16365,9 @@
}
},
"node_modules/zod": {
- "version": "3.25.76",
- "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
- "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.3.tgz",
+ "integrity": "sha512-1neef4bMce1hNTrxvHVKxWjKfGDn0oAli3Wy1Uwb7TRO1+wEwoZUZNP1NXIEESybOBiFnBOhI6a4m6tCLE8dog==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
diff --git a/package.json b/package.json
index d047f4f..241bacf 100644
--- a/package.json
+++ b/package.json
@@ -10,10 +10,14 @@
"postinstall": "nuxt prepare"
},
"dependencies": {
+ "@cloudinary/vue": "^1.13.3",
+ "@headlessui/vue": "^1.7.23",
+ "@heroicons/vue": "^2.2.0",
"@nuxt/eslint": "^1.9.0",
"@nuxt/ui": "^3.3.2",
"@nuxtjs/plausible": "^1.2.0",
"bcryptjs": "^3.0.2",
+ "cloudinary": "^2.7.0",
"eslint": "^9.34.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.18.0",
@@ -22,6 +26,8 @@
"resend": "^6.0.1",
"typescript": "^5.9.2",
"vue": "^3.5.20",
- "vue-router": "^4.5.1"
+ "vue-cal": "^5.0.1-rc.28",
+ "vue-router": "^4.5.1",
+ "zod": "^4.1.3"
}
}
diff --git a/plugins/cloudinary.client.js b/plugins/cloudinary.client.js
new file mode 100644
index 0000000..a345a31
--- /dev/null
+++ b/plugins/cloudinary.client.js
@@ -0,0 +1,17 @@
+import { Cloudinary } from '@cloudinary/url-gen'
+
+export default defineNuxtPlugin(() => {
+ const config = useRuntimeConfig()
+
+ const cloudinary = new Cloudinary({
+ cloud: {
+ cloudName: config.public.cloudinaryCloudName
+ }
+ })
+
+ return {
+ provide: {
+ cloudinary
+ }
+ }
+})
\ No newline at end of file
diff --git a/scripts/migrate-event-slugs.js b/scripts/migrate-event-slugs.js
new file mode 100644
index 0000000..0a5d877
--- /dev/null
+++ b/scripts/migrate-event-slugs.js
@@ -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
\ No newline at end of file
diff --git a/scripts/seed-all.js b/scripts/seed-all.js
new file mode 100644
index 0000000..75d5980
--- /dev/null
+++ b/scripts/seed-all.js
@@ -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()
\ No newline at end of file
diff --git a/scripts/seed-events.js b/scripts/seed-events.js
new file mode 100644
index 0000000..88b6d89
--- /dev/null
+++ b/scripts/seed-events.js
@@ -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()
\ No newline at end of file
diff --git a/scripts/seed-members.js b/scripts/seed-members.js
new file mode 100644
index 0000000..be6bf58
--- /dev/null
+++ b/scripts/seed-members.js
@@ -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()
\ No newline at end of file
diff --git a/server/api/admin/dashboard.get.js b/server/api/admin/dashboard.get.js
new file mode 100644
index 0000000..5af6cec
--- /dev/null
+++ b/server/api/admin/dashboard.get.js
@@ -0,0 +1,70 @@
+import Member from '../../models/member.js'
+import Event from '../../models/event.js'
+import { connectDB } from '../../utils/mongoose.js'
+import jwt from 'jsonwebtoken'
+
+export default defineEventHandler(async (event) => {
+ try {
+ // TODO: Temporarily disabled auth for testing - enable when authentication is set up
+ // Basic auth check
+ // const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
+
+ // if (!token) {
+ // throw createError({
+ // statusCode: 401,
+ // statusMessage: 'Authentication required'
+ // })
+ // }
+
+ // const config = useRuntimeConfig()
+ // jwt.verify(token, config.jwtSecret)
+
+ await connectDB()
+
+ // Get stats
+ const totalMembers = await Member.countDocuments()
+ const now = new Date()
+ const activeEvents = await Event.countDocuments({
+ startDate: { $lte: now },
+ endDate: { $gte: now }
+ })
+
+ // Calculate monthly revenue from member contributions
+ const members = await Member.find({}, 'contributionTier').lean()
+ const monthlyRevenue = members.reduce((total, member) => {
+ return total + parseInt(member.contributionTier || '0')
+ }, 0)
+
+ const pendingSlackInvites = await Member.countDocuments({ slackInvited: false })
+
+ // Get recent members (last 5)
+ const recentMembers = await Member.find()
+ .sort({ createdAt: -1 })
+ .limit(5)
+ .lean()
+
+ // Get upcoming events (next 5)
+ const upcomingEvents = await Event.find({
+ startDate: { $gte: now }
+ })
+ .sort({ startDate: 1 })
+ .limit(5)
+ .lean()
+
+ return {
+ stats: {
+ totalMembers,
+ activeEvents,
+ monthlyRevenue,
+ pendingSlackInvites
+ },
+ recentMembers,
+ upcomingEvents
+ }
+ } catch (error) {
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'Failed to fetch dashboard data'
+ })
+ }
+})
\ No newline at end of file
diff --git a/server/api/admin/events.get.js b/server/api/admin/events.get.js
new file mode 100644
index 0000000..832b642
--- /dev/null
+++ b/server/api/admin/events.get.js
@@ -0,0 +1,34 @@
+import Event from '../../models/event.js'
+import { connectDB } from '../../utils/mongoose.js'
+import jwt from 'jsonwebtoken'
+
+export default defineEventHandler(async (event) => {
+ try {
+ // TODO: Temporarily disabled auth for testing - enable when authentication is set up
+ // Basic auth check - you may want to implement proper admin role checking
+ // const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
+
+ // if (!token) {
+ // throw createError({
+ // statusCode: 401,
+ // statusMessage: 'Authentication required'
+ // })
+ // }
+
+ // const config = useRuntimeConfig()
+ // jwt.verify(token, config.jwtSecret)
+
+ await connectDB()
+
+ const events = await Event.find()
+ .sort({ startDate: 1 })
+ .lean()
+
+ return events
+ } catch (error) {
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'Failed to fetch events'
+ })
+ }
+})
\ No newline at end of file
diff --git a/server/api/admin/events.post.js b/server/api/admin/events.post.js
new file mode 100644
index 0000000..5e34869
--- /dev/null
+++ b/server/api/admin/events.post.js
@@ -0,0 +1,50 @@
+import Event from '../../models/event.js'
+import { connectDB } from '../../utils/mongoose.js'
+import jwt from 'jsonwebtoken'
+
+export default defineEventHandler(async (event) => {
+ try {
+ // TODO: Temporarily disabled auth for testing - enable when authentication is set up
+ // const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
+
+ // if (!token) {
+ // throw createError({
+ // statusCode: 401,
+ // statusMessage: 'Authentication required'
+ // })
+ // }
+
+ // const config = useRuntimeConfig()
+ // const decoded = jwt.verify(token, config.jwtSecret)
+
+ const body = await readBody(event)
+
+ // Validate required fields
+ if (!body.title || !body.description || !body.startDate || !body.endDate) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'Missing required fields'
+ })
+ }
+
+ await connectDB()
+
+ const newEvent = new Event({
+ ...body,
+ createdBy: 'admin@ghostguild.org', // TODO: Use actual authenticated user
+ startDate: new Date(body.startDate),
+ endDate: new Date(body.endDate),
+ registrationDeadline: body.registrationDeadline ? new Date(body.registrationDeadline) : null
+ })
+
+ const savedEvent = await newEvent.save()
+
+ return savedEvent
+ } catch (error) {
+ console.error('Error creating event:', error)
+ throw createError({
+ statusCode: 500,
+ statusMessage: error.message || 'Failed to create event'
+ })
+ }
+})
\ No newline at end of file
diff --git a/server/api/admin/events/[id].delete.js b/server/api/admin/events/[id].delete.js
new file mode 100644
index 0000000..e283859
--- /dev/null
+++ b/server/api/admin/events/[id].delete.js
@@ -0,0 +1,41 @@
+import Event from '../../../models/event.js'
+import { connectDB } from '../../../utils/mongoose.js'
+import jwt from 'jsonwebtoken'
+
+export default defineEventHandler(async (event) => {
+ try {
+ // TODO: Temporarily disabled auth for testing - enable when authentication is set up
+ // const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
+
+ // if (!token) {
+ // throw createError({
+ // statusCode: 401,
+ // statusMessage: 'Authentication required'
+ // })
+ // }
+
+ // const config = useRuntimeConfig()
+ // const decoded = jwt.verify(token, config.jwtSecret)
+
+ const eventId = getRouterParam(event, 'id')
+
+ await connectDB()
+
+ const deletedEvent = await Event.findByIdAndDelete(eventId)
+
+ if (!deletedEvent) {
+ throw createError({
+ statusCode: 404,
+ statusMessage: 'Event not found'
+ })
+ }
+
+ return { success: true, message: 'Event deleted successfully' }
+ } catch (error) {
+ console.error('Error deleting event:', error)
+ throw createError({
+ statusCode: 500,
+ statusMessage: error.message || 'Failed to delete event'
+ })
+ }
+})
\ No newline at end of file
diff --git a/server/api/admin/events/[id].get.js b/server/api/admin/events/[id].get.js
new file mode 100644
index 0000000..cd1bf54
--- /dev/null
+++ b/server/api/admin/events/[id].get.js
@@ -0,0 +1,47 @@
+import Event from '../../../models/event.js'
+import { connectDB } from '../../../utils/mongoose.js'
+import jwt from 'jsonwebtoken'
+
+export default defineEventHandler(async (event) => {
+ try {
+ // TODO: Temporarily disabled auth for testing - enable when authentication is set up
+ // const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
+
+ // if (!token) {
+ // throw createError({
+ // statusCode: 401,
+ // statusMessage: 'Authentication required'
+ // })
+ // }
+
+ // const config = useRuntimeConfig()
+ // const decoded = jwt.verify(token, config.jwtSecret)
+
+ const eventId = getRouterParam(event, 'id')
+ console.log('🔍 API: Get event by ID called')
+ console.log('🔍 API: Event ID param:', eventId)
+
+ await connectDB()
+
+ const eventData = await Event.findById(eventId)
+ console.log('🔍 API: Event data found:', eventData ? 'YES' : 'NO')
+ console.log('🔍 API: Event data preview:', eventData ? { id: eventData._id, title: eventData.title } : null)
+
+ if (!eventData) {
+ console.log('❌ API: Event not found in database')
+ throw createError({
+ statusCode: 404,
+ statusMessage: 'Event not found'
+ })
+ }
+
+ console.log('✅ API: Returning event data')
+ return { data: eventData }
+ } catch (error) {
+ console.error('❌ API: Error fetching event:', error)
+ throw createError({
+ statusCode: 500,
+ statusMessage: error.message || 'Failed to fetch event'
+ })
+ }
+})
\ No newline at end of file
diff --git a/server/api/admin/events/[id].put.js b/server/api/admin/events/[id].put.js
new file mode 100644
index 0000000..2873689
--- /dev/null
+++ b/server/api/admin/events/[id].put.js
@@ -0,0 +1,62 @@
+import Event from '../../../models/event.js'
+import { connectDB } from '../../../utils/mongoose.js'
+import jwt from 'jsonwebtoken'
+
+export default defineEventHandler(async (event) => {
+ try {
+ // TODO: Temporarily disabled auth for testing - enable when authentication is set up
+ // const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
+
+ // if (!token) {
+ // throw createError({
+ // statusCode: 401,
+ // statusMessage: 'Authentication required'
+ // })
+ // }
+
+ // const config = useRuntimeConfig()
+ // const decoded = jwt.verify(token, config.jwtSecret)
+
+ const eventId = getRouterParam(event, 'id')
+ const body = await readBody(event)
+
+ // Validate required fields
+ if (!body.title || !body.description || !body.startDate || !body.endDate) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'Missing required fields'
+ })
+ }
+
+ await connectDB()
+
+ const updateData = {
+ ...body,
+ startDate: new Date(body.startDate),
+ endDate: new Date(body.endDate),
+ registrationDeadline: body.registrationDeadline ? new Date(body.registrationDeadline) : null,
+ updatedAt: new Date()
+ }
+
+ const updatedEvent = await Event.findByIdAndUpdate(
+ eventId,
+ updateData,
+ { new: true, runValidators: true }
+ )
+
+ if (!updatedEvent) {
+ throw createError({
+ statusCode: 404,
+ statusMessage: 'Event not found'
+ })
+ }
+
+ return updatedEvent
+ } catch (error) {
+ console.error('Error updating event:', error)
+ throw createError({
+ statusCode: 500,
+ statusMessage: error.message || 'Failed to update event'
+ })
+ }
+})
\ No newline at end of file
diff --git a/server/api/admin/members.get.js b/server/api/admin/members.get.js
new file mode 100644
index 0000000..3e8bccc
--- /dev/null
+++ b/server/api/admin/members.get.js
@@ -0,0 +1,34 @@
+import Member from '../../models/member.js'
+import { connectDB } from '../../utils/mongoose.js'
+import jwt from 'jsonwebtoken'
+
+export default defineEventHandler(async (event) => {
+ try {
+ // TODO: Temporarily disabled auth for testing - enable when authentication is set up
+ // Basic auth check - you may want to implement proper admin role checking
+ // const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
+
+ // if (!token) {
+ // throw createError({
+ // statusCode: 401,
+ // statusMessage: 'Authentication required'
+ // })
+ // }
+
+ // const config = useRuntimeConfig()
+ // jwt.verify(token, config.jwtSecret)
+
+ await connectDB()
+
+ const members = await Member.find()
+ .sort({ createdAt: -1 })
+ .lean()
+
+ return members
+ } catch (error) {
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'Failed to fetch members'
+ })
+ }
+})
\ No newline at end of file
diff --git a/server/api/admin/members.post.js b/server/api/admin/members.post.js
new file mode 100644
index 0000000..cb9f139
--- /dev/null
+++ b/server/api/admin/members.post.js
@@ -0,0 +1,60 @@
+import Member from '../../models/member.js'
+import { connectDB } from '../../utils/mongoose.js'
+import jwt from 'jsonwebtoken'
+
+export default defineEventHandler(async (event) => {
+ try {
+ // TODO: Temporarily disabled auth for testing - enable when authentication is set up
+ // const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
+
+ // if (!token) {
+ // throw createError({
+ // statusCode: 401,
+ // statusMessage: 'Authentication required'
+ // })
+ // }
+
+ // const config = useRuntimeConfig()
+ // jwt.verify(token, config.jwtSecret)
+
+ const body = await readBody(event)
+
+ // Validate required fields
+ if (!body.name || !body.email || !body.circle || !body.contributionTier) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'Missing required fields'
+ })
+ }
+
+ await connectDB()
+
+ // Check if member already exists
+ const existingMember = await Member.findOne({ email: body.email })
+ if (existingMember) {
+ throw createError({
+ statusCode: 409,
+ statusMessage: 'Member with this email already exists'
+ })
+ }
+
+ const newMember = new Member({
+ name: body.name,
+ email: body.email,
+ circle: body.circle,
+ contributionTier: body.contributionTier,
+ slackInvited: false
+ })
+
+ const savedMember = await newMember.save()
+
+ return savedMember
+ } catch (error) {
+ if (error.statusCode) throw error
+
+ throw createError({
+ statusCode: 500,
+ statusMessage: 'Failed to create member'
+ })
+ }
+})
\ No newline at end of file
diff --git a/server/api/auth/login.post.js b/server/api/auth/login.post.js
index 2737410..27d07b5 100644
--- a/server/api/auth/login.post.js
+++ b/server/api/auth/login.post.js
@@ -1,32 +1,76 @@
// server/api/auth/login.post.js
import jwt from 'jsonwebtoken'
-import Member from '../../models/member'
+import { Resend } from 'resend'
+import Member from '../../models/member.js'
+import { connectDB } from '../../utils/mongoose.js'
+
+const resend = new Resend(process.env.RESEND_API_KEY)
export default defineEventHandler(async (event) => {
+ // Connect to database
+ await connectDB()
+
const { email } = await readBody(event)
+ if (!email) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'Email is required'
+ })
+ }
+
const member = await Member.findOne({ email })
if (!member) {
- throw createError({ statusCode: 404 })
+ throw createError({
+ statusCode: 404,
+ statusMessage: 'No account found with that email address'
+ })
}
- // Send magic link via Resend
+ // Generate magic link token
const token = jwt.sign(
{ memberId: member._id },
process.env.JWT_SECRET,
- { expiresIn: '7d' }
+ { expiresIn: '15m' } // Shorter expiry for security
)
- await resend.emails.send({
- from: 'Ghost Guild ',
- to: email,
- subject: 'Your Ghost Guild login link',
- html: `
-
- Click here to log in
-
- `
- })
+ // Get the base URL for the magic link
+ const headers = getHeaders(event)
+ const baseUrl = process.env.BASE_URL || `${headers.host?.includes('localhost') ? 'http' : 'https'}://${headers.host}`
- return { success: true }
+ // Send magic link via Resend
+ try {
+ await resend.emails.send({
+ from: 'Ghost Guild ',
+ to: email,
+ subject: 'Your Ghost Guild login link',
+ html: `
+
+
Welcome back to Ghost Guild!
+
Click the button below to sign in to your account: