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