forked from github/plane
Chore: mobx setup and app sidebar and theme management (#1798)
* dev: Mobx integration for app sidebar and custom theme * dev: Handled edge case and conditional rendering for mobx store
This commit is contained in:
parent
a164dfd532
commit
b6744dcd29
@ -24,8 +24,12 @@ import issuesService from "services/issues.service";
|
||||
import inboxService from "services/inbox.service";
|
||||
// fetch keys
|
||||
import { INBOX_LIST, ISSUE_DETAILS } from "constants/fetch-keys";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const CommandPalette: React.FC = () => {
|
||||
const store: any = useMobxStore();
|
||||
|
||||
const [isPaletteOpen, setIsPaletteOpen] = useState(false);
|
||||
const [isIssueModalOpen, setIsIssueModalOpen] = useState(false);
|
||||
const [isProjectModalOpen, setIsProjectModalOpen] = useState(false);
|
||||
@ -96,7 +100,8 @@ export const CommandPalette: React.FC = () => {
|
||||
setIsIssueModalOpen(true);
|
||||
} else if ((ctrlKey || metaKey) && keyPressed === "b") {
|
||||
e.preventDefault();
|
||||
toggleCollapsed();
|
||||
// toggleCollapsed();
|
||||
store.theme.setSidebarCollapsed(!store?.theme?.sidebarCollapsed);
|
||||
} else if (key === "Delete") {
|
||||
e.preventDefault();
|
||||
setIsBulkDeleteIssuesModalOpen(true);
|
||||
@ -120,7 +125,7 @@ export const CommandPalette: React.FC = () => {
|
||||
}
|
||||
}
|
||||
},
|
||||
[toggleCollapsed, copyIssueUrlToClipboard]
|
||||
[copyIssueUrlToClipboard]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -15,6 +15,8 @@ import userService from "services/user.service";
|
||||
import { applyTheme } from "helpers/theme.helper";
|
||||
// types
|
||||
import { ICustomTheme } from "types";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
type Props = {
|
||||
preLoadedData?: Partial<ICustomTheme> | null;
|
||||
@ -32,6 +34,8 @@ const defaultValues: ICustomTheme = {
|
||||
};
|
||||
|
||||
export const CustomThemeSelector: React.FC<Props> = ({ preLoadedData }) => {
|
||||
const store: any = useMobxStore();
|
||||
|
||||
const [darkPalette, setDarkPalette] = useState(false);
|
||||
|
||||
const {
|
||||
@ -60,21 +64,15 @@ export const CustomThemeSelector: React.FC<Props> = ({ preLoadedData }) => {
|
||||
theme: "custom",
|
||||
};
|
||||
|
||||
await userService
|
||||
.updateUser({
|
||||
theme: payload,
|
||||
})
|
||||
.then((res) => {
|
||||
mutateUser((prevData) => {
|
||||
if (!prevData) return prevData;
|
||||
|
||||
return { ...prevData, ...res };
|
||||
}, false);
|
||||
|
||||
store.user
|
||||
.updateCurrentUserSettings({ theme: payload })
|
||||
.then((response: any) => {
|
||||
setTheme("custom");
|
||||
applyTheme(payload.palette, darkPalette);
|
||||
})
|
||||
.catch((err) => console.log(err));
|
||||
.catch((error: any) => {
|
||||
console.log("error", error);
|
||||
});
|
||||
};
|
||||
|
||||
const handleUpdateTheme = async (formData: any) => {
|
||||
|
@ -21,8 +21,12 @@ import { NotificationsOutlined } from "@mui/icons-material";
|
||||
import emptyNotification from "public/empty-state/notification.svg";
|
||||
// helpers
|
||||
import { getNumberCount } from "helpers/string.helper";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const NotificationPopover = () => {
|
||||
const store: any = useMobxStore();
|
||||
|
||||
const {
|
||||
notifications,
|
||||
archived,
|
||||
@ -77,17 +81,17 @@ export const NotificationPopover = () => {
|
||||
tooltipContent="Notifications"
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!sidebarCollapse}
|
||||
disabled={!store?.theme?.sidebarCollapsed}
|
||||
>
|
||||
<Popover.Button
|
||||
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||
isActive
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
|
||||
} ${sidebarCollapse ? "justify-center" : ""}`}
|
||||
} ${store?.theme?.sidebarCollapsed ? "justify-center" : ""}`}
|
||||
>
|
||||
<NotificationsOutlined fontSize="small" />
|
||||
{sidebarCollapse ? null : <span>Notifications</span>}
|
||||
{store?.theme?.sidebarCollapsed ? null : <span>Notifications</span>}
|
||||
{totalNotificationCount && totalNotificationCount > 0 ? (
|
||||
<span className="ml-auto bg-custom-primary-300 rounded-full text-xs text-white px-1.5">
|
||||
{getNumberCount(totalNotificationCount)}
|
||||
|
@ -26,8 +26,12 @@ import { orderArrayBy } from "helpers/array.helper";
|
||||
import { IProject } from "types";
|
||||
// fetch-keys
|
||||
import { PROJECTS_LIST } from "constants/fetch-keys";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export const ProjectSidebarList: FC = () => {
|
||||
const store: any = useMobxStore();
|
||||
|
||||
const [deleteProjectModal, setDeleteProjectModal] = useState(false);
|
||||
const [projectToDelete, setProjectToDelete] = useState<IProject | null>(null);
|
||||
|
||||
@ -139,7 +143,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
<Disclosure as="div" className="flex flex-col space-y-2" defaultOpen={true}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
{!sidebarCollapse && (
|
||||
{!store?.theme?.sidebarCollapsed && (
|
||||
<Disclosure.Button
|
||||
as="button"
|
||||
type="button"
|
||||
@ -165,7 +169,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={sidebarCollapse}
|
||||
sidebarCollapse={store?.theme?.sidebarCollapsed}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
@ -194,7 +198,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
<Disclosure as="div" className="flex flex-col space-y-2" defaultOpen={true}>
|
||||
{({ open }) => (
|
||||
<>
|
||||
{!sidebarCollapse && (
|
||||
{!store?.theme?.sidebarCollapsed && (
|
||||
<Disclosure.Button
|
||||
as="button"
|
||||
type="button"
|
||||
@ -215,7 +219,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={sidebarCollapse}
|
||||
sidebarCollapse={store?.theme?.sidebarCollapsed}
|
||||
provided={provided}
|
||||
snapshot={snapshot}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
@ -243,7 +247,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
{!sidebarCollapse && (
|
||||
{!store?.theme?.sidebarCollapsed && (
|
||||
<Disclosure.Button
|
||||
as="button"
|
||||
type="button"
|
||||
@ -261,7 +265,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
<SingleSidebarProject
|
||||
key={project.id}
|
||||
project={project}
|
||||
sidebarCollapse={sidebarCollapse}
|
||||
sidebarCollapse={store?.theme?.sidebarCollapsed}
|
||||
handleDeleteProject={() => handleDeleteProject(project)}
|
||||
handleCopyText={() => handleCopyText(project.id)}
|
||||
shortContextMenu
|
||||
@ -284,7 +288,7 @@ export const ProjectSidebarList: FC = () => {
|
||||
}}
|
||||
>
|
||||
<PlusIcon className="h-5 w-5" />
|
||||
{!sidebarCollapse && "Add Project"}
|
||||
{!store?.theme?.sidebarCollapsed && "Add Project"}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
@ -11,6 +11,8 @@ import useOutsideClickDetector from "hooks/use-outside-click-detector";
|
||||
import { Bolt, HelpOutlineOutlined, WestOutlined } from "@mui/icons-material";
|
||||
import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
|
||||
import { DocumentIcon, DiscordIcon, GithubIcon } from "components/icons";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
const helpOptions = [
|
||||
{
|
||||
@ -41,6 +43,8 @@ export interface WorkspaceHelpSectionProps {
|
||||
}
|
||||
|
||||
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setSidebarActive }) => {
|
||||
const store: any = useMobxStore();
|
||||
|
||||
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
|
||||
|
||||
const helpOptionsRef = useRef<HTMLDivElement | null>(null);
|
||||
@ -53,23 +57,23 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
|
||||
<>
|
||||
<div
|
||||
className={`flex w-full items-center justify-between gap-1 self-baseline border-t border-custom-border-200 bg-custom-sidebar-background-100 py-2 px-4 ${
|
||||
sidebarCollapse ? "flex-col" : ""
|
||||
store?.theme?.sidebarCollapsed ? "flex-col" : ""
|
||||
}`}
|
||||
>
|
||||
{!sidebarCollapse && (
|
||||
{!store?.theme?.sidebarCollapsed && (
|
||||
<div className="w-1/2 text-center cursor-default rounded-md px-2.5 py-1.5 font-medium outline-none text-sm bg-green-500/10 text-green-500">
|
||||
Free Plan
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={`flex items-center gap-1 ${
|
||||
sidebarCollapse ? "flex-col justify-center" : "justify-evenly w-1/2"
|
||||
store?.theme?.sidebarCollapsed ? "flex-col justify-center" : "justify-evenly w-1/2"
|
||||
}`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-90 outline-none ${
|
||||
sidebarCollapse ? "w-full" : ""
|
||||
store?.theme?.sidebarCollapsed ? "w-full" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
const e = new KeyboardEvent("keydown", {
|
||||
@ -83,7 +87,7 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
|
||||
<button
|
||||
type="button"
|
||||
className={`grid place-items-center rounded-md p-1.5 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-90 outline-none ${
|
||||
sidebarCollapse ? "w-full" : ""
|
||||
store?.theme?.sidebarCollapsed ? "w-full" : ""
|
||||
}`}
|
||||
onClick={() => setIsNeedHelpOpen((prev) => !prev)}
|
||||
>
|
||||
@ -99,13 +103,13 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
|
||||
<button
|
||||
type="button"
|
||||
className={`hidden md:grid place-items-center rounded-md p-1.5 text-custom-text-200 hover:text-custom-text-100 hover:bg-custom-background-90 outline-none ${
|
||||
sidebarCollapse ? "w-full" : ""
|
||||
store?.theme?.sidebarCollapsed ? "w-full" : ""
|
||||
}`}
|
||||
onClick={() => toggleCollapsed()}
|
||||
onClick={() => store.theme.setSidebarCollapsed(!store?.theme?.sidebarCollapsed)}
|
||||
>
|
||||
<WestOutlined
|
||||
fontSize="small"
|
||||
className={`duration-300 ${sidebarCollapse ? "rotate-180" : ""}`}
|
||||
className={`duration-300 ${store?.theme?.sidebarCollapsed ? "rotate-180" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
@ -122,7 +126,7 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
|
||||
>
|
||||
<div
|
||||
className={`absolute bottom-2 ${
|
||||
sidebarCollapse ? "left-full" : "left-[-75px]"
|
||||
store?.theme?.sidebarCollapsed ? "left-full" : "left-[-75px]"
|
||||
} space-y-2 rounded-sm bg-custom-background-80 p-1 shadow-md`}
|
||||
ref={helpOptionsRef}
|
||||
>
|
||||
|
@ -23,6 +23,8 @@ import { CheckIcon, PlusIcon } from "@heroicons/react/24/outline";
|
||||
import { truncateText } from "helpers/string.helper";
|
||||
// types
|
||||
import { IWorkspace } from "types";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
// Static Data
|
||||
const userLinks = (workspaceSlug: string, userId: string) => [
|
||||
@ -54,6 +56,8 @@ const profileLinks = (workspaceSlug: string, userId: string) => [
|
||||
];
|
||||
|
||||
export const WorkspaceSidebarDropdown = () => {
|
||||
const store: any = useMobxStore();
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
@ -108,7 +112,7 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
<Menu.Button className="text-custom-sidebar-text-200 flex w-full items-center rounded-sm text-sm font-medium focus:outline-none">
|
||||
<div
|
||||
className={`flex w-full items-center gap-x-2 rounded-sm bg-custom-sidebar-background-80 p-1 ${
|
||||
sidebarCollapse ? "justify-center" : ""
|
||||
store?.theme?.sidebarCollapsed ? "justify-center" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="relative grid h-6 w-6 place-items-center rounded bg-gray-700 uppercase text-white">
|
||||
@ -123,7 +127,7 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{!sidebarCollapse && (
|
||||
{!store?.theme?.sidebarCollapsed && (
|
||||
<h4 className="text-custom-text-100">
|
||||
{activeWorkspace?.name ? truncateText(activeWorkspace.name, 14) : "Loading..."}
|
||||
</h4>
|
||||
@ -243,7 +247,7 @@ export const WorkspaceSidebarDropdown = () => {
|
||||
</Transition>
|
||||
</Menu>
|
||||
|
||||
{!sidebarCollapse && (
|
||||
{!store?.theme?.sidebarCollapsed && (
|
||||
<Menu as="div" className="relative flex-shrink-0">
|
||||
<Menu.Button className="grid place-items-center outline-none">
|
||||
<Avatar user={user} height="28px" width="28px" fontSize="14px" />
|
||||
|
@ -16,6 +16,8 @@ import {
|
||||
TaskAltOutlined,
|
||||
WorkOutlineOutlined,
|
||||
} from "@mui/icons-material";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
const workspaceLinks = (workspaceSlug: string) => [
|
||||
{
|
||||
@ -41,6 +43,8 @@ const workspaceLinks = (workspaceSlug: string) => [
|
||||
];
|
||||
|
||||
export const WorkspaceSidebarMenu = () => {
|
||||
const store: any = useMobxStore();
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
|
||||
@ -61,17 +65,17 @@ export const WorkspaceSidebarMenu = () => {
|
||||
tooltipContent={link.name}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!sidebarCollapse}
|
||||
disabled={!store?.theme?.sidebarCollapsed}
|
||||
>
|
||||
<div
|
||||
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||
isActive
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||
} ${sidebarCollapse ? "justify-center" : ""}`}
|
||||
} ${store?.theme?.sidebarCollapsed ? "justify-center" : ""}`}
|
||||
>
|
||||
{<link.Icon fontSize="small" />}
|
||||
{!sidebarCollapse && link.name}
|
||||
{!store?.theme?.sidebarCollapsed && link.name}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</a>
|
||||
|
@ -7,20 +7,25 @@ import {
|
||||
WorkspaceSidebarMenu,
|
||||
} from "components/workspace";
|
||||
import { ProjectSidebarList } from "components/project";
|
||||
// mobx react lite
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
export interface SidebarProps {
|
||||
toggleSidebar: boolean;
|
||||
setToggleSidebar: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ toggleSidebar, setToggleSidebar }) => {
|
||||
const Sidebar: React.FC<SidebarProps> = observer(({ toggleSidebar, setToggleSidebar }) => {
|
||||
const store: any = useMobxStore();
|
||||
// theme
|
||||
const { collapsed: sidebarCollapse } = useTheme();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed md:relative inset-y-0 flex flex-col bg-custom-sidebar-background-100 h-full flex-shrink-0 flex-grow-0 border-r border-custom-sidebar-border-200 z-20 duration-300 ${
|
||||
sidebarCollapse ? "" : "md:w-[280px]"
|
||||
store?.theme?.sidebarCollapsed ? "" : "md:w-[280px]"
|
||||
} ${toggleSidebar ? "left-0" : "-left-full md:left-0"}`}
|
||||
>
|
||||
<div className="flex h-full w-full flex-1 flex-col">
|
||||
@ -31,6 +36,6 @@ const Sidebar: React.FC<SidebarProps> = ({ toggleSidebar, setToggleSidebar }) =>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default Sidebar;
|
||||
|
33
apps/app/lib/mobx/store-init.tsx
Normal file
33
apps/app/lib/mobx/store-init.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { useEffect } from "react";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
|
||||
const MobxStoreInit = () => {
|
||||
const store: any = useMobxStore();
|
||||
|
||||
useEffect(() => {
|
||||
// sidebar collapsed toggle
|
||||
if (
|
||||
localStorage &&
|
||||
localStorage.getItem("app_sidebar_collapsed") &&
|
||||
store?.theme?.sidebarCollapsed === null
|
||||
)
|
||||
store.theme.setSidebarCollapsed(
|
||||
localStorage.getItem("app_sidebar_collapsed")
|
||||
? localStorage.getItem("app_sidebar_collapsed") === "true"
|
||||
? true
|
||||
: false
|
||||
: false
|
||||
);
|
||||
|
||||
// theme
|
||||
if (localStorage && localStorage.getItem("theme") && store.theme.theme === null)
|
||||
store.theme.setTheme(
|
||||
localStorage.getItem("theme") ? localStorage.getItem("theme") : "system"
|
||||
);
|
||||
}, [store?.theme]);
|
||||
|
||||
return <></>;
|
||||
};
|
||||
|
||||
export default MobxStoreInit;
|
30
apps/app/lib/mobx/store-provider.tsx
Normal file
30
apps/app/lib/mobx/store-provider.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { createContext, useContext } from "react";
|
||||
// mobx store
|
||||
import { RootStore } from "store/root";
|
||||
|
||||
let rootStore: any = null;
|
||||
|
||||
export const MobxStoreContext = createContext(null);
|
||||
|
||||
const initializeStore = () => {
|
||||
const _rootStore = rootStore ?? new RootStore();
|
||||
|
||||
if (typeof window === "undefined") return _rootStore;
|
||||
|
||||
if (!rootStore) rootStore = _rootStore;
|
||||
|
||||
return _rootStore;
|
||||
};
|
||||
|
||||
export const MobxStoreProvider = ({ children }: any) => {
|
||||
const store = initializeStore();
|
||||
|
||||
return <MobxStoreContext.Provider value={store}>{children}</MobxStoreContext.Provider>;
|
||||
};
|
||||
|
||||
// hook
|
||||
export const useMobxStore = () => {
|
||||
const context = useContext(MobxStoreContext);
|
||||
if (context === undefined) throw new Error("useMobxStore must be used within MobxStoreProvider");
|
||||
return context;
|
||||
};
|
@ -36,6 +36,8 @@
|
||||
"dotenv": "^16.0.3",
|
||||
"js-cookie": "^3.0.1",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"mobx": "^6.10.0",
|
||||
"mobx-react-lite": "^4.0.3",
|
||||
"next": "12.3.2",
|
||||
"next-pwa": "^5.6.0",
|
||||
"next-themes": "^0.2.1",
|
||||
|
@ -33,6 +33,9 @@ import {
|
||||
SITE_KEYWORDS,
|
||||
SITE_TITLE,
|
||||
} from "constants/seo-variables";
|
||||
// mobx store provider
|
||||
import { MobxStoreProvider } from "lib/mobx/store-provider";
|
||||
import MobxStoreInit from "lib/mobx/store-init";
|
||||
|
||||
const CrispWithNoSSR = dynamic(() => import("constants/crisp"), { ssr: false });
|
||||
|
||||
@ -45,9 +48,10 @@ Router.events.on("routeChangeComplete", NProgress.done);
|
||||
function MyApp({ Component, pageProps }: AppProps) {
|
||||
return (
|
||||
// <UserProvider>
|
||||
<ThemeProvider themes={THEMES} defaultTheme="system">
|
||||
<ToastContextProvider>
|
||||
<ThemeContextProvider>
|
||||
// mobx root provider
|
||||
<MobxStoreProvider {...pageProps}>
|
||||
<ThemeProvider themes={THEMES} defaultTheme="system">
|
||||
<ToastContextProvider>
|
||||
<CrispWithNoSSR />
|
||||
<Head>
|
||||
<title>{SITE_TITLE}</title>
|
||||
@ -64,10 +68,11 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||
<link rel="manifest" href="/site.webmanifest.json" />
|
||||
<link rel="shortcut icon" href="/favicon/favicon.ico" />
|
||||
</Head>
|
||||
<MobxStoreInit />
|
||||
<Component {...pageProps} />
|
||||
</ThemeContextProvider>
|
||||
</ToastContextProvider>
|
||||
</ThemeProvider>
|
||||
</ToastContextProvider>
|
||||
</ThemeProvider>
|
||||
</MobxStoreProvider>
|
||||
// </UserProvider>
|
||||
);
|
||||
}
|
||||
|
17
apps/app/store/root.ts
Normal file
17
apps/app/store/root.ts
Normal file
@ -0,0 +1,17 @@
|
||||
// mobx lite
|
||||
import { enableStaticRendering } from "mobx-react-lite";
|
||||
// store imports
|
||||
import UserStore from "./user";
|
||||
import ThemeStore from "./theme";
|
||||
|
||||
enableStaticRendering(typeof window === "undefined");
|
||||
|
||||
export class RootStore {
|
||||
user;
|
||||
theme;
|
||||
|
||||
constructor() {
|
||||
this.user = new UserStore(this);
|
||||
this.theme = new ThemeStore(this);
|
||||
}
|
||||
}
|
66
apps/app/store/theme.ts
Normal file
66
apps/app/store/theme.ts
Normal file
@ -0,0 +1,66 @@
|
||||
// mobx
|
||||
import { action, observable, makeObservable } from "mobx";
|
||||
// helper
|
||||
import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper";
|
||||
// interfaces
|
||||
import { ICurrentUserSettings } from "types";
|
||||
|
||||
class ThemeStore {
|
||||
sidebarCollapsed: boolean | null = null;
|
||||
theme: string | null = null;
|
||||
// root store
|
||||
rootStore;
|
||||
|
||||
constructor(_rootStore: any | null = null) {
|
||||
makeObservable(this, {
|
||||
// observable
|
||||
sidebarCollapsed: observable,
|
||||
theme: observable,
|
||||
// action
|
||||
setSidebarCollapsed: action,
|
||||
setTheme: action,
|
||||
// computed
|
||||
});
|
||||
|
||||
this.rootStore = _rootStore;
|
||||
this.initialLoad();
|
||||
}
|
||||
|
||||
setSidebarCollapsed(collapsed: boolean | null = null) {
|
||||
if (collapsed === null) {
|
||||
let _sidebarCollapsed: string | boolean | null =
|
||||
localStorage.getItem("app_sidebar_collapsed");
|
||||
_sidebarCollapsed = _sidebarCollapsed ? (_sidebarCollapsed === "true" ? true : false) : false;
|
||||
this.sidebarCollapsed = _sidebarCollapsed;
|
||||
} else {
|
||||
this.sidebarCollapsed = collapsed;
|
||||
localStorage.setItem("app_sidebar_collapsed", collapsed.toString());
|
||||
}
|
||||
}
|
||||
|
||||
setTheme = async (_theme: ICurrentUserSettings) => {
|
||||
try {
|
||||
localStorage.setItem("theme", _theme.theme.toString());
|
||||
this.theme = _theme.theme.toString();
|
||||
|
||||
if (this.theme === "custom") {
|
||||
let themeSettings = this.rootStore.user.currentUserSettings || null;
|
||||
if (themeSettings && themeSettings.theme.palette) {
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
// init load
|
||||
initialLoad() {}
|
||||
}
|
||||
|
||||
export default ThemeStore;
|
106
apps/app/store/user.ts
Normal file
106
apps/app/store/user.ts
Normal file
@ -0,0 +1,106 @@
|
||||
// mobx
|
||||
import { action, observable, computed, makeObservable } from "mobx";
|
||||
// services
|
||||
import UserService from "services/user.service";
|
||||
// interfaces
|
||||
import { ICurrentUser, ICurrentUserSettings } from "types/users";
|
||||
|
||||
class UserStore {
|
||||
currentUser: ICurrentUser | null = null;
|
||||
currentUserSettings: ICurrentUserSettings | null = null;
|
||||
// root store
|
||||
rootStore;
|
||||
|
||||
constructor(_rootStore: any) {
|
||||
makeObservable(this, {
|
||||
// observable
|
||||
currentUser: observable.ref,
|
||||
currentUserSettings: observable.ref,
|
||||
// action
|
||||
setCurrentUser: action,
|
||||
setCurrentUserSettings: action,
|
||||
updateCurrentUser: action,
|
||||
updateCurrentUserSettings: action,
|
||||
// computed
|
||||
});
|
||||
this.rootStore = _rootStore;
|
||||
this.initialLoad();
|
||||
}
|
||||
|
||||
setCurrentUser = async () => {
|
||||
try {
|
||||
let userResponse: ICurrentUser | null = await UserService.currentUser();
|
||||
userResponse = userResponse || null;
|
||||
if (userResponse) {
|
||||
this.currentUser = {
|
||||
id: userResponse?.id,
|
||||
avatar: userResponse?.avatar,
|
||||
first_name: userResponse?.first_name,
|
||||
last_name: userResponse?.last_name,
|
||||
username: userResponse?.username,
|
||||
email: userResponse?.email,
|
||||
mobile_number: userResponse?.mobile_number,
|
||||
is_email_verified: userResponse?.is_email_verified,
|
||||
is_tour_completed: userResponse?.is_tour_completed,
|
||||
onboarding_step: userResponse?.onboarding_step,
|
||||
is_onboarded: userResponse?.is_onboarded,
|
||||
role: userResponse?.role,
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Fetching current user error", error);
|
||||
}
|
||||
};
|
||||
|
||||
setCurrentUserSettings = async () => {
|
||||
try {
|
||||
let userSettingsResponse: ICurrentUserSettings | null = await UserService.currentUser();
|
||||
userSettingsResponse = userSettingsResponse || null;
|
||||
if (userSettingsResponse) {
|
||||
this.currentUserSettings = {
|
||||
theme: userSettingsResponse?.theme,
|
||||
};
|
||||
this.rootStore.theme.setTheme();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Fetching current user error", error);
|
||||
}
|
||||
};
|
||||
|
||||
updateCurrentUser = async (user: ICurrentUser) => {
|
||||
try {
|
||||
let userResponse: ICurrentUser = await UserService.updateUser(user);
|
||||
userResponse = userResponse || null;
|
||||
if (userResponse) {
|
||||
this.currentUser = userResponse;
|
||||
return userResponse;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Updating user error", error);
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
updateCurrentUserSettings = async (userTheme: ICurrentUserSettings) => {
|
||||
try {
|
||||
let userSettingsResponse: ICurrentUserSettings = await UserService.updateUser(userTheme);
|
||||
userSettingsResponse = userSettingsResponse || null;
|
||||
if (userSettingsResponse) {
|
||||
this.currentUserSettings = userSettingsResponse;
|
||||
this.rootStore.theme.setTheme(userTheme);
|
||||
return userSettingsResponse;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Updating user settings error", error);
|
||||
return error;
|
||||
}
|
||||
};
|
||||
|
||||
// init load
|
||||
initialLoad() {
|
||||
this.setCurrentUser();
|
||||
this.setCurrentUserSettings();
|
||||
}
|
||||
}
|
||||
|
||||
export default UserStore;
|
41
apps/app/types/users.d.ts
vendored
41
apps/app/types/users.d.ts
vendored
@ -38,17 +38,6 @@ export interface IUser {
|
||||
[...rest: string]: any;
|
||||
}
|
||||
|
||||
export interface ICustomTheme {
|
||||
background: string;
|
||||
text: string;
|
||||
primary: string;
|
||||
sidebarBackground: string;
|
||||
sidebarText: string;
|
||||
darkPalette: boolean;
|
||||
palette: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export interface ICurrentUserResponse extends IUser {
|
||||
assigned_issues: number;
|
||||
last_workspace_id: string | null;
|
||||
@ -158,3 +147,33 @@ export interface IUserProfileProjectSegregation {
|
||||
user_timezone: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ICurrentUser {
|
||||
id: readonly string;
|
||||
avatar: string;
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
username: string;
|
||||
email: string;
|
||||
mobile_number: string;
|
||||
is_email_verified: boolean;
|
||||
is_tour_completed: boolean;
|
||||
onboarding_step: TOnboardingSteps;
|
||||
is_onboarded: boolean;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export interface ICustomTheme {
|
||||
background: string;
|
||||
text: string;
|
||||
primary: string;
|
||||
sidebarBackground: string;
|
||||
sidebarText: string;
|
||||
darkPalette: boolean;
|
||||
palette: string;
|
||||
theme: string;
|
||||
}
|
||||
|
||||
export interface ICurrentUserSettings {
|
||||
theme: ICustomTheme;
|
||||
}
|
||||
|
12
yarn.lock
12
yarn.lock
@ -6673,6 +6673,18 @@ mkdirp@^0.5.5:
|
||||
dependencies:
|
||||
minimist "^1.2.6"
|
||||
|
||||
mobx-react-lite@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/mobx-react-lite/-/mobx-react-lite-4.0.3.tgz#f7aa5ac3be558ca19a53b2929d9599679769c2a8"
|
||||
integrity sha512-wEE1oT5zvDdvplG4HnRrFgPwg5GFVVrEtl42Er85k23zeu3om8H8wbDPgdbQP88zAihVsik6xJfw6VnzUl8fQw==
|
||||
dependencies:
|
||||
use-sync-external-store "^1.2.0"
|
||||
|
||||
mobx@^6.10.0:
|
||||
version "6.10.0"
|
||||
resolved "https://registry.yarnpkg.com/mobx/-/mobx-6.10.0.tgz#3537680fe98d45232cc19cc8f76280bd8bb6b0b7"
|
||||
integrity sha512-WMbVpCMFtolbB8swQ5E2YRrU+Yu8iLozCVx3CdGjbBKlP7dFiCSuiG06uea3JCFN5DnvtAX7+G5Bp82e2xu0ww==
|
||||
|
||||
moo@^0.5.1:
|
||||
version "0.5.2"
|
||||
resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.2.tgz#f9fe82473bc7c184b0d32e2215d3f6e67278733c"
|
||||
|
Loading…
Reference in New Issue
Block a user