diff --git a/app/(defaults)/create-upcoming-event/page.tsx b/app/(defaults)/create-upcoming-event/page.tsx new file mode 100644 index 0000000..596b1b9 --- /dev/null +++ b/app/(defaults)/create-upcoming-event/page.tsx @@ -0,0 +1,17 @@ +import CreateUpcomingEventForm from '@/components/gallery/CreateUpcomingEventForm'; +import { Metadata } from 'next'; +import React from 'react'; + +export const metadata: Metadata = { + title: 'Create Upcoming Event', +}; + +const CreateUpcomingEventPage = () => { + return ( + <> + + + ); +}; + +export default CreateUpcomingEventPage; diff --git a/app/(defaults)/edit-upcoming-event/page.tsx b/app/(defaults)/edit-upcoming-event/page.tsx new file mode 100644 index 0000000..3eb3309 --- /dev/null +++ b/app/(defaults)/edit-upcoming-event/page.tsx @@ -0,0 +1,17 @@ +import EditUpcomingEventForm from '@/components/gallery/EditUpcomingEventForm'; +import { Metadata } from 'next'; +import React, { Suspense } from 'react'; + +export const metadata: Metadata = { + title: 'Edit Upcoming Event', +}; + +const EditUpcomingEventPage = () => { + return ( + Loading...}> + + + ); +}; + +export default EditUpcomingEventPage; diff --git a/app/(defaults)/upcoming-events/page.tsx b/app/(defaults)/upcoming-events/page.tsx new file mode 100644 index 0000000..eb37714 --- /dev/null +++ b/app/(defaults)/upcoming-events/page.tsx @@ -0,0 +1,17 @@ +import ListOfUpcomingEvents from '@/components/gallery/ListOfUpcomingEvents'; +import { Metadata } from 'next'; +import React from 'react'; + +export const metadata: Metadata = { + title: 'Upcoming Events', +}; + +const UpcomingEventsPage = () => { + return ( + <> + + + ); +}; + +export default UpcomingEventsPage; diff --git a/components/gallery/CreateUpcomingEventForm.tsx b/components/gallery/CreateUpcomingEventForm.tsx new file mode 100644 index 0000000..b4b9c95 --- /dev/null +++ b/components/gallery/CreateUpcomingEventForm.tsx @@ -0,0 +1,377 @@ +'use client'; +import React, { useState, ChangeEvent, FormEvent } from 'react'; +import IconTrashLines from '../icon/icon-trash-lines'; +import axios from 'axios'; +import Cookies from 'universal-cookie'; +import { useRouter } from 'next/navigation'; +import { showMessage } from '@/utils/CommonFunction.utils'; +import { buildApiUrl } from '@/utils/BaseUrl.utils'; + +interface FormValues { + title: string; + slug: string; + date: string; + time: string; + location: string; + image: File | null; + link: string; + btn_text: string; + admission: string; + description: string; +} + +interface FormErrors { + [key: string]: string; +} + +const slugify = (text: string) => { + if (!text) return ''; + return text + .toString() + .toLowerCase() + .trim() + .replace(/\s+/g, '-') + .replace(/[^\w\-]+/g, '') + .replace(/\-\-+/g, '-'); +}; + +const CreateUpcomingEventForm: React.FC = () => { + const router = useRouter(); + + const [formData, setFormData] = useState({ + title: '', + slug: '', + date: '', + time: '', + location: '', + image: null, + link: '', + btn_text: 'Details Coming Soon', + admission: '', + description: '', + }); + + const [errors, setErrors] = useState({}); + const [previewUrl, setPreviewUrl] = useState(null); + const [loading, setLoading] = useState(false); + + const handleChange = (e: ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => { + const nextState = { ...prev, [name]: value }; + if (name === 'title') { + const autoSlug = slugify(value); + if (!prev.slug || prev.slug === slugify(prev.title)) { + nextState.slug = autoSlug; + } + if (!prev.link || prev.link === `/upcoming-event/${slugify(prev.title)}`) { + nextState.link = `/upcoming-event/${autoSlug}`; + } + } + return nextState; + }); + }; + + const handleFileChange = (e: ChangeEvent) => { + const file = e.target.files?.[0] || null; + setFormData(prev => ({ + ...prev, + image: file, + })); + + if (file) { + const url = URL.createObjectURL(file); + setPreviewUrl(url); + } else { + setPreviewUrl(null); + } + }; + + const validateForm = (): boolean => { + const newErrors: FormErrors = {}; + + if (!formData.title.trim()) newErrors.title = 'Event title is required'; + if (!formData.date.trim()) newErrors.date = 'Event date is required'; + + if (formData.image && !formData.image.type.startsWith('image/')) { + newErrors.image = 'Only image files are allowed'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + if (!validateForm()) return; + + setLoading(true); + + try { + const cookies = new Cookies(); + const token = cookies.get('token'); + + let imageUrl = ''; + + // Upload image if selected + if (formData.image && formData.image.type.startsWith('image/')) { + const data = new FormData(); + data.append('file', formData.image); + + const imageUpload = await axios.post(buildApiUrl('upload/single'), data, { + headers: { + 'Content-Type': 'multipart/form-data', + Authorization: `Bearer ${token}`, + }, + }); + + imageUrl = imageUpload?.data?.data?.fullUrl || imageUpload?.data?.data?.path || ''; + } + + const createData = { + title: formData.title, + slug: formData.slug, + date: formData.date, + time: formData.time, + location: formData.location, + image: imageUrl, + link: formData.link, + btn_text: formData.btn_text, + admission: formData.admission, + description: formData.description, + }; + + await axios.post(buildApiUrl('upcoming-events'), createData, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + showMessage('Upcoming Event Created Successfully', 'success'); + router.push('/upcoming-events'); + } catch (error: any) { + console.error('Create error:', error); + showMessage(error?.response?.data?.message || 'Failed to create upcoming event'); + } finally { + setLoading(false); + } + }; + + const handleImageDelete = () => { + setFormData(prev => ({ + ...prev, + image: null, + })); + setPreviewUrl(null); + }; + + return ( +
+

Create Upcoming Event

+ +
+
+ {/* Event Title */} +
+ + + {errors.title &&

{errors.title}

} +
+ + {/* Event Date */} +
+ + + {errors.date &&

{errors.date}

} +
+ + {/* Event Time */} +
+ + +
+ + {/* Venue / Location */} +
+ + +
+ + {/* Button Text */} +
+ + +
+
+ +
+ {/* Slug */} +
+ + +

Leave empty to automatically generate from event title.

+
+ + {/* Event Link */} +
+ + +

Leave empty to auto-link to the new separate details page (`/upcoming-event/<slug>`).

+
+ + {/* Admission */} +
+ + +
+ + {/* Event Description */} +
+ +