diff --git a/web/app/profile/appearance/page.tsx b/web/app/profile/appearance/page.tsx index 775ff637b0..378695415c 100644 --- a/web/app/profile/appearance/page.tsx +++ b/web/app/profile/appearance/page.tsx @@ -1,78 +1,25 @@ "use client"; -import { useEffect, useState } from "react"; import { observer } from "mobx-react"; -import { useTheme } from "next-themes"; -import { IUserTheme } from "@plane/types"; -import { setPromiseToast } from "@plane/ui"; // components import { LogoSpinner } from "@/components/common"; -import { CustomThemeSelector, ThemeSwitch, PageHead } from "@/components/core"; +import { PageHead } from "@/components/core"; +import { PreferencesList } from "@/components/preferences/list"; import { ProfileSettingContentHeader, ProfileSettingContentWrapper } from "@/components/profile"; -// constants -import { I_THEME_OPTION, THEME_OPTIONS } from "@/constants/themes"; -// helpers -import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper"; // hooks import { useUserProfile } from "@/hooks/store"; const ProfileAppearancePage = observer(() => { - const { setTheme } = useTheme(); - // states - const [currentTheme, setCurrentTheme] = useState(null); // hooks - const { data: userProfile, updateUserTheme } = useUserProfile(); - - useEffect(() => { - if (userProfile?.theme?.theme) { - const userThemeOption = THEME_OPTIONS.find((t) => t.value === userProfile?.theme?.theme); - if (userThemeOption) { - setCurrentTheme(userThemeOption); - } - } - }, [userProfile?.theme?.theme]); - - const handleThemeChange = (themeOption: I_THEME_OPTION) => { - applyThemeChange({ theme: themeOption.value }); - - const updateCurrentUserThemePromise = updateUserTheme({ theme: themeOption.value }); - setPromiseToast(updateCurrentUserThemePromise, { - loading: "Updating theme...", - success: { - title: "Success!", - message: () => "Theme updated successfully!", - }, - error: { - title: "Error!", - message: () => "Failed to Update the theme", - }, - }); - }; - - const applyThemeChange = (theme: Partial) => { - setTheme(theme?.theme || "system"); - - if (theme?.theme === "custom" && theme?.palette) { - applyTheme(theme?.palette !== ",,,," ? theme?.palette : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", false); - } else unsetCustomCssVariables(); - }; + const { data: userProfile } = useUserProfile(); return ( <> {userProfile ? ( - -
-
-

Theme

-

Select or customize your interface color scheme.

-
-
- -
-
- {userProfile?.theme?.theme === "custom" && } + +
) : (
diff --git a/web/core/components/preferences/config.ts b/web/core/components/preferences/config.ts new file mode 100644 index 0000000000..bd025fc987 --- /dev/null +++ b/web/core/components/preferences/config.ts @@ -0,0 +1,24 @@ +import { SmoothCursorToggle } from "./smooth-cursor-toggle"; +import { ThemeSwitcher } from "./theme-switcher"; + +export interface PreferenceOption { + id: string; + title: string; + description: string; + component: React.ComponentType<{ option: PreferenceOption }>; +} + +export const PREFERENCE_OPTIONS: PreferenceOption[] = [ + { + id: "theme", + title: "Theme", + description: "Select or customize your interface color scheme.", + component: ThemeSwitcher, + }, + { + id: "smoothCursor", + title: "Smooth cursor movement", + description: "Select the cursor motion style that feels right for you", + component: SmoothCursorToggle, + }, +]; diff --git a/web/core/components/preferences/index.ts b/web/core/components/preferences/index.ts new file mode 100644 index 0000000000..c03d1d593e --- /dev/null +++ b/web/core/components/preferences/index.ts @@ -0,0 +1,2 @@ +export * from "./theme-switcher"; +export * from "./section"; diff --git a/web/core/components/preferences/list.tsx b/web/core/components/preferences/list.tsx new file mode 100644 index 0000000000..0dc748d503 --- /dev/null +++ b/web/core/components/preferences/list.tsx @@ -0,0 +1,23 @@ +import { observer } from "mobx-react"; +import { PREFERENCE_OPTIONS } from "./config"; +import { PreferencesSection } from "./section"; + +export const PreferencesList = observer(() => { + console.log("list"); + return ( +
+ {PREFERENCE_OPTIONS.map((option) => { + const Component = option.component; + return ; + return ( + } + /> + ); + })} +
+ ); +}); diff --git a/web/core/components/preferences/section.tsx b/web/core/components/preferences/section.tsx new file mode 100644 index 0000000000..fbea24c063 --- /dev/null +++ b/web/core/components/preferences/section.tsx @@ -0,0 +1,15 @@ +interface SettingsSectionProps { + title: string; + description: string; + control: React.ReactNode; +} + +export const PreferencesSection = ({ title, description, control }: SettingsSectionProps) => ( +
+
+

{title}

+

{description}

+
+
{control}
+
+); diff --git a/web/core/components/preferences/smooth-cursor-toggle.tsx b/web/core/components/preferences/smooth-cursor-toggle.tsx new file mode 100644 index 0000000000..d8b523fc37 --- /dev/null +++ b/web/core/components/preferences/smooth-cursor-toggle.tsx @@ -0,0 +1,24 @@ +import { ToggleSwitch } from "@plane/ui"; +import { PreferenceOption } from "./config"; +import { PreferencesSection } from "."; + +export const SmoothCursorToggle = (props: { option: PreferenceOption }) => { + console.log("toggled"); + return ( + { + console.log("toggled"); + }} + label={"smooth-cursor-toggle"} + size={"sm"} + className={"some"} + /> + } + /> + ); +}; diff --git a/web/core/components/preferences/theme-switcher.tsx b/web/core/components/preferences/theme-switcher.tsx new file mode 100644 index 0000000000..90e13bb289 --- /dev/null +++ b/web/core/components/preferences/theme-switcher.tsx @@ -0,0 +1,92 @@ +"use client"; + +import { useEffect, useState, useCallback } from "react"; +import { observer } from "mobx-react"; +import { useTheme } from "next-themes"; +import { IUserTheme } from "@plane/types"; +import { setPromiseToast } from "@plane/ui"; + +// components +import { CustomThemeSelector, ThemeSwitch } from "@/components/core"; +// constants +import { I_THEME_OPTION, THEME_OPTIONS } from "@/constants/themes"; +// helpers +import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper"; +// hooks +import { useUserProfile } from "@/hooks/store"; +import { PreferenceOption } from "./config"; +import { PreferencesSection } from "."; + +export const ThemeSwitcher = observer((props: { option: PreferenceOption }) => { + // hooks + const { setTheme } = useTheme(); + const { data: userProfile, updateUserTheme } = useUserProfile(); + + // states + const [currentTheme, setCurrentTheme] = useState(null); + + // initialize theme + useEffect(() => { + if (!userProfile?.theme?.theme) return; + + const userThemeOption = THEME_OPTIONS.find((t) => t.value === userProfile.theme.theme); + + if (userThemeOption) { + setCurrentTheme(userThemeOption); + } + }, [userProfile?.theme?.theme]); + + // handlers + const applyThemeChange = useCallback( + (theme: Partial) => { + const themeValue = theme?.theme || "system"; + setTheme(themeValue); + + if (theme?.theme === "custom" && theme?.palette) { + const defaultPalette = "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5"; + const palette = theme.palette !== ",,,," ? theme.palette : defaultPalette; + applyTheme(palette, false); + } else { + unsetCustomCssVariables(); + } + }, + [setTheme] + ); + + const handleThemeChange = useCallback( + async (themeOption: I_THEME_OPTION) => { + try { + applyThemeChange({ theme: themeOption.value }); + + const updatePromise = updateUserTheme({ theme: themeOption.value }); + setPromiseToast(updatePromise, { + loading: "Updating theme...", + success: { + title: "Success!", + message: () => "Theme updated successfully!", + }, + error: { + title: "Error!", + message: () => "Failed to update the theme", + }, + }); + } catch (error) { + console.error("Error updating theme:", error); + } + }, + [applyThemeChange, updateUserTheme] + ); + + if (!userProfile) return null; + + return ( + <> + } + /> + {userProfile.theme?.theme === "custom" && } + + ); +});