mirror of
https://github.com/makeplane/plane.git
synced 2026-07-12 21:40:18 +02:00
fix: refactored config screen for multiple options
This commit is contained in:
@@ -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<I_THEME_OPTION | null>(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<IUserTheme>) => {
|
||||
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 (
|
||||
<>
|
||||
<PageHead title="Profile - Appearance" />
|
||||
{userProfile ? (
|
||||
<ProfileSettingContentWrapper>
|
||||
<ProfileSettingContentHeader title="Appearance" />
|
||||
<div className="grid grid-cols-12 gap-4 py-6 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold text-custom-text-100">Theme</h4>
|
||||
<p className="text-sm text-custom-text-200">Select or customize your interface color scheme.</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<ThemeSwitch value={currentTheme} onChange={handleThemeChange} />
|
||||
</div>
|
||||
</div>
|
||||
{userProfile?.theme?.theme === "custom" && <CustomThemeSelector applyThemeChange={applyThemeChange} />}
|
||||
<ProfileSettingContentHeader title="Preferences" />
|
||||
<PreferencesList />
|
||||
</ProfileSettingContentWrapper>
|
||||
) : (
|
||||
<div className="grid h-full w-full place-items-center px-4 sm:px-0">
|
||||
|
||||
24
web/core/components/preferences/config.ts
Normal file
24
web/core/components/preferences/config.ts
Normal file
@@ -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,
|
||||
},
|
||||
];
|
||||
2
web/core/components/preferences/index.ts
Normal file
2
web/core/components/preferences/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./theme-switcher";
|
||||
export * from "./section";
|
||||
23
web/core/components/preferences/list.tsx
Normal file
23
web/core/components/preferences/list.tsx
Normal file
@@ -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 (
|
||||
<div className="space-y-6">
|
||||
{PREFERENCE_OPTIONS.map((option) => {
|
||||
const Component = option.component;
|
||||
return <Component key={option.id} option={option} />;
|
||||
return (
|
||||
<PreferencesSection
|
||||
key={option.id}
|
||||
title={option.title}
|
||||
description={option.description}
|
||||
control={<Component />}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
15
web/core/components/preferences/section.tsx
Normal file
15
web/core/components/preferences/section.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
interface SettingsSectionProps {
|
||||
title: string;
|
||||
description: string;
|
||||
control: React.ReactNode;
|
||||
}
|
||||
|
||||
export const PreferencesSection = ({ title, description, control }: SettingsSectionProps) => (
|
||||
<div className="grid grid-cols-12 gap-4 py-6 sm:gap-16">
|
||||
<div className="col-span-12 sm:col-span-6">
|
||||
<h4 className="text-lg font-semibold text-custom-text-100">{title}</h4>
|
||||
<p className="text-sm text-custom-text-200">{description}</p>
|
||||
</div>
|
||||
<div className="col-span-12 sm:col-span-6">{control}</div>
|
||||
</div>
|
||||
);
|
||||
24
web/core/components/preferences/smooth-cursor-toggle.tsx
Normal file
24
web/core/components/preferences/smooth-cursor-toggle.tsx
Normal file
@@ -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 (
|
||||
<PreferencesSection
|
||||
title={props.option.title}
|
||||
description={props.option.description}
|
||||
control={
|
||||
<ToggleSwitch
|
||||
value={true}
|
||||
onChange={() => {
|
||||
console.log("toggled");
|
||||
}}
|
||||
label={"smooth-cursor-toggle"}
|
||||
size={"sm"}
|
||||
className={"some"}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
92
web/core/components/preferences/theme-switcher.tsx
Normal file
92
web/core/components/preferences/theme-switcher.tsx
Normal file
@@ -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<I_THEME_OPTION | null>(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<IUserTheme>) => {
|
||||
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 (
|
||||
<>
|
||||
<PreferencesSection
|
||||
title={props.option.title}
|
||||
description={props.option.description}
|
||||
control={<ThemeSwitch value={currentTheme} onChange={handleThemeChange} />}
|
||||
/>
|
||||
{userProfile.theme?.theme === "custom" && <CustomThemeSelector applyThemeChange={applyThemeChange} />}
|
||||
</>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user