TCA-Admin-Frontend/components/gallery/CreateUpcomingEventForm.tsx
2026-08-06 10:03:08 +05:30

378 lines
15 KiB
TypeScript

'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;