Form validation for event create/update (#218)
* refactor: migrate EventModal to use shared EventFormFields component * feat: add form validation for event create/update - Validate title is required - Validate end datetime must be after start datetime - Disable save button when form is invalid - Show error messages below invalid fields with red highlight (MUI) - Title validation in create mode only shows error after field is touched - Track touched state to avoid showing errors on initial load * feat: improve event modal UX with auto-focus and default times - Auto-focus title field when opening create/update event modal - Set default datetime when creating event without selecting range: * Start time = current time + 1 hour (rounded to :00) * End time = start time + 1 hour - Applied duplicate event to display datetime from original event - Fix logic to detect empty event object vs valid event with uid * fix: improve validation UX and add required field indicator - Only show validation errors when modal is open (isOpen check) - Add red asterisk (*) to Title label to indicate required field - Prevent validation error flash when closing modal - Fix TypeScript errors in EventUpdateModal - Keep validation behavior: only show error after user types and deletes * feat: auto-focus title field when toggling between normal and extended mode in event modal * update: only validation when click on save button * fix: prevent form data loss and validation errors in event modals Fixes: 1. Form fields no longer clear when changing start/end dates 2. Extended mode stays active when modifying dates 3. No validation errors shown after successful save
This commit is contained in:
@@ -84,9 +84,9 @@ function EventUpdateModal({
|
||||
const calendarsList = useAppSelector((state) => state.calendars.list);
|
||||
|
||||
const userPersonnalCalendars: Calendars[] = useMemo(() => {
|
||||
const allCalendars = Object.values(calendarsList);
|
||||
const allCalendars = Object.values(calendarsList) as Calendars[];
|
||||
return allCalendars.filter(
|
||||
(c) => c.id?.split("/")[0] === user.userData?.openpaasId
|
||||
(c: Calendars) => c.id?.split("/")[0] === user.userData?.openpaasId
|
||||
);
|
||||
}, [calendarsList, user.userData?.openpaasId]);
|
||||
|
||||
@@ -158,6 +158,8 @@ function EventUpdateModal({
|
||||
const [attendees, setAttendees] = useState<userAttendee[]>([]);
|
||||
const [hasVideoConference, setHasVideoConference] = useState(false);
|
||||
const [meetingLink, setMeetingLink] = useState<string | null>(null);
|
||||
const [isFormValid, setIsFormValid] = useState(false);
|
||||
const [showValidationErrors, setShowValidationErrors] = useState(false);
|
||||
|
||||
const resetAllStateToDefault = useCallback(() => {
|
||||
setShowMore(false);
|
||||
@@ -188,6 +190,9 @@ function EventUpdateModal({
|
||||
// Initialize form state when event data is available
|
||||
useEffect(() => {
|
||||
if (event && open) {
|
||||
// Reset validation errors when modal opens
|
||||
setShowValidationErrors(false);
|
||||
|
||||
// Editing existing event - populate fields with event data
|
||||
setTitle(event.title ?? "");
|
||||
setDescription(event.description ?? "");
|
||||
@@ -258,7 +263,8 @@ function EventUpdateModal({
|
||||
setAttendees(
|
||||
event.attendee
|
||||
? event.attendee.filter(
|
||||
(a) => a.cal_address !== event.organizer?.cal_address
|
||||
(a: userAttendee) =>
|
||||
a.cal_address !== event.organizer?.cal_address
|
||||
)
|
||||
: []
|
||||
);
|
||||
@@ -302,11 +308,21 @@ function EventUpdateModal({
|
||||
|
||||
const handleClose = () => {
|
||||
closeModal();
|
||||
setShowValidationErrors(false);
|
||||
resetAllStateToDefault();
|
||||
setFreshEvent(null);
|
||||
initializedKeyRef.current = null;
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
// Show validation errors when Save is clicked
|
||||
setShowValidationErrors(true);
|
||||
|
||||
// Check if form is valid before saving
|
||||
if (!isFormValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!event) return;
|
||||
|
||||
const organizer = event.organizer;
|
||||
@@ -317,6 +333,9 @@ function EventUpdateModal({
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset validation state when validation passes
|
||||
setShowValidationErrors(false);
|
||||
|
||||
// Handle recurrence instances
|
||||
const [baseUID, recurrenceId] = event.uid.split("/");
|
||||
|
||||
@@ -507,7 +526,7 @@ function EventUpdateModal({
|
||||
})
|
||||
)
|
||||
.unwrap()
|
||||
.catch((error) => {
|
||||
.catch((error: any) => {
|
||||
dispatch(updateEventLocal({ calId, event: oldEvent }));
|
||||
showErrorNotification("Failed to update event. Changes reverted.");
|
||||
});
|
||||
@@ -666,12 +685,10 @@ function EventUpdateModal({
|
||||
</Button>
|
||||
)}
|
||||
<Box display="flex" gap={1} ml={showMore ? "auto" : 0}>
|
||||
{showMore && (
|
||||
<Button variant="outlined" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<Button variant="contained" onClick={handleSave} disabled={!title}>
|
||||
<Button variant="outlined" onClick={handleClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave}>
|
||||
Save
|
||||
</Button>
|
||||
</Box>
|
||||
@@ -726,6 +743,7 @@ function EventUpdateModal({
|
||||
setShowDescription={setShowDescription}
|
||||
showRepeat={typeOfAction !== "solo" && showRepeat}
|
||||
setShowRepeat={setShowRepeat}
|
||||
isOpen={open}
|
||||
userPersonnalCalendars={userPersonnalCalendars}
|
||||
timezoneList={timezoneList}
|
||||
onCalendarChange={(newCalendarId) => {
|
||||
@@ -734,6 +752,8 @@ function EventUpdateModal({
|
||||
setNewCalId(selectedCalendar.id);
|
||||
}
|
||||
}}
|
||||
onValidationChange={setIsFormValid}
|
||||
showValidationErrors={showValidationErrors}
|
||||
/>
|
||||
</ResponsiveDialog>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user