diff --git a/packages/console/src/cloud/App.tsx b/packages/console/src/cloud/App.tsx
index be16c4c6a..b09f47a34 100644
--- a/packages/console/src/cloud/App.tsx
+++ b/packages/console/src/cloud/App.tsx
@@ -1,5 +1,6 @@
import { BrowserRouter, Route, Routes } from 'react-router-dom';
+import { AppThemeProvider } from '@/contexts/AppThemeProvider';
import Callback from '@cloud/pages/Callback';
import * as styles from './App.module.scss';
@@ -10,14 +11,16 @@ import { CloudRoute } from './types';
const App = () => {
return (
-
-
- } />
- } />
- } />
- } />
-
-
+
+
+
+ } />
+ } />
+ } />
+ } />
+
+
+
);
};
diff --git a/packages/console/src/cloud/pages/Main/Redirect.tsx b/packages/console/src/cloud/pages/Main/Redirect.tsx
index c1f897d29..7d1f25f84 100644
--- a/packages/console/src/cloud/pages/Main/Redirect.tsx
+++ b/packages/console/src/cloud/pages/Main/Redirect.tsx
@@ -4,7 +4,7 @@ import { trySafe } from '@silverhand/essentials';
import { useContext, useEffect } from 'react';
import { useHref } from 'react-router-dom';
-import { AppLoadingOffline } from '@/components/AppLoading/Offline';
+import AppLoading from '@/components/AppLoading';
import { TenantsContext } from '@/contexts/TenantsProvider';
type Props = {
@@ -39,7 +39,7 @@ const Redirect = ({ tenants, toTenantId }: Props) => {
return
Forbidden
;
}
- return ;
+ return ;
};
export default Redirect;
diff --git a/packages/console/src/cloud/pages/Main/Tenants.tsx b/packages/console/src/cloud/pages/Main/Tenants.tsx
index 64f75ee69..d2ce72d8a 100644
--- a/packages/console/src/cloud/pages/Main/Tenants.tsx
+++ b/packages/console/src/cloud/pages/Main/Tenants.tsx
@@ -2,7 +2,7 @@ import type { TenantInfo } from '@logto/schemas';
import { useCallback, useContext, useEffect } from 'react';
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
-import { AppLoadingOffline } from '@/components/AppLoading/Offline';
+import AppLoading from '@/components/AppLoading';
import Button from '@/components/Button';
import DangerousRaw from '@/components/DangerousRaw';
import { TenantsContext } from '@/contexts/TenantsProvider';
@@ -56,7 +56,7 @@ const Tenants = ({ data, onAdd }: Props) => {
);
}
- return ;
+ return ;
};
export default Tenants;
diff --git a/packages/console/src/cloud/pages/Main/index.tsx b/packages/console/src/cloud/pages/Main/index.tsx
index 4a1689399..a31114f80 100644
--- a/packages/console/src/cloud/pages/Main/index.tsx
+++ b/packages/console/src/cloud/pages/Main/index.tsx
@@ -4,7 +4,7 @@ import { useContext, useEffect } from 'react';
import { useHref } from 'react-router-dom';
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
-import { AppLoadingOffline } from '@/components/AppLoading/Offline';
+import AppLoading from '@/components/AppLoading';
import { TenantsContext } from '@/contexts/TenantsProvider';
import Redirect from './Redirect';
@@ -40,7 +40,7 @@ const Protected = () => {
);
}
- return ;
+ return ;
};
const Main = () => {
@@ -55,7 +55,7 @@ const Main = () => {
}, [href, isAuthenticated, isLoading, signIn]);
if (!isAuthenticated) {
- return ;
+ return ;
}
return ;
diff --git a/packages/console/src/components/AppError/index.tsx b/packages/console/src/components/AppError/index.tsx
index 1eed1eb3c..953aa5d9e 100644
--- a/packages/console/src/components/AppError/index.tsx
+++ b/packages/console/src/components/AppError/index.tsx
@@ -6,8 +6,8 @@ import ErrorDark from '@/assets/images/error-dark.svg';
import Error from '@/assets/images/error.svg';
import KeyboardArrowDown from '@/assets/images/keyboard-arrow-down.svg';
import KeyboardArrowUp from '@/assets/images/keyboard-arrow-up.svg';
+import useTheme from '@/hooks/use-theme';
import { onKeyDownHandler } from '@/utils/a11y';
-import { getThemeFromLocalStorage } from '@/utils/theme';
import * as styles from './index.module.scss';
@@ -22,7 +22,7 @@ type Props = {
const AppError = ({ title, errorCode, errorMessage, callStack, children }: Props) => {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const [isDetailsOpen, setIsDetailsOpen] = useState(false);
- const theme = getThemeFromLocalStorage(); // Should be able to use the component in an offline environment
+ const theme = useTheme();
return (
diff --git a/packages/console/src/components/AppLoading/Offline.tsx b/packages/console/src/components/AppLoading/Offline.tsx
deleted file mode 100644
index 89c5b5600..000000000
--- a/packages/console/src/components/AppLoading/Offline.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { Theme } from '@logto/schemas';
-
-import IllustrationDark from '@/assets/images/loading-illustration-dark.svg';
-import Illustration from '@/assets/images/loading-illustration.svg';
-import { Daisy as Spinner } from '@/components/Spinner';
-import { getThemeFromLocalStorage } from '@/utils/theme';
-
-import * as styles from './index.module.scss';
-
-/**
- * An fullscreen loading component fetches local stored theme without sending request.
- */
-export const AppLoadingOffline = () => {
- const theme = getThemeFromLocalStorage();
-
- return (
-
- {theme === Theme.Light ? : }
-
-
- );
-};
diff --git a/packages/console/src/contexts/AppThemeProvider/index.tsx b/packages/console/src/contexts/AppThemeProvider/index.tsx
index 244e1a496..6a79ca0d4 100644
--- a/packages/console/src/contexts/AppThemeProvider/index.tsx
+++ b/packages/console/src/contexts/AppThemeProvider/index.tsx
@@ -1,15 +1,17 @@
import { Theme } from '@logto/schemas';
-import { conditionalString } from '@silverhand/essentials';
+import { conditionalString, trySafe } from '@silverhand/essentials';
import type { ReactNode } from 'react';
import { useEffect, useMemo, useState, createContext } from 'react';
-import useUserPreferences from '@/hooks/use-user-preferences';
-import { DynamicAppearanceMode } from '@/types/appearance-mode';
+import { appearanceModeStorageKey } from '@/consts';
+import type { AppearanceMode } from '@/types/appearance-mode';
+import { appearanceModeGuard, DynamicAppearanceMode } from '@/types/appearance-mode';
import * as styles from './index.module.scss';
type Props = {
fixedTheme?: Theme;
+ appearanceMode?: AppearanceMode;
children: ReactNode;
};
@@ -23,17 +25,17 @@ const darkThemeWatchMedia = window.matchMedia('(prefers-color-scheme: dark)');
const getThemeBySystemConfiguration = (): Theme =>
darkThemeWatchMedia.matches ? Theme.Dark : Theme.Light;
+export const getAppearanceModeFromLocalStorage = (): AppearanceMode =>
+ trySafe(() => appearanceModeGuard.parse(localStorage.getItem(appearanceModeStorageKey))) ??
+ DynamicAppearanceMode.System;
+
export const AppThemeContext = createContext
({
theme: defaultTheme,
});
-export const AppThemeProvider = ({ fixedTheme, children }: Props) => {
+export const AppThemeProvider = ({ fixedTheme, appearanceMode, children }: Props) => {
const [theme, setTheme] = useState(defaultTheme);
- const {
- data: { appearanceMode },
- } = useUserPreferences();
-
useEffect(() => {
if (fixedTheme) {
setTheme(fixedTheme);
@@ -41,8 +43,11 @@ export const AppThemeProvider = ({ fixedTheme, children }: Props) => {
return;
}
- if (appearanceMode !== DynamicAppearanceMode.System) {
- setTheme(appearanceMode);
+ // Note: if the appearanceMode is not available, attempt to retrieve the last saved value from localStorage.
+ const appliedAppearanceMode = appearanceMode ?? getAppearanceModeFromLocalStorage();
+
+ if (appliedAppearanceMode !== DynamicAppearanceMode.System) {
+ setTheme(appliedAppearanceMode);
return;
}
diff --git a/packages/console/src/hooks/use-user-preferences.ts b/packages/console/src/hooks/use-user-preferences.ts
index 8e8f49e91..bf9dbd046 100644
--- a/packages/console/src/hooks/use-user-preferences.ts
+++ b/packages/console/src/hooks/use-user-preferences.ts
@@ -3,8 +3,8 @@ import { useEffect, useMemo } from 'react';
import { z } from 'zod';
import { appearanceModeStorageKey } from '@/consts';
+import { getAppearanceModeFromLocalStorage } from '@/contexts/AppThemeProvider';
import { appearanceModeGuard } from '@/types/appearance-mode';
-import { getAppearanceModeFromLocalStorage } from '@/utils/theme';
import useMeCustomData from './use-me-custom-data';
diff --git a/packages/console/src/pages/Main/index.tsx b/packages/console/src/pages/Main/index.tsx
index 445e705a0..50af7fd41 100644
--- a/packages/console/src/pages/Main/index.tsx
+++ b/packages/console/src/pages/Main/index.tsx
@@ -8,6 +8,7 @@ import AppContent from '@/containers/AppContent';
import ConsoleContent from '@/containers/ConsoleContent';
import { AppThemeProvider } from '@/contexts/AppThemeProvider';
import useSwrOptions from '@/hooks/use-swr-options';
+import useUserPreferences from '@/hooks/use-user-preferences';
import Callback from '@/pages/Callback';
import Welcome from '@/pages/Welcome';
@@ -15,11 +16,14 @@ import HandleSocialCallback from '../Profile/containers/HandleSocialCallback';
const Main = () => {
const swrOptions = useSwrOptions();
+ const {
+ data: { appearanceMode },
+ } = useUserPreferences();
return (
-
+
diff --git a/packages/console/src/utils/theme.ts b/packages/console/src/utils/theme.ts
deleted file mode 100644
index 4a6423405..000000000
--- a/packages/console/src/utils/theme.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { Theme } from '@logto/schemas';
-import { trySafe } from '@silverhand/essentials';
-
-import { appearanceModeStorageKey } from '@/consts';
-import { appearanceModeGuard, DynamicAppearanceMode } from '@/types/appearance-mode';
-import type { AppearanceMode } from '@/types/appearance-mode';
-
-export const getTheme = (appearanceMode: AppearanceMode): Theme => {
- if (appearanceMode !== DynamicAppearanceMode.System) {
- return appearanceMode;
- }
-
- const darkThemeWatchMedia = window.matchMedia('(prefers-color-scheme: dark)');
- const theme = darkThemeWatchMedia.matches ? Theme.Dark : Theme.Light;
-
- return theme;
-};
-
-export const getThemeFromLocalStorage = () => getTheme(getAppearanceModeFromLocalStorage());
-
-export const getAppearanceModeFromLocalStorage = (): AppearanceMode =>
- trySafe(() => appearanceModeGuard.parse(localStorage.getItem(appearanceModeStorageKey))) ??
- DynamicAppearanceMode.System;