268 lines
No EOL
9.4 KiB
Vue
268 lines
No EOL
9.4 KiB
Vue
<template>
|
|
<div>
|
|
<div class="bg-white border-b">
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="py-6">
|
|
<div class="flex items-center gap-4 mb-2">
|
|
<NuxtLink to="/admin/series-management" class="text-gray-500 hover:text-gray-700">
|
|
<Icon name="heroicons:arrow-left" class="w-5 h-5" />
|
|
</NuxtLink>
|
|
<h1 class="text-2xl font-bold text-gray-900">Create New Series</h1>
|
|
</div>
|
|
<p class="text-gray-600">Create a new event series to group related events together</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<!-- Error Summary -->
|
|
<div v-if="formErrors.length > 0" class="mb-6 p-4 bg-red-50 border border-red-200 rounded-lg">
|
|
<div class="flex">
|
|
<Icon name="heroicons:exclamation-circle" class="w-5 h-5 text-red-500 mr-3 mt-0.5" />
|
|
<div>
|
|
<h3 class="text-sm font-medium text-red-800 mb-2">Please fix the following errors:</h3>
|
|
<ul class="text-sm text-red-700 space-y-1">
|
|
<li v-for="error in formErrors" :key="error">• {{ error }}</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Success Message -->
|
|
<div v-if="showSuccessMessage" class="mb-6 p-4 bg-green-50 border border-green-200 rounded-lg">
|
|
<div class="flex">
|
|
<Icon name="heroicons:check-circle" class="w-5 h-5 text-green-500 mr-3 mt-0.5" />
|
|
<div>
|
|
<h3 class="text-sm font-medium text-green-800">Series created successfully!</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form @submit.prevent="createSeries">
|
|
<!-- Series Information -->
|
|
<div class="mb-8">
|
|
<h2 class="text-lg font-semibold text-gray-900 mb-4">Series Information</h2>
|
|
|
|
<div class="grid grid-cols-1 gap-6">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">
|
|
Series Title <span class="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
v-model="seriesForm.title"
|
|
type="text"
|
|
placeholder="e.g., Cooperative Game Development Fundamentals"
|
|
required
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
:class="{ 'border-red-300 focus:ring-red-500': fieldErrors.title }"
|
|
@input="generateSlugFromTitle"
|
|
/>
|
|
<p v-if="fieldErrors.title" class="mt-1 text-sm text-red-600">{{ fieldErrors.title }}</p>
|
|
</div>
|
|
|
|
<div v-if="generatedSlug">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Generated Series ID</label>
|
|
<div class="w-full bg-gray-100 border border-gray-300 rounded-lg px-3 py-2 text-gray-700 font-mono text-sm">
|
|
{{ generatedSlug }}
|
|
</div>
|
|
<p class="mt-1 text-sm text-gray-500">
|
|
This unique identifier will be automatically generated from your title
|
|
</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">
|
|
Series Description <span class="text-red-500">*</span>
|
|
</label>
|
|
<textarea
|
|
v-model="seriesForm.description"
|
|
placeholder="Describe what the series covers and its goals"
|
|
required
|
|
rows="4"
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
:class="{ 'border-red-300 focus:ring-red-500': fieldErrors.description }"
|
|
></textarea>
|
|
<p v-if="fieldErrors.description" class="mt-1 text-sm text-red-600">{{ fieldErrors.description }}</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Series Type</label>
|
|
<select
|
|
v-model="seriesForm.type"
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
>
|
|
<option value="workshop_series">Workshop Series</option>
|
|
<option value="recurring_meetup">Recurring Meetup</option>
|
|
<option value="multi_day">Multi-Day Event</option>
|
|
<option value="course">Course</option>
|
|
<option value="tournament">Tournament</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Total Events Planned</label>
|
|
<input
|
|
v-model.number="seriesForm.totalEvents"
|
|
type="number"
|
|
min="1"
|
|
placeholder="e.g., 4"
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
|
/>
|
|
<p class="text-sm text-gray-500 mt-1">How many events will be in this series? (optional)</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="flex justify-between items-center pt-6 border-t border-gray-200">
|
|
<NuxtLink
|
|
to="/admin/series-management"
|
|
class="px-4 py-2 text-gray-600 hover:text-gray-900 font-medium"
|
|
>
|
|
Cancel
|
|
</NuxtLink>
|
|
|
|
<div class="flex gap-3">
|
|
<button
|
|
type="button"
|
|
@click="createAndAddEvent"
|
|
:disabled="creating"
|
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
|
|
>
|
|
{{ creating ? 'Creating...' : 'Create & Add Event' }}
|
|
</button>
|
|
|
|
<button
|
|
type="submit"
|
|
:disabled="creating"
|
|
class="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
|
|
>
|
|
{{ creating ? 'Creating...' : 'Create Series' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
definePageMeta({
|
|
layout: 'admin'
|
|
})
|
|
|
|
const router = useRouter()
|
|
|
|
const creating = ref(false)
|
|
const showSuccessMessage = ref(false)
|
|
const formErrors = ref([])
|
|
const fieldErrors = ref({})
|
|
const generatedSlug = ref('')
|
|
|
|
const seriesForm = reactive({
|
|
title: '',
|
|
description: '',
|
|
type: 'workshop_series',
|
|
totalEvents: null
|
|
})
|
|
|
|
// Generate slug from title
|
|
const generateSlug = (text) => {
|
|
return text
|
|
.toLowerCase()
|
|
.trim()
|
|
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters except spaces and dashes
|
|
.replace(/\s+/g, '-') // Replace spaces with dashes
|
|
.replace(/-+/g, '-') // Replace multiple dashes with single dash
|
|
.replace(/^-+|-+$/g, '') // Remove leading/trailing dashes
|
|
}
|
|
|
|
const generateSlugFromTitle = () => {
|
|
if (seriesForm.title) {
|
|
generatedSlug.value = generateSlug(seriesForm.title)
|
|
} else {
|
|
generatedSlug.value = ''
|
|
}
|
|
}
|
|
|
|
const validateForm = () => {
|
|
formErrors.value = []
|
|
fieldErrors.value = {}
|
|
|
|
if (!seriesForm.title.trim()) {
|
|
formErrors.value.push('Series title is required')
|
|
fieldErrors.value.title = 'Please enter a series title'
|
|
}
|
|
|
|
if (!seriesForm.description.trim()) {
|
|
formErrors.value.push('Series description is required')
|
|
fieldErrors.value.description = 'Please provide a description for the series'
|
|
}
|
|
|
|
if (!generatedSlug.value) {
|
|
formErrors.value.push('Series title must generate a valid ID')
|
|
fieldErrors.value.title = 'Please enter a title that can generate a valid series ID'
|
|
}
|
|
|
|
return formErrors.value.length === 0
|
|
}
|
|
|
|
const createSeries = async (redirectAfter = true) => {
|
|
if (!validateForm()) {
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
return false
|
|
}
|
|
|
|
creating.value = true
|
|
try {
|
|
const response = await $fetch('/api/admin/series', {
|
|
method: 'POST',
|
|
body: {
|
|
...seriesForm,
|
|
id: generatedSlug.value
|
|
}
|
|
})
|
|
|
|
showSuccessMessage.value = true
|
|
setTimeout(() => { showSuccessMessage.value = false }, 5000)
|
|
|
|
if (redirectAfter) {
|
|
setTimeout(() => {
|
|
router.push('/admin/series-management')
|
|
}, 1500)
|
|
}
|
|
|
|
return response.data
|
|
} catch (error) {
|
|
console.error('Failed to create series:', error)
|
|
formErrors.value = [`Failed to create series: ${error.data?.statusMessage || error.message}`]
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
return false
|
|
} finally {
|
|
creating.value = false
|
|
}
|
|
}
|
|
|
|
const createAndAddEvent = async () => {
|
|
const series = await createSeries(false)
|
|
if (series) {
|
|
// Navigate to event creation with series pre-filled
|
|
const seriesData = {
|
|
series: {
|
|
isSeriesEvent: true,
|
|
id: generatedSlug.value,
|
|
title: seriesForm.title,
|
|
description: seriesForm.description,
|
|
type: seriesForm.type,
|
|
position: 1,
|
|
totalEvents: seriesForm.totalEvents
|
|
}
|
|
}
|
|
|
|
sessionStorage.setItem('seriesEventData', JSON.stringify(seriesData))
|
|
router.push('/admin/events/create?series=true')
|
|
}
|
|
}
|
|
</script> |