From e95cd1e38530678e19ca08fc1be8770529f2f024 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Mon, 6 May 2024 14:00:19 +0530 Subject: [PATCH] chore: profile themning --- packages/types/src/users.d.ts | 20 ++++---- .../core/theme/custom-theme-selector.tsx | 45 +++++++++------- web/lib/wrappers/store-wrapper.tsx | 19 ++++--- web/pages/profile/preferences/theme.tsx | 51 ++++++++----------- web/store/theme.store.ts | 43 ++-------------- web/store/user/profile.store.ts | 38 +++++++++++++- yarn.lock | 2 +- 7 files changed, 108 insertions(+), 110 deletions(-) diff --git a/packages/types/src/users.d.ts b/packages/types/src/users.d.ts index 452455876..c191cac89 100644 --- a/packages/types/src/users.d.ts +++ b/packages/types/src/users.d.ts @@ -27,7 +27,7 @@ export interface IUser { user_timezone: string; username: string; last_login_medium: TLoginMediums; - // theme: IUserTheme; + theme: IUserTheme; } export interface IUserAccount { @@ -48,7 +48,7 @@ export type TUserProfile = { palette: string | undefined; primary: string | undefined; background: string | undefined; - darkPalette: string | undefined; + darkPalette: boolean | undefined; sidebarText: string | undefined; sidebarBackground: string | undefined; }; @@ -80,14 +80,14 @@ export interface IUserSettings { } export interface IUserTheme { - background: string; - text: string; - primary: string; - sidebarBackground: string; - sidebarText: string; - darkPalette: boolean; - palette: string; - theme: string; + text: string | undefined; + theme: string | undefined; + palette: string | undefined; + primary: string | undefined; + background: string | undefined; + darkPalette: boolean | undefined; + sidebarText: string | undefined; + sidebarBackground: string | undefined; } export interface IUserLite { diff --git a/web/components/core/theme/custom-theme-selector.tsx b/web/components/core/theme/custom-theme-selector.tsx index 9309a3bfe..d7e115a19 100644 --- a/web/components/core/theme/custom-theme-selector.tsx +++ b/web/components/core/theme/custom-theme-selector.tsx @@ -4,9 +4,9 @@ import { Controller, useForm } from "react-hook-form"; // types import { IUserTheme } from "@plane/types"; // ui -import { Button, InputColorPicker } from "@plane/ui"; +import { Button, InputColorPicker, setPromiseToast } from "@plane/ui"; // hooks -import { useUser } from "@/hooks/store"; +import { useUserProfile } from "@/hooks/store"; const inputRules = { required: "Background color is required", @@ -25,13 +25,9 @@ const inputRules = { }; export const CustomThemeSelector: React.FC = observer(() => { - const { - userProfile: { data: userProfile }, - } = useUser(); - - const userTheme: any = userProfile?.theme; - // hooks const { setTheme } = useTheme(); + // hooks + const { data: userProfile, updateUserTheme } = useUserProfile(); const { control, @@ -40,17 +36,18 @@ export const CustomThemeSelector: React.FC = observer(() => { watch, } = useForm({ defaultValues: { - background: userTheme?.background !== "" ? userTheme?.background : "#0d101b", - text: userTheme?.text !== "" ? userTheme?.text : "#c5c5c5", - primary: userTheme?.primary !== "" ? userTheme?.primary : "#3f76ff", - sidebarBackground: userTheme?.sidebarBackground !== "" ? userTheme?.sidebarBackground : "#0d101b", - sidebarText: userTheme?.sidebarText !== "" ? userTheme?.sidebarText : "#c5c5c5", - darkPalette: userTheme?.darkPalette || false, - palette: userTheme?.palette !== "" ? userTheme?.palette : "", + background: userProfile?.theme?.background !== "" ? userProfile?.theme?.background : "#0d101b", + text: userProfile?.theme?.text !== "" ? userProfile?.theme?.text : "#c5c5c5", + primary: userProfile?.theme?.primary !== "" ? userProfile?.theme?.primary : "#3f76ff", + sidebarBackground: + userProfile?.theme?.sidebarBackground !== "" ? userProfile?.theme?.sidebarBackground : "#0d101b", + sidebarText: userProfile?.theme?.sidebarText !== "" ? userProfile?.theme?.sidebarText : "#c5c5c5", + darkPalette: userProfile?.theme?.darkPalette || false, + palette: userProfile?.theme?.palette !== "" ? userProfile?.theme?.palette : "", }, }); - const handleUpdateTheme = async (formData: any) => { + const handleUpdateTheme = async (formData: Partial) => { const payload: IUserTheme = { background: formData.background, text: formData.text, @@ -61,12 +58,22 @@ export const CustomThemeSelector: React.FC = observer(() => { palette: `${formData.background},${formData.text},${formData.primary},${formData.sidebarBackground},${formData.sidebarText}`, theme: "custom", }; - setTheme("custom"); - console.log(payload); + const updateCurrentUserThemePromise = updateUserTheme(payload); + setPromiseToast(updateCurrentUserThemePromise, { + loading: "Updating theme...", + success: { + title: "Success!", + message: () => "Theme updated successfully!", + }, + error: { + title: "Error!", + message: () => "Failed to Update the theme", + }, + }); - // return updateUserProfile({ theme: payload }); + return; }; const handleValueChange = (val: string | undefined, onChange: any) => { diff --git a/web/lib/wrappers/store-wrapper.tsx b/web/lib/wrappers/store-wrapper.tsx index 1d9a4f9c1..ec5b79bfa 100644 --- a/web/lib/wrappers/store-wrapper.tsx +++ b/web/lib/wrappers/store-wrapper.tsx @@ -5,7 +5,7 @@ import { useTheme } from "next-themes"; // helpers import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper"; // hooks -import { useAppRouter, useAppTheme, useUser } from "@/hooks/store"; +import { useAppRouter, useAppTheme, useUserProfile } from "@/hooks/store"; type TStoreWrapper = { children: ReactNode; @@ -20,9 +20,7 @@ const StoreWrapper: FC = observer((props) => { // store hooks const { setQuery } = useAppRouter(); const { sidebarCollapsed, toggleSidebar } = useAppTheme(); - const { - userProfile: { data: userProfile }, - } = useUser(); + const { data: userProfile } = useUserProfile(); // states const [dom, setDom] = useState(); @@ -40,14 +38,19 @@ const StoreWrapper: FC = observer((props) => { * Setting up the theme of the user by fetching it from local storage */ useEffect(() => { - if (!userProfile) return; - if (window) setDom(window.document?.querySelector("[data-theme='custom']") || undefined); + if (!userProfile?.theme?.theme) return; + if (window) setDom(() => window.document?.querySelector("[data-theme='custom']") || undefined); setTheme(userProfile?.theme?.theme || "system"); if (userProfile?.theme?.theme === "custom" && userProfile?.theme?.palette && dom) - applyTheme(userProfile?.theme?.palette, false); + applyTheme( + userProfile?.theme?.palette !== ",,,," + ? userProfile?.theme?.palette + : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", + false + ); else unsetCustomCssVariables(); - }, [userProfile, setTheme, dom]); + }, [userProfile, userProfile?.theme?.theme, userProfile?.theme?.palette, setTheme, dom]); useEffect(() => { if (!router.query) return; diff --git a/web/pages/profile/preferences/theme.tsx b/web/pages/profile/preferences/theme.tsx index a80001eda..7c5593333 100644 --- a/web/pages/profile/preferences/theme.tsx +++ b/web/pages/profile/preferences/theme.tsx @@ -2,64 +2,55 @@ import { useEffect, useState, ReactElement } from "react"; import { observer } from "mobx-react"; import { useTheme } from "next-themes"; // ui -import { - Spinner, - // setPromiseToast -} from "@plane/ui"; +import { Spinner, setPromiseToast } from "@plane/ui"; // components import { CustomThemeSelector, ThemeSwitch, PageHead } from "@/components/core"; // constants import { I_THEME_OPTION, THEME_OPTIONS } from "@/constants/themes"; // hooks -import { useUser } from "@/hooks/store"; +import { useUserProfile } from "@/hooks/store"; // layouts import { ProfilePreferenceSettingsLayout } from "@/layouts/settings-layout/profile/preferences"; // type import { NextPageWithLayout } from "@/lib/types"; const ProfilePreferencesThemePage: NextPageWithLayout = observer(() => { + const { setTheme } = useTheme(); // states const [currentTheme, setCurrentTheme] = useState(null); - // store hooks - const { - data: currentUser, - userProfile: { data: userProfile }, - } = useUser(); - // computed - const userTheme = userProfile?.theme; // hooks - const { setTheme } = useTheme(); + const { data: userProfile, updateUserTheme } = useUserProfile(); useEffect(() => { - if (userTheme) { - const userThemeOption = THEME_OPTIONS.find((t) => t.value === userTheme?.theme); + if (userProfile?.theme?.theme) { + const userThemeOption = THEME_OPTIONS.find((t) => t.value === userProfile?.theme?.theme); if (userThemeOption) { setCurrentTheme(userThemeOption); } } - }, [userTheme]); + }, [userProfile?.theme?.theme]); const handleThemeChange = (themeOption: I_THEME_OPTION) => { setTheme(themeOption.value); - // const updateCurrentUserThemePromise = updateCurrentUserTheme(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", - // }, - // }); + setPromiseToast(updateCurrentUserThemePromise, { + loading: "Updating theme...", + success: { + title: "Success!", + message: () => "Theme updated successfully!", + }, + error: { + title: "Error!", + message: () => "Failed to Update the theme", + }, + }); }; return ( <> - {currentUser ? ( + {userProfile ? (

Preferences

@@ -73,7 +64,7 @@ const ProfilePreferencesThemePage: NextPageWithLayout = observer(() => {
- {userTheme?.theme === "custom" && } + {userProfile?.theme?.theme === "custom" && } ) : (
diff --git a/web/store/theme.store.ts b/web/store/theme.store.ts index da499a63c..33537fe79 100644 --- a/web/store/theme.store.ts +++ b/web/store/theme.store.ts @@ -1,18 +1,15 @@ -// mobx import { action, observable, makeObservable } from "mobx"; -// helper -import { applyTheme, unsetCustomCssVariables } from "@/helpers/theme.helper"; +// store types +import { RootStore } from "@/store/root.store"; export interface IThemeStore { // observables - theme: string | null; sidebarCollapsed: boolean | undefined; profileSidebarCollapsed: boolean | undefined; workspaceAnalyticsSidebarCollapsed: boolean | undefined; issueDetailSidebarCollapsed: boolean | undefined; // actions toggleSidebar: (collapsed?: boolean) => void; - setTheme: (theme: any) => void; toggleProfileSidebar: (collapsed?: boolean) => void; toggleWorkspaceAnalyticsSidebar: (collapsed?: boolean) => void; toggleIssueDetailSidebar: (collapsed?: boolean) => void; @@ -21,31 +18,23 @@ export interface IThemeStore { export class ThemeStore implements IThemeStore { // observables sidebarCollapsed: boolean | undefined = undefined; - theme: string | null = null; profileSidebarCollapsed: boolean | undefined = undefined; workspaceAnalyticsSidebarCollapsed: boolean | undefined = undefined; issueDetailSidebarCollapsed: boolean | undefined = undefined; - // root store - rootStore; - constructor(_rootStore: any | null = null) { + constructor(private store: RootStore) { makeObservable(this, { // observable sidebarCollapsed: observable.ref, - theme: observable.ref, profileSidebarCollapsed: observable.ref, workspaceAnalyticsSidebarCollapsed: observable.ref, issueDetailSidebarCollapsed: observable.ref, // action toggleSidebar: action, - setTheme: action, toggleProfileSidebar: action, toggleWorkspaceAnalyticsSidebar: action, toggleIssueDetailSidebar: action, - // computed }); - // root store - this.rootStore = _rootStore; } /** @@ -95,30 +84,4 @@ export class ThemeStore implements IThemeStore { } localStorage.setItem("issue_detail_sidebar_collapsed", this.issueDetailSidebarCollapsed.toString()); }; - - /** - * Sets the user theme and applies it to the platform - * @param _theme - */ - setTheme = async (_theme: { theme: any }) => { - try { - const currentTheme: string = _theme?.theme?.theme?.toString(); - // updating the local storage theme value - localStorage.setItem("theme", currentTheme); - // updating the mobx theme value - this.theme = currentTheme; - // applying the theme to platform if the selected theme is custom - if (currentTheme === "custom") { - const themeSettings = this.rootStore.user.currentUserSettings || null; - applyTheme( - themeSettings?.theme?.palette !== ",,,," - ? themeSettings?.theme?.palette - : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5", - themeSettings?.theme?.darkPalette - ); - } else unsetCustomCssVariables(); - } catch (error) { - console.error("setting user theme error", error); - } - }; } diff --git a/web/store/user/profile.store.ts b/web/store/user/profile.store.ts index 51c9f0a2d..606cbe6bf 100644 --- a/web/store/user/profile.store.ts +++ b/web/store/user/profile.store.ts @@ -1,9 +1,11 @@ +import cloneDeep from "lodash/cloneDeep"; import set from "lodash/set"; import { action, makeObservable, observable, runInAction } from "mobx"; // services import { UserService } from "services/user.service"; // types -import { TUserProfile } from "@plane/types"; +import { IUserTheme, TUserProfile } from "@plane/types"; +import { RootStore } from "@/store/root.store"; type TError = { status: string; @@ -20,6 +22,7 @@ export interface IUserProfileStore { updateUserProfile: (data: Partial) => Promise; updateUserOnBoard: () => Promise; updateTourCompleted: () => Promise; + updateUserTheme: (data: Partial) => Promise; } export class ProfileStore implements IUserProfileStore { @@ -59,7 +62,7 @@ export class ProfileStore implements IUserProfileStore { // services userService: UserService; - constructor() { + constructor(public store: RootStore) { makeObservable(this, { // observables isLoading: observable.ref, @@ -70,6 +73,7 @@ export class ProfileStore implements IUserProfileStore { updateUserProfile: action, updateUserOnBoard: action, updateTourCompleted: action, + updateUserTheme: action, }); // services this.userService = new UserService(); @@ -179,4 +183,34 @@ export class ProfileStore implements IUserProfileStore { throw error; } }; + + /** + * @description updates the user theme + * @returns @returns {Promise} + */ + updateUserTheme = async (data: Partial) => { + const currentProfileTheme = cloneDeep(this.data.theme); + try { + runInAction(() => { + Object.keys(data).forEach((key: string) => { + const userKey: keyof IUserTheme = key as keyof IUserTheme; + if (this.data.theme) set(this.data.theme, userKey, data[userKey]); + }); + }); + const userProfile = await this.userService.updateCurrentUserProfile({ theme: this.data.theme }); + return userProfile; + } catch (error) { + runInAction(() => { + Object.keys(data).forEach((key: string) => { + const userKey: keyof IUserTheme = key as keyof IUserTheme; + if (currentProfileTheme) set(this.data.theme, userKey, currentProfileTheme[userKey]); + }); + this.error = { + status: "user-profile-theme-update-error", + message: "Failed to update user profile theme", + }; + }); + throw error; + } + }; } diff --git a/yarn.lock b/yarn.lock index f8ed88233..50d914594 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2759,7 +2759,7 @@ dependencies: "@types/react" "*" -"@types/react@*", "@types/react@^18.2.42", "@types/react@^18.2.48": +"@types/react@*", "@types/react@18.2.48", "@types/react@^18.2.42", "@types/react@^18.2.48": version "18.2.48" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.48.tgz#11df5664642d0bd879c1f58bc1d37205b064e8f1" integrity sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==