From 00742119bece127a6543645e77b4bc51174d3252 Mon Sep 17 00:00:00 2001 From: Camille Moussu <66134347+Eriikah@users.noreply.github.com> Date: Wed, 21 Jan 2026 15:17:49 +0100 Subject: [PATCH] [#446] removed direct state update (#468) --- .../features/Calendars/CalendarSlice.test.tsx | 67 +- .../features/Events/EventDisplay.test.tsx | 8 - .../features/Events/EventRepetition.test.tsx | 55 -- __test__/features/Events/TempUpdate.test.tsx | 859 ------------------ __test__/utils/findCalendarById.test.ts | 6 +- src/components/Calendar/Calendar.tsx | 7 - src/components/Calendar/CalendarModal.tsx | 15 +- .../Calendar/handlers/eventHandlers.ts | 87 +- .../hooks/useCalendarEventHandlers.ts | 7 - .../Calendar/utils/calendarUtils.ts | 32 - .../Event/eventHandlers/eventHandlers.ts | 16 +- src/features/Calendars/CalendarSlice.ts | 94 +- .../services/addSharedCalendarAsync.ts | 2 +- .../Calendars/services/createCalendarAsync.ts | 25 +- .../Calendars/services/deleteEventAsync.ts | 2 +- .../services/deleteEventInstanceAsync.ts | 2 +- .../services/getCalendarDetailAsync.ts | 2 +- .../services/getCalendarsListAsync.ts | 2 +- .../Calendars/services/getEventAsync.ts | 2 +- .../services/getTempCalendarsListAsync.ts | 11 +- .../services/importEventFromFileAsync.ts | 2 +- .../Calendars/services/moveEventAsync.ts | 27 +- .../services/patchACLCalendarAsync.ts | 2 +- .../Calendars/services/patchCalendarAsync.ts | 2 +- .../Calendars/services/putEventAsync.ts | 35 +- .../Calendars/services/refreshCalendar.ts | 2 +- .../Calendars/services/removeCalendarAsync.ts | 2 +- .../services/updateEventInstanceAsync.ts | 2 +- .../Calendars/services/updateSeriesAsync.ts | 2 +- src/features/Calendars/types/RejectedError.ts | 6 + .../AttendanceValidation.tsx | 4 - .../AttendanceValidation/RSVPButton.tsx | 4 - .../AttendanceValidation/handleRSVPClick.tsx | 12 +- src/features/Events/EventDisplayPreview.tsx | 20 +- src/features/Events/EventModal.tsx | 8 +- src/features/Events/EventUpdateModal.tsx | 34 +- src/utils/findCalendarById.ts | 6 +- src/websocket/WebSocketGate.tsx | 14 + 38 files changed, 119 insertions(+), 1366 deletions(-) delete mode 100644 __test__/features/Events/TempUpdate.test.tsx create mode 100644 src/features/Calendars/types/RejectedError.ts diff --git a/__test__/features/Calendars/CalendarSlice.test.tsx b/__test__/features/Calendars/CalendarSlice.test.tsx index 2ef2aff..6698a04 100644 --- a/__test__/features/Calendars/CalendarSlice.test.tsx +++ b/__test__/features/Calendars/CalendarSlice.test.tsx @@ -396,6 +396,22 @@ describe("CalendarSlice", () => { it("createCalendarAsync.fulfilled adds a new calendar", () => { const payload = { + userData: { + openpaasId: "u1", + family_name: "Owner", + email: "o@example.com", + sid: "", + sub: "", + given_name: "Test", + name: "Test Owner", + }, + calId: "cal1", + color: { "apple:color": "#f00" }, + name: "Test", + desc: "Desc", + }; + + const payloadResponse = { userId: "u1", calId: "cal1", color: { "apple:color": "#f00" }, @@ -406,7 +422,7 @@ describe("CalendarSlice", () => { }; const state = reducer( initialState, - createCalendarAsync.fulfilled(payload, "req4", payload) + createCalendarAsync.fulfilled(payloadResponse, "req4", payload) ); expect(state.list["u1/cal1"].name).toBe("Test"); expect(state.list["u1/cal1"].color?.["apple:color"]).toBe("#f00"); @@ -441,28 +457,6 @@ describe("CalendarSlice", () => { expect(state.list["c1"].name).toBe("Shared"); }); - it("deleteEventAsync.fulfilled removes event", () => { - const calId = "c1"; - const prev = { - ...initialState, - list: { - [calId]: { - id: calId, - events: { e1: { uid: "e1" } }, - } as unknown as Calendar, - }, - }; - const state = reducer( - prev, - deleteEventAsync.fulfilled({ calId, eventId: "e1" }, "req6", { - calId, - eventId: "e1", - eventURL: "e1.ics", - }) - ); - expect(state.list[calId].events).toEqual({}); - }); - it("getTempCalendarsListAsync.fulfilled updates templist", () => { const payload = { t1: { @@ -490,19 +484,6 @@ describe("CalendarSlice", () => { expect(state.templist.t1.name).toBe("Temp"); }); - it("putEventAsync.fulfilled updates calendar events", () => { - const cal = { id: "c1", events: {} } as any; - const payload = { calId: "c1", events: [{ uid: "e1" }] as any[] }; - const state = reducer( - initialState, - putEventAsync.fulfilled(payload, "req8", { - cal, - newEvent: { uid: "e1" } as CalendarEvent, - }) - ); - expect(state.list.c1.events.e1.uid).toBe("e1"); - }); - it("getEventAsync.fulfilled adds single event", () => { const payload = { calId: "c1", event: { uid: "e1" } as any }; const state = reducer( @@ -512,20 +493,6 @@ describe("CalendarSlice", () => { expect(state.list.c1.events.e1.uid).toBe("e1"); }); - it("moveEventAsync.fulfilled updates events after move", () => { - const cal = { id: "c1", events: {} } as any; - const payload = { calId: "c1", events: [{ uid: "e1" }] as any[] }; - const state = reducer( - initialState, - moveEventAsync.fulfilled(payload, "req10", { - cal, - newEvent: { uid: "e1" } as CalendarEvent, - newURL: "e1.ics", - }) - ); - expect(state.list.c1.events.e1.uid).toBe("e1"); - }); - it("getCalendarDetailAsync.fulfilled adds calendar events", () => { const payload = { calId: "c1", events: [{ uid: "e1" }] as any[] }; const state = reducer( diff --git a/__test__/features/Events/EventDisplay.test.tsx b/__test__/features/Events/EventDisplay.test.tsx index c3f4648..26aeeb7 100644 --- a/__test__/features/Events/EventDisplay.test.tsx +++ b/__test__/features/Events/EventDisplay.test.tsx @@ -1832,7 +1832,6 @@ describe("Event Full Display", () => { (promise as any).unwrap = () => promise; return () => promise as any; }); - const spyRemove = jest.spyOn(calendarSlice, "removeEvent"); const testDate = new Date("2025-01-15T10:00:00.000Z"); const testEndDate = new Date("2025-01-15T11:00:00.000Z"); @@ -1902,13 +1901,6 @@ describe("Event Full Display", () => { }, { timeout: 3000 } ); - - await waitFor( - () => { - expect(spyRemove).toHaveBeenCalled(); - }, - { timeout: 3000 } - ); }); it("edit modal displays event time in original event timezone", () => { diff --git a/__test__/features/Events/EventRepetition.test.tsx b/__test__/features/Events/EventRepetition.test.tsx index 389c1b8..d04f73b 100644 --- a/__test__/features/Events/EventRepetition.test.tsx +++ b/__test__/features/Events/EventRepetition.test.tsx @@ -784,61 +784,6 @@ describe("Event Drag and Drop - Recurring Events", () => { expect(mockSetOpenEditModePopup).toHaveBeenCalledWith("edit"); expect(mockSetAfterChoiceFunc).toHaveBeenCalled(); }); - - it("directly updates non-recurring event on drag", () => { - const mockDispatch = jest.fn(); - const nonRecurringState = { - "667037022b752d0026472254/cal1": { - ...basePreloadedState.calendars.list["667037022b752d0026472254/cal1"], - events: { - "single-event": { - uid: "single-event", - title: "Single Event", - calId: "667037022b752d0026472254/cal1", - start: day.toISOString(), - end: new Date("2025-03-15T11:00:00Z").toISOString(), - timezone: "UTC", - }, - }, - }, - }; - - const eventHandlers = createEventHandlers({ - setSelectedRange: jest.fn(), - setOpenEventModal: jest.fn(), - setTempEvent: jest.fn(), - setOpenEventDisplay: jest.fn(), - dispatch: mockDispatch, - calendarRange: { start: new Date(), end: new Date() }, - setEventDisplayedId: jest.fn(), - setEventDisplayedCalId: jest.fn(), - setEventDisplayedTemp: jest.fn(), - calendars: nonRecurringState, - setSelectedEvent: jest.fn(), - setAfterChoiceFunc: jest.fn(), - setOpenEditModePopup: jest.fn(), - } as unknown as EventHandlersProps); - - const mockArg = { - event: { - _def: { - extendedProps: { - uid: "single-event", - calId: "667037022b752d0026472254/cal1", - }, - }, - }, - delta: { years: 0, months: 0, days: 1, milliseconds: 0 }, - }; - - eventHandlers.handleEventDrop(mockArg); - - expect(mockDispatch).toHaveBeenCalledWith( - expect.objectContaining({ - type: expect.stringContaining("updateEventLocal"), - }) - ); - }); }); describe("Event Resize - Recurring Events", () => { diff --git a/__test__/features/Events/TempUpdate.test.tsx b/__test__/features/Events/TempUpdate.test.tsx deleted file mode 100644 index 39bb39f..0000000 --- a/__test__/features/Events/TempUpdate.test.tsx +++ /dev/null @@ -1,859 +0,0 @@ -import * as appHooks from "@/app/hooks"; -import CalendarLayout from "@/components/Calendar/CalendarLayout"; -import * as calendarUtils from "@/components/Calendar/utils/calendarUtils"; -import * as eventUtils from "@/components/Event/utils/eventUtils"; -import * as CalendarSlice from "@/features/Calendars/CalendarSlice"; -import { Calendar } from "@/features/Calendars/CalendarTypes"; -import * as eventThunks from "@/features/Calendars/services"; -import EventPreviewModal from "@/features/Events/EventDisplayPreview"; -import EventPopover from "@/features/Events/EventModal"; -import { CalendarEvent } from "@/features/Events/EventsTypes"; -import EventUpdateModal from "@/features/Events/EventUpdateModal"; -import * as userApi from "@/features/User/userAPI"; -import { DateSelectArg } from "@fullcalendar/core"; -import { jest } from "@jest/globals"; -import { ThunkDispatch } from "@reduxjs/toolkit"; -import "@testing-library/jest-dom"; -import { act, fireEvent, screen, waitFor } from "@testing-library/react"; -import { SpiedFunction } from "jest-mock"; -import { renderWithProviders } from "../../utils/Renderwithproviders"; - -describe("Update tempcalendars called with correct params", () => { - const today = new Date(); - const start = new Date(today); - start.setHours(10, 0, 0, 0); - const end = new Date(today); - end.setHours(11, 0, 0, 0); - - let refreshCalendarsSpy: SpiedFunction< - ( - dispatch: ThunkDispatch, - calendars: Calendar[], - calendarRange: { start: Date; end: Date }, - calType?: "temp" - ) => Promise - >; - let refreshSingularCalendarSpy: SpiedFunction< - ( - dispatch: ThunkDispatch, - calendar: Calendar, - calendarRange: { start: Date; end: Date }, - calType?: "temp" - ) => Promise - >; - let updateTempCalendarSpy: SpiedFunction< - ( - tempcalendars: Record, - event: CalendarEvent, - dispatch: ThunkDispatch, - calendarRange: { start: Date; end: Date } - ) => Promise - >; - - beforeEach(() => { - jest.clearAllMocks(); - const dispatch = jest.fn((thunk) => { - if (typeof thunk === "function") { - return thunk(dispatch, () => ({}), undefined); - } - return thunk; - }) as ThunkDispatch; - jest.spyOn(appHooks, "useAppDispatch").mockReturnValue(dispatch); - refreshCalendarsSpy = jest - .spyOn(eventUtils, "refreshCalendars") - .mockResolvedValue(); - updateTempCalendarSpy = jest.spyOn(calendarUtils, "updateTempCalendar"); - refreshSingularCalendarSpy = jest.spyOn( - eventUtils, - "refreshSingularCalendar" - ); - }); - - afterEach(() => { - refreshCalendarsSpy.mockRestore(); - updateTempCalendarSpy.mockRestore(); - refreshSingularCalendarSpy.mockRestore(); - }); - - const createPreloadedState = (withTempList = true) => ({ - user: { - userData: { - sub: "test", - email: "owner@test.com", - sid: "mockSid", - openpaasId: "667037022b752d0026472254", - }, - organiserData: { - cn: "Owner", - cal_address: "owner@test.com", - }, - tokens: { - accessToken: "token", - }, - }, - calendars: { - list: { - "667037022b752d0026472254/cal1": { - name: "Calendar 1", - id: "667037022b752d0026472254/cal1", - color: "#FF0000", - ownerEmails: ["owner@test.com"], - events: { - event1: { - id: "event1", - calId: "667037022b752d0026472254/cal1", - uid: "event1", - title: "Test Event", - start: start.toISOString(), - end: end.toISOString(), - attendee: [{ cal_address: "attendee@test.com" }], - organizer: { - cn: "Owner", - cal_address: "owner@test.com", - }, - }, - }, - }, - }, - templist: withTempList - ? { - temp1: { - id: "temp1", - name: "Temp Calendar", - color: "#00FF00", - events: {}, - ownerEmails: ["attendee@test.com"], - }, - } - : undefined, - pending: false, - }, - }); - - it("should call updateTempCalendar with correct params after event deletion", async () => { - const preloadedState = createPreloadedState(true); - - const mockOnClose = jest.fn(); - renderWithProviders( - , - preloadedState - ); - - await waitFor(() => { - fireEvent.click(screen.getByTestId("MoreVertIcon")); - expect(screen.getByText("eventPreview.deleteEvent")).toBeInTheDocument(); - }); - - const deleteMenuItem = screen.getByText("eventPreview.deleteEvent"); - fireEvent.click(deleteMenuItem); - - await waitFor(() => - expect(updateTempCalendarSpy).toHaveBeenCalledWith( - expect.objectContaining({ - temp1: expect.objectContaining({ id: "temp1" }), - }), - expect.objectContaining({ - attendee: [ - expect.objectContaining({ cal_address: "attendee@test.com" }), - ], - }), - expect.any(Function), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }) - ) - ); - - await waitFor(() => - expect(refreshSingularCalendarSpy).toHaveBeenCalledWith( - expect.any(Function), - expect.objectContaining({ id: "temp1" }), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }), - "temp" - ) - ); - }); - - it("should NOT call updateTempCalendar when templist is undefined", async () => { - const preloadedState = createPreloadedState(false); - - const mockOnClose = jest.fn(); - renderWithProviders( - , - preloadedState - ); - - await waitFor(() => { - fireEvent.click(screen.getByTestId("MoreVertIcon")); - expect(screen.getByText("eventPreview.deleteEvent")).toBeInTheDocument(); - }); - - const deleteMenuItem = screen.getByText("eventPreview.deleteEvent"); - fireEvent.click(deleteMenuItem); - - await waitFor(() => { - expect(updateTempCalendarSpy).not.toHaveBeenCalled(); - expect(refreshSingularCalendarSpy).not.toHaveBeenCalled(); - }); - }); - - it("should call updateTempCalendar with correct params after event creation", async () => { - const preloadedState = createPreloadedState(true); - const defaultSelectedRange = { - startStr: "2025-07-18T09:00Z", - endStr: "2025-07-18T10:00Z", - start: new Date("2025-07-18T09:00Z"), - end: new Date("2025-07-18T10:00Z"), - allDay: false, - resource: undefined, - } as unknown as DateSelectArg; - jest.spyOn(userApi, "searchUsers"); - - // Mock putEventAsync to return success with unwrap - const putEventAsyncMock = jest - .spyOn(eventThunks, "putEventAsync") - .mockImplementation((payload) => { - const action = { - type: "calendars/putEvent/fulfilled", - payload: { - calId: payload.cal.id, - events: [], - calType: payload.calType, - }, - unwrap: () => - Promise.resolve({ - calId: payload.cal.id, - events: [], - calType: payload.calType, - }), - }; - const promise = Promise.resolve(action) as any; - return () => promise; - }); - - renderWithProviders( - , - preloadedState - ); - - await waitFor(() => { - expect(screen.getByRole("dialog")).toBeInTheDocument(); - }); - - const titleInput = screen.getByLabelText(/title/i); - fireEvent.change(titleInput, { target: { value: "New Event" } }); - - const attendeeInput = screen.getByLabelText(/peopleSearch.label/i); - fireEvent.change(attendeeInput, { target: { value: "attendee@test.com" } }); - fireEvent.keyDown(attendeeInput, { key: "Enter", code: "Enter" }); - - const saveButton = screen.getByText(/save/i); - await act(async () => { - fireEvent.click(saveButton); - }); - - await waitFor( - () => - expect(updateTempCalendarSpy).toHaveBeenCalledWith( - expect.objectContaining({ - temp1: expect.objectContaining({ id: "temp1" }), - }), - expect.objectContaining({ - title: "New Event", - }), - expect.any(Function), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }) - ), - { timeout: 3000 } - ); - - await waitFor(() => - expect(refreshSingularCalendarSpy).toHaveBeenCalledWith( - expect.any(Function), - expect.objectContaining({ id: "temp1" }), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }), - "temp" - ) - ); - }); - - it("should call updateTempCalendar with correct params after event update", async () => { - const preloadedState = createPreloadedState(true); - - const mockOnClose = jest.fn(); - renderWithProviders( - , - preloadedState - ); - - await waitFor(() => { - expect(screen.getByRole("dialog")).toBeInTheDocument(); - }); - - const saveButton = screen.getByText(/save/i); - fireEvent.click(saveButton); - - await waitFor(() => - expect(updateTempCalendarSpy).toHaveBeenCalledWith( - expect.objectContaining({ - temp1: expect.objectContaining({ id: "temp1" }), - }), - expect.objectContaining({ - attendee: [ - expect.objectContaining({ cal_address: "attendee@test.com" }), - ], - }), - expect.any(Function), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }) - ) - ); - - await waitFor(() => - expect(refreshSingularCalendarSpy).toHaveBeenCalledWith( - expect.any(Function), - expect.objectContaining({ id: "temp1" }), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }), - "temp" - ) - ); - }); - - it("should call updateTempCalendar with correct params after recurring event instance update", async () => { - const preloadedStateWithRecurring = { - ...createPreloadedState(true), - calendars: { - ...createPreloadedState(true).calendars, - list: { - "667037022b752d0026472254/cal1": { - name: "Calendar 1", - id: "667037022b752d0026472254/cal1", - color: "#FF0000", - ownerEmails: ["owner@test.com"], - events: { - event1: { - id: "event1", - calId: "667037022b752d0026472254/cal1", - uid: "event1", - title: "Recurring Event", - start: start.toISOString(), - end: end.toISOString(), - attendee: [{ cal_address: "attendee@test.com" }], - rrule: { - freq: "WEEKLY", - }, - organizer: { - cn: "Owner", - cal_address: "owner@test.com", - }, - }, - }, - }, - }, - }, - }; - - const mockOnClose = jest.fn(); - renderWithProviders( - , - preloadedStateWithRecurring - ); - - await waitFor(() => { - expect(screen.getByRole("dialog")).toBeInTheDocument(); - }); - - const saveButton = screen.getByText(/save/i); - fireEvent.click(saveButton); - - await waitFor(() => - expect(updateTempCalendarSpy).toHaveBeenCalledWith( - expect.objectContaining({ - temp1: expect.objectContaining({ id: "temp1" }), - }), - expect.objectContaining({ - attendee: [ - expect.objectContaining({ cal_address: "attendee@test.com" }), - ], - }), - expect.any(Function), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }) - ) - ); - - await waitFor(() => - expect(refreshSingularCalendarSpy).toHaveBeenCalledWith( - expect.any(Function), - expect.objectContaining({ id: "temp1" }), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }), - "temp" - ) - ); - }); - - it("should call updateTempCalendar with correct params after recurring series update", async () => { - const preloadedStateWithRecurring = { - ...createPreloadedState(true), - calendars: { - ...createPreloadedState(true).calendars, - list: { - "667037022b752d0026472254/cal1": { - name: "Calendar 1", - id: "667037022b752d0026472254/cal1", - color: "#FF0000", - ownerEmails: ["owner@test.com"], - events: { - event1: { - id: "event1", - calId: "667037022b752d0026472254/cal1", - uid: "event1", - title: "Recurring Event", - start: start.toISOString(), - end: end.toISOString(), - attendee: [{ cal_address: "attendee@test.com" }], - rrule: { - freq: "DAILY", - }, - organizer: { - cn: "Owner", - cal_address: "owner@test.com", - }, - }, - }, - }, - }, - }, - }; - - const mockOnClose = jest.fn(); - renderWithProviders( - , - preloadedStateWithRecurring - ); - - await waitFor(() => { - expect(screen.getByRole("dialog")).toBeInTheDocument(); - }); - - const saveButton = screen.getByText(/save/i); - fireEvent.click(saveButton); - - await waitFor(() => - expect(updateTempCalendarSpy).toHaveBeenCalledWith( - expect.objectContaining({ - temp1: expect.objectContaining({ id: "temp1" }), - }), - expect.objectContaining({ - attendee: [ - expect.objectContaining({ cal_address: "attendee@test.com" }), - ], - }), - expect.any(Function), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }) - ) - ); - - await waitFor(() => - expect(refreshSingularCalendarSpy).toHaveBeenCalledWith( - expect.any(Function), - expect.objectContaining({ id: "temp1" }), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }), - "temp" - ) - ); - }); - - it("should call refreshCalendars with 'temp' param in CalendarLayout handleRefresh", async () => { - const preloadedState = createPreloadedState(true); - - renderWithProviders(, preloadedState); - - fireEvent.click(screen.getByTestId("RefreshIcon")); - - await waitFor(() => { - // Should call refreshCalendars for regular calendars - expect(refreshCalendarsSpy).toHaveBeenCalledWith( - expect.any(Function), - expect.any(Array), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }) - ); - - // Should also call refreshCalendars with 'temp' for templist - expect(refreshCalendarsSpy).toHaveBeenCalledWith( - expect.any(Function), - expect.arrayContaining([expect.objectContaining({ id: "temp1" })]), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }), - "temp" - ); - }); - }); - - it("should only refresh temp calendars where attendees match ownerEmails", async () => { - const preloadedStateMultipleTempCals = { - ...createPreloadedState(true), - calendars: { - ...createPreloadedState(true).calendars, - templist: { - temp1: { - id: "temp1", - name: "Temp Calendar 1", - color: "#00FF00", - events: {}, - ownerEmails: ["attendee@test.com"], // matches event attendee - }, - temp2: { - id: "temp2", - name: "Temp Calendar 2", - color: "#0000FF", - events: {}, - ownerEmails: ["other@test.com"], // does NOT match event attendee - }, - }, - }, - }; - - const mockOnClose = jest.fn(); - renderWithProviders( - , - preloadedStateMultipleTempCals - ); - - await waitFor(() => { - fireEvent.click(screen.getByTestId("MoreVertIcon")); - expect(screen.getByText("eventPreview.deleteEvent")).toBeInTheDocument(); - }); - - const deleteMenuItem = screen.getByText("eventPreview.deleteEvent"); - fireEvent.click(deleteMenuItem); - - await waitFor(() => { - // Should only refresh temp1, not temp2 - const temp1Calls = refreshSingularCalendarSpy.mock.calls.filter( - (call) => call[1]?.id === "temp1" - ); - const temp2Calls = refreshSingularCalendarSpy.mock.calls.filter( - (call) => call[1]?.id === "temp2" - ); - - expect(temp1Calls.length).toBeGreaterThan(0); - expect(temp2Calls.length).toBe(0); - }); - }); - - it("should call emptyEventsCal with correct params before refreshing", async () => { - const emptyEventsCalSpy = jest.spyOn(CalendarSlice, "emptyEventsCal"); - - const preloadedState = createPreloadedState(true); - - const mockOnClose = jest.fn(); - renderWithProviders( - , - preloadedState - ); - - await waitFor(() => { - fireEvent.click(screen.getByTestId("MoreVertIcon")); - expect(screen.getByText("eventPreview.deleteEvent")).toBeInTheDocument(); - }); - - const deleteMenuItem = screen.getByText("eventPreview.deleteEvent"); - fireEvent.click(deleteMenuItem); - - await waitFor(() => { - // emptyEventsCal should be called with calType: "temp" for singular calendar refresh - expect(emptyEventsCalSpy).toHaveBeenCalledWith( - expect.objectContaining({ - calId: "temp1", - calType: "temp", - }) - ); - }); - - emptyEventsCalSpy.mockRestore(); - }); - - it("should handle temp calendar with multiple ownerEmails where one matches", async () => { - const preloadedStateMultipleOwners = { - ...createPreloadedState(true), - calendars: { - ...createPreloadedState(true).calendars, - list: { - "667037022b752d0026472254/cal1": { - name: "Calendar 1", - id: "667037022b752d0026472254/cal1", - color: "#FF0000", - ownerEmails: ["owner@test.com"], - events: { - event1: { - id: "event1", - calId: "667037022b752d0026472254/cal1", - uid: "event1", - title: "Test Event", - start: start.toISOString(), - end: end.toISOString(), - attendee: [{ cal_address: "attendee@test.com" }], - organizer: { - cn: "Owner", - cal_address: "owner@test.com", - }, - }, - }, - }, - }, - templist: { - temp1: { - id: "temp1", - name: "Shared Calendar", - color: "#00FF00", - events: {}, - ownerEmails: [ - "someoneelse@test.com", - "attendee@test.com", // matches - "anotherperson@test.com", - ], - }, - }, - }, - }; - - const mockOnClose = jest.fn(); - renderWithProviders( - , - preloadedStateMultipleOwners - ); - - await waitFor(() => { - fireEvent.click(screen.getByTestId("MoreVertIcon")); - expect(screen.getByText("eventPreview.deleteEvent")).toBeInTheDocument(); - }); - - const deleteMenuItem = screen.getByText("eventPreview.deleteEvent"); - fireEvent.click(deleteMenuItem); - - await waitFor(() => { - // Should still refresh temp1 because one ownerEmail matches - const temp1Calls = refreshSingularCalendarSpy.mock.calls.filter( - (call) => call[1]?.id === "temp1" - ); - - expect(temp1Calls.length).toBeGreaterThan(0); - expect(temp1Calls[0][3]).toBe("temp"); - }); - }); - it("should update multiple temp calendars when event has multiple attendees matching different ownerEmails", async () => { - const preloadedStateMultipleAttendees = { - ...createPreloadedState(true), - calendars: { - ...createPreloadedState(true).calendars, - list: { - "667037022b752d0026472254/cal1": { - name: "Calendar 1", - id: "667037022b752d0026472254/cal1", - color: "#FF0000", - ownerEmails: ["owner@test.com"], - events: { - event1: { - id: "event1", - calId: "667037022b752d0026472254/cal1", - uid: "event1", - title: "Test Event", - start: start.toISOString(), - end: end.toISOString(), - attendee: [ - { cal_address: "attendee1@test.com" }, - { cal_address: "attendee2@test.com" }, - { cal_address: "attendee3@test.com" }, - ], - organizer: { - cn: "Owner", - cal_address: "owner@test.com", - }, - }, - }, - }, - }, - templist: { - temp1: { - id: "temp1", - name: "Temp Calendar 1", - color: "#00FF00", - events: {}, - ownerEmails: ["attendee1@test.com"], - }, - temp2: { - id: "temp2", - name: "Temp Calendar 2", - color: "#0000FF", - events: {}, - ownerEmails: ["attendee2@test.com"], - }, - temp3: { - id: "temp3", - name: "Temp Calendar 3", - color: "#FF00FF", - events: {}, - ownerEmails: ["attendee3@test.com"], - }, - temp4: { - id: "temp4", - name: "Temp Calendar 4", - color: "#FFFF00", - events: {}, - ownerEmails: ["nonmatching@test.com"], // should NOT be refreshed - }, - }, - }, - }; - - const mockOnClose = jest.fn(); - renderWithProviders( - , - preloadedStateMultipleAttendees - ); - - await waitFor(() => { - fireEvent.click(screen.getByTestId("MoreVertIcon")); - expect(screen.getByText("eventPreview.deleteEvent")).toBeInTheDocument(); - }); - - const deleteMenuItem = screen.getByText("eventPreview.deleteEvent"); - fireEvent.click(deleteMenuItem); - - await waitFor(() => { - // Should be called once with all templist - expect(updateTempCalendarSpy).toHaveBeenCalledWith( - expect.objectContaining({ - temp1: expect.objectContaining({ id: "temp1" }), - temp2: expect.objectContaining({ id: "temp2" }), - temp3: expect.objectContaining({ id: "temp3" }), - temp4: expect.objectContaining({ id: "temp4" }), - }), - expect.objectContaining({ - attendee: expect.arrayContaining([ - expect.objectContaining({ cal_address: "attendee1@test.com" }), - expect.objectContaining({ cal_address: "attendee2@test.com" }), - expect.objectContaining({ cal_address: "attendee3@test.com" }), - ]), - }), - expect.any(Function), - expect.objectContaining({ - start: expect.any(Date), - end: expect.any(Date), - }) - ); - }); - - await waitFor(() => { - // Should refresh temp1, temp2, and temp3 (matching attendees) - const temp1Calls = refreshSingularCalendarSpy.mock.calls.filter( - (call) => call[1]?.id === "temp1" - ); - const temp2Calls = refreshSingularCalendarSpy.mock.calls.filter( - (call) => call[1]?.id === "temp2" - ); - const temp3Calls = refreshSingularCalendarSpy.mock.calls.filter( - (call) => call[1]?.id === "temp3" - ); - const temp4Calls = refreshSingularCalendarSpy.mock.calls.filter( - (call) => call[1]?.id === "temp4" - ); - - expect(temp1Calls.length).toBeGreaterThan(0); - expect(temp2Calls.length).toBeGreaterThan(0); - expect(temp3Calls.length).toBeGreaterThan(0); - expect(temp4Calls.length).toBe(0); // Should NOT be refreshed - - // Verify all matching calendars were refreshed with 'temp' param - expect(temp1Calls[0][3]).toBe("temp"); - expect(temp2Calls[0][3]).toBe("temp"); - expect(temp3Calls[0][3]).toBe("temp"); - }); - }); -}); diff --git a/__test__/utils/findCalendarById.test.ts b/__test__/utils/findCalendarById.test.ts index 9a72388..6676a87 100644 --- a/__test__/utils/findCalendarById.test.ts +++ b/__test__/utils/findCalendarById.test.ts @@ -59,7 +59,7 @@ describe("findCalendarById", () => { expect(result).toBeUndefined(); }); - it("should prioritize main list over temp list", () => { + it("should prioritize temp list over main list", () => { const stateWithDuplicate = { calendars: { list: { @@ -73,8 +73,8 @@ describe("findCalendarById", () => { const result = findCalendarById(stateWithDuplicate, "dup1"); - expect(result?.calendar).toEqual(mockCalendar1); - expect(result?.type).toBeUndefined(); + expect(result?.calendar).toEqual(mockTempCalendar); + expect(result?.type).toBe("temp"); }); it("should handle undefined list or templist", () => { diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx index ea6f406..44e0a16 100644 --- a/src/components/Calendar/Calendar.tsx +++ b/src/components/Calendar/Calendar.tsx @@ -406,10 +406,6 @@ export default function CalendarApp({ }); }, [calendarsWithClearedCache, dispatch, rangeKey, rangeStart, rangeEnd]); - const tempCalendarControllersRef = useRef>( - new Map() - ); - useEffect(() => { const currentIds = new Set(tempCalendarIds); Object.keys(tempFetchedRangesRef.current).forEach((id) => { @@ -558,9 +554,6 @@ export default function CalendarApp({ setSelectedRange, setAnchorEl, calendarRef, - selectedCalendars, - tempcalendars, - calendarRange, dispatch, setOpenEventDisplay, setEventDisplayedId, diff --git a/src/components/Calendar/CalendarModal.tsx b/src/components/Calendar/CalendarModal.tsx index cef2073..5bc2d49 100644 --- a/src/components/Calendar/CalendarModal.tsx +++ b/src/components/Calendar/CalendarModal.tsx @@ -30,11 +30,10 @@ function CalendarPopover({ }) { const { t } = useI18n(); const dispatch = useAppDispatch(); - const userId = - useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; + const userData = useAppSelector((state) => state.user.userData) ?? {}; const calendars = useAppSelector((state) => state.calendars.list); const isOwn = calendar?.id - ? extractEventBaseUuid(calendar.id) === userId + ? extractEventBaseUuid(calendar.id) === userData.openpaasId : true; // existing calendar params @@ -104,14 +103,14 @@ function CalendarPopover({ name: name.trim(), desc: desc.trim(), color: color, - userId, + userData, calId, }) ); dispatch( patchACLCalendarAsync({ - calId: `${userId}/${calId}`, - calLink: `/calendars/${userId}/${calId}.json`, + calId: `${userData.openpaasId}/${calId}`, + calLink: `/calendars/${userData.openpaasId}/${calId}.json`, request: visibility === "public" ? "{DAV:}read" : "", }) ); @@ -142,7 +141,7 @@ function CalendarPopover({ importedContent && dispatch( importEventFromFileAsync({ - calLink: `/calendar/${userId}/${calId}.json`, + calLink: `/calendar/${userData.openpaasId}/${calId}.json`, file: importedContent, }) ); @@ -227,7 +226,7 @@ function CalendarPopover({ importTarget={importTarget} setImportTarget={setImportTarget} setImportedContent={setImportedContent} - userId={userId} + userId={userData.openpaasId ?? ""} newCalParams={{ name: newCalName, setName: setNewCalName, diff --git a/src/components/Calendar/handlers/eventHandlers.ts b/src/components/Calendar/handlers/eventHandlers.ts index ce42120..2e1642f 100644 --- a/src/components/Calendar/handlers/eventHandlers.ts +++ b/src/components/Calendar/handlers/eventHandlers.ts @@ -1,10 +1,8 @@ +import { AppDispatch } from "@/app/store"; import { User } from "@/components/Attendees/PeopleSearch"; import { formatLocalDateTime } from "@/components/Event/utils/dateTimeFormatters"; -import { refreshCalendars } from "@/components/Event/utils/eventUtils"; -import { updateEventLocal } from "@/features/Calendars/CalendarSlice"; import { Calendar } from "@/features/Calendars/CalendarTypes"; import { - getCalendarDetailAsync, getEventAsync, putEventAsync, updateEventInstanceAsync, @@ -14,21 +12,15 @@ import { getEvent } from "@/features/Events/EventApi"; import { CalendarEvent } from "@/features/Events/EventsTypes"; import { userAttendee } from "@/features/User/models/attendee"; import { createAttendee } from "@/features/User/models/attendee.mapper"; -import { - formatDateToYYYYMMDDTHHMMSS, - getDeltaInMilliseconds, -} from "@/utils/dateUtils"; -import { CalendarApi, DateSelectArg } from "@fullcalendar/core"; -import { updateTempCalendar } from "../utils/calendarUtils"; +import { getDeltaInMilliseconds } from "@/utils/dateUtils"; +import { CalendarApi, DateSelectArg, EventDropArg } from "@fullcalendar/core"; +import { EventResizeDoneArg } from "@fullcalendar/interaction"; export interface EventHandlersProps { setSelectedRange: (range: DateSelectArg | null) => void; setAnchorEl: (el: HTMLElement | null) => void; calendarRef: React.RefObject; - selectedCalendars: string[]; - tempcalendars: Record; - calendarRange: { start: Date; end: Date }; - dispatch: any; + dispatch: AppDispatch; setOpenEventDisplay: (open: boolean) => void; setEventDisplayedId: (id: string) => void; setEventDisplayedCalId: (id: string) => void; @@ -47,9 +39,6 @@ export const createEventHandlers = (props: EventHandlersProps) => { setSelectedRange, setAnchorEl, calendarRef, - selectedCalendars, - tempcalendars, - calendarRange, dispatch, setOpenEventDisplay, setEventDisplayedId, @@ -88,35 +77,10 @@ export const createEventHandlers = (props: EventHandlersProps) => { setAnchorEl(document.body); }; - const handleClosePopover = (refresh?: boolean) => { + const handleClosePopover = () => { calendarRef.current?.unselect(); setAnchorEl(null); setSelectedRange(null); - if (refresh) { - selectedCalendars.forEach((calId) => - dispatch( - getCalendarDetailAsync({ - calId, - match: { - start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), - end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), - }, - }) - ) - ); - Object.keys(tempcalendars).forEach((calId) => - dispatch( - getCalendarDetailAsync({ - calId, - match: { - start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start), - end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end), - }, - calType: "temp", - }) - ) - ); - } }; const handleCloseEventDisplay = () => { @@ -151,11 +115,11 @@ export const createEventHandlers = (props: EventHandlersProps) => { } }; - const handleEventAllow = (dropInfo: any, draggedEvent: any) => { + const handleEventAllow = () => { return true; }; - const handleEventDrop = async (arg: any) => { + const handleEventDrop = async (arg: EventDropArg) => { if (!arg.event || !arg.event._def || !arg.event._def.extendedProps) { return; } @@ -182,7 +146,6 @@ export const createEventHandlers = (props: EventHandlersProps) => { } as CalendarEvent, true ); - if (isRecurring) { setSelectedEvent(event); setOpenEditModePopup("edit"); @@ -192,9 +155,6 @@ export const createEventHandlers = (props: EventHandlersProps) => { await dispatch( updateEventInstanceAsync({ cal: calendar, event: newEvent }) ); - dispatch( - updateEventLocal({ calId: newEvent.calId, event: newEvent }) - ); } else if (typeOfAction === "all") { const master = await getEvent(newEvent, true); await dispatch( @@ -208,30 +168,17 @@ export const createEventHandlers = (props: EventHandlersProps) => { }, }) ); - await refreshCalendars( - dispatch, - Object.values(calendars), - calendarRange - ); - await updateTempCalendar( - tempcalendars, - event, - dispatch, - calendarRange - ); } } ); } else { - dispatch(updateEventLocal({ calId: newEvent.calId, event: newEvent })); await dispatch( putEventAsync({ cal: calendars[newEvent.calId], newEvent }) ); } - await updateTempCalendar(tempcalendars, event, dispatch, calendarRange); }; - const handleEventResize = async (arg: any) => { + const handleEventResize = async (arg: EventResizeDoneArg) => { if (!arg.event || !arg.event._def || !arg.event._def.extendedProps) { return; } @@ -261,7 +208,6 @@ export const createEventHandlers = (props: EventHandlersProps) => { } as CalendarEvent, true ); - if (isRecurring) { setSelectedEvent(event); setOpenEditModePopup("edit"); @@ -271,9 +217,6 @@ export const createEventHandlers = (props: EventHandlersProps) => { await dispatch( updateEventInstanceAsync({ cal: calendar, event: newEvent }) ); - dispatch( - updateEventLocal({ calId: newEvent.calId, event: newEvent }) - ); } else if (typeOfAction === "all") { const master = await getEvent(newEvent, true); @@ -288,17 +231,6 @@ export const createEventHandlers = (props: EventHandlersProps) => { }, }) ); - await refreshCalendars( - dispatch, - Object.values(calendars), - calendarRange - ); - await updateTempCalendar( - tempcalendars, - event, - dispatch, - calendarRange - ); } } ); @@ -307,7 +239,6 @@ export const createEventHandlers = (props: EventHandlersProps) => { putEventAsync({ cal: calendars[newEvent.calId], newEvent }) ); } - await updateTempCalendar(tempcalendars, event, dispatch, calendarRange); }; return { diff --git a/src/components/Calendar/hooks/useCalendarEventHandlers.ts b/src/components/Calendar/hooks/useCalendarEventHandlers.ts index 5a2f33b..10a860c 100644 --- a/src/components/Calendar/hooks/useCalendarEventHandlers.ts +++ b/src/components/Calendar/hooks/useCalendarEventHandlers.ts @@ -19,9 +19,6 @@ export const useCalendarEventHandlers = (props: EventHandlersProps) => { props.calendarRef, props.setAnchorEl, props.setSelectedRange, - props.selectedCalendars, - props.tempcalendars, - props.calendarRange, props.dispatch, ]), handleCloseEventDisplay: useCallback( @@ -43,14 +40,10 @@ export const useCalendarEventHandlers = (props: EventHandlersProps) => { props.setSelectedEvent, props.setOpenEditModePopup, props.setAfterChoiceFunc, - props.tempcalendars, - props.calendarRange, ]), handleEventResize: useCallback(eventHandlers.handleEventResize, [ props.calendars, props.dispatch, - props.tempcalendars, - props.calendarRange, ]), }; }; diff --git a/src/components/Calendar/utils/calendarUtils.ts b/src/components/Calendar/utils/calendarUtils.ts index e9c83d5..8d438b1 100644 --- a/src/components/Calendar/utils/calendarUtils.ts +++ b/src/components/Calendar/utils/calendarUtils.ts @@ -271,35 +271,3 @@ export function getCalendarVisibility(acl: AclEntry[]): "private" | "public" { if (hasRead) return "public"; return "private"; } - -export async function updateTempCalendar( - tempcalendars: Record, - event: CalendarEvent, - dispatch: ThunkDispatch, - calendarRange: { start: Date; end: Date } -) { - if (tempcalendars && event?.attendee) { - const attendeesEmails = event.attendee - .map((a) => a.cal_address) - .filter(Boolean); - - const tempCalendarValues = Object.values(tempcalendars); - - for (const tempCalendar of tempCalendarValues) { - const ownerEmails = tempCalendar.ownerEmails || []; - - // Check if any of the attendees are owners of this temp calendar - const isOwnerAttendee = ownerEmails.some((ownerEmail) => - attendeesEmails.includes(ownerEmail) - ); - if (isOwnerAttendee) { - await refreshSingularCalendar( - dispatch, - tempCalendar, - calendarRange, - "temp" - ); - } - } - } -} diff --git a/src/components/Event/eventHandlers/eventHandlers.ts b/src/components/Event/eventHandlers/eventHandlers.ts index c2921e6..1e0a0c0 100644 --- a/src/components/Event/eventHandlers/eventHandlers.ts +++ b/src/components/Event/eventHandlers/eventHandlers.ts @@ -12,8 +12,6 @@ import { PartStat } from "@/features/User/models/attendee"; import { createAttendee } from "@/features/User/models/attendee.mapper"; import { userData } from "@/features/User/userDataTypes"; import { buildFamilyName } from "@/utils/buildFamilyName"; -import { getCalendarRange } from "@/utils/dateUtils"; -import { refreshCalendars } from "../utils/eventUtils"; function updateEventAttendees( event: CalendarEvent, @@ -78,15 +76,11 @@ async function handleSoloRSVP( } async function handleAllRSVP( - dispatch: AppDispatch, event: CalendarEvent, userEmail: string, - rsvp: PartStat, - calendars: Calendar[] + rsvp: PartStat ) { - const calendarRange = getCalendarRange(new Date(event.start)); await updateSeriesPartstat(event, userEmail, rsvp); - await refreshCalendars(dispatch, calendars, calendarRange); } async function handleDefaultRSVP( @@ -103,8 +97,7 @@ export async function handleRSVP( user: userData | undefined, event: CalendarEvent, rsvp: PartStat, - typeOfAction?: string, - calendars?: Calendar[] + typeOfAction?: string ) { const newEvent = { ...event, @@ -114,13 +107,10 @@ export async function handleRSVP( if (typeOfAction === "solo") { await handleSoloRSVP(dispatch, calendar, newEvent); } else if (typeOfAction === "all") { - if (!calendars || calendars.length === 0) { - throw new Error("Cannot update all occurrences without calendar list"); - } if (!user?.email) { throw new Error("Cannot update all occurrences without user email"); } - await handleAllRSVP(dispatch, event, user.email, rsvp, calendars); + await handleAllRSVP(event, user.email, rsvp); } else { await handleDefaultRSVP(dispatch, calendar, newEvent); } diff --git a/src/features/Calendars/CalendarSlice.ts b/src/features/Calendars/CalendarSlice.ts index 2e1ebfe..17353e0 100644 --- a/src/features/Calendars/CalendarSlice.ts +++ b/src/features/Calendars/CalendarSlice.ts @@ -23,12 +23,6 @@ import { updateSeriesAsync, } from "./services"; -// Define error type for rejected actions -export interface RejectedError { - message: string; - status?: number; -} - const CalendarSlice = createSlice({ name: "calendars", initialState: { @@ -182,42 +176,10 @@ const CalendarSlice = createSlice({ ); } ) - .addCase( - putEventAsync.fulfilled, - ( - state, - action: PayloadAction<{ - calId: string; - events: CalendarEvent[]; - calType?: "temp"; - }> - ) => { - state.pending = false; - const type = action.payload.calType === "temp" ? "templist" : "list"; - - if (!state[type][action.payload.calId]) { - state[type][action.payload.calId] = { - id: action.payload.calId, - events: {}, - } as Calendar; - } - action.payload.events.forEach((event) => { - state[type][action.payload.calId].events[event.uid] = event; - }); - Object.keys(state[type][action.payload.calId].events).forEach( - (id) => { - state[type][action.payload.calId].events[id].color = - state[type][action.payload.calId].color; - state[type][action.payload.calId].events[id].calId = - action.payload.calId; - if (!state[type][action.payload.calId].events[id].timezone) { - state[type][action.payload.calId].events[id].timezone = - browserDefaultTimeZone; - } - } - ); - } - ) + .addCase(putEventAsync.fulfilled, (state) => { + state.pending = false; + state.error = null; + }) .addCase( getEventAsync.fulfilled, ( @@ -236,50 +198,12 @@ const CalendarSlice = createSlice({ action.payload.event; } ) - .addCase( - moveEventAsync.fulfilled, - ( - state, - action: PayloadAction<{ calId: string; events: CalendarEvent[] }> - ) => { - state.pending = false; - if (!state.list[action.payload.calId]) { - state.list[action.payload.calId] = { - id: action.payload.calId, - events: {}, - } as Calendar; - } - action.payload.events.forEach((event) => { - state.list[action.payload.calId].events[event.uid] = event; - }); - Object.keys(state.list[action.payload.calId].events).forEach((id) => { - state.list[action.payload.calId].events[id].color = - state.list[action.payload.calId].color; - state.list[action.payload.calId].events[id].calId = - action.payload.calId; - if (!state.list[action.payload.calId].events[id].timezone) { - state.list[action.payload.calId].events[id].timezone = - browserDefaultTimeZone; - } - }); - } - ) - .addCase(deleteEventAsync.fulfilled, (state, action) => { + .addCase(moveEventAsync.fulfilled, (state) => { + state.pending = false; + state.error = null; + }) + .addCase(deleteEventAsync.fulfilled, (state) => { state.pending = false; - const [baseId, recurrenceId] = action.payload.eventId.split("/"); - if (recurrenceId) { - Object.keys(state.list[action.payload.calId].events).forEach( - (element) => { - if (extractEventBaseUuid(element) === baseId) { - delete state.list[action.payload.calId].events[element]; - } - } - ); - } else { - delete state.list[action.payload.calId].events[ - action.payload.eventId - ]; - } state.error = null; }) .addCase(deleteEventInstanceAsync.fulfilled, (state, action) => { diff --git a/src/features/Calendars/services/addSharedCalendarAsync.ts b/src/features/Calendars/services/addSharedCalendarAsync.ts index 686a99a..c1a6e26 100644 --- a/src/features/Calendars/services/addSharedCalendarAsync.ts +++ b/src/features/Calendars/services/addSharedCalendarAsync.ts @@ -2,7 +2,7 @@ import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { getUserDetails } from "@/features/User/userAPI"; import { addSharedCalendar } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; export const addSharedCalendarAsync = createAsyncThunk< { diff --git a/src/features/Calendars/services/createCalendarAsync.ts b/src/features/Calendars/services/createCalendarAsync.ts index e8b2361..9a3e11d 100644 --- a/src/features/Calendars/services/createCalendarAsync.ts +++ b/src/features/Calendars/services/createCalendarAsync.ts @@ -1,8 +1,9 @@ import { getUserDetails } from "@/features/User/userAPI"; +import { userData } from "@/features/User/userDataTypes"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { postCalendar } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; export const createCalendarAsync = createAsyncThunk< { @@ -15,7 +16,7 @@ export const createCalendarAsync = createAsyncThunk< ownerEmails: string[]; }, { - userId: string; + userData: userData; calId: string; color: Record; name: string; @@ -24,21 +25,25 @@ export const createCalendarAsync = createAsyncThunk< { rejectValue: RejectedError } >( "calendars/createCalendar", - async ({ userId, calId, color, name, desc }, { rejectWithValue }) => { + async ({ userData, calId, color, name, desc }, { rejectWithValue }) => { try { - await postCalendar(userId, calId, color, name, desc); - const ownerData: any = await getUserDetails(userId.split("/")[0]); + if (!userData.openpaasId) { + throw new Error("No openpaasId"); + } + + await postCalendar(userData.openpaasId, calId, color, name, desc); + const owner = [userData.given_name, userData.family_name] + .filter(Boolean) + .join(" "); return { - userId, + userId: userData.openpaasId, calId, color, name, desc, - owner: [ownerData.firstname, ownerData.lastname] - .filter(Boolean) - .join(" "), - ownerEmails: ownerData.emails ?? [], + owner, + ownerEmails: userData.email ? [userData.email] : [], }; } catch (err: any) { return rejectWithValue({ diff --git a/src/features/Calendars/services/deleteEventAsync.ts b/src/features/Calendars/services/deleteEventAsync.ts index 52ebbd6..2b59750 100644 --- a/src/features/Calendars/services/deleteEventAsync.ts +++ b/src/features/Calendars/services/deleteEventAsync.ts @@ -1,7 +1,7 @@ import { deleteEvent } from "@/features/Events/EventApi"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; export const deleteEventAsync = createAsyncThunk< { calId: string; eventId: string }, diff --git a/src/features/Calendars/services/deleteEventInstanceAsync.ts b/src/features/Calendars/services/deleteEventInstanceAsync.ts index f879b89..4a8b8a0 100644 --- a/src/features/Calendars/services/deleteEventInstanceAsync.ts +++ b/src/features/Calendars/services/deleteEventInstanceAsync.ts @@ -2,7 +2,7 @@ import { deleteEventInstance } from "@/features/Events/EventApi"; import { CalendarEvent } from "@/features/Events/EventsTypes"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; import { Calendar } from "../CalendarTypes"; export const deleteEventInstanceAsync = createAsyncThunk< diff --git a/src/features/Calendars/services/getCalendarDetailAsync.ts b/src/features/Calendars/services/getCalendarDetailAsync.ts index 532bc76..d25d264 100644 --- a/src/features/Calendars/services/getCalendarDetailAsync.ts +++ b/src/features/Calendars/services/getCalendarDetailAsync.ts @@ -2,7 +2,7 @@ import { CalendarEvent } from "@/features/Events/EventsTypes"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { getCalendar } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; import { extractCalendarEvents } from "../utils/extractCalendarEvents"; export const getCalendarDetailAsync = createAsyncThunk< diff --git a/src/features/Calendars/services/getCalendarsListAsync.ts b/src/features/Calendars/services/getCalendarsListAsync.ts index 6a84296..8d50805 100644 --- a/src/features/Calendars/services/getCalendarsListAsync.ts +++ b/src/features/Calendars/services/getCalendarsListAsync.ts @@ -2,7 +2,7 @@ import { getOpenPaasUser, getUserDetails } from "@/features/User/userAPI"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { getCalendars } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; import { Calendar } from "../CalendarTypes"; import { normalizeCalendar } from "../utils/normalizeCalendar"; diff --git a/src/features/Calendars/services/getEventAsync.ts b/src/features/Calendars/services/getEventAsync.ts index 4989e46..b0f6e37 100644 --- a/src/features/Calendars/services/getEventAsync.ts +++ b/src/features/Calendars/services/getEventAsync.ts @@ -2,7 +2,7 @@ import { getEvent } from "@/features/Events/EventApi"; import { CalendarEvent } from "@/features/Events/EventsTypes"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; export const getEventAsync = createAsyncThunk< { calId: string; event: CalendarEvent }, diff --git a/src/features/Calendars/services/getTempCalendarsListAsync.ts b/src/features/Calendars/services/getTempCalendarsListAsync.ts index acada08..2ef35db 100644 --- a/src/features/Calendars/services/getTempCalendarsListAsync.ts +++ b/src/features/Calendars/services/getTempCalendarsListAsync.ts @@ -4,7 +4,7 @@ import { getUserDetails } from "@/features/User/userAPI"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { getCalendars } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; import { Calendar } from "../CalendarTypes"; export const getTempCalendarsListAsync = createAsyncThunk< @@ -14,9 +14,14 @@ export const getTempCalendarsListAsync = createAsyncThunk< >("calendars/getTempCalendars", async (tempUser, { rejectWithValue }) => { try { const importedCalendars: Record = {}; - + if (!tempUser.openpaasId) { + const username = tempUser.displayName || tempUser.email || "User"; + throw new Error( + `TRANSLATION:calendar.userDoesNotHaveValidId|name=${encodeURIComponent(username)}` + ); + } const calendars = (await getCalendars( - tempUser.openpaasId ?? "", + tempUser.openpaasId, "sharedPublic=true&" )) as Record; diff --git a/src/features/Calendars/services/importEventFromFileAsync.ts b/src/features/Calendars/services/importEventFromFileAsync.ts index cab6941..47f3843 100644 --- a/src/features/Calendars/services/importEventFromFileAsync.ts +++ b/src/features/Calendars/services/importEventFromFileAsync.ts @@ -2,7 +2,7 @@ import { importEventFromFile } from "@/features/Events/EventApi"; import { importFile } from "@/utils/apiUtils"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; export const importEventFromFileAsync = createAsyncThunk< void, diff --git a/src/features/Calendars/services/moveEventAsync.ts b/src/features/Calendars/services/moveEventAsync.ts index a3b3362..1268196 100644 --- a/src/features/Calendars/services/moveEventAsync.ts +++ b/src/features/Calendars/services/moveEventAsync.ts @@ -8,11 +8,11 @@ import { import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { getCalendar } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; import { Calendar } from "../CalendarTypes"; export const moveEventAsync = createAsyncThunk< - { calId: string; events: CalendarEvent[] }, + { calId: string }, { cal: Calendar; newEvent: CalendarEvent; newURL: string }, { rejectValue: RejectedError } >( @@ -21,31 +21,8 @@ export const moveEventAsync = createAsyncThunk< try { await moveEvent(newEvent, newURL); - const eventDate = new Date(newEvent.start); - const { start: weekStart, end: weekEnd } = computeWeekRange(eventDate); - - const calEvents = (await getCalendar(cal.id, { - start: formatDateToYYYYMMDDTHHMMSS(weekStart), - end: formatDateToYYYYMMDDTHHMMSS(weekEnd), - })) as Record; - const events: CalendarEvent[] = calEvents._embedded["dav:item"].flatMap( - (eventdata: any) => { - const vevents = eventdata.data[2] as any[][]; - const eventURL = eventdata._links.self.href; - return vevents.map((vevent: any[]) => { - return parseCalendarEvent( - vevent[1], - cal.color ?? {}, - cal.id, - eventURL - ); - }); - } - ); - return { calId: cal.id, - events, }; } catch (err: any) { return rejectWithValue({ diff --git a/src/features/Calendars/services/patchACLCalendarAsync.ts b/src/features/Calendars/services/patchACLCalendarAsync.ts index 93f90c9..1c939e2 100644 --- a/src/features/Calendars/services/patchACLCalendarAsync.ts +++ b/src/features/Calendars/services/patchACLCalendarAsync.ts @@ -1,7 +1,7 @@ import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { updateAclCalendar } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; export const patchACLCalendarAsync = createAsyncThunk< { diff --git a/src/features/Calendars/services/patchCalendarAsync.ts b/src/features/Calendars/services/patchCalendarAsync.ts index 1007208..4c94f1f 100644 --- a/src/features/Calendars/services/patchCalendarAsync.ts +++ b/src/features/Calendars/services/patchCalendarAsync.ts @@ -1,7 +1,7 @@ import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { proppatchCalendar } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; export const patchCalendarAsync = createAsyncThunk< { diff --git a/src/features/Calendars/services/putEventAsync.ts b/src/features/Calendars/services/putEventAsync.ts index 45ebfde..8ba1c03 100644 --- a/src/features/Calendars/services/putEventAsync.ts +++ b/src/features/Calendars/services/putEventAsync.ts @@ -1,18 +1,12 @@ import { putEvent } from "@/features/Events/EventApi"; import { CalendarEvent } from "@/features/Events/EventsTypes"; -import { parseCalendarEvent } from "@/features/Events/eventUtils"; -import { - computeWeekRange, - formatDateToYYYYMMDDTHHMMSS, -} from "@/utils/dateUtils"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; -import { getCalendar } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; import { Calendar } from "../CalendarTypes"; export const putEventAsync = createAsyncThunk< - { calId: string; events: CalendarEvent[]; calType?: "temp" }, + { calId: string; calType?: "temp" }, { cal: Calendar; newEvent: CalendarEvent; calType?: "temp" }, { rejectValue: RejectedError } >( @@ -23,34 +17,9 @@ export const putEventAsync = createAsyncThunk< newEvent, cal.ownerEmails ? cal.ownerEmails[0] : undefined ); - const eventDate = new Date(newEvent.start); - - const { start: weekStart, end: weekEnd } = computeWeekRange(eventDate); - - const calEvents = (await getCalendar(cal.id, { - start: formatDateToYYYYMMDDTHHMMSS(weekStart), - end: formatDateToYYYYMMDDTHHMMSS(weekEnd), - })) as Record; - const events: CalendarEvent[] = calEvents._embedded["dav:item"].flatMap( - (eventdata: any) => { - const vevents = eventdata.data[2] as any[][]; - const eventURL = eventdata._links.self.href; - const valarm = eventdata.data[2][0][2][0]; - return vevents.map((vevent: any[]) => { - return parseCalendarEvent( - vevent[1], - cal.color ?? {}, - cal.id, - eventURL, - valarm - ); - }); - } - ); return { calId: cal.id, - events, calType, }; } catch (err: any) { diff --git a/src/features/Calendars/services/refreshCalendar.ts b/src/features/Calendars/services/refreshCalendar.ts index 54bfeb8..7b8aa72 100644 --- a/src/features/Calendars/services/refreshCalendar.ts +++ b/src/features/Calendars/services/refreshCalendar.ts @@ -3,7 +3,7 @@ import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import pMap from "p-map"; import { fetchSyncTokenChanges } from "../api/fetchSyncTokenChanges"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; import { Calendar } from "../CalendarTypes"; import { expandEventFunction } from "../utils/expandEventFunction"; import { processSyncUpdates } from "../utils/processSyncTokenUpdates"; diff --git a/src/features/Calendars/services/removeCalendarAsync.ts b/src/features/Calendars/services/removeCalendarAsync.ts index e51b0cc..cb51a96 100644 --- a/src/features/Calendars/services/removeCalendarAsync.ts +++ b/src/features/Calendars/services/removeCalendarAsync.ts @@ -1,7 +1,7 @@ import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; import { removeCalendar } from "../CalendarApi"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; export const removeCalendarAsync = createAsyncThunk< { diff --git a/src/features/Calendars/services/updateEventInstanceAsync.ts b/src/features/Calendars/services/updateEventInstanceAsync.ts index 8c2bc2e..6f8a5ee 100644 --- a/src/features/Calendars/services/updateEventInstanceAsync.ts +++ b/src/features/Calendars/services/updateEventInstanceAsync.ts @@ -2,7 +2,7 @@ import { putEventWithOverrides } from "@/features/Events/EventApi"; import { CalendarEvent } from "@/features/Events/EventsTypes"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; import { Calendar } from "../CalendarTypes"; export const updateEventInstanceAsync = createAsyncThunk< diff --git a/src/features/Calendars/services/updateSeriesAsync.ts b/src/features/Calendars/services/updateSeriesAsync.ts index 0eef646..39ac5a7 100644 --- a/src/features/Calendars/services/updateSeriesAsync.ts +++ b/src/features/Calendars/services/updateSeriesAsync.ts @@ -2,7 +2,7 @@ import { updateSeries } from "@/features/Events/EventApi"; import { CalendarEvent } from "@/features/Events/EventsTypes"; import { formatReduxError } from "@/utils/errorUtils"; import { createAsyncThunk } from "@reduxjs/toolkit"; -import { RejectedError } from "../CalendarSlice"; +import { RejectedError } from "../types/RejectedError"; import { Calendar } from "../CalendarTypes"; export const updateSeriesAsync = createAsyncThunk< diff --git a/src/features/Calendars/types/RejectedError.ts b/src/features/Calendars/types/RejectedError.ts new file mode 100644 index 0000000..83d15e2 --- /dev/null +++ b/src/features/Calendars/types/RejectedError.ts @@ -0,0 +1,6 @@ +// Define error type for rejected actions + +export interface RejectedError { + message: string; + status?: number; +} diff --git a/src/features/Events/AttendanceValidation/AttendanceValidation.tsx b/src/features/Events/AttendanceValidation/AttendanceValidation.tsx index d70e10d..ddf6a5b 100644 --- a/src/features/Events/AttendanceValidation/AttendanceValidation.tsx +++ b/src/features/Events/AttendanceValidation/AttendanceValidation.tsx @@ -1,4 +1,3 @@ -import { Calendar } from "@/features/Calendars/CalendarTypes"; import { userData } from "@/features/User/userDataTypes"; import { Box, Typography } from "@linagora/twake-mui"; import { Dispatch, SetStateAction } from "react"; @@ -8,7 +7,6 @@ import { RSVPButton } from "./RSVPButton"; interface AttendanceValidationProps { contextualizedEvent: ContextualizedEvent; - calendarList: Calendar[]; user: userData | undefined; setAfterChoiceFunc: Dispatch>; setOpenEditModePopup: Dispatch>; @@ -16,7 +14,6 @@ interface AttendanceValidationProps { export function AttendanceValidation({ contextualizedEvent, - calendarList, user, setAfterChoiceFunc, setOpenEditModePopup, @@ -36,7 +33,6 @@ export function AttendanceValidation({ const commonButtonProps = { contextualizedEvent, user, - calendarList, setAfterChoiceFunc, setOpenEditModePopup, }; diff --git a/src/features/Events/AttendanceValidation/RSVPButton.tsx b/src/features/Events/AttendanceValidation/RSVPButton.tsx index cf67ae8..6b8a72f 100644 --- a/src/features/Events/AttendanceValidation/RSVPButton.tsx +++ b/src/features/Events/AttendanceValidation/RSVPButton.tsx @@ -1,5 +1,4 @@ import { useAppDispatch } from "@/app/hooks"; -import { Calendar } from "@/features/Calendars/CalendarTypes"; import { PartStat } from "@/features/User/models/attendee"; import { userData } from "@/features/User/userDataTypes"; import { Button } from "@linagora/twake-mui"; @@ -20,7 +19,6 @@ interface RSVPButtonProps { rsvpValue: PartStat; contextualizedEvent: ContextualizedEvent; user: userData | undefined; - calendarList: Calendar[]; setAfterChoiceFunc: Dispatch>; setOpenEditModePopup: Dispatch>; } @@ -29,7 +27,6 @@ export function RSVPButton({ rsvpValue, contextualizedEvent, user, - calendarList, setAfterChoiceFunc, setOpenEditModePopup, }: RSVPButtonProps) { @@ -54,7 +51,6 @@ export function RSVPButton({ rsvpValue, contextualizedEvent, user, - calendarList, setAfterChoiceFunc, setOpenEditModePopup, dispatch diff --git a/src/features/Events/AttendanceValidation/handleRSVPClick.tsx b/src/features/Events/AttendanceValidation/handleRSVPClick.tsx index becccf2..ac69d2a 100644 --- a/src/features/Events/AttendanceValidation/handleRSVPClick.tsx +++ b/src/features/Events/AttendanceValidation/handleRSVPClick.tsx @@ -1,6 +1,5 @@ import { AppDispatch } from "@/app/store"; import { handleRSVP } from "@/components/Event/eventHandlers/eventHandlers"; -import { Calendar } from "@/features/Calendars/CalendarTypes"; import { PartStat } from "@/features/User/models/attendee"; import { userData } from "@/features/User/userDataTypes"; import { Dispatch, SetStateAction } from "react"; @@ -10,7 +9,6 @@ export async function handleRSVPClick( rsvp: PartStat, contextualizedEvent: ContextualizedEvent, user: userData | undefined, - calendarList: Calendar[], setAfterChoiceFunc: Dispatch>, setOpenEditModePopup: Dispatch>, dispatch: AppDispatch @@ -19,15 +17,7 @@ export async function handleRSVPClick( if (isRecurring) { setAfterChoiceFunc(() => async (type: string) => { try { - await handleRSVP( - dispatch, - calendar, - user, - event, - rsvp, - type, - calendarList - ); + await handleRSVP(dispatch, calendar, user, event, rsvp, type); } catch (error) { console.error("Error handling RSVP:", error); } diff --git a/src/features/Events/EventDisplayPreview.tsx b/src/features/Events/EventDisplayPreview.tsx index e8c4bec..fe9726e 100644 --- a/src/features/Events/EventDisplayPreview.tsx +++ b/src/features/Events/EventDisplayPreview.tsx @@ -1,17 +1,13 @@ import { useAppDispatch, useAppSelector } from "@/app/hooks"; import { CalendarName } from "@/components/Calendar/CalendarName"; import { getTimezoneOffset } from "@/components/Calendar/TimezoneSelector"; -import { - formatEventChipTitle, - updateTempCalendar, -} from "@/components/Calendar/utils/calendarUtils"; +import { formatEventChipTitle } from "@/components/Calendar/utils/calendarUtils"; import ResponsiveDialog from "@/components/Dialog/ResponsiveDialog"; import { EditModeDialog } from "@/components/Event/EditModeDialog"; import EventDuplication from "@/components/Event/EventDuplicate"; import { handleDelete } from "@/components/Event/eventHandlers/eventHandlers"; import { InfoRow } from "@/components/Event/InfoRow"; import { renderAttendeeBadge } from "@/components/Event/utils/eventUtils"; -import { getCalendarRange } from "@/utils/dateUtils"; import { browserDefaultTimeZone } from "@/utils/timezone"; import { DateSelectArg } from "@fullcalendar/core"; import { @@ -282,18 +278,6 @@ export default function EventPreviewModal({ (a) => a.cal_address === event.organizer?.cal_address ); - const updateTempList = async () => { - if (calendars.templist) { - const calendarRange = getCalendarRange(new Date(event.start)); - await updateTempCalendar( - calendars.templist, - event, - dispatch, - calendarRange - ); - } - }; - return ( <> state.user.organiserData); const userId = useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; - const tempList = useAppSelector((state) => state.calendars.templist); const calList = useAppSelector((state) => state.calendars.list); const selectPersonalCalendars = createSelector( (state: any) => state.calendars, @@ -838,11 +837,6 @@ function EventPopover({ } } - if (tempList) { - const calendarRange = getCalendarRange(new Date(start)); - await updateTempCalendar(tempList, newEvent, dispatch, calendarRange); - } - // Clear temp data on successful save clearEventFormTempData("create"); diff --git a/src/features/Events/EventUpdateModal.tsx b/src/features/Events/EventUpdateModal.tsx index 81cc05a..8f77b80 100644 --- a/src/features/Events/EventUpdateModal.tsx +++ b/src/features/Events/EventUpdateModal.tsx @@ -65,7 +65,6 @@ function EventUpdateModal({ }) { const { t } = useI18n(); const dispatch = useAppDispatch(); - const tempList = useAppSelector((state) => state.calendars.templist); const calList = useAppSelector((state) => state.calendars.list); // Get event from Redux store (cached data) as fallback const cachedEvent = useAppSelector( @@ -78,6 +77,10 @@ function EventUpdateModal({ // Use fresh data if available, otherwise use eventData from props, otherwise use cached data const event = freshEvent || eventData || cachedEvent; + useEffect(() => { + setFreshEvent(null); + }, [eventId, calId]); + // Fetch fresh event data when modal opens useEffect(() => { if (open && cachedEvent && !eventData) { @@ -714,11 +717,6 @@ function EventUpdateModal({ // Clear cache to ensure navigation to other weeks works dispatch(clearFetchCache(calId)); - if (tempList) { - const calendarRange = getCalendarRange(new Date(start)); - await updateTempCalendar(tempList, event, dispatch, calendarRange); - } - // STEP 5: Remove old recurring instances only after the rest succeeds removeSeriesInstancesFromUI(); @@ -834,7 +832,7 @@ function EventUpdateModal({ const repetitionRulesChanged = changes.repetitionRulesChanged; if (repetitionRulesChanged) { - // Date/time or repetition rules changed - remove all overrides and refresh + // Date/time or repetition rules changed - remove all overrides const seriesInstancesSnapshot = getSeriesInstances(); @@ -892,23 +890,6 @@ function EventUpdateModal({ } } - // STEP 3: Fetch to get new instances with correct timing - // If refreshCalendars fails, we need to throw error to reopen modal - try { - const calendarRange = getCalendarRange(new Date(start)); - await refreshCalendars( - dispatch, - Object.values(calendarsList), - calendarRange - ); - } catch (refreshError: any) { - // If refreshCalendars fails, throw error to reopen modal - throw new Error( - refreshError?.message || - "Failed to refresh calendar events. Please try again." - ); - } - // Clear cache after reload dispatch(clearFetchCache(calId)); @@ -1085,15 +1066,10 @@ function EventUpdateModal({ if (moveResult && typeof moveResult.unwrap === "function") { await moveResult.unwrap(); } - dispatch(removeEvent({ calendarUid: calId, eventUid: event.uid })); // Clear temp data on successful move clearEventFormTempData("update"); } - if (tempList) { - const calendarRange = getCalendarRange(new Date(start)); - await updateTempCalendar(tempList, event, dispatch, calendarRange); - } // Reset all state to default values only on successful save (after all branches) clearEventFormTempData("update"); diff --git a/src/utils/findCalendarById.ts b/src/utils/findCalendarById.ts index b7fc7a3..b39e723 100644 --- a/src/utils/findCalendarById.ts +++ b/src/utils/findCalendarById.ts @@ -8,10 +8,10 @@ export function findCalendarById( if (calendarId.length === 0) { return; } - if (state.calendars?.list?.[calendarId]) { - return { calendar: state.calendars.list[calendarId] }; - } if (state.calendars?.templist?.[calendarId]) { return { calendar: state.calendars.templist[calendarId], type: "temp" }; } + if (state.calendars?.list?.[calendarId]) { + return { calendar: state.calendars.list[calendarId] }; + } } diff --git a/src/websocket/WebSocketGate.tsx b/src/websocket/WebSocketGate.tsx index d7d8baa..6cefb3d 100644 --- a/src/websocket/WebSocketGate.tsx +++ b/src/websocket/WebSocketGate.tsx @@ -10,6 +10,7 @@ import { syncCalendarRegistrations } from "./operations"; export function WebSocketGate() { const socketRef = useRef(null); const previousCalendarListRef = useRef([]); + const previousTempCalendarListRef = useRef([]); const dispatch = useAppDispatch(); const isAuthenticated = useAppSelector((state) => Boolean(state.user.userData && state.user.tokens) @@ -18,6 +19,10 @@ export function WebSocketGate() { const [isSocketOpen, setIsSocketOpen] = useState(false); const calendarList = useSelectedCalendars(); + const tempCalendarList = Object.keys( + useAppSelector((state) => state?.calendars?.templist) ?? {} + ); + const onMessage = useCallback( (message: unknown) => { updateCalendars(message, dispatch); @@ -76,5 +81,14 @@ export function WebSocketGate() { ); }, [isSocketOpen, calendarList]); + useEffect(() => { + syncCalendarRegistrations( + isSocketOpen, + socketRef, + tempCalendarList, + previousTempCalendarListRef + ); + }, [isSocketOpen, tempCalendarList]); + return null; }