From 357a3a50fa8d5e73483b1f52003d9deb72867226 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?=
 <60678893+juliajforesti@users.noreply.github.com>
Date: Tue, 1 Aug 2023 10:51:37 -0300
Subject: [PATCH] feat: high-contrast theme (#29805)

---
 .changeset/smooth-planes-cough.md             |   7 +
 .../GenericUpsellModal/GenericUpsellModal.tsx |   4 +-
 .../providers/UserProvider/UserProvider.tsx   |  12 +-
 .../sidebar/header/hooks/useAccountItems.tsx  |  43 ++--
 .../sidebar/header/hooks/useThemeItems.tsx    |  33 ---
 .../sidebar/header/hooks/useUserMenu.tsx      |  24 +-
 .../preferences/PreferencesGlobalSection.tsx  |  21 +-
 apps/meteor/client/views/account/routes.tsx   |   8 +
 .../client/views/account/sidebarItems.tsx     |  15 +-
 .../themes/HighContrastUpsellModal.tsx        |  41 ++++
 .../client/views/account/themes/ThemePage.tsx | 103 +++++++++
 .../client/views/account/themes/themeItems.ts |  24 ++
 .../rocketchat-i18n/i18n/en.i18n.json         |  11 +
 .../images/high-contrast-upsell-modal.png     | Bin 0 -> 13392 bytes
 .../server/methods/saveUserPreferences.ts     |   3 +-
 .../ui-theming/src/PaletteStyleTag.tsx        |  15 +-
 .../ui-theming/src/hooks/useThemeMode.ts      |  25 ++-
 ee/packages/ui-theming/src/palette.ts         |  10 +-
 .../ui-theming/src/paletteHighContrast.ts     | 210 ++++++++++++++++++
 ee/packages/ui-theming/src/types/themes.ts    |   3 +
 .../v1/users/UsersSetPreferenceParamsPOST.ts  |   3 +-
 21 files changed, 513 insertions(+), 102 deletions(-)
 create mode 100644 .changeset/smooth-planes-cough.md
 delete mode 100644 apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx
 create mode 100644 apps/meteor/client/views/account/themes/HighContrastUpsellModal.tsx
 create mode 100644 apps/meteor/client/views/account/themes/ThemePage.tsx
 create mode 100644 apps/meteor/client/views/account/themes/themeItems.ts
 create mode 100644 apps/meteor/public/images/high-contrast-upsell-modal.png
 create mode 100644 ee/packages/ui-theming/src/paletteHighContrast.ts
 create mode 100644 ee/packages/ui-theming/src/types/themes.ts

diff --git a/.changeset/smooth-planes-cough.md b/.changeset/smooth-planes-cough.md
new file mode 100644
index 00000000000..9ad4239f034
--- /dev/null
+++ b/.changeset/smooth-planes-cough.md
@@ -0,0 +1,7 @@
+---
+'@rocket.chat/ui-theming': minor
+'@rocket.chat/rest-typings': minor
+'@rocket.chat/meteor': minor
+---
+
+feat: high-contrast theme
diff --git a/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx b/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx
index 15c47db711b..79215ef7c10 100644
--- a/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx
+++ b/apps/meteor/client/components/GenericUpsellModal/GenericUpsellModal.tsx
@@ -15,7 +15,7 @@ type GenericUpsellModalProps = {
 	icon?: IconName;
 	img: ComponentProps<typeof Modal.HeroImage>['src'];
 	onCancel?: () => void;
-	onClose?: () => void;
+	onClose: () => void;
 	onConfirm?: () => void;
 	annotation?: ReactNode;
 } & ComponentProps<typeof Modal>;
@@ -30,8 +30,8 @@ const GenericUpsellModal = ({
 	icon,
 	description,
 	onCancel,
+	onClose,
 	onConfirm,
-	onClose = onCancel,
 	annotation,
 	...props
 }: GenericUpsellModalProps) => {
diff --git a/apps/meteor/client/providers/UserProvider/UserProvider.tsx b/apps/meteor/client/providers/UserProvider/UserProvider.tsx
index d4f982d3350..fa5eca49cae 100644
--- a/apps/meteor/client/providers/UserProvider/UserProvider.tsx
+++ b/apps/meteor/client/providers/UserProvider/UserProvider.tsx
@@ -1,6 +1,6 @@
 import type { IRoom, ISubscription, IUser } from '@rocket.chat/core-typings';
 import { useLocalStorage } from '@rocket.chat/fuselage-hooks';
-import { UserContext, useSetting } from '@rocket.chat/ui-contexts';
+import { UserContext, useEndpoint, useSetting } from '@rocket.chat/ui-contexts';
 import type { LoginService, SubscriptionWithRoom } from '@rocket.chat/ui-contexts';
 import { Meteor } from 'meteor/meteor';
 import type { ContextType, ReactElement, ReactNode } from 'react';
@@ -10,6 +10,7 @@ import { Subscriptions, ChatRoom } from '../../../app/models/client';
 import { getUserPreference } from '../../../app/utils/client';
 import { sdk } from '../../../app/utils/client/lib/SDKClient';
 import { afterLogoutCleanUpCallback } from '../../../lib/callbacks/afterLogoutCleanUpCallback';
+import { useIsEnterprise } from '../../hooks/useIsEnterprise';
 import { useReactiveValue } from '../../hooks/useReactiveValue';
 import { createReactiveSubscriptionFactory } from '../../lib/createReactiveSubscriptionFactory';
 import { useEmailVerificationWarning } from './hooks/useEmailVerificationWarning';
@@ -66,6 +67,9 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => {
 	const user = useReactiveValue(getUser);
 	const [language, setLanguage] = useLocalStorage('userLanguage', user?.language ?? 'en');
 
+	const { data: license } = useIsEnterprise();
+	const setUserPreferences = useEndpoint('POST', '/v1/users.setPreferences');
+
 	const loginMethod: LoginMethods = (isLdapEnabled && 'loginWithLDAP') || (isCrowdEnabled && 'loginWithCrowd') || 'loginWithPassword';
 
 	useLDAPAndCrowdCollisionWarning();
@@ -166,6 +170,12 @@ const UserProvider = ({ children }: UserProviderProps): ReactElement => {
 		}
 	}, [user?.language, language, setLanguage]);
 
+	useEffect(() => {
+		if (!license?.isEnterprise && user?.settings?.preferences?.themeAppearence === 'high-contrast') {
+			setUserPreferences({ data: { themeAppearence: 'light' } });
+		}
+	}, [license?.isEnterprise, setUserPreferences, user?.settings?.preferences?.themeAppearence]);
+
 	return <UserContext.Provider children={children} value={contextValue} />;
 };
 
diff --git a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx
index e4757c2d48b..10966e4b859 100644
--- a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx
+++ b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx
@@ -1,29 +1,28 @@
 import { Badge } from '@rocket.chat/fuselage';
 import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
 import { defaultFeaturesPreview, useFeaturePreviewList } from '@rocket.chat/ui-client';
-import { useLogout, useRoute, useTranslation } from '@rocket.chat/ui-contexts';
+import { useRouter, useTranslation } from '@rocket.chat/ui-contexts';
 import React from 'react';
 
 import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem';
 
 export const useAccountItems = (): GenericMenuItemProps[] => {
 	const t = useTranslation();
-	const accountRoute = useRoute('account-index');
-	const featurePreviewRoute = useRoute('feature-preview');
-	const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList();
+	const router = useRouter();
 
-	const logout = useLogout();
+	const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList();
 
 	const handleMyAccount = useMutableCallback(() => {
-		accountRoute.push({});
+		router.navigate('/account');
 	});
-
-	const handleFeaturePreview = useMutableCallback(() => {
-		featurePreviewRoute.push();
+	const handleThemes = useMutableCallback(() => {
+		router.navigate('/account/theme');
 	});
-
-	const handleLogout = useMutableCallback(() => {
-		logout();
+	const handlePreferences = useMutableCallback(() => {
+		router.navigate('/account/preferences');
+	});
+	const handleFeaturePreview = useMutableCallback(() => {
+		router.navigate('/account/feature-preview');
 	});
 
 	const featurePreviewItem = {
@@ -42,17 +41,23 @@ export const useAccountItems = (): GenericMenuItemProps[] => {
 
 	return [
 		{
-			id: 'my-account',
+			id: 'profile',
 			icon: 'user',
-			content: t('My_Account'),
+			content: t('Profile'),
 			onClick: handleMyAccount,
 		},
-		...(featurePreviewEnabled && defaultFeaturesPreview.length > 0 ? [featurePreviewItem] : []),
 		{
-			id: 'logout',
-			icon: 'sign-out',
-			content: t('Logout'),
-			onClick: handleLogout,
+			id: 'theme',
+			icon: 'palette',
+			content: t('Theme'),
+			onClick: handleThemes,
 		},
+		{
+			id: 'preferences',
+			icon: 'customize',
+			content: t('Preferences'),
+			onClick: handlePreferences,
+		},
+		...(featurePreviewEnabled && defaultFeaturesPreview.length > 0 ? [featurePreviewItem] : []),
 	];
 };
diff --git a/apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx b/apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx
deleted file mode 100644
index d571acde162..00000000000
--- a/apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { RadioButton } from '@rocket.chat/fuselage';
-import { useTranslation } from '@rocket.chat/ui-contexts';
-import { useThemeMode } from '@rocket.chat/ui-theming/src/hooks/useThemeMode';
-import React from 'react';
-
-import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem';
-
-export const useThemeItems = (): GenericMenuItemProps[] => {
-	const t = useTranslation();
-
-	const [selectedTheme, setTheme] = useThemeMode();
-
-	return [
-		{
-			id: 'light',
-			icon: 'sun',
-			content: t('Theme_light'),
-			addon: <RadioButton checked={selectedTheme === 'light'} onChange={setTheme('light')} m='x4' />,
-		},
-		{
-			id: 'dark',
-			icon: 'moon',
-			content: t('Theme_dark'),
-			addon: <RadioButton checked={selectedTheme === 'dark'} onChange={setTheme('dark')} m='x4' />,
-		},
-		{
-			id: 'auto',
-			icon: 'desktop',
-			content: t('Theme_match_system'),
-			addon: <RadioButton checked={selectedTheme === 'auto'} onChange={setTheme('auto')} m='x4' />,
-		},
-	];
-};
diff --git a/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx b/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx
index 232011dbe31..111065b0ac4 100644
--- a/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx
+++ b/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx
@@ -1,19 +1,31 @@
 import type { IUser } from '@rocket.chat/core-typings';
-import { useTranslation } from '@rocket.chat/ui-contexts';
+import { useMutableCallback } from '@rocket.chat/fuselage-hooks';
+import { useLogout, useTranslation } from '@rocket.chat/ui-contexts';
 import React from 'react';
 
+import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem';
 import UserMenuHeader from '../UserMenuHeader';
 import { useAccountItems } from './useAccountItems';
 import { useStatusItems } from './useStatusItems';
-import { useThemeItems } from './useThemeItems';
 
 export const useUserMenu = (user: IUser) => {
 	const t = useTranslation();
 
 	const statusItems = useStatusItems(user);
-	const themeItems = useThemeItems();
 	const accountItems = useAccountItems();
 
+	const logout = useLogout();
+	const handleLogout = useMutableCallback(() => {
+		logout();
+	});
+
+	const logoutItem: GenericMenuItemProps = {
+		id: 'logout',
+		icon: 'sign-out',
+		content: t('Logout'),
+		onClick: handleLogout,
+	};
+
 	return [
 		{
 			title: <UserMenuHeader user={user} />,
@@ -24,11 +36,11 @@ export const useUserMenu = (user: IUser) => {
 			items: statusItems,
 		},
 		{
-			title: t('Theme'),
-			items: themeItems,
+			title: t('Account'),
+			items: accountItems,
 		},
 		{
-			items: accountItems,
+			items: [logoutItem],
 		},
 	];
 };
diff --git a/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.tsx b/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.tsx
index edc7e64b9dc..7c2410deb9f 100644
--- a/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.tsx
+++ b/apps/meteor/client/views/account/preferences/PreferencesGlobalSection.tsx
@@ -1,5 +1,5 @@
 import type { SelectOption } from '@rocket.chat/fuselage';
-import { Select, Accordion, Field, FieldGroup, MultiSelect } from '@rocket.chat/fuselage';
+import { Accordion, Field, FieldGroup, MultiSelect } from '@rocket.chat/fuselage';
 import { useUserPreference, useTranslation } from '@rocket.chat/ui-contexts';
 import type { ReactElement } from 'react';
 import React, { useMemo } from 'react';
@@ -11,7 +11,6 @@ const PreferencesGlobalSection = ({ onChange, commitRef, ...props }: FormSection
 	const t = useTranslation();
 
 	const userDontAskAgainList = useUserPreference<{ action: string; label: string }[]>('dontAskAgainList');
-	const themePreference = useUserPreference<'light' | 'dark' | 'auto'>('themeAppearence');
 
 	const options = useMemo(
 		() => (userDontAskAgainList || []).map(({ action, label }) => [action, label]) as SelectOption[],
@@ -23,26 +22,18 @@ const PreferencesGlobalSection = ({ onChange, commitRef, ...props }: FormSection
 	const { values, handlers, commit } = useForm(
 		{
 			dontAskAgainList: selectedOptions,
-			themeAppearence: themePreference,
 		},
 		onChange,
 	);
 
-	const { dontAskAgainList, themeAppearence } = values as {
+	const { dontAskAgainList } = values as {
 		dontAskAgainList: string[];
-		themeAppearence: string;
 	};
 
-	const { handleDontAskAgainList, handleThemeAppearence } = handlers;
+	const { handleDontAskAgainList } = handlers;
 
 	commitRef.current.global = commit;
 
-	const themeOptions: SelectOption[] = [
-		['auto', t('Theme_match_system')],
-		['light', t('Theme_light')],
-		['dark', t('Theme_dark')],
-	];
-
 	return (
 		<Accordion.Item title={t('Global')} {...props}>
 			<FieldGroup>
@@ -57,12 +48,6 @@ const PreferencesGlobalSection = ({ onChange, commitRef, ...props }: FormSection
 						/>
 					</Field.Row>
 				</Field>
-				<Field>
-					<Field.Label>{t('Theme_Appearence')}</Field.Label>
-					<Field.Row>
-						<Select value={themeAppearence} onChange={handleThemeAppearence} options={themeOptions} />
-					</Field.Row>
-				</Field>
 			</FieldGroup>
 		</Accordion.Item>
 	);
diff --git a/apps/meteor/client/views/account/routes.tsx b/apps/meteor/client/views/account/routes.tsx
index f6a49eb268d..6d17b1e1308 100644
--- a/apps/meteor/client/views/account/routes.tsx
+++ b/apps/meteor/client/views/account/routes.tsx
@@ -36,6 +36,10 @@ declare module '@rocket.chat/ui-contexts' {
 			pathname: '/account/feature-preview';
 			pattern: '/account/feature-preview';
 		};
+		'theme': {
+			pathname: '/account/theme';
+			pattern: '/account/theme';
+		};
 	}
 }
 
@@ -79,3 +83,7 @@ registerAccountRoute('/feature-preview', {
 	name: 'feature-preview',
 	component: lazy(() => import('./featurePreview/AccountFeaturePreviewPage')),
 });
+registerAccountRoute('/theme', {
+	name: 'theme',
+	component: lazy(() => import('./themes/ThemePage')),
+});
diff --git a/apps/meteor/client/views/account/sidebarItems.tsx b/apps/meteor/client/views/account/sidebarItems.tsx
index a2d0720fe6e..1bdb488d419 100644
--- a/apps/meteor/client/views/account/sidebarItems.tsx
+++ b/apps/meteor/client/views/account/sidebarItems.tsx
@@ -12,17 +12,22 @@ export const {
 	getSidebarItems: getAccountSidebarItems,
 	subscribeToSidebarItems: subscribeToAccountSidebarItems,
 } = createSidebarItems([
-	{
-		href: '/account/preferences',
-		i18nLabel: 'Preferences',
-		icon: 'customize',
-	},
 	{
 		href: '/account/profile',
 		i18nLabel: 'Profile',
 		icon: 'user',
 		permissionGranted: (): boolean => settings.get('Accounts_AllowUserProfileChange'),
 	},
+	{
+		href: '/account/theme',
+		i18nLabel: 'Theme',
+		icon: 'palette',
+	},
+	{
+		href: '/account/preferences',
+		i18nLabel: 'Preferences',
+		icon: 'customize',
+	},
 	{
 		href: '/account/security',
 		i18nLabel: 'Security',
diff --git a/apps/meteor/client/views/account/themes/HighContrastUpsellModal.tsx b/apps/meteor/client/views/account/themes/HighContrastUpsellModal.tsx
new file mode 100644
index 00000000000..c06501aae28
--- /dev/null
+++ b/apps/meteor/client/views/account/themes/HighContrastUpsellModal.tsx
@@ -0,0 +1,41 @@
+import { useRole, useTranslation } from '@rocket.chat/ui-contexts';
+import React from 'react';
+
+import GenericUpsellModal from '../../../components/GenericUpsellModal';
+import { useUpsellActions } from '../../../components/GenericUpsellModal/hooks';
+
+const HighContrastUpsellModal = ({ onClose }: { onClose: () => void }) => {
+	const t = useTranslation();
+
+	const isAdmin = useRole('admin');
+	const { handleGoFullyFeatured, handleTalkToSales } = useUpsellActions();
+
+	if (!isAdmin) {
+		return (
+			<GenericUpsellModal
+				title={t('High_contrast_upsell_title')}
+				img='images/high-contrast-upsell-modal.png'
+				subtitle={t('High_contrast_upsell_subtitle')}
+				description={t('High_contrast_upsell_description')}
+				onClose={onClose}
+				onCancel={onClose}
+				cancelText={t('Close')}
+				annotation={t('High_contrast_upsell_annotation')}
+			/>
+		);
+	}
+	return (
+		<GenericUpsellModal
+			title={t('High_contrast_upsell_title')}
+			img='images/high-contrast-upsell-modal.png'
+			subtitle={t('High_contrast_upsell_subtitle')}
+			description={t('High_contrast_upsell_description')}
+			onClose={onClose}
+			onCancel={handleTalkToSales}
+			onConfirm={handleGoFullyFeatured}
+			cancelText={t('Talk_to_sales')}
+			confirmText={t('Start_free_trial')}
+		/>
+	);
+};
+export default HighContrastUpsellModal;
diff --git a/apps/meteor/client/views/account/themes/ThemePage.tsx b/apps/meteor/client/views/account/themes/ThemePage.tsx
new file mode 100644
index 00000000000..3f3f022be5f
--- /dev/null
+++ b/apps/meteor/client/views/account/themes/ThemePage.tsx
@@ -0,0 +1,103 @@
+import { Accordion, Box, Button, ButtonGroup, Field, RadioButton, Tag } from '@rocket.chat/fuselage';
+import { ExternalLink } from '@rocket.chat/ui-client';
+import { useEndpoint, useSetModal, useToastMessageDispatch, useTranslation, useUserPreference } from '@rocket.chat/ui-contexts';
+import type { ThemePreference } from '@rocket.chat/ui-theming/src/types/themes';
+import React from 'react';
+import { useForm } from 'react-hook-form';
+
+import Page from '../../../components/Page';
+import { useIsEnterprise } from '../../../hooks/useIsEnterprise';
+import HighContrastUpsellModal from './HighContrastUpsellModal';
+import { themeItems as themes } from './themeItems';
+
+const ThemePage = () => {
+	const t = useTranslation();
+	const setModal = useSetModal();
+	const dispatchToastMessage = useToastMessageDispatch();
+	const { data: license } = useIsEnterprise();
+
+	const themePreference = useUserPreference<ThemePreference>('themeAppearence') || 'auto';
+	const setUserPreferences = useEndpoint('POST', '/v1/users.setPreferences');
+
+	const {
+		formState: { isDirty },
+		handleSubmit,
+		register,
+		reset,
+	} = useForm({
+		defaultValues: { themeAppearence: themePreference },
+	});
+
+	const handleSave = async ({ themeAppearence }: { themeAppearence: ThemePreference }) => {
+		try {
+			await setUserPreferences({ data: { themeAppearence } });
+			dispatchToastMessage({ type: 'success', message: t('Preferences_saved') });
+		} catch (error) {
+			dispatchToastMessage({ type: 'error', message: error });
+		} finally {
+			reset({ themeAppearence });
+		}
+	};
+
+	return (
+		<Page>
+			<Page.Header title={t('Theme')}>
+				<ButtonGroup>
+					<Button primary disabled={!isDirty} onClick={handleSubmit(handleSave)}>
+						{t('Save_changes')}
+					</Button>
+				</ButtonGroup>
+			</Page.Header>
+			<Page.ScrollableContentWithShadow>
+				<Box maxWidth='x600' w='full' alignSelf='center' mb='x40' mi='x36'>
+					<Box fontScale='p1' mbe='x24'>
+						<Box pb='x16'>{t('Choose_theme_description')}</Box>
+					</Box>
+					<Accordion>
+						<Accordion.Item defaultExpanded={true} title={t('Theme')}>
+							{themes.map(({ id, title, description, ...item }, index) => {
+								const externalLink = 'externalLink' in item && item.externalLink;
+								const communityDisabled = 'isEEOnly' in item && item.isEEOnly && !license?.isEnterprise;
+
+								return (
+									<Field key={id} pbe={themes.length - 1 ? undefined : 'x28'} pbs={index === 0 ? undefined : 'x28'}>
+										<Box display='flex' flexDirection='row' justifyContent='spaceBetween' flexGrow={1}>
+											<Field.Label display='flex' alignItems='center' htmlFor={id}>
+												{t.has(title) ? t(title) : title}
+												{communityDisabled && (
+													<Box is='span' mis='x8'>
+														<Tag variant='featured'>{t('Enterprise')}</Tag>
+													</Box>
+												)}
+											</Field.Label>
+											<Field.Row>
+												{communityDisabled ? (
+													<RadioButton
+														onClick={() => setModal(<HighContrastUpsellModal onClose={() => setModal(null)} />)}
+														checked={false}
+													/>
+												) : (
+													<RadioButton id={id} {...register('themeAppearence')} value={id} />
+												)}
+											</Field.Row>
+										</Box>
+										<Field.Hint mbs='x12' style={{ whiteSpace: 'break-spaces' }}>
+											{t.has(description) ? t(description) : description}
+											{externalLink && communityDisabled && (
+												<Box mbs='x12'>
+													<ExternalLink to={externalLink}>{t('Talk_to_an_expert')}</ExternalLink>
+												</Box>
+											)}
+										</Field.Hint>
+									</Field>
+								);
+							})}
+						</Accordion.Item>
+					</Accordion>
+				</Box>
+			</Page.ScrollableContentWithShadow>
+		</Page>
+	);
+};
+
+export default ThemePage;
diff --git a/apps/meteor/client/views/account/themes/themeItems.ts b/apps/meteor/client/views/account/themes/themeItems.ts
new file mode 100644
index 00000000000..82457ac3938
--- /dev/null
+++ b/apps/meteor/client/views/account/themes/themeItems.ts
@@ -0,0 +1,24 @@
+export const themeItems = [
+	{
+		id: 'light',
+		title: 'Theme_light',
+		description: 'Theme_light_description',
+	},
+	{
+		id: 'dark',
+		title: 'Theme_dark',
+		description: 'Theme_dark_description',
+	},
+	{
+		id: 'auto',
+		title: 'Theme_match_system',
+		description: 'Theme_match_system_description',
+	},
+	{
+		isEEOnly: true,
+		id: 'high-contrast',
+		title: 'Theme_high_contrast',
+		externalLink: 'https://www.rocket.chat/sales-contact',
+		description: 'Theme_high_contrast_description',
+	},
+];
diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
index 0bd39f8e43e..09e3b5ed0bb 100644
--- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
+++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json
@@ -4892,6 +4892,8 @@
   "The_user_will_be_removed_from_s": "The user will be removed from %s",
   "The_user_wont_be_able_to_type_in_s": "The user won't be able to type in %s",
   "Theme": "Theme",
+  "Themes": "Themes",
+  "Choose_theme_description": "Choose the interface appearance that best suits your needs.",
   "theme-color-attention-color": "Attention Color",
   "theme-color-component-color": "Component Color",
   "theme-color-content-background-color": "Content Background Color",
@@ -5800,7 +5802,9 @@
   "Something_Went_Wrong": "Something went wrong",
   "Toolbox_room_actions": "Primary Room actions",
   "Theme_light": "Light",
+  "Theme_light_description": "More accessible for individuals with visual impairments and a good choice for well-lit environments.",
   "Theme_dark": "Dark",
+  "Theme_dark_description": "Reduce eye strain and fatigue in low-light conditions by minimizing the amount of light emitted by the screen.",
   "Enable_of_limit_apps_currently_enabled": "**{{enabled}} of {{limit}} {{context}} apps currently enabled.**  \n  \nWorkspaces on Community Edition can have up to {{limit}} {{context}} apps enabled.  \n  \n**{{appName}} will be disabled by default.** Disable another {{context}} app or upgrade to Enterprise to enable this app.",
   "Enable_of_limit_apps_currently_enabled_exceeded": "**{{enabled}} of {{limit}} {{context}} apps currently enabled.**  \n  \nCommunity edition app limit has been exceeded.  \n  \nWorkspaces on Community Edition can have up to {{limit}} {{context}} apps enabled.  \n  \n**{{appName}} will be disabled by default.** You will need to disable at least {{exceed}} other {{context}} apps or upgrade to Enterprise to enable this app.",
   "Workspaces_on_Community_edition_install_app": "Workspaces on Community Edition can have up to {{limit}} {{context}} apps enabled. Upgrade to Enterprise to enable unlimited apps.",
@@ -5814,6 +5818,13 @@
   "Disable_at_least_more_apps": "You will need to disable at least {{numberOfExceededApps}} other apps or upgrade to Enterprise to enable this app.",
   "Community_Private_apps_limit_exceeded": "Community edition app limit has been exceeded.",
   "Theme_match_system": "Match system",
+  "Theme_match_system_description": "Automatically match the theme to your system preferences. This option is only available if your browser supports the prefers-color-scheme media query.",
+  "Theme_high_contrast": "High contrast",
+  "Theme_high_contrast_description": "Maximum tonal differentiation with bold colors and sharp contrasts provide enhanced accessibility.",
+  "High_contrast_upsell_title": "Enable high contrast theme",
+  "High_contrast_upsell_subtitle": "Enhance your team’s reading experience",
+  "High_contrast_upsell_description": "Especially designed for individuals with visual impairments or conditions such as color vision deficiency, low vision, or sensitivity to low contrast. \nThis theme increases contrast between text and background elements, making content more distinguishable and easier to read.",
+  "High_contrast_upsell_annotation": "Talk to your workspace admin about enabling the high contrast theme for everyone.",
   "Join_your_team": "Join your team",
   "Create_a_password": "Create a password",
   "Create_an_account": "Create an account",
diff --git a/apps/meteor/public/images/high-contrast-upsell-modal.png b/apps/meteor/public/images/high-contrast-upsell-modal.png
new file mode 100644
index 0000000000000000000000000000000000000000..b761a1b0b76c81cda36007039682c3a5493b8448
GIT binary patch
literal 13392
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
ZrjobB@s5`tfeS!L%JLd=)iU6){{_?5;TZq`

literal 0
HcmV?d00001

diff --git a/apps/meteor/server/methods/saveUserPreferences.ts b/apps/meteor/server/methods/saveUserPreferences.ts
index 4d82a3f35ba..ad2934f3f99 100644
--- a/apps/meteor/server/methods/saveUserPreferences.ts
+++ b/apps/meteor/server/methods/saveUserPreferences.ts
@@ -1,5 +1,6 @@
 import { Subscriptions, Users } from '@rocket.chat/models';
 import type { ServerMethods } from '@rocket.chat/ui-contexts';
+import type { ThemePreference } from '@rocket.chat/ui-theming/src/types/themes';
 import { Match, check } from 'meteor/check';
 import { Meteor } from 'meteor/meteor';
 
@@ -34,7 +35,7 @@ type UserPreferences = {
 	sidebarGroupByType: boolean;
 	muteFocusedConversations: boolean;
 	dontAskAgainList: { action: string; label: string }[];
-	themeAppearence: 'auto' | 'light' | 'dark';
+	themeAppearence: ThemePreference;
 	receiveLoginDetectionEmail: boolean;
 	notifyCalendarEvents: boolean;
 };
diff --git a/ee/packages/ui-theming/src/PaletteStyleTag.tsx b/ee/packages/ui-theming/src/PaletteStyleTag.tsx
index 84bbee2eb8a..95f0f40c7fd 100644
--- a/ee/packages/ui-theming/src/PaletteStyleTag.tsx
+++ b/ee/packages/ui-theming/src/PaletteStyleTag.tsx
@@ -7,14 +7,21 @@ import { useCreateStyleContainer } from './hooks/useCreateStyleContainer';
 import { useThemeMode } from './hooks/useThemeMode';
 import { defaultPalette } from './palette';
 import { darkPalette } from './paletteDark';
+import { paletteHighContrast } from './paletteHighContrast';
 
 export const PaletteStyleTag = memo(function PaletteStyleTag() {
 	const [, , theme] = useThemeMode();
 
-	const palette =
-		theme === 'dark'
-			? convertToCss(filterOnlyChangedColors(defaultPalette, darkPalette), '.rcx-content--main')
-			: convertToCss(filterOnlyChangedColors(defaultPalette, {}), '.rcx-content--main');
+	const getPalette = () => {
+		if (theme === 'dark') {
+			return darkPalette;
+		}
+		if (theme === 'high-contrast') {
+			return paletteHighContrast;
+		}
+		return {};
+	};
+	const palette = convertToCss(filterOnlyChangedColors(defaultPalette, getPalette()), '.rcx-content--main');
 
 	return createPortal(palette, useCreateStyleContainer('main-palette'));
 });
diff --git a/ee/packages/ui-theming/src/hooks/useThemeMode.ts b/ee/packages/ui-theming/src/hooks/useThemeMode.ts
index a467345f810..63cf01bf6eb 100644
--- a/ee/packages/ui-theming/src/hooks/useThemeMode.ts
+++ b/ee/packages/ui-theming/src/hooks/useThemeMode.ts
@@ -1,29 +1,38 @@
 import { useDarkMode } from '@rocket.chat/fuselage-hooks';
 import { useEndpoint, useUserPreference } from '@rocket.chat/ui-contexts';
+import type { ThemePreference as ThemeMode, Themes } from '@rocket.chat/ui-theming/src/types/themes';
 import { useCallback, useState } from 'react';
 
-type ThemeMode = 'light' | 'dark' | 'auto';
-
 /**
  * Returns the current option set by the user, the theme mode resolved given the user configuration and OS (if applies) and a function to set it.
  * @param defaultThemeMode The default theme mode to use if the user has not set any.
  * @returns [currentThemeMode, setThemeMode, resolvedThemeMode]
  */
 
-export const useThemeMode = (): [ThemeMode, (value: ThemeMode) => () => void, 'light' | 'dark'] => {
-	const theme = useUserPreference<ThemeMode>('themeAppearence') || 'auto';
+export const useThemeMode = (): [ThemeMode, (value: ThemeMode) => () => void, Themes] => {
+	const themeMode = useUserPreference<ThemeMode>('themeAppearence') || 'auto';
 
 	const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences');
 
 	const [updaters] = useState(
 		(): Record<ThemeMode, () => void> => ({
-			light: () => saveUserPreferences({ data: { themeAppearence: 'light' } }),
-			dark: () => saveUserPreferences({ data: { themeAppearence: 'dark' } }),
-			auto: () => saveUserPreferences({ data: { themeAppearence: 'auto' } }),
+			'light': () => saveUserPreferences({ data: { themeAppearence: 'light' } }),
+			'dark': () => saveUserPreferences({ data: { themeAppearence: 'dark' } }),
+			'auto': () => saveUserPreferences({ data: { themeAppearence: 'auto' } }),
+			'high-contrast': () => saveUserPreferences({ data: { themeAppearence: 'high-contrast' } }),
 		}),
 	);
 
 	const setTheme = useCallback((value: ThemeMode): (() => void) => updaters[value], [updaters]);
 
-	return [theme, setTheme, useDarkMode(theme === 'auto' ? undefined : theme === 'dark') ? 'dark' : 'light'];
+	const useTheme = () => {
+		if (useDarkMode(themeMode === 'auto' ? undefined : themeMode === 'dark')) {
+			return 'dark';
+		}
+		if (themeMode === 'high-contrast') {
+			return 'high-contrast';
+		}
+		return 'light';
+	};
+	return [themeMode, setTheme, useTheme()];
 };
diff --git a/ee/packages/ui-theming/src/palette.ts b/ee/packages/ui-theming/src/palette.ts
index 4abcefa6524..4825beec0cf 100644
--- a/ee/packages/ui-theming/src/palette.ts
+++ b/ee/packages/ui-theming/src/palette.ts
@@ -24,7 +24,7 @@ export const palette = [
 			{ name: 'surface-neutral', token: 'N400', color: '#E4E7EA' },
 			{ name: 'surface-disabled', token: 'N100', color: '#F7F8FA' },
 			{ name: 'surface-hover', token: 'N200', color: '#F2F3F5' },
-			{ name: 'surface-selected', token: 'N400', color: '#E4E7EA' },
+			{ name: 'surface-selected', token: '', color: '#D7DBE0' },
 			{ name: 'surface-dark', token: 'N900', color: '#1F2329' },
 			{ name: 'surface-featured', token: '', color: '#5F1477' },
 			{ name: 'surface-featured-hover', token: '', color: '#4A105D' },
@@ -176,17 +176,19 @@ export const palette = [
 		description: 'Font',
 		list: [
 			{ name: 'button-font-on-primary', token: 'white', color: '#FFFFFF' },
+			{ name: 'button-font-on-primary-disabled', token: 'white', color: '#FFFFFF' },
 			{ name: 'button-font-on-secondary', token: 'N900', color: '#1F2329' },
+			{ name: 'button-font-on-secondary-disabled', token: 'N600', color: '#CBCED1' },
 			{ name: 'button-font-on-secondary-danger', token: 'D900', color: '#BB0B21' },
-			{ name: 'button-font-on-danger', token: 'white', color: '#FFFFFF' },
-			{ name: 'button-font-on-primary-disabled', token: 'white', color: '#FFFFFF' },
-			{ name: 'button-font-on-secondary-disabled', token: 'N600', color: '#9EA2A8' },
 			{
 				name: 'button-font-on-secondary-danger-disabled',
 				token: 'D300',
 				color: '#F98F9D',
 			},
+			{ name: 'button-font-on-danger', token: 'white', color: '#FFFFFF' },
 			{ name: 'button-font-on-danger-disabled', token: 'white', color: '#FFFFFF' },
+			{ name: 'button-font-on-success', token: '', color: '#EBECEF' },
+			{ name: 'button-font-on-success-disabled', token: 'white', color: '#FFFFFF' },
 		],
 	},
 ];
diff --git a/ee/packages/ui-theming/src/paletteHighContrast.ts b/ee/packages/ui-theming/src/paletteHighContrast.ts
new file mode 100644
index 00000000000..82ebb68f1e9
--- /dev/null
+++ b/ee/packages/ui-theming/src/paletteHighContrast.ts
@@ -0,0 +1,210 @@
+export const palette = [
+	{
+		category: 'Stroke',
+		description: "Use as component's outline, stroke, dividers",
+		list: [
+			{ name: 'stroke-extra-light', token: 'N250', color: '#EBECEF' },
+			{ name: 'stroke-light', token: 'N500', color: '#CBCED1' },
+			{ name: 'stroke-medium', token: 'N600', color: '#9EA2A8' },
+			{ name: 'stroke-dark', token: 'N700', color: '#6C727A' },
+			{ name: 'stroke-extra-dark', token: 'N800', color: '#2F343D' },
+			{ name: 'stroke-extra-light-highlight', token: 'P200', color: '#D1EBFE' },
+			{ name: 'stroke-highlight', token: 'P500', color: '#156FF5' },
+			{ name: 'stroke-extra-light-error', token: 'D200', color: '#FFC1C9' },
+			{ name: 'stroke-error', token: 'D500', color: '#EC0D2A' },
+		],
+	},
+	{
+		category: 'Surface',
+		description: 'Use as a container on top of the background',
+		list: [
+			{ name: 'surface-light', token: 'white', color: '#FFFFFF' },
+			{ name: 'surface-tint', token: 'N100', color: '#F7F8FA' },
+			{ name: 'surface-room', token: 'white', color: '#FFFFFF' },
+			{ name: 'surface-neutral', token: 'N400', color: '#E4E7EA' },
+			{ name: 'surface-disabled', token: 'N100', color: '#F7F8FA' },
+			{ name: 'surface-hover', token: 'N200', color: '#F2F3F5' },
+			{ name: 'surface-selected', token: 'N400', color: '#D7DBE0' },
+			{ name: 'surface-dark', token: 'N900', color: '#1F2329' },
+			{ name: 'surface-featured', token: '', color: '#5F1477' },
+			{ name: 'surface-featured-hover', token: '', color: '#4A105D' },
+			{ name: 'surface-overlay', token: '', color: 'rgba(47, 52, 61, 0.5)' },
+		],
+	},
+	{
+		category: 'Shadow',
+		description: 'Use as a shadow color',
+		list: [
+			{ name: 'shadow-highlight', token: '', color: '#D1EBFE' },
+			{ name: 'shadow-danger', token: '', color: '#FFE9EC' },
+		],
+	},
+	{
+		category: 'Font',
+		description: 'These should be applied according to surfaces',
+		list: [
+			{ name: 'font-white', token: 'white', color: '#FFFFFF' },
+			{ name: 'font-disabled', token: '', color: '#F7F8FA' },
+			{ name: 'font-annotation', token: '', color: '#7020C0' },
+			{ name: 'font-hint', token: '', color: '#3C3F44' },
+			{ name: 'font-secondary-info', token: '', color: '#3C3F44' },
+			{ name: 'font-default', token: '', color: '#24272E' },
+			{ name: 'font-titles-labels', token: '', color: '#1F2329' },
+			{ name: 'font-info', token: '', color: '#084BB0' },
+			{ name: 'font-danger', token: '', color: '#A90A1E' },
+			{ name: 'font-pure-black', token: '', color: '#2F343D' },
+			{ name: 'font-pure-white', token: '', color: '#FFFFFF' },
+		],
+	},
+	{
+		category: 'Status',
+		description: 'Status Background',
+		list: [
+			{ name: 'status-background-info', token: 'P200', color: '#D1EBFE' },
+			{ name: 'status-background-success', token: 'S500', color: '#C0F6E4' },
+			{ name: 'status-background-danger', token: 'D200', color: '#FFC1C9' },
+			{ name: 'status-background-warning', token: 'W200', color: '#FFECAD' },
+			{ name: 'status-background-warning-2', token: 'W100', color: '#FFF8E0' },
+			{ name: 'status-background-service-1', token: 'S1-200', color: '#FAD1B0' },
+			{ name: 'status-background-service-2', token: 'S2-200', color: '#EDD0F7' },
+			{ name: 'status-background-service-3', token: 'S2-700', color: '#5F1477' },
+		],
+	},
+	{
+		description: 'Status Font',
+		list: [
+			{ name: 'status-font-on-info', token: '', color: '#053070' },
+			{ name: 'status-font-on-success', token: '', color: '#0D5940' },
+			{ name: 'status-font-on-danger', token: 'D800', color: '#9B1325' },
+			{ name: 'status-font-on-warning', token: 'W900', color: '#B88D00' },
+			{ name: 'status-font-on-warning-2', token: 'N800', color: '#2F343D' },
+			{ name: 'status-font-on-service-1', token: 'S1-800', color: '#974809' },
+			{ name: 'status-font-on-service-2 ', token: 'S2-600', color: '#7F1B9F' },
+			{ name: 'status-font-on-service-3 ', token: 'white', color: '#FFFFFF' },
+		],
+	},
+	{
+		category: 'Badge',
+		description: 'Badge Background',
+		list: [
+			{ name: 'badge-background-level-0', token: '', color: '#F4F5F6' },
+			{ name: 'badge-background-level-1', token: 'N700', color: '#52565B' },
+			{ name: 'badge-background-level-2', token: '', color: '#064FBC' },
+			{ name: 'badge-background-level-3', token: '', color: '#874108' },
+			{ name: 'badge-background-level-4', token: '', color: '#AE091F' },
+		],
+	},
+	{
+		category: 'Status Bullet',
+		description: 'Used to show user status',
+		list: [
+			{ name: 'status-bullet-online', token: '', color: '#158D65' },
+			{ name: 'status-bullet-away', token: '', color: '#AC892F' },
+			{ name: 'status-bullet-busy', token: '', color: '#DA1F37' },
+			{ name: 'status-bullet-disabled', token: '', color: '#F38C39' },
+			{ name: 'status-bullet-offline', token: '', color: '#AC892F' },
+			{ name: 'status-bullet-loading', token: '', color: '#9ea2a8' },
+		],
+	},
+	{
+		category: 'Elevation',
+		description: 'Elevation border and shadow levels',
+		list: [
+			{ name: 'shadow-elevation-border', token: '', color: '#EBECEF' },
+			{ name: 'shadow-elevation-1', token: '', color: 'rgba(47, 52, 61, 0.1)' },
+			{ name: 'shadow-elevation-2x', token: '', color: 'rgba(47, 52, 61, 0.08)' },
+			{ name: 'shadow-elevation-2y', token: '', color: 'rgba(47, 52, 61, 0.12)' },
+		],
+	},
+	{
+		category: 'Button',
+		description: 'Primary Background',
+		list: [
+			{ name: 'button-background-primary-default', token: '', color: '#084FBA' },
+			{ name: 'button-background-primary-hover', token: '', color: '#063D8E' },
+			{ name: 'button-background-primary-press', token: '', color: '#09305D' },
+			{ name: 'button-background-primary-focus', token: '', color: '#084FBA' },
+			{ name: 'button-background-primary-keyfocus', token: '', color: '#084FBA' },
+			{ name: 'button-background-primary-disabled', token: '', color: '#8CCDFD' },
+		],
+	},
+	{
+		description: 'Secondary Background',
+		list: [
+			{ name: 'button-background-secondary-default', token: 'N400', color: '#E4E7EA' },
+			{ name: 'button-background-secondary-hover', token: 'N500', color: '#CBCED1' },
+			{ name: 'button-background-secondary-press', token: '', color: '#C4C6CA' },
+			{ name: 'button-background-secondary-focus', token: 'N400', color: '#E4E7EA' },
+			{ name: 'button-background-secondary-keyfocus', token: 'N400', color: '#E4E7EA' },
+			{ name: 'button-background-secondary-disabled', token: 'N300', color: '#EEEFF1' },
+		],
+	},
+	{
+		description: 'Secondary Danger Background',
+		list: [
+			{ name: 'button-background-secondary-danger-default', token: '', color: '#F4F5F6' },
+			{ name: 'button-background-secondary-danger-hover', token: '', color: '#E4E6E7' },
+			{ name: 'button-background-secondary-danger-press', token: '', color: '#C9CBCF' },
+			{ name: 'button-background-secondary-danger-focus', token: 'N400', color: '#E4E7EA' },
+			{ name: 'button-background-secondary-danger-keyfocus', token: 'N400', color: '#E4E7EA' },
+			{ name: 'button-background-secondary-danger-disabled', token: '', color: '#FAFAFA' },
+		],
+	},
+	{
+		description: 'Danger Background',
+		list: [
+			{ name: 'button-background-danger-default', token: '', color: '#B30A20' },
+			{ name: 'button-background-danger-hover', token: '', color: '#901323' },
+			{ name: 'button-background-danger-press', token: '', color: '#7A101D' },
+			{ name: 'button-background-danger-focus', token: '', color: '#B30920' },
+			{ name: 'button-background-danger-keyfocus', token: '', color: '#B30A20' },
+			{ name: 'button-background-danger-disabled', token: 'D200', color: '#FFC1C9' },
+		],
+	},
+	{
+		description: 'Success Background',
+		list: [
+			{ name: 'button-background-success-default', token: '', color: '#158D65' },
+			{ name: 'button-background-success-hover', token: 'S900', color: '#106D4F' },
+			{ name: 'button-background-success-press', token: 'S1000', color: '#0D5940' },
+			{ name: 'button-background-success-focus', token: '', color: '#158D65' },
+			{ name: 'button-background-success-keyfocus', token: '', color: '#158D65' },
+			{ name: 'button-background-success-disabled', token: 'S200', color: '#C0F6E4' },
+		],
+	},
+	{
+		description: 'Font',
+		list: [
+			{ name: 'button-font-on-primary', token: 'white', color: '#FFFFFF' },
+			{ name: 'button-font-on-primary-disabled', token: '', color: '#09305D' },
+			{ name: 'button-font-on-secondary', token: '', color: '#14161A' },
+			{ name: 'button-font-on-secondary-disabled', token: '', color: '#4D5257' },
+			{ name: 'button-font-on-danger', token: 'white', color: '#FFFFFF' },
+			{ name: 'button-font-on-danger-disabled', token: '', color: '#7A101D' },
+			{ name: 'button-font-on-secondary-danger', token: '', color: '#6E0210' },
+			{
+				name: 'button-font-on-secondary-danger-disabled',
+				token: '',
+				color: '#AE091F',
+			},
+			{ name: 'button-font-on-success', token: '', color: '#EBECEF' },
+			{ name: 'button-font-on-success-disabled', token: 'white', color: '#FFFFFF' },
+		],
+	},
+];
+
+export const paletteHighContrast = {
+	...palette.reduce(
+		(rec, group) => ({
+			...rec,
+			...group.list.reduce(
+				(rec, item) => ({
+					...rec,
+					[item.name]: item.color,
+				}),
+				{} as Record<string, string>,
+			),
+		}),
+		{} as Record<string, string>,
+	),
+};
diff --git a/ee/packages/ui-theming/src/types/themes.ts b/ee/packages/ui-theming/src/types/themes.ts
new file mode 100644
index 00000000000..2fe98dfbe2b
--- /dev/null
+++ b/ee/packages/ui-theming/src/types/themes.ts
@@ -0,0 +1,3 @@
+export type ThemePreference = 'light' | 'dark' | 'auto' | 'high-contrast';
+
+export type Themes = 'light' | 'dark' | 'high-contrast';
diff --git a/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts
index ce1e33e02dc..c283867345e 100644
--- a/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts
+++ b/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts
@@ -1,3 +1,4 @@
+import type { ThemePreference } from '@rocket.chat/ui-theming/src/types/themes';
 import Ajv from 'ajv';
 
 const ajv = new Ajv({
@@ -40,7 +41,7 @@ export type UsersSetPreferencesParamsPOST = {
 		muteFocusedConversations?: boolean;
 		dontAskAgainList?: Array<{ action: string; label: string }>;
 		featuresPreview?: { name: string; value: boolean }[];
-		themeAppearence?: 'auto' | 'light' | 'dark';
+		themeAppearence?: ThemePreference;
 		receiveLoginDetectionEmail?: boolean;
 		notifyCalendarEvents?: boolean;
 		idleTimeLimit?: number;
-- 
GitLab