fix: layout fixes

This commit is contained in:
sriramveeraghanta 2023-10-03 00:33:03 +05:30
parent b1448c947e
commit 41fd9ce6e8
58 changed files with 394 additions and 458 deletions

View File

@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
import { observer } from "mobx-react-lite";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
@ -18,13 +18,10 @@ import { CreateUpdatePageModal } from "components/pages";
import { copyTextToClipboard } from "helpers/string.helper"; import { copyTextToClipboard } from "helpers/string.helper";
// services // services
import issuesService from "services/issue.service"; import issuesService from "services/issue.service";
import inboxService from "services/inbox.service";
// fetch keys // fetch keys
import { INBOX_LIST, ISSUE_DETAILS } from "constants/fetch-keys"; import { ISSUE_DETAILS } from "constants/fetch-keys";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { observable } from "mobx";
import { observer } from "mobx-react-lite";
export const CommandPalette: React.FC = observer(() => { export const CommandPalette: React.FC = observer(() => {
const store: any = useMobxStore(); const store: any = useMobxStore();
@ -75,7 +72,7 @@ export const CommandPalette: React.FC = observer(() => {
const handleKeyDown = useCallback( const handleKeyDown = useCallback(
(e: KeyboardEvent) => { (e: KeyboardEvent) => {
const { key, ctrlKey, metaKey, altKey, shiftKey } = e; const { key, ctrlKey, metaKey, altKey } = e;
if (!key) return; if (!key) return;
const keyPressed = key.toLowerCase(); const keyPressed = key.toLowerCase();

View File

@ -34,12 +34,12 @@ export const ProjectSidebarList: FC = observer(() => {
// states // states
const [isFavoriteProjectCreate, setIsFavoriteProjectCreate] = useState(false); const [isFavoriteProjectCreate, setIsFavoriteProjectCreate] = useState(false);
const [isProjectModalOpen, setIsProjectModalOpen] = useState(false); const [isProjectModalOpen, setIsProjectModalOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false); // scroll animation state const [isScrolled, setIsScrolled] = useState(false); // scroll animation state
// refs
const containerRef = useRef<HTMLDivElement | null>(null); const containerRef = useRef<HTMLDivElement | null>(null);
// user
const { user } = useUserAuth(); const { user } = useUserAuth();
// toast
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const joinedProjects = workspaceSlug && projectStore.joinedProjects; const joinedProjects = workspaceSlug && projectStore.joinedProjects;

View File

@ -1,11 +1,7 @@
import React, { useRef, useState } from "react"; import React, { useRef, useState } from "react";
import Link from "next/link"; import Link from "next/link";
// headless ui
import { Transition } from "@headlessui/react"; import { Transition } from "@headlessui/react";
// hooks // hooks
import useTheme from "hooks/use-theme";
import useOutsideClickDetector from "hooks/use-outside-click-detector"; import useOutsideClickDetector from "hooks/use-outside-click-detector";
// icons // icons
import { Bolt, HelpOutlineOutlined, WestOutlined } from "@mui/icons-material"; import { Bolt, HelpOutlineOutlined, WestOutlined } from "@mui/icons-material";
@ -13,6 +9,7 @@ import { ChatBubbleOvalLeftEllipsisIcon } from "@heroicons/react/24/outline";
import { DocumentIcon, DiscordIcon, GithubIcon } from "components/icons"; import { DocumentIcon, DiscordIcon, GithubIcon } from "components/icons";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
const helpOptions = [ const helpOptions = [
{ {
@ -38,15 +35,14 @@ const helpOptions = [
}, },
]; ];
export interface WorkspaceHelpSectionProps { export interface WorkspaceHelpSectionProps {}
setSidebarActive: React.Dispatch<React.SetStateAction<boolean>>;
}
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setSidebarActive }) => {
const store: any = useMobxStore();
export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(() => {
// store
const { theme: themeStore } = useMobxStore();
// states
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false); const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
// refs
const helpOptionsRef = useRef<HTMLDivElement | null>(null); const helpOptionsRef = useRef<HTMLDivElement | null>(null);
useOutsideClickDetector(helpOptionsRef, () => setIsNeedHelpOpen(false)); useOutsideClickDetector(helpOptionsRef, () => setIsNeedHelpOpen(false));
@ -55,23 +51,23 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
<> <>
<div <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 ${ 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 ${
store?.theme?.sidebarCollapsed ? "flex-col" : "" themeStore?.sidebarCollapsed ? "flex-col" : ""
}`} }`}
> >
{!store?.theme?.sidebarCollapsed && ( {!themeStore?.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"> <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 Free Plan
</div> </div>
)} )}
<div <div
className={`flex items-center gap-1 ${ className={`flex items-center gap-1 ${
store?.theme?.sidebarCollapsed ? "flex-col justify-center" : "justify-evenly w-1/2" themeStore?.sidebarCollapsed ? "flex-col justify-center" : "justify-evenly w-1/2"
}`} }`}
> >
<button <button
type="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 ${ 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 ${
store?.theme?.sidebarCollapsed ? "w-full" : "" themeStore?.sidebarCollapsed ? "w-full" : ""
}`} }`}
onClick={() => { onClick={() => {
const e = new KeyboardEvent("keydown", { const e = new KeyboardEvent("keydown", {
@ -85,7 +81,7 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
<button <button
type="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 ${ 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 ${
store?.theme?.sidebarCollapsed ? "w-full" : "" themeStore?.sidebarCollapsed ? "w-full" : ""
}`} }`}
onClick={() => setIsNeedHelpOpen((prev) => !prev)} onClick={() => setIsNeedHelpOpen((prev) => !prev)}
> >
@ -94,20 +90,20 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
<button <button
type="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 md:hidden" 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 md:hidden"
onClick={() => setSidebarActive(false)} onClick={() => themeStore.setSidebarCollapsed(!themeStore?.sidebarCollapsed)}
> >
<WestOutlined fontSize="small" /> <WestOutlined fontSize="small" />
</button> </button>
<button <button
type="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 ${ 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 ${
store?.theme?.sidebarCollapsed ? "w-full" : "" themeStore?.sidebarCollapsed ? "w-full" : ""
}`} }`}
onClick={() => store.theme.setSidebarCollapsed(!store?.theme?.sidebarCollapsed)} onClick={() => themeStore.setSidebarCollapsed(!themeStore?.sidebarCollapsed)}
> >
<WestOutlined <WestOutlined
fontSize="small" fontSize="small"
className={`duration-300 ${store?.theme?.sidebarCollapsed ? "rotate-180" : ""}`} className={`duration-300 ${themeStore?.sidebarCollapsed ? "rotate-180" : ""}`}
/> />
</button> </button>
</div> </div>
@ -124,7 +120,7 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
> >
<div <div
className={`absolute bottom-2 ${ className={`absolute bottom-2 ${
store?.theme?.sidebarCollapsed ? "left-full" : "left-[-75px]" themeStore?.sidebarCollapsed ? "left-full" : "left-[-75px]"
} space-y-2 rounded-sm bg-custom-background-80 p-1 shadow-md`} } space-y-2 rounded-sm bg-custom-background-80 p-1 shadow-md`}
ref={helpOptionsRef} ref={helpOptionsRef}
> >
@ -160,4 +156,4 @@ export const WorkspaceHelpSection: React.FC<WorkspaceHelpSectionProps> = ({ setS
</div> </div>
</> </>
); );
}; });

View File

@ -7,14 +7,10 @@ import useTheme from "hooks/use-theme";
import { NotificationPopover } from "components/notifications"; import { NotificationPopover } from "components/notifications";
import { Tooltip } from "components/ui"; import { Tooltip } from "components/ui";
// icons // icons
import { import { BarChartRounded, GridViewOutlined, TaskAltOutlined, WorkOutlineOutlined } from "@mui/icons-material";
BarChartRounded,
GridViewOutlined,
TaskAltOutlined,
WorkOutlineOutlined,
} from "@mui/icons-material";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
import { observer } from "mobx-react-lite";
const workspaceLinks = (workspaceSlug: string) => [ const workspaceLinks = (workspaceSlug: string) => [
{ {
@ -39,21 +35,16 @@ const workspaceLinks = (workspaceSlug: string) => [
}, },
]; ];
export const WorkspaceSidebarMenu = () => { export const WorkspaceSidebarMenu = observer(() => {
const store: any = useMobxStore(); const { theme: themeStore } = useMobxStore();
// router
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { collapsed: sidebarCollapse } = useTheme();
return ( return (
<div className="w-full cursor-pointer space-y-1 p-4"> <div className="w-full cursor-pointer space-y-1 p-4">
{workspaceLinks(workspaceSlug as string).map((link, index) => { {workspaceLinks(workspaceSlug as string).map((link, index) => {
const isActive = const isActive = link.name === "Settings" ? router.asPath.includes(link.href) : router.asPath === link.href;
link.name === "Settings"
? router.asPath.includes(link.href)
: router.asPath === link.href;
return ( return (
<Link key={index} href={link.href}> <Link key={index} href={link.href}>
@ -62,17 +53,17 @@ export const WorkspaceSidebarMenu = () => {
tooltipContent={link.name} tooltipContent={link.name}
position="right" position="right"
className="ml-2" className="ml-2"
disabled={!store?.theme?.sidebarCollapsed} disabled={!themeStore?.sidebarCollapsed}
> >
<div <div
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${ className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
isActive isActive
? "bg-custom-primary-100/10 text-custom-primary-100" ? "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" : "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
} ${store?.theme?.sidebarCollapsed ? "justify-center" : ""}`} } ${themeStore?.sidebarCollapsed ? "justify-center" : ""}`}
> >
{<link.Icon fontSize="small" />} {<link.Icon fontSize="small" />}
{!store?.theme?.sidebarCollapsed && link.name} {!themeStore?.sidebarCollapsed && link.name}
</div> </div>
</Tooltip> </Tooltip>
</a> </a>
@ -83,4 +74,4 @@ export const WorkspaceSidebarMenu = () => {
<NotificationPopover /> <NotificationPopover />
</div> </div>
); );
}; });

View File

View File

View File

@ -0,0 +1,3 @@
export * from "./layout";
export * from "./sidebar";
export * from "./header";

View File

@ -0,0 +1,42 @@
import { FC, ReactNode } from "react";
// layouts
import { UserAuthWrapper, WorkspaceAuthWrapper } from "layouts/auth-layout";
// components
import { CommandPalette } from "components/command-palette";
import { AppSidebar } from "./sidebar";
export interface IAppLayout {
bg: string;
children: ReactNode;
}
export const AppLayout: FC<IAppLayout> = (props) => {
const { bg = "primary", children } = props;
return (
<div>
<CommandPalette />
<UserAuthWrapper>
<WorkspaceAuthWrapper>
<div>
<AppSidebar />
<div className="relative flex h-screen w-full overflow-hidden">
<main
className={`relative flex h-full w-full flex-col overflow-hidden ${
bg === "primary"
? "bg-custom-background-100"
: bg === "secondary"
? "bg-custom-background-90"
: "bg-custom-background-80"
}`}
>
<div className="h-full w-full overflow-hidden">
<div className="relative h-full w-full overflow-x-hidden overflow-y-scroll">{children}</div>
</div>
</main>
</div>
</div>
</WorkspaceAuthWrapper>
</UserAuthWrapper>
</div>
);
};

View File

@ -0,0 +1,36 @@
import { FC } from "react";
import { observer } from "mobx-react-lite";
// components
import {
WorkspaceHelpSection,
WorkspaceSidebarDropdown,
WorkspaceSidebarMenu,
WorkspaceSidebarQuickAction,
} from "components/workspace";
import { ProjectSidebarList } from "components/project";
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
export interface IAppSidebar {}
export const AppSidebar: FC<IAppSidebar> = observer(() => {
// store
const { theme: themStore } = useMobxStore();
return (
<div
id="app-sidebar"
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 ${
themStore?.sidebarCollapsed ? "" : "md:w-[280px]"
} ${themStore?.sidebarCollapsed ? "left-0" : "-left-full md:left-0"}`}
>
<div className="flex h-full w-full flex-1 flex-col">
<WorkspaceSidebarDropdown />
<WorkspaceSidebarQuickAction />
<WorkspaceSidebarMenu />
<ProjectSidebarList />
<WorkspaceHelpSection />
</div>
</div>
);
});

View File

@ -0,0 +1,3 @@
export * from "./project-authorization-wrapper";
export * from "./workspace-authorization-wrapper";
export * from "./user-authorization-wrapper";

View File

@ -6,8 +6,8 @@ import { useRouter } from "next/router";
// contexts // contexts
import { useProjectMyMembership, ProjectMemberProvider } from "contexts/project-member.context"; import { useProjectMyMembership, ProjectMemberProvider } from "contexts/project-member.context";
// layouts // layouts
import AppHeader from "layouts/app-layout/app-header"; import AppHeader from "layouts/app-layout-legacy/app-header";
import AppSidebar from "layouts/app-layout/app-sidebar"; import AppSidebar from "layouts/app-layout-legacy/app-sidebar";
// components // components
import { NotAuthorizedView, JoinProject } from "components/auth-screens"; import { NotAuthorizedView, JoinProject } from "components/auth-screens";
import { CommandPalette } from "components/command-palette"; import { CommandPalette } from "components/command-palette";

View File

@ -22,7 +22,6 @@ export const UserAuthorizationLayout: React.FC<Props> = ({ children }) => {
return ( return (
<div className="h-screen grid place-items-center p-4"> <div className="h-screen grid place-items-center p-4">
<div className="flex flex-col items-center gap-3 text-center"> <div className="flex flex-col items-center gap-3 text-center">
<h3 className="text-xl">Loading your profile...</h3>
<Spinner /> <Spinner />
</div> </div>
</div> </div>

View File

@ -10,8 +10,8 @@ import workspaceServices from "services/workspace.service";
// contexts // contexts
import { WorkspaceMemberProvider } from "contexts/workspace-member.context"; import { WorkspaceMemberProvider } from "contexts/workspace-member.context";
// layouts // layouts
import AppSidebar from "layouts/app-layout/app-sidebar"; import AppSidebar from "layouts/app-layout-legacy/app-sidebar";
import AppHeader from "layouts/app-layout/app-header"; import AppHeader from "layouts/app-layout-legacy/app-header";
import { UserAuthorizationLayout } from "./user-authorization-wrapper"; import { UserAuthorizationLayout } from "./user-authorization-wrapper";
// components // components
import { NotAuthorizedView, NotAWorkspaceMember } from "components/auth-screens"; import { NotAuthorizedView, NotAWorkspaceMember } from "components/auth-screens";

View File

@ -1,2 +1,2 @@
export * from "./project-authorization-wrapper"; export * from "./user-wrapper";
export * from "./workspace-authorization-wrapper"; export * from "./workspace-wrapper";

View File

@ -0,0 +1,39 @@
import { FC, ReactNode } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
// services
import userService from "services/user.service";
// ui
import { Spinner } from "components/ui";
// fetch-keys
import { CURRENT_USER } from "constants/fetch-keys";
export interface IUserAuthWrapper {
children: ReactNode;
}
export const UserAuthWrapper: FC<IUserAuthWrapper> = (props) => {
const { children } = props;
// router
const router = useRouter();
// fetching user information
const { data: currentUser, error } = useSWR(CURRENT_USER, () => userService.currentUser());
if (!currentUser && !error) {
return (
<div className="h-screen grid place-items-center p-4">
<div className="flex flex-col items-center gap-3 text-center">
<Spinner />
</div>
</div>
);
}
if (error) {
const redirectTo = router.asPath;
router.push(`/?next=${redirectTo}`);
return null;
}
return <>{children}</>;
};

View File

@ -0,0 +1,73 @@
import { FC, ReactNode } from "react";
import { useRouter } from "next/router";
import Link from "next/link";
import useSWR from "swr";
// services
import workspaceServices from "services/workspace.service";
// icons
import { Spinner, PrimaryButton, SecondaryButton } from "components/ui";
// fetch-keys
import { WORKSPACE_MEMBERS_ME } from "constants/fetch-keys";
export interface IWorkspaceAuthWrapper {
children: ReactNode;
noHeader?: boolean;
bg?: "primary" | "secondary";
breadcrumbs?: JSX.Element;
left?: JSX.Element;
right?: JSX.Element;
}
export const WorkspaceAuthWrapper: FC<IWorkspaceAuthWrapper> = (props) => {
const { children } = props;
// router
const router = useRouter();
const { workspaceSlug } = router.query;
// fetching user workspace information
const { data: workspaceMemberMe, error } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS_ME(workspaceSlug as string) : null,
workspaceSlug ? () => workspaceServices.workspaceMemberMe(workspaceSlug.toString()) : null
);
// while data is being loaded
if (!workspaceMemberMe && !error) {
return (
<div className="grid h-screen place-items-center p-4 bg-custom-background-100">
<div className="flex flex-col items-center gap-3 text-center">
<Spinner />
</div>
</div>
);
}
// while user does not have access to view that workspace
if (error?.status === 401 || error?.status === 403) {
return (
<div className={`h-screen w-full overflow-hidden bg-custom-background-100`}>
<div className="grid h-full place-items-center p-4">
<div className="space-y-8 text-center">
<div className="space-y-2">
<h3 className="text-lg font-semibold">Not Authorized!</h3>
<p className="mx-auto w-1/2 text-sm text-custom-text-200">
You{"'"}re not a member of this workspace. Please contact the workspace admin to get an invitation or
check your pending invitations.
</p>
</div>
<div className="flex items-center justify-center gap-2">
<Link href="/invitations">
<a>
<SecondaryButton>Check pending invites</SecondaryButton>
</a>
</Link>
<Link href="/create-workspace">
<a>
<PrimaryButton>Create new workspace</PrimaryButton>
</a>
</Link>
</div>
</div>
</div>
</div>
);
}
return <>{children}</>;
};

View File

@ -1,7 +1,7 @@
// hooks // hooks
import { useWorkspaceMyMembership } from "contexts/workspace-member.context"; import { useWorkspaceMyMembership } from "contexts/workspace-member.context";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import { ProfileNavbar, ProfileSidebar } from "components/profile"; import { ProfileNavbar, ProfileSidebar } from "components/profile";
// ui // ui

View File

@ -15,7 +15,7 @@ import { Tab } from "@headlessui/react";
import analyticsService from "services/analytics.service"; import analyticsService from "services/analytics.service";
import trackEventServices from "services/track_event.service"; import trackEventServices from "services/track_event.service";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import { CustomAnalytics, ScopeAndDemand } from "components/analytics"; import { CustomAnalytics, ScopeAndDemand } from "components/analytics";
// ui // ui

View File

@ -9,19 +9,14 @@ import useSWR, { mutate } from "swr";
// next-themes // next-themes
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// services // services
import userService from "services/user.service"; import userService from "services/user.service";
// hooks // hooks
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
import useProjects from "hooks/use-projects"; import useProjects from "hooks/use-projects";
// components // components
import { import { CompletedIssuesGraph, IssuesList, IssuesPieChart, IssuesStats } from "components/workspace";
CompletedIssuesGraph,
IssuesList,
IssuesPieChart,
IssuesStats,
} from "components/workspace";
import { TourRoot } from "components/onboarding"; import { TourRoot } from "components/onboarding";
// ui // ui
import { PrimaryButton, ProductUpdatesModal } from "components/ui"; import { PrimaryButton, ProductUpdatesModal } from "components/ui";
@ -59,8 +54,7 @@ const WorkspacePage: NextPage = () => {
); );
const today = new Date(); const today = new Date();
const greeting = const greeting = today.getHours() < 12 ? "morning" : today.getHours() < 18 ? "afternoon" : "evening";
today.getHours() < 12 ? "morning" : today.getHours() < 18 ? "afternoon" : "evening";
useEffect(() => { useEffect(() => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
@ -100,10 +94,7 @@ const WorkspacePage: NextPage = () => {
} }
> >
{isProductUpdatesModalOpen && ( {isProductUpdatesModalOpen && (
<ProductUpdatesModal <ProductUpdatesModal isOpen={isProductUpdatesModalOpen} setIsOpen={setIsProductUpdatesModalOpen} />
isOpen={isProductUpdatesModalOpen}
setIsOpen={setIsProductUpdatesModalOpen}
/>
)} )}
{user && !user.is_tour_completed && ( {user && !user.is_tour_completed && (
<div className="fixed top-0 left-0 h-full w-full bg-custom-backdrop bg-opacity-50 transition-opacity z-20 grid place-items-center"> <div className="fixed top-0 left-0 h-full w-full bg-custom-backdrop bg-opacity-50 transition-opacity z-20 grid place-items-center">
@ -156,9 +147,7 @@ const WorkspacePage: NextPage = () => {
<div className="mt-7 bg-custom-primary-100/5 flex justify-between gap-5 md:gap-8"> <div className="mt-7 bg-custom-primary-100/5 flex justify-between gap-5 md:gap-8">
<div className="p-5 md:p-8 pr-0"> <div className="p-5 md:p-8 pr-0">
<h5 className="text-xl font-semibold">Create a project</h5> <h5 className="text-xl font-semibold">Create a project</h5>
<p className="mt-2 mb-5"> <p className="mt-2 mb-5">Manage your projects by creating issues, cycles, modules, views and pages.</p>
Manage your projects by creating issues, cycles, modules, views and pages.
</p>
<PrimaryButton <PrimaryButton
onClick={() => { onClick={() => {
const e = new KeyboardEvent("keydown", { const e = new KeyboardEvent("keydown", {

View File

@ -5,7 +5,7 @@ import { useRouter } from "next/router";
// icons // icons
import { PlusIcon } from "@heroicons/react/24/outline"; import { PlusIcon } from "@heroicons/react/24/outline";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// hooks // hooks
import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter"; import useMyIssuesFilters from "hooks/my-issues/use-my-issues-filter";
// components // components
@ -107,9 +107,7 @@ const MyIssuesPage: NextPage = () => {
type="button" type="button"
onClick={tab.onClick} onClick={tab.onClick}
className={`border-b-2 p-4 text-sm font-medium outline-none whitespace-nowrap ${ className={`border-b-2 p-4 text-sm font-medium outline-none whitespace-nowrap ${
tab.selected tab.selected ? "border-custom-primary-100 text-custom-primary-100" : "border-transparent"
? "border-custom-primary-100 text-custom-primary-100"
: "border-transparent"
}`} }`}
> >
{tab.label} {tab.label}

View File

@ -6,7 +6,7 @@ import Link from "next/link";
// services // services
import userService from "services/user.service"; import userService from "services/user.service";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import { ActivityIcon, ActivityMessage } from "components/core"; import { ActivityIcon, ActivityMessage } from "components/core";
import { TipTapEditor } from "components/tiptap"; import { TipTapEditor } from "components/tiptap";
@ -60,8 +60,7 @@ const ProfileActivity = () => {
activityItem.new_value === "restore" && ( activityItem.new_value === "restore" && (
<Icon iconName="history" className="text-sm text-custom-text-200" /> <Icon iconName="history" className="text-sm text-custom-text-200" />
) )
) : activityItem.actor_detail.avatar && ) : activityItem.actor_detail.avatar && activityItem.actor_detail.avatar !== "" ? (
activityItem.actor_detail.avatar !== "" ? (
<img <img
src={activityItem.actor_detail.avatar} src={activityItem.actor_detail.avatar}
alt={activityItem.actor_detail.display_name} alt={activityItem.actor_detail.display_name}
@ -98,11 +97,7 @@ const ProfileActivity = () => {
<div className="issue-comments-section p-0"> <div className="issue-comments-section p-0">
<TipTapEditor <TipTapEditor
workspaceSlug={workspaceSlug as string} workspaceSlug={workspaceSlug as string}
value={ value={activityItem?.new_value !== "" ? activityItem.new_value : activityItem.old_value}
activityItem?.new_value !== ""
? activityItem.new_value
: activityItem.old_value
}
customClassName="text-xs border border-custom-border-200 bg-custom-background-100" customClassName="text-xs border border-custom-border-200 bg-custom-background-100"
noBorder noBorder
borderOnFocus={false} borderOnFocus={false}
@ -124,9 +119,7 @@ const ProfileActivity = () => {
activityItem.field !== "estimate" ? ( activityItem.field !== "estimate" ? (
<span className="text-custom-text-200"> <span className="text-custom-text-200">
created{" "} created{" "}
<Link <Link href={`/${workspaceSlug}/projects/${activityItem.project}/issues/${activityItem.issue}`}>
href={`/${workspaceSlug}/projects/${activityItem.project}/issues/${activityItem.issue}`}
>
<a className="inline-flex items-center hover:underline"> <a className="inline-flex items-center hover:underline">
this issue. <ArrowTopRightOnSquareIcon className="ml-1 h-3.5 w-3.5" /> this issue. <ArrowTopRightOnSquareIcon className="ml-1 h-3.5 w-3.5" />
</a> </a>
@ -150,10 +143,7 @@ const ProfileActivity = () => {
<div className="flex h-6 w-6 items-center justify-center"> <div className="flex h-6 w-6 items-center justify-center">
{activityItem.field ? ( {activityItem.field ? (
activityItem.new_value === "restore" ? ( activityItem.new_value === "restore" ? (
<Icon <Icon iconName="history" className="!text-2xl text-custom-text-200" />
iconName="history"
className="!text-2xl text-custom-text-200"
/>
) : ( ) : (
<ActivityIcon activity={activityItem} /> <ActivityIcon activity={activityItem} />
) )
@ -179,26 +169,19 @@ const ProfileActivity = () => {
</div> </div>
<div className="min-w-0 flex-1 py-4 border-b border-custom-border-200"> <div className="min-w-0 flex-1 py-4 border-b border-custom-border-200">
<div className="text-sm text-custom-text-200 break-words"> <div className="text-sm text-custom-text-200 break-words">
{activityItem.field === "archived_at" && {activityItem.field === "archived_at" && activityItem.new_value !== "restore" ? (
activityItem.new_value !== "restore" ? (
<span className="text-gray font-medium">Plane</span> <span className="text-gray font-medium">Plane</span>
) : activityItem.actor_detail.is_bot ? ( ) : activityItem.actor_detail.is_bot ? (
<span className="text-gray font-medium"> <span className="text-gray font-medium">
{activityItem.actor_detail.first_name} Bot {activityItem.actor_detail.first_name} Bot
</span> </span>
) : ( ) : (
<Link <Link href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`}>
href={`/${workspaceSlug}/profile/${activityItem.actor_detail.id}`} <a className="text-gray font-medium">{activityItem.actor_detail.display_name}</a>
>
<a className="text-gray font-medium">
{activityItem.actor_detail.display_name}
</a>
</Link> </Link>
)}{" "} )}{" "}
{message}{" "} {message}{" "}
<span className="whitespace-nowrap"> <span className="whitespace-nowrap">{timeAgo(activityItem.created_at)}</span>
{timeAgo(activityItem.created_at)}
</span>
</div> </div>
</div> </div>
</> </>

View File

@ -11,7 +11,7 @@ import userService from "services/user.service";
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import { ImagePickerPopover, ImageUploadModal } from "components/core"; import { ImagePickerPopover, ImageUploadModal } from "components/core";
import { SettingsSidebar } from "components/project"; import { SettingsSidebar } from "components/project";
@ -174,10 +174,7 @@ const Profile: NextPage = () => {
<div className={`flex flex-col gap-8 pr-9 py-9 w-full overflow-y-auto`}> <div className={`flex flex-col gap-8 pr-9 py-9 w-full overflow-y-auto`}>
<div className="relative h-44 w-full mt-6"> <div className="relative h-44 w-full mt-6">
<img <img
src={ src={watch("cover_image") ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"}
watch("cover_image") ??
"https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"
}
className="h-44 w-full rounded-lg object-cover" className="h-44 w-full rounded-lg object-cover"
alt={myProfile?.name ?? "Cover image"} alt={myProfile?.name ?? "Cover image"}
/> />
@ -214,10 +211,7 @@ const Profile: NextPage = () => {
onChange={(imageUrl) => { onChange={(imageUrl) => {
setValue("cover_image", imageUrl); setValue("cover_image", imageUrl);
}} }}
value={ value={watch("cover_image") ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"}
watch("cover_image") ??
"https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"
}
/> />
)} )}
/> />
@ -307,9 +301,7 @@ const Profile: NextPage = () => {
</CustomSelect> </CustomSelect>
)} )}
/> />
{errors.role && ( {errors.role && <span className="text-xs text-red-500">Please select a role</span>}
<span className="text-xs text-red-500">Please select a role</span>
)}
</div> </div>
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
@ -328,8 +320,7 @@ const Profile: NextPage = () => {
validate: (value) => { validate: (value) => {
if (value.trim().length < 1) return "Display name can't be empty."; if (value.trim().length < 1) return "Display name can't be empty.";
if (value.split(" ").length > 1) if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces.";
return "Display name can't have two consecutive spaces.";
if (value.replace(/\s/g, "").length < 1) if (value.replace(/\s/g, "").length < 1)
return "Display name must be at least 1 characters long."; return "Display name must be at least 1 characters long.";
@ -353,11 +344,7 @@ const Profile: NextPage = () => {
render={({ field: { value, onChange } }) => ( render={({ field: { value, onChange } }) => (
<CustomSearchSelect <CustomSearchSelect
value={value} value={value}
label={ label={value ? TIME_ZONES.find((t) => t.value === value)?.label ?? value : "Select a timezone"}
value
? TIME_ZONES.find((t) => t.value === value)?.label ?? value
: "Select a timezone"
}
options={timeZoneOptions} options={timeZoneOptions}
onChange={onChange} onChange={onChange}
verticalPosition="top" verticalPosition="top"
@ -366,9 +353,7 @@ const Profile: NextPage = () => {
/> />
)} )}
/> />
{errors.role && ( {errors.role && <span className="text-xs text-red-500">Please select a role</span>}
<span className="text-xs text-red-500">Please select a role</span>
)}
</div> </div>
<div className="flex items-center justify-between py-2"> <div className="flex items-center justify-between py-2">

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from "react";
// hooks // hooks
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import { CustomThemeSelector, ThemeSwitch } from "components/core"; import { CustomThemeSelector, ThemeSwitch } from "components/core";
// ui // ui
@ -36,14 +36,10 @@ const ProfilePreferences = observer(() => {
background: currentTheme.background !== "" ? currentTheme.background : "#0d101b", background: currentTheme.background !== "" ? currentTheme.background : "#0d101b",
text: currentTheme.text !== "" ? currentTheme.text : "#c5c5c5", text: currentTheme.text !== "" ? currentTheme.text : "#c5c5c5",
primary: currentTheme.primary !== "" ? currentTheme.primary : "#3f76ff", primary: currentTheme.primary !== "" ? currentTheme.primary : "#3f76ff",
sidebarBackground: sidebarBackground: currentTheme.sidebarBackground !== "" ? currentTheme.sidebarBackground : "#0d101b",
currentTheme.sidebarBackground !== "" ? currentTheme.sidebarBackground : "#0d101b",
sidebarText: currentTheme.sidebarText !== "" ? currentTheme.sidebarText : "#c5c5c5", sidebarText: currentTheme.sidebarText !== "" ? currentTheme.sidebarText : "#c5c5c5",
darkPalette: false, darkPalette: false,
palette: palette: currentTheme.palette !== ",,,," ? currentTheme.palette : "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
currentTheme.palette !== ",,,,"
? currentTheme.palette
: "#0d101b,#c5c5c5,#3f76ff,#0d101b,#c5c5c5",
theme: "custom", theme: "custom",
}); });
setCustomThemeSelectorOptions((prevData) => true); setCustomThemeSelectorOptions((prevData) => true);
@ -71,9 +67,7 @@ const ProfilePreferences = observer(() => {
<div className="grid grid-cols-12 gap-4 sm:gap-16 py-6"> <div className="grid grid-cols-12 gap-4 sm:gap-16 py-6">
<div className="col-span-12 sm:col-span-6"> <div className="col-span-12 sm:col-span-6">
<h4 className="text-lg font-semibold text-custom-text-100">Theme</h4> <h4 className="text-lg font-semibold text-custom-text-100">Theme</h4>
<p className="text-sm text-custom-text-200"> <p className="text-sm text-custom-text-200">Select or customize your interface color scheme.</p>
Select or customize your interface color scheme.
</p>
</div> </div>
<div className="col-span-12 sm:col-span-6"> <div className="col-span-12 sm:col-span-6">
<ThemeSwitch <ThemeSwitch

View File

@ -12,7 +12,7 @@ import issuesService from "services/issue.service";
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import { IssueDetailsSidebar, IssueMainContent } from "components/issues"; import { IssueDetailsSidebar, IssueMainContent } from "components/issues";
// ui // ui

View File

@ -5,7 +5,7 @@ import useSWR from "swr";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// contexts // contexts
import { IssueViewContextProvider } from "contexts/issue-view.context"; import { IssueViewContextProvider } from "contexts/issue-view.context";
// helper // helper
@ -28,9 +28,7 @@ const ProjectArchivedIssues: NextPage = () => {
const { data: projectDetails } = useSWR( const { data: projectDetails } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
? () => projectService.getProject(workspaceSlug as string, projectId as string)
: null
); );
return ( return (
@ -39,9 +37,7 @@ const ProjectArchivedIssues: NextPage = () => {
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem <BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Archived Issues`} />
title={`${truncateText(projectDetails?.name ?? "Project", 32)} Archived Issues`}
/>
</Breadcrumbs> </Breadcrumbs>
} }
right={ right={

View File

@ -7,7 +7,7 @@ import useSWR from "swr";
import { ArrowLeftIcon } from "@heroicons/react/24/outline"; import { ArrowLeftIcon } from "@heroicons/react/24/outline";
import { CyclesIcon } from "components/icons"; import { CyclesIcon } from "components/icons";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// contexts // contexts
import { IssueViewContextProvider } from "contexts/issue-view.context"; import { IssueViewContextProvider } from "contexts/issue-view.context";
// components // components

View File

@ -6,7 +6,7 @@ import useSWR from "swr";
import useLocalStorage from "hooks/use-local-storage"; import useLocalStorage from "hooks/use-local-storage";
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import { CyclesView, ActiveCycleDetails, CreateUpdateCycleModal } from "components/cycles"; import { CyclesView, ActiveCycleDetails, CreateUpdateCycleModal } from "components/cycles";
// ui // ui

View File

@ -5,7 +5,7 @@ import useSWR from "swr";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// contexts // contexts
import { IssueViewContextProvider } from "contexts/issue-view.context"; import { IssueViewContextProvider } from "contexts/issue-view.context";
// helper // helper
@ -28,9 +28,7 @@ const ProjectDraftIssues: NextPage = () => {
const { data: projectDetails } = useSWR( const { data: projectDetails } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
? () => projectService.getProject(workspaceSlug as string, projectId as string)
: null
); );
return ( return (
@ -39,9 +37,7 @@ const ProjectDraftIssues: NextPage = () => {
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem <BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Draft Issues`} />
title={`${truncateText(projectDetails?.name ?? "Project", 32)} Draft Issues`}
/>
</Breadcrumbs> </Breadcrumbs>
} }
right={ right={

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
// hooks // hooks
import useProjectDetails from "hooks/use-project-details"; import useProjectDetails from "hooks/use-project-details";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// contexts // contexts
import { InboxViewContextProvider } from "contexts/inbox-view-context"; import { InboxViewContextProvider } from "contexts/inbox-view-context";
// components // components
@ -30,9 +30,7 @@ const ProjectInbox: NextPage = () => {
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem <BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Inbox`} />
title={`${truncateText(projectDetails?.name ?? "Project", 32)} Inbox`}
/>
</Breadcrumbs> </Breadcrumbs>
} }
right={ right={

View File

@ -11,7 +11,7 @@ import issuesService from "services/issue.service";
// hooks // hooks
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import { IssueDetailsSidebar, IssueMainContent } from "components/issues"; import { IssueDetailsSidebar, IssueMainContent } from "components/issues";
// ui // ui

View File

@ -10,7 +10,7 @@ import { useMobxStore } from "lib/mobx/store-provider";
import projectService from "services/project.service"; import projectService from "services/project.service";
import inboxService from "services/inbox.service"; import inboxService from "services/inbox.service";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// helper // helper
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
// components // components

View File

@ -12,7 +12,7 @@ import modulesService from "services/modules.service";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// contexts // contexts
import { IssueViewContextProvider } from "contexts/issue-view.context"; import { IssueViewContextProvider } from "contexts/issue-view.context";
// components // components
@ -45,32 +45,20 @@ const SingleModule: React.FC = () => {
const { data: modules } = useSWR( const { data: modules } = useSWR(
workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null, workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => modulesService.getModules(workspaceSlug as string, projectId as string) : null
? () => modulesService.getModules(workspaceSlug as string, projectId as string)
: null
); );
const { data: moduleIssues } = useSWR( const { data: moduleIssues } = useSWR(
workspaceSlug && projectId && moduleId ? MODULE_ISSUES(moduleId as string) : null, workspaceSlug && projectId && moduleId ? MODULE_ISSUES(moduleId as string) : null,
workspaceSlug && projectId && moduleId workspaceSlug && projectId && moduleId
? () => ? () => modulesService.getModuleIssues(workspaceSlug as string, projectId as string, moduleId as string)
modulesService.getModuleIssues(
workspaceSlug as string,
projectId as string,
moduleId as string
)
: null : null
); );
const { data: moduleDetails, error } = useSWR( const { data: moduleDetails, error } = useSWR(
moduleId ? MODULE_DETAILS(moduleId as string) : null, moduleId ? MODULE_DETAILS(moduleId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId
? () => ? () => modulesService.getModuleDetails(workspaceSlug as string, projectId as string, moduleId as string)
modulesService.getModuleDetails(
workspaceSlug as string,
projectId as string,
moduleId as string
)
: null : null
); );
@ -82,13 +70,7 @@ const SingleModule: React.FC = () => {
}; };
await modulesService await modulesService
.addIssuesToModule( .addIssuesToModule(workspaceSlug as string, projectId as string, moduleId as string, payload, user)
workspaceSlug as string,
projectId as string,
moduleId as string,
payload,
user
)
.catch(() => .catch(() =>
setToastAlert({ setToastAlert({
type: "error", type: "error",
@ -176,10 +158,7 @@ const SingleModule: React.FC = () => {
/> />
) : ( ) : (
<> <>
<AnalyticsProjectModal <AnalyticsProjectModal isOpen={analyticsModal} onClose={() => setAnalyticsModal(false)} />
isOpen={analyticsModal}
onClose={() => setAnalyticsModal(false)}
/>
<div <div
className={`h-full flex flex-col ${moduleSidebar ? "mr-[24rem]" : ""} ${ className={`h-full flex flex-col ${moduleSidebar ? "mr-[24rem]" : ""} ${
analyticsModal ? "mr-[50%]" : "" analyticsModal ? "mr-[50%]" : ""

View File

@ -5,18 +5,14 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// hooks // hooks
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
import modulesService from "services/modules.service"; import modulesService from "services/modules.service";
// components // components
import { import { CreateUpdateModuleModal, ModulesListGanttChartView, SingleModuleCard } from "components/modules";
CreateUpdateModuleModal,
ModulesListGanttChartView,
SingleModuleCard,
} from "components/modules";
// ui // ui
import { EmptyState, Icon, Loader, PrimaryButton, Tooltip } from "components/ui"; import { EmptyState, Icon, Loader, PrimaryButton, Tooltip } from "components/ui";
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
@ -56,16 +52,12 @@ const ProjectModules: NextPage = () => {
const { data: activeProject } = useSWR( const { data: activeProject } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
? () => projectService.getProject(workspaceSlug as string, projectId as string)
: null
); );
const { data: modules, mutate: mutateModules } = useSWR( const { data: modules, mutate: mutateModules } = useSWR(
workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null, workspaceSlug && projectId ? MODULE_LIST(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => modulesService.getModules(workspaceSlug as string, projectId as string) : null
? () => modulesService.getModules(workspaceSlug as string, projectId as string)
: null
); );
const handleEditModule = (module: IModule) => { const handleEditModule = (module: IModule) => {
@ -95,24 +87,17 @@ const ProjectModules: NextPage = () => {
{moduleViewOptions.map((option) => ( {moduleViewOptions.map((option) => (
<Tooltip <Tooltip
key={option.type} key={option.type}
tooltipContent={ tooltipContent={<span className="capitalize">{replaceUnderscoreIfSnakeCase(option.type)} View</span>}
<span className="capitalize">{replaceUnderscoreIfSnakeCase(option.type)} View</span>
}
position="bottom" position="bottom"
> >
<button <button
type="button" type="button"
className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${ className={`grid h-7 w-7 place-items-center rounded p-1 outline-none hover:bg-custom-sidebar-background-80 duration-300 ${
modulesView === option.type modulesView === option.type ? "bg-custom-sidebar-background-80" : "text-custom-sidebar-text-200"
? "bg-custom-sidebar-background-80"
: "text-custom-sidebar-text-200"
}`} }`}
onClick={() => setModulesView(option.type)} onClick={() => setModulesView(option.type)}
> >
<Icon <Icon iconName={option.icon} className={`!text-base ${option.type === "grid" ? "rotate-90" : ""}`} />
iconName={option.icon}
className={`!text-base ${option.type === "grid" ? "rotate-90" : ""}`}
/>
</button> </button>
</Tooltip> </Tooltip>
))} ))}

View File

@ -21,7 +21,7 @@ import issuesService from "services/issue.service";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages"; import { CreateUpdateBlockInline, SinglePageBlock } from "components/pages";
import { CreateLabelModal } from "components/labels"; import { CreateLabelModal } from "components/labels";

View File

@ -15,7 +15,7 @@ import useUserAuth from "hooks/use-user-auth";
// icons // icons
import { PlusIcon } from "components/icons"; import { PlusIcon } from "components/icons";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import { RecentPagesList, CreateUpdatePageModal, TPagesListProps } from "components/pages"; import { RecentPagesList, CreateUpdatePageModal, TPagesListProps } from "components/pages";
// ui // ui
@ -31,33 +31,21 @@ import { PROJECT_DETAILS } from "constants/fetch-keys";
// helper // helper
import { truncateText } from "helpers/string.helper"; import { truncateText } from "helpers/string.helper";
const AllPagesList = dynamic<TPagesListProps>( const AllPagesList = dynamic<TPagesListProps>(() => import("components/pages").then((a) => a.AllPagesList), {
() => import("components/pages").then((a) => a.AllPagesList), ssr: false,
{ });
ssr: false,
}
);
const FavoritePagesList = dynamic<TPagesListProps>( const FavoritePagesList = dynamic<TPagesListProps>(() => import("components/pages").then((a) => a.FavoritePagesList), {
() => import("components/pages").then((a) => a.FavoritePagesList), ssr: false,
{ });
ssr: false,
}
);
const MyPagesList = dynamic<TPagesListProps>( const MyPagesList = dynamic<TPagesListProps>(() => import("components/pages").then((a) => a.MyPagesList), {
() => import("components/pages").then((a) => a.MyPagesList), ssr: false,
{ });
ssr: false,
}
);
const OtherPagesList = dynamic<TPagesListProps>( const OtherPagesList = dynamic<TPagesListProps>(() => import("components/pages").then((a) => a.OtherPagesList), {
() => import("components/pages").then((a) => a.OtherPagesList), ssr: false,
{ });
ssr: false,
}
);
const tabsList = ["Recent", "All", "Favorites", "Created by me", "Created by others"]; const tabsList = ["Recent", "All", "Favorites", "Created by me", "Created by others"];
@ -75,9 +63,7 @@ const ProjectPages: NextPage = () => {
const { data: projectDetails } = useSWR( const { data: projectDetails } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
? () => projectService.getProject(workspaceSlug as string, projectId as string)
: null
); );
const currentTabValue = (tab: string | null) => { const currentTabValue = (tab: string | null) => {
@ -109,9 +95,7 @@ const ProjectPages: NextPage = () => {
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} /> <BreadcrumbItem title="Projects" link={`/${workspaceSlug}/projects`} />
<BreadcrumbItem <BreadcrumbItem title={`${truncateText(projectDetails?.name ?? "Project", 32)} Pages`} />
title={`${truncateText(projectDetails?.name ?? "Project", 32)} Pages`}
/>
</Breadcrumbs> </Breadcrumbs>
} }
right={ right={

View File

@ -7,7 +7,7 @@ import { mutate } from "swr";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// hooks // hooks
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
import useProjectDetails from "hooks/use-project-details"; import useProjectDetails from "hooks/use-project-details";
@ -45,8 +45,7 @@ const AutomationsSettings: NextPage = () => {
mutate<IProject[]>( mutate<IProject[]>(
PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }), PROJECTS_LIST(workspaceSlug as string, { is_favorite: "all" }),
(prevData) => (prevData) => (prevData ?? []).map((p) => (p.id === projectDetails.id ? { ...p, ...formData } : p)),
(prevData ?? []).map((p) => (p.id === projectDetails.id ? { ...p, ...formData } : p)),
false false
); );

View File

@ -10,7 +10,7 @@ import projectService from "services/project.service";
// hooks // hooks
import useProjectDetails from "hooks/use-project-details"; import useProjectDetails from "hooks/use-project-details";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates"; import { CreateUpdateEstimateModal, SingleEstimate } from "components/estimates";
import { SettingsSidebar } from "components/project"; import { SettingsSidebar } from "components/project";

View File

@ -8,7 +8,7 @@ import useSWR, { mutate } from "swr";
import projectService from "services/project.service"; import projectService from "services/project.service";
import trackEventServices, { MiscellaneousEventType } from "services/track_event.service"; import trackEventServices, { MiscellaneousEventType } from "services/track_event.service";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";

View File

@ -3,7 +3,7 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
import { Disclosure, Transition } from "@headlessui/react"; import { Disclosure, Transition } from "@headlessui/react";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// services // services
import projectService from "services/project.service"; import projectService from "services/project.service";
// components // components

View File

@ -5,7 +5,7 @@ import { useRouter } from "next/router";
import useSWR from "swr"; import useSWR from "swr";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// services // services
import IntegrationService from "services/integration.service"; import IntegrationService from "services/integration.service";
import projectService from "services/project.service"; import projectService from "services/project.service";

View File

@ -10,7 +10,7 @@ import useUserAuth from "hooks/use-user-auth";
import projectService from "services/project.service"; import projectService from "services/project.service";
import issuesService from "services/issue.service"; import issuesService from "services/issue.service";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import { import {
CreateUpdateLabelInline, CreateUpdateLabelInline,

View File

@ -15,7 +15,7 @@ import useProjectMembers from "hooks/use-project-members";
import useProjectDetails from "hooks/use-project-details"; import useProjectDetails from "hooks/use-project-details";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove"; import ConfirmProjectMemberRemove from "components/project/confirm-project-member-remove";
import SendProjectInvitationModal from "components/project/send-project-invitation-modal"; import SendProjectInvitationModal from "components/project/send-project-invitation-modal";
@ -80,9 +80,8 @@ const MembersSettings: NextPage = () => {
formState: { isSubmitting }, formState: { isSubmitting },
} = useForm<IProject>({ defaultValues }); } = useForm<IProject>({ defaultValues });
const { data: activeWorkspace } = useSWR( const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
); );
const { data: people } = useSWR( const { data: people } = useSWR(
@ -93,21 +92,16 @@ const MembersSettings: NextPage = () => {
); );
const { data: projectMembers, mutate: mutateMembers } = useSWR( const { data: projectMembers, mutate: mutateMembers } = useSWR(
workspaceSlug && projectId workspaceSlug && projectId ? PROJECT_MEMBERS_WITH_EMAIL(workspaceSlug.toString(), projectId.toString()) : null,
? PROJECT_MEMBERS_WITH_EMAIL(workspaceSlug.toString(), projectId.toString())
: null,
workspaceSlug && projectId workspaceSlug && projectId
? () => projectService.projectMembersWithEmail(workspaceSlug as string, projectId as string) ? () => projectService.projectMembersWithEmail(workspaceSlug as string, projectId as string)
: null : null
); );
const { data: projectInvitations, mutate: mutateInvitations } = useSWR( const { data: projectInvitations, mutate: mutateInvitations } = useSWR(
workspaceSlug && projectId ? PROJECT_INVITATIONS_WITH_EMAIL(workspaceSlug.toString(), projectId.toString()) : null,
workspaceSlug && projectId workspaceSlug && projectId
? PROJECT_INVITATIONS_WITH_EMAIL(workspaceSlug.toString(), projectId.toString()) ? () => projectService.projectInvitationsWithEmail(workspaceSlug as string, projectId as string)
: null,
workspaceSlug && projectId
? () =>
projectService.projectInvitationsWithEmail(workspaceSlug as string, projectId as string)
: null : null
); );
@ -231,21 +225,12 @@ const MembersSettings: NextPage = () => {
setSelectedRemoveMember(null); setSelectedRemoveMember(null);
setSelectedInviteRemoveMember(null); setSelectedInviteRemoveMember(null);
}} }}
data={members.find( data={members.find((item) => item.id === selectedRemoveMember || item.id === selectedInviteRemoveMember)}
(item) => item.id === selectedRemoveMember || item.id === selectedInviteRemoveMember
)}
handleDelete={async () => { handleDelete={async () => {
if (!activeWorkspace || !projectDetails) return; if (!activeWorkspace || !projectDetails) return;
if (selectedRemoveMember) { if (selectedRemoveMember) {
await projectService.deleteProjectMember( await projectService.deleteProjectMember(activeWorkspace.slug, projectDetails.id, selectedRemoveMember);
activeWorkspace.slug, mutateMembers((prevData: any) => prevData?.filter((item: any) => item.id !== selectedRemoveMember), false);
projectDetails.id,
selectedRemoveMember
);
mutateMembers(
(prevData: any) => prevData?.filter((item: any) => item.id !== selectedRemoveMember),
false
);
} }
if (selectedInviteRemoveMember) { if (selectedInviteRemoveMember) {
await projectService.deleteProjectInvitation( await projectService.deleteProjectInvitation(
@ -254,8 +239,7 @@ const MembersSettings: NextPage = () => {
selectedInviteRemoveMember selectedInviteRemoveMember
); );
mutateInvitations( mutateInvitations(
(prevData: any) => (prevData: any) => prevData?.filter((item: any) => item.id !== selectedInviteRemoveMember),
prevData?.filter((item: any) => item.id !== selectedInviteRemoveMember),
false false
); );
} }
@ -348,10 +332,7 @@ const MembersSettings: NextPage = () => {
<div className="divide-y divide-custom-border-200"> <div className="divide-y divide-custom-border-200">
{members.length > 0 {members.length > 0
? members.map((member) => ( ? members.map((member) => (
<div <div key={member.id} className="flex items-center justify-between px-3.5 py-[18px]">
key={member.id}
className="flex items-center justify-between px-3.5 py-[18px]"
>
<div className="flex items-center gap-x-6 gap-y-2"> <div className="flex items-center gap-x-6 gap-y-2">
{member.avatar && member.avatar !== "" ? ( {member.avatar && member.avatar !== "" ? (
<div className="relative flex h-10 w-10 items-center justify-center rounded-lg p-4 capitalize text-white"> <div className="relative flex h-10 w-10 items-center justify-center rounded-lg p-4 capitalize text-white">
@ -377,19 +358,13 @@ const MembersSettings: NextPage = () => {
<span> <span>
{member.first_name} {member.last_name} {member.first_name} {member.last_name}
</span> </span>
<span className="text-custom-text-300 text-sm ml-2"> <span className="text-custom-text-300 text-sm ml-2">({member.display_name})</span>
({member.display_name})
</span>
</a> </a>
</Link> </Link>
) : ( ) : (
<h4 className="text-sm">{member.display_name || member.email}</h4> <h4 className="text-sm">{member.display_name || member.email}</h4>
)} )}
{isOwner && ( {isOwner && <p className="mt-0.5 text-xs text-custom-sidebar-text-300">{member.email}</p>}
<p className="mt-0.5 text-xs text-custom-sidebar-text-300">
{member.email}
</p>
)}
</div> </div>
</div> </div>
<div className="flex items-center gap-3 text-xs"> <div className="flex items-center gap-3 text-xs">
@ -419,27 +394,19 @@ const MembersSettings: NextPage = () => {
mutateMembers( mutateMembers(
(prevData: any) => (prevData: any) =>
prevData.map((m: any) => prevData.map((m: any) => (m.id === member.id ? { ...m, role: value } : m)),
m.id === member.id ? { ...m, role: value } : m
),
false false
); );
projectService projectService
.updateProjectMember( .updateProjectMember(activeWorkspace.slug, projectDetails.id, member.id, {
activeWorkspace.slug, role: value,
projectDetails.id, })
member.id,
{
role: value,
}
)
.catch(() => { .catch(() => {
setToastAlert({ setToastAlert({
type: "error", type: "error",
title: "Error!", title: "Error!",
message: message: "An error occurred while updating member role. Please try again.",
"An error occurred while updating member role. Please try again.",
}); });
}); });
}} }}
@ -447,18 +414,11 @@ const MembersSettings: NextPage = () => {
disabled={ disabled={
member.memberId === user?.id || member.memberId === user?.id ||
!member.member || !member.member ||
(currentUser && (currentUser && currentUser.role !== 20 && currentUser.role < member.role)
currentUser.role !== 20 &&
currentUser.role < member.role)
} }
> >
{Object.keys(ROLE).map((key) => { {Object.keys(ROLE).map((key) => {
if ( if (currentUser && currentUser.role !== 20 && currentUser.role < parseInt(key)) return null;
currentUser &&
currentUser.role !== 20 &&
currentUser.role < parseInt(key)
)
return null;
return ( return (
<CustomSelect.Option key={key} value={key}> <CustomSelect.Option key={key} value={key}>
@ -477,10 +437,7 @@ const MembersSettings: NextPage = () => {
<span className="flex items-center justify-start gap-2"> <span className="flex items-center justify-start gap-2">
<XMarkIcon className="h-4 w-4" /> <XMarkIcon className="h-4 w-4" />
<span> <span> {member.memberId !== user?.id ? "Remove member" : "Leave project"}</span>
{" "}
{member.memberId !== user?.id ? "Remove member" : "Leave project"}
</span>
</span> </span>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
</CustomMenu> </CustomMenu>

View File

@ -10,7 +10,7 @@ import stateService from "services/project_state.service";
import useProjectDetails from "hooks/use-project-details"; import useProjectDetails from "hooks/use-project-details";
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// components // components
import { CreateUpdateStateInline, DeleteStateModal, SingleState, StateGroup } from "components/states"; import { CreateUpdateStateInline, DeleteStateModal, SingleState, StateGroup } from "components/states";
import { SettingsSidebar } from "components/project"; import { SettingsSidebar } from "components/project";

View File

@ -6,7 +6,7 @@ import useSWR from "swr";
import projectService from "services/project.service"; import projectService from "services/project.service";
import viewsService from "services/views.service"; import viewsService from "services/views.service";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// contexts // contexts
import { IssueViewContextProvider } from "contexts/issue-view.context"; import { IssueViewContextProvider } from "contexts/issue-view.context";
// components // components
@ -30,27 +30,18 @@ const SingleView: React.FC = () => {
const { data: activeProject } = useSWR( const { data: activeProject } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
? () => projectService.getProject(workspaceSlug as string, projectId as string)
: null
); );
const { data: views } = useSWR( const { data: views } = useSWR(
workspaceSlug && projectId ? VIEWS_LIST(projectId as string) : null, workspaceSlug && projectId ? VIEWS_LIST(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => viewsService.getViews(workspaceSlug as string, projectId as string) : null
? () => viewsService.getViews(workspaceSlug as string, projectId as string)
: null
); );
const { data: viewDetails, error } = useSWR( const { data: viewDetails, error } = useSWR(
workspaceSlug && projectId && viewId ? VIEW_DETAILS(viewId as string) : null, workspaceSlug && projectId && viewId ? VIEW_DETAILS(viewId as string) : null,
workspaceSlug && projectId && viewId workspaceSlug && projectId && viewId
? () => ? () => viewsService.getViewDetails(workspaceSlug as string, projectId as string, viewId as string)
viewsService.getViewDetails(
workspaceSlug as string,
projectId as string,
viewId as string
)
: null : null
); );

View File

@ -10,7 +10,7 @@ import useUserAuth from "hooks/use-user-auth";
import viewsService from "services/views.service"; import viewsService from "services/views.service";
import projectService from "services/project.service"; import projectService from "services/project.service";
// layouts // layouts
import { ProjectAuthorizationWrapper } from "layouts/auth-layout"; import { ProjectAuthorizationWrapper } from "layouts/auth-layout-legacy";
// ui // ui
import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs";
//icons //icons
@ -40,16 +40,12 @@ const ProjectViews: NextPage = () => {
const { data: activeProject } = useSWR( const { data: activeProject } = useSWR(
workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null, workspaceSlug && projectId ? PROJECT_DETAILS(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => projectService.getProject(workspaceSlug as string, projectId as string) : null
? () => projectService.getProject(workspaceSlug as string, projectId as string)
: null
); );
const { data: views } = useSWR( const { data: views } = useSWR(
workspaceSlug && projectId ? VIEWS_LIST(projectId as string) : null, workspaceSlug && projectId ? VIEWS_LIST(projectId as string) : null,
workspaceSlug && projectId workspaceSlug && projectId ? () => viewsService.getViews(workspaceSlug as string, projectId as string) : null
? () => viewsService.getViews(workspaceSlug as string, projectId as string)
: null
); );
const handleEditView = (view: IView) => { const handleEditView = (view: IView) => {

View File

@ -9,7 +9,7 @@ import useProjects from "hooks/use-projects";
import useWorkspaces from "hooks/use-workspaces"; import useWorkspaces from "hooks/use-workspaces";
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// ui // ui
import { Icon, PrimaryButton } from "components/ui"; import { Icon, PrimaryButton } from "components/ui";
import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs"; import { Breadcrumbs, BreadcrumbItem } from "components/breadcrumbs";

View File

@ -7,7 +7,7 @@ import useSWR from "swr";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// component // component
import { SettingsSidebar } from "components/project"; import { SettingsSidebar } from "components/project";
// ui // ui
@ -25,9 +25,8 @@ const BillingSettings: NextPage = () => {
query: { workspaceSlug }, query: { workspaceSlug },
} = useRouter(); } = useRouter();
const { data: activeWorkspace } = useSWR( const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
); );
return ( return (
@ -56,9 +55,7 @@ const BillingSettings: NextPage = () => {
<div className="px-4 py-6"> <div className="px-4 py-6">
<div> <div>
<h4 className="text-md mb-1 leading-6">Current plan</h4> <h4 className="text-md mb-1 leading-6">Current plan</h4>
<p className="mb-3 text-sm text-custom-text-200"> <p className="mb-3 text-sm text-custom-text-200">You are currently using the free plan</p>
You are currently using the free plan
</p>
<a href="https://plane.so/pricing" target="_blank" rel="noreferrer"> <a href="https://plane.so/pricing" target="_blank" rel="noreferrer">
<SecondaryButton outline>View Plans</SecondaryButton> <SecondaryButton outline>View Plans</SecondaryButton>
</a> </a>

View File

@ -5,7 +5,7 @@ import useSWR from "swr";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import ExportGuide from "components/exporter/guide"; import ExportGuide from "components/exporter/guide";
import { SettingsSidebar } from "components/project"; import { SettingsSidebar } from "components/project";
@ -22,9 +22,8 @@ const ImportExport: NextPage = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { data: activeWorkspace } = useSWR( const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
); );
return ( return (

View File

@ -5,7 +5,7 @@ import useSWR from "swr";
// services // services
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import IntegrationGuide from "components/integration/guide"; import IntegrationGuide from "components/integration/guide";
import { SettingsSidebar } from "components/project"; import { SettingsSidebar } from "components/project";
@ -22,9 +22,8 @@ const ImportExport: NextPage = () => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug } = router.query; const { workspaceSlug } = router.query;
const { data: activeWorkspace } = useSWR( const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
); );
return ( return (

View File

@ -13,7 +13,7 @@ import fileService from "services/file.service";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
import useUserAuth from "hooks/use-user-auth"; import useUserAuth from "hooks/use-user-auth";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import { ImageUploadModal } from "components/core"; import { ImageUploadModal } from "components/core";
import { DeleteWorkspaceModal } from "components/workspace"; import { DeleteWorkspaceModal } from "components/workspace";
@ -59,9 +59,8 @@ const WorkspaceSettings: NextPage = () => {
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const { data: activeWorkspace } = useSWR( const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, () =>
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug as string) : null, workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug as string) : null)
); );
const { const {
@ -156,9 +155,7 @@ const WorkspaceSettings: NextPage = () => {
<WorkspaceAuthorizationLayout <WorkspaceAuthorizationLayout
breadcrumbs={ breadcrumbs={
<Breadcrumbs> <Breadcrumbs>
<BreadcrumbItem <BreadcrumbItem title={`${truncateText(activeWorkspace?.name ?? "Workspace", 32)} Settings`} />
title={`${truncateText(activeWorkspace?.name ?? "Workspace", 32)} Settings`}
/>
</Breadcrumbs> </Breadcrumbs>
} }
> >
@ -191,11 +188,7 @@ const WorkspaceSettings: NextPage = () => {
<div className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}> <div className={`pr-9 py-8 w-full overflow-y-auto ${isAdmin ? "" : "opacity-60"}`}>
<div className="flex gap-5 items-center pb-7 border-b border-custom-border-200"> <div className="flex gap-5 items-center pb-7 border-b border-custom-border-200">
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<button <button type="button" onClick={() => setIsImageUploadModalOpen(true)} disabled={!isAdmin}>
type="button"
onClick={() => setIsImageUploadModalOpen(true)}
disabled={!isAdmin}
>
{watch("logo") && watch("logo") !== null && watch("logo") !== "" ? ( {watch("logo") && watch("logo") !== null && watch("logo") !== "" ? (
<div className="relative mx-auto flex h-14 w-14"> <div className="relative mx-auto flex h-14 w-14">
<img <img
@ -214,8 +207,7 @@ const WorkspaceSettings: NextPage = () => {
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">
<h3 className="text-lg font-semibold leading-6">{watch("name")}</h3> <h3 className="text-lg font-semibold leading-6">{watch("name")}</h3>
<span className="text-sm tracking-tight">{`${ <span className="text-sm tracking-tight">{`${
typeof window !== "undefined" && typeof window !== "undefined" && window.location.origin.replace("http://", "").replace("https://", "")
window.location.origin.replace("http://", "").replace("https://", "")
}/${activeWorkspace.slug}`}</span> }/${activeWorkspace.slug}`}</span>
<div className="flex item-center gap-2.5"> <div className="flex item-center gap-2.5">
<button <button
@ -267,9 +259,7 @@ const WorkspaceSettings: NextPage = () => {
<CustomSelect <CustomSelect
value={value} value={value}
onChange={onChange} onChange={onChange}
label={ label={ORGANIZATION_SIZE.find((c) => c === value) ?? "Select organization size"}
ORGANIZATION_SIZE.find((c) => c === value) ?? "Select organization size"
}
width="w-full" width="w-full"
input input
disabled={!isAdmin} disabled={!isAdmin}
@ -303,11 +293,7 @@ const WorkspaceSettings: NextPage = () => {
</div> </div>
<div className="flex items-center justify-between py-2"> <div className="flex items-center justify-between py-2">
<PrimaryButton <PrimaryButton onClick={handleSubmit(onSubmit)} loading={isSubmitting} disabled={!isAdmin}>
onClick={handleSubmit(onSubmit)}
loading={isSubmitting}
disabled={!isAdmin}
>
{isSubmitting ? "Updating..." : "Update Workspace"} {isSubmitting ? "Updating..." : "Update Workspace"}
</PrimaryButton> </PrimaryButton>
</div> </div>
@ -337,17 +323,12 @@ const WorkspaceSettings: NextPage = () => {
<Disclosure.Panel> <Disclosure.Panel>
<div className="flex flex-col gap-8"> <div className="flex flex-col gap-8">
<span className="text-sm tracking-tight"> <span className="text-sm tracking-tight">
The danger zone of the project delete page is a critical area that The danger zone of the project delete page is a critical area that requires careful
requires careful consideration and attention. When deleting a project, all consideration and attention. When deleting a project, all of the data and resources within
of the data and resources within that project will be permanently removed that project will be permanently removed and cannot be recovered.
and cannot be recovered.
</span> </span>
<div> <div>
<DangerButton <DangerButton onClick={() => setIsOpen(true)} className="!text-sm" outline>
onClick={() => setIsOpen(true)}
className="!text-sm"
outline
>
Delete my project Delete my project
</DangerButton> </DangerButton>
</div> </div>

View File

@ -8,7 +8,7 @@ import useSWR from "swr";
import workspaceService from "services/workspace.service"; import workspaceService from "services/workspace.service";
import IntegrationService from "services/integration.service"; import IntegrationService from "services/integration.service";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import { SingleIntegrationCard } from "components/integration"; import { SingleIntegrationCard } from "components/integration";
import { SettingsSidebar } from "components/project"; import { SettingsSidebar } from "components/project";

View File

@ -12,7 +12,7 @@ import useToast from "hooks/use-toast";
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
import useWorkspaceMembers from "hooks/use-workspace-members"; import useWorkspaceMembers from "hooks/use-workspace-members";
// layouts // layouts
import { WorkspaceAuthorizationLayout } from "layouts/auth-layout"; import { WorkspaceAuthorizationLayout } from "layouts/auth-layout-legacy";
// components // components
import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove"; import ConfirmWorkspaceMemberRemove from "components/workspace/confirm-workspace-member-remove";
import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal"; import SendWorkspaceInvitationModal from "components/workspace/send-workspace-invitation-modal";
@ -25,11 +25,7 @@ import { XMarkIcon } from "components/icons";
// types // types
import type { NextPage } from "next"; import type { NextPage } from "next";
// fetch-keys // fetch-keys
import { import { WORKSPACE_DETAILS, WORKSPACE_INVITATION_WITH_EMAIL, WORKSPACE_MEMBERS_WITH_EMAIL } from "constants/fetch-keys";
WORKSPACE_DETAILS,
WORKSPACE_INVITATION_WITH_EMAIL,
WORKSPACE_MEMBERS_WITH_EMAIL,
} from "constants/fetch-keys";
// constants // constants
import { ROLE } from "constants/workspace"; import { ROLE } from "constants/workspace";
// helper // helper
@ -49,23 +45,18 @@ const MembersSettings: NextPage = () => {
const { isOwner } = useWorkspaceMembers(workspaceSlug?.toString(), Boolean(workspaceSlug)); const { isOwner } = useWorkspaceMembers(workspaceSlug?.toString(), Boolean(workspaceSlug));
const { data: activeWorkspace } = useSWR( const { data: activeWorkspace } = useSWR(workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug.toString()) : null, () =>
workspaceSlug ? WORKSPACE_DETAILS(workspaceSlug.toString()) : null, workspaceSlug ? workspaceService.getWorkspace(workspaceSlug.toString()) : null
() => (workspaceSlug ? workspaceService.getWorkspace(workspaceSlug.toString()) : null)
); );
const { data: workspaceMembers, mutate: mutateMembers } = useSWR( const { data: workspaceMembers, mutate: mutateMembers } = useSWR(
workspaceSlug ? WORKSPACE_MEMBERS_WITH_EMAIL(workspaceSlug.toString()) : null, workspaceSlug ? WORKSPACE_MEMBERS_WITH_EMAIL(workspaceSlug.toString()) : null,
workspaceSlug workspaceSlug ? () => workspaceService.workspaceMembersWithEmail(workspaceSlug.toString()) : null
? () => workspaceService.workspaceMembersWithEmail(workspaceSlug.toString())
: null
); );
const { data: workspaceInvitations, mutate: mutateInvitations } = useSWR( const { data: workspaceInvitations, mutate: mutateInvitations } = useSWR(
workspaceSlug ? WORKSPACE_INVITATION_WITH_EMAIL(workspaceSlug.toString()) : null, workspaceSlug ? WORKSPACE_INVITATION_WITH_EMAIL(workspaceSlug.toString()) : null,
workspaceSlug workspaceSlug ? () => workspaceService.workspaceInvitationsWithEmail(workspaceSlug.toString()) : null
? () => workspaceService.workspaceInvitationsWithEmail(workspaceSlug.toString())
: null
); );
const members = [ const members = [
@ -143,15 +134,12 @@ const MembersSettings: NextPage = () => {
}); });
}) })
.finally(() => { .finally(() => {
mutateMembers((prevData: any) => mutateMembers((prevData: any) => prevData?.filter((item: any) => item.id !== selectedRemoveMember));
prevData?.filter((item: any) => item.id !== selectedRemoveMember)
);
}); });
} }
if (selectedInviteRemoveMember) { if (selectedInviteRemoveMember) {
mutateInvitations( mutateInvitations(
(prevData: any) => (prevData: any) => prevData?.filter((item: any) => item.id !== selectedInviteRemoveMember),
prevData?.filter((item: any) => item.id !== selectedInviteRemoveMember),
false false
); );
workspaceService workspaceService
@ -206,10 +194,7 @@ const MembersSettings: NextPage = () => {
<div className="divide-y divide-custom-border-200"> <div className="divide-y divide-custom-border-200">
{members.length > 0 {members.length > 0
? members.map((member) => ( ? members.map((member) => (
<div <div key={member.id} className="group flex items-center justify-between px-3.5 py-[18px]">
key={member.id}
className="group flex items-center justify-between px-3.5 py-[18px]"
>
<div className="flex items-center gap-x-8 gap-y-2"> <div className="flex items-center gap-x-8 gap-y-2">
{member.avatar && member.avatar !== "" ? ( {member.avatar && member.avatar !== "" ? (
<Link href={`/${workspaceSlug}/profile/${member.memberId}`}> <Link href={`/${workspaceSlug}/profile/${member.memberId}`}>
@ -239,21 +224,13 @@ const MembersSettings: NextPage = () => {
<span> <span>
{member.first_name} {member.last_name} {member.first_name} {member.last_name}
</span> </span>
<span className="text-custom-text-300 text-sm ml-2"> <span className="text-custom-text-300 text-sm ml-2">({member.display_name})</span>
({member.display_name})
</span>
</a> </a>
</Link> </Link>
) : ( ) : (
<h4 className="text-sm cursor-default"> <h4 className="text-sm cursor-default">{member.display_name || member.email}</h4>
{member.display_name || member.email}
</h4>
)}
{isOwner && (
<p className="mt-0.5 text-xs text-custom-sidebar-text-300">
{member.email}
</p>
)} )}
{isOwner && <p className="mt-0.5 text-xs text-custom-sidebar-text-300">{member.email}</p>}
</div> </div>
</div> </div>
<div className="flex items-center gap-3 text-xs"> <div className="flex items-center gap-3 text-xs">
@ -288,9 +265,7 @@ const MembersSettings: NextPage = () => {
mutateMembers( mutateMembers(
(prevData: any) => (prevData: any) =>
prevData?.map((m: any) => prevData?.map((m: any) => (m.id === member.id ? { ...m, role: value } : m)),
m.id === member.id ? { ...m, role: value } : m
),
false false
); );
@ -302,8 +277,7 @@ const MembersSettings: NextPage = () => {
setToastAlert({ setToastAlert({
type: "error", type: "error",
title: "Error!", title: "Error!",
message: message: "An error occurred while updating member role. Please try again.",
"An error occurred while updating member role. Please try again.",
}); });
}); });
}} }}
@ -311,18 +285,11 @@ const MembersSettings: NextPage = () => {
disabled={ disabled={
member.memberId === currentUser?.member.id || member.memberId === currentUser?.member.id ||
!member.status || !member.status ||
(currentUser && (currentUser && currentUser.role !== 20 && currentUser.role < member.role)
currentUser.role !== 20 &&
currentUser.role < member.role)
} }
> >
{Object.keys(ROLE).map((key) => { {Object.keys(ROLE).map((key) => {
if ( if (currentUser && currentUser.role !== 20 && currentUser.role < parseInt(key)) return null;
currentUser &&
currentUser.role !== 20 &&
currentUser.role < parseInt(key)
)
return null;
return ( return (
<CustomSelect.Option key={key} value={key}> <CustomSelect.Option key={key} value={key}>
@ -344,10 +311,7 @@ const MembersSettings: NextPage = () => {
<span className="flex items-center justify-start gap-2"> <span className="flex items-center justify-start gap-2">
<XMarkIcon className="h-4 w-4" /> <XMarkIcon className="h-4 w-4" />
<span> <span> {user?.id === member.memberId ? "Leave" : "Remove member"}</span>
{" "}
{user?.id === member.memberId ? "Leave" : "Remove member"}
</span>
</span> </span>
</CustomMenu.MenuItem> </CustomMenu.MenuItem>
</CustomMenu> </CustomMenu>

View File

@ -2,7 +2,7 @@ import React from "react";
// layouts // layouts
import DefaultLayout from "layouts/default-layout"; import DefaultLayout from "layouts/default-layout";
import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper"; import { UserAuthorizationLayout } from "layouts/auth-layout-legacy/user-authorization-wrapper";
// components // components
import { CalendarView } from "components/issues"; import { CalendarView } from "components/issues";
// types // types

View File

@ -13,7 +13,7 @@ import userService from "services/user.service";
import useUser from "hooks/use-user"; import useUser from "hooks/use-user";
// layouts // layouts
import DefaultLayout from "layouts/default-layout"; import DefaultLayout from "layouts/default-layout";
import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper"; import { UserAuthorizationLayout } from "layouts/auth-layout-legacy/user-authorization-wrapper";
// components // components
import { CreateWorkspaceForm } from "components/workspace"; import { CreateWorkspaceForm } from "components/workspace";
// images // images
@ -59,9 +59,7 @@ const CreateWorkspace: NextPage = () => {
false false
); );
await userService await userService.updateUser({ last_workspace_id: workspace.id }).then(() => router.push(`/${workspace.slug}`));
.updateUser({ last_workspace_id: workspace.id })
.then(() => router.push(`/${workspace.slug}`));
}; };
return ( return (

View File

@ -15,7 +15,7 @@ import useUser from "hooks/use-user";
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// layouts // layouts
import DefaultLayout from "layouts/default-layout"; import DefaultLayout from "layouts/default-layout";
import { UserAuthorizationLayout } from "layouts/auth-layout/user-authorization-wrapper"; import { UserAuthorizationLayout } from "layouts/auth-layout-legacy/user-authorization-wrapper";
// ui // ui
import { SecondaryButton, PrimaryButton, EmptyState } from "components/ui"; import { SecondaryButton, PrimaryButton, EmptyState } from "components/ui";
// icons // icons
@ -51,16 +51,11 @@ const OnBoard: NextPage = () => {
workspaceService.userWorkspaceInvitations() workspaceService.userWorkspaceInvitations()
); );
const handleInvitation = ( const handleInvitation = (workspace_invitation: IWorkspaceMemberInvitation, action: "accepted" | "withdraw") => {
workspace_invitation: IWorkspaceMemberInvitation,
action: "accepted" | "withdraw"
) => {
if (action === "accepted") { if (action === "accepted") {
setInvitationsRespond((prevData) => [...prevData, workspace_invitation.id]); setInvitationsRespond((prevData) => [...prevData, workspace_invitation.id]);
} else if (action === "withdraw") { } else if (action === "withdraw") {
setInvitationsRespond((prevData) => setInvitationsRespond((prevData) => prevData.filter((item: string) => item !== workspace_invitation.id));
prevData.filter((item: string) => item !== workspace_invitation.id)
);
} }
}; };
@ -144,9 +139,7 @@ const OnBoard: NextPage = () => {
? "border-custom-primary-100" ? "border-custom-primary-100"
: "border-custom-border-200 hover:bg-custom-background-80" : "border-custom-border-200 hover:bg-custom-background-80"
}`} }`}
onClick={() => onClick={() => handleInvitation(invitation, isSelected ? "withdraw" : "accepted")}
handleInvitation(invitation, isSelected ? "withdraw" : "accepted")
}
> >
<div className="flex-shrink-0"> <div className="flex-shrink-0">
<div className="grid place-items-center h-9 w-9 rounded"> <div className="grid place-items-center h-9 w-9 rounded">
@ -166,9 +159,7 @@ const OnBoard: NextPage = () => {
</div> </div>
</div> </div>
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="text-sm font-medium"> <div className="text-sm font-medium">{truncateText(invitation.workspace.name, 30)}</div>
{truncateText(invitation.workspace.name, 30)}
</div>
<p className="text-xs text-custom-text-200">{ROLE[invitation.role]}</p> <p className="text-xs text-custom-text-200">{ROLE[invitation.role]}</p>
</div> </div>
<span <span

View File

@ -2,8 +2,6 @@
import { action, observable, makeObservable } from "mobx"; import { action, observable, makeObservable } from "mobx";
// helper // helper
import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper"; import { applyTheme, unsetCustomCssVariables } from "helpers/theme.helper";
// interfaces
import { ICurrentUserSettings } from "types";
class ThemeStore { class ThemeStore {
sidebarCollapsed: boolean | null = null; sidebarCollapsed: boolean | null = null;
@ -14,8 +12,8 @@ class ThemeStore {
constructor(_rootStore: any | null = null) { constructor(_rootStore: any | null = null) {
makeObservable(this, { makeObservable(this, {
// observable // observable
sidebarCollapsed: observable, sidebarCollapsed: observable.ref,
theme: observable, theme: observable.ref,
// action // action
setSidebarCollapsed: action, setSidebarCollapsed: action,
setTheme: action, setTheme: action,