* added error snackbar for api fails * improved error page to be shown only for userdata errors and to allow retrying * fixed test breaking only in local because of timezone and reduced warnings for easier reading of tests results * added error management for failure inside calendars imports * added error snackbar for api fails * improved error page to be shown only for userdata errors and to allow retrying * added error management for failure inside calendars imports Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -14,24 +14,24 @@ export async function getEvent(event: CalendarEvent, isMaster?: boolean) {
|
||||
const eventData = await response.text();
|
||||
|
||||
const eventical = ICAL.parse(eventData);
|
||||
|
||||
const vevents = (eventical[2] || []).filter(
|
||||
([name]: [string]) => name.toLowerCase() === "vevent"
|
||||
);
|
||||
let targetVevent;
|
||||
if (isMaster) {
|
||||
// Find master VEVENT (the one without recurrence-id)
|
||||
const vevents = eventical[2].filter(
|
||||
([name]: string[]) => name === "vevent"
|
||||
);
|
||||
|
||||
targetVevent = vevents.find(
|
||||
([, props]: [string, any[]]) =>
|
||||
!props.find(([k]: string[]) => k.toLowerCase() === "recurrence-id")
|
||||
);
|
||||
if (!targetVevent) {
|
||||
// Fallback to first VEVENT if no master found
|
||||
targetVevent = eventical[2][1];
|
||||
targetVevent = vevents[0];
|
||||
}
|
||||
} else {
|
||||
// For non-master, use first VEVENT as before
|
||||
targetVevent = eventical[2][1];
|
||||
targetVevent = vevents[0];
|
||||
}
|
||||
|
||||
const eventjson = parseCalendarEvent(
|
||||
|
||||
@@ -90,7 +90,7 @@ export default function EventDisplayModal({
|
||||
const [end, setEnd] = useState(
|
||||
formatLocalDateTime(new Date(event?.end ?? Date.now()))
|
||||
);
|
||||
const [allday, setAllDay] = useState(event?.allday);
|
||||
const [allday, setAllDay] = useState(event?.allday ?? false);
|
||||
const [repetition, setRepetition] = useState<RepetitionObject>(
|
||||
event?.repetition ?? ({} as RepetitionObject)
|
||||
);
|
||||
@@ -280,7 +280,7 @@ export default function EventDisplayModal({
|
||||
fullWidth
|
||||
disabled={!isOwn}
|
||||
label="Title"
|
||||
value={title}
|
||||
value={title ?? ""}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
size="small"
|
||||
margin="dense"
|
||||
@@ -366,7 +366,7 @@ export default function EventDisplayModal({
|
||||
<Select
|
||||
disabled={!isOwn}
|
||||
labelId="calendar-select-label"
|
||||
value={calendarid.toString()}
|
||||
value={calendarid.toString() ?? ""}
|
||||
label="Calendar"
|
||||
onChange={(e: SelectChangeEvent) => {
|
||||
const newId = Number(e.target.value);
|
||||
|
||||
@@ -47,10 +47,10 @@ export function HandleLogin() {
|
||||
initiateLogin();
|
||||
}, [userData, dispatch]);
|
||||
useEffect(() => {
|
||||
if (!calendars.pending && !userData.loading) {
|
||||
if (userData.error) {
|
||||
dispatch(push("/error"));
|
||||
}
|
||||
if (!calendars.pending && !userData.loading) {
|
||||
if (!calendars.pending && !userData.loading && !userData.error) {
|
||||
dispatch(push("/calendar"));
|
||||
}
|
||||
}, [calendars.pending, userData.loading]);
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||
import { userData, userOrganiser } from "./userDataTypes";
|
||||
import { getOpenPaasUser } from "./userAPI";
|
||||
import { formatReduxError } from "../../utils/errorUtils";
|
||||
|
||||
export const getOpenPaasUserDataAsync = createAsyncThunk<any>(
|
||||
"user/getOpenPaasUserData",
|
||||
async () => {
|
||||
export const getOpenPaasUserDataAsync = createAsyncThunk<
|
||||
Record<string, string>,
|
||||
void,
|
||||
{ rejectValue: { message: string; status?: number } }
|
||||
>("user/getOpenPaasUserData", async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const user = (await getOpenPaasUser()) as Record<string, string>;
|
||||
|
||||
return user;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue({
|
||||
message: formatReduxError(err),
|
||||
status: err.response?.status,
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
export const userSlice = createSlice({
|
||||
name: "user",
|
||||
@@ -18,6 +26,7 @@ export const userSlice = createSlice({
|
||||
organiserData: null as unknown as userOrganiser,
|
||||
tokens: null as unknown as Record<string, string>,
|
||||
loading: true,
|
||||
error: null as unknown as string | null,
|
||||
},
|
||||
reducers: {
|
||||
setUserData: (state, action) => {
|
||||
@@ -32,6 +41,9 @@ export const userSlice = createSlice({
|
||||
setTokens: (state, action) => {
|
||||
state.tokens = action.payload;
|
||||
},
|
||||
clearError: (state) => {
|
||||
state.error = null;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
@@ -54,13 +66,17 @@ export const userSlice = createSlice({
|
||||
.addCase(getOpenPaasUserDataAsync.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
.addCase(getOpenPaasUserDataAsync.rejected, (state) => {
|
||||
state.loading = false;
|
||||
.addCase(getOpenPaasUserDataAsync.rejected, (state, action) => {
|
||||
if (action.payload?.status !== 401) {
|
||||
state.loading = false;
|
||||
state.error =
|
||||
action.payload?.message || "Failed to fetch user information";
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// Action creators are generated for each case reducer function
|
||||
export const { setUserData, setTokens } = userSlice.actions;
|
||||
export const { setUserData, setTokens, clearError } = userSlice.actions;
|
||||
|
||||
export default userSlice.reducer;
|
||||
|
||||
Reference in New Issue
Block a user