upcoming events dynamic

This commit is contained in:
srikanth_M 2026-08-06 10:03:08 +05:30
parent 69f054f785
commit 7825858e00
8 changed files with 1118 additions and 2 deletions

View File

@ -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 (
<>
<CreateUpcomingEventForm />
</>
);
};
export default CreateUpcomingEventPage;

View File

@ -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 (
<Suspense fallback={<div className="p-10 text-center">Loading...</div>}>
<EditUpcomingEventForm />
</Suspense>
);
};
export default EditUpcomingEventPage;

View File

@ -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 (
<>
<ListOfUpcomingEvents />
</>
);
};
export default UpcomingEventsPage;

View File

@ -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<FormValues>({
title: '',
slug: '',
date: '',
time: '',
location: '',
image: null,
link: '',
btn_text: 'Details Coming Soon',
admission: '',
description: '',
});
const [errors, setErrors] = useState<FormErrors>({});
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
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<HTMLInputElement>) => {
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 (
<form onSubmit={handleSubmit} className="max-w-4xl mx-auto p-6 bg-white rounded shadow-md dark:bg-black dark:border dark:border-[#1B2E4B]">
<h2 className="text-2xl font-bold mb-6 text-gray-800 dark:text-white">Create Upcoming Event</h2>
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
<div className="space-y-4">
{/* Event Title */}
<div>
<label htmlFor="title" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Title <span className="text-red-500">*</span>
</label>
<input
type="text"
name="title"
id="title"
value={formData.title}
onChange={handleChange}
placeholder="e.g. KW Multicultural Festival"
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
{errors.title && <p className="text-red-500 text-sm mt-1">{errors.title}</p>}
</div>
{/* Event Date */}
<div>
<label htmlFor="date" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Date <span className="text-red-500">*</span>
</label>
<input
type="text"
name="date"
id="date"
value={formData.date}
onChange={handleChange}
placeholder="e.g. Jun 20, 2026 and Jun 21, 2026"
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
{errors.date && <p className="text-red-500 text-sm mt-1">{errors.date}</p>}
</div>
{/* Event Time */}
<div>
<label htmlFor="time" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Time
</label>
<input
type="text"
name="time"
id="time"
value={formData.time}
onChange={handleChange}
placeholder="e.g. 2:00 PM to 4:00 PM or Details will be announced"
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Venue / Location */}
<div>
<label htmlFor="location" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Venue / Location
</label>
<input
type="text"
name="location"
id="location"
value={formData.location}
onChange={handleChange}
placeholder="e.g. Victoria Park, Kitchener"
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Button Text */}
<div>
<label htmlFor="btn_text" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Button Text
</label>
<input
type="text"
name="btn_text"
id="btn_text"
value={formData.btn_text}
onChange={handleChange}
placeholder="e.g. Details Coming Soon or Learn More"
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
</div>
<div className="space-y-4">
{/* Slug */}
<div>
<label htmlFor="slug" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Slug (Optional)
</label>
<input
type="text"
name="slug"
id="slug"
value={formData.slug}
onChange={handleChange}
placeholder="e.g. kw-multicultural-festival-2026"
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
<p className="text-xs text-gray-500 mt-1">Leave empty to automatically generate from event title.</p>
</div>
{/* Event Link */}
<div>
<label htmlFor="link" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Link (Optional)
</label>
<input
type="text"
name="link"
id="link"
value={formData.link}
onChange={handleChange}
placeholder="e.g. /upcoming-event/kw-multicultural-festival-2026"
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
<p className="text-xs text-gray-500 mt-1">Leave empty to auto-link to the new separate details page (`/upcoming-event/&lt;slug&gt;`).</p>
</div>
{/* Admission */}
<div>
<label htmlFor="admission" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Admission Details (Optional)
</label>
<input
type="text"
name="admission"
id="admission"
value={formData.admission}
onChange={handleChange}
placeholder="e.g. Free Entry / Ticket Required"
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Event Description */}
<div>
<label htmlFor="description" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Description
</label>
<textarea
name="description"
id="description"
rows={3}
value={formData.description}
onChange={handleChange}
placeholder="More details will be updated soon..."
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Image Upload */}
<div>
<label htmlFor="image" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Image
</label>
<input
type="file"
name="image"
id="image"
accept="image/*"
onChange={handleFileChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
{errors.image && <p className="text-red-500 text-sm mt-1">{errors.image}</p>}
{/* Preview */}
{previewUrl && (
<div className="mt-3 relative w-40 h-40 border rounded overflow-hidden">
<img src={previewUrl} alt="Preview" className="w-full h-full object-cover" />
<button
type="button"
onClick={handleImageDelete}
className="absolute top-1 right-1 bg-red-600 text-white p-1 rounded-full text-xs"
>
<IconTrashLines />
</button>
</div>
)}
</div>
</div>
</div>
{/* Action Buttons */}
<div className="mt-8 flex justify-end gap-3">
<button
type="button"
onClick={() => router.push('/upcoming-events')}
className="px-5 py-2 border rounded font-semibold text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
>
Cancel
</button>
<button
type="submit"
disabled={loading}
className="px-6 py-2 bg-primary text-white rounded font-semibold hover:bg-primary/90 disabled:opacity-50"
>
{loading ? 'Creating...' : 'Create Event'}
</button>
</div>
</form>
);
};
export default CreateUpcomingEventForm;

View File

@ -0,0 +1,426 @@
'use client';
import React, { useState, useEffect, ChangeEvent, FormEvent } from 'react';
import IconTrashLines from '../icon/icon-trash-lines';
import axios from 'axios';
import Cookies from 'universal-cookie';
import { useRouter, useSearchParams } 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;
existingImageUrl: string;
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 EditUpcomingEventForm: React.FC = () => {
const router = useRouter();
const searchParams = useSearchParams();
const id = searchParams.get('id');
const [formData, setFormData] = useState<FormValues>({
title: '',
slug: '',
date: '',
time: '',
location: '',
image: null,
existingImageUrl: '',
link: '',
btn_text: 'Details Coming Soon',
admission: '',
description: '',
});
const [errors, setErrors] = useState<FormErrors>({});
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [fetching, setFetching] = useState(true);
useEffect(() => {
if (id) {
fetchEventDetails(id);
} else {
showMessage('Invalid Event ID');
router.push('/upcoming-events');
}
}, [id]);
const fetchEventDetails = async (eventId: string) => {
try {
const cookies = new Cookies();
const token = cookies.get('token');
const res = await axios.get(buildApiUrl(`upcoming-events/${eventId}`), {
headers: { Authorization: `Bearer ${token}` },
});
if (res?.data?.success && res?.data?.data) {
const ev = res.data.data;
const titleStr = ev.title || ev.eventtitle || '';
const computedSlug = (ev.slug && ev.slug.trim()) ? ev.slug.trim() : slugify(titleStr);
const computedLink = (ev.link && ev.link.trim()) ? ev.link.trim() : `/upcoming-event/${computedSlug}`;
setFormData({
title: titleStr,
slug: computedSlug,
date: ev.date || ev.eventdate || '',
time: ev.time || '',
location: ev.location || '',
image: null,
existingImageUrl: ev.image || ev.eventimageurl || '',
link: computedLink,
btn_text: ev.btn_text || ev.btnText || 'Details Coming Soon',
admission: ev.admission || '',
description: ev.description || ev.desc || ev.eventdescription || '',
});
setPreviewUrl(ev.image || ev.eventimageurl || null);
}
} catch (error) {
console.error('Error fetching event details:', error);
showMessage('Failed to load event details');
} finally {
setFetching(false);
}
};
const handleChange = (e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
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<HTMLInputElement>) => {
const file = e.target.files?.[0] || null;
setFormData(prev => ({
...prev,
image: file,
}));
if (file) {
const url = URL.createObjectURL(file);
setPreviewUrl(url);
} else {
setPreviewUrl(formData.existingImageUrl || 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() || !id) return;
setLoading(true);
try {
const cookies = new Cookies();
const token = cookies.get('token');
let imageUrl = formData.existingImageUrl;
// Upload new 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 || imageUrl;
}
const updateData = {
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.put(buildApiUrl(`upcoming-events/${id}`), updateData, {
headers: {
Authorization: `Bearer ${token}`,
},
});
showMessage('Upcoming Event Updated Successfully', 'success');
router.push('/upcoming-events');
} catch (error: any) {
console.error('Update error:', error);
showMessage(error?.response?.data?.message || 'Failed to update upcoming event');
} finally {
setLoading(false);
}
};
const handleImageDelete = () => {
setFormData(prev => ({
...prev,
image: null,
existingImageUrl: '',
}));
setPreviewUrl(null);
};
if (fetching) {
return (
<div className="p-10 text-center text-gray-500">
Loading event details...
</div>
);
}
return (
<form onSubmit={handleSubmit} className="max-w-4xl mx-auto p-6 bg-white rounded shadow-md dark:bg-black dark:border dark:border-[#1B2E4B]">
<h2 className="text-2xl font-bold mb-6 text-gray-800 dark:text-white">Edit Upcoming Event</h2>
<div className="grid grid-cols-1 xl:grid-cols-2 gap-6">
<div className="space-y-4">
{/* Event Title */}
<div>
<label htmlFor="title" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Title <span className="text-red-500">*</span>
</label>
<input
type="text"
name="title"
id="title"
value={formData.title}
onChange={handleChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
{errors.title && <p className="text-red-500 text-sm mt-1">{errors.title}</p>}
</div>
{/* Event Date */}
<div>
<label htmlFor="date" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Date <span className="text-red-500">*</span>
</label>
<input
type="text"
name="date"
id="date"
value={formData.date}
onChange={handleChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
{errors.date && <p className="text-red-500 text-sm mt-1">{errors.date}</p>}
</div>
{/* Event Time */}
<div>
<label htmlFor="time" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Time
</label>
<input
type="text"
name="time"
id="time"
value={formData.time}
onChange={handleChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Venue / Location */}
<div>
<label htmlFor="location" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Venue / Location
</label>
<input
type="text"
name="location"
id="location"
value={formData.location}
onChange={handleChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Button Text */}
<div>
<label htmlFor="btn_text" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Button Text
</label>
<input
type="text"
name="btn_text"
id="btn_text"
value={formData.btn_text}
onChange={handleChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
</div>
<div className="space-y-4">
{/* Slug */}
<div>
<label htmlFor="slug" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Slug
</label>
<input
type="text"
name="slug"
id="slug"
value={formData.slug}
onChange={handleChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Event Link */}
<div>
<label htmlFor="link" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Link
</label>
<input
type="text"
name="link"
id="link"
value={formData.link}
onChange={handleChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Admission */}
<div>
<label htmlFor="admission" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Admission Details
</label>
<input
type="text"
name="admission"
id="admission"
value={formData.admission}
onChange={handleChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Event Description */}
<div>
<label htmlFor="description" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Description
</label>
<textarea
name="description"
id="description"
rows={3}
value={formData.description}
onChange={handleChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
</div>
{/* Image Upload */}
<div>
<label htmlFor="image" className="block font-medium text-gray-700 dark:text-gray-300 mb-1">
Event Image
</label>
<input
type="file"
name="image"
id="image"
accept="image/*"
onChange={handleFileChange}
className="w-full border rounded px-3 py-2 dark:bg-gray-900 dark:border-gray-700 dark:text-white"
/>
{errors.image && <p className="text-red-500 text-sm mt-1">{errors.image}</p>}
{/* Preview */}
{previewUrl && (
<div className="mt-3 relative w-40 h-40 border rounded overflow-hidden">
<img src={previewUrl} alt="Preview" className="w-full h-full object-cover" />
<button
type="button"
onClick={handleImageDelete}
className="absolute top-1 right-1 bg-red-600 text-white p-1 rounded-full text-xs"
>
<IconTrashLines />
</button>
</div>
)}
</div>
</div>
</div>
{/* Action Buttons */}
<div className="mt-8 flex justify-end gap-3">
<button
type="button"
onClick={() => router.push('/upcoming-events')}
className="px-5 py-2 border rounded font-semibold text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-800"
>
Cancel
</button>
<button
type="submit"
disabled={loading}
className="px-6 py-2 bg-primary text-white rounded font-semibold hover:bg-primary/90 disabled:opacity-50"
>
{loading ? 'Updating...' : 'Update Event'}
</button>
</div>
</form>
);
};
export default EditUpcomingEventForm;

View File

@ -0,0 +1,262 @@
'use client';
import axios from 'axios';
import Cookies from 'universal-cookie';
import { Metadata } from 'next';
import Link from 'next/link';
import React, { useEffect, useState } from 'react';
import IconTrashLines from '../icon/icon-trash-lines';
import IconPencil from '../icon/icon-pencil';
import IconEye from '../icon/icon-eye';
import Swal from 'sweetalert2';
import { useRouter } from 'next/navigation';
import { buildApiUrl } from '@/utils/BaseUrl.utils';
export const metadata: Metadata = {
title: 'Upcoming Events',
};
const ListOfUpcomingEvents = () => {
const router = useRouter();
const [events, setEvents] = useState<any[]>([]);
const [selectedEvent, setSelectedEvent] = useState<any | null>(null);
useEffect(() => {
getEvents();
}, []);
const getEvents = async () => {
try {
const cookies = new Cookies();
const token = cookies.get('token');
const eventRes: any = await axios.get(buildApiUrl('upcoming-events'), {
headers: { Authorization: `Bearer ${token}` },
});
console.log('Upcoming Events Res:', eventRes);
if (eventRes?.data?.success) {
setEvents(eventRes?.data?.data || []);
}
} catch (error) {
console.error('Error fetching upcoming events:', error);
}
};
const handleEdit = (event: any) => {
router.push(`/edit-upcoming-event?id=${event.id}`);
};
const handleView = (event: any) => {
setSelectedEvent(event);
};
const showAlert = async (event: any) => {
Swal.fire({
icon: 'warning',
title: 'Are you sure?',
text: `You are about to delete "${event.title || event.eventtitle}". You won't be able to revert this!`,
showCancelButton: true,
confirmButtonText: 'Delete',
padding: '2em',
customClass: { popup: 'sweet-alerts' },
}).then(async (result) => {
if (result.isConfirmed) {
try {
const cookies = new Cookies();
const token = cookies.get('token');
await axios.delete(buildApiUrl(`upcoming-events/${event.id}`), {
headers: { Authorization: `Bearer ${token}` },
});
Swal.fire({
title: 'Deleted!',
text: 'Upcoming event has been deleted.',
icon: 'success',
customClass: { popup: 'sweet-alerts' },
});
getEvents();
} catch (error) {
Swal.fire({
title: 'Error!',
text: 'Failed to delete the upcoming event.',
icon: 'error',
customClass: { popup: 'sweet-alerts' },
});
}
}
});
};
return (
<div>
<div className="mt-10 container">
<div className="flex justify-between items-center mb-6">
<h3 className="text-xl font-bold md:text-3xl">Upcoming Events</h3>
<Link
href="/create-upcoming-event"
className="bg-primary text-white px-4 py-2 rounded-md font-semibold hover:bg-primary/90 transition"
>
+ Add New Event
</Link>
</div>
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2 xl:grid-cols-3">
{/* First box: Create New Upcoming Event */}
<Link
href="/create-upcoming-event"
className="flex flex-col items-center justify-center min-h-[300px] rounded-md border border-dashed border-blue-500 bg-blue-50 p-5 text-center shadow hover:bg-blue-100 transition dark:border-blue-800 dark:bg-blue-900/20 dark:hover:bg-blue-900/40"
>
<div>
<div className="mb-3 text-5xl text-blue-600 dark:text-white">+</div>
<h5 className="text-lg font-semibold text-blue-800 dark:text-white">Create New Upcoming Event</h5>
</div>
</Link>
{/* List dynamic event cards */}
{events.map((event: any, index: number) => {
const title = event.title || event.eventtitle || 'Untitled Event';
const date = event.date || event.eventdate || 'Date TBD';
const time = event.time || '';
const location = event.location || '';
const image = event.image || event.eventimageurl || '/assets/images/placeholder.jpg';
const description = event.description || event.desc || event.eventdescription || '';
const btnText = event.btn_text || event.btnText || 'Details Coming Soon';
return (
<div
key={event.id || index}
className="relative flex flex-col justify-between rounded-md border border-white-light bg-white p-5 shadow-[0px_0px_2px_0px_rgba(145,158,171,0.20),0px_12px_24px_-4px_rgba(145,158,171,0.12)] dark:border-[#1B2E4B] dark:bg-black"
>
{/* Top-right action buttons */}
<div className="absolute top-5 right-5 z-10 flex gap-2">
<button
type="button"
onClick={() => handleView(event)}
className="bg-emerald-600 text-white p-1.5 rounded hover:bg-emerald-700 transition"
title="View Details"
>
<IconEye />
</button>
<button
type="button"
onClick={() => handleEdit(event)}
className="bg-blue-600 text-white p-1.5 rounded hover:bg-blue-700 transition"
title="Edit Event"
>
<IconPencil />
</button>
<button
type="button"
onClick={() => showAlert(event)}
className="bg-red-600 text-white p-1.5 rounded hover:bg-red-700 transition"
title="Delete Event"
>
<IconTrashLines />
</button>
</div>
<div>
{/* Image */}
<div className="max-h-48 overflow-hidden rounded-md mt-0 bg-gray-100 dark:bg-gray-800 flex items-center justify-center">
<img
src={image}
alt={title}
className="w-full h-48 object-cover"
onError={(e: any) => {
e.target.src = 'https://via.placeholder.com/400x200?text=Upcoming+Event';
}}
/>
</div>
{/* Event content */}
<div className="flex-1 mt-4">
<h4 className="text-xl mb-1.5 font-bold text-gray-800 dark:text-white line-clamp-1">
{title}
</h4>
<p className="text-sm font-semibold text-blue-600 dark:text-blue-400 mb-1">
📅 {date} {time ? `| 🕒 ${time}` : ''}
</p>
{location && (
<p className="text-sm text-gray-600 dark:text-gray-400 mb-2">
📍 {location}
</p>
)}
<p className="text-sm text-gray-500 dark:text-gray-400 line-clamp-2 mt-2">
{description}
</p>
</div>
</div>
{/* Footer badge */}
<div className="mt-4 pt-3 border-t border-gray-100 dark:border-gray-800 flex justify-between items-center">
<span className="text-xs font-semibold px-2.5 py-1 rounded bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300">
{btnText}
</span>
</div>
</div>
);
})}
</div>
</div>
{/* View Modal */}
{selectedEvent && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4">
<div className="relative w-full max-w-2xl rounded-lg bg-white p-6 shadow-xl dark:bg-gray-900 max-h-[90vh] overflow-y-auto">
<button
type="button"
onClick={() => setSelectedEvent(null)}
className="absolute top-4 right-4 text-gray-500 hover:text-black dark:hover:text-white text-2xl font-bold"
>
&times;
</button>
<h3 className="text-2xl font-bold mb-4 pr-8 text-gray-900 dark:text-white">
{selectedEvent.title || selectedEvent.eventtitle}
</h3>
{(selectedEvent.image || selectedEvent.eventimageurl) && (
<img
src={selectedEvent.image || selectedEvent.eventimageurl}
alt={selectedEvent.title}
className="w-full max-h-64 object-cover rounded-md mb-4"
/>
)}
<div className="space-y-3 text-sm text-gray-700 dark:text-gray-300">
<p><strong>Date:</strong> {selectedEvent.date || selectedEvent.eventdate || 'N/A'}</p>
{selectedEvent.time && <p><strong>Time:</strong> {selectedEvent.time}</p>}
{selectedEvent.location && <p><strong>Location:</strong> {selectedEvent.location}</p>}
{selectedEvent.admission && <p><strong>Admission:</strong> {selectedEvent.admission}</p>}
{selectedEvent.link && <p><strong>Link:</strong> {selectedEvent.link}</p>}
<p><strong>Button Text:</strong> {selectedEvent.btn_text || selectedEvent.btnText || 'Details Coming Soon'}</p>
<div className="pt-2 border-t border-gray-200 dark:border-gray-700">
<strong>Description:</strong>
<p className="mt-1 whitespace-pre-line">{selectedEvent.description || selectedEvent.desc || selectedEvent.eventdescription || 'No description provided.'}</p>
</div>
</div>
<div className="mt-6 flex justify-end gap-2">
<button
type="button"
onClick={() => {
const ev = selectedEvent;
setSelectedEvent(null);
handleEdit(ev);
}}
className="bg-blue-600 text-white px-4 py-2 rounded font-semibold hover:bg-blue-700"
>
Edit Event
</button>
<button
type="button"
onClick={() => setSelectedEvent(null)}
className="bg-gray-500 text-white px-4 py-2 rounded font-semibold hover:bg-gray-600"
>
Close
</button>
</div>
</div>
</div>
)}
</div>
);
};
export default ListOfUpcomingEvents;

View File

@ -229,10 +229,10 @@ const Sidebar = () => {
<AnimateHeight duration={300} height={currentMenu === 'upcoming-events' ? 'auto' : 0}> <AnimateHeight duration={300} height={currentMenu === 'upcoming-events' ? 'auto' : 0}>
<ul className="sub-menu text-gray-500"> <ul className="sub-menu text-gray-500">
<li> <li>
<Link href="#">{t('List')}</Link> <Link href="/upcoming-events">{t('List')}</Link>
</li> </li>
<li> <li>
<Link href="#">{t('Add New')}</Link> <Link href="/create-upcoming-event">{t('Add New')}</Link>
</li> </li>
</ul> </ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 KiB