Skip to content

Commit 3b153f8

Browse files
committed
fix(calendar): address PR #154 review feedback
- Remove dangerous Delete-key-to-discard in QuickEditInput (was invisible when text was present, Enter would silently delete the event) - Delete undo now preserves full event snapshot (attendees, recurrence, conferenceData, etc.) instead of just 7 basic fields - Cap undo stack at 20 entries to prevent unbounded growth
1 parent 5b48bdb commit 3b153f8

3 files changed

Lines changed: 16 additions & 91 deletions

File tree

templates/calendar/app/components/calendar/DayView.tsx

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ function QuickEditInput({
3838
onCancel: (eventId: string) => void;
3939
}) {
4040
const [value, setValue] = useState("");
41-
const [confirmDelete, setConfirmDelete] = useState(false);
4241
const inputRef = useRef<HTMLInputElement>(null);
4342

4443
useEffect(() => {
@@ -49,18 +48,11 @@ function QuickEditInput({
4948
<input
5049
ref={inputRef}
5150
value={value}
52-
onChange={(e) => {
53-
setValue(e.target.value);
54-
setConfirmDelete(false);
55-
}}
51+
onChange={(e) => setValue(e.target.value)}
5652
onKeyDown={(e) => {
5753
if (e.key === "Enter") {
5854
e.preventDefault();
59-
if (confirmDelete) {
60-
onCancel(eventId);
61-
} else {
62-
onSave(eventId, value);
63-
}
55+
onSave(eventId, value);
6456
} else if (e.key === "Escape") {
6557
e.preventDefault();
6658
onCancel(eventId);
@@ -70,20 +62,11 @@ function QuickEditInput({
7062
) {
7163
e.preventDefault();
7264
onCancel(eventId);
73-
} else if (e.key === "Delete" && value.trim()) {
74-
e.preventDefault();
75-
if (confirmDelete) {
76-
onCancel(eventId);
77-
} else {
78-
setConfirmDelete(true);
79-
}
8065
}
8166
e.stopPropagation();
8267
}}
8368
onBlur={() => (value.trim() ? onSave(eventId, value) : onCancel(eventId))}
84-
placeholder={
85-
confirmDelete ? "Press Delete or Enter to discard" : "(No title)"
86-
}
69+
placeholder="(No title)"
8770
className="w-full bg-transparent text-[11px] font-semibold text-foreground placeholder:text-foreground/40 outline-none leading-tight"
8871
/>
8972
);

templates/calendar/app/hooks/use-undo.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ function getSnapshot(): number {
1919
return undoStack.length;
2020
}
2121

22+
const MAX_UNDO_STACK = 20;
23+
2224
/** Push an undo action onto the stack. */
2325
export function setUndoAction(action: UndoEntry) {
2426
undoStack.push(action);
27+
if (undoStack.length > MAX_UNDO_STACK) {
28+
undoStack.splice(0, undoStack.length - MAX_UNDO_STACK);
29+
}
2530
notify();
2631
}
2732

templates/calendar/app/pages/CalendarView.tsx

Lines changed: 8 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export default function CalendarView() {
8282
const [createDialogOpen, setCreateDialogOpen] = useState(false);
8383
const [createDefaultStart, setCreateDefaultStart] = useState<string>();
8484
const [createDefaultEnd, setCreateDefaultEnd] = useState<string>();
85-
const [quickEditEventId, setQuickEditEventId] = useState<string | null>(null);
85+
// quickEditEventId removed — click-to-create now opens CreateEventPopover
8686
const [commandPaletteOpen, setCommandPaletteOpen] = useState(false);
8787
const [shortcutsHelpOpen, setShortcutsHelpOpen] = useState(false);
8888
const [deleteDialogEvent, setDeleteDialogEvent] =
@@ -194,18 +194,10 @@ export default function CalendarView() {
194194
ev.attendees && ev.attendees.filter((a) => !a.self).length > 0;
195195
const removeOnly = !isOrganizer && !!hasOtherAttendees;
196196

197-
// Snapshot for undo
198-
const snapshot = { ...ev };
197+
// Snapshot for undo — preserve all event fields so undo recreates faithfully
198+
const { id: _id, source: _source, ...snapshot } = ev;
199199
const undo = () => {
200-
createEvent.mutate({
201-
title: snapshot.title,
202-
description: snapshot.description ?? "",
203-
location: snapshot.location ?? "",
204-
start: snapshot.start,
205-
end: snapshot.end,
206-
allDay: snapshot.allDay ?? false,
207-
color: snapshot.color,
208-
});
200+
createEvent.mutate(snapshot);
209201
};
210202

211203
deleteEvent.mutate(
@@ -328,59 +320,10 @@ export default function CalendarView() {
328320
startTime: string,
329321
endTime: string,
330322
) {
331-
const dateStr = format(clickedDate, "yyyy-MM-dd");
332-
const startISO = new Date(`${dateStr}T${startTime}:00`).toISOString();
333-
const endISO = new Date(`${dateStr}T${endTime}:00`).toISOString();
334-
const tempId = `temp-${Date.now()}`;
335-
336-
createEvent.mutate(
337-
{
338-
title: "(No title)",
339-
description: "",
340-
location: "",
341-
start: startISO,
342-
end: endISO,
343-
allDay: false,
344-
_tempId: tempId,
345-
},
346-
{
347-
onSuccess: (result) => {
348-
// Synchronously swap the optimistic temp event for the real one
349-
// in the cache so the inline input stays mounted when we update
350-
// quickEditEventId to the real ID.
351-
const { _tempId, ...realEvent } = result;
352-
queryClient.setQueriesData<CalendarEvent[]>(
353-
{ queryKey: ["events"] },
354-
(old) =>
355-
old?.map((e) => (e.id === _tempId ? { ...e, ...realEvent } : e)),
356-
);
357-
setQuickEditEventId(realEvent.id);
358-
},
359-
onError: () => {
360-
setQuickEditEventId(null);
361-
toast.error("Failed to create event");
362-
},
363-
},
364-
);
365-
366-
// Immediately show inline editor on the optimistic event
367-
setQuickEditEventId(tempId);
368-
}
369-
370-
function handleQuickEditSave(eventId: string, title: string) {
371-
setQuickEditEventId(null);
372-
if (title.trim() && title.trim() !== "(No title)") {
373-
updateEvent.mutate({ id: eventId, title: title.trim() });
374-
}
375-
}
376-
377-
function handleQuickEditCancel(eventId: string) {
378-
setQuickEditEventId(null);
379-
// Delete the event if title was never set
380-
const ev = events.find((e) => e.id === eventId);
381-
if (!ev || ev.title === "(No title)") {
382-
deleteEvent.mutate({ id: eventId, scope: "single", sendUpdates: "none" });
383-
}
323+
setSelectedDate(clickedDate);
324+
setCreateDefaultStart(startTime);
325+
setCreateDefaultEnd(endTime);
326+
setCreateDialogOpen(true);
384327
}
385328

386329
// IconKeyboard shortcuts — don't fire when user is typing in an input
@@ -688,9 +631,6 @@ export default function CalendarView() {
688631
onDeleteEvent={handleDeleteEvent}
689632
onEventTimeChange={handleEventTimeChange}
690633
onClickTimeSlot={handleClickTimeSlot}
691-
quickEditEventId={quickEditEventId}
692-
onQuickEditSave={handleQuickEditSave}
693-
onQuickEditCancel={handleQuickEditCancel}
694634
isLoading={eventsLoading}
695635
/>
696636
)}
@@ -702,9 +642,6 @@ export default function CalendarView() {
702642
onDeleteEvent={handleDeleteEvent}
703643
onEventTimeChange={handleEventTimeChange}
704644
onClickTimeSlot={handleClickTimeSlot}
705-
quickEditEventId={quickEditEventId}
706-
onQuickEditSave={handleQuickEditSave}
707-
onQuickEditCancel={handleQuickEditCancel}
708645
isLoading={eventsLoading}
709646
/>
710647
)}

0 commit comments

Comments
 (0)