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:
lenhanphung
2025-10-22 16:52:38 +07:00
committed by GitHub
parent a7a4bb4507
commit 1c8826facc
4 changed files with 634 additions and 605 deletions
+30 -10
View File
@@ -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>
);