diff --git a/frontend/src/components/account/ThemeSwitcher.tsx b/frontend/src/components/account/ThemeSwitcher.tsx new file mode 100644 index 0000000..9bd7782 --- /dev/null +++ b/frontend/src/components/account/ThemeSwitcher.tsx @@ -0,0 +1,67 @@ +import { + Box, + Center, + ColorScheme, + SegmentedControl, + Stack, + useMantineColorScheme, +} from "@mantine/core"; +import { useColorScheme } from "@mantine/hooks"; +import { useState } from "react"; +import { TbDeviceLaptop, TbMoon, TbSun } from "react-icons/tb"; +import usePreferences from "../../hooks/usePreferences"; + +const ThemeSwitcher = () => { + const preferences = usePreferences(); + const [colorScheme, setColorScheme] = useState( + preferences.get("colorScheme") + ); + const { toggleColorScheme } = useMantineColorScheme(); + const systemColorScheme = useColorScheme(); + + return ( + + { + preferences.set("colorScheme", value); + setColorScheme(value); + toggleColorScheme( + value == "system" ? systemColorScheme : (value as ColorScheme) + ); + }} + data={[ + { + label: ( +
+ + Dark +
+ ), + value: "dark", + }, + { + label: ( +
+ + Light +
+ ), + value: "light", + }, + { + label: ( +
+ + System +
+ ), + value: "system", + }, + ]} + /> +
+ ); +}; + +export default ThemeSwitcher; diff --git a/frontend/src/hooks/usePreferences.ts b/frontend/src/hooks/usePreferences.ts new file mode 100644 index 0000000..14d3221 --- /dev/null +++ b/frontend/src/hooks/usePreferences.ts @@ -0,0 +1,30 @@ +const defaultPreferences = [ + { + key: "colorScheme", + value: "system", + }, +]; + +const get = (key: string) => { + if (typeof window !== "undefined") { + const preferences = JSON.parse(localStorage.getItem("preferences") ?? "{}"); + return ( + preferences[key] ?? + defaultPreferences.find((p) => p.key == key)?.value ?? + null + ); + } +}; + +const set = (key: string, value: string) => { + if (typeof window !== "undefined") { + const preferences = JSON.parse(localStorage.getItem("preferences") ?? "{}"); + preferences[key] = value; + localStorage.setItem("preferences", JSON.stringify(preferences)); + } +}; +const usePreferences = () => { + return { get, set }; +}; + +export default usePreferences; diff --git a/frontend/src/pages/_app.tsx b/frontend/src/pages/_app.tsx index 6db2d39..93bbbe9 100644 --- a/frontend/src/pages/_app.tsx +++ b/frontend/src/pages/_app.tsx @@ -1,5 +1,6 @@ import { ColorScheme, + ColorSchemeProvider, Container, LoadingOverlay, MantineProvider, @@ -11,7 +12,8 @@ import type { AppProps } from "next/app"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import Header from "../components/navBar/NavBar"; -import useConfig, { ConfigContext } from "../hooks/config.hook"; +import { ConfigContext } from "../hooks/config.hook"; +import usePreferences from "../hooks/usePreferences"; import { UserContext } from "../hooks/user.hook"; import authService from "../services/auth.service"; import configService from "../services/config.service"; @@ -25,9 +27,9 @@ import { GlobalLoadingContext } from "../utils/loading.util"; function App({ Component, pageProps }: AppProps) { const systemTheme = useColorScheme(); const router = useRouter(); - const config = useConfig(); + const preferences = usePreferences(); - const [colorScheme, setColorScheme] = useState(); + const [colorScheme, setColorScheme] = useState("light"); const [isLoading, setIsLoading] = useState(true); const [user, setUser] = useState(null); const [configVariables, setConfigVariables] = useState(null); @@ -56,7 +58,11 @@ function App({ Component, pageProps }: AppProps) { }, [router.asPath]); useEffect(() => { - setColorScheme(systemTheme); + setColorScheme( + preferences.get("colorScheme") == "system" + ? systemTheme + : preferences.get("colorScheme") + ); }, [systemTheme]); return ( @@ -65,26 +71,31 @@ function App({ Component, pageProps }: AppProps) { withNormalizeCSS theme={{ colorScheme, ...globalStyle }} > - - - - - {isLoading ? ( - - ) : ( - - - -
- - - - {" "} - - )} - - - + setColorScheme(value ?? "light")} + > + + + + + {isLoading ? ( + + ) : ( + + + +
+ + + + {" "} + + )} + + + + ); } diff --git a/frontend/src/pages/account/index.tsx b/frontend/src/pages/account/index.tsx index 3fde837..1248c31 100644 --- a/frontend/src/pages/account/index.tsx +++ b/frontend/src/pages/account/index.tsx @@ -17,6 +17,7 @@ import { useRouter } from "next/router"; import { Tb2Fa } from "react-icons/tb"; import * as yup from "yup"; import showEnableTotpModal from "../../components/account/showEnableTotpModal"; +import ThemeSwitcher from "../../components/account/ThemeSwitcher"; import useUser from "../../hooks/user.hook"; import authService from "../../services/auth.service"; import userService from "../../services/user.service"; @@ -164,8 +165,6 @@ const Account = () => { - {/* TODO: This is ugly, make it prettier */} - {/* If we have totp enabled, show different text */} {user.totpVerified ? ( <>
{ - -
+ + + Color scheme + + + +