Fix duplicate calendar edits and prevent double-save submissions

This commit is contained in:
Linus Rath
2026-03-19 14:40:16 +01:00
parent 77514bd054
commit 41f91244d9
2 changed files with 12 additions and 23 deletions
+12 -6
View File
@@ -25,7 +25,7 @@ interface EventModalProps {
calendars: Calendar[]; calendars: Calendar[];
defaultDate?: Date; defaultDate?: Date;
defaultEndDate?: Date; defaultEndDate?: Date;
onSave: (data: Partial<CalendarEvent>, sendSchedulingMessages?: boolean) => void; onSave: (data: Partial<CalendarEvent>, sendSchedulingMessages?: boolean) => void | Promise<void>;
onDelete?: (id: string, sendSchedulingMessages?: boolean) => void; onDelete?: (id: string, sendSchedulingMessages?: boolean) => void;
onDuplicate?: (data: Partial<CalendarEvent>) => void; onDuplicate?: (data: Partial<CalendarEvent>) => void;
onRsvp?: (eventId: string, participantId: string, status: CalendarParticipant['participationStatus']) => void; onRsvp?: (eventId: string, participantId: string, status: CalendarParticipant['participationStatus']) => void;
@@ -209,6 +209,7 @@ export function EventModal({
return "none"; return "none";
}); });
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [attendees, setAttendees] = useState<{ name: string; email: string }[]>(() => { const [attendees, setAttendees] = useState<{ name: string; email: string }[]>(() => {
if (!event?.participants) return []; if (!event?.participants) return [];
@@ -231,9 +232,9 @@ export function EventModal({
setAttendees(prev => prev.filter(a => a.email.toLowerCase() !== email.toLowerCase())); setAttendees(prev => prev.filter(a => a.email.toLowerCase() !== email.toLowerCase()));
}, []); }, []);
const handleSave = useCallback(() => { const handleSave = useCallback(async () => {
const trimmedTitle = title.trim(); const trimmedTitle = title.trim();
if (!trimmedTitle) return; if (!trimmedTitle || isSaving) return;
if (trimmedTitle.length > 500 || description.trim().length > 10000 || location.trim().length > 500) return; if (trimmedTitle.length > 500 || description.trim().length > 10000 || location.trim().length > 500) return;
const startStr = allDay const startStr = allDay
@@ -343,8 +344,13 @@ export function EventModal({
} }
const shouldSendScheduling = attendees.length > 0 && sendInvitations; const shouldSendScheduling = attendees.length > 0 && sendInvitations;
onSave(data, shouldSendScheduling); setIsSaving(true);
}, [title, description, location, startDate, startTime, endDate, endTime, allDay, calendarId, recurrence, alert, attendees, sendInvitations, currentUserEmails, existingParticipants, event, onSave]); try {
await onSave(data, shouldSendScheduling);
} finally {
setIsSaving(false);
}
}, [title, description, location, startDate, startTime, endDate, endTime, allDay, calendarId, recurrence, alert, attendees, sendInvitations, currentUserEmails, existingParticipants, event, onSave, isSaving]);
const handleRsvp = useCallback((status: CalendarParticipant['participationStatus']) => { const handleRsvp = useCallback((status: CalendarParticipant['participationStatus']) => {
if (!event || !userParticipantId || !onRsvp) return; if (!event || !userParticipantId || !onRsvp) return;
@@ -945,7 +951,7 @@ export function EventModal({
<Button variant="outline" onClick={isEdit ? () => setMode("view") : onClose}> <Button variant="outline" onClick={isEdit ? () => setMode("view") : onClose}>
{t("form.cancel")} {t("form.cancel")}
</Button> </Button>
<Button onClick={handleSave} disabled={!title.trim()}> <Button onClick={handleSave} disabled={!title.trim() || isSaving}>
{t("form.save")} {t("form.save")}
</Button> </Button>
</div> </div>
-17
View File
@@ -142,13 +142,6 @@ export const useCalendarStore = create<CalendarStore>()(
} }
const created = await client.createCalendarEvent(cleanEvent, sendSchedulingMessages, targetAccountId); const created = await client.createCalendarEvent(cleanEvent, sendSchedulingMessages, targetAccountId);
set((state) => ({ events: [...state.events, created] })); set((state) => ({ events: [...state.events, created] }));
if (sendSchedulingMessages && created.participants) {
try {
await client.sendImipInvitation(created);
} catch (e) {
debug.error('Failed to send invitation emails:', e);
}
}
return created; return created;
} catch (error) { } catch (error) {
debug.error('Failed to create event:', error); debug.error('Failed to create event:', error);
@@ -178,16 +171,6 @@ export const useCalendarStore = create<CalendarStore>()(
set((state) => ({ set((state) => ({
events: state.events.map(e => e.id === id ? { ...e, ...updates } : e), events: state.events.map(e => e.id === id ? { ...e, ...updates } : e),
})); }));
if (sendSchedulingMessages) {
try {
const updatedEvent = await client.getCalendarEvent(realId, targetAccountId);
if (updatedEvent?.participants) {
await client.sendImipInvitation(updatedEvent);
}
} catch (e) {
debug.error('Failed to send update notification emails:', e);
}
}
} catch (error) { } catch (error) {
debug.error('Failed to update event:', error); debug.error('Failed to update event:', error);
set({ error: 'Failed to update event' }); set({ error: 'Failed to update event' });