[#421] added eslint check to CI (#534)

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2026-02-10 10:41:37 +01:00
committed by GitHub
parent 8a6ec8fc39
commit d6e464afad
116 changed files with 1232 additions and 801 deletions
-3
View File
@@ -1,3 +0,0 @@
{
"extends": ["react-app", "react-app/jest"]
}
Vendored
+5
View File
@@ -33,6 +33,11 @@ pipeline {
sh 'npm run test' sh 'npm run test'
} }
} }
stage('Check Lint') {
steps {
sh 'npm run lint'
}
}
stage('Check Formatting') { stage('Check Formatting') {
steps { steps {
sh 'npx prettier --check .' sh 'npx prettier --check .'
+2 -2
View File
@@ -532,7 +532,7 @@ describe("calendar Availability search", () => {
}) })
); );
const calendarRef = (window as any).__calendarRef; const calendarRef = window.__calendarRef;
await waitFor(() => { await waitFor(() => {
expect(calendarRef.current).not.toBeNull(); expect(calendarRef.current).not.toBeNull();
@@ -573,7 +573,7 @@ describe("calendar Availability search", () => {
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
}); });
const calendarRef = (window as any).__calendarRef; const calendarRef = window.__calendarRef;
const calendarApi = calendarRef.current; const calendarApi = calendarRef.current;
const view = calendarApi?.view; const view = calendarApi?.view;
await act(async () => { await act(async () => {
@@ -309,7 +309,7 @@ describe("CalendarPopover - Tabs Scenarios", () => {
}); });
it("copies CalDAV link from Access tab", async () => { it("copies CalDAV link from Access tab", async () => {
(window as any).CALENDAR_BASE_URL = "https://cal.example.org"; window.CALENDAR_BASE_URL = "https://cal.example.org";
Object.assign(navigator, { Object.assign(navigator, {
clipboard: { writeText: jest.fn() }, clipboard: { writeText: jest.fn() },
}); });
@@ -443,7 +443,7 @@ describe("CalendarPopover - Tabs Scenarios", () => {
}); });
it("fetches and resets the secret link", async () => { it("fetches and resets the secret link", async () => {
(window as any).CALENDAR_BASE_URL = "https://cal.example.org"; window.CALENDAR_BASE_URL = "https://cal.example.org";
(getSecretLink as jest.Mock) (getSecretLink as jest.Mock)
.mockResolvedValueOnce({ .mockResolvedValueOnce({
@@ -10,15 +10,12 @@ import { Calendar } from "@/features/Calendars/CalendarTypes";
import { import {
addSharedCalendarAsync, addSharedCalendarAsync,
createCalendarAsync, createCalendarAsync,
deleteEventAsync,
getCalendarDetailAsync, getCalendarDetailAsync,
getCalendarsListAsync, getCalendarsListAsync,
getEventAsync, getEventAsync,
getTempCalendarsListAsync, getTempCalendarsListAsync,
moveEventAsync,
patchACLCalendarAsync, patchACLCalendarAsync,
patchCalendarAsync, patchCalendarAsync,
putEventAsync,
removeCalendarAsync, removeCalendarAsync,
} from "@/features/Calendars/services"; } from "@/features/Calendars/services";
import { CalendarEvent } from "@/features/Events/EventsTypes"; import { CalendarEvent } from "@/features/Events/EventsTypes";
+3 -3
View File
@@ -68,18 +68,18 @@ describe("eventApi", () => {
}); });
it("putEvent logs when status is 201", async () => { it("putEvent logs when status is 201", async () => {
const consoleLogSpy = jest.spyOn(console, "log").mockImplementation(); const consoleInfoSpy = jest.spyOn(console, "info").mockImplementation();
const mockResponse = { status: 201, url: "/dav/cals/test.ics" }; const mockResponse = { status: 201, url: "/dav/cals/test.ics" };
(api as unknown as jest.Mock).mockReturnValue(mockResponse); (api as unknown as jest.Mock).mockReturnValue(mockResponse);
await putEvent(mockEvent); await putEvent(mockEvent);
expect(consoleLogSpy).toHaveBeenCalledWith( expect(consoleInfoSpy).toHaveBeenCalledWith(
"Event created successfully:", "Event created successfully:",
"/dav/cals/test.ics" "/dav/cals/test.ics"
); );
consoleLogSpy.mockRestore(); consoleInfoSpy.mockRestore();
}); });
test("moveEvent sends MOVE request with destination header", async () => { test("moveEvent sends MOVE request with destination header", async () => {
@@ -27,7 +27,7 @@ describe("Event Preview Display", () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
sessionStorage.clear(); sessionStorage.clear();
(window as any).MAIL_SPA_URL = null; window.MAIL_SPA_URL = null;
}); });
const preloadedState = { const preloadedState = {
@@ -598,7 +598,7 @@ describe("Event Preview Display", () => {
}); });
}); });
it("properly render message button when MAIL_SPA_URL is not null and event has attendees", () => { it("properly render message button when MAIL_SPA_URL is not null and event has attendees", () => {
(window as any).MAIL_SPA_URL = "test"; window.MAIL_SPA_URL = "test";
renderWithProviders( renderWithProviders(
<EventPreviewModal <EventPreviewModal
open={true} open={true}
@@ -612,7 +612,7 @@ describe("Event Preview Display", () => {
expect(screen.getByText("eventPreview.emailAttendees")).toBeInTheDocument(); expect(screen.getByText("eventPreview.emailAttendees")).toBeInTheDocument();
}); });
it("doesnt render message button when MAIL_SPA_URL is not null and event has no attendees", () => { it("doesnt render message button when MAIL_SPA_URL is not null and event has no attendees", () => {
(window as any).MAIL_SPA_URL = "test"; window.MAIL_SPA_URL = "test";
renderWithProviders( renderWithProviders(
<EventPreviewModal <EventPreviewModal
open={true} open={true}
@@ -637,7 +637,7 @@ describe("Event Preview Display", () => {
expect(screen.queryByTestId("EmailIcon")).not.toBeInTheDocument(); expect(screen.queryByTestId("EmailIcon")).not.toBeInTheDocument();
}); });
it("message button opens url with attendees as uri and title as subject", () => { it("message button opens url with attendees as uri and title as subject", () => {
(window as any).MAIL_SPA_URL = "test"; window.MAIL_SPA_URL = "test";
const mockOpen = jest.fn(); const mockOpen = jest.fn();
window.open = mockOpen; window.open = mockOpen;
@@ -669,7 +669,7 @@ describe("Event Preview Display", () => {
}); });
it("message button encodes special characters in event title correctly", () => { it("message button encodes special characters in event title correctly", () => {
(window as any).MAIL_SPA_URL = "test"; window.MAIL_SPA_URL = "test";
const mockOpen = jest.fn(); const mockOpen = jest.fn();
window.open = mockOpen; window.open = mockOpen;
@@ -952,7 +952,7 @@ describe("Event Preview Display", () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
(window as any).MAIL_SPA_URL = null; window.MAIL_SPA_URL = null;
}); });
const createStateWithAttendees = (attendees: any[]) => ({ const createStateWithAttendees = (attendees: any[]) => ({
@@ -322,7 +322,7 @@ describe("WebSocketGate", () => {
it("should reset reconnection attempts counter on successful connection", async () => { it("should reset reconnection attempts counter on successful connection", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const consoleLog = jest.spyOn(console, "log").mockImplementation(); const consoleLog = jest.spyOn(console, "info").mockImplementation();
let onCloseCallback: Function | undefined; let onCloseCallback: Function | undefined;
(createWebSocketConnection as jest.Mock).mockImplementation( (createWebSocketConnection as jest.Mock).mockImplementation(
@@ -576,7 +576,7 @@ describe("WebSocketGate", () => {
it("should reset attempt counter when online event fires", async () => { it("should reset attempt counter when online event fires", async () => {
jest.useFakeTimers(); jest.useFakeTimers();
const consoleLog = jest.spyOn(console, "log").mockImplementation(); const consoleLog = jest.spyOn(console, "info").mockImplementation();
let onCloseCallback: Function | undefined; let onCloseCallback: Function | undefined;
(createWebSocketConnection as jest.Mock).mockImplementation( (createWebSocketConnection as jest.Mock).mockImplementation(
@@ -49,22 +49,22 @@ describe("createWebSocketConnection", () => {
beforeEach(() => { beforeEach(() => {
({ webSocketInstances, mockWebSocket, cleanup } = setupWebsocket()); ({ webSocketInstances, mockWebSocket, cleanup } = setupWebsocket());
(window as any).WEBSOCKET_URL = "wss://calendar.example.com"; window.WEBSOCKET_URL = "wss://calendar.example.com";
(fetchWebSocketTicket as jest.Mock).mockResolvedValue(mockTicket); (fetchWebSocketTicket as jest.Mock).mockResolvedValue(mockTicket);
}); });
afterEach(() => { afterEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
delete (window as any).WEBSOCKET_URL; delete window.WEBSOCKET_URL;
delete (window as any).CALENDAR_BASE_URL; delete window.CALENDAR_BASE_URL;
cleanup(); cleanup();
}); });
/** ---------- Tests ---------- */ /** ---------- Tests ---------- */
it("throws when WEBSOCKET_URL is not defined", async () => { it("throws when WEBSOCKET_URL is not defined", async () => {
delete (window as any).WEBSOCKET_URL; delete window.WEBSOCKET_URL;
const mockCallbacks = { const mockCallbacks = {
onMessage: jest.fn(), onMessage: jest.fn(),
}; };
@@ -88,8 +88,8 @@ describe("createWebSocketConnection", () => {
}); });
it("creates WebSocket with correct URL and ticket without the WEBSOCKET_URL", async () => { it("creates WebSocket with correct URL and ticket without the WEBSOCKET_URL", async () => {
delete (window as any).WEBSOCKET_URL; delete window.WEBSOCKET_URL;
(window as any).CALENDAR_BASE_URL = "https://calendar.example.com"; window.CALENDAR_BASE_URL = "https://calendar.example.com";
await createAndOpenConnection(); await createAndOpenConnection();
expect(mockWebSocket).toHaveBeenCalledWith( expect(mockWebSocket).toHaveBeenCalledWith(
@@ -57,7 +57,7 @@ describe("websocket messages storm", () => {
jest.resetModules(); jest.resetModules();
(getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange); (getDisplayedCalendarRange as jest.Mock).mockReturnValue(mockRange);
(store.getState as jest.Mock).mockReturnValue(mockState); (store.getState as jest.Mock).mockReturnValue(mockState);
(window as any).WS_DEBOUNCE_PERIOD_MS = 500; window.WS_DEBOUNCE_PERIOD_MS = 500;
mockAccumulators.calendarsToRefresh = new Map<string, any>(); mockAccumulators.calendarsToRefresh = new Map<string, any>();
mockAccumulators.calendarsToHide = new Set(); mockAccumulators.calendarsToHide = new Set();
mockAccumulators.currentDebouncePeriod = 0; mockAccumulators.currentDebouncePeriod = 0;
@@ -118,7 +118,7 @@ describe("websocket messages storm", () => {
}); });
it("executes immediately when debounce is disabled", () => { it("executes immediately when debounce is disabled", () => {
(window as any).WS_DEBOUNCE_PERIOD_MS = 0; window.WS_DEBOUNCE_PERIOD_MS = 0;
updateCalendars( updateCalendars(
{ "/calendars/cal1/entry1": { syncToken: "abc" } }, { "/calendars/cal1/entry1": { syncToken: "abc" } },
@@ -42,16 +42,16 @@ describe("registerToCalendars", () => {
}); });
it("should log registration", () => { it("should log registration", () => {
const consoleLogSpy = jest.spyOn(console, "log").mockImplementation(); const consoleInfoSpy = jest.spyOn(console, "info").mockImplementation();
const calendarURIs = ["/calendars/cal1", "/calendars/cal2"]; const calendarURIs = ["/calendars/cal1", "/calendars/cal2"];
registerToCalendars(mockSocket, calendarURIs); registerToCalendars(mockSocket, calendarURIs);
expect(consoleLogSpy).toHaveBeenCalledWith( expect(consoleInfoSpy).toHaveBeenCalledWith(
"Registered to calendars", "Registered to calendars",
calendarURIs calendarURIs
); );
consoleLogSpy.mockRestore(); consoleInfoSpy.mockRestore();
}); });
}); });
@@ -43,16 +43,16 @@ describe("unregisterToCalendars", () => {
}); });
it("should log unregistration", () => { it("should log unregistration", () => {
const consoleLogSpy = jest.spyOn(console, "log").mockImplementation(); const consoleInfoSpy = jest.spyOn(console, "info").mockImplementation();
const calendarURIs = ["/calendars/cal1", "/calendars/cal2"]; const calendarURIs = ["/calendars/cal1", "/calendars/cal2"];
unregisterToCalendars(mockSocket, calendarURIs); unregisterToCalendars(mockSocket, calendarURIs);
expect(consoleLogSpy).toHaveBeenCalledWith( expect(consoleInfoSpy).toHaveBeenCalledWith(
"Unregistered to calendars", "Unregistered to calendars",
calendarURIs calendarURIs
); );
consoleLogSpy.mockRestore(); consoleInfoSpy.mockRestore();
}); });
}); });
+1 -6
View File
@@ -1,10 +1,5 @@
import { getCalendarVisibility } from "@/components/Calendar/utils/calendarUtils"; import { getCalendarVisibility } from "@/components/Calendar/utils/calendarUtils";
import { AclEntry } from "@/features/Calendars/types/CalendarData";
interface AclEntry {
privilege: string;
principal: string;
protected: boolean;
}
describe("getCalendarVisibility", () => { describe("getCalendarVisibility", () => {
it("returns 'public' when {DAV:}authenticated has {DAV:}read", () => { it("returns 'public' when {DAV:}authenticated has {DAV:}read", () => {
+74
View File
@@ -0,0 +1,74 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import importPlugin from "eslint-plugin-import";
import jest from "eslint-plugin-jest";
import prettier from "eslint-config-prettier";
export default [
{
ignores: ["dist", "build", "node_modules"],
},
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{js,jsx,ts,tsx}"],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaFeatures: { jsx: true },
project: true,
},
},
plugins: {
react,
"react-hooks": reactHooks,
import: importPlugin,
jest,
},
settings: {
react: {
version: "detect",
},
},
rules: {
/* React */
"react/react-in-jsx-scope": "off", // React 18+
"react/prop-types": "off",
/* Hooks */
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
/* Imports */
"import/order": "off",
/* TS */
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
/* Jest */
"jest/no-disabled-tests": "warn",
"jest/no-focused-tests": "error",
/* Prettier compatibility */
...prettier.rules,
/* No noises */
"no-debugger": "error",
"no-console": ["warn", { allow: ["info", "warn", "error"] }],
},
},
];
+151 -85
View File
@@ -65,7 +65,7 @@
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.2.1", "eslint-plugin-jest": "^29.13.0",
"eslint-plugin-promise": "^7.2.1", "eslint-plugin-promise": "^7.2.1",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
@@ -77,7 +77,8 @@
"prettier": "^3.6.2", "prettier": "^3.6.2",
"ts-jest": "^29.4.6", "ts-jest": "^29.4.6",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"typescript-eslint": "^8.54.0"
}, },
"engines": { "engines": {
"node": "24.x" "node": "24.x"
@@ -141,6 +142,7 @@
"integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.27.1", "@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.28.5", "@babel/generator": "^7.28.5",
@@ -2073,6 +2075,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
}, },
@@ -2096,6 +2099,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=18" "node": ">=18"
} }
@@ -2198,6 +2202,7 @@
"resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
"integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
@@ -2241,6 +2246,7 @@
"resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
"integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"@emotion/babel-plugin": "^11.13.5", "@emotion/babel-plugin": "^11.13.5",
@@ -2287,9 +2293,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@eslint-community/eslint-utils": { "node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -2513,6 +2519,7 @@
"resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz", "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.20.tgz",
"integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==", "integrity": "sha512-1cukXLlePFiJ8YKXn/4tMKsy0etxYLCkXk8nUCFi11nRONF2Ba2CD5b21/ovtOO2tL6afTJfwmc1ed3HG7eB1g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"preact": "~10.12.1" "preact": "~10.12.1"
} }
@@ -3271,6 +3278,7 @@
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.6.tgz", "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.6.tgz",
"integrity": "sha512-R4DaYF3dgCQCUAkr4wW1w26GHXcf5rCmBRHVBuuvJvaGLmZdD8EjatP80Nz5JCw0KxORAzwftnHzXVnjR8HnFw==", "integrity": "sha512-R4DaYF3dgCQCUAkr4wW1w26GHXcf5rCmBRHVBuuvJvaGLmZdD8EjatP80Nz5JCw0KxORAzwftnHzXVnjR8HnFw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@mui/core-downloads-tracker": "^7.3.6", "@mui/core-downloads-tracker": "^7.3.6",
@@ -3381,6 +3389,7 @@
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.6.tgz", "resolved": "https://registry.npmjs.org/@mui/system/-/system-7.3.6.tgz",
"integrity": "sha512-8fehAazkHNP1imMrdD2m2hbA9sl7Ur6jfuNweh5o4l9YPty4iaZzRXqYvBCWQNwFaSHmMEj2KPbyXGp7Bt73Rg==", "integrity": "sha512-8fehAazkHNP1imMrdD2m2hbA9sl7Ur6jfuNweh5o4l9YPty4iaZzRXqYvBCWQNwFaSHmMEj2KPbyXGp7Bt73Rg==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.28.4", "@babel/runtime": "^7.28.4",
"@mui/private-theming": "^7.3.6", "@mui/private-theming": "^7.3.6",
@@ -3739,6 +3748,7 @@
"integrity": "sha512-LvoOF53PL6zXgdzEhgnnP51S4FseDFH1bHrobK4EK6zZX/tN8qgf5tdlmN7h4OkMv/Qs1oUfvj0QcLWSstnnvA==", "integrity": "sha512-LvoOF53PL6zXgdzEhgnnP51S4FseDFH1bHrobK4EK6zZX/tN8qgf5tdlmN7h4OkMv/Qs1oUfvj0QcLWSstnnvA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@rspack/core": "1.6.8", "@rspack/core": "1.6.8",
"@rspack/lite-tapable": "~1.1.0", "@rspack/lite-tapable": "~1.1.0",
@@ -3966,6 +3976,7 @@
"integrity": "sha512-FolcIAH5FW4J2FET+qwjd1kNeFbCkd0VLuIHO0thyolEjaPSxw5qxG67DA7BZGm6PVcoiSgPLks1DL6eZ8c+fA==", "integrity": "sha512-FolcIAH5FW4J2FET+qwjd1kNeFbCkd0VLuIHO0thyolEjaPSxw5qxG67DA7BZGm6PVcoiSgPLks1DL6eZ8c+fA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@module-federation/runtime-tools": "0.21.6", "@module-federation/runtime-tools": "0.21.6",
"@rspack/binding": "1.6.8", "@rspack/binding": "1.6.8",
@@ -4225,6 +4236,7 @@
"integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.21.3", "@babel/core": "^7.21.3",
"@svgr/babel-preset": "8.1.0", "@svgr/babel-preset": "8.1.0",
@@ -4309,6 +4321,7 @@
"integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"dependencies": { "dependencies": {
"tslib": "^2.8.0" "tslib": "^2.8.0"
} }
@@ -4319,6 +4332,7 @@
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.10.4", "@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5", "@babel/runtime": "^7.12.5",
@@ -4621,6 +4635,7 @@
"integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==", "integrity": "sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
@@ -4642,6 +4657,7 @@
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
"integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/prop-types": "*", "@types/prop-types": "*",
"csstype": "^3.2.2" "csstype": "^3.2.2"
@@ -4653,6 +4669,7 @@
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"peerDependencies": { "peerDependencies": {
"@types/react": "^18.0.0" "@types/react": "^18.0.0"
} }
@@ -4704,20 +4721,21 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
"integrity": "sha512-XtssGWJvypyM2ytBnSnKtHYOGT+4ZwTnBVl36TA4nRO2f4PRNGz5/1OszHzcZCvcBMh+qb7I06uoCmLTRdR9og==", "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/regexpp": "^4.10.0", "@eslint-community/regexpp": "^4.12.2",
"@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/type-utils": "8.51.0", "@typescript-eslint/type-utils": "8.54.0",
"@typescript-eslint/utils": "8.51.0", "@typescript-eslint/utils": "8.54.0",
"@typescript-eslint/visitor-keys": "8.51.0", "@typescript-eslint/visitor-keys": "8.54.0",
"ignore": "^7.0.0", "ignore": "^7.0.5",
"natural-compare": "^1.4.0", "natural-compare": "^1.4.0",
"ts-api-utils": "^2.2.0" "ts-api-utils": "^2.4.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4727,23 +4745,24 @@
"url": "https://opencollective.com/typescript-eslint" "url": "https://opencollective.com/typescript-eslint"
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/parser": "^8.51.0", "@typescript-eslint/parser": "^8.54.0",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/parser": { "node_modules/@typescript-eslint/parser": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
"integrity": "sha512-3xP4XzzDNQOIqBMWogftkwxhg5oMKApqY0BAflmLZiFYHqyhSOxv/cd/zPQLTcCXr4AkaKb25joocY0BD1WC6A==", "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.51.0", "@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/typescript-estree": "8.54.0",
"@typescript-eslint/visitor-keys": "8.51.0", "@typescript-eslint/visitor-keys": "8.54.0",
"debug": "^4.3.4" "debug": "^4.4.3"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4758,15 +4777,15 @@
} }
}, },
"node_modules/@typescript-eslint/project-service": { "node_modules/@typescript-eslint/project-service": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
"integrity": "sha512-Luv/GafO07Z7HpiI7qeEW5NW8HUtZI/fo/kE0YbtQEFpJRUuR0ajcWfCE5bnMvL7QQFrmT/odMe8QZww8X2nfQ==", "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.51.0", "@typescript-eslint/tsconfig-utils": "^8.54.0",
"@typescript-eslint/types": "^8.51.0", "@typescript-eslint/types": "^8.54.0",
"debug": "^4.3.4" "debug": "^4.4.3"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4780,14 +4799,14 @@
} }
}, },
"node_modules/@typescript-eslint/scope-manager": { "node_modules/@typescript-eslint/scope-manager": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
"integrity": "sha512-JhhJDVwsSx4hiOEQPeajGhCWgBMBwVkxC/Pet53EpBVs7zHHtayKefw1jtPaNRXpI9RA2uocdmpdfE7T+NrizA==", "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.51.0", "@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.51.0" "@typescript-eslint/visitor-keys": "8.54.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4798,9 +4817,9 @@
} }
}, },
"node_modules/@typescript-eslint/tsconfig-utils": { "node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
"integrity": "sha512-Qi5bSy/vuHeWyir2C8u/uqGMIlIDu8fuiYWv48ZGlZ/k+PRPHtaAu7erpc7p5bzw2WNNSniuxoMSO4Ar6V9OXw==", "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -4815,17 +4834,17 @@
} }
}, },
"node_modules/@typescript-eslint/type-utils": { "node_modules/@typescript-eslint/type-utils": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
"integrity": "sha512-0XVtYzxnobc9K0VU7wRWg1yiUrw4oQzexCG2V2IDxxCxhqBMSMbjB+6o91A+Uc0GWtgjCa3Y8bi7hwI0Tu4n5Q==", "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.51.0", "@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.51.0", "@typescript-eslint/typescript-estree": "8.54.0",
"@typescript-eslint/utils": "8.51.0", "@typescript-eslint/utils": "8.54.0",
"debug": "^4.3.4", "debug": "^4.4.3",
"ts-api-utils": "^2.2.0" "ts-api-utils": "^2.4.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4840,9 +4859,9 @@
} }
}, },
"node_modules/@typescript-eslint/types": { "node_modules/@typescript-eslint/types": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
"integrity": "sha512-TizAvWYFM6sSscmEakjY3sPqGwxZRSywSsPEiuZF6d5GmGD9Gvlsv0f6N8FvAAA0CD06l3rIcWNbsN1e5F/9Ag==", "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -4854,21 +4873,21 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree": { "node_modules/@typescript-eslint/typescript-estree": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
"integrity": "sha512-1qNjGqFRmlq0VW5iVlcyHBbCjPB7y6SxpBkrbhNWMy/65ZoncXCEPJxkRZL8McrseNH6lFhaxCIaX+vBuFnRng==", "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/project-service": "8.51.0", "@typescript-eslint/project-service": "8.54.0",
"@typescript-eslint/tsconfig-utils": "8.51.0", "@typescript-eslint/tsconfig-utils": "8.54.0",
"@typescript-eslint/types": "8.51.0", "@typescript-eslint/types": "8.54.0",
"@typescript-eslint/visitor-keys": "8.51.0", "@typescript-eslint/visitor-keys": "8.54.0",
"debug": "^4.3.4", "debug": "^4.4.3",
"minimatch": "^9.0.4", "minimatch": "^9.0.5",
"semver": "^7.6.0", "semver": "^7.7.3",
"tinyglobby": "^0.2.15", "tinyglobby": "^0.2.15",
"ts-api-utils": "^2.2.0" "ts-api-utils": "^2.4.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4882,9 +4901,9 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.7.3", "version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"bin": { "bin": {
@@ -4895,16 +4914,16 @@
} }
}, },
"node_modules/@typescript-eslint/utils": { "node_modules/@typescript-eslint/utils": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
"integrity": "sha512-11rZYxSe0zabiKaCP2QAwRf/dnmgFgvTmeDTtZvUvXG3UuAdg/GU02NExmmIXzz3vLGgMdtrIosI84jITQOxUA==", "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.7.0", "@eslint-community/eslint-utils": "^4.9.1",
"@typescript-eslint/scope-manager": "8.51.0", "@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.51.0", "@typescript-eslint/types": "8.54.0",
"@typescript-eslint/typescript-estree": "8.51.0" "@typescript-eslint/typescript-estree": "8.54.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -4919,13 +4938,13 @@
} }
}, },
"node_modules/@typescript-eslint/visitor-keys": { "node_modules/@typescript-eslint/visitor-keys": {
"version": "8.51.0", "version": "8.54.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.51.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
"integrity": "sha512-mM/JRQOzhVN1ykejrvwnBRV3+7yTKK8tVANVN3o1O0t0v7o+jqdVu9crPy5Y9dov15TJk/FTIgoUGHrTOVL3Zg==", "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@typescript-eslint/types": "8.51.0", "@typescript-eslint/types": "8.54.0",
"eslint-visitor-keys": "^4.2.1" "eslint-visitor-keys": "^4.2.1"
}, },
"engines": { "engines": {
@@ -5244,6 +5263,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"acorn": "bin/acorn" "acorn": "bin/acorn"
}, },
@@ -6003,6 +6023,7 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"baseline-browser-mapping": "^2.9.0", "baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759", "caniuse-lite": "^1.0.30001759",
@@ -6714,6 +6735,7 @@
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz",
"integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.21.0" "@babel/runtime": "^7.21.0"
}, },
@@ -6729,7 +6751,8 @@
"version": "1.11.19", "version": "1.11.19",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz",
"integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
@@ -7329,6 +7352,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1", "@eslint-community/regexpp": "^4.12.1",
@@ -7518,9 +7542,9 @@
} }
}, },
"node_modules/eslint-plugin-jest": { "node_modules/eslint-plugin-jest": {
"version": "29.11.2", "version": "29.13.0",
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.11.2.tgz", "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-29.13.0.tgz",
"integrity": "sha512-ISlgFckxC8Pon9/3gD2Cosbt9qA9VmrNobI6aGHfimklCOuPR5uftI413YBwa2rqLWy+VHDlx5je/6elPUMLlg==", "integrity": "sha512-VoONe0NsaQLb7ijvg4k35rzchqfyCaBsXammNMCkTyLvKLTpzQOVdXiPC54q7Vp/W7shMcqPBLwAc3yRSiGjSQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -7531,8 +7555,9 @@
}, },
"peerDependencies": { "peerDependencies": {
"@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/eslint-plugin": "^8.0.0",
"eslint": "^8.57.0 || ^9.0.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
"jest": "*" "jest": "*",
"typescript": ">=4.8.4 <6.0.0"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
@@ -7540,6 +7565,9 @@
}, },
"jest": { "jest": {
"optional": true "optional": true
},
"typescript": {
"optional": true
} }
} }
}, },
@@ -8683,6 +8711,7 @@
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz", "resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==", "integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/runtime": "^7.7.6" "@babel/runtime": "^7.7.6"
} }
@@ -9657,6 +9686,7 @@
"integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==", "integrity": "sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@jest/core": "30.2.0", "@jest/core": "30.2.0",
"@jest/types": "30.2.0", "@jest/types": "30.2.0",
@@ -10420,6 +10450,7 @@
"integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==", "integrity": "sha512-/xdLSWxK5QkqG524ONSjvg3V/FkNyCv538OIBdQqPNaAta3AsXj/Bd2FbvR87yMbXO2hFSWiAe/Q6IkVPDw+mw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@babel/core": "^7.19.6", "@babel/core": "^7.19.6",
"@svgr/babel-preset": "^6.5.1", "@svgr/babel-preset": "^6.5.1",
@@ -10878,6 +10909,7 @@
"integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"cssstyle": "^4.2.1", "cssstyle": "^4.2.1",
"data-urls": "^5.0.0", "data-urls": "^5.0.0",
@@ -11314,6 +11346,7 @@
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": "*" "node": "*"
} }
@@ -11323,6 +11356,7 @@
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz", "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
"integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==", "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"moment": "^2.29.4" "moment": "^2.29.4"
}, },
@@ -11358,7 +11392,6 @@
} }
], ],
"license": "MIT", "license": "MIT",
"peer": true,
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@@ -12307,6 +12340,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0" "loose-envify": "^1.1.0"
}, },
@@ -12329,6 +12363,7 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
"integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"loose-envify": "^1.1.0", "loose-envify": "^1.1.0",
"scheduler": "^0.23.2" "scheduler": "^0.23.2"
@@ -12348,6 +12383,7 @@
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT", "license": "MIT",
"peer": true,
"dependencies": { "dependencies": {
"@types/use-sync-external-store": "^0.0.6", "@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0" "use-sync-external-store": "^1.4.0"
@@ -12372,6 +12408,7 @@
"integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -12472,7 +12509,8 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"license": "MIT" "license": "MIT",
"peer": true
}, },
"node_modules/redux-first-history": { "node_modules/redux-first-history": {
"version": "5.2.0", "version": "5.2.0",
@@ -13699,6 +13737,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"engines": { "engines": {
"node": ">=12" "node": ">=12"
}, },
@@ -13783,9 +13822,9 @@
} }
}, },
"node_modules/ts-api-utils": { "node_modules/ts-api-utils": {
"version": "2.3.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
"integrity": "sha512-6eg3Y9SF7SsAvGzRHQvvc1skDAhwI4YQ32ui1scxD1Ccr0G5qIIbUBT3pFTKX8kmWIQClHobtUdNuaBgwdfdWg==", "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@@ -13879,6 +13918,7 @@
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true, "dev": true,
"peer": true,
"dependencies": { "dependencies": {
"@cspotcode/source-map-support": "^0.8.0", "@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7", "@tsconfig/node10": "^1.0.7",
@@ -14095,6 +14135,7 @@
"integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"peer": true,
"bin": { "bin": {
"tsc": "bin/tsc", "tsc": "bin/tsc",
"tsserver": "bin/tsserver" "tsserver": "bin/tsserver"
@@ -14103,6 +14144,30 @@
"node": ">=4.2.0" "node": ">=4.2.0"
} }
}, },
"node_modules/typescript-eslint": {
"version": "8.54.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz",
"integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.54.0",
"@typescript-eslint/parser": "8.54.0",
"@typescript-eslint/typescript-estree": "8.54.0",
"@typescript-eslint/utils": "8.54.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
},
"node_modules/uglify-js": { "node_modules/uglify-js": {
"version": "3.19.3", "version": "3.19.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz",
@@ -14903,6 +14968,7 @@
"integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"peer": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/colinhacks" "url": "https://github.com/sponsors/colinhacks"
} }
+5 -3
View File
@@ -41,7 +41,8 @@
"build": "rsbuild build", "build": "rsbuild build",
"preview": "rsbuild preview", "preview": "rsbuild preview",
"test": "jest", "test": "jest",
"lint": "eslint src --ext .js,.jsx,.ts,.tsx", "lint": "eslint src",
"lint:fix": "eslint src --fix",
"jest-preview": "jest-preview" "jest-preview": "jest-preview"
}, },
"browserslist": { "browserslist": {
@@ -83,7 +84,7 @@
"eslint": "^9.39.1", "eslint": "^9.39.1",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0", "eslint-plugin-import": "^2.32.0",
"eslint-plugin-jest": "^29.2.1", "eslint-plugin-jest": "^29.13.0",
"eslint-plugin-promise": "^7.2.1", "eslint-plugin-promise": "^7.2.1",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
@@ -95,6 +96,7 @@
"prettier": "^3.6.2", "prettier": "^3.6.2",
"ts-jest": "^29.4.6", "ts-jest": "^29.4.6",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"typescript-eslint": "^8.54.0"
} }
} }
+1 -1
View File
@@ -48,7 +48,7 @@ function App() {
); );
const settingsLanguage = useAppSelector((state) => state.settings.language); const settingsLanguage = useAppSelector((state) => state.settings.language);
const savedLang = localStorage.getItem("lang"); const savedLang = localStorage.getItem("lang");
const defaultLang = (window as any).LANG; const defaultLang = window.LANG;
const lang = const lang =
[userLanguage, settingsLanguage, savedLang, defaultLang].find( [userLanguage, settingsLanguage, savedLang, defaultLang].find(
+2 -2
View File
@@ -15,7 +15,7 @@ export default function UserSearch({
placeholder, placeholder,
}: { }: {
attendees: userAttendee[]; attendees: userAttendee[];
setAttendees: Function; setAttendees: (attendees: userAttendee[]) => void;
disabled?: boolean; disabled?: boolean;
inputSlot?: ( inputSlot?: (
params: ExtendedAutocompleteRenderInputParams params: ExtendedAutocompleteRenderInputParams
@@ -47,7 +47,7 @@ export default function UserSearch({
disabled={disabled} disabled={disabled}
inputSlot={inputSlot} inputSlot={inputSlot}
placeholder={placeholder} placeholder={placeholder}
onChange={(event: any, value: User[]) => { onChange={(_event, value: User[]) => {
setAttendees( setAttendees(
value.map((attendee: User) => value.map((attendee: User) =>
createAttendee({ createAttendee({
+6 -6
View File
@@ -23,6 +23,7 @@ import {
useEffect, useEffect,
useState, useState,
type ReactNode, type ReactNode,
type SyntheticEvent,
} from "react"; } from "react";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
@@ -55,7 +56,7 @@ export function PeopleSearch({
customSlotProps, customSlotProps,
}: { }: {
selectedUsers: User[]; selectedUsers: User[];
onChange: (event: any, users: User[]) => void; onChange: (event: SyntheticEvent, users: User[]) => void;
objectTypes: string[]; objectTypes: string[];
disabled?: boolean; disabled?: boolean;
freeSolo?: boolean; freeSolo?: boolean;
@@ -155,7 +156,7 @@ export function PeopleSearch({
), ),
}; };
const enhancedParamsWithInputProps = { const enhancedParams = {
...params, ...params,
InputProps: inputProps, InputProps: inputProps,
inputProps: { inputProps: {
@@ -164,8 +165,6 @@ export function PeopleSearch({
}, },
}; };
const { InputProps, ...enhancedParams } = enhancedParamsWithInputProps;
const handleEnterKey = (e: React.KeyboardEvent<HTMLInputElement>) => { const handleEnterKey = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter" && onToggleEventPreview) { if (e.key === "Enter" && onToggleEventPreview) {
e.preventDefault(); e.preventDefault();
@@ -193,7 +192,7 @@ export function PeopleSearch({
{t("peopleSearch.label")} {t("peopleSearch.label")}
</label> </label>
{inputSlot({ {inputSlot({
...enhancedParamsWithInputProps, ...enhancedParams,
error: !!inputError, error: !!inputError,
helperText: inputError, helperText: inputError,
placeholder: searchPlaceholder, placeholder: searchPlaceholder,
@@ -218,6 +217,7 @@ export function PeopleSearch({
</> </>
); );
}, },
// eslint-disable-next-line react-hooks/exhaustive-deps
[inputError, t, onToggleEventPreview, loading, searchPlaceholder] [inputError, t, onToggleEventPreview, loading, searchPlaceholder]
); );
@@ -296,7 +296,7 @@ export function PeopleSearch({
} }
renderOption={(props, option) => { renderOption={(props, option) => {
if (selectedUsers.find((u) => u.email === option.email)) return null; if (selectedUsers.find((u) => u.email === option.email)) return null;
const { key, ...otherProps } = props as any; const { key, ...otherProps } = props;
return ( return (
<ListItem key={key + option?.email} {...otherProps} disableGutters> <ListItem key={key + option?.email} {...otherProps} disableGutters>
<ListItemAvatar> <ListItemAvatar>
+1 -1
View File
@@ -22,7 +22,7 @@ import { SnackbarAlert } from "../Loading/SnackBarAlert";
export function AccessTab({ calendar }: { calendar: Calendar }) { export function AccessTab({ calendar }: { calendar: Calendar }) {
const { t } = useI18n(); const { t } = useI18n();
const calDAVLink = `${(window as any).CALENDAR_BASE_URL}${calendar.link.replace(".json", "")}`; const calDAVLink = `${window.CALENDAR_BASE_URL}${calendar.link.replace(".json", "")}`;
const [secretLink, setSecretLink] = useState(""); const [secretLink, setSecretLink] = useState("");
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
+18 -15
View File
@@ -15,6 +15,7 @@ import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
import { setSelectedCalendars as setSelectedCalendarsToStorage } from "@/utils/storage/setSelectedCalendars"; import { setSelectedCalendars as setSelectedCalendarsToStorage } from "@/utils/storage/setSelectedCalendars";
import { useSelectedCalendars } from "@/utils/storage/useSelectedCalendars"; import { useSelectedCalendars } from "@/utils/storage/useSelectedCalendars";
import { browserDefaultTimeZone } from "@/utils/timezone"; import { browserDefaultTimeZone } from "@/utils/timezone";
import type { EventApi, LocaleInput } from "@fullcalendar/core";
import { CalendarApi, DateSelectArg } from "@fullcalendar/core"; import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
import frLocale from "@fullcalendar/core/locales/fr"; import frLocale from "@fullcalendar/core/locales/fr";
import ruLocale from "@fullcalendar/core/locales/ru"; import ruLocale from "@fullcalendar/core/locales/ru";
@@ -47,7 +48,7 @@ import {
updateSlotLabelVisibility, updateSlotLabelVisibility,
} from "./utils/calendarUtils"; } from "./utils/calendarUtils";
const localeMap: Record<string, any> = { const localeMap: Record<string, LocaleInput | undefined> = {
fr: frLocale, fr: frLocale,
ru: ruLocale, ru: ruLocale,
vi: viLocale, vi: viLocale,
@@ -221,7 +222,7 @@ export default function CalendarApp({
[rangeStart, rangeEnd] [rangeStart, rangeEnd]
); );
let filteredEvents: CalendarEvent[] = extractEvents( const filteredEvents: CalendarEvent[] = extractEvents(
selectedCalendars, selectedCalendars,
calendars || {}, calendars || {},
userData?.email, userData?.email,
@@ -233,7 +234,7 @@ export default function CalendarApp({
[tempcalendars] [tempcalendars]
); );
let filteredTempEvents: CalendarEvent[] = extractEvents( const filteredTempEvents: CalendarEvent[] = extractEvents(
tempCalendarIds, tempCalendarIds,
tempcalendars || {}, tempcalendars || {},
userData?.email, userData?.email,
@@ -303,7 +304,7 @@ export default function CalendarApp({
}, },
}) })
).unwrap(); ).unwrap();
} catch (error) { } catch {
fetchedRangesRef.current[id] = ""; fetchedRangesRef.current[id] = "";
} }
}); });
@@ -323,6 +324,7 @@ export default function CalendarApp({
return () => { return () => {
cancelled = true; cancelled = true;
}; };
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [dispatch, rangeKey, sortedSelectedCalendars, rangeStart, rangeEnd]); }, [dispatch, rangeKey, sortedSelectedCalendars, rangeStart, rangeEnd]);
useEffect(() => { useEffect(() => {
@@ -352,6 +354,7 @@ export default function CalendarApp({
prefetchedCalendarsRef.current[id] = ""; prefetchedCalendarsRef.current[id] = "";
}); });
}); });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
calendarIdsString, calendarIdsString,
selectedCalendars, selectedCalendars,
@@ -458,7 +461,7 @@ export default function CalendarApp({
calType: "temp", calType: "temp",
}) })
).unwrap(); ).unwrap();
} catch (error) { } catch {
tempFetchedRangesRef.current[id] = ""; tempFetchedRangesRef.current[id] = "";
} }
}); });
@@ -521,7 +524,7 @@ export default function CalendarApp({
}) })
); );
} }
} catch (err) { } catch {
// Ignore sessionStorage errors // Ignore sessionStorage errors
} }
} }
@@ -543,10 +546,10 @@ export default function CalendarApp({
null null
); );
const [, setTypeOfAction] = useState<"solo" | "all" | undefined>(undefined); const [, setTypeOfAction] = useState<"solo" | "all" | undefined>(undefined);
const [afterChoiceFunc, setAfterChoiceFunc] = useState<Function>(); const [afterChoiceFunc, setAfterChoiceFunc] = useState<
const [selectedEvent, setSelectedEvent] = useState<CalendarEvent>( ((type: "solo" | "all" | undefined) => void) | undefined
{} as CalendarEvent >();
); const [, setSelectedEvent] = useState<CalendarEvent>({} as CalendarEvent);
const [selectedRange, setSelectedRange] = useState<DateSelectArg | null>( const [selectedRange, setSelectedRange] = useState<DateSelectArg | null>(
null null
); );
@@ -587,7 +590,7 @@ export default function CalendarApp({
}); });
if (process.env.NODE_ENV === "test") { if (process.env.NODE_ENV === "test") {
(window as any).__calendarRef = calendarRef; window.__calendarRef = calendarRef;
} }
const { t, lang } = useI18n(); const { t, lang } = useI18n();
@@ -635,7 +638,6 @@ export default function CalendarApp({
<MiniCalendar <MiniCalendar
calendarRef={calendarRef} calendarRef={calendarRef}
selectedDate={selectedMiniDate} selectedDate={selectedMiniDate}
setSelectedDate={setSelectedDate}
setSelectedMiniDate={setSelectedMiniDate} setSelectedMiniDate={setSelectedMiniDate}
dottedEvents={dottedEvents} dottedEvents={dottedEvents}
/> />
@@ -697,7 +699,7 @@ export default function CalendarApp({
userData?.email, userData?.email,
isPending isPending
)} )}
eventOrder={(a: any, b: any) => eventOrder={(a: EventApi, b: EventApi) =>
a.extendedProps.priority - b.extendedProps.priority a.extendedProps.priority - b.extendedProps.priority
} }
weekNumbers={ weekNumbers={
@@ -825,10 +827,11 @@ export default function CalendarApp({
<EditModeDialog <EditModeDialog
type={openEditModePopup} type={openEditModePopup}
setOpen={setOpenEditModePopup} setOpen={setOpenEditModePopup}
event={selectedEvent}
eventAction={(type: "solo" | "all" | undefined) => { eventAction={(type: "solo" | "all" | undefined) => {
setTypeOfAction(type); setTypeOfAction(type);
afterChoiceFunc && afterChoiceFunc(type); if (afterChoiceFunc) {
afterChoiceFunc(type);
}
}} }}
/> />
{openEventDisplay && eventDisplayedId && eventDisplayedCalId && ( {openEventDisplay && eventDisplayedId && eventDisplayedCalId && (
@@ -123,11 +123,11 @@ function ColorPickerBox({
selectedColor ?? { light: "#ffffff", dark: "#808080" } selectedColor ?? { light: "#ffffff", dark: "#808080" }
); );
const [color, setColor] = useState(oldColor); const [color, setColor] = useState(oldColor);
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null); const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
const open = Boolean(anchorEl); const open = Boolean(anchorEl);
const theme = useTheme(); const theme = useTheme();
const handleClick = (event: any) => { const handleClick = (event: React.MouseEvent<HTMLDivElement>) => {
setAnchorEl(event.currentTarget); setAnchorEl(event.currentTarget);
}; };
+2 -1
View File
@@ -1,6 +1,7 @@
import { useAppDispatch, useAppSelector } from "@/app/hooks"; import { useAppDispatch, useAppSelector } from "@/app/hooks";
import SettingsPage from "@/features/Settings/SettingsPage"; import SettingsPage from "@/features/Settings/SettingsPage";
import { getCalendarRange } from "@/utils/dateUtils"; import { getCalendarRange } from "@/utils/dateUtils";
import type { CalendarApi } from "@fullcalendar/core";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { ErrorSnackbar } from "../Error/ErrorSnackbar"; import { ErrorSnackbar } from "../Error/ErrorSnackbar";
import { refreshCalendars } from "../Event/utils/eventUtils"; import { refreshCalendars } from "../Event/utils/eventUtils";
@@ -8,7 +9,7 @@ import { Menubar, MenubarProps } from "../Menubar/Menubar";
import CalendarApp from "./Calendar"; import CalendarApp from "./Calendar";
export default function CalendarLayout() { export default function CalendarLayout() {
const calendarRef = useRef<any>(null); const calendarRef = useRef<CalendarApi | null>(null);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const error = useAppSelector((state) => state.calendars.error); const error = useAppSelector((state) => state.calendars.error);
const selectedCalendars = useAppSelector((state) => state.calendars.list); const selectedCalendars = useAppSelector((state) => state.calendars.list);
+7 -5
View File
@@ -138,28 +138,30 @@ function CalendarPopover({
newCalColor, newCalColor,
newCalVisibility newCalVisibility
); );
importedContent && if (importedContent) {
dispatch( dispatch(
importEventFromFileAsync({ importEventFromFileAsync({
calLink: `/calendar/${userData.openpaasId}/${calId}.json`, calLink: `/calendar/${userData.openpaasId}/${calId}.json`,
file: importedContent, file: importedContent,
}) })
); );
}
} }
} else { } else {
importedContent && if (importedContent) {
dispatch( dispatch(
importEventFromFileAsync({ importEventFromFileAsync({
calLink: calendars[importTarget].link, calLink: calendars[importTarget].link,
file: importedContent, file: importedContent,
}) })
); );
}
} }
handleClose({}, "backdropClick"); handleClose({}, "backdropClick");
}; };
const handleClose = ( const handleClose = (
e: {}, e: object | null,
reason: "backdropClick" | "escapeKeyDown" reason: "backdropClick" | "escapeKeyDown"
): void => { ): void => {
onClose(e, reason); onClose(e, reason);
@@ -182,7 +184,7 @@ function CalendarPopover({
open={open} open={open}
onClose={() => handleClose({}, "backdropClick")} onClose={() => handleClose({}, "backdropClick")}
title={ title={
<Tabs value={tab} onChange={(e, v) => setTab(v)}> <Tabs value={tab} onChange={(_e, v) => setTab(v)}>
<Tab <Tab
value="settings" value="settings"
label={ label={
@@ -203,7 +205,7 @@ function CalendarPopover({
<> <>
<Button <Button
variant="outlined" variant="outlined"
onClick={(e) => handleClose({}, "backdropClick")} onClick={() => handleClose({}, "backdropClick")}
> >
{t("common.cancel")} {t("common.cancel")}
</Button> </Button>
+10 -9
View File
@@ -21,7 +21,7 @@ import { ColorPicker } from "./CalendarColorPicker";
import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils"; import { defaultColors, getAccessiblePair } from "./utils/calendarColorsUtils";
interface CalendarWithOwner { interface CalendarWithOwner {
cal: Record<string, any>; cal: Record<string, unknown>;
owner: User; owner: User;
} }
@@ -117,7 +117,7 @@ function SelectedCalendarsList({
> >
>((acc, cal) => { >((acc, cal) => {
const exists = Object.values(calendars).some( const exists = Object.values(calendars).some(
(existing: any) => (existing: Calendar) =>
existing.id === existing.id ===
cal.cal?._links?.self?.href cal.cal?._links?.self?.href
.replace("/calendars/", "") .replace("/calendars/", "")
@@ -190,13 +190,14 @@ function SelectedCalendarsList({
} }
export default function CalendarSearch({ export default function CalendarSearch({
anchorEl,
open, open,
onClose, onClose,
}: { }: {
anchorEl: HTMLElement | null;
open: boolean; open: boolean;
onClose: Function; onClose: (
result?: string[] | Record<string, never>,
reason?: "backdropClick" | "escapeKeyDown"
) => void;
}) { }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const theme = useTheme(); const theme = useTheme();
@@ -214,7 +215,7 @@ export default function CalendarSearch({
selectedCal.map(async (cal) => { selectedCal.map(async (cal) => {
const calId = crypto.randomUUID(); const calId = crypto.randomUUID();
const exists = Object.values(calendars).some( const exists = Object.values(calendars).some(
(existing: any) => (existing: Calendar) =>
existing.id === existing.id ===
cal.cal?._links?.self?.href cal.cal?._links?.self?.href
.replace("/calendars/", "") .replace("/calendars/", "")
@@ -279,7 +280,7 @@ export default function CalendarSearch({
objectTypes={["user"]} objectTypes={["user"]}
selectedUsers={selectedUsers} selectedUsers={selectedUsers}
inputSlot={(params) => <TextField {...params} size="small" />} inputSlot={(params) => <TextField {...params} size="small" />}
onChange={async (event: any, value: User[]) => { onChange={async (_event: React.SyntheticEvent, value: User[]) => {
setSelectedUsers(value); setSelectedUsers(value);
const cals = await Promise.all( const cals = await Promise.all(
@@ -288,10 +289,10 @@ export default function CalendarSearch({
const cals = (await getCalendars( const cals = (await getCalendars(
user.openpaasId, user.openpaasId,
"sharedPublic=true&" "sharedPublic=true&"
)) as Record<string, any>; )) as Record<string, unknown>;
return cals._embedded?.["dav:calendar"] return cals._embedded?.["dav:calendar"]
? cals._embedded["dav:calendar"].map( ? cals._embedded["dav:calendar"].map(
(cal: Record<string, any>) => ({ cal, owner: user }) (cal: Record<string, unknown>) => ({ cal, owner: user })
) )
: { cal: undefined, owner: user }; : { cal: undefined, owner: user };
} }
+11 -9
View File
@@ -13,12 +13,11 @@ import {
ListItem, ListItem,
Menu, Menu,
MenuItem, MenuItem,
Typography,
} from "@linagora/twake-mui"; } from "@linagora/twake-mui";
import AddIcon from "@mui/icons-material/Add"; import AddIcon from "@mui/icons-material/Add";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import MoreHorizIcon from "@mui/icons-material/MoreHoriz"; import MoreHorizIcon from "@mui/icons-material/MoreHoriz";
import { useEffect, useMemo, useState } from "react"; import { SetStateAction, useEffect, useMemo, useState } from "react";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import CalendarPopover from "./CalendarModal"; import CalendarPopover from "./CalendarModal";
import CalendarSearch from "./CalendarSearch"; import CalendarSearch from "./CalendarSearch";
@@ -39,9 +38,9 @@ function CalendarAccordion({
selectedCalendars: string[]; selectedCalendars: string[];
handleToggle: (id: string) => void; handleToggle: (id: string) => void;
showAddButton?: boolean; showAddButton?: boolean;
onAddClick?: Function; onAddClick?: () => void;
defaultExpanded?: boolean; defaultExpanded?: boolean;
setOpen: Function; setOpen: (id: string) => void;
}) { }) {
const allCalendars = useAppSelector((state) => state.calendars.list); const allCalendars = useAppSelector((state) => state.calendars.list);
const { t } = useI18n(); const { t } = useI18n();
@@ -86,8 +85,12 @@ function CalendarAccordion({
<IconButton <IconButton
component="span" component="span"
onClick={(e) => { onClick={(e) => {
expended && e.stopPropagation(); if (expended) {
onAddClick && onAddClick(); e.stopPropagation();
}
if (onAddClick) {
onAddClick();
}
}} }}
> >
<AddIcon /> <AddIcon />
@@ -116,7 +119,7 @@ export default function CalendarSelection({
setSelectedCalendars, setSelectedCalendars,
}: { }: {
selectedCalendars: string[]; selectedCalendars: string[];
setSelectedCalendars: Function; setSelectedCalendars: (value: SetStateAction<string[]>) => void;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
const userId = const userId =
@@ -202,7 +205,6 @@ export default function CalendarSelection({
}} }}
/> />
<CalendarSearch <CalendarSearch
anchorEl={anchorElCalOthers}
open={Boolean(anchorElCalOthers)} open={Boolean(anchorElCalOthers)}
onClose={(newCalIds?: string[]) => { onClose={(newCalIds?: string[]) => {
setAnchorElCalOthers(null); setAnchorElCalOthers(null);
@@ -228,7 +230,7 @@ function CalendarSelector({
isPersonal: boolean; isPersonal: boolean;
selectedCalendars: string[]; selectedCalendars: string[];
handleCalendarToggle: (name: string) => void; handleCalendarToggle: (name: string) => void;
setOpen: Function; setOpen: () => void;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
+7 -7
View File
@@ -24,21 +24,21 @@ export function ImportTab({
}: { }: {
userId: string; userId: string;
importTarget: string; importTarget: string;
setImportTarget: Function; setImportTarget: (target: string) => void;
setImportedContent: Function; setImportedContent: (content: File | null) => void;
newCalParams: { newCalParams: {
name: string; name: string;
setName: Function; setName: (name: string) => void;
description: string; description: string;
setDescription: (d: string) => void; setDescription: (d: string) => void;
color: Record<string, string>; color: Record<string, string>;
setColor: Function; setColor: (color: Record<string, string>) => void;
visibility: "public" | "private"; visibility: "public" | "private";
setVisibility: Function; setVisibility: (visibility: "public" | "private") => void;
}; };
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
const [importMode, setImportMode] = useState<"file" | "url">("file"); const [importMode] = useState<"file" | "url">("file");
const [importFile, setImportFile] = useState<File | null>(null); const [importFile, setImportFile] = useState<File | null>(null);
const [importUrl, setImportUrl] = useState(""); const [importUrl, setImportUrl] = useState("");
const calendars = useAppSelector((state) => state.calendars.list); const calendars = useAppSelector((state) => state.calendars.list);
@@ -48,7 +48,7 @@ export function ImportTab({
useEffect(() => { useEffect(() => {
setImportedContent(importMode === "file" ? importFile : null); setImportedContent(importMode === "file" ? importFile : null);
}, [importFile, importUrl, importMode]); }, [importFile, importUrl, importMode, setImportedContent]);
return ( return (
<> <>
+5 -4
View File
@@ -13,19 +13,20 @@ import moment from "moment";
import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { CalendarEvent } from "@/features/Events/EventsTypes";
import type { CalendarApi } from "@fullcalendar/core";
export function MiniCalendar({ export function MiniCalendar({
calendarRef, calendarRef,
selectedDate, selectedDate,
setSelectedDate,
setSelectedMiniDate, setSelectedMiniDate,
dottedEvents, dottedEvents,
}: { }: {
calendarRef: any; calendarRef: React.MutableRefObject<CalendarApi | null>;
selectedDate: Date; selectedDate: Date;
setSelectedDate: (d: Date) => void;
setSelectedMiniDate: (d: Date) => void; setSelectedMiniDate: (d: Date) => void;
dottedEvents: any[]; dottedEvents: CalendarEvent[];
}) { }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const calendars = useAppSelector((state) => state.calendars); const calendars = useAppSelector((state) => state.calendars);
+3 -3
View File
@@ -27,13 +27,13 @@ export function SettingsTab({
calendar, calendar,
}: { }: {
name: string; name: string;
setName: Function; setName: (name: string) => void;
description: string; description: string;
setDescription: (d: string) => void; setDescription: (d: string) => void;
color: Record<string, string>; color: Record<string, string>;
setColor: Function; setColor: (color: Record<string, string>) => void;
visibility: "public" | "private"; visibility: "public" | "private";
setVisibility: Function; setVisibility: (visibility: "public" | "private") => void;
calendar?: Calendar; calendar?: Calendar;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
@@ -22,7 +22,7 @@ export function TempCalendarsInput({
setTempUsers: (users: User[]) => void; setTempUsers: (users: User[]) => void;
handleToggleEventPreview: () => void; handleToggleEventPreview: () => void;
selectedCalendars: string[]; selectedCalendars: string[];
setSelectedCalendars: Function; setSelectedCalendars: React.Dispatch<React.SetStateAction<string[]>>;
}) { }) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const tempcalendars = const tempcalendars =
@@ -34,7 +34,7 @@ export function TempCalendarsInput({
const prevUsersRef = useRef<User[]>([]); const prevUsersRef = useRef<User[]>([]);
const userColorsRef = useRef(new Map<string, string>()); const userColorsRef = useRef(new Map<string, string>());
const handleUserChange = async (_: any, users: User[]) => { const handleUserChange = async (_: React.SyntheticEvent, users: User[]) => {
setTempUsers(users); setTempUsers(users);
const prevUsers = prevUsersRef.current; const prevUsers = prevUsersRef.current;
@@ -13,7 +13,12 @@ import { CalendarEvent } from "@/features/Events/EventsTypes";
import { userAttendee } from "@/features/User/models/attendee"; import { userAttendee } from "@/features/User/models/attendee";
import { createAttendee } from "@/features/User/models/attendee.mapper"; import { createAttendee } from "@/features/User/models/attendee.mapper";
import { getDeltaInMilliseconds } from "@/utils/dateUtils"; import { getDeltaInMilliseconds } from "@/utils/dateUtils";
import { CalendarApi, DateSelectArg, EventDropArg } from "@fullcalendar/core"; import {
CalendarApi,
DateSelectArg,
EventClickArg,
EventDropArg,
} from "@fullcalendar/core";
import { EventResizeDoneArg } from "@fullcalendar/interaction"; import { EventResizeDoneArg } from "@fullcalendar/interaction";
export interface EventHandlersProps { export interface EventHandlersProps {
@@ -27,7 +32,9 @@ export interface EventHandlersProps {
setEventDisplayedTemp: (temp: boolean) => void; setEventDisplayedTemp: (temp: boolean) => void;
calendars: Record<string, Calendar>; calendars: Record<string, Calendar>;
setSelectedEvent: (event: CalendarEvent) => void; setSelectedEvent: (event: CalendarEvent) => void;
setAfterChoiceFunc: (func: Function) => void; setAfterChoiceFunc: (
func: ((type: "solo" | "all" | undefined) => void) | undefined
) => void;
setOpenEditModePopup: (open: string) => void; setOpenEditModePopup: (open: string) => void;
tempUsers: User[]; tempUsers: User[];
setTempEvent: (event: CalendarEvent) => void; setTempEvent: (event: CalendarEvent) => void;
@@ -87,7 +94,7 @@ export const createEventHandlers = (props: EventHandlersProps) => {
setOpenEventDisplay(false); setOpenEventDisplay(false);
}; };
const handleEventClick = (info: any) => { const handleEventClick = (info: EventClickArg) => {
info.jsEvent.preventDefault(); info.jsEvent.preventDefault();
if (info.event.url) { if (info.event.url) {
@@ -1,5 +1,9 @@
interface CalendarHTMLElement extends HTMLElement {
__calendarMouseMoveHandler?: (e: MouseEvent) => void;
__calendarMouseLeaveHandler?: () => void;
}
export interface MouseHandlersProps { export interface MouseHandlersProps {
calendarEl: HTMLElement; calendarEl: CalendarHTMLElement;
} }
export const createMouseHandlers = (props: MouseHandlersProps) => { export const createMouseHandlers = (props: MouseHandlersProps) => {
@@ -75,24 +79,24 @@ export const createMouseHandlers = (props: MouseHandlersProps) => {
calendarEl.addEventListener("mousemove", handleMouseMove); calendarEl.addEventListener("mousemove", handleMouseMove);
calendarEl.addEventListener("mouseleave", handleMouseLeave); calendarEl.addEventListener("mouseleave", handleMouseLeave);
(calendarEl as any).__calendarMouseMoveHandler = handleMouseMove; calendarEl.__calendarMouseMoveHandler = handleMouseMove;
(calendarEl as any).__calendarMouseLeaveHandler = handleMouseLeave; calendarEl.__calendarMouseLeaveHandler = handleMouseLeave;
}; };
const removeMouseEventListeners = () => { const removeMouseEventListeners = () => {
if ((calendarEl as any).__calendarMouseMoveHandler) { if (calendarEl.__calendarMouseMoveHandler) {
calendarEl.removeEventListener( calendarEl.removeEventListener(
"mousemove", "mousemove",
(calendarEl as any).__calendarMouseMoveHandler calendarEl.__calendarMouseMoveHandler
); );
delete (calendarEl as any).__calendarMouseMoveHandler; delete calendarEl.__calendarMouseMoveHandler;
} }
if ((calendarEl as any).__calendarMouseLeaveHandler) { if (calendarEl.__calendarMouseLeaveHandler) {
calendarEl.removeEventListener( calendarEl.removeEventListener(
"mouseleave", "mouseleave",
(calendarEl as any).__calendarMouseLeaveHandler calendarEl.__calendarMouseLeaveHandler
); );
delete (calendarEl as any).__calendarMouseLeaveHandler; delete calendarEl.__calendarMouseLeaveHandler;
} }
}; };
@@ -2,7 +2,14 @@ import { EventErrorHandler } from "@/components/Error/EventErrorHandler";
import { EventChip } from "@/components/Event/EventChip/EventChip"; import { EventChip } from "@/components/Event/EventChip/EventChip";
import { Calendar } from "@/features/Calendars/CalendarTypes"; import { Calendar } from "@/features/Calendars/CalendarTypes";
import { userAttendee } from "@/features/User/models/attendee"; import { userAttendee } from "@/features/User/models/attendee";
import { CalendarApi, NowIndicatorContentArg } from "@fullcalendar/core"; import {
CalendarApi,
DayHeaderMountArg,
EventContentArg,
EventMountArg,
NowIndicatorContentArg,
ViewMountArg,
} from "@fullcalendar/core";
import React from "react"; import React from "react";
import { createMouseHandlers } from "./mouseHandlers"; import { createMouseHandlers } from "./mouseHandlers";
@@ -46,7 +53,7 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
} }
}; };
const handleDayHeaderDidMount = (arg: any) => { const handleDayHeaderDidMount = (arg: DayHeaderMountArg) => {
if (arg.view.type === "timeGridWeek") { if (arg.view.type === "timeGridWeek") {
const headerEl = arg.el; const headerEl = arg.el;
@@ -61,22 +68,19 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
}; };
headerEl.addEventListener("click", handleDayHeaderClick); headerEl.addEventListener("click", handleDayHeaderClick);
(headerEl as any).__dayHeaderClickHandler = handleDayHeaderClick; headerEl.__dayHeaderClickHandler = handleDayHeaderClick;
} }
}; };
const handleDayHeaderWillUnmount = (arg: any) => { const handleDayHeaderWillUnmount = (arg: DayHeaderMountArg) => {
const headerEl = arg.el; const headerEl = arg.el;
if ((headerEl as any).__dayHeaderClickHandler) { if (headerEl.__dayHeaderClickHandler) {
headerEl.removeEventListener( headerEl.removeEventListener("click", headerEl.__dayHeaderClickHandler);
"click", delete headerEl.__dayHeaderClickHandler;
(headerEl as any).__dayHeaderClickHandler
);
delete (headerEl as any).__dayHeaderClickHandler;
} }
}; };
const handleViewDidMount = (arg: any) => { const handleViewDidMount = (arg: ViewMountArg) => {
if (arg.view.type === "timeGridWeek" || arg.view.type === "timeGridDay") { if (arg.view.type === "timeGridWeek" || arg.view.type === "timeGridDay") {
const calendarEl = document.querySelector(".fc") as HTMLElement; const calendarEl = document.querySelector(".fc") as HTMLElement;
if (calendarEl) { if (calendarEl) {
@@ -86,15 +90,15 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
} }
}; };
const handleViewWillUnmount = (arg: any) => { const handleViewWillUnmount = (arg: ViewMountArg) => {
if ((arg.el as any).__timeInterval) { if (arg.el.__timeInterval) {
clearInterval((arg.el as any).__timeInterval); clearInterval(arg.el.__timeInterval);
delete (arg.el as any).__timeInterval; delete arg.el.__timeInterval;
} }
if ((arg.el as any).__timeObserver) { if (arg.el.__timeObserver) {
(arg.el as any).__timeObserver.disconnect(); arg.el.__timeObserver.disconnect();
delete (arg.el as any).__timeObserver; delete arg.el.__timeObserver;
} }
const calendarEl = document.querySelector(".fc") as HTMLElement; const calendarEl = document.querySelector(".fc") as HTMLElement;
@@ -104,7 +108,7 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
} }
}; };
const handleEventContent = (arg: any) => { const handleEventContent = (arg: EventContentArg) => {
return React.createElement(EventChip, { return React.createElement(EventChip, {
arg, arg,
calendars, calendars,
@@ -113,7 +117,7 @@ export const createViewHandlers = (props: ViewHandlersProps) => {
}); });
}; };
const handleEventDidMount = (arg: any) => { const handleEventDidMount = (arg: EventMountArg) => {
const attendees = arg.event._def.extendedProps.attendee || []; const attendees = arg.event._def.extendedProps.attendee || [];
if (!calendars[arg.event._def.extendedProps.calId]) return; if (!calendars[arg.event._def.extendedProps.calId]) return;
const ownerEmails = new Set( const ownerEmails = new Set(
@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useCallback } from "react"; import { useCallback } from "react";
import { import {
createEventHandlers, createEventHandlers,
@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useCallback } from "react"; import { useCallback } from "react";
import { import {
createViewHandlers, createViewHandlers,
+14 -12
View File
@@ -1,12 +1,12 @@
import { refreshSingularCalendar } from "@/components/Event/utils/eventUtils"; import { AppDispatch } from "@/app/store";
import { Calendar } from "@/features/Calendars/CalendarTypes"; import { Calendar } from "@/features/Calendars/CalendarTypes";
import { getCalendarDetailAsync } from "@/features/Calendars/services"; import { getCalendarDetailAsync } from "@/features/Calendars/services";
import { AclEntry } from "@/features/Calendars/types/CalendarData";
import { CalendarEvent } from "@/features/Events/EventsTypes"; import { CalendarEvent } from "@/features/Events/EventsTypes";
import { formatDateToYYYYMMDDTHHMMSS } from "@/utils/dateUtils"; import { formatDateToYYYYMMDDTHHMMSS } from "@/utils/dateUtils";
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid"; import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
import { convertEventDateTimeToISO } from "@/utils/timezone"; import { convertEventDateTimeToISO } from "@/utils/timezone";
import { SlotLabelContentArg } from "@fullcalendar/core"; import { SlotLabelContentArg } from "@fullcalendar/core";
import { ThunkDispatch } from "@reduxjs/toolkit";
import moment from "moment-timezone"; import moment from "moment-timezone";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
@@ -56,7 +56,10 @@ export const checkIfCurrentWeekOrDay = (): boolean => {
return !!nowIndicator; return !!nowIndicator;
}; };
export function formatEventChipTitle(e: CalendarEvent, t: Function) { export function formatEventChipTitle(
e: CalendarEvent,
t: (key: string) => string
) {
if (!e.title) { if (!e.title) {
return t("event.untitled"); return t("event.untitled");
} }
@@ -72,6 +75,7 @@ export const eventToFullCalendarFormat = (
userAddress: string | undefined, userAddress: string | undefined,
pending: boolean pending: boolean
) => { ) => {
// eslint-disable-next-line react-hooks/rules-of-hooks
const { t } = useI18n(); const { t } = useI18n();
return filteredEvents return filteredEvents
.concat(filteredTempEvents.map((e) => ({ ...e, temp: true }))) .concat(filteredTempEvents.map((e) => ({ ...e, temp: true })))
@@ -83,7 +87,11 @@ export const eventToFullCalendarFormat = (
: true; // if there are no organizer in the event we assume it was organized by the owner : true; // if there are no organizer in the event we assume it was organized by the owner
const isPersonnalEvent = const isPersonnalEvent =
extractEventBaseUuid(e.calId) === userId && isOrganiser; extractEventBaseUuid(e.calId) === userId && isOrganiser;
const convertedEvent: any = { const convertedEvent: CalendarEvent & {
colors: Record<string, string> | undefined;
editable: boolean;
priority: number;
} = {
...e, ...e,
title: formatEventChipTitle(e, t), title: formatEventChipTitle(e, t),
colors: e.color, colors: e.color,
@@ -147,7 +155,7 @@ export const updateCalsDetails = (
pending: boolean, pending: boolean,
rangeKey: string, rangeKey: string,
previousRangeKey: string, previousRangeKey: string,
dispatch: Function, dispatch: AppDispatch,
calendarRange: { start: Date; end: Date }, calendarRange: { start: Date; end: Date },
calType?: "temp", calType?: "temp",
controllers?: Map<string, AbortController> controllers?: Map<string, AbortController>
@@ -223,15 +231,9 @@ export const updateCalsDetails = (
} }
}; };
interface AclEntry {
privilege: string;
principal: string;
protected: boolean;
}
export function getCalendarVisibility(acl: AclEntry[]): "private" | "public" { export function getCalendarVisibility(acl: AclEntry[]): "private" | "public" {
let hasRead = false; let hasRead = false;
let hasFreeBusy = false; // const hasFreeBusy = false;
if (acl) { if (acl) {
for (const entry of acl) { for (const entry of acl) {
if (entry.principal !== "{DAV:}authenticated") continue; if (entry.principal !== "{DAV:}authenticated") continue;
+1 -1
View File
@@ -158,7 +158,7 @@ function ResponsiveDialog({
}, [isExpanded]); }, [isExpanded]);
const handleClose = ( const handleClose = (
event: {}, event: unknown,
reason: "backdropClick" | "escapeKeyDown" reason: "backdropClick" | "escapeKeyDown"
) => { ) => {
if (isExpanded && reason === "backdropClick") { if (isExpanded && reason === "backdropClick") {
-3
View File
@@ -1,4 +1,3 @@
import { CalendarEvent } from "@/features/Events/EventsTypes";
import { import {
Button, Button,
ButtonGroup, ButtonGroup,
@@ -16,12 +15,10 @@ import { useI18n } from "twake-i18n";
export function EditModeDialog({ export function EditModeDialog({
type, type,
setOpen, setOpen,
event,
eventAction, eventAction,
}: { }: {
type: string | null; type: string | null;
setOpen: (e: string | null) => void; setOpen: (e: string | null) => void;
event: CalendarEvent;
eventAction: (type: "solo" | "all" | undefined) => void; eventAction: (type: "solo" | "all" | undefined) => void;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
@@ -1,4 +1,5 @@
import { EventErrorHandler } from "@/components/Error/EventErrorHandler"; import { EventErrorHandler } from "@/components/Error/EventErrorHandler";
import { EventContentArg } from "@fullcalendar/core";
import { Card, Typography } from "@linagora/twake-mui"; import { Card, Typography } from "@linagora/twake-mui";
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
@@ -7,18 +8,30 @@ export function ErrorEventChip({
errorHandler, errorHandler,
error, error,
}: { }: {
event: any; event: EventContentArg["event"];
errorHandler: EventErrorHandler; errorHandler: EventErrorHandler;
error: any; error: unknown;
}) { }) {
const hasReported = useRef(false); const hasReported = useRef(false);
useEffect(() => { useEffect(() => {
if (!hasReported.current) { if (!hasReported.current) {
const message = let message: string;
error instanceof Error
? error.message if (error instanceof Error) {
: `${error.class} error during rendering ${event._def.extendedProps.uid || event.id}`; message = error.message;
} else if (
typeof error === "object" &&
error !== null &&
"class" in error
) {
message = `${String(
(error as { class: string }).class
)} error during rendering ${event._def.extendedProps.uid || event.id}`;
} else {
message = `Unknown error during rendering ${event._def.extendedProps.uid || event.id}`;
}
errorHandler.reportError( errorHandler.reportError(
event._def.extendedProps.uid || event.id, event._def.extendedProps.uid || event.id,
message message
+2 -2
View File
@@ -59,7 +59,7 @@ export function EventChip({
const ownerEmails = new Set( const ownerEmails = new Set(
calendar.ownerEmails?.map((e) => e.toLowerCase()) calendar.ownerEmails?.map((e) => e.toLowerCase())
); );
const delegated = calendar.delegated; // const delegated = calendar.delegated;
// Determine owner attendee // Determine owner attendee
const ownerAttendee = getOwnerAttendee(attendees, ownerEmails); const ownerAttendee = getOwnerAttendee(attendees, ownerEmails);
@@ -258,7 +258,7 @@ export function EventChip({
!event._def.extendedProps.allday && !event._def.extendedProps.allday &&
event._def.extendedProps.organizer && event._def.extendedProps.organizer &&
!showCompact && !showCompact &&
(window as any).displayOrgAvatar && ( window.displayOrgAvatar && (
<Avatar <Avatar
children={OrganizerAvatar.children} children={OrganizerAvatar.children}
color={OrganizerAvatar.color} color={OrganizerAvatar.color}
@@ -1,6 +1,7 @@
import { EventErrorHandler } from "@/components/Error/EventErrorHandler"; import { EventErrorHandler } from "@/components/Error/EventErrorHandler";
import { Calendar } from "@/features/Calendars/CalendarTypes"; import { Calendar } from "@/features/Calendars/CalendarTypes";
import { userAttendee } from "@/features/User/models/attendee"; import { userAttendee } from "@/features/User/models/attendee";
import { EventContentArg } from "@fullcalendar/core";
import { getContrastRatio } from "@linagora/twake-mui"; import { getContrastRatio } from "@linagora/twake-mui";
import CancelIcon from "@mui/icons-material/Cancel"; import CancelIcon from "@mui/icons-material/Cancel";
import HelpOutlineIcon from "@mui/icons-material/HelpOutline"; import HelpOutlineIcon from "@mui/icons-material/HelpOutline";
@@ -12,7 +13,7 @@ import { EVENT_DURATION } from "./EventChip";
const COMPACT_WIDTH_THRESHOLD = 100; const COMPACT_WIDTH_THRESHOLD = 100;
export interface EventChipProps { export interface EventChipProps {
arg: any; arg: EventContentArg["event"];
calendars: Record<string, Calendar>; calendars: Record<string, Calendar>;
tempcalendars: Record<string, Calendar>; tempcalendars: Record<string, Calendar>;
errorHandler: EventErrorHandler; errorHandler: EventErrorHandler;
@@ -23,9 +24,9 @@ export interface IconDisplayConfig {
needAction: boolean; needAction: boolean;
private: boolean; private: boolean;
} }
export function getEventDuration(event: any): number { export function getEventDuration(event: EventContentArg["event"]): number {
return moment(event._instance.range.end).diff( return moment(event._instance?.range.end).diff(
moment(event._instance.range.start), moment(event._instance?.range.start),
"minutes" "minutes"
); );
} }
@@ -34,7 +35,10 @@ export function getBestColor(colors: { light: string; dark: string }): string {
const contrastToLight = getContrastRatio(colors?.light, "#fff"); const contrastToLight = getContrastRatio(colors?.light, "#fff");
return contrastToDark > contrastToLight ? colors?.dark : colors?.light; return contrastToDark > contrastToLight ? colors?.dark : colors?.light;
} }
export function getEventTimes(event: any, timeZone: string) { export function getEventTimes(
event: EventContentArg["event"],
timeZone: string
) {
return { return {
startTime: moment.tz(event.start, timeZone).format("HH:mm"), startTime: moment.tz(event.start, timeZone).format("HH:mm"),
endTime: moment.tz(event.end, timeZone).format("HH:mm"), endTime: moment.tz(event.end, timeZone).format("HH:mm"),
@@ -52,7 +56,7 @@ export function getOwnerAttendee(
export function getTitleStyle( export function getTitleStyle(
bestColor: string, bestColor: string,
partstat?: string, partstat?: string,
calendar?: any calendar?: Calendar
): React.CSSProperties { ): React.CSSProperties {
const baseStyle: React.CSSProperties = { const baseStyle: React.CSSProperties = {
fontFamily: "Roboto", fontFamily: "Roboto",
@@ -122,7 +126,7 @@ export function getCardStyle(
bestColor: string, bestColor: string,
eventLength: number, eventLength: number,
partstat?: string, partstat?: string,
calendar?: any calendar?: Calendar
): React.CSSProperties { ): React.CSSProperties {
const baseStyle: React.CSSProperties = getCardVariantStyle( const baseStyle: React.CSSProperties = getCardVariantStyle(
getEventVariant(eventLength), getEventVariant(eventLength),
-5
View File
@@ -1,14 +1,9 @@
import { CalendarEvent } from "@/features/Events/EventsTypes";
import { MenuItem } from "@linagora/twake-mui"; import { MenuItem } from "@linagora/twake-mui";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
export default function EventDuplication({ export default function EventDuplication({
onClose,
event,
onOpenDuplicate, onOpenDuplicate,
}: { }: {
onClose: Function;
event: CalendarEvent;
onOpenDuplicate?: () => void; onOpenDuplicate?: () => void;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
+2 -6
View File
@@ -21,17 +21,15 @@ import {
ToggleButton, ToggleButton,
ToggleButtonGroup, ToggleButtonGroup,
Typography, Typography,
useTheme,
} from "@linagora/twake-mui"; } from "@linagora/twake-mui";
import { alpha } from "@mui/material/styles";
import { import {
Close as DeleteIcon, Close as DeleteIcon,
ContentCopy as CopyIcon, ContentCopy as CopyIcon,
Public as PublicIcon,
LocationOn as LocationIcon, LocationOn as LocationIcon,
Public as PublicIcon,
} from "@mui/icons-material"; } from "@mui/icons-material";
import SquareRoundedIcon from "@mui/icons-material/SquareRounded";
import LockOutlineIcon from "@mui/icons-material/LockOutline"; import LockOutlineIcon from "@mui/icons-material/LockOutline";
import SquareRoundedIcon from "@mui/icons-material/SquareRounded";
import React from "react"; import React from "react";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import AttendeeSelector from "../Attendees/AttendeeSearch"; import AttendeeSelector from "../Attendees/AttendeeSearch";
@@ -163,7 +161,6 @@ export default function EventFormFields({
onHasEndDateChangedChange, onHasEndDateChangedChange,
}: EventFormFieldsProps) { }: EventFormFieldsProps) {
const { t } = useI18n(); const { t } = useI18n();
const theme = useTheme();
// Internal state for 4 separate fields // Internal state for 4 separate fields
const [startDate, setStartDate] = React.useState(""); const [startDate, setStartDate] = React.useState("");
@@ -377,7 +374,6 @@ export default function EventFormFields({
showMore, showMore,
}); });
}, [ }, [
title,
startDate, startDate,
startTime, startTime,
endDate, endDate,
+1 -1
View File
@@ -25,7 +25,7 @@ export default function RepeatEvent({
}: { }: {
repetition: RepetitionObject; repetition: RepetitionObject;
eventStart: Date; eventStart: Date;
setRepetition: Function; setRepetition: (repetition: RepetitionObject) => void;
isOwn?: boolean; isOwn?: boolean;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
@@ -83,7 +83,6 @@ export const DateTimeFields: React.FC<DateTimeFieldsProps> = ({
showMore, showMore,
hasEndDateChanged, hasEndDateChanged,
showEndDate, showEndDate,
onToggleEndDate,
validation, validation,
onStartDateChange, onStartDateChange,
onStartTimeChange, onStartTimeChange,
@@ -88,7 +88,7 @@ export const DateTimeSummary: React.FC<DateTimeSummaryProps> = ({
const offset = getTimezoneOffset(tz, dateForOffset); const offset = getTimezoneOffset(tz, dateForOffset);
const tzName = tz.replace(/_/g, " "); const tzName = tz.replace(/_/g, " ");
return `(${offset}) ${tzName}`; return `(${offset}) ${tzName}`;
} catch (error) { } catch {
return tz.replace(/_/g, " "); return tz.replace(/_/g, " ");
} }
}; };
@@ -19,12 +19,12 @@ import { parseTimeInput } from "../utils/dateTimeHelpers";
type FieldType = "date" | "time" | "date-time"; type FieldType = "date" | "time" | "date-time";
type GenericPickerFieldProps = PickerFieldProps<any, any, any> & { type GenericPickerFieldProps = PickerFieldProps<Dayjs, false, false> & {
fieldType: FieldType; fieldType: FieldType;
validator: ( validator: (
value: any, value: Dayjs | null,
context: PickerValidationScope, context: PickerValidationScope,
adapter: PickerFieldAdapter<any> adapter: PickerFieldAdapter<Dayjs>
) => string | null; ) => string | null;
}; };
@@ -223,7 +223,7 @@ function EditableTimePickerField(props: GenericPickerFieldProps) {
dispatchCloseOtherPickers(); dispatchCloseOtherPickers();
}; };
const handleBlur = (e: React.FocusEvent<HTMLInputElement>) => { const handleBlur = (_e: React.FocusEvent<HTMLInputElement>) => {
// If dropdown is open, don't parse input // If dropdown is open, don't parse input
// MUI will handle selection and sync value via useEffect // MUI will handle selection and sync value via useEffect
if (pickerContext.open) { if (pickerContext.open) {
@@ -12,15 +12,16 @@ import {
useValidation, useValidation,
validateDate, validateDate,
} from "@mui/x-date-pickers/validation"; } from "@mui/x-date-pickers/validation";
import { Dayjs } from "dayjs";
type FieldType = "date" | "time" | "date-time"; type FieldType = "date" | "time" | "date-time";
type GenericPickerFieldProps = PickerFieldProps<any, any, any> & { type GenericPickerFieldProps = PickerFieldProps<Dayjs, false, false> & {
fieldType: FieldType; fieldType: FieldType;
validator: ( validator: (
value: any, value: Dayjs | null,
context: PickerValidationScope, context: PickerValidationScope,
adapter: PickerFieldAdapter<any> adapter: PickerFieldAdapter<Dayjs>
) => string | null; ) => string | null;
}; };
@@ -119,28 +119,18 @@ export async function handleRSVP(
export function handleDelete( export function handleDelete(
isRecurring: boolean, isRecurring: boolean,
typeOfAction: "solo" | "all" | undefined, typeOfAction: "solo" | "all" | undefined,
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void, onClose: (event: unknown, reason: "backdropClick" | "escapeKeyDown") => void,
dispatch: Function, dispatch: AppDispatch,
calendar: Calendar, calendar: Calendar,
event: CalendarEvent, event: CalendarEvent,
calId: string, calId: string,
eventId: string eventId: string
) { ) {
if (isRecurring) { onClose({}, "backdropClick");
onClose({}, "backdropClick");
if (typeOfAction === "solo") { if (isRecurring && typeOfAction === "solo") {
dispatch(deleteEventInstanceAsync({ cal: calendar, event })); dispatch(deleteEventInstanceAsync({ cal: calendar, event }));
} else {
dispatch(
deleteEventAsync({
calId,
eventId,
eventURL: event.URL,
})
);
}
} else { } else {
onClose({}, "backdropClick");
dispatch( dispatch(
deleteEventAsync({ deleteEventAsync({
calId, calId,
+4 -2
View File
@@ -16,7 +16,7 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle";
export function renderAttendeeBadge( export function renderAttendeeBadge(
a: userAttendee, a: userAttendee,
key: string, key: string,
t: Function, t: (key: string) => string,
isFull?: boolean, isFull?: boolean,
isOrganizer?: boolean isOrganizer?: boolean
) { ) {
@@ -110,7 +110,9 @@ export async function refreshCalendars(
) { ) {
if (process.env.NODE_ENV === "test") return; if (process.env.NODE_ENV === "test") return;
!calType && (await dispatch(getCalendarsListAsync())); if (!calType) {
await dispatch(getCalendarsListAsync());
}
const results = await Promise.allSettled( const results = await Promise.allSettled(
calendars.map((calendar) => calendars.map((calendar) =>
+1 -1
View File
@@ -8,7 +8,7 @@ export function SnackbarAlert({
severity = "success", severity = "success",
}: { }: {
open: boolean; open: boolean;
setOpen: Function; setOpen: (o: boolean) => void;
message: string; message: string;
severity?: AlertColor; severity?: AlertColor;
}) { }) {
+6 -4
View File
@@ -142,7 +142,7 @@ export default function SearchBar() {
setFilterError(false); setFilterError(false);
}; };
const handleContactSelect = (_event: any, contacts: User[]) => { const handleContactSelect = (contacts: User[]) => {
setSelectedContacts(contacts); setSelectedContacts(contacts);
setSearch(""); setSearch("");
if (contacts.length > 0) { if (contacts.length > 0) {
@@ -173,7 +173,9 @@ export default function SearchBar() {
dispatch(setView("search")); dispatch(setView("search"));
setAnchorEl(null); setAnchorEl(null);
} else { } else {
filterOpen && setFilterError(true); if (filterOpen) {
setFilterError(true);
}
} }
}; };
@@ -222,8 +224,8 @@ export default function SearchBar() {
{extended && ( {extended && (
<PeopleSearch <PeopleSearch
selectedUsers={selectedContacts} selectedUsers={selectedContacts}
onChange={(event, users) => { onChange={(_event, users) => {
handleContactSelect(event, users); handleContactSelect(users);
}} }}
objectTypes={["user", "contact"]} objectTypes={["user", "contact"]}
onToggleEventPreview={() => {}} onToggleEventPreview={() => {}}
+2 -2
View File
@@ -56,9 +56,9 @@ export function Menubar({
currentView, currentView,
onViewChange, onViewChange,
}: MenubarProps) { }: MenubarProps) {
const { t, lang } = useI18n(); // deliberately NOT using f() const { t } = useI18n(); // deliberately NOT using f()
const user = useAppSelector((state) => state.user.userData); const user = useAppSelector((state) => state.user.userData);
const applist: AppIconProps[] = (window as any).appList ?? []; const applist: AppIconProps[] = window.appList ?? [];
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [userMenuAnchorEl, setUserMenuAnchorEl] = useState<null | HTMLElement>( const [userMenuAnchorEl, setUserMenuAnchorEl] = useState<null | HTMLElement>(
null null
+5 -4
View File
@@ -1,10 +1,11 @@
import { api } from "@/utils/apiUtils"; import { api } from "@/utils/apiUtils";
import { CalendarInput, CalendarList } from "./types/CalendarData";
export async function getCalendars( export async function getCalendars(
userId: string, userId: string,
scope: string = "personal=true&sharedDelegationStatus=accepted&sharedPublicSubscription=true&", scope: string = "personal=true&sharedDelegationStatus=accepted&sharedPublicSubscription=true&",
signal?: AbortSignal signal?: AbortSignal
) { ): Promise<CalendarList> {
const calendars = await api const calendars = await api
.get(`dav/calendars/${userId}.json?${scope}`, { .get(`dav/calendars/${userId}.json?${scope}`, {
headers: { headers: {
@@ -13,7 +14,7 @@ export async function getCalendars(
signal, signal,
}) })
.json(); .json();
return calendars; return calendars as CalendarList;
} }
export async function getCalendar( export async function getCalendar(
@@ -59,7 +60,7 @@ export async function postCalendar(
export async function addSharedCalendar( export async function addSharedCalendar(
userId: string, userId: string,
calId: string, calId: string,
cal: Record<string, any> cal: CalendarInput
) { ) {
const response = await api.post(`dav/calendars/${userId}.json`, { const response = await api.post(`dav/calendars/${userId}.json`, {
headers: { headers: {
@@ -70,7 +71,7 @@ export async function addSharedCalendar(
...cal.cal, ...cal.cal,
"dav:name": "dav:name":
cal.cal["dav:name"] === "#default" cal.cal["dav:name"] === "#default"
? cal.owner.displayName + "'s calendar" ? (cal.owner?.displayName ?? "Unknown") + "'s calendar"
: cal.cal["dav:name"], : cal.cal["dav:name"],
"calendarserver:source": { "calendarserver:source": {
acl: cal.cal.acl, acl: cal.cal.acl,
@@ -1,7 +1,8 @@
import { formatReduxError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit";
import { getUserDetails } from "@/features/User/userAPI"; import { getUserDetails } from "@/features/User/userAPI";
import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit";
import { addSharedCalendar } from "../CalendarApi"; import { addSharedCalendar } from "../CalendarApi";
import { CalendarInput } from "../types/CalendarData";
import { RejectedError } from "../types/RejectedError"; import { RejectedError } from "../types/RejectedError";
export const addSharedCalendarAsync = createAsyncThunk< export const addSharedCalendarAsync = createAsyncThunk<
@@ -14,14 +15,14 @@ export const addSharedCalendarAsync = createAsyncThunk<
owner: string; owner: string;
ownerEmails: string[]; ownerEmails: string[];
}, },
{ userId: string; calId: string; cal: Record<string, any> }, { userId: string; calId: string; cal: CalendarInput },
{ rejectValue: RejectedError } { rejectValue: RejectedError }
>( >(
"calendars/addSharedCalendar", "calendars/addSharedCalendar",
async ({ userId, calId, cal }, { rejectWithValue }) => { async ({ userId, calId, cal }, { rejectWithValue }) => {
try { try {
await addSharedCalendar(userId, calId, cal); await addSharedCalendar(userId, calId, cal);
const ownerData: any = await getUserDetails( const ownerData = await getUserDetails(
cal.cal._links.self.href cal.cal._links.self.href
.replace("/calendars/", "") .replace("/calendars/", "")
.replace(".json", "") .replace(".json", "")
@@ -29,28 +30,25 @@ export const addSharedCalendarAsync = createAsyncThunk<
); );
return { return {
calId: cal.cal._links.self.href calId: cal.cal._links.self?.href
.replace("/calendars/", "") ?.replace("/calendars/", "")
.replace(".json", ""), .replace(".json", ""),
color: cal.color, color: cal.color,
link: `/calendars/${userId}/${calId}.json`, link: `/calendars/${userId}/${calId}.json`,
desc: cal.cal["caldav:description"], desc: cal.cal["caldav:description"] ?? "",
name: name:
ownerData.id !== userId && cal.cal["dav:name"] === "#default" ownerData.id !== userId && cal.cal["dav:name"] === "#default"
? `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${ ? `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${
ownerData.lastname ownerData.lastname ?? ""
}` + "'s calendar" }` + "'s calendar"
: cal.cal["dav:name"], : (cal.cal["dav:name"] ?? ""),
owner: `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${ owner: `${ownerData.firstname ? `${ownerData.firstname} ` : ""}${
ownerData.lastname ownerData.lastname ?? ""
}`, }`,
ownerEmails: ownerData.emails, ownerEmails: ownerData.emails,
}; };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,6 +1,5 @@
import { getUserDetails } from "@/features/User/userAPI";
import { userData } from "@/features/User/userDataTypes"; import { userData } from "@/features/User/userDataTypes";
import { formatReduxError } from "@/utils/errorUtils"; import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { postCalendar } from "../CalendarApi"; import { postCalendar } from "../CalendarApi";
import { RejectedError } from "../types/RejectedError"; import { RejectedError } from "../types/RejectedError";
@@ -45,11 +44,8 @@ export const createCalendarAsync = createAsyncThunk<
owner, owner,
ownerEmails: userData.email ? [userData.email] : [], ownerEmails: userData.email ? [userData.email] : [],
}; };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,5 +1,5 @@
import { deleteEvent } from "@/features/Events/EventApi"; import { deleteEvent } from "@/features/Events/EventApi";
import { formatReduxError } from "@/utils/errorUtils"; import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { RejectedError } from "../types/RejectedError"; import { RejectedError } from "../types/RejectedError";
@@ -13,11 +13,8 @@ export const deleteEventAsync = createAsyncThunk<
try { try {
await deleteEvent(eventURL); await deleteEvent(eventURL);
return { calId, eventId }; return { calId, eventId };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -11,12 +11,13 @@ export const deleteEventInstanceAsync = createAsyncThunk<
{ rejectValue: RejectedError } { rejectValue: RejectedError }
>("calendars/delEventInstance", async ({ cal, event }, { rejectWithValue }) => { >("calendars/delEventInstance", async ({ cal, event }, { rejectWithValue }) => {
try { try {
await deleteEventInstance(event, cal.ownerEmails?.[0]); await deleteEventInstance(event);
return { calId: cal.id, eventId: event.uid }; return { calId: cal.id, eventId: event.uid };
} catch (err: any) { } catch (err) {
const error = err as { response?: { status?: number } };
return rejectWithValue({ return rejectWithValue({
message: formatReduxError(err), message: formatReduxError(err),
status: err.response?.status, status: error.response?.status,
}); });
} }
}); });
@@ -1,9 +1,14 @@
import { CalendarEvent } from "@/features/Events/EventsTypes"; import { CalendarEvent } from "@/features/Events/EventsTypes";
import { formatReduxError } from "@/utils/errorUtils"; import {
CalendarData,
CalendarItem,
} from "@/features/Calendars/types/CalendarData";
import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { getCalendar } from "../CalendarApi"; import { getCalendar } from "../CalendarApi";
import { RejectedError } from "../types/RejectedError"; import { RejectedError } from "../types/RejectedError";
import { extractCalendarEvents } from "../utils/extractCalendarEvents"; import { extractCalendarEvents } from "../utils/extractCalendarEvents";
import { defaultColors } from "@/components/Calendar/utils/calendarColorsUtils";
export const getCalendarDetailAsync = createAsyncThunk< export const getCalendarDetailAsync = createAsyncThunk<
{ {
@@ -23,24 +28,27 @@ export const getCalendarDetailAsync = createAsyncThunk<
"calendars/getCalendarDetails", "calendars/getCalendarDetails",
async ({ calId, match, calType, signal }, { rejectWithValue }) => { async ({ calId, match, calType, signal }, { rejectWithValue }) => {
try { try {
const calendar = (await getCalendar(calId, match, signal)) as any; const calendar = (await getCalendar(
calId,
match,
signal
)) as CalendarData;
const color = calendar["apple:color"]; const color = calendar["apple:color"]
? { light: calendar["apple:color"], dark: calendar["apple:color"] }
: defaultColors[0];
const syncToken = calendar._embedded?.["sync-token"]; const syncToken = calendar._embedded?.["sync-token"];
const items = calendar._embedded?.["dav:item"]; const items = calendar._embedded?.["dav:item"];
const events: CalendarEvent[] = Array.isArray(items) const events: CalendarEvent[] = Array.isArray(items)
? items.flatMap((item: any) => ? items.flatMap((item: CalendarItem) =>
extractCalendarEvents(item, { calId, color }) extractCalendarEvents(item, { calId, color })
) )
: []; : [];
return { calId, events, calType, syncToken }; return { calId, events, calType, syncToken };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,32 +1,33 @@
import { RootState } from "@/app/store";
import { OpenPaasUserData } from "@/features/User/type/OpenPaasUserData";
import { getOpenPaasUser, getUserDetails } from "@/features/User/userAPI"; import { getOpenPaasUser, getUserDetails } from "@/features/User/userAPI";
import { formatReduxError } from "@/utils/errorUtils"; import { formatReduxError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { getCalendars } from "../CalendarApi"; import { getCalendars } from "../CalendarApi";
import { RejectedError } from "../types/RejectedError";
import { Calendar } from "../CalendarTypes"; import { Calendar } from "../CalendarTypes";
import { CalendarData } from "../types/CalendarData";
import { RejectedError } from "../types/RejectedError";
import { normalizeCalendar } from "../utils/normalizeCalendar"; import { normalizeCalendar } from "../utils/normalizeCalendar";
export const getCalendarsListAsync = createAsyncThunk< export const getCalendarsListAsync = createAsyncThunk<
{ importedCalendars: Record<string, Calendar>; errors: string }, { importedCalendars: Record<string, Calendar>; errors: string },
void, void,
{ rejectValue: RejectedError; state: any } { rejectValue: RejectedError; state: RootState }
>("calendars/getCalendars", async (_, { rejectWithValue, getState }) => { >("calendars/getCalendars", async (_, { rejectWithValue, getState }) => {
const state = getState() as any; const state = getState();
const existingCalendars = state.calendars.list || {}; const existingCalendars = state.calendars.list || {};
const existingUser = { id: state.user?.userData?.openpaasId || undefined }; const existingUser = { id: state.user?.userData?.openpaasId || undefined };
try { try {
const fetchedCalendars: Record<string, Calendar> = {}; const fetchedCalendars: Record<string, Calendar> = {};
const user = existingUser.id const user = existingUser.id
? existingUser ? existingUser
: ((await getOpenPaasUser()) as Record<string, string>); : ((await getOpenPaasUser()) as OpenPaasUserData);
const calendars = (await getCalendars(user.id)) as Record<string, any>; const calendars = await getCalendars(user.id);
const rawCalendars = calendars._embedded["dav:calendar"] as Record< const rawCalendars = calendars._embedded["dav:calendar"];
string,
any
>[];
const errors: string[] = []; const errors: string[] = [];
const normalizedCalendars = rawCalendars.map((cal) => const normalizedCalendars = rawCalendars.map((cal: CalendarData) =>
normalizeCalendar(cal) normalizeCalendar(cal)
); );
@@ -34,14 +35,14 @@ export const getCalendarsListAsync = createAsyncThunk<
new Set(normalizedCalendars.map(({ ownerId }) => ownerId).filter(Boolean)) new Set(normalizedCalendars.map(({ ownerId }) => ownerId).filter(Boolean))
); );
const ownerDataMap = new Map<string, any>(); const ownerDataMap = new Map<string, OpenPaasUserData>();
const OWNER_BATCH_SIZE = 20; const OWNER_BATCH_SIZE = 20;
const fetchOwnerData = async (ownerId: string) => { const fetchOwnerData = async (ownerId: string) => {
try { try {
const data = await getUserDetails(ownerId); const data = await getUserDetails(ownerId);
ownerDataMap.set(ownerId, data); ownerDataMap.set(ownerId, data);
} catch (error: any) { } catch (error) {
console.error(`Failed to fetch user details for ${ownerId}:`, error); console.error(`Failed to fetch user details for ${ownerId}:`, error);
ownerDataMap.set(ownerId, { ownerDataMap.set(ownerId, {
firstname: "", firstname: "",
@@ -72,7 +73,7 @@ export const getCalendarsListAsync = createAsyncThunk<
const color = { const color = {
light: cal["apple:color"] ?? "#006BD8", light: cal["apple:color"] ?? "#006BD8",
dark: cal["X-TWAKE-Dark-theme-color"] ?? "#FFF", dark: "#FFF",
}; };
fetchedCalendars[id] = { fetchedCalendars[id] = {
id, id,
@@ -120,10 +121,11 @@ export const getCalendarsListAsync = createAsyncThunk<
importedCalendars, importedCalendars,
errors: errors.join("\n"), errors: errors.join("\n"),
}; };
} catch (err: any) { } catch (err) {
const error = err as { response?: { status?: number } };
return rejectWithValue({ return rejectWithValue({
message: formatReduxError(err), message: formatReduxError(err),
status: err.response?.status, status: error.response?.status,
}); });
} }
}); });
@@ -15,10 +15,11 @@ export const getEventAsync = createAsyncThunk<
calId: event.calId, calId: event.calId,
event: response, event: response,
}; };
} catch (err: any) { } catch (err) {
const error = err as { response?: { status?: number } };
return rejectWithValue({ return rejectWithValue({
message: formatReduxError(err), message: formatReduxError(err),
status: err.response?.status, status: error.response?.status,
}); });
} }
}); });
@@ -4,8 +4,8 @@ import { getUserDetails } from "@/features/User/userAPI";
import { formatReduxError } from "@/utils/errorUtils"; import { formatReduxError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { getCalendars } from "../CalendarApi"; import { getCalendars } from "../CalendarApi";
import { RejectedError } from "../types/RejectedError";
import { Calendar } from "../CalendarTypes"; import { Calendar } from "../CalendarTypes";
import { RejectedError } from "../types/RejectedError";
export const getTempCalendarsListAsync = createAsyncThunk< export const getTempCalendarsListAsync = createAsyncThunk<
Record<string, Calendar>, Record<string, Calendar>,
@@ -20,10 +20,10 @@ export const getTempCalendarsListAsync = createAsyncThunk<
`TRANSLATION:calendar.userDoesNotHaveValidId|name=${encodeURIComponent(username)}` `TRANSLATION:calendar.userDoesNotHaveValidId|name=${encodeURIComponent(username)}`
); );
} }
const calendars = (await getCalendars( const calendars = await getCalendars(
tempUser.openpaasId, tempUser.openpaasId,
"sharedPublic=true&" "sharedPublic=true&"
)) as Record<string, any>; );
const rawCalendars = calendars._embedded?.["dav:calendar"]; const rawCalendars = calendars._embedded?.["dav:calendar"];
if (!rawCalendars || rawCalendars.length === 0) { if (!rawCalendars || rawCalendars.length === 0) {
@@ -36,17 +36,20 @@ export const getTempCalendarsListAsync = createAsyncThunk<
} }
for (const cal of rawCalendars) { for (const cal of rawCalendars) {
const name = cal["dav:name"]; const name = cal["dav:name"] ?? "";
const description = cal["caldav:description"]; const description = cal["caldav:description"] ?? "";
const delegated = cal["calendarserver:delegatedsource"] ? true : false; const delegated = cal["calendarserver:delegatedsource"] ? true : false;
const source = cal["calendarserver:source"] const source = cal["calendarserver:source"]
? cal["calendarserver:source"]._links.self.href ? cal["calendarserver:source"]._links.self?.href
: cal._links.self.href; : cal._links.self?.href;
const link = cal._links.self.href; if (!source) {
throw new Error("No source for calendar");
}
const link = cal._links.self?.href ?? "";
const id = source.replace("/calendars/", "").replace(".json", ""); const id = source.replace("/calendars/", "").replace(".json", "");
const visibility = getCalendarVisibility(cal["acl"]); const visibility = getCalendarVisibility(cal["acl"] ?? []);
const ownerData: any = await getUserDetails(id.split("/")[0]); const ownerData = await getUserDetails(id.split("/")[0]);
importedCalendars[id] = { importedCalendars[id] = {
id, id,
@@ -66,10 +69,11 @@ export const getTempCalendarsListAsync = createAsyncThunk<
} }
return importedCalendars; return importedCalendars;
} catch (err: any) { } catch (err) {
const error = err as { response?: { status?: number } };
return rejectWithValue({ return rejectWithValue({
message: formatReduxError(err), message: formatReduxError(err),
status: err.response?.status, status: error.response?.status,
}); });
} }
}); });
@@ -22,10 +22,11 @@ export const importEventFromFileAsync = createAsyncThunk<
}); });
} }
await importEventFromFile(id, calLink); await importEventFromFile(id, calLink);
} catch (err: any) { } catch (err) {
const error = err as { response?: { status?: number } };
return rejectWithValue({ return rejectWithValue({
message: formatReduxError(err), message: formatReduxError(err),
status: err.response?.status, status: error.response?.status,
}); });
} }
}); });
@@ -1,15 +1,9 @@
import { moveEvent } from "@/features/Events/EventApi"; import { moveEvent } from "@/features/Events/EventApi";
import { CalendarEvent } from "@/features/Events/EventsTypes"; import { CalendarEvent } from "@/features/Events/EventsTypes";
import { parseCalendarEvent } from "@/features/Events/eventUtils"; import { toRejectedError } from "@/utils/errorUtils";
import {
computeWeekRange,
formatDateToYYYYMMDDTHHMMSS,
} from "@/utils/dateUtils";
import { formatReduxError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { getCalendar } from "../CalendarApi";
import { RejectedError } from "../types/RejectedError";
import { Calendar } from "../CalendarTypes"; import { Calendar } from "../CalendarTypes";
import { RejectedError } from "../types/RejectedError";
export const moveEventAsync = createAsyncThunk< export const moveEventAsync = createAsyncThunk<
{ calId: string }, { calId: string },
@@ -24,11 +18,8 @@ export const moveEventAsync = createAsyncThunk<
return { return {
calId: cal.id, calId: cal.id,
}; };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,4 +1,4 @@
import { formatReduxError } from "@/utils/errorUtils"; import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { updateAclCalendar } from "../CalendarApi"; import { updateAclCalendar } from "../CalendarApi";
import { RejectedError } from "../types/RejectedError"; import { RejectedError } from "../types/RejectedError";
@@ -25,11 +25,8 @@ export const patchACLCalendarAsync = createAsyncThunk<
calLink, calLink,
request, request,
}; };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,4 +1,4 @@
import { formatReduxError } from "@/utils/errorUtils"; import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { proppatchCalendar } from "../CalendarApi"; import { proppatchCalendar } from "../CalendarApi";
import { RejectedError } from "../types/RejectedError"; import { RejectedError } from "../types/RejectedError";
@@ -25,11 +25,8 @@ export const patchCalendarAsync = createAsyncThunk<
calLink, calLink,
patch, patch,
}; };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,9 +1,9 @@
import { putEvent } from "@/features/Events/EventApi"; import { putEvent } from "@/features/Events/EventApi";
import { CalendarEvent } from "@/features/Events/EventsTypes"; import { CalendarEvent } from "@/features/Events/EventsTypes";
import { formatReduxError } from "@/utils/errorUtils"; import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { RejectedError } from "../types/RejectedError";
import { Calendar } from "../CalendarTypes"; import { Calendar } from "../CalendarTypes";
import { RejectedError } from "../types/RejectedError";
export const putEventAsync = createAsyncThunk< export const putEventAsync = createAsyncThunk<
{ calId: string; calType?: "temp" }, { calId: string; calType?: "temp" },
@@ -22,11 +22,8 @@ export const putEventAsync = createAsyncThunk<
calId: cal.id, calId: cal.id,
calType, calType,
}; };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,10 +1,10 @@
import { CalendarEvent } from "@/features/Events/EventsTypes"; import { CalendarEvent } from "@/features/Events/EventsTypes";
import { formatReduxError } from "@/utils/errorUtils"; import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import pMap from "p-map"; import pMap from "p-map";
import { fetchSyncTokenChanges } from "../api/fetchSyncTokenChanges"; import { fetchSyncTokenChanges } from "../api/fetchSyncTokenChanges";
import { RejectedError } from "../types/RejectedError";
import { Calendar } from "../CalendarTypes"; import { Calendar } from "../CalendarTypes";
import { RejectedError } from "../types/RejectedError";
import { expandEventFunction } from "../utils/expandEventFunction"; import { expandEventFunction } from "../utils/expandEventFunction";
import { processSyncUpdates } from "../utils/processSyncTokenUpdates"; import { processSyncUpdates } from "../utils/processSyncTokenUpdates";
@@ -70,11 +70,8 @@ export const refreshCalendarWithSyncToken = createAsyncThunk<
syncToken: newSyncToken, syncToken: newSyncToken,
syncStatus: newSyncToken ? "SUCCESS" : "NO_NEW_SYNC_TOKEN", syncStatus: newSyncToken ? "SUCCESS" : "NO_NEW_SYNC_TOKEN",
}; };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,4 +1,4 @@
import { formatReduxError } from "@/utils/errorUtils"; import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { removeCalendar } from "../CalendarApi"; import { removeCalendar } from "../CalendarApi";
import { RejectedError } from "../types/RejectedError"; import { RejectedError } from "../types/RejectedError";
@@ -20,11 +20,8 @@ export const removeCalendarAsync = createAsyncThunk<
return { return {
calId, calId,
}; };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,6 +1,6 @@
import { putEventWithOverrides } from "@/features/Events/EventApi"; import { putEventWithOverrides } from "@/features/Events/EventApi";
import { CalendarEvent } from "@/features/Events/EventsTypes"; import { CalendarEvent } from "@/features/Events/EventsTypes";
import { formatReduxError } from "@/utils/errorUtils"; import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { RejectedError } from "../types/RejectedError"; import { RejectedError } from "../types/RejectedError";
import { Calendar } from "../CalendarTypes"; import { Calendar } from "../CalendarTypes";
@@ -15,11 +15,8 @@ export const updateEventInstanceAsync = createAsyncThunk<
try { try {
await putEventWithOverrides(event, cal.ownerEmails?.[0]); await putEventWithOverrides(event, cal.ownerEmails?.[0]);
return { calId: cal.id, event }; return { calId: cal.id, event };
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -1,9 +1,9 @@
import { updateSeries } from "@/features/Events/EventApi"; import { updateSeries } from "@/features/Events/EventApi";
import { CalendarEvent } from "@/features/Events/EventsTypes"; import { CalendarEvent } from "@/features/Events/EventsTypes";
import { formatReduxError } from "@/utils/errorUtils"; import { toRejectedError } from "@/utils/errorUtils";
import { createAsyncThunk } from "@reduxjs/toolkit"; import { createAsyncThunk } from "@reduxjs/toolkit";
import { RejectedError } from "../types/RejectedError";
import { Calendar } from "../CalendarTypes"; import { Calendar } from "../CalendarTypes";
import { RejectedError } from "../types/RejectedError";
export const updateSeriesAsync = createAsyncThunk< export const updateSeriesAsync = createAsyncThunk<
void, void,
@@ -14,11 +14,8 @@ export const updateSeriesAsync = createAsyncThunk<
async ({ cal, event, removeOverrides = true }, { rejectWithValue }) => { async ({ cal, event, removeOverrides = true }, { rejectWithValue }) => {
try { try {
await updateSeries(event, cal.ownerEmails?.[0] ?? "", removeOverrides); await updateSeries(event, cal.ownerEmails?.[0] ?? "", removeOverrides);
} catch (err: any) { } catch (err) {
return rejectWithValue({ return rejectWithValue(toRejectedError(err));
message: formatReduxError(err),
status: err.response?.status,
});
} }
} }
); );
@@ -0,0 +1,7 @@
// Type for async thunk results
export interface AsyncThunkResult {
type: string;
error?: { message?: string };
payload?: { message?: string };
unwrap?: () => Promise<unknown>;
}
@@ -0,0 +1,90 @@
import { User } from "@/components/Attendees/PeopleSearch";
import { CalDavLink } from "../api/types";
// Access control entry
export interface AclEntry {
privilege: string;
principal: string;
protected: boolean;
}
// VObject property value can be various types
export type VObjectValue =
| string
| number
| boolean
| Date
| Record<string, unknown>
| null
| RepetitionRule
| undefined;
// VObject property tuple
export type VObjectProperty = [
string,
Record<string, unknown>,
string | Array<unknown>,
VObjectValue,
];
export type VCalComponent = [
string,
VObjectProperty[],
VCalComponent[],
...unknown[],
];
export interface Organizer {
cn?: string;
email: string;
}
// The `dav:item` object in _embedded
export interface CalendarItem {
_links: CalDavLink;
etag: string;
status: number;
data: [
"vcalendar",
Array<
VObjectProperty | [string, VObjectProperty[], unknown[]] // vevent array
>,
];
}
// Main calendar data
export interface CalendarData {
_links: CalDavLink;
"caldav:description"?: string;
"dav:name"?: string;
"apple:color"?: string;
id?: string;
acl?: AclEntry[];
invite?: unknown;
_embedded: {
"sync-token": string;
"dav:item": CalendarItem[];
};
"calendarserver:source"?: { _links: CalDavLink };
"calendarserver:delegatedsource"?: string;
}
export interface CalendarList {
_embedded: { "dav:calendar": CalendarData[] };
}
// Calendar input for forms or UI
export interface CalendarInput {
cal: CalendarData;
color: Record<string, string>;
owner?: User;
}
// Vevent repetition rule
export interface RepetitionRule {
freq: string;
interval?: number;
count?: number;
until?: string;
byday?: string | string[];
}
@@ -20,7 +20,7 @@ export function expandEventFunction(
}); });
return events; return events;
} catch (err) { } catch (err) {
console.error("Failed to fetch event", eventUrl); console.error("Failed to fetch event", eventUrl, err);
return undefined; return undefined;
} }
}; };
@@ -2,6 +2,7 @@ import { defaultColors } from "@/components/Calendar/utils/calendarColorsUtils";
import { CalendarEvent } from "@/features/Events/EventsTypes"; import { CalendarEvent } from "@/features/Events/EventsTypes";
import { parseCalendarEvent } from "@/features/Events/eventUtils"; import { parseCalendarEvent } from "@/features/Events/eventUtils";
import { CalDavItem } from "../api/types"; import { CalDavItem } from "../api/types";
import { VCalComponent } from "../types/CalendarData";
export function extractCalendarEvents( export function extractCalendarEvents(
item: CalDavItem, item: CalDavItem,
@@ -50,7 +51,7 @@ export function extractCalendarEvents(
.filter(Boolean) as CalendarEvent[]; .filter(Boolean) as CalendarEvent[];
} }
function extractValarm(vevent: any[]) { function extractValarm(vevent: VCalComponent[]) {
const subComponents = vevent[2]; const subComponents = vevent[2];
if (!Array.isArray(subComponents)) { if (!Array.isArray(subComponents)) {
return undefined; return undefined;
@@ -1,19 +1,23 @@
import { getCalendarVisibility } from "@/components/Calendar/utils/calendarUtils"; import { getCalendarVisibility } from "@/components/Calendar/utils/calendarUtils";
import { CalendarData } from "../types/CalendarData";
export function normalizeCalendar(rawCalendar: Record<string, any>) { export function normalizeCalendar(rawCalendar: CalendarData) {
const description = rawCalendar["caldav:description"]; const description = rawCalendar["caldav:description"];
let delegated = false; let delegated = false;
let source = rawCalendar["calendarserver:source"] let source = rawCalendar["calendarserver:source"]
? rawCalendar["calendarserver:source"]._links.self.href ? rawCalendar["calendarserver:source"]._links.self?.href
: rawCalendar._links.self.href; : rawCalendar._links.self?.href;
const link = rawCalendar._links.self.href; const link = rawCalendar._links.self?.href;
if (rawCalendar["calendarserver:delegatedsource"]) { if (rawCalendar["calendarserver:delegatedsource"]) {
source = rawCalendar["calendarserver:delegatedsource"]; source = rawCalendar["calendarserver:delegatedsource"];
delegated = true; delegated = true;
} }
if (!source) {
throw new Error("No source for calendar");
}
const id = source.replace("/calendars/", "").replace(".json", ""); const id = source.replace("/calendars/", "").replace(".json", "");
const ownerId = id.split("/")[0]; const ownerId = id.split("/")[0];
const visibility = getCalendarVisibility(rawCalendar["acl"]); const visibility = getCalendarVisibility(rawCalendar["acl"] ?? []);
return { return {
cal: rawCalendar, cal: rawCalendar,
description, description,
@@ -30,6 +30,6 @@ export function processSyncUpdates(
} }
function extractFileNameFromHref(href: string): string { function extractFileNameFromHref(href: string): string {
const fileNameMatch = href.match(/\/([^\/]+)\.ics$/); // CalDAV href are like /calendars/userID/CalendarID/EventId.ics const fileNameMatch = href.match(/\/([^/]+)\.ics$/); // CalDAV href are like /calendars/userID/CalendarID/EventId.ics
return fileNameMatch ? fileNameMatch[1] : href; return fileNameMatch ? fileNameMatch[1] : href;
} }
@@ -9,7 +9,9 @@ import { PartStat } from "@/features/User/models/attendee";
interface AttendanceValidationProps { interface AttendanceValidationProps {
contextualizedEvent: ContextualizedEvent; contextualizedEvent: ContextualizedEvent;
user: userData | undefined; user: userData | undefined;
setAfterChoiceFunc: Dispatch<SetStateAction<Function | undefined>>; setAfterChoiceFunc: (
func: ((type: "solo" | "all" | undefined) => void) | undefined
) => void;
setOpenEditModePopup: Dispatch<SetStateAction<string | null>>; setOpenEditModePopup: Dispatch<SetStateAction<string | null>>;
} }
@@ -1,8 +1,8 @@
import { useAppDispatch } from "@/app/hooks"; import { useAppDispatch } from "@/app/hooks";
import { PartStat } from "@/features/User/models/attendee"; import { PartStat } from "@/features/User/models/attendee";
import { userData } from "@/features/User/userDataTypes"; import { userData } from "@/features/User/userDataTypes";
import { Button, CircularProgress, Box, Theme } from "@linagora/twake-mui"; import { Box, Button, CircularProgress, Theme } from "@linagora/twake-mui";
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react"; import { Dispatch, SetStateAction, useEffect, useRef } from "react";
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { ContextualizedEvent } from "../EventsTypes"; import { ContextualizedEvent } from "../EventsTypes";
import { handleRSVPClick } from "./handleRSVPClick"; import { handleRSVPClick } from "./handleRSVPClick";
@@ -19,7 +19,9 @@ interface RSVPButtonProps {
rsvpValue: PartStat; rsvpValue: PartStat;
contextualizedEvent: ContextualizedEvent; contextualizedEvent: ContextualizedEvent;
user: userData | undefined; user: userData | undefined;
setAfterChoiceFunc: Dispatch<SetStateAction<Function | undefined>>; setAfterChoiceFunc: (
func: ((type: "solo" | "all" | undefined) => void) | undefined
) => void;
setOpenEditModePopup: Dispatch<SetStateAction<string | null>>; setOpenEditModePopup: Dispatch<SetStateAction<string | null>>;
isLoading: boolean; isLoading: boolean;
onLoadingChange: (loading: boolean, value?: PartStat) => void; onLoadingChange: (loading: boolean, value?: PartStat) => void;
@@ -9,7 +9,9 @@ export async function handleRSVPClick(
rsvp: PartStat, rsvp: PartStat,
contextualizedEvent: ContextualizedEvent, contextualizedEvent: ContextualizedEvent,
user: userData | undefined, user: userData | undefined,
setAfterChoiceFunc: Dispatch<SetStateAction<Function | undefined>>, setAfterChoiceFunc: (
func: ((type: "solo" | "all" | undefined) => void) | undefined
) => void,
setOpenEditModePopup: Dispatch<SetStateAction<string | null>>, setOpenEditModePopup: Dispatch<SetStateAction<string | null>>,
dispatch: AppDispatch, dispatch: AppDispatch,
onLoadingChange?: (loading: boolean, value?: PartStat) => void onLoadingChange?: (loading: boolean, value?: PartStat) => void
+35 -19
View File
@@ -3,6 +3,7 @@ import { resolveTimezoneId, convertEventDateTimeToISO } from "@/utils/timezone";
import { TIMEZONES } from "@/utils/timezone-data"; import { TIMEZONES } from "@/utils/timezone-data";
import ICAL from "ical.js"; import ICAL from "ical.js";
import { CalDavItem } from "../Calendars/api/types"; import { CalDavItem } from "../Calendars/api/types";
import { SearchEventsResponse } from "../Search/types/SearchEventsResponse";
import { CalendarEvent } from "./EventsTypes"; import { CalendarEvent } from "./EventsTypes";
import { import {
calendarEventToJCal, calendarEventToJCal,
@@ -11,6 +12,23 @@ import {
parseCalendarEvent, parseCalendarEvent,
} from "./eventUtils"; } from "./eventUtils";
type JCalValue = string | number | boolean | null;
type JCalParams = Record<string, JCalValue | JCalValue[]>;
type JCalProperty = [
name: string,
params: JCalParams,
type: string,
value: JCalValue,
];
type JCalComponent = [
name: string,
properties: JCalProperty[],
components?: JCalComponent[],
];
export async function reportEvent( export async function reportEvent(
event: CalendarEvent, event: CalendarEvent,
match: { start: string; end: string } match: { start: string; end: string }
@@ -43,8 +61,8 @@ export async function getEvent(event: CalendarEvent, isMaster?: boolean) {
let targetVevent; let targetVevent;
if (isMaster) { if (isMaster) {
targetVevent = vevents.find( targetVevent = vevents.find(
([, props]: [string, any[]]) => ([, props]: JCalComponent) =>
!props.find(([k]: string[]) => k.toLowerCase() === "recurrence-id") !props.find(([k]) => k.toLowerCase() === "recurrence-id")
); );
if (!targetVevent) { if (!targetVevent) {
targetVevent = vevents[0]; targetVevent = vevents[0];
@@ -152,7 +170,7 @@ export async function putEvent(event: CalendarEvent, calOwnerEmail?: string) {
}); });
if (response.status === 201) { if (response.status === 201) {
console.log("Event created successfully:", response.url || event.URL); console.info("Event created successfully:", response.url || event.URL);
} }
return response; return response;
@@ -198,17 +216,14 @@ export async function putEventWithOverrides(
}); });
} }
export const deleteEventInstance = async ( export const deleteEventInstance = async (event: CalendarEvent) => {
event: CalendarEvent,
calOwnerEmail?: string
) => {
// Get all VEVENTs (master + overrides) from the series // Get all VEVENTs (master + overrides) from the series
const vevents = await getAllRecurrentEvent(event); const vevents = await getAllRecurrentEvent(event);
// Find the master VEVENT // Find the master VEVENT
const masterIndex = vevents.findIndex( const masterIndex = vevents.findIndex(
([, props]: [string, any[]]) => ([, props]: JCalComponent) =>
!props.find(([k]: string[]) => k.toLowerCase() === "recurrence-id") !props.find(([k]) => k.toLowerCase() === "recurrence-id")
); );
if (masterIndex === -1) { if (masterIndex === -1) {
@@ -220,8 +235,9 @@ export const deleteEventInstance = async (
const masterProps = vevents[masterIndex][1]; const masterProps = vevents[masterIndex][1];
// Check if this date is already in EXDATE (avoid duplicates) // Check if this date is already in EXDATE (avoid duplicates)
const normalizeRecurrenceId = (id: string) => (id ?? "").replace(/Z$/, ""); const normalizeRecurrenceId = (id: JCalValue) =>
const isDuplicate = masterProps.some((prop: any[]) => { String(id ?? "").replace(/Z$/, "");
const isDuplicate = masterProps.some((prop: JCalProperty) => {
if (prop[0].toLowerCase() === "exdate" && prop[3]) { if (prop[0].toLowerCase() === "exdate" && prop[3]) {
return ( return (
normalizeRecurrenceId(prop[3]) === normalizeRecurrenceId(exdateValue) normalizeRecurrenceId(prop[3]) === normalizeRecurrenceId(exdateValue)
@@ -240,9 +256,9 @@ export const deleteEventInstance = async (
vevents[masterIndex][1] = masterProps; vevents[masterIndex][1] = masterProps;
// Remove the override instance if it exists (in case it was an override being deleted) // Remove the override instance if it exists (in case it was an override being deleted)
const filteredVevents = vevents.filter(([, props]: [string, any[]]) => { const filteredVevents = vevents.filter(([, props]: JCalComponent) => {
const recurrenceIdProp = props.find( const recurrenceIdProp = props.find(
([k]: string[]) => k.toLowerCase() === "recurrence-id" ([k]) => k.toLowerCase() === "recurrence-id"
); );
if (!recurrenceIdProp) return true; // Keep master if (!recurrenceIdProp) return true; // Keep master
return ( return (
@@ -278,9 +294,9 @@ export const updateSeriesPartstat = async (
const vevents = await getAllRecurrentEvent(event); const vevents = await getAllRecurrentEvent(event);
// Update PARTSTAT in ALL VEVENTs (master + exceptions) // Update PARTSTAT in ALL VEVENTs (master + exceptions)
const updatedVevents = vevents.map((vevent: any[]) => { const updatedVevents = vevents.map((vevent: JCalComponent) => {
const properties = vevent[1]; const properties = vevent[1];
const updatedProperties = properties.map((prop: any[]) => { const updatedProperties = properties.map((prop: JCalProperty) => {
// Find ATTENDEE properties // Find ATTENDEE properties
if (prop[0] === "attendee") { if (prop[0] === "attendee") {
const calAddress = prop[3]; const calAddress = prop[3];
@@ -321,7 +337,7 @@ export const updateSeries = async (
) => { ) => {
const vevents = await getAllRecurrentEvent(event); const vevents = await getAllRecurrentEvent(event);
const masterIndex = vevents.findIndex( const masterIndex = vevents.findIndex(
([, props]: [string, string[]]) => ([, props]: JCalComponent) =>
!props.find(([k]) => k.toLowerCase() === "recurrence-id") !props.find(([k]) => k.toLowerCase() === "recurrence-id")
); );
if (masterIndex === -1) { if (masterIndex === -1) {
@@ -404,7 +420,7 @@ export async function searchEvent(
organizers: string[]; organizers: string[];
attendees: string[]; attendees: string[];
} }
) { ): Promise<SearchEventsResponse> {
const { keywords, searchIn, organizers, attendees } = filters; const { keywords, searchIn, organizers, attendees } = filters;
const reqParam: { const reqParam: {
@@ -413,7 +429,7 @@ export async function searchEvent(
organizers?: string[]; organizers?: string[];
attendees?: string[]; attendees?: string[];
} = { } = {
query: !!keywords ? keywords : query, query: keywords ? keywords : query,
calendars: searchIn.map((calId) => { calendars: searchIn.map((calId) => {
const [userId, calendarId] = calId.split("/"); const [userId, calendarId] = calId.split("/");
return { calendarId, userId }; return { calendarId, userId };
@@ -431,5 +447,5 @@ export async function searchEvent(
}) })
.json(); .json();
return response; return response as SearchEventsResponse;
} }
+22 -19
View File
@@ -1,3 +1,4 @@
/* eslint-disable react-hooks/rules-of-hooks */
import { useAppDispatch, useAppSelector } from "@/app/hooks"; import { useAppDispatch, useAppSelector } from "@/app/hooks";
import { CalendarName } from "@/components/Calendar/CalendarName"; import { CalendarName } from "@/components/Calendar/CalendarName";
import { getTimezoneOffset } from "@/utils/timezone"; import { getTimezoneOffset } from "@/utils/timezone";
@@ -59,7 +60,7 @@ export default function EventPreviewModal({
calId: string; calId: string;
tempEvent?: boolean; tempEvent?: boolean;
open: boolean; open: boolean;
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void; onClose: (event: unknown, reason: "backdropClick" | "escapeKeyDown") => void;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@@ -67,9 +68,7 @@ export default function EventPreviewModal({
const timezone = const timezone =
useAppSelector((state) => state.settings.timeZone) ?? useAppSelector((state) => state.settings.timeZone) ??
browserDefaultTimeZone; browserDefaultTimeZone;
const calendarList = Object.values(
useAppSelector((state) => state.calendars.list)
);
const calendar = tempEvent const calendar = tempEvent
? calendars.templist[calId] ? calendars.templist[calId]
: calendars.list[calId]; : calendars.list[calId];
@@ -95,14 +94,16 @@ export default function EventPreviewModal({
const [typeOfAction, setTypeOfAction] = useState<"solo" | "all" | undefined>( const [typeOfAction, setTypeOfAction] = useState<"solo" | "all" | undefined>(
undefined undefined
); );
const [afterChoiceFunc, setAfterChoiceFunc] = useState<Function>(); const [afterChoiceFunc, setAfterChoiceFunc] = useState<
((type: "solo" | "all" | undefined) => void) | undefined
>();
const attendeePreview = makeAttendeePreview(event.attendee, t); const attendeePreview = makeAttendeePreview(event.attendee, t);
const hasCheckedSessionStorageRef = useRef(false); const hasCheckedSessionStorageRef = useRef(false);
const [toggleActionMenu, setToggleActionMenu] = useState<Element | null>( const [toggleActionMenu, setToggleActionMenu] = useState<Element | null>(
null null
); );
const mailSpaUrl = (window as any).MAIL_SPA_URL ?? null; const mailSpaUrl = window.MAIL_SPA_URL ?? null;
useEffect(() => { useEffect(() => {
if (!event || !calendar) { if (!event || !calendar) {
@@ -160,7 +161,7 @@ export default function EventPreviewModal({
} }
} }
} }
} catch (err) { } catch {
// Ignore sessionStorage errors // Ignore sessionStorage errors
} }
}; };
@@ -201,7 +202,7 @@ export default function EventPreviewModal({
// Clear sessionStorage after reopening // Clear sessionStorage after reopening
try { try {
sessionStorage.removeItem("eventUpdateModalReopen"); sessionStorage.removeItem("eventUpdateModalReopen");
} catch (err) { } catch {
// Ignore sessionStorage errors // Ignore sessionStorage errors
} }
} }
@@ -241,7 +242,7 @@ export default function EventPreviewModal({
} }
} }
} }
} catch (err) { } catch {
// Ignore sessionStorage errors // Ignore sessionStorage errors
} }
}; };
@@ -296,7 +297,7 @@ export default function EventPreviewModal({
gap={0.5} gap={0.5}
width="100%" width="100%"
> >
{(window as any).DEBUG && ( {window.DEBUG && (
<IconButton <IconButton
size="small" size="small"
onClick={async () => { onClick={async () => {
@@ -373,8 +374,6 @@ export default function EventPreviewModal({
</MenuItem> </MenuItem>
)} )}
<EventDuplication <EventDuplication
event={event}
onClose={() => setToggleActionMenu(null)}
onOpenDuplicate={() => { onOpenDuplicate={() => {
setToggleActionMenu(null); setToggleActionMenu(null);
setHidePreview(true); setHidePreview(true);
@@ -723,10 +722,11 @@ export default function EventPreviewModal({
<EditModeDialog <EditModeDialog
type={openEditModePopup} type={openEditModePopup}
setOpen={setOpenEditModePopup} setOpen={setOpenEditModePopup}
event={event}
eventAction={(type: "solo" | "all" | undefined) => { eventAction={(type: "solo" | "all" | undefined) => {
setTypeOfAction(type); setTypeOfAction(type);
afterChoiceFunc && afterChoiceFunc(type); if (afterChoiceFunc) {
afterChoiceFunc(type);
}
}} }}
/> />
<EventUpdateModal <EventUpdateModal
@@ -759,7 +759,7 @@ export default function EventPreviewModal({
return data.typeOfAction; return data.typeOfAction;
} }
} }
} catch (err) { } catch {
// Ignore // Ignore
} }
return undefined; return undefined;
@@ -791,7 +791,7 @@ export default function EventPreviewModal({
function makeRecurrenceString( function makeRecurrenceString(
event: CalendarEvent, event: CalendarEvent,
t: Function t: (k: string, p?: string | object) => string
): string | undefined { ): string | undefined {
if (!event.repetition) return; if (!event.repetition) return;
@@ -851,7 +851,7 @@ function makeRecurrenceString(
function formatDate( function formatDate(
date: Date | string, date: Date | string,
t: Function, t: (k: string, p?: string | object) => string,
timeZone: string, timeZone: string,
allday?: boolean allday?: boolean
) { ) {
@@ -880,7 +880,7 @@ function formatDate(
function formatEnd( function formatEnd(
start: Date | string, start: Date | string,
end: Date | string, end: Date | string,
t: Function, t: (k: string, p?: string | object) => string,
timeZone: string, timeZone: string,
allday?: boolean allday?: boolean
) { ) {
@@ -924,7 +924,10 @@ function formatEnd(
} }
} }
export function makeAttendeePreview(attendees: userAttendee[], t: Function) { export function makeAttendeePreview(
attendees: userAttendee[],
t: (k: string, p?: string | object) => string
) {
const attendeePreview = []; const attendeePreview = [];
const yesCount = attendees?.filter((a) => a.partstat === "ACCEPTED").length; const yesCount = attendees?.filter((a) => a.partstat === "ACCEPTED").length;
const noCount = attendees?.filter((a) => a.partstat === "DECLINED").length; const noCount = attendees?.filter((a) => a.partstat === "DECLINED").length;
+22 -18
View File
@@ -1,6 +1,5 @@
import { useAppDispatch, useAppSelector } from "@/app/hooks"; import { useAppDispatch, useAppSelector } from "@/app/hooks";
import { getTimezoneOffset, resolveTimezone } from "@/utils/timezone"; import { RootState } from "@/app/store";
import { updateTempCalendar } from "@/components/Calendar/utils/calendarUtils";
import { ResponsiveDialog } from "@/components/Dialog"; import { ResponsiveDialog } from "@/components/Dialog";
import EventFormFields from "@/components/Event/EventFormFields"; import EventFormFields from "@/components/Event/EventFormFields";
import { addDays } from "@/components/Event/utils/dateRules"; import { addDays } from "@/components/Event/utils/dateRules";
@@ -9,7 +8,6 @@ import {
formatLocalDateTime, formatLocalDateTime,
} from "@/components/Event/utils/dateTimeFormatters"; } from "@/components/Event/utils/dateTimeFormatters";
import { convertFormDateTimeToISO } from "@/components/Event/utils/dateTimeHelpers"; import { convertFormDateTimeToISO } from "@/components/Event/utils/dateTimeHelpers";
import { getCalendarRange } from "@/utils/dateUtils";
import { import {
buildEventFormTempData, buildEventFormTempData,
clearEventFormTempData, clearEventFormTempData,
@@ -19,7 +17,11 @@ import {
saveEventFormDataToTemp, saveEventFormDataToTemp,
showErrorNotification, showErrorNotification,
} from "@/utils/eventFormTempStorage"; } from "@/utils/eventFormTempStorage";
import { browserDefaultTimeZone } from "@/utils/timezone"; import {
browserDefaultTimeZone,
getTimezoneOffset,
resolveTimezone,
} from "@/utils/timezone";
import { TIMEZONES } from "@/utils/timezone-data"; import { TIMEZONES } from "@/utils/timezone-data";
import { addVideoConferenceToDescription } from "@/utils/videoConferenceUtils"; import { addVideoConferenceToDescription } from "@/utils/videoConferenceUtils";
import { CalendarApi, DateSelectArg } from "@fullcalendar/core"; import { CalendarApi, DateSelectArg } from "@fullcalendar/core";
@@ -37,6 +39,7 @@ import React, {
import { useI18n } from "twake-i18n"; import { useI18n } from "twake-i18n";
import { Calendar } from "../Calendars/CalendarTypes"; import { Calendar } from "../Calendars/CalendarTypes";
import { putEventAsync } from "../Calendars/services"; import { putEventAsync } from "../Calendars/services";
import { AsyncThunkResult } from "../Calendars/types/AsyncThunkResult";
import { userAttendee } from "../User/models/attendee"; import { userAttendee } from "../User/models/attendee";
import { CalendarEvent, RepetitionObject } from "./EventsTypes"; import { CalendarEvent, RepetitionObject } from "./EventsTypes";
@@ -52,7 +55,7 @@ function EventPopover({
open: boolean; open: boolean;
onClose: (refresh?: boolean) => void; onClose: (refresh?: boolean) => void;
selectedRange: DateSelectArg | null; selectedRange: DateSelectArg | null;
setSelectedRange: Function; setSelectedRange: React.Dispatch<React.SetStateAction<DateSelectArg | null>>;
calendarRef: React.RefObject<CalendarApi | null>; calendarRef: React.RefObject<CalendarApi | null>;
event?: CalendarEvent; event?: CalendarEvent;
}) { }) {
@@ -64,8 +67,8 @@ function EventPopover({
useAppSelector((state) => state.user.userData?.openpaasId) ?? ""; useAppSelector((state) => state.user.userData?.openpaasId) ?? "";
const calList = useAppSelector((state) => state.calendars.list); const calList = useAppSelector((state) => state.calendars.list);
const selectPersonalCalendars = createSelector( const selectPersonalCalendars = createSelector(
(state: any) => state.calendars, (state: RootState) => state.calendars,
(calendars: any) => (calendars: RootState["calendars"]) =>
Object.keys(calendars.list || {}) Object.keys(calendars.list || {})
.map((id) => { .map((id) => {
if (id.split("/")[0] === userId) { if (id.split("/")[0] === userId) {
@@ -704,7 +707,6 @@ function EventPopover({
}, 0); }, 0);
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [open, event?.uid]); }, [open, event?.uid]);
const handleSave = async () => { const handleSave = async () => {
@@ -818,20 +820,20 @@ function EventPopover({
); );
// Handle result of putEventAsync - check if rejected first // Handle result of putEventAsync - check if rejected first
const typedResult = result as AsyncThunkResult;
// Check if result is a rejected action // Check if result is a rejected action
if (result.type && result.type.endsWith("/rejected")) { if (typedResult.type && typedResult.type.endsWith("/rejected")) {
throw new Error( throw new Error(
result.error?.message || result.payload?.message || "API call failed" typedResult.error?.message ||
typedResult.payload?.message ||
"API call failed"
); );
} }
// If result has unwrap, call it (it will throw if rejected) // If result has unwrap, call it (it will throw if rejected)
if (result && typeof result.unwrap === "function") { if (typedResult && typeof typedResult.unwrap === "function") {
try { await typedResult.unwrap();
await result.unwrap();
} catch (unwrapError: any) {
throw unwrapError;
}
} }
// Clear temp data on successful save // Clear temp data on successful save
@@ -839,7 +841,9 @@ function EventPopover({
// Reset all state to default values only on successful save // Reset all state to default values only on successful save
resetAllStateToDefault(); resetAllStateToDefault();
} catch (error: any) { } catch (error) {
const errorObj = error as { message?: string };
// API failed - restore form data and mark as error // API failed - restore form data and mark as error
const errorFormData = { const errorFormData = {
...formDataToSave, ...formDataToSave,
@@ -849,7 +853,7 @@ function EventPopover({
// Show error notification // Show error notification
showErrorNotification( showErrorNotification(
error?.message || "Failed to create event. Please try again." errorObj.message || "Failed to create event. Please try again."
); );
// Try to reopen modal by dispatching custom event // Try to reopen modal by dispatching custom event
+70 -105
View File
@@ -11,6 +11,7 @@ import {
updateEventInstanceAsync, updateEventInstanceAsync,
updateSeriesAsync, updateSeriesAsync,
} from "@/features/Calendars/services"; } from "@/features/Calendars/services";
import { assertThunkSuccess } from "@/utils/assertThunkSuccess";
import { import {
buildEventFormTempData, buildEventFormTempData,
clearEventFormTempData, clearEventFormTempData,
@@ -24,8 +25,8 @@ import {
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid"; import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
import { import {
browserDefaultTimeZone, browserDefaultTimeZone,
resolveTimezone,
getTimezoneOffset, getTimezoneOffset,
resolveTimezone,
} from "@/utils/timezone"; } from "@/utils/timezone";
import { TIMEZONES } from "@/utils/timezone-data"; import { TIMEZONES } from "@/utils/timezone-data";
import { addVideoConferenceToDescription } from "@/utils/videoConferenceUtils"; import { addVideoConferenceToDescription } from "@/utils/videoConferenceUtils";
@@ -39,6 +40,7 @@ import {
updateEventLocal, updateEventLocal,
} from "../Calendars/CalendarSlice"; } from "../Calendars/CalendarSlice";
import { Calendar } from "../Calendars/CalendarTypes"; import { Calendar } from "../Calendars/CalendarTypes";
import { AsyncThunkResult } from "../Calendars/types/AsyncThunkResult";
import { userAttendee } from "../User/models/attendee"; import { userAttendee } from "../User/models/attendee";
import { deleteEvent, getEvent, putEvent } from "./EventApi"; import { deleteEvent, getEvent, putEvent } from "./EventApi";
import { CalendarEvent, RepetitionObject } from "./EventsTypes"; import { CalendarEvent, RepetitionObject } from "./EventsTypes";
@@ -56,7 +58,7 @@ function EventUpdateModal({
eventId: string; eventId: string;
calId: string; calId: string;
open: boolean; open: boolean;
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void; onClose: (event: unknown, reason: "backdropClick" | "escapeKeyDown") => void;
onCloseAll?: () => void; onCloseAll?: () => void;
eventData?: CalendarEvent | null; eventData?: CalendarEvent | null;
typeOfAction?: "solo" | "all"; typeOfAction?: "solo" | "all";
@@ -192,7 +194,7 @@ function EventUpdateModal({
}; };
const fetchedMasterEvent = await getEvent(masterEventToFetch, true); const fetchedMasterEvent = await getEvent(masterEventToFetch, true);
setMasterEvent(fetchedMasterEvent); setMasterEvent(fetchedMasterEvent);
} catch (err: any) { } catch (err) {
console.error("Failed to fetch master event:", err); console.error("Failed to fetch master event:", err);
// Fallback to using the clicked instance // Fallback to using the clicked instance
setMasterEvent(event); setMasterEvent(event);
@@ -335,6 +337,7 @@ function EventUpdateModal({
} }
} }
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ }, [
open, open,
event, event,
@@ -675,16 +678,22 @@ function EventUpdateModal({
const deletePromises = Array.from(uniqueURLs).map(async (url) => { const deletePromises = Array.from(uniqueURLs).map(async (url) => {
try { try {
await deleteEvent(url); await deleteEvent(url);
} catch (deleteError: any) { } catch (deleteError) {
// Check if error is an object with response or message properties
const errorObj = deleteError as {
response?: { status?: number };
message?: string;
};
// Silently ignore 404 - file might already be deleted // Silently ignore 404 - file might already be deleted
const is404 = const is404 =
deleteError.response?.status === 404 || errorObj.response?.status === 404 ||
deleteError.message?.includes("404") || errorObj.message?.includes("404") ||
deleteError.message?.includes("Not Found"); errorObj.message?.includes("Not Found");
if (!is404) { if (!is404) {
console.error( console.error(
`Failed to delete event file: ${deleteError.message}` `Failed to delete event file: ${errorObj.message || "Unknown error"}`
); );
} }
} }
@@ -724,7 +733,10 @@ function EventUpdateModal({
// Reset all state to default values only on successful save // Reset all state to default values only on successful save
resetAllStateToDefault(); resetAllStateToDefault();
initializedKeyRef.current = null; initializedKeyRef.current = null;
} catch (err: any) { } catch (err) {
// Check if error is an object with a message property
const errorObj = err as { message?: string };
// API failed - restore form data and mark as error // API failed - restore form data and mark as error
const errorFormData = { const errorFormData = {
...formDataToSave, ...formDataToSave,
@@ -734,7 +746,8 @@ function EventUpdateModal({
// Show error notification // Show error notification
showErrorNotification( showErrorNotification(
err?.message || "Failed to convert recurring event. Please try again." errorObj.message ||
"Failed to convert recurring event. Please try again."
); );
if (createdSingleEventUid) { if (createdSingleEventUid) {
@@ -769,7 +782,6 @@ function EventUpdateModal({
if (recurrenceId) { if (recurrenceId) {
if (typeOfAction === "solo") { if (typeOfAction === "solo") {
// Update single instance with optimistic update + rollback // Update single instance with optimistic update + rollback
const oldEvent = { ...event };
dispatch( dispatch(
updateEventLocal({ updateEventLocal({
@@ -778,43 +790,30 @@ function EventUpdateModal({
}) })
); );
try { const result = await dispatch(
const result = await dispatch( updateEventInstanceAsync({
updateEventInstanceAsync({ cal: targetCalendar,
cal: targetCalendar, event: { ...newEvent, recurrenceId },
event: { ...newEvent, recurrenceId }, })
}) );
);
// Handle result of updateEventInstanceAsync // Handle result of updateEventInstanceAsync
if (result && typeof (result as any).unwrap === "function") { const typedResult = result as AsyncThunkResult;
try { if (typedResult && typeof typedResult.unwrap === "function") {
await (result as any).unwrap(); await typedResult.unwrap();
} catch (unwrapError: any) { } else {
throw unwrapError; // Check if result is rejected
} if (typedResult.type && typedResult.type.endsWith("/rejected")) {
} else { throw new Error(
// Check if result is rejected typedResult.error?.message ||
if ( typedResult.payload?.message ||
result.type && "API call failed"
(result.type as string).endsWith("/rejected") );
) {
const rejectedResult = result as any;
throw new Error(
rejectedResult.error?.message ||
rejectedResult.payload?.message ||
"API call failed"
);
}
} }
// Clear temp data on successful save
clearEventFormTempData("update");
} catch (error: any) {
// Rollback optimistic update
dispatch(updateEventLocal({ calId, event: oldEvent }));
throw error; // Re-throw to be caught by outer catch
} }
// Clear temp data on successful save
clearEventFormTempData("update");
} else if (typeOfAction === "all") { } else if (typeOfAction === "all") {
// Update all instances - check if repetition rules changed // Update all instances - check if repetition rules changed
const baseUID = extractEventBaseUuid(event.uid); const baseUID = extractEventBaseUuid(event.uid);
@@ -872,22 +871,18 @@ function EventUpdateModal({
); );
// Handle result of updateSeriesAsync // Handle result of updateSeriesAsync
if (result && typeof (result as any).unwrap === "function") { const typedResult = result as AsyncThunkResult;
try { if (typedResult && typeof typedResult.unwrap === "function") {
await (result as any).unwrap(); await typedResult.unwrap();
} catch (unwrapError: any) {
throw unwrapError;
}
} else { } else {
// Check if result is rejected // Check if result is rejected
if ( if (
result.type && typedResult.type &&
(result.type as string).endsWith("/rejected") typedResult.type.endsWith("/rejected")
) { ) {
const rejectedResult = result as any;
throw new Error( throw new Error(
rejectedResult.error?.message || typedResult.error?.message ||
rejectedResult.payload?.message || typedResult.payload?.message ||
"API call failed" "API call failed"
); );
} }
@@ -949,26 +944,7 @@ function EventUpdateModal({
); );
// Handle result of updateSeriesAsync // Handle result of updateSeriesAsync
if (result && typeof (result as any).unwrap === "function") { assertThunkSuccess(result);
try {
await (result as any).unwrap();
} catch (unwrapError: any) {
throw unwrapError;
}
} else {
// Check if result is rejected
if (
result.type &&
(result.type as string).endsWith("/rejected")
) {
const rejectedResult = result as any;
throw new Error(
rejectedResult.error?.message ||
rejectedResult.payload?.message ||
"API call failed"
);
}
}
// Clear cache to ensure navigation shows updated data // Clear cache to ensure navigation shows updated data
dispatch(clearFetchCache(calId)); dispatch(clearFetchCache(calId));
@@ -990,20 +966,7 @@ function EventUpdateModal({
); );
// Handle result of putEventAsync - check if rejected first // Handle result of putEventAsync - check if rejected first
if (result.type && result.type.endsWith("/rejected")) { assertThunkSuccess(result);
throw new Error(
result.error?.message ||
result.payload?.message ||
"API call failed"
);
}
if (result && typeof result.unwrap === "function") {
try {
await result.unwrap();
} catch (unwrapError: any) {
throw unwrapError;
}
}
// Remove old single event AFTER new recurring instances are added to store // Remove old single event AFTER new recurring instances are added to store
// This prevents empty grid during the transition // This prevents empty grid during the transition
@@ -1024,19 +987,16 @@ function EventUpdateModal({
); );
// Handle result of putEventAsync - check if rejected first // Handle result of putEventAsync - check if rejected first
if (result.type && result.type.endsWith("/rejected")) { const typedResult = result as AsyncThunkResult;
if (typedResult.type && typedResult.type.endsWith("/rejected")) {
throw new Error( throw new Error(
result.error?.message || typedResult.error?.message ||
result.payload?.message || typedResult.payload?.message ||
"API call failed" "API call failed"
); );
} }
if (result && typeof result.unwrap === "function") { if (typedResult && typeof typedResult.unwrap === "function") {
try { await typedResult.unwrap();
await result.unwrap();
} catch (unwrapError: any) {
throw unwrapError;
}
} }
// Clear temp data on successful save // Clear temp data on successful save
@@ -1060,8 +1020,9 @@ function EventUpdateModal({
); );
// Handle result of putEventAsync // Handle result of putEventAsync
if (putResult && typeof putResult.unwrap === "function") { const typedPutResult = putResult as AsyncThunkResult;
await putResult.unwrap(); if (typedPutResult && typeof typedPutResult.unwrap === "function") {
await typedPutResult.unwrap();
} }
// Then move it to the new calendar // Then move it to the new calendar
@@ -1074,8 +1035,9 @@ function EventUpdateModal({
); );
// Handle result of moveEventAsync // Handle result of moveEventAsync
if (moveResult && typeof moveResult.unwrap === "function") { const typedMoveResult = moveResult as AsyncThunkResult;
await moveResult.unwrap(); if (typedMoveResult && typeof typedMoveResult.unwrap === "function") {
await typedMoveResult.unwrap();
} }
// Clear temp data on successful move // Clear temp data on successful move
@@ -1086,7 +1048,10 @@ function EventUpdateModal({
clearEventFormTempData("update"); clearEventFormTempData("update");
resetAllStateToDefault(); resetAllStateToDefault();
initializedKeyRef.current = null; initializedKeyRef.current = null;
} catch (error: any) { } catch (error) {
// Check if error is an object with a message property
const errorObj = error as { message?: string };
// Handle errors for all branches // Handle errors for all branches
// Rollback optimistic updates if any // Rollback optimistic updates if any
// API failed - restore form data and mark as error // API failed - restore form data and mark as error
@@ -1098,7 +1063,7 @@ function EventUpdateModal({
// Show error notification // Show error notification
showErrorNotification( showErrorNotification(
error?.message || "Failed to update event. Please try again." errorObj.message || "Failed to update event. Please try again."
); );
// Try to reopen modal // Try to reopen modal
+3 -3
View File
@@ -31,10 +31,10 @@ export interface CalendarEvent {
export interface RepetitionObject { export interface RepetitionObject {
freq: string; freq: string;
interval?: number; interval?: number | null;
byday?: string[] | null; byday?: string[] | null;
occurrences?: number; occurrences?: number | null;
endDate?: string; endDate?: string | null;
} }
export interface AlarmObject { export interface AlarmObject {
+68 -61
View File
@@ -1,17 +1,19 @@
import { convertFormDateTimeToISO } from "@/components/Event/utils/dateTimeHelpers"; import { convertFormDateTimeToISO } from "@/components/Event/utils/dateTimeHelpers";
import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid"; import { extractEventBaseUuid } from "@/utils/extractEventBaseUuid";
import { resolveTimezoneId, convertEventDateTimeToISO } from "@/utils/timezone"; import { convertEventDateTimeToISO, resolveTimezoneId } from "@/utils/timezone";
import { TIMEZONES } from "@/utils/timezone-data"; import { TIMEZONES } from "@/utils/timezone-data";
import ICAL from "ical.js"; import ICAL from "ical.js";
import moment from "moment-timezone"; import moment from "moment-timezone";
import {
RepetitionRule,
VObjectProperty,
} from "../Calendars/types/CalendarData";
import { userAttendee } from "../User/models/attendee"; import { userAttendee } from "../User/models/attendee";
import { createAttendee } from "../User/models/attendee.mapper"; import { createAttendee } from "../User/models/attendee.mapper";
import { AlarmObject, CalendarEvent, RepetitionObject } from "./EventsTypes"; import { AlarmObject, CalendarEvent, RepetitionObject } from "./EventsTypes";
type RawEntry = [string, Record<string, string>, string, any];
function inferTimezoneFromValue( function inferTimezoneFromValue(
params: Record<string, string> | undefined, params: Record<string, string> | undefined
value: string
): string | undefined { ): string | undefined {
if (!params) { if (!params) {
return undefined; return undefined;
@@ -30,11 +32,11 @@ function inferTimezoneFromValue(
} }
export function parseCalendarEvent( export function parseCalendarEvent(
data: RawEntry[], data: VObjectProperty[],
color: Record<string, string>, color: Record<string, string>,
calendarid: string, calendarid: string,
eventURL: string, eventURL: string,
valarm?: RawEntry[] valarm?: VObjectProperty[]
): CalendarEvent { ): CalendarEvent {
const event: Partial<CalendarEvent> = { color, attendee: [] }; const event: Partial<CalendarEvent> = { color, attendee: [] };
let recurrenceId; let recurrenceId;
@@ -44,18 +46,20 @@ export function parseCalendarEvent(
for (const [key, params, , value] of data) { for (const [key, params, , value] of data) {
switch (key.toLowerCase()) { switch (key.toLowerCase()) {
case "uid": case "uid":
event.uid = value; event.uid = String(value);
break; break;
case "transp": case "transp":
event.transp = value; event.transp = String(value);
break; break;
case "dtstart": { case "dtstart": {
event.start = value; event.start = String(value);
const detectedTz = inferTimezoneFromValue(params, value); const detectedTz = inferTimezoneFromValue(
params as Record<string, string>
);
if (detectedTz) { if (detectedTz) {
event.timezone = detectedTz; event.timezone = detectedTz;
} }
if (dateRegex.test(value)) { if (dateRegex.test(String(value))) {
event.allday = true; event.allday = true;
} else { } else {
event.allday = false; event.allday = false;
@@ -63,14 +67,16 @@ export function parseCalendarEvent(
break; break;
} }
case "dtend": { case "dtend": {
event.end = value; event.end = String(value);
if (!event.timezone) { if (!event.timezone) {
const detectedTz = inferTimezoneFromValue(params, value); const detectedTz = inferTimezoneFromValue(
params as Record<string, string>
);
if (detectedTz) { if (detectedTz) {
event.timezone = detectedTz; event.timezone = detectedTz;
} }
} }
if (dateRegex.test(value)) { if (dateRegex.test(String(value))) {
event.allday = true; event.allday = true;
} else { } else {
event.allday = false; event.allday = false;
@@ -78,50 +84,54 @@ export function parseCalendarEvent(
break; break;
} }
case "class": case "class":
event.class = value; event.class = String(value);
break; break;
case "x-openpaas-videoconference": case "x-openpaas-videoconference":
event.x_openpass_videoconference = value; event.x_openpass_videoconference = String(value);
break; break;
case "summary": case "summary":
event.title = value; event.title = String(value);
break; break;
case "description": case "description":
event.description = value; event.description = String(value);
break; break;
case "location": case "location":
event.location = value; event.location = String(value);
break; break;
case "organizer": case "organizer": {
const paramsObj = params as Record<string, string>;
event.organizer = { event.organizer = {
cn: params?.cn ?? "", cn: paramsObj?.cn ?? "",
cal_address: value?.replace(/^mailto:/i, ""), cal_address: String(value).replace(/^mailto:/i, ""),
}; };
break; break;
case "attendee": }
case "attendee": {
const paramsObj = params as Record<string, string>;
(event.attendee as userAttendee[]).push( (event.attendee as userAttendee[]).push(
createAttendee({ createAttendee({
cn: params?.cn, cn: paramsObj?.cn,
cal_address: value.replace(/^mailto:/i, ""), cal_address: String(value).replace(/^mailto:/i, ""),
partstat: params?.partstat as userAttendee["partstat"], partstat: paramsObj?.partstat as userAttendee["partstat"],
rsvp: params?.rsvp as userAttendee["rsvp"], rsvp: paramsObj?.rsvp as userAttendee["rsvp"],
role: params?.role as userAttendee["role"], role: paramsObj?.role as userAttendee["role"],
cutype: params?.cutype as userAttendee["cutype"], cutype: paramsObj?.cutype as userAttendee["cutype"],
}) })
); );
break; break;
}
case "dtstamp": case "dtstamp":
event.stamp = value; event.stamp = String(value);
break; break;
case "sequence": case "sequence":
event.sequence = Number(value); event.sequence = Number(value);
break; break;
case "recurrence-id": case "recurrence-id":
recurrenceId = value; recurrenceId = String(value);
break; break;
case "exdate": case "exdate":
if (!event.exdates) event.exdates = []; if (!event.exdates) event.exdates = [];
event.exdates.push(value); event.exdates.push(String(value));
break; break;
case "status": case "status":
event.status = String(value); event.status = String(value);
@@ -129,25 +139,27 @@ export function parseCalendarEvent(
case "duration": case "duration":
duration = String(value); duration = String(value);
break; break;
case "rrule": case "rrule": {
event.repetition = { freq: value.freq.toLowerCase() }; const ruleValue = value as RepetitionRule;
if (value.byday) { event.repetition = { freq: ruleValue.freq.toLowerCase() };
if (typeof value.byday === "string") { if (ruleValue.byday) {
event.repetition.byday = [value.byday]; if (typeof ruleValue.byday === "string") {
event.repetition.byday = [ruleValue.byday];
} else { } else {
event.repetition.byday = value.byday; event.repetition.byday = ruleValue.byday;
} }
} }
if (value.until) { if (ruleValue.until) {
event.repetition.endDate = value.until; event.repetition.endDate = ruleValue.until;
} }
if (value.count) { if (ruleValue.count) {
event.repetition.occurrences = value.count; event.repetition.occurrences = ruleValue.count;
} }
if (value.interval) { if (ruleValue.interval) {
event.repetition.interval = value.interval; event.repetition.interval = ruleValue.interval;
} }
break; break;
}
} }
} }
if (recurrenceId && event.uid) { if (recurrenceId && event.uid) {
@@ -160,10 +172,10 @@ export function parseCalendarEvent(
for (const [key, , , value] of valarm[1]) { for (const [key, , , value] of valarm[1]) {
switch (key.toLowerCase()) { switch (key.toLowerCase()) {
case "action": case "action":
event.alarm.action = value; event.alarm.action = String(value);
break; break;
case "trigger": case "trigger":
event.alarm.trigger = value; event.alarm.trigger = String(value);
break; break;
} }
} }
@@ -209,10 +221,10 @@ export function parseCalendarEvent(
export function calendarEventToJCal( export function calendarEventToJCal(
event: CalendarEvent, event: CalendarEvent,
calOwnerEmail?: string calOwnerEmail?: string
): any[] { ) {
const tzid = event.timezone; // Fallback to UTC if no timezone provided const tzid = event.timezone; // Fallback to UTC if no timezone provided
const vevent: any[] = makeVevent(event, tzid, calOwnerEmail); const vevent = makeVevent(event, tzid, calOwnerEmail);
const timezoneData = TIMEZONES.zones[event.timezone]; const timezoneData = TIMEZONES.zones[event.timezone];
const vtimezone = makeTimezone(timezoneData, event); const vtimezone = makeTimezone(timezoneData, event);
@@ -242,7 +254,7 @@ export function makeVevent(
calOwnerEmail: string | undefined, calOwnerEmail: string | undefined,
isMasterEvent?: boolean isMasterEvent?: boolean
) { ) {
const vevent: any[] = [ const vevent: [string, unknown[]] = [
"vevent", "vevent",
[ [
["uid", {}, "text", extractEventBaseUuid(event.uid)], ["uid", {}, "text", extractEventBaseUuid(event.uid)],
@@ -279,18 +291,13 @@ export function makeVevent(
]; ];
vevent.push([["valarm", valarm]]); vevent.push([["valarm", valarm]]);
} }
vevent.push([]);
if (event.end) { if (event.end) {
const startDate = new Date(event.start);
const endDate = new Date(event.end);
let finalEndDate = endDate;
vevent[1].push([ vevent[1].push([
"dtend", "dtend",
{ tzid }, { tzid },
event.allday ? "date" : "date-time", event.allday ? "date" : "date-time",
formatDateToICal(finalEndDate, event.allday ?? false, tzid), formatDateToICal(new Date(event.end), event.allday ?? false, tzid),
]); ]);
} }
if (event.organizer) { if (event.organizer) {
@@ -311,21 +318,21 @@ export function makeVevent(
vevent[1].push(["description", {}, "text", event.description]); vevent[1].push(["description", {}, "text", event.description]);
} }
if (event.repetition?.freq) { if (event.repetition?.freq) {
const repetitionRule: Record<string, any> = { freq: event.repetition.freq }; const repetitionRule: RepetitionRule = { freq: event.repetition.freq };
if (event.repetition.interval) { if (event.repetition.interval) {
repetitionRule["interval"] = event.repetition.interval; repetitionRule.interval = event.repetition.interval;
} }
if (event.repetition.occurrences) { if (event.repetition.occurrences) {
repetitionRule["count"] = event.repetition.occurrences; repetitionRule.count = event.repetition.occurrences;
} }
if (event.repetition.endDate) { if (event.repetition.endDate) {
repetitionRule["until"] = event.repetition.endDate; repetitionRule.until = event.repetition.endDate;
} }
if ( if (
event.repetition.byday !== null && event.repetition.byday !== null &&
event.repetition.byday !== undefined event.repetition.byday !== undefined
) { ) {
repetitionRule["byday"] = event.repetition.byday; repetitionRule.byday = event.repetition.byday;
} }
vevent[1].push(["rrule", {}, "recur", repetitionRule]); vevent[1].push(["rrule", {}, "recur", repetitionRule]);
} }
@@ -362,7 +369,7 @@ export function makeVevent(
return vevent; return vevent;
} }
function formatDateToICal(date: Date, allday: Boolean, timezone?: string) { function formatDateToICal(date: Date, allday: boolean, timezone?: string) {
const pad = (n: number) => n.toString().padStart(2, "0"); const pad = (n: number) => n.toString().padStart(2, "0");
if (allday) { if (allday) {
+2 -1
View File
@@ -6,6 +6,7 @@ import { TIMEZONES } from "@/utils/timezone-data";
import { addVideoConferenceToDescription } from "@/utils/videoConferenceUtils"; import { addVideoConferenceToDescription } from "@/utils/videoConferenceUtils";
import { userAttendee } from "../User/models/attendee"; import { userAttendee } from "../User/models/attendee";
import { CalendarEvent, RepetitionObject } from "./EventsTypes"; import { CalendarEvent, RepetitionObject } from "./EventsTypes";
import { Calendar } from "../Calendars/CalendarTypes";
export interface TimezoneListResult { export interface TimezoneListResult {
zones: string[]; zones: string[];
@@ -58,7 +59,7 @@ export interface PopulateFormFromEventParams {
setHasVideoConference: (value: boolean) => void; setHasVideoConference: (value: boolean) => void;
setMeetingLink: (value: string | null) => void; setMeetingLink: (value: string | null) => void;
setCalendarid?: (value: string) => void; setCalendarid?: (value: string) => void;
calendarsList?: Record<string, any>; calendarsList?: Record<string, Calendar>;
calId?: string; calId?: string;
} }
+4 -3
View File
@@ -21,6 +21,7 @@ import EventPreviewModal from "../Events/EventDisplayPreview";
import { CalendarEvent } from "../Events/EventsTypes"; import { CalendarEvent } from "../Events/EventsTypes";
import { setView } from "../Settings/SettingsSlice"; import { setView } from "../Settings/SettingsSlice";
import "./searchResult.styl"; import "./searchResult.styl";
import { SearchEventResult } from "./types/SearchEventResult";
const styles = { const styles = {
M3BodyLarge: { M3BodyLarge: {
@@ -110,7 +111,7 @@ export default function SearchResultsPage() {
layout = ( layout = (
<Box className="search-result-content-body"> <Box className="search-result-content-body">
<Stack sx={{ mt: 2 }}> <Stack sx={{ mt: 2 }}>
{results?.map((r: any, idx: number) => ( {results.map((r: SearchEventResult, idx: number) => (
<ResultItem <ResultItem
key={`row-${idx}-event-${r.data.uid}`} key={`row-${idx}-event-${r.data.uid}`}
eventData={r} eventData={r}
@@ -144,7 +145,7 @@ function ResultItem({
eventData, eventData,
dispatch, dispatch,
}: { }: {
eventData: Record<string, any>; eventData: SearchEventResult;
dispatch: AppDispatch; dispatch: AppDispatch;
}) { }) {
const { t } = useI18n(); const { t } = useI18n();
@@ -163,7 +164,7 @@ function ResultItem({
const [openPreview, setOpenPreview] = useState(false); const [openPreview, setOpenPreview] = useState(false);
const handleOpenResult = async (eventData: Record<string, any>) => { const handleOpenResult = async (eventData: SearchEventResult) => {
if (calendar) { if (calendar) {
const event = { const event = {
URL: eventData._links.self.href, URL: eventData._links.self.href,
+15 -9
View File
@@ -4,7 +4,7 @@ import { searchEvent } from "../Events/EventApi";
export interface SearchResultsState { export interface SearchResultsState {
hits: number; hits: number;
results: Record<string, any>[]; results: Record<string, unknown>[];
error: string | null; error: string | null;
loading: boolean; loading: boolean;
} }
@@ -17,24 +17,30 @@ const initialState: SearchResultsState = {
}; };
export const searchEventsAsync = createAsyncThunk< export const searchEventsAsync = createAsyncThunk<
{ hits: number; events: Record<string, any>[] }, { hits: number; events: Record<string, unknown>[] },
{ search: string; filters: any }, {
search: string;
filters: {
searchIn: string[];
keywords: string;
organizers: string[];
attendees: string[];
};
},
{ rejectValue: { message: string; status?: number } } { rejectValue: { message: string; status?: number } }
>("events/searchEvents", async ({ search, filters }, { rejectWithValue }) => { >("events/searchEvents", async ({ search, filters }, { rejectWithValue }) => {
try { try {
const response = (await searchEvent(search, filters)) as Record< const response = await searchEvent(search, filters);
string,
any
>;
return { return {
hits: Number(response._total_hits), hits: Number(response._total_hits),
events: response._embedded?.events ?? [], events: response._embedded?.events ?? [],
}; };
} catch (err: any) { } catch (err) {
const error = err as { response?: { status?: number } };
return rejectWithValue({ return rejectWithValue({
message: formatReduxError(err), message: formatReduxError(err),
status: err.response?.status, status: error.response?.status,
}); });
} }
}); });
@@ -0,0 +1,20 @@
export type SearchEventData = {
uid: string;
userId: string;
calendarId: string;
start: string;
end?: string;
allDay?: boolean;
summary?: string;
description?: string;
location?: string;
class?: string;
dtstamp?: string;
isRecurrentMaster?: boolean;
attendees?: unknown[];
organizer?: {
cn?: string;
email?: string;
};
["x-openpaas-videoconference"]?: string;
};
@@ -0,0 +1,10 @@
import { SearchEventData } from "./SearchEventData";
export type SearchEventResult = {
data: SearchEventData;
_links: {
self: {
href: string;
};
};
};
@@ -0,0 +1,6 @@
export type SearchEventsResponse = {
_total_hits?: number | string;
_embedded?: {
events?: Record<string, unknown>[];
};
};
+3 -3
View File
@@ -1,8 +1,7 @@
import { useAppDispatch, useAppSelector } from "@/app/hooks"; import { useAppDispatch, useAppSelector } from "@/app/hooks";
import { useTimeZoneList } from "@/components/Calendar/TimezoneSelector"; import { useTimeZoneList } from "@/components/Calendar/TimezoneSelector";
import { getTimezoneOffset } from "@/utils/timezone";
import { TimezoneAutocomplete } from "@/components/Timezone/TimezoneAutocomplete"; import { TimezoneAutocomplete } from "@/components/Timezone/TimezoneAutocomplete";
import { browserDefaultTimeZone } from "@/utils/timezone"; import { browserDefaultTimeZone, getTimezoneOffset } from "@/utils/timezone";
import { import {
Box, Box,
FormControl, FormControl,
@@ -14,6 +13,7 @@ import {
ListItemText, ListItemText,
MenuItem, MenuItem,
Select, Select,
SelectChangeEvent,
Snackbar, Snackbar,
Switch, Switch,
Tab, Tab,
@@ -105,7 +105,7 @@ export default function SettingsPage() {
setActiveSettingsSubTab(newValue); setActiveSettingsSubTab(newValue);
}; };
const handleLanguageChange = (event: any) => { const handleLanguageChange = (event: SelectChangeEvent<string>) => {
const newLanguage = event.target.value; const newLanguage = event.target.value;
const previousLanguage = currentLanguage; const previousLanguage = currentLanguage;
+16 -12
View File
@@ -1,5 +1,6 @@
import { browserDefaultTimeZone } from "@/utils/timezone"; import { browserDefaultTimeZone } from "@/utils/timezone";
import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { ConfigurationItem, ModuleConfiguration } from "../User/userDataTypes";
import { getOpenPaasUserDataAsync } from "../User/userSlice"; import { getOpenPaasUserDataAsync } from "../User/userSlice";
export interface SettingsState { export interface SettingsState {
@@ -12,7 +13,7 @@ export interface SettingsState {
} }
const savedLang = localStorage.getItem("lang"); const savedLang = localStorage.getItem("lang");
const defaultLang = savedLang ?? (window as any).LANG ?? "en"; const defaultLang = savedLang ?? window.LANG ?? "en";
const savedTimeZone = localStorage.getItem("timeZone"); const savedTimeZone = localStorage.getItem("timeZone");
// If savedTimeZone is the string "null" or doesn't exist, use null // If savedTimeZone is the string "null" or doesn't exist, use null
@@ -59,12 +60,15 @@ export const settingsSlice = createSlice({
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(getOpenPaasUserDataAsync.fulfilled, (state, action) => { builder.addCase(getOpenPaasUserDataAsync.fulfilled, (state, action) => {
const coreModule = action.payload.configurations?.modules?.find( const coreModule = action.payload.configurations?.modules?.find(
(module: any) => module.name === "core" (module: ModuleConfiguration) => module.name === "core"
); );
const datetimeConfig = coreModule?.configurations?.find( const datetimeConfig = coreModule?.configurations?.find(
(config: any) => config.name === "datetime" (config: ConfigurationItem) => config.name === "datetime"
); );
const timeZone = datetimeConfig?.value?.timeZone; const datetimeValue = datetimeConfig?.value as
| { timeZone?: string }
| undefined;
const timeZone = datetimeValue?.timeZone;
if (timeZone) { if (timeZone) {
state.timeZone = timeZone; state.timeZone = timeZone;
@@ -76,25 +80,25 @@ export const settingsSlice = createSlice({
localStorage.setItem("timeZone", browserDefaultTimeZone); localStorage.setItem("timeZone", browserDefaultTimeZone);
} }
const esnCalendarModule = action.payload.configurations?.modules?.find( const esnCalendarModule = action.payload.configurations?.modules?.find(
(module: any) => module.name === "linagora.esn.calendar" (module: ModuleConfiguration) => module.name === "linagora.esn.calendar"
); );
const hideDeclinedEventsConfig = esnCalendarModule?.configurations?.find( const hideDeclinedEventsConfig = esnCalendarModule?.configurations?.find(
(config: any) => config.name === "hideDeclinedEvents" (config: ConfigurationItem) => config.name === "hideDeclinedEvents"
); );
state.hideDeclinedEvents = state.hideDeclinedEvents =
typeof hideDeclinedEventsConfig?.value === "boolean" typeof hideDeclinedEventsConfig?.value === "boolean"
? hideDeclinedEventsConfig.value ? hideDeclinedEventsConfig.value
: null; : null;
const calendarModule = action.payload.configurations.modules.find( const calendarModule = action.payload.configurations?.modules?.find(
(module: any) => module.name === "calendar" (module: ModuleConfiguration) => module.name === "calendar"
); );
if (calendarModule?.configurations) { if (calendarModule?.configurations) {
const alarmEmailsConfig = calendarModule.configurations.find( const displayWeekNumbersConfig = calendarModule.configurations.find(
(config: any) => config.name === "displayWeekNumbers" (config: ConfigurationItem) => config.name === "displayWeekNumbers"
); );
if (alarmEmailsConfig) { if (displayWeekNumbersConfig) {
state.displayWeekNumbers = alarmEmailsConfig.value === true; state.displayWeekNumbers = displayWeekNumbersConfig.value === true;
} }
} }
}); });
+1
View File
@@ -52,6 +52,7 @@ export function HandleLogin() {
}; };
initiateLogin(); initiateLogin();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userData.userData, calendars.list, dispatch]); }, [userData.userData, calendars.list, dispatch]);
// Navigate to /calendar only when all data is ready // Navigate to /calendar only when all data is ready
+21 -16
View File
@@ -1,14 +1,14 @@
import * as client from "openid-client";
import { getLocation } from "@/utils/apiUtils"; import { getLocation } from "@/utils/apiUtils";
import * as client from "openid-client";
export const clientConfig = { export const clientConfig = {
url: (window as any).SSO_BASE_URL ?? "", url: window.SSO_BASE_URL ?? "",
client_id: (window as any).SSO_CLIENT_ID ?? "", client_id: window.SSO_CLIENT_ID ?? "",
scope: (window as any).SSO_SCOPE ?? "", scope: window.SSO_SCOPE ?? "",
redirect_uri: (window as any).SSO_REDIRECT_URI ?? "", redirect_uri: window.SSO_REDIRECT_URI ?? "",
response_type: (window as any).SSO_RESPONSE_TYPE ?? "", response_type: window.SSO_RESPONSE_TYPE ?? "",
code_challenge_method: (window as any).SSO_CODE_CHALLENGE_METHOD ?? "", code_challenge_method: window.SSO_CODE_CHALLENGE_METHOD ?? "",
post_logout_redirect_uri: (window as any).SSO_POST_LOGOUT_REDIRECT ?? "", post_logout_redirect_uri: window.SSO_POST_LOGOUT_REDIRECT ?? "",
}; };
export async function getClientConfig() { export async function getClientConfig() {
@@ -19,18 +19,21 @@ export async function getClientConfig() {
} }
export async function Auth() { export async function Auth() {
let code_verifier = client.randomPKCECodeVerifier(); const code_verifier = client.randomPKCECodeVerifier();
let code_challenge = await client.calculatePKCECodeChallenge(code_verifier); const code_challenge = await client.calculatePKCECodeChallenge(code_verifier);
const openIdClientConfig = await getClientConfig(); const openIdClientConfig = await getClientConfig();
let state = client.randomState(); const state = client.randomState();
let parameters: Record<string, string> = { const parameters: Record<string, string> = {
redirect_uri: clientConfig.redirect_uri, redirect_uri: clientConfig.redirect_uri,
scope: clientConfig.scope!, scope: clientConfig.scope!,
code_challenge, code_challenge,
code_challenge_method: clientConfig.code_challenge_method, code_challenge_method: clientConfig.code_challenge_method,
state, state,
}; };
let redirectTo = client.buildAuthorizationUrl(openIdClientConfig, parameters); const redirectTo = client.buildAuthorizationUrl(
openIdClientConfig,
parameters
);
return { redirectTo, code_verifier, state }; return { redirectTo, code_verifier, state };
} }
@@ -44,13 +47,15 @@ export async function Logout() {
return endSessionUrl; return endSessionUrl;
} }
export async function Callback(code_verifier: string, state: any) { export async function Callback(
code_verifier: string,
state: string | undefined
) {
try { try {
const openIdClientConfig = await getClientConfig(); const openIdClientConfig = await getClientConfig();
const currentLocation = getLocation(); const currentLocation = getLocation();
console.log("Callback URL:", currentLocation); console.info("Callback URL:", currentLocation);
console.log("Code verifier:", code_verifier);
const tokenSet = await client.authorizationCodeGrant( const tokenSet = await client.authorizationCodeGrant(
openIdClientConfig, openIdClientConfig,
@@ -0,0 +1,11 @@
import { ModuleConfiguration } from "../userDataTypes";
export interface OpenPaasUserData {
firstname?: string;
lastname?: string;
id?: string;
preferredEmail?: string;
configurations?: {
modules?: ModuleConfiguration[];
};
}
+18 -12
View File
@@ -1,5 +1,11 @@
import { User } from "@/components/Attendees/PeopleSearch"; import { User } from "@/components/Attendees/PeopleSearch";
import { api } from "@/utils/apiUtils"; import { api } from "@/utils/apiUtils";
import { OpenPaasUserData } from "./type/OpenPaasUserData";
import {
ConfigurationItem,
ModuleConfiguration,
SearchResponseItem,
} from "./userDataTypes";
export async function getOpenPaasUser() { export async function getOpenPaasUser() {
const user = await api.get(`api/user`); const user = await api.get(`api/user`);
@@ -10,7 +16,7 @@ export async function searchUsers(
query: string, query: string,
objectTypes: string[] = ["user", "contact"] objectTypes: string[] = ["user", "contact"]
): Promise<User[]> { ): Promise<User[]> {
const response: any[] = await api const response: SearchResponseItem[] = await api
.post(`api/people/search`, { .post(`api/people/search`, {
body: JSON.stringify({ body: JSON.stringify({
limit: 10, limit: 10,
@@ -29,9 +35,9 @@ export async function searchUsers(
})); }));
} }
export async function getUserDetails(id: string) { export async function getUserDetails(id: string): Promise<OpenPaasUserData> {
const user = await api.get(`api/users/${id}`).json(); const user = await api.get(`api/users/${id}`).json();
return user; return user as OpenPaasUserData;
} }
export interface UserConfigurationUpdates { export interface UserConfigurationUpdates {
@@ -39,7 +45,7 @@ export interface UserConfigurationUpdates {
notifications?: Record<string, unknown>; notifications?: Record<string, unknown>;
timezone?: string | null; timezone?: string | null;
displayWeekNumbers?: boolean; displayWeekNumbers?: boolean;
previousConfig?: Record<string, any>; previousConfig?: Record<string, unknown>;
alarmEmails?: boolean; alarmEmails?: boolean;
hideDeclinedEvents?: boolean; hideDeclinedEvents?: boolean;
} }
@@ -47,9 +53,9 @@ export interface UserConfigurationUpdates {
export async function updateUserConfigurations( export async function updateUserConfigurations(
updates: UserConfigurationUpdates updates: UserConfigurationUpdates
): Promise<Response | { status: number }> { ): Promise<Response | { status: number }> {
const coreConfigs: Array<{ name: string; value: any }> = []; const coreConfigs: ConfigurationItem[] = [];
const calendarConfigs: Array<{ name: string; value: any }> = []; const calendarConfigs: ConfigurationItem[] = [];
const esnCalendarConfigs: Array<{ name: string; value: any }> = []; const esnCalendarConfigs: ConfigurationItem[] = [];
if (updates.language !== undefined) { if (updates.language !== undefined) {
coreConfigs.push({ name: "language", value: updates.language }); coreConfigs.push({ name: "language", value: updates.language });
@@ -58,10 +64,13 @@ export async function updateUserConfigurations(
coreConfigs.push({ name: "notifications", value: updates.notifications }); coreConfigs.push({ name: "notifications", value: updates.notifications });
} }
if (updates.timezone !== undefined) { if (updates.timezone !== undefined) {
const previousDatetime = updates.previousConfig?.datetime as
| { timeZone?: string }
| undefined;
coreConfigs.push({ coreConfigs.push({
name: "datetime", name: "datetime",
value: { value: {
...updates.previousConfig?.datetime, ...previousDatetime,
timeZone: updates.timezone, timeZone: updates.timezone,
}, },
}); });
@@ -85,10 +94,7 @@ export async function updateUserConfigurations(
}); });
} }
const modules: Array<{ const modules: ModuleConfiguration[] = [];
name: string;
configurations: Array<{ name: string; value: any }>;
}> = [];
if (coreConfigs.length > 0) { if (coreConfigs.length > 0) {
modules.push({ modules.push({
+20 -7
View File
@@ -11,13 +11,7 @@ export interface userData {
} }
export interface UserConfigurations { export interface UserConfigurations {
modules?: Array<{ modules?: ModuleConfiguration[];
name: string;
configurations?: Array<{
name: string;
value: any;
}>;
}>;
} }
export interface NotificationSettings { export interface NotificationSettings {
@@ -33,3 +27,22 @@ export interface userOrganiser {
cn: string; cn: string;
cal_address: string; cal_address: string;
} }
// Type for search response from the API
export interface SearchResponseItem {
id?: string;
emailAddresses?: Array<{ value?: string }>;
names?: Array<{ displayName?: string }>;
photos?: Array<{ url?: string }>;
}
// Type for configuration item
export interface ConfigurationItem {
name: string;
value: unknown;
}
// Type for module configuration
export interface ModuleConfiguration {
name: string;
configurations: ConfigurationItem[];
}
+54 -30
View File
@@ -1,24 +1,44 @@
import { formatReduxError } from "@/utils/errorUtils"; import { formatReduxError } from "@/utils/errorUtils";
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { OpenPaasUserData } from "./type/OpenPaasUserData";
import { import {
getOpenPaasUser, getOpenPaasUser,
updateUserConfigurations, updateUserConfigurations,
UserConfigurationUpdates, UserConfigurationUpdates,
} from "./userAPI"; } from "./userAPI";
import { userData, userOrganiser } from "./userDataTypes"; import {
ConfigurationItem,
ModuleConfiguration,
userData,
userOrganiser,
} from "./userDataTypes";
// Type for core config datetime
interface DatetimeConfig {
timeZone: string | null;
[key: string]: unknown;
}
// Type for core config
interface CoreConfig {
language: string | null;
datetime: DatetimeConfig;
[key: string]: unknown;
}
export const getOpenPaasUserDataAsync = createAsyncThunk< export const getOpenPaasUserDataAsync = createAsyncThunk<
Record<string, any>, OpenPaasUserData,
void, void,
{ rejectValue: { message: string; status?: number } } { rejectValue: { message: string; status?: number } }
>("user/getOpenPaasUserData", async (_, { rejectWithValue }) => { >("user/getOpenPaasUserData", async (_, { rejectWithValue }) => {
try { try {
const user = (await getOpenPaasUser()) as Record<string, any>; const user = await getOpenPaasUser();
return user; return user as OpenPaasUserData;
} catch (err: any) { } catch (err) {
const error = err as { response?: { status?: number } };
return rejectWithValue({ return rejectWithValue({
message: formatReduxError(err), message: formatReduxError(err),
status: err.response?.status, status: error.response?.status,
}); });
} }
}); });
@@ -31,10 +51,11 @@ export const updateUserConfigurationsAsync = createAsyncThunk<
try { try {
await updateUserConfigurations(updates); await updateUserConfigurations(updates);
return updates; return updates;
} catch (err: any) { } catch (err) {
const error = err as { response?: { status?: number } };
return rejectWithValue({ return rejectWithValue({
message: formatReduxError(err), message: formatReduxError(err),
status: err.response?.status, status: error.response?.status,
}); });
} }
}); });
@@ -46,11 +67,11 @@ export const userSlice = createSlice({
organiserData: null as unknown as userOrganiser, organiserData: null as unknown as userOrganiser,
tokens: null as unknown as Record<string, string>, tokens: null as unknown as Record<string, string>,
coreConfig: { coreConfig: {
language: null as string | null, language: null,
datetime: { datetime: {
timeZone: null as string | null, timeZone: null,
}, },
} as Record<string, any>, } as CoreConfig,
alarmEmailsEnabled: null as boolean | null, alarmEmailsEnabled: null as boolean | null,
loading: true, loading: true,
error: null as unknown as string | null, error: null as unknown as string | null,
@@ -76,7 +97,7 @@ export const userSlice = createSlice({
}, },
setTimezone: (state, action) => { setTimezone: (state, action) => {
if (!state.coreConfig.datetime) { if (!state.coreConfig.datetime) {
state.coreConfig.datetime = {}; state.coreConfig.datetime = { timeZone: null };
} }
state.coreConfig.datetime.timeZone = action.payload; state.coreConfig.datetime.timeZone = action.payload;
if (state.userData) { if (state.userData) {
@@ -110,40 +131,44 @@ export const userSlice = createSlice({
state.organiserData.cal_address = action.payload.preferredEmail; state.organiserData.cal_address = action.payload.preferredEmail;
state.userData.email = action.payload.preferredEmail; state.userData.email = action.payload.preferredEmail;
} }
// Extract data from configurations.modules // Extract data from configurations.modules
if (action.payload.configurations?.modules) { if (action.payload.configurations?.modules) {
const coreModule = action.payload.configurations.modules.find( const coreModule = action.payload.configurations.modules.find(
(module: any) => module.name === "core" (module: ModuleConfiguration) => module.name === "core"
); );
if (coreModule?.configurations) { if (coreModule?.configurations) {
const newCoreConfig = Object.fromEntries( const newCoreConfig = Object.fromEntries(
coreModule.configurations.map( coreModule.configurations.map((e: ConfigurationItem) => [
(e: { name: string; value: any }) => [e.name, e.value] e.name,
) e.value,
])
); );
state.coreConfig = { state.coreConfig = {
...state.coreConfig, ...state.coreConfig,
...newCoreConfig, ...newCoreConfig,
}; } as CoreConfig;
const languageConfig = coreModule.configurations.find( const languageConfig = coreModule.configurations.find(
(config: any) => config.name === "language" (config: ConfigurationItem) => config.name === "language"
); );
if (languageConfig?.value) { if (languageConfig?.value) {
state.coreConfig.language = languageConfig.value; state.coreConfig.language = languageConfig.value as string;
if (state.userData) if (state.userData)
state.userData.language = languageConfig.value; state.userData.language = languageConfig.value as string;
} }
const datetimeConfig = coreModule.configurations.find( const datetimeConfig = coreModule.configurations.find(
(config: any) => config.name === "datetime" (config: ConfigurationItem) => config.name === "datetime"
); );
if (datetimeConfig?.value) { if (datetimeConfig?.value) {
const serverTimeZone = datetimeConfig.value.timeZone; const datetimeValue = datetimeConfig.value as {
timeZone?: string;
};
const serverTimeZone = datetimeValue.timeZone;
state.coreConfig.datetime = { state.coreConfig.datetime = {
...state.coreConfig.datetime, ...state.coreConfig.datetime,
...datetimeConfig.value, ...(typeof datetimeConfig.value === "object" &&
datetimeConfig.value !== null
? datetimeConfig.value
: {}),
timeZone: serverTimeZone !== undefined ? serverTimeZone : null, timeZone: serverTimeZone !== undefined ? serverTimeZone : null,
}; };
if (state.userData) { if (state.userData) {
@@ -160,14 +185,13 @@ export const userSlice = createSlice({
} }
} }
} }
// Extract alarmEmails from configurations.modules // Extract alarmEmails from configurations.modules
const calendarModule = action.payload.configurations.modules.find( const calendarModule = action.payload.configurations.modules.find(
(module: any) => module.name === "calendar" (module: ModuleConfiguration) => module.name === "calendar"
); );
if (calendarModule?.configurations) { if (calendarModule?.configurations) {
const alarmEmailsConfig = calendarModule.configurations.find( const alarmEmailsConfig = calendarModule.configurations.find(
(config: any) => config.name === "alarmEmails" (config: ConfigurationItem) => config.name === "alarmEmails"
); );
if (alarmEmailsConfig) { if (alarmEmailsConfig) {
state.alarmEmailsEnabled = alarmEmailsConfig.value === true; state.alarmEmailsEnabled = alarmEmailsConfig.value === true;
@@ -194,7 +218,7 @@ export const userSlice = createSlice({
} }
if (action.payload.timezone !== undefined) { if (action.payload.timezone !== undefined) {
if (!state.coreConfig.datetime) { if (!state.coreConfig.datetime) {
state.coreConfig.datetime = {}; state.coreConfig.datetime = { timeZone: null };
} }
state.coreConfig.datetime.timeZone = action.payload.timezone; state.coreConfig.datetime.timeZone = action.payload.timezone;
if (state.userData) { if (state.userData) {

Some files were not shown because too many files have changed in this diff Show More