* [#337] added timezone loading from backend * [#337] changed data structure to allow patching timezone while keeping other datetime params value * [#337] fixed gap, event create modale sync with settings and added checkbox * [#337] isBrowserDefaultTimezone is set to true when API return null timezone Co-authored-by: Camille Moussu <cmoussu@linagora.com>
This commit is contained in:
@@ -30,6 +30,7 @@ import { User } from "../../components/Attendees/PeopleSearch";
|
||||
import { getCalendarVisibility } from "../../components/Calendar/utils/calendarUtils";
|
||||
import { importFile } from "../../utils/apiUtils";
|
||||
import { formatReduxError } from "../../utils/errorUtils";
|
||||
import { browserDefaultTimeZone } from "../../utils/timezone";
|
||||
|
||||
// Define error type for rejected actions
|
||||
interface RejectedError {
|
||||
@@ -653,13 +654,11 @@ const CalendarSlice = createSlice({
|
||||
templist: {} as Record<string, Calendars>,
|
||||
pending: false,
|
||||
error: null as string | null,
|
||||
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
} as {
|
||||
list: Record<string, Calendars>;
|
||||
templist: Record<string, Calendars>;
|
||||
pending: boolean;
|
||||
error: string | null;
|
||||
timeZone: string;
|
||||
},
|
||||
reducers: {
|
||||
createCalendar: (
|
||||
@@ -720,9 +719,6 @@ const CalendarSlice = createSlice({
|
||||
const { calId, event } = action.payload;
|
||||
state.list[calId].events[event.uid] = event;
|
||||
},
|
||||
setTimeZone: (state, action: PayloadAction<string>) => {
|
||||
state.timeZone = action.payload;
|
||||
},
|
||||
clearFetchCache: (state, action: PayloadAction<string>) => {
|
||||
if (!state.list[action.payload]) return;
|
||||
state.list[action.payload].lastCacheCleared = Date.now();
|
||||
@@ -795,7 +791,7 @@ const CalendarSlice = createSlice({
|
||||
action.payload.calId;
|
||||
if (!state[type][action.payload.calId].events[id].timezone) {
|
||||
state[type][action.payload.calId].events[id].timezone =
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
browserDefaultTimeZone;
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -831,7 +827,7 @@ const CalendarSlice = createSlice({
|
||||
action.payload.calId;
|
||||
if (!state[type][action.payload.calId].events[id].timezone) {
|
||||
state[type][action.payload.calId].events[id].timezone =
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
browserDefaultTimeZone;
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -878,7 +874,7 @@ const CalendarSlice = createSlice({
|
||||
action.payload.calId;
|
||||
if (!state.list[action.payload.calId].events[id].timezone) {
|
||||
state.list[action.payload.calId].events[id].timezone =
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
browserDefaultTimeZone;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1172,7 +1168,6 @@ export const {
|
||||
updateEventLocal,
|
||||
removeTempCal,
|
||||
emptyEventsCal,
|
||||
setTimeZone,
|
||||
clearFetchCache,
|
||||
clearError,
|
||||
updateCalColor,
|
||||
|
||||
@@ -49,6 +49,7 @@ import { CalendarEvent } from "./EventsTypes";
|
||||
import EventUpdateModal from "./EventUpdateModal";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
import { userAttendee } from "../User/userDataTypes";
|
||||
import { browserDefaultTimeZone } from "../../utils/timezone";
|
||||
|
||||
export default function EventPreviewModal({
|
||||
eventId,
|
||||
@@ -67,8 +68,8 @@ export default function EventPreviewModal({
|
||||
const dispatch = useAppDispatch();
|
||||
const calendars = useAppSelector((state) => state.calendars);
|
||||
const timezone =
|
||||
useAppSelector((state) => state.calendars.timeZone) ??
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
useAppSelector((state) => state.settings.timeZone) ??
|
||||
browserDefaultTimeZone;
|
||||
const calendarList = Object.values(
|
||||
useAppSelector((state) => state.calendars.list)
|
||||
);
|
||||
@@ -456,10 +457,10 @@ export default function EventPreviewModal({
|
||||
)}
|
||||
</Box>
|
||||
<Typography color="text.secondaryContainer" gutterBottom>
|
||||
{formatDate(event.start, t, event.allday)}
|
||||
{formatDate(event.start, t, timezone, event.allday)}
|
||||
{event.end &&
|
||||
formatEnd(event.start, event.end, t, event.allday) &&
|
||||
` – ${formatEnd(event.start, event.end, t, event.allday)} ${!event.allday ? getTimezoneOffset(timezone, new Date(event.start)) : ""}`}
|
||||
formatEnd(event.start, event.end, t, timezone, event.allday) &&
|
||||
` – ${formatEnd(event.start, event.end, t, timezone, event.allday)} ${!event.allday ? getTimezoneOffset(timezone, new Date(event.start)) : ""}`}
|
||||
</Typography>
|
||||
</>
|
||||
}
|
||||
@@ -952,13 +953,19 @@ function makeRecurrenceString(
|
||||
return recur.join(", ");
|
||||
}
|
||||
|
||||
function formatDate(date: Date | string, t: Function, allday?: boolean) {
|
||||
function formatDate(
|
||||
date: Date | string,
|
||||
t: Function,
|
||||
timeZone: string,
|
||||
allday?: boolean
|
||||
) {
|
||||
if (allday) {
|
||||
return new Date(date).toLocaleDateString(t("locale"), {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
weekday: "long",
|
||||
day: "numeric",
|
||||
timeZone,
|
||||
});
|
||||
} else {
|
||||
return new Date(date).toLocaleString(t("locale"), {
|
||||
@@ -968,6 +975,7 @@ function formatDate(date: Date | string, t: Function, allday?: boolean) {
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
timeZone,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -976,6 +984,7 @@ function formatEnd(
|
||||
start: Date | string,
|
||||
end: Date | string,
|
||||
t: Function,
|
||||
timeZone: string,
|
||||
allday?: boolean
|
||||
) {
|
||||
const startDate = new Date(start);
|
||||
@@ -993,12 +1002,14 @@ function formatEnd(
|
||||
year: "numeric",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
timeZone,
|
||||
});
|
||||
} else {
|
||||
if (sameDay) {
|
||||
return endDate.toLocaleTimeString(t("locale"), {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
timeZone,
|
||||
});
|
||||
}
|
||||
return endDate.toLocaleString(t("locale"), {
|
||||
@@ -1007,6 +1018,7 @@ function formatEnd(
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
timeZone,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
restoreFormDataFromTemp,
|
||||
EventFormState,
|
||||
} from "../../utils/eventFormTempStorage";
|
||||
import { browserDefaultTimeZone } from "../../utils/timezone";
|
||||
|
||||
function EventPopover({
|
||||
open,
|
||||
@@ -84,17 +85,14 @@ function EventPopover({
|
||||
|
||||
const timezoneList = useMemo(() => {
|
||||
const zones = Object.keys(TIMEZONES.zones).sort();
|
||||
const browserTz = resolveTimezone(
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
);
|
||||
const browserTz = resolveTimezone(browserDefaultTimeZone);
|
||||
|
||||
return { zones, browserTz, getTimezoneOffset };
|
||||
}, []);
|
||||
|
||||
const calendarTimezone = useAppSelector((state) => state.calendars.timeZone);
|
||||
const calendarTimezone = useAppSelector((state) => state.settings.timeZone);
|
||||
const resolvedCalendarTimezone = useMemo(() => {
|
||||
const tz =
|
||||
calendarTimezone || Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
const tz = calendarTimezone || browserDefaultTimeZone;
|
||||
return resolveTimezone(tz);
|
||||
}, [calendarTimezone]);
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
EventFormState,
|
||||
EventFormContext,
|
||||
} from "../../utils/eventFormTempStorage";
|
||||
import { browserDefaultTimeZone } from "../../utils/timezone";
|
||||
|
||||
function EventUpdateModal({
|
||||
eventId,
|
||||
@@ -114,9 +115,7 @@ function EventUpdateModal({
|
||||
|
||||
const timezoneList = useMemo(() => {
|
||||
const zones = Object.keys(TIMEZONES.zones).sort();
|
||||
const browserTz = resolveTimezone(
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
);
|
||||
const browserTz = resolveTimezone(browserDefaultTimeZone);
|
||||
|
||||
const getTimezoneOffset = (tzName: string): string => {
|
||||
const resolvedTz = resolveTimezone(tzName);
|
||||
@@ -161,7 +160,7 @@ function EventUpdateModal({
|
||||
const [busy, setBusy] = useState("OPAQUE");
|
||||
const [eventClass, setEventClass] = useState("PUBLIC");
|
||||
const [timezone, setTimezone] = useState(
|
||||
resolveTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone)
|
||||
resolveTimezone(browserDefaultTimeZone)
|
||||
);
|
||||
const [newCalId, setNewCalId] = useState(calId);
|
||||
const defaultCalendarId = useMemo(
|
||||
@@ -196,9 +195,7 @@ function EventUpdateModal({
|
||||
setAlarm("");
|
||||
setEventClass("PUBLIC");
|
||||
setBusy("OPAQUE");
|
||||
setTimezone(
|
||||
resolveTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone)
|
||||
);
|
||||
setTimezone(resolveTimezone(browserDefaultTimeZone));
|
||||
setHasVideoConference(false);
|
||||
setMeetingLink(null);
|
||||
}, [defaultCalendarId]);
|
||||
@@ -230,7 +227,7 @@ function EventUpdateModal({
|
||||
// Get event's original timezone
|
||||
const eventTimezone = event.timezone
|
||||
? resolveTimezone(event.timezone)
|
||||
: resolveTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
: resolveTimezone(browserDefaultTimeZone);
|
||||
|
||||
// Format dates based on all-day status
|
||||
if (event.start) {
|
||||
@@ -298,9 +295,7 @@ function EventUpdateModal({
|
||||
const resolvedTimezone = resolveTimezone(event.timezone);
|
||||
setTimezone(resolvedTimezone);
|
||||
} else {
|
||||
const browserTz = resolveTimezone(
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
);
|
||||
const browserTz = resolveTimezone(browserDefaultTimeZone);
|
||||
setTimezone(browserTz);
|
||||
}
|
||||
setHasVideoConference(event.x_openpass_videoconference ? true : false);
|
||||
|
||||
@@ -4,6 +4,7 @@ import { CalendarEvent, RepetitionObject } from "./EventsTypes";
|
||||
import { userAttendee } from "../User/userDataTypes";
|
||||
import { formatDateTimeInTimezone } from "../../components/Event/utils/dateTimeFormatters";
|
||||
import { addVideoConferenceToDescription } from "../../utils/videoConferenceUtils";
|
||||
import { browserDefaultTimeZone } from "../../utils/timezone";
|
||||
|
||||
export interface TimezoneListResult {
|
||||
zones: string[];
|
||||
@@ -13,9 +14,7 @@ export interface TimezoneListResult {
|
||||
|
||||
export function createTimezoneList(): TimezoneListResult {
|
||||
const zones = Object.keys(TIMEZONES.zones).sort();
|
||||
const browserTz = resolveTimezone(
|
||||
Intl.DateTimeFormat().resolvedOptions().timeZone
|
||||
);
|
||||
const browserTz = resolveTimezone(browserDefaultTimeZone);
|
||||
|
||||
const getTimezoneOffset = (tzName: string): string => {
|
||||
const resolvedTz = resolveTimezone(tzName);
|
||||
@@ -101,8 +100,7 @@ export function populateFormFromEvent(
|
||||
// Get event's timezone for formatting
|
||||
const eventTimezone = event.timezone
|
||||
? resolveTimezone(event.timezone)
|
||||
: calendarTimezone ||
|
||||
resolveTimezone(Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
: calendarTimezone || resolveTimezone(browserDefaultTimeZone);
|
||||
|
||||
// Format dates based on all-day status and timezone
|
||||
if (event.start) {
|
||||
|
||||
@@ -13,19 +13,33 @@ import {
|
||||
MenuItem,
|
||||
Typography,
|
||||
Snackbar,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
} from "@mui/material";
|
||||
import ArrowBackIcon from "@mui/icons-material/ArrowBack";
|
||||
import SettingsIcon from "@mui/icons-material/Settings";
|
||||
import SyncIcon from "@mui/icons-material/Sync";
|
||||
// import SyncIcon from "@mui/icons-material/Sync";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import { setView, setLanguage as setSettingsLanguage } from "./SettingsSlice";
|
||||
import {
|
||||
setView,
|
||||
setLanguage as setSettingsLanguage,
|
||||
setTimeZone as setSettingsTimeZone,
|
||||
setIsBrowserDefaultTimeZone,
|
||||
} from "./SettingsSlice";
|
||||
import {
|
||||
updateUserConfigurationsAsync,
|
||||
setLanguage as setUserLanguage,
|
||||
setTimezone as setUserTimeZone,
|
||||
} from "../User/userSlice";
|
||||
import { AVAILABLE_LANGUAGES } from "./constants";
|
||||
import { useI18n } from "cozy-ui/transpiled/react/providers/I18n";
|
||||
import "./SettingsPage.styl";
|
||||
import {
|
||||
useTimeZoneList,
|
||||
getTimezoneOffset,
|
||||
} from "../../components/Calendar/TimezoneSelector";
|
||||
import { TimezoneAutocomplete } from "../../components/Timezone/TimezoneAutocomplete";
|
||||
import { browserDefaultTimeZone } from "../../utils/timezone";
|
||||
|
||||
type SidebarNavItem = "settings" | "sync";
|
||||
type SettingsSubTab = "settings" | "notifications";
|
||||
@@ -33,14 +47,30 @@ type SettingsSubTab = "settings" | "notifications";
|
||||
export default function SettingsPage() {
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useI18n();
|
||||
const userLanguage = useAppSelector((state) => state.user?.language);
|
||||
// const previousConfig = useAppSelector((state) => state.user.coreConfig);
|
||||
const userLanguage = useAppSelector(
|
||||
(state) => state.user?.coreConfig.language
|
||||
);
|
||||
const settingsLanguage = useAppSelector((state) => state.settings?.language);
|
||||
const currentLanguage = userLanguage || settingsLanguage || "en";
|
||||
|
||||
const timezoneList = useTimeZoneList();
|
||||
const userTimeZone = useAppSelector(
|
||||
(state) => state.user?.coreConfig?.datetime?.timeZone
|
||||
);
|
||||
const settingTimeZone = useAppSelector((state) => state.settings?.timeZone);
|
||||
const currentTimeZone =
|
||||
userTimeZone ?? settingTimeZone ?? browserDefaultTimeZone;
|
||||
const isBrowserDefault = useAppSelector(
|
||||
(state) => state.settings.isBrowserDefaultTimeZone
|
||||
);
|
||||
|
||||
const [activeNavItem, setActiveNavItem] =
|
||||
useState<SidebarNavItem>("settings");
|
||||
const [activeSettingsSubTab, setActiveSettingsSubTab] =
|
||||
useState<SettingsSubTab>("settings");
|
||||
const [languageErrorOpen, setLanguageErrorOpen] = useState(false);
|
||||
const [timeZoneErrorOpen, setTimeZoneErrorOpen] = useState(false);
|
||||
|
||||
const handleBackClick = () => {
|
||||
dispatch(setView("calendar"));
|
||||
@@ -84,6 +114,53 @@ export default function SettingsPage() {
|
||||
setLanguageErrorOpen(false);
|
||||
};
|
||||
|
||||
const handleTimeZoneChange = (newTimeZone: string) => {
|
||||
// const previousTimeZone = currentTimeZone;
|
||||
|
||||
// Optimistic update - update UI immediately
|
||||
dispatch(setUserTimeZone(newTimeZone));
|
||||
dispatch(setSettingsTimeZone(newTimeZone));
|
||||
|
||||
// // Call API in background, don't wait for it
|
||||
// dispatch(
|
||||
// updateUserConfigurationsAsync({ timezone: newTimeZone, previousConfig })
|
||||
// )
|
||||
// .unwrap()
|
||||
// .catch((error) => {
|
||||
// console.error("Failed to update TimeZone:", error);
|
||||
// // Rollback on error
|
||||
// dispatch(setUserTimeZone(previousTimeZone));
|
||||
// dispatch(setSettingsTimeZone(previousTimeZone));
|
||||
// setTimeZoneErrorOpen(true);
|
||||
// });
|
||||
};
|
||||
|
||||
const handleTimeZoneDefaultChange = (isDefault: boolean) => {
|
||||
// Optimistic update - update UI immediately
|
||||
dispatch(setIsBrowserDefaultTimeZone(isDefault));
|
||||
if (isDefault) {
|
||||
dispatch(setUserTimeZone(null));
|
||||
dispatch(setSettingsTimeZone(browserDefaultTimeZone));
|
||||
}
|
||||
|
||||
// // Call API in background, don't wait for it
|
||||
// dispatch(
|
||||
// updateUserConfigurationsAsync({ timezone: newTimeZone, previousConfig })
|
||||
// )
|
||||
// .unwrap()
|
||||
// .catch((error) => {
|
||||
// console.error("Failed to update TimeZone:", error);
|
||||
// // Rollback on error
|
||||
// dispatch(setUserTimeZone(previousTimeZone));
|
||||
// dispatch(setSettingsTimeZone(previousTimeZone));
|
||||
// setTimeZoneErrorOpen(true);
|
||||
// });
|
||||
};
|
||||
|
||||
const handleTimeZoneErrorClose = () => {
|
||||
setTimeZoneErrorOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="main-layout settings-layout">
|
||||
<Box className="settings-sidebar">
|
||||
@@ -138,33 +215,75 @@ export default function SettingsPage() {
|
||||
<>
|
||||
{activeSettingsSubTab === "settings" && (
|
||||
<Box className="settings-tab-content">
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
{t("settings.language") || "Language"}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ mb: 3 }}
|
||||
>
|
||||
{t("settings.languageDescription") ||
|
||||
"This will be the language used in your Twake Calendar"}
|
||||
</Typography>
|
||||
<FormControl size="small" sx={{ minWidth: 500 }}>
|
||||
<Select
|
||||
value={currentLanguage}
|
||||
onChange={handleLanguageChange}
|
||||
variant="outlined"
|
||||
aria-label={
|
||||
t("settings.languageSelector") || "Language selector"
|
||||
}
|
||||
<Box sx={{ mb: 6 }}>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
{t("settings.language") || "Language"}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
sx={{ mb: 3 }}
|
||||
>
|
||||
{AVAILABLE_LANGUAGES.map(({ code, label }) => (
|
||||
<MenuItem key={code} value={code}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
{t("settings.languageDescription") ||
|
||||
"This will be the language used in your Twake Calendar"}
|
||||
</Typography>
|
||||
<FormControl size="small" sx={{ minWidth: 500 }}>
|
||||
<Select
|
||||
value={currentLanguage}
|
||||
onChange={handleLanguageChange}
|
||||
variant="outlined"
|
||||
aria-label={
|
||||
t("settings.languageSelector") || "Language selector"
|
||||
}
|
||||
>
|
||||
{AVAILABLE_LANGUAGES.map(({ code, label }) => (
|
||||
<MenuItem key={code} value={code}>
|
||||
{label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ mb: 1 }}>
|
||||
{t("settings.timeZone")}
|
||||
</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
mb: 6,
|
||||
}}
|
||||
>
|
||||
<FormControl size="small" sx={{ minWidth: 500 }}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={isBrowserDefault}
|
||||
onChange={() =>
|
||||
handleTimeZoneDefaultChange(!isBrowserDefault)
|
||||
}
|
||||
aria-label={t("settings.timeZoneBrowserDefault")}
|
||||
/>
|
||||
}
|
||||
label={t("settings.timeZoneBrowserDefault")}
|
||||
labelPlacement="start"
|
||||
sx={{
|
||||
minWidth: 400,
|
||||
justifyContent: "space-between",
|
||||
marginLeft: 0,
|
||||
mb: 2,
|
||||
}}
|
||||
/>
|
||||
{!isBrowserDefault && (
|
||||
<TimezoneAutocomplete
|
||||
value={currentTimeZone}
|
||||
zones={timezoneList.zones}
|
||||
getTimezoneOffset={getTimezoneOffset}
|
||||
onChange={handleTimeZoneChange}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
{activeSettingsSubTab === "notifications" && (
|
||||
@@ -194,6 +313,12 @@ export default function SettingsPage() {
|
||||
t("settings.languageUpdateError") || "Failed to update language"
|
||||
}
|
||||
/>
|
||||
<Snackbar
|
||||
open={timeZoneErrorOpen}
|
||||
autoHideDuration={4000}
|
||||
onClose={handleTimeZoneErrorClose}
|
||||
message={t("settings.timeZoneUpdateError")}
|
||||
/>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,26 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { browserDefaultTimeZone } from "../../utils/timezone";
|
||||
import { getOpenPaasUserDataAsync } from "../User/userSlice";
|
||||
|
||||
export interface SettingsState {
|
||||
language: string;
|
||||
timeZone: string | null; // Allow null to represent browser default
|
||||
isBrowserDefaultTimeZone: boolean;
|
||||
view: "calendar" | "settings" | "search";
|
||||
}
|
||||
|
||||
const savedLang = localStorage.getItem("lang");
|
||||
const defaultLang = savedLang ?? (window as any).LANG ?? "en";
|
||||
|
||||
const savedTimeZone = localStorage.getItem("timeZone");
|
||||
// If savedTimeZone is the string "null" or doesn't exist, use null
|
||||
const defaultTimeZone =
|
||||
savedTimeZone === "null" || !savedTimeZone ? null : savedTimeZone;
|
||||
|
||||
const initialState: SettingsState = {
|
||||
language: defaultLang,
|
||||
timeZone: defaultTimeZone,
|
||||
isBrowserDefaultTimeZone: defaultTimeZone === null,
|
||||
view: "calendar",
|
||||
};
|
||||
|
||||
@@ -21,6 +32,13 @@ export const settingsSlice = createSlice({
|
||||
state.language = action.payload;
|
||||
localStorage.setItem("lang", action.payload);
|
||||
},
|
||||
setTimeZone: (state, action: PayloadAction<string>) => {
|
||||
state.timeZone = action.payload;
|
||||
localStorage.setItem("timeZone", action.payload);
|
||||
},
|
||||
setIsBrowserDefaultTimeZone: (state, action: PayloadAction<boolean>) => {
|
||||
state.isBrowserDefaultTimeZone = action.payload;
|
||||
},
|
||||
setView: (
|
||||
state,
|
||||
action: PayloadAction<"calendar" | "settings" | "search">
|
||||
@@ -28,7 +46,33 @@ export const settingsSlice = createSlice({
|
||||
state.view = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(getOpenPaasUserDataAsync.fulfilled, (state, action) => {
|
||||
const coreModule = action.payload.configurations?.modules?.find(
|
||||
(module: any) => module.name === "core"
|
||||
);
|
||||
const datetimeConfig = coreModule?.configurations?.find(
|
||||
(config: any) => config.name === "datetime"
|
||||
);
|
||||
const timeZone = datetimeConfig?.value?.timeZone;
|
||||
|
||||
if (timeZone) {
|
||||
state.timeZone = timeZone;
|
||||
state.isBrowserDefaultTimeZone = false;
|
||||
localStorage.setItem("timeZone", timeZone);
|
||||
} else {
|
||||
state.timeZone = browserDefaultTimeZone;
|
||||
state.isBrowserDefaultTimeZone = true;
|
||||
localStorage.setItem("timeZone", browserDefaultTimeZone);
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { setLanguage, setView } = settingsSlice.actions;
|
||||
export const {
|
||||
setLanguage,
|
||||
setTimeZone,
|
||||
setView,
|
||||
setIsBrowserDefaultTimeZone,
|
||||
} = settingsSlice.actions;
|
||||
export default settingsSlice.reducer;
|
||||
|
||||
@@ -2,8 +2,8 @@ import { User } from "../../components/Attendees/PeopleSearch";
|
||||
import { api } from "../../utils/apiUtils";
|
||||
|
||||
export async function getOpenPaasUser() {
|
||||
const user = await api.get(`api/user`).json();
|
||||
return user;
|
||||
const user = await api.get(`api/user`);
|
||||
return user.json();
|
||||
}
|
||||
|
||||
export async function searchUsers(
|
||||
@@ -38,6 +38,7 @@ export interface UserConfigurationUpdates {
|
||||
language?: string;
|
||||
notifications?: Record<string, unknown>;
|
||||
timezone?: string;
|
||||
previousConfig?: Record<string, any>;
|
||||
}
|
||||
|
||||
export async function updateUserConfigurations(
|
||||
@@ -52,7 +53,13 @@ export async function updateUserConfigurations(
|
||||
coreConfigs.push({ name: "notifications", value: updates.notifications });
|
||||
}
|
||||
if (updates.timezone !== undefined) {
|
||||
coreConfigs.push({ name: "timezone", value: updates.timezone });
|
||||
coreConfigs.push({
|
||||
name: "datetime",
|
||||
value: {
|
||||
...updates.previousConfig?.datetime,
|
||||
timeZone: updates.timezone,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (coreConfigs.length === 0) {
|
||||
|
||||
@@ -7,6 +7,7 @@ export interface userData {
|
||||
sub: string;
|
||||
openpaasId?: string;
|
||||
language?: string;
|
||||
timezone?: string | null;
|
||||
}
|
||||
|
||||
export interface UserConfigurations {
|
||||
|
||||
@@ -45,7 +45,12 @@ export const userSlice = createSlice({
|
||||
userData: null as unknown as userData,
|
||||
organiserData: null as unknown as userOrganiser,
|
||||
tokens: null as unknown as Record<string, string>,
|
||||
language: null as string | null,
|
||||
coreConfig: {
|
||||
language: null as string | null,
|
||||
datetime: {
|
||||
timeZone: null as string | null,
|
||||
},
|
||||
} as Record<string, any>,
|
||||
loading: true,
|
||||
error: null as unknown as string | null,
|
||||
},
|
||||
@@ -63,11 +68,20 @@ export const userSlice = createSlice({
|
||||
state.tokens = action.payload;
|
||||
},
|
||||
setLanguage: (state, action) => {
|
||||
state.language = action.payload;
|
||||
state.coreConfig.language = action.payload;
|
||||
if (state.userData) {
|
||||
state.userData.language = action.payload;
|
||||
}
|
||||
},
|
||||
setTimezone: (state, action) => {
|
||||
if (!state.coreConfig.datetime) {
|
||||
state.coreConfig.datetime = {};
|
||||
}
|
||||
state.coreConfig.datetime.timeZone = action.payload;
|
||||
if (state.userData) {
|
||||
state.userData.timezone = action.payload;
|
||||
}
|
||||
},
|
||||
clearError: (state) => {
|
||||
state.error = null;
|
||||
},
|
||||
@@ -90,18 +104,53 @@ export const userSlice = createSlice({
|
||||
state.userData.email = action.payload.preferredEmail;
|
||||
}
|
||||
|
||||
// Extract language from configurations.modules
|
||||
// Extract data from configurations.modules
|
||||
if (action.payload.configurations?.modules) {
|
||||
const coreModule = action.payload.configurations.modules.find(
|
||||
(module: any) => module.name === "core"
|
||||
);
|
||||
if (coreModule?.configurations) {
|
||||
const newCoreConfig = Object.fromEntries(
|
||||
coreModule.configurations.map(
|
||||
(e: { name: string; value: any }) => [e.name, e.value]
|
||||
)
|
||||
);
|
||||
|
||||
state.coreConfig = {
|
||||
...state.coreConfig,
|
||||
...newCoreConfig,
|
||||
};
|
||||
const languageConfig = coreModule.configurations.find(
|
||||
(config: any) => config.name === "language"
|
||||
);
|
||||
if (languageConfig?.value) {
|
||||
state.language = languageConfig.value;
|
||||
state.userData.language = languageConfig.value;
|
||||
state.coreConfig.language = languageConfig.value;
|
||||
if (state.userData)
|
||||
state.userData.language = languageConfig.value;
|
||||
}
|
||||
|
||||
const datetimeConfig = coreModule.configurations.find(
|
||||
(config: any) => config.name === "datetime"
|
||||
);
|
||||
if (datetimeConfig?.value) {
|
||||
const serverTimeZone = datetimeConfig.value.timeZone;
|
||||
state.coreConfig.datetime = {
|
||||
...state.coreConfig.datetime,
|
||||
...datetimeConfig.value,
|
||||
timeZone: serverTimeZone !== undefined ? serverTimeZone : null,
|
||||
};
|
||||
if (state.userData) {
|
||||
state.userData.timezone =
|
||||
serverTimeZone !== undefined ? serverTimeZone : null;
|
||||
}
|
||||
} else {
|
||||
state.coreConfig.datetime = {
|
||||
...state.coreConfig.datetime,
|
||||
timeZone: null,
|
||||
};
|
||||
if (state.userData) {
|
||||
state.userData.timezone = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,11 +167,20 @@ export const userSlice = createSlice({
|
||||
})
|
||||
.addCase(updateUserConfigurationsAsync.fulfilled, (state, action) => {
|
||||
if (action.payload.language !== undefined) {
|
||||
state.language = action.payload.language;
|
||||
state.coreConfig.language = action.payload.language;
|
||||
if (state.userData) {
|
||||
state.userData.language = action.payload.language;
|
||||
}
|
||||
}
|
||||
if (action.payload.timezone !== undefined) {
|
||||
if (!state.coreConfig.datetime) {
|
||||
state.coreConfig.datetime = {};
|
||||
}
|
||||
state.coreConfig.datetime.timeZone = action.payload.timezone;
|
||||
if (state.userData) {
|
||||
state.userData.timezone = action.payload.timezone;
|
||||
}
|
||||
}
|
||||
})
|
||||
.addCase(updateUserConfigurationsAsync.rejected, (state, action) => {
|
||||
if (action.payload?.status !== 401) {
|
||||
@@ -134,7 +192,7 @@ export const userSlice = createSlice({
|
||||
});
|
||||
|
||||
// Action creators are generated for each case reducer function
|
||||
export const { setUserData, setTokens, setLanguage, clearError } =
|
||||
export const { setUserData, setTokens, setLanguage, setTimezone, clearError } =
|
||||
userSlice.actions;
|
||||
|
||||
export default userSlice.reducer;
|
||||
|
||||
Reference in New Issue
Block a user