diff --git a/src/actions/communityPortal/eventActions.js b/src/actions/communityPortal/eventActions.js new file mode 100644 index 0000000000..4ebd88af93 --- /dev/null +++ b/src/actions/communityPortal/eventActions.js @@ -0,0 +1,26 @@ +import axios from 'axios'; +import { ENDPOINTS } from '../../utils/URL'; +import { toast } from 'react-toastify'; + +export const createEvent = eventData => { + return async dispatch => { + try { + const res = await axios.post(ENDPOINTS.EVENTS, eventData); + if (res.status === 201) { + toast.success('Event created successfully!'); + return { success: true, event: res.data }; + } + return { success: false, error: 'Unexpected response status' }; + } catch (error) { + if (error.response?.status === 500) { + toast.error('Error creating event. Please try again.'); + } else if (error.response?.status === 404 || error.response?.status === 403 || error.response?.status === 400) { + toast.error('Permission or Validation Error. Please check your input or access rights'); + } else { + toast.error('Error creating event. Please try again.'); + } + return { success: false, error: error.response?.data || error.message }; + } + }; +}; + diff --git a/src/components/CommunityPortal/Reports/Participation/CreateEventModal.jsx b/src/components/CommunityPortal/Reports/Participation/CreateEventModal.jsx new file mode 100644 index 0000000000..5707d9a506 --- /dev/null +++ b/src/components/CommunityPortal/Reports/Participation/CreateEventModal.jsx @@ -0,0 +1,405 @@ +import { useState } from 'react'; +import { Modal, ModalHeader, ModalBody, ModalFooter, Button } from 'reactstrap'; +import { useDispatch, useSelector } from 'react-redux'; +import moment from 'moment-timezone'; +import { createEvent } from '../../../../actions/communityPortal/eventActions'; +import '../../../Header/DarkMode.module.css'; + +function CreateEventModal({ isOpen, toggle }) { + const dispatch = useDispatch(); + const darkMode = useSelector(state => state.theme.darkMode); + const [loading, setLoading] = useState(false); + const [errors, setErrors] = useState({}); + + const [formData, setFormData] = useState({ + title: '', + type: 'Workshop', + location: 'Virtual', + startTime: moment() + .tz('America/Los_Angeles') + .format('HH:mm'), + endTime: moment() + .tz('America/Los_Angeles') + .add(1, 'hour') + .format('HH:mm'), + date: moment() + .tz('America/Los_Angeles') + .format('YYYY-MM-DD'), + description: '', + maxAttendees: 10, + coverImage: '', + }); + + const resetForm = () => { + setFormData({ + title: '', + type: 'Workshop', + location: 'Virtual', + startTime: moment() + .tz('America/Los_Angeles') + .format('HH:mm'), + endTime: moment() + .tz('America/Los_Angeles') + .add(1, 'hour') + .format('HH:mm'), + date: moment() + .tz('America/Los_Angeles') + .format('YYYY-MM-DD'), + description: '', + maxAttendees: 10, + coverImage: '', + }); + setErrors({}); + setLoading(false); + }; + + const handleToggle = () => { + if (!loading) { + toggle(); + if (!isOpen) { + resetForm(); + } + } + }; + + const handleChange = e => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + // Clear error for this field when user starts typing + if (errors[name]) { + setErrors(prev => { + const newErrors = { ...prev }; + delete newErrors[name]; + return newErrors; + }); + } + }; + + const validateForm = () => { + const newErrors = {}; + + if (!formData.title.trim()) { + newErrors.title = 'Title is required'; + } + + if (!formData.description.trim()) { + newErrors.description = 'Description is required'; + } + + if (!formData.date) { + newErrors.date = 'Date is required'; + } + + if (!formData.startTime) { + newErrors.startTime = 'Start time is required'; + } + + if (!formData.endTime) { + newErrors.endTime = 'End time is required'; + } + + if (formData.maxAttendees < 1) { + newErrors.maxAttendees = 'Max attendees must be at least 1'; + } + + // Validate that end time is after start time + if (formData.startTime && formData.endTime) { + const start = moment(`${formData.date} ${formData.startTime}`, 'YYYY-MM-DD HH:mm'); + const end = moment(`${formData.date} ${formData.endTime}`, 'YYYY-MM-DD HH:mm'); + if (end.isSameOrBefore(start)) { + newErrors.endTime = 'End time must be after start time'; + } + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async e => { + e.preventDefault(); + + if (!validateForm()) { + return; + } + + setLoading(true); + + // Format the event data according to the Event model + const eventData = { + title: formData.title.trim(), + type: formData.type, + location: formData.location, + startTime: moment(`${formData.date} ${formData.startTime}`, 'YYYY-MM-DD HH:mm') + .tz('America/Los_Angeles') + .format(), + endTime: moment(`${formData.date} ${formData.endTime}`, 'YYYY-MM-DD HH:mm') + .tz('America/Los_Angeles') + .format(), + date: moment(formData.date) + .tz('America/Los_Angeles') + .toDate(), + description: formData.description.trim(), + maxAttendees: parseInt(formData.maxAttendees, 10), + status: 'New', + isActive: true, + }; + + if (formData.coverImage.trim()) { + eventData.coverImage = formData.coverImage.trim(); + } + + try { + const result = await dispatch(createEvent(eventData)); + if (result?.success) { + handleToggle(); + // The events list will be refreshed when the component re-renders + } + } catch (error) { + // Error handling is done in the action + } finally { + setLoading(false); + } + }; + + return ( + + + Create New Event + + +
+
+ + * + + {errors.title &&
{errors.title}
} +
+ +
+ + * + +
+ +
+ + * + +
+ +
+ + * + + {errors.date &&
{errors.date}
} +
+ +
+
+ + * + + {errors.startTime &&
{errors.startTime}
} +
+ +
+ + * + + {errors.endTime &&
{errors.endTime}
} +
+
+ +
+ + * +