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:
parent
6e7e27ac4e
commit
e4a0a9ab0f
61 changed files with 7902 additions and 950 deletions
390
app/pages/events/index.vue
Normal file
390
app/pages/events/index.vue
Normal file
|
|
@ -0,0 +1,390 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- Page Header -->
|
||||
<PageHeader
|
||||
title="Events"
|
||||
subtitle="Join our community events, workshops, and gatherings designed to connect developers and share knowledge about cooperative game development."
|
||||
theme="blue"
|
||||
size="large"
|
||||
/>
|
||||
|
||||
<!-- Event Calendar -->
|
||||
<section class="py-20 bg-white dark:bg-gray-900">
|
||||
<UContainer>
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-8">
|
||||
Event Calendar
|
||||
</h2>
|
||||
<div class="flex items-center justify-center gap-2 mb-8">
|
||||
<div class="w-6 h-6 bg-blue-500 rounded-full" />
|
||||
<div class="w-6 h-6 bg-blue-400 rounded-full" />
|
||||
<div class="w-8 h-1 bg-blue-300 rounded-full" />
|
||||
<div class="w-8 h-1 bg-blue-200 rounded-full" />
|
||||
<div class="w-8 h-1 bg-blue-100 rounded-full" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-5xl mx-auto">
|
||||
<div class="bg-gray-50 dark:bg-gray-800 rounded-2xl p-6 border border-gray-200 dark:border-gray-700">
|
||||
<ClientOnly>
|
||||
<div v-if="pending" class="min-h-[400px] bg-gray-100 dark:bg-gray-700 rounded-xl flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"></div>
|
||||
<p class="text-gray-600 dark:text-gray-400">Loading events...</p>
|
||||
</div>
|
||||
</div>
|
||||
<VueCal
|
||||
v-else
|
||||
:events="events"
|
||||
:time="false"
|
||||
active-view="month"
|
||||
class="custom-calendar"
|
||||
:disable-views="['years', 'year']"
|
||||
:hide-weekends="false"
|
||||
today-button
|
||||
events-on-month-view="short"
|
||||
:editable-events="{
|
||||
title: false,
|
||||
drag: false,
|
||||
resize: false,
|
||||
delete: false,
|
||||
create: false
|
||||
}"
|
||||
@event-click="onEventClick"
|
||||
/>
|
||||
<template #fallback>
|
||||
<div class="min-h-[400px] bg-gray-100 dark:bg-gray-700 rounded-xl flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500 mx-auto mb-4"></div>
|
||||
<p class="text-gray-600 dark:text-gray-400">Loading calendar...</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ClientOnly>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
|
||||
<!-- Upcoming Events -->
|
||||
<section class="py-20 bg-gray-50 dark:bg-gray-800">
|
||||
<UContainer>
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-8">
|
||||
Upcoming Events
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||
<NuxtLink
|
||||
v-for="event in upcomingEvents"
|
||||
:key="event.id"
|
||||
:to="`/events/${event.slug || event.id}`"
|
||||
class="group bg-white dark:bg-gray-900 rounded-xl overflow-hidden shadow-lg border border-gray-200 dark:border-gray-700 hover:border-blue-400 dark:hover:border-blue-600 transition-all hover:shadow-xl"
|
||||
>
|
||||
<!-- Feature Image -->
|
||||
<div v-if="event.featureImage?.url" class="aspect-video w-full overflow-hidden">
|
||||
<img
|
||||
:src="getOptimizedImageUrl(event.featureImage.publicId, 'w_400,h_200,c_fill')"
|
||||
:alt="event.featureImage.alt || event.title"
|
||||
class="w-full h-full object-cover group-hover:scale-105 transition-transform duration-300"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div :class="[
|
||||
'inline-flex items-center px-3 py-1 rounded-full text-xs font-medium',
|
||||
event.class === 'event-community' ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400' :
|
||||
event.class === 'event-workshop' ? 'bg-emerald-100 text-emerald-700 dark:bg-emerald-900/30 dark:text-emerald-400' :
|
||||
event.class === 'event-social' ? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400' :
|
||||
'bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400'
|
||||
]">
|
||||
{{ event.class === 'event-community' ? 'Community' :
|
||||
event.class === 'event-workshop' ? 'Workshop' :
|
||||
event.class === 'event-social' ? 'Social' : 'Showcase' }}
|
||||
</div>
|
||||
<Icon v-if="event.membersOnly" name="heroicons:lock-closed" class="w-4 h-4 text-purple-500" />
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2 group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
|
||||
{{ event.title }}
|
||||
</h3>
|
||||
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
{{ event.content }}
|
||||
</p>
|
||||
|
||||
<div class="flex items-center text-sm text-gray-500 dark:text-gray-500">
|
||||
<Icon name="heroicons:calendar" class="w-4 h-4 mr-1" />
|
||||
{{ formatEventDate(event.start) }}
|
||||
</div>
|
||||
|
||||
<div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-800">
|
||||
<span class="inline-flex items-center text-sm font-medium text-blue-600 dark:text-blue-400 group-hover:translate-x-1 transition-transform">
|
||||
View Details
|
||||
<Icon name="heroicons:arrow-right" class="w-4 h-4 ml-1" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
|
||||
<!-- Attend Our Events -->
|
||||
<section class="py-20 bg-white dark:bg-gray-900">
|
||||
<UContainer>
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-8">
|
||||
Attend Our Events
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="max-w-4xl mx-auto">
|
||||
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-2xl p-8 border border-blue-200 dark:border-blue-800 mb-12">
|
||||
<div class="space-y-6 mb-8">
|
||||
<div class="h-2 bg-blue-500 rounded-full" />
|
||||
<div class="h-2 bg-blue-400 rounded-full w-5/6" />
|
||||
<div class="h-2 bg-blue-300 rounded-full w-2/3" />
|
||||
</div>
|
||||
|
||||
<div class="prose prose-lg dark:prose-invert max-w-none">
|
||||
<p class="text-lg leading-relaxed text-gray-700 dark:text-gray-300 mb-6">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p class="text-lg leading-relaxed text-gray-700 dark:text-gray-300 mb-6">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p class="text-lg leading-relaxed text-gray-700 dark:text-gray-300">
|
||||
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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-8">
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 bg-blue-100 dark:bg-blue-900/30 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<div class="w-8 h-8 bg-blue-500 rounded" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Monthly Meetups</h3>
|
||||
<div class="space-y-1 mb-3">
|
||||
<div class="h-1 bg-blue-500 rounded-full" />
|
||||
<div class="h-1 bg-blue-300 rounded-full w-3/4 mx-auto" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Casual networking and knowledge sharing sessions
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 bg-emerald-100 dark:bg-emerald-900/30 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<div class="w-8 h-8 bg-emerald-500" style="clip-path: polygon(50% 0%, 0% 100%, 100% 100%)" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Workshops</h3>
|
||||
<div class="space-y-1 mb-3">
|
||||
<div class="h-1 bg-emerald-500 rounded-full" />
|
||||
<div class="h-1 bg-emerald-300 rounded-full w-5/6 mx-auto" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Hands-on learning about cooperative business models
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 bg-purple-100 dark:bg-purple-900/30 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<div class="w-8 h-8 bg-purple-500 rounded-full" />
|
||||
</div>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-2">Social Events</h3>
|
||||
<div class="space-y-1 mb-3">
|
||||
<div class="h-1 bg-purple-500 rounded-full" />
|
||||
<div class="h-1 bg-purple-300 rounded-full w-2/3 mx-auto" />
|
||||
</div>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Community building and celebration gatherings
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
|
||||
<!-- Event Highlights -->
|
||||
<section class="py-20 bg-gray-50 dark:bg-gray-800">
|
||||
<UContainer>
|
||||
<div class="text-center mb-16">
|
||||
<h2 class="text-3xl font-bold text-blue-600 dark:text-blue-400 mb-8">
|
||||
Event Highlights
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-12 max-w-6xl mx-auto">
|
||||
<div class="space-y-6">
|
||||
<div class="space-y-4">
|
||||
<div class="h-2 bg-blue-500 rounded-full" />
|
||||
<div class="h-2 bg-blue-400 rounded-full w-5/6" />
|
||||
<div class="h-2 bg-blue-300 rounded-full w-3/4" />
|
||||
<div class="h-2 bg-blue-200 rounded-full w-1/2" />
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Recent Highlights
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 leading-relaxed mb-4">
|
||||
Our latest workshop on "Building Sustainable Game Co-ops" brought together 50+ developers to explore practical strategies for transitioning to cooperative models.
|
||||
</p>
|
||||
<p class="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
The quarterly showcase featured three member studios presenting their games and sharing insights about democratic decision-making in creative projects.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 dark:text-white mb-3">
|
||||
Upcoming Features
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
Next month's event will include a panel discussion on funding cooperative studios, featuring successful co-op founders and supporting investors.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="w-full max-w-md h-64 bg-blue-100 dark:bg-blue-900/30 rounded-2xl border-2 border-dashed border-blue-300 dark:border-blue-700 flex items-center justify-center">
|
||||
<div class="text-center">
|
||||
<div class="w-16 h-16 bg-blue-200 dark:bg-blue-800 rounded-xl flex items-center justify-center mx-auto mb-4">
|
||||
<div class="w-8 h-8 bg-blue-500 rounded" />
|
||||
</div>
|
||||
<p class="text-blue-600 dark:text-blue-400 font-medium">Event Photos</p>
|
||||
<p class="text-sm text-blue-500 dark:text-blue-500 mt-2">Coming Soon</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</UContainer>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { VueCal } from 'vue-cal'
|
||||
import 'vue-cal/style.css'
|
||||
|
||||
// Fetch events from API
|
||||
const { data: eventsData, pending, error } = await useFetch('/api/events')
|
||||
|
||||
// Transform events for calendar display
|
||||
const events = computed(() => {
|
||||
if (!eventsData.value) return []
|
||||
|
||||
return eventsData.value.map(event => ({
|
||||
id: event.id || event._id,
|
||||
slug: event.slug,
|
||||
start: new Date(event.startDate),
|
||||
end: new Date(event.endDate),
|
||||
title: event.title,
|
||||
content: event.description,
|
||||
class: `event-${event.eventType}`,
|
||||
membersOnly: event.membersOnly,
|
||||
eventType: event.eventType,
|
||||
location: event.location,
|
||||
registeredCount: event.registeredCount,
|
||||
maxAttendees: event.maxAttendees,
|
||||
featureImage: event.featureImage
|
||||
}))
|
||||
})
|
||||
|
||||
// Get upcoming events (future events)
|
||||
const upcomingEvents = computed(() => {
|
||||
const now = new Date()
|
||||
return events.value
|
||||
.filter(event => event.start > now)
|
||||
.sort((a, b) => a.start - b.start)
|
||||
.slice(0, 6) // Show max 6 upcoming events
|
||||
})
|
||||
|
||||
// Format event date for display
|
||||
const formatEventDate = (date) => {
|
||||
return new Intl.DateTimeFormat('en-US', {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric'
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
// Get optimized Cloudinary image URL
|
||||
const getOptimizedImageUrl = (publicId, transformations) => {
|
||||
if (!publicId) return ''
|
||||
|
||||
const config = useRuntimeConfig()
|
||||
return `https://res.cloudinary.com/${config.public.cloudinaryCloudName}/image/upload/${transformations}/f_auto,q_auto/${publicId}`
|
||||
}
|
||||
|
||||
// Handle calendar event click
|
||||
const onEventClick = (event) => {
|
||||
if (event.id) {
|
||||
navigateTo(`/events/${event.slug || event.id}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Custom calendar styling to match the site theme */
|
||||
.custom-calendar {
|
||||
--vuecal-primary-color: #3b82f6;
|
||||
--vuecal-text-color: #374151;
|
||||
--vuecal-border-color: #e5e7eb;
|
||||
--vuecal-header-color: #f9fafb;
|
||||
--vuecal-today-color: #dbeafe;
|
||||
}
|
||||
|
||||
.dark .custom-calendar {
|
||||
--vuecal-primary-color: #60a5fa;
|
||||
--vuecal-text-color: #d1d5db;
|
||||
--vuecal-border-color: #4b5563;
|
||||
--vuecal-header-color: #374151;
|
||||
--vuecal-today-color: #1e3a8a;
|
||||
}
|
||||
|
||||
/* Event type styling */
|
||||
.vuecal__event.event-community {
|
||||
background-color: #3b82f6;
|
||||
border-color: #2563eb;
|
||||
}
|
||||
|
||||
.vuecal__event.event-workshop {
|
||||
background-color: #10b981;
|
||||
border-color: #059669;
|
||||
}
|
||||
|
||||
.vuecal__event.event-social {
|
||||
background-color: #8b5cf6;
|
||||
border-color: #7c3aed;
|
||||
}
|
||||
|
||||
.vuecal__event.event-showcase {
|
||||
background-color: #f59e0b;
|
||||
border-color: #d97706;
|
||||
}
|
||||
|
||||
/* Responsive calendar */
|
||||
.vuecal {
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vuecal__header {
|
||||
background-color: var(--vuecal-header-color);
|
||||
color: var(--vuecal-text-color);
|
||||
}
|
||||
|
||||
.vuecal__title {
|
||||
color: var(--vuecal-primary-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
Loading…
Add table
Add a link
Reference in a new issue