Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: discard changes of timezone, locale and dark mode reset initial … #346

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,33 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';

import { Select, SelectItem, SingleSelectionOnChange, Text } from '@zextras/carbonio-design-system';
import { find } from 'lodash';

import type { AddMod, DarkReaderPropValues, RemoveMod } from '../../../../types';
import { ThemeCallbacksContext } from '../../../boot/theme-provider';
import { DARK_READER_PROP_KEY, SHELL_APP_ID } from '../../../constants';
import {
isDarkReaderPropValues,
useDarkReaderResultValue
} from '../../../dark-mode/use-dark-reader-result-value';
import { getT } from '../../../store/i18n';
import { useReset } from '../../hooks/use-reset';
import { SettingsSectionProps } from '../utils';

type DarkReaderSelectItem = Array<SelectItem & { value: DarkReaderPropValues }>;

interface DarkThemeSettingSectionProps {
interface DarkThemeSettingSectionProps extends SettingsSectionProps {
addMod: AddMod;
removeMod: RemoveMod;
}

const DarkThemeSettingSection = ({
addMod,
removeMod
removeMod,
resetRef
}: DarkThemeSettingSectionProps): React.JSX.Element | null => {
const { setDarkReaderState } = useContext(ThemeCallbacksContext);
const darkReaderResultValue = useDarkReaderResultValue();
const [selection, setSelection] = useState<SelectItem>();

Expand Down Expand Up @@ -58,9 +59,8 @@ const DarkThemeSettingSection = ({
if (item) {
setSelection(item);
}
setDarkReaderState(value);
},
[items, setDarkReaderState]
[items]
);

const onSelectionChange = useCallback<SingleSelectionOnChange>(
Expand All @@ -85,6 +85,14 @@ const DarkThemeSettingSection = ({
}
}, [darkReaderResultValue, items, setSelectNewValue]);

const init = useCallback(() => {
if (darkReaderResultValue) {
setSelectNewValue(darkReaderResultValue);
}
}, [darkReaderResultValue, setSelectNewValue]);

useReset(resetRef, init);

if (!selection) {
return null;
}
Expand Down
102 changes: 102 additions & 0 deletions src/settings/general-settings.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* SPDX-FileCopyrightText: 2023 Zextras <https://www.zextras.com>
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react';

import 'jest-styled-components';
import { screen, within } from '@testing-library/react';
import { find } from 'lodash';

import {
LocaleDescriptorWithLabels,
localeList,
TimeZoneDescriptor,
timeZoneList
} from './components/utils';
import GeneralSettings from './general-settings';
import { useAccountStore } from '../store/account';
import { useI18nStore } from '../store/i18n';
import { TESTID_SELECTORS } from '../test/constants';
import { setup } from '../test/utils';

describe('General setting', () => {
const { defaultI18n } = useI18nStore.getState();
const timeZoneArray = timeZoneList(defaultI18n.t);
const localeArray = localeList(defaultI18n.t);
test('When timezone is changed, discard button become enabled and when clicked the initial value is restored', async () => {
const zimbraPrefTimeZoneIdValue = 'UTC';

useAccountStore.setState((previousState) => ({
...previousState,
settings: {
...previousState.settings,
prefs: { zimbraPrefTimeZoneId: zimbraPrefTimeZoneIdValue }
}
}));
const { user } = setup(<GeneralSettings />);
const match = find(
timeZoneArray,
(item) => item.value === zimbraPrefTimeZoneIdValue
) as TimeZoneDescriptor;
expect(match).toBeDefined();
expect(screen.getByText(match.label)).toBeVisible();
expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled();
await user.click(screen.getByText(match.label));
await user.click(
within(screen.getByTestId(TESTID_SELECTORS.dropdown)).getByText(timeZoneArray[0].label)
);
expect(screen.getByRole('button', { name: /discard changes/i })).toBeEnabled();
await user.click(screen.getByRole('button', { name: /discard changes/i }));
expect(screen.getByText(match.label)).toBeVisible();
expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled();
});

test('When locale is changed, discard button become enabled and when clicked the initial value is restored', async () => {
const zimbraPrefLocaleValue = 'en';

useAccountStore.setState((previousState) => ({
...previousState,
settings: {
...previousState.settings,
prefs: { zimbraPrefLocale: zimbraPrefLocaleValue }
}
}));
const { user } = setup(<GeneralSettings />);
const match = find(
localeArray,
(item) => item.value === zimbraPrefLocaleValue
) as LocaleDescriptorWithLabels;
expect(match).toBeDefined();
expect(screen.getByText(match.label)).toBeVisible();
expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled();
await user.click(screen.getByText(match.label));
await user.click(
within(screen.getByTestId(TESTID_SELECTORS.dropdown)).getByText(localeArray[0].label)
);
expect(screen.getByRole('button', { name: /discard changes/i })).toBeEnabled();
await user.click(screen.getByRole('button', { name: /discard changes/i }));
expect(screen.getByText(match.label)).toBeVisible();
expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled();
});

test('When dark mode is changed, discard button become enabled and when clicked the initial value is restored', async () => {
useAccountStore.setState((previousState) => ({
...previousState,
settings: {
...previousState.settings,
props: [{ name: 'zappDarkreaderMode', zimlet: 'carbonio-shell-ui', _content: 'auto' }]
}
}));
const { user } = setup(<GeneralSettings />);
expect(screen.getByText('Auto')).toBeVisible();
expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled();
await user.click(screen.getByText('Auto'));
await user.click(within(screen.getByTestId(TESTID_SELECTORS.dropdown)).getByText(/disabled/i));
expect(screen.getByRole('button', { name: /discard changes/i })).toBeEnabled();
await user.click(screen.getByRole('button', { name: /discard changes/i }));
expect(screen.getByText('Auto')).toBeVisible();
expect(screen.getByRole('button', { name: /discard changes/i })).toBeDisabled();
});
});
13 changes: 11 additions & 2 deletions src/settings/general-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { SearchSettings } from './components/general-settings/search-settings';
import UserQuota from './components/general-settings/user-quota';
import SettingsHeader, { SettingsHeaderProps } from './components/settings-header';
import { ResetComponentImperativeHandler } from './components/utils';
import LanguageAndTimeZoneSettings from './language-and-timezone-settings';
import { LanguageAndTimeZoneSettings } from './language-and-timezone-settings';
import {
AccountState,
AddMod,
Expand Down Expand Up @@ -215,6 +215,8 @@ const GeneralSettings = (): React.JSX.Element => {
}, [mods, setLocalStorageSettings, createSnackbar, t]);

const scalingSettingSectionRef = useRef<ResetComponentImperativeHandler>(null);
const darkThemeSettingSectionRef = useRef<ResetComponentImperativeHandler>(null);
const languageAndTimeZoneSettingsSectionRef = useRef<ResetComponentImperativeHandler>(null);
const outOfOfficeSettingsSectionRef = useRef<ResetComponentImperativeHandler>(null);
const searchSettingsSectionRef = useRef<ResetComponentImperativeHandler>(null);

Expand All @@ -223,6 +225,8 @@ const GeneralSettings = (): React.JSX.Element => {
if (size(localStorageUnAppliedChanges) > 0) {
scalingSettingSectionRef.current?.reset();
}
darkThemeSettingSectionRef.current?.reset();
languageAndTimeZoneSettingsSectionRef.current?.reset();
outOfOfficeSettingsSectionRef.current?.reset();
searchSettingsSectionRef?.current?.reset();
}, [localStorageUnAppliedChanges]);
Expand Down Expand Up @@ -252,13 +256,18 @@ const GeneralSettings = (): React.JSX.Element => {
addLocalStoreChange={addLocalStoreChange}
cleanLocalStoreChange={cleanLocalStoreChange}
/>
<DarkThemeSettingSection addMod={addMod} removeMod={removeMod} />
<DarkThemeSettingSection
resetRef={darkThemeSettingSectionRef}
addMod={addMod}
removeMod={removeMod}
/>
</AppearanceSettings>
<LanguageAndTimeZoneSettings
settings={userSettings}
addMod={addMod}
open={open}
setOpen={setOpen}
resetRef={languageAndTimeZoneSettingsSectionRef}
/>

<OutOfOfficeSettings
Expand Down
118 changes: 67 additions & 51 deletions src/settings/language-and-timezone-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,95 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/

import React, { FC, useCallback, useMemo } from 'react';
import React, { useCallback, useMemo, useState } from 'react';

import {
Container,
FormSubSection,
Modal,
Padding,
Select,
SelectItem,
SingleSelectionOnChange,
Text
} from '@zextras/carbonio-design-system';
import { find } from 'lodash';

import { localeList, timeZoneList } from './components/utils';
import {
localeList,
SettingsSectionProps,
timeZoneList,
upsertPrefOnUnsavedChanges
} from './components/utils';
import { timezoneAndLanguageSubSection } from './general-settings-sub-sections';
import { AccountSettings, AddMod, PrefsMods } from '../../types';
import { useReset } from './hooks/use-reset';
import { AccountSettings, AddMod } from '../../types';
import { getT } from '../store/i18n';

const LanguageAndTimeZone: FC<{
interface LanguageAndTimeZoneSettingsProps extends SettingsSectionProps {
settings: AccountSettings;
addMod: AddMod;
open: boolean;
setOpen: (arg: boolean) => void;
addMod: AddMod;
}> = ({ settings, addMod, open, setOpen }) => {
}

export const LanguageAndTimeZoneSettings = ({
settings,
addMod,
open,
setOpen,
resetRef
}: LanguageAndTimeZoneSettingsProps): React.JSX.Element => {
const t = getT();
const locales = useMemo(() => localeList(t), [t]);
const timezones = useMemo(() => timeZoneList(t), [t]);
const sectionTitle = useMemo(() => timezoneAndLanguageSubSection(t), [t]);

const updatePrefs = useCallback(
(prefValue: PrefsMods[keyof PrefsMods], prefKey: keyof PrefsMods) => {
addMod('prefs', prefKey, prefValue);
},
[addMod]
);
const updatePref = useMemo(() => upsertPrefOnUnsavedChanges(addMod), [addMod]);

const defaultLocale = useMemo(
() =>
(settings.prefs.zimbraPrefLocale &&
find(locales, { id: `${settings.prefs.zimbraPrefLocale}` })) ||
find(locales, { id: 'en' }),
[locales, settings.prefs.zimbraPrefLocale]
const [prefLocale, setPrefLocale] = useState<string>(settings.prefs.zimbraPrefLocale ?? '');

const prefLocaleSelectedValue = useMemo<SelectItem>(
() => find(locales, (item) => item.value === prefLocale) as SelectItem,
CataldoMazzilli marked this conversation as resolved.
Show resolved Hide resolved
[locales, prefLocale]
);

const onLocaleChange = useCallback<SingleSelectionOnChange>(
(value) => {
if (value && value !== settings.prefs.zimbraPrefLocale)
updatePrefs(value, 'zimbraPrefLocale');
if (value) {
updatePref('zimbraPrefLocale', value);
setPrefLocale(value);
}
},
[settings.prefs.zimbraPrefLocale, updatePrefs]
[updatePref]
);

const defaultTimeZone = useMemo(
() =>
(settings.prefs.zimbraPrefTimeZoneId &&
find(timezones, { value: `${settings.prefs.zimbraPrefTimeZoneId}` })) ||
find(timezones, { value: 'UTC' }),
[timezones, settings.prefs.zimbraPrefTimeZoneId]
const [prefTimeZoneId, setPrefTimeZoneId] = useState<string>(
settings.prefs.zimbraPrefTimeZoneId ?? ''
);

const prefTimeZoneIdSelectedValue = useMemo<SelectItem>(
() => find(timezones, (item) => item.value === prefTimeZoneId) as SelectItem,
CataldoMazzilli marked this conversation as resolved.
Show resolved Hide resolved
[timezones, prefTimeZoneId]
);

const onTimeZoneChange = useCallback<SingleSelectionOnChange>(
(value) => {
if (value && value !== settings.prefs.zimbraPrefTimeZoneId)
updatePrefs(value, 'zimbraPrefTimeZoneId');
if (value) {
updatePref('zimbraPrefTimeZoneId', value);
setPrefTimeZoneId(value);
}
},
[settings.prefs.zimbraPrefTimeZoneId, updatePrefs]
[updatePref]
);

const init = useCallback(() => {
setPrefLocale(settings.prefs.zimbraPrefLocale ?? '');
setPrefTimeZoneId(settings.prefs.zimbraPrefTimeZoneId ?? '');
}, [settings.prefs.zimbraPrefLocale, settings.prefs.zimbraPrefTimeZoneId]);

useReset(resetRef, init);

return (
<FormSubSection
label={sectionTitle.label}
Expand All @@ -80,31 +101,28 @@ const LanguageAndTimeZone: FC<{
id={sectionTitle.id}
>
<Container crossAlignment="baseline" padding={{ all: 'small' }}>
{Object.keys(settings.prefs).length > 0 && (
<Select
items={locales}
background={'gray5'}
label={t('label.language', 'Language')}
onChange={onLocaleChange}
selection={prefLocaleSelectedValue}
showCheckbox={false}
dropdownMaxHeight="12.5rem"
selectedBackgroundColor="highlight"
/>

<Padding top="small" width="100%">
CataldoMazzilli marked this conversation as resolved.
Show resolved Hide resolved
<Select
items={locales}
items={timezones}
background={'gray5'}
label={t('label.language', 'Language')}
onChange={onLocaleChange}
defaultSelection={defaultLocale}
label={t('label.time_zone', 'Time Zone')}
onChange={onTimeZoneChange}
selection={prefTimeZoneIdSelectedValue}
showCheckbox={false}
dropdownMaxHeight="12.5rem"
selectedBackgroundColor="highlight"
/>
)}
<Padding top="small" width="100%">
{Object.keys(settings.prefs).length > 0 && timezones && defaultTimeZone && (
<Select
items={timezones}
background={'gray5'}
label={t('label.time_zone', 'Time Zone')}
onChange={onTimeZoneChange}
selection={defaultTimeZone}
showCheckbox={false}
dropdownMaxHeight="12.5rem"
selectedBackgroundColor="highlight"
/>
)}
</Padding>
<Modal
title={t('label.reload', 'Reload')}
Expand All @@ -126,5 +144,3 @@ const LanguageAndTimeZone: FC<{
</FormSubSection>
);
};

export default LanguageAndTimeZone;
Loading