651 calendar auto hide upon loads (#667)

* [#651] fix duplicated initialisatoin at login
* [#651] prevent event overwrite when getting calendars list

Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
Camille Moussu
2026-03-18 18:13:04 +01:00
committed by GitHub
parent a17320b861
commit a8bff830ad
5 changed files with 112 additions and 61 deletions
+32 -14
View File
@@ -1,10 +1,12 @@
import * as appHooks from "@/app/hooks";
import { AppDispatch } from "@/app/store";
import { AppDispatch, setupStore } from "@/app/store";
import HandleLogin from "@/features/User/HandleLogin";
import * as oidcAuth from "@/features/User/oidcAuth";
import { clientConfig } from "@/features/User/oidcAuth";
import { useInitializeApp } from "@/features/User/useInitializeApp";
import * as apiUtils from "@/utils/apiUtils";
import { screen, waitFor } from "@testing-library/react";
import { renderHook, screen, waitFor } from "@testing-library/react";
import { Provider } from "react-redux";
import { push } from "redux-first-history";
import { renderWithProviders } from "../../utils/Renderwithproviders";
@@ -17,6 +19,17 @@ describe("HandleLogin", () => {
const dispatch = jest.fn() as AppDispatch;
jest.spyOn(appHooks, "useAppDispatch").mockReturnValue(dispatch);
sessionStorage.clear();
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
});
test("redirects and sets sessionStorage when no userData", async () => {
@@ -28,18 +41,23 @@ describe("HandleLogin", () => {
jest.spyOn(oidcAuth, "Auth").mockResolvedValue(loginUrlMock);
renderWithProviders(<HandleLogin />, {
user: {
userData: null,
tokens: null,
loading: false,
error: null,
},
calendars: {
list: {},
pending: false,
error: null,
},
const { result } = renderHook(() => useInitializeApp(), {
wrapper: ({ children }) => (
<Provider
store={setupStore({
user: {
userData: null,
tokens: null,
loading: false,
error: null,
coreConfig: { language: "en" },
},
calendars: { list: {}, pending: false, error: null },
})}
>
{children}
</Provider>
),
});
await waitFor(
+3
View File
@@ -13,6 +13,7 @@ import { Loading } from "./components/Loading/Loading";
import { AVAILABLE_LANGUAGES } from "./features/Settings/constants";
import HandleLogin from "./features/User/HandleLogin";
import { CallbackResume } from "./features/User/LoginCallback";
import { useInitializeApp } from "./features/User/useInitializeApp";
import { ScreenTooSmall } from "./ScreenTooSmall";
import { WebSocketGate } from "./websocket/WebSocketGate";
@@ -68,6 +69,8 @@ function App() {
() => window.matchMedia(SMALL_SCREEN_QUERY).matches
);
useInitializeApp();
useEffect(() => {
const mediaQuery = window.matchMedia(SMALL_SCREEN_QUERY);
const onChange = (event: MediaQueryListEvent) =>
+16 -1
View File
@@ -126,10 +126,25 @@ const CalendarSlice = createSlice({
}>
) => {
state.pending = false;
state.list = action.payload.importedCalendars;
state.error = action.payload.errors.length
? action.payload.errors
: null;
Object.entries(action.payload.importedCalendars).forEach(
([id, cal]) => {
state.list[id] = {
...cal,
events: state.list[id]?.events || {},
};
}
);
// Remove calendars that no longer exist
Object.keys(state.list).forEach((id) => {
if (!action.payload.importedCalendars[id]) {
delete state.list[id];
}
});
}
)
.addCase(
-46
View File
@@ -1,60 +1,14 @@
import { useAppDispatch, useAppSelector } from "@/app/hooks";
import { redirectTo } from "@/utils/apiUtils";
import { useEffect, useRef } from "react";
import { push } from "redux-first-history";
import { getCalendarsListAsync } from "../Calendars/services";
import { Auth } from "./oidcAuth";
import { getOpenPaasUserDataAsync, setTokens, setUserData } from "./userSlice";
import { setAppLoading } from "@/app/loadingSlice";
export function HandleLogin() {
const userData = useAppSelector((state) => state.user);
const calendars = useAppSelector((state) => state.calendars);
const dispatch = useAppDispatch();
const hasInitiatedRef = useRef(false);
const hasNavigatedRef = useRef(false);
// Initiate login or load saved data
useEffect(() => {
if (hasInitiatedRef.current) return;
if (userData.userData && !calendars.pending) return;
hasInitiatedRef.current = true;
const initiateLogin = async () => {
const savedToken = sessionStorage.getItem("tokenSet")
? JSON.parse(sessionStorage.getItem("tokenSet")!)
: null;
const savedUser = sessionStorage.getItem("userData")
? JSON.parse(sessionStorage.getItem("userData")!)
: null;
if (savedToken && savedUser) {
dispatch(setAppLoading(true));
dispatch(setTokens(savedToken));
dispatch(setUserData(savedUser));
await dispatch(getOpenPaasUserDataAsync());
await dispatch(getCalendarsListAsync());
return;
}
const loginurl = await Auth();
sessionStorage.setItem(
"redirectState",
JSON.stringify({
code_verifier: loginurl.code_verifier,
state: loginurl.state,
})
);
redirectTo(loginurl.redirectTo);
};
initiateLogin();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userData.userData, calendars.list, dispatch]);
// Navigate to /calendar only when all data is ready
useEffect(() => {
if (hasNavigatedRef.current) return;
+61
View File
@@ -0,0 +1,61 @@
import { useAppDispatch, useAppSelector } from "@/app/hooks";
import { setAppLoading } from "@/app/loadingSlice";
import { getCalendarsListAsync } from "@/features/Calendars/services";
import { Auth } from "@/features/User/oidcAuth";
import {
getOpenPaasUserDataAsync,
setTokens,
setUserData,
} from "@/features/User/userSlice";
import { redirectTo } from "@/utils/apiUtils";
import { useEffect, useRef } from "react";
export function useInitializeApp() {
const userData = useAppSelector((state) => state.user);
const calendars = useAppSelector((state) => state.calendars);
const dispatch = useAppDispatch();
const hasInitiatedRef = useRef(false);
useEffect(() => {
if (hasInitiatedRef.current) return;
if (userData.userData && !calendars.pending) return;
if (window.location.pathname === "/callback") return;
hasInitiatedRef.current = true;
const initiateLogin = async () => {
const savedToken = sessionStorage.getItem("tokenSet")
? JSON.parse(sessionStorage.getItem("tokenSet")!)
: null;
const savedUser = sessionStorage.getItem("userData")
? JSON.parse(sessionStorage.getItem("userData")!)
: null;
if (savedToken && savedUser) {
dispatch(setAppLoading(true));
dispatch(setTokens(savedToken));
dispatch(setUserData(savedUser));
try {
await dispatch(getOpenPaasUserDataAsync());
await dispatch(getCalendarsListAsync());
} finally {
dispatch(setAppLoading(false));
}
return;
}
const loginurl = await Auth();
sessionStorage.setItem(
"redirectState",
JSON.stringify({
code_verifier: loginurl.code_verifier,
state: loginurl.state,
})
);
redirectTo(loginurl.redirectTo);
};
initiateLogin();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userData.userData]);
}