594 lines
No EOL
23 KiB
Vue
594 lines
No EOL
23 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/events" 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">
|
|
{{ editingEvent ? 'Edit Event' : 'Create New Event' }}
|
|
</h1>
|
|
<div class="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-4">
|
|
<strong>DEBUG:</strong> Create page loaded successfully!
|
|
<div class="text-sm mt-1">
|
|
Route query: {{ JSON.stringify($route.query) }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="text-gray-600">Fill out the form below to create or update an event</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="max-w-4xl 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">
|
|
{{ editingEvent ? 'Event updated successfully!' : 'Event created successfully!' }}
|
|
</h3>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<form @submit.prevent="saveEvent" class="bg-white rounded-lg shadow p-6">
|
|
<!-- Basic Information -->
|
|
<div class="mb-8">
|
|
<h2 class="text-lg font-semibold text-gray-900 mb-4">Basic Information</h2>
|
|
|
|
<div class="grid grid-cols-1 gap-6">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">
|
|
Event Title <span class="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
v-model="eventForm.title"
|
|
type="text"
|
|
placeholder="Enter a clear, descriptive event title"
|
|
required
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
:class="{ 'border-red-300 focus:ring-red-500': fieldErrors.title }"
|
|
/>
|
|
<p v-if="fieldErrors.title" class="mt-1 text-sm text-red-600">{{ fieldErrors.title }}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Feature Image</label>
|
|
<ImageUpload v-model="eventForm.featureImage" />
|
|
<p class="mt-1 text-sm text-gray-500">Upload a high-quality image (1200x630px recommended) to represent your event</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">
|
|
Event Description <span class="text-red-500">*</span>
|
|
</label>
|
|
<textarea
|
|
v-model="eventForm.description"
|
|
placeholder="Provide a clear description of what attendees can expect from this event"
|
|
required
|
|
rows="4"
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-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>
|
|
<p class="mt-1 text-sm text-gray-500">This will be displayed on the event listing and detail pages</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Additional Content</label>
|
|
<textarea
|
|
v-model="eventForm.content"
|
|
placeholder="Add detailed information, agenda, requirements, or other important details"
|
|
rows="6"
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
></textarea>
|
|
<p class="mt-1 text-sm text-gray-500">Optional: Provide additional context, agenda items, or detailed requirements</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Event Details -->
|
|
<div class="mb-8">
|
|
<h2 class="text-lg font-semibold text-gray-900 mb-4">Event Details</h2>
|
|
|
|
<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">
|
|
Event Type <span class="text-red-500">*</span>
|
|
</label>
|
|
<select
|
|
v-model="eventForm.eventType"
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
>
|
|
<option value="community">Community Meetup</option>
|
|
<option value="workshop">Workshop</option>
|
|
<option value="social">Social Event</option>
|
|
<option value="showcase">Showcase</option>
|
|
</select>
|
|
<p class="mt-1 text-sm text-gray-500">Choose the category that best describes your event</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">
|
|
Location <span class="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
v-model="eventForm.location"
|
|
type="text"
|
|
placeholder="e.g., https://zoom.us/j/123... or #channel-name"
|
|
required
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
:class="{ 'border-red-300 focus:ring-red-500': fieldErrors.location }"
|
|
/>
|
|
<p v-if="fieldErrors.location" class="mt-1 text-sm text-red-600">{{ fieldErrors.location }}</p>
|
|
<p class="mt-1 text-sm text-gray-500">Enter a video conference link or Slack channel (starting with #)</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">
|
|
Start Date & Time <span class="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
v-model="eventForm.startDate"
|
|
type="datetime-local"
|
|
required
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
:class="{ 'border-red-300 focus:ring-red-500': fieldErrors.startDate }"
|
|
/>
|
|
<p v-if="fieldErrors.startDate" class="mt-1 text-sm text-red-600">{{ fieldErrors.startDate }}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">
|
|
End Date & Time <span class="text-red-500">*</span>
|
|
</label>
|
|
<input
|
|
v-model="eventForm.endDate"
|
|
type="datetime-local"
|
|
required
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
:class="{ 'border-red-300 focus:ring-red-500': fieldErrors.endDate }"
|
|
/>
|
|
<p v-if="fieldErrors.endDate" class="mt-1 text-sm text-red-600">{{ fieldErrors.endDate }}</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Max Attendees</label>
|
|
<input
|
|
v-model="eventForm.maxAttendees"
|
|
type="number"
|
|
min="1"
|
|
placeholder="Leave blank for unlimited"
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
/>
|
|
<p class="mt-1 text-sm text-gray-500">Set a maximum number of attendees (optional)</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Registration Deadline</label>
|
|
<input
|
|
v-model="eventForm.registrationDeadline"
|
|
type="datetime-local"
|
|
placeholder="Optional"
|
|
class="w-full border border-gray-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
/>
|
|
<p class="mt-1 text-sm text-gray-500">When should registration close? (optional)</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Target Audience -->
|
|
<div class="mb-8">
|
|
<h2 class="text-lg font-semibold text-gray-900 mb-4">Target Audience</h2>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-3">Target Circles</label>
|
|
<div class="space-y-3">
|
|
<label class="flex items-start">
|
|
<input
|
|
v-model="eventForm.targetCircles"
|
|
value="community"
|
|
type="checkbox"
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-1"
|
|
/>
|
|
<div class="ml-3">
|
|
<span class="text-sm font-medium text-gray-700">Community Circle</span>
|
|
<p class="text-xs text-gray-500">New members and those exploring the community</p>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-start">
|
|
<input
|
|
v-model="eventForm.targetCircles"
|
|
value="founder"
|
|
type="checkbox"
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-1"
|
|
/>
|
|
<div class="ml-3">
|
|
<span class="text-sm font-medium text-gray-700">Founder Circle</span>
|
|
<p class="text-xs text-gray-500">Entrepreneurs and business leaders</p>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-start">
|
|
<input
|
|
v-model="eventForm.targetCircles"
|
|
value="practitioner"
|
|
type="checkbox"
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-1"
|
|
/>
|
|
<div class="ml-3">
|
|
<span class="text-sm font-medium text-gray-700">Practitioner Circle</span>
|
|
<p class="text-xs text-gray-500">Experts and professionals sharing knowledge</p>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
<p class="mt-2 text-sm text-gray-500">Select which circles this event is most relevant for (leave blank for all circles)</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Event Settings -->
|
|
<div class="mb-8">
|
|
<h2 class="text-lg font-semibold text-gray-900 mb-4">Event Settings</h2>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
<div class="space-y-4">
|
|
<label class="flex items-start">
|
|
<input
|
|
v-model="eventForm.isOnline"
|
|
type="checkbox"
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-1"
|
|
/>
|
|
<div class="ml-3">
|
|
<span class="text-sm font-medium text-gray-700">Online Event</span>
|
|
<p class="text-xs text-gray-500">Event will be conducted virtually</p>
|
|
</div>
|
|
</label>
|
|
|
|
<label class="flex items-start">
|
|
<input
|
|
v-model="eventForm.registrationRequired"
|
|
type="checkbox"
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-1"
|
|
/>
|
|
<div class="ml-3">
|
|
<span class="text-sm font-medium text-gray-700">Registration Required</span>
|
|
<p class="text-xs text-gray-500">Attendees must register before attending</p>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<label class="flex items-start">
|
|
<input
|
|
v-model="eventForm.isVisible"
|
|
type="checkbox"
|
|
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500 mt-1"
|
|
/>
|
|
<div class="ml-3">
|
|
<span class="text-sm font-medium text-gray-700">Visible on Public Calendar</span>
|
|
<p class="text-xs text-gray-500">Event will appear on the public events page</p>
|
|
</div>
|
|
</label>
|
|
|
|
<label class="flex items-start">
|
|
<input
|
|
v-model="eventForm.isCancelled"
|
|
type="checkbox"
|
|
class="rounded border-gray-300 text-red-600 focus:ring-red-500 mt-1"
|
|
/>
|
|
<div class="ml-3">
|
|
<span class="text-sm font-medium text-gray-700">Event Cancelled</span>
|
|
<p class="text-xs text-gray-500">Mark this event as cancelled</p>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cancellation Message (conditional) -->
|
|
<div v-if="eventForm.isCancelled" class="mb-8">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Cancellation Message</label>
|
|
<textarea
|
|
v-model="eventForm.cancellationMessage"
|
|
placeholder="Explain why the event was cancelled and any next steps..."
|
|
rows="3"
|
|
class="w-full border border-red-300 rounded-lg px-3 py-2 focus:ring-2 focus:ring-red-500 focus:border-transparent"
|
|
></textarea>
|
|
<p class="text-xs text-gray-500 mt-1">This message will be displayed to users viewing the event page</p>
|
|
</div>
|
|
|
|
<!-- Form Actions -->
|
|
<div class="flex justify-between items-center pt-6 border-t border-gray-200">
|
|
<NuxtLink
|
|
to="/admin/events"
|
|
class="px-4 py-2 text-gray-600 hover:text-gray-900 font-medium"
|
|
>
|
|
Cancel
|
|
</NuxtLink>
|
|
|
|
<div class="flex gap-3">
|
|
<button
|
|
v-if="!editingEvent"
|
|
type="button"
|
|
@click="saveAndCreateAnother"
|
|
:disabled="creating"
|
|
class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
|
|
>
|
|
{{ creating ? 'Saving...' : 'Save & Create Another' }}
|
|
</button>
|
|
|
|
<button
|
|
type="submit"
|
|
:disabled="creating"
|
|
class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed font-medium"
|
|
>
|
|
{{ creating ? 'Saving...' : (editingEvent ? 'Update Event' : 'Create Event') }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
console.log('🚀 CREATE PAGE: SCRIPT STARTED!')
|
|
|
|
definePageMeta({
|
|
layout: 'admin'
|
|
})
|
|
|
|
console.log('🔍 CREATE PAGE: Script loading...')
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
console.log('🔍 CREATE PAGE: Route object:', route)
|
|
console.log('🔍 CREATE PAGE: Current route query:', route.query)
|
|
|
|
const creating = ref(false)
|
|
const editingEvent = ref(null)
|
|
const showSuccessMessage = ref(false)
|
|
const formErrors = ref([])
|
|
const fieldErrors = ref({})
|
|
|
|
const eventForm = reactive({
|
|
title: '',
|
|
description: '',
|
|
content: '',
|
|
featureImage: null,
|
|
startDate: '',
|
|
endDate: '',
|
|
eventType: 'community',
|
|
location: '',
|
|
isOnline: true,
|
|
isVisible: true,
|
|
isCancelled: false,
|
|
cancellationMessage: '',
|
|
targetCircles: [],
|
|
maxAttendees: '',
|
|
registrationRequired: false,
|
|
registrationDeadline: ''
|
|
})
|
|
|
|
// Check if we're editing an event
|
|
if (route.query.edit) {
|
|
console.log('🔍 Edit mode detected')
|
|
console.log('🔍 Edit ID from query:', route.query.edit)
|
|
console.log('🔍 Full route query:', route.query)
|
|
|
|
try {
|
|
console.log('🔍 Fetching event data from API...')
|
|
const response = await $fetch(`/api/admin/events/${route.query.edit}`)
|
|
console.log('🔍 API response:', response)
|
|
|
|
const event = response.data
|
|
console.log('🔍 Event data:', event)
|
|
|
|
if (event) {
|
|
console.log('🔍 Setting up edit form with event data')
|
|
editingEvent.value = event
|
|
Object.assign(eventForm, {
|
|
title: event.title,
|
|
description: event.description,
|
|
content: event.content || '',
|
|
featureImage: event.featureImage || null,
|
|
startDate: new Date(event.startDate).toISOString().slice(0, 16),
|
|
endDate: new Date(event.endDate).toISOString().slice(0, 16),
|
|
eventType: event.eventType,
|
|
location: event.location || '',
|
|
isOnline: event.isOnline,
|
|
isVisible: event.isVisible !== undefined ? event.isVisible : true,
|
|
isCancelled: event.isCancelled || false,
|
|
cancellationMessage: event.cancellationMessage || '',
|
|
targetCircles: event.targetCircles || [],
|
|
maxAttendees: event.maxAttendees || '',
|
|
registrationRequired: event.registrationRequired,
|
|
registrationDeadline: event.registrationDeadline ? new Date(event.registrationDeadline).toISOString().slice(0, 16) : ''
|
|
})
|
|
console.log('🔍 Form populated with:', eventForm)
|
|
} else {
|
|
console.log('❌ No event data found in response')
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Failed to load event for editing:', error)
|
|
console.error('❌ Error details:', error.data)
|
|
}
|
|
} else {
|
|
console.log('🔍 Create mode - no edit ID in query')
|
|
}
|
|
|
|
// Check if we're duplicating an event
|
|
if (route.query.duplicate && process.client) {
|
|
const duplicateData = sessionStorage.getItem('duplicateEventData')
|
|
if (duplicateData) {
|
|
try {
|
|
const data = JSON.parse(duplicateData)
|
|
Object.assign(eventForm, data)
|
|
sessionStorage.removeItem('duplicateEventData')
|
|
} catch (error) {
|
|
console.error('Failed to load duplicate event data:', error)
|
|
}
|
|
}
|
|
}
|
|
|
|
const validateForm = () => {
|
|
formErrors.value = []
|
|
fieldErrors.value = {}
|
|
|
|
// Required field validation
|
|
if (!eventForm.title.trim()) {
|
|
formErrors.value.push('Event title is required')
|
|
fieldErrors.value.title = 'Please enter an event title'
|
|
}
|
|
|
|
if (!eventForm.description.trim()) {
|
|
formErrors.value.push('Event description is required')
|
|
fieldErrors.value.description = 'Please provide a description for your event'
|
|
}
|
|
|
|
if (!eventForm.startDate) {
|
|
formErrors.value.push('Start date and time is required')
|
|
fieldErrors.value.startDate = 'Please select when the event starts'
|
|
}
|
|
|
|
if (!eventForm.endDate) {
|
|
formErrors.value.push('End date and time is required')
|
|
fieldErrors.value.endDate = 'Please select when the event ends'
|
|
}
|
|
|
|
if (!eventForm.location.trim()) {
|
|
formErrors.value.push('Location is required')
|
|
fieldErrors.value.location = 'Please enter a location (URL or Slack channel)'
|
|
}
|
|
|
|
// Date validation
|
|
if (eventForm.startDate && eventForm.endDate) {
|
|
const startDate = new Date(eventForm.startDate)
|
|
const endDate = new Date(eventForm.endDate)
|
|
|
|
if (startDate >= endDate) {
|
|
formErrors.value.push('End date must be after start date')
|
|
fieldErrors.value.endDate = 'End date must be after the start date'
|
|
}
|
|
|
|
if (startDate < new Date()) {
|
|
formErrors.value.push('Start date cannot be in the past')
|
|
fieldErrors.value.startDate = 'Event cannot start in the past'
|
|
}
|
|
}
|
|
|
|
// Location format validation
|
|
if (eventForm.location.trim()) {
|
|
const urlPattern = /^https?:\/\/.+/
|
|
const slackPattern = /^#[a-zA-Z0-9-_]+$/
|
|
|
|
if (!urlPattern.test(eventForm.location) && !slackPattern.test(eventForm.location)) {
|
|
formErrors.value.push('Location must be a valid URL or Slack channel (starting with #)')
|
|
fieldErrors.value.location = 'Enter a video conference link (https://...) or Slack channel (#channel-name)'
|
|
}
|
|
}
|
|
|
|
// Registration deadline validation
|
|
if (eventForm.registrationDeadline && eventForm.startDate) {
|
|
const regDeadline = new Date(eventForm.registrationDeadline)
|
|
const startDate = new Date(eventForm.startDate)
|
|
|
|
if (regDeadline >= startDate) {
|
|
formErrors.value.push('Registration deadline must be before the event starts')
|
|
fieldErrors.value.registrationDeadline = 'Registration must close before the event starts'
|
|
}
|
|
}
|
|
|
|
return formErrors.value.length === 0
|
|
}
|
|
|
|
const saveEvent = async (redirect = true) => {
|
|
if (!validateForm()) {
|
|
// Scroll to top to show errors
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
return false
|
|
}
|
|
|
|
creating.value = true
|
|
try {
|
|
if (editingEvent.value) {
|
|
await $fetch(`/api/admin/events/${editingEvent.value._id}`, {
|
|
method: 'PUT',
|
|
body: eventForm
|
|
})
|
|
} else {
|
|
await $fetch('/api/admin/events', {
|
|
method: 'POST',
|
|
body: eventForm
|
|
})
|
|
}
|
|
|
|
showSuccessMessage.value = true
|
|
setTimeout(() => { showSuccessMessage.value = false }, 5000)
|
|
|
|
if (redirect) {
|
|
setTimeout(() => {
|
|
router.push('/admin/events')
|
|
}, 1500)
|
|
}
|
|
|
|
return true
|
|
} catch (error) {
|
|
console.error('Failed to save event:', error)
|
|
formErrors.value = [`Failed to ${editingEvent.value ? 'update' : 'create'} event: ${error.data?.statusMessage || error.message}`]
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
return false
|
|
} finally {
|
|
creating.value = false
|
|
}
|
|
}
|
|
|
|
const saveAndCreateAnother = async () => {
|
|
const success = await saveEvent(false)
|
|
if (success) {
|
|
// Reset form for new event
|
|
Object.assign(eventForm, {
|
|
title: '',
|
|
description: '',
|
|
content: '',
|
|
featureImage: null,
|
|
startDate: '',
|
|
endDate: '',
|
|
eventType: 'community',
|
|
location: '',
|
|
isOnline: true,
|
|
isVisible: true,
|
|
isCancelled: false,
|
|
cancellationMessage: '',
|
|
targetCircles: [],
|
|
maxAttendees: '',
|
|
registrationRequired: false,
|
|
registrationDeadline: ''
|
|
})
|
|
|
|
// Clear any existing errors
|
|
formErrors.value = []
|
|
fieldErrors.value = {}
|
|
|
|
// Scroll to top
|
|
window.scrollTo({ top: 0, behavior: 'smooth' })
|
|
}
|
|
}
|
|
</script> |