Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3865bbdd30 | |||
| e54b940f86 | |||
| 543636eb40 | |||
| 6c2fecd322 | |||
| e4084fe156 |
@@ -37,7 +37,7 @@ Meet [Plane](https://plane.so). An open-source software development tool to mana
|
||||
|
||||
> Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our [Discord](https://discord.com/invite/A92xrEGCge) or GitHub issues, and we will use your feedback to improve on our upcoming releases.
|
||||
|
||||
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/self-hosting/docker-compose).
|
||||
The easiest way to get started with Plane is by creating a [Plane Cloud](https://app.plane.so) account. Plane Cloud offers a hosted solution for Plane. If you prefer to self-host Plane, please refer to our [deployment documentation](https://docs.plane.so/docker-compose).
|
||||
|
||||
## ⚡️ Contributors Quick Start
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@ SENTRY_DSN=""
|
||||
SENTRY_ENVIRONMENT="development"
|
||||
|
||||
# Database Settings
|
||||
PGUSER="plane"
|
||||
PGPASSWORD="plane"
|
||||
PGHOST="plane-db"
|
||||
PGDATABASE="plane"
|
||||
DATABASE_URL=postgresql://${PGUSER}:${PGPASSWORD}@${PGHOST}/${PGDATABASE}
|
||||
POSTGRES_USER="plane"
|
||||
POSTGRES_PASSWORD="plane"
|
||||
POSTGRES_HOST="plane-db"
|
||||
POSTGRES_DB="plane"
|
||||
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}/${POSTGRES_DB}
|
||||
|
||||
# Oauth variables
|
||||
GOOGLE_CLIENT_ID=""
|
||||
|
||||
@@ -53,6 +53,11 @@ export const CoreEditorExtensions = (
|
||||
class: "leading-normal -mb-2",
|
||||
},
|
||||
},
|
||||
hardBreak: {
|
||||
HTMLAttributes: {
|
||||
class: "p-2",
|
||||
},
|
||||
},
|
||||
code: false,
|
||||
codeBlock: false,
|
||||
horizontalRule: false,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { Command } from "cmdk";
|
||||
// icons
|
||||
import { SettingIcon } from "components/icons";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
import Link from "next/link";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles, WORKSPACE_SETTINGS_LINKS } from "constants/workspace";
|
||||
|
||||
type Props = {
|
||||
closePalette: () => void;
|
||||
@@ -10,60 +12,40 @@ type Props = {
|
||||
|
||||
export const CommandPaletteWorkspaceSettingsActions: React.FC<Props> = (props) => {
|
||||
const { closePalette } = props;
|
||||
|
||||
// router
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// mobx store
|
||||
const {
|
||||
membership: { currentWorkspaceRole },
|
||||
} = useUser();
|
||||
// derived values
|
||||
const workspaceMemberInfo = currentWorkspaceRole || EUserWorkspaceRoles.GUEST;
|
||||
|
||||
const redirect = (path: string) => {
|
||||
closePalette();
|
||||
router.push(path);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||
<Link href={`/${workspaceSlug}/settings`}>
|
||||
<div className="flex items-center gap-2 text-custom-text-200">
|
||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||
General
|
||||
</div>
|
||||
</Link>
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||
<Link href={`/${workspaceSlug}/settings/members`}>
|
||||
<div className="flex items-center gap-2 text-custom-text-200">
|
||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||
Members
|
||||
</div>
|
||||
</Link>
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||
<Link href={`/${workspaceSlug}/settings/billing`}>
|
||||
<div className="flex items-center gap-2 text-custom-text-200">
|
||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||
Billing and Plans
|
||||
</div>
|
||||
</Link>
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||
<Link href={`/${workspaceSlug}/settings/integrations`}>
|
||||
<div className="flex items-center gap-2 text-custom-text-200">
|
||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||
Integrations
|
||||
</div>
|
||||
</Link>
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||
<Link href={`/${workspaceSlug}/settings/imports`}>
|
||||
<div className="flex items-center gap-2 text-custom-text-200">
|
||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||
Import
|
||||
</div>
|
||||
</Link>
|
||||
</Command.Item>
|
||||
<Command.Item onSelect={closePalette} className="focus:outline-none">
|
||||
<Link href={`/${workspaceSlug}/settings/exports`}>
|
||||
<div className="flex items-center gap-2 text-custom-text-200">
|
||||
<SettingIcon className="h-4 w-4 text-custom-text-200" />
|
||||
Export
|
||||
</div>
|
||||
</Link>
|
||||
</Command.Item>
|
||||
{WORKSPACE_SETTINGS_LINKS.map(
|
||||
(setting) =>
|
||||
workspaceMemberInfo >= setting.access && (
|
||||
<Command.Item
|
||||
key={setting.key}
|
||||
onSelect={() => redirect(`/${workspaceSlug}${setting.href}`)}
|
||||
className="focus:outline-none"
|
||||
>
|
||||
<Link href={`/${workspaceSlug}${setting.href}`}>
|
||||
<div className="flex items-center gap-2 text-custom-text-200">
|
||||
<setting.Icon className="h-4 w-4 text-custom-text-200" />
|
||||
{setting.label}
|
||||
</div>
|
||||
</Link>
|
||||
</Command.Item>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -19,13 +19,7 @@ export const WorkspaceActiveCycleHeader = observer(() => {
|
||||
label="Active Cycles"
|
||||
/>
|
||||
</Breadcrumbs>
|
||||
<span
|
||||
className="flex items-center justify-center px-3.5 py-0.5 text-xs leading-5 rounded-xl"
|
||||
style={{
|
||||
color: "#F59E0B",
|
||||
backgroundColor: "#F59E0B20",
|
||||
}}
|
||||
>
|
||||
<span className="flex items-center justify-center px-3.5 py-0.5 text-xs leading-4 rounded-xl text-orange-500 bg-orange-500/20">
|
||||
Beta
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,6 @@ import React from "react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { BarChart2, Briefcase, CheckCircle, LayoutGrid, SendToBack } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useUser } from "hooks/store";
|
||||
// components
|
||||
@@ -11,34 +10,7 @@ import { NotificationPopover } from "components/notifications";
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "constants/workspace";
|
||||
|
||||
const workspaceLinks = (workspaceSlug: string) => [
|
||||
{
|
||||
Icon: LayoutGrid,
|
||||
name: "Dashboard",
|
||||
href: `/${workspaceSlug}`,
|
||||
},
|
||||
{
|
||||
Icon: BarChart2,
|
||||
name: "Analytics",
|
||||
href: `/${workspaceSlug}/analytics`,
|
||||
},
|
||||
{
|
||||
Icon: Briefcase,
|
||||
name: "Projects",
|
||||
href: `/${workspaceSlug}/projects`,
|
||||
},
|
||||
{
|
||||
Icon: CheckCircle,
|
||||
name: "All Issues",
|
||||
href: `/${workspaceSlug}/workspace-views/all-issues`,
|
||||
},
|
||||
{
|
||||
Icon: SendToBack,
|
||||
name: "Active cycles",
|
||||
href: `/${workspaceSlug}/active-cycles`,
|
||||
},
|
||||
];
|
||||
import { SIDEBAR_MENU_ITEMS } from "constants/dashboard";
|
||||
|
||||
export const WorkspaceSidebarMenu = observer(() => {
|
||||
// store hooks
|
||||
@@ -50,48 +22,41 @@ export const WorkspaceSidebarMenu = observer(() => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug } = router.query;
|
||||
// computed
|
||||
const isAuthorizedUser = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
|
||||
const workspaceMemberInfo = currentWorkspaceRole || EUserWorkspaceRoles.GUEST;
|
||||
|
||||
return (
|
||||
<div className="w-full cursor-pointer space-y-1 p-4">
|
||||
{workspaceLinks(workspaceSlug as string).map((link, index) => {
|
||||
const isActive = link.name === "Settings" ? router.asPath.includes(link.href) : router.asPath === link.href;
|
||||
if (!isAuthorizedUser && link.name === "Analytics") return;
|
||||
return (
|
||||
<Link key={index} href={link.href}>
|
||||
<span className="block w-full">
|
||||
<Tooltip
|
||||
tooltipContent={link.name}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!themeStore?.sidebarCollapsed}
|
||||
>
|
||||
<div
|
||||
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||
isActive
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80 focus:bg-custom-sidebar-background-80"
|
||||
} ${themeStore?.sidebarCollapsed ? "justify-center" : ""}`}
|
||||
<div className="w-full cursor-pointer space-y-2 p-4">
|
||||
{SIDEBAR_MENU_ITEMS.map(
|
||||
(link) =>
|
||||
workspaceMemberInfo >= link.access && (
|
||||
<Link key={link.key} href={`/${workspaceSlug}${link.href}`}>
|
||||
<span className="block w-full my-1">
|
||||
<Tooltip
|
||||
tooltipContent={link.label}
|
||||
position="right"
|
||||
className="ml-2"
|
||||
disabled={!themeStore?.sidebarCollapsed}
|
||||
>
|
||||
{<link.Icon className="h-4 w-4" />}
|
||||
{!themeStore?.sidebarCollapsed && link.name}
|
||||
{link.name === "Active Cycles" && (
|
||||
<span
|
||||
className="flex items-center justify-center px-3.5 py-0.5 text-xs leading-4 rounded-xl"
|
||||
style={{
|
||||
color: "#F59E0B",
|
||||
backgroundColor: "#F59E0B20",
|
||||
}}
|
||||
>
|
||||
Beta
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
<div
|
||||
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||
link.highlight(router.asPath, `/${workspaceSlug}`)
|
||||
? "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"
|
||||
} ${themeStore?.sidebarCollapsed ? "justify-center" : ""}`}
|
||||
>
|
||||
{<link.Icon className="h-4 w-4" />}
|
||||
{!themeStore?.sidebarCollapsed && link.label}
|
||||
{!themeStore?.sidebarCollapsed && link.key === "active-cycles" && (
|
||||
<span className="flex items-center justify-center px-3.5 py-0.5 text-xs leading-4 rounded-xl text-orange-500 bg-orange-500/20">
|
||||
Beta
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
</span>
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
<NotificationPopover />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,11 @@ import CompletedCreatedIssuesDark from "public/empty-state/dashboard/dark/comple
|
||||
import CompletedCreatedIssuesLight from "public/empty-state/dashboard/light/completed-created-issues.svg";
|
||||
// types
|
||||
import { TDurationFilterOptions, TIssuesListTypes, TStateGroups } from "@plane/types";
|
||||
import { Props } from "components/icons/types";
|
||||
// constants
|
||||
import { EUserWorkspaceRoles } from "./workspace";
|
||||
// icons
|
||||
import { BarChart2, Briefcase, CheckCircle, LayoutGrid, SendToBack } from "lucide-react";
|
||||
|
||||
// gradients for issues by priority widget graph bars
|
||||
export const PRIORITY_GRAPH_GRADIENTS = [
|
||||
@@ -246,3 +251,53 @@ export const CREATED_ISSUES_EMPTY_STATES = {
|
||||
lightImage: CompletedCreatedIssuesLight,
|
||||
},
|
||||
};
|
||||
|
||||
export const SIDEBAR_MENU_ITEMS: {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
access: EUserWorkspaceRoles;
|
||||
highlight: (pathname: string, baseUrl: string) => boolean;
|
||||
Icon: React.FC<Props>;
|
||||
}[] = [
|
||||
{
|
||||
key: "dashboard",
|
||||
label: "Dashboard",
|
||||
href: ``,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}`,
|
||||
Icon: LayoutGrid,
|
||||
},
|
||||
{
|
||||
key: "analytics",
|
||||
label: "Analytics",
|
||||
href: `/analytics`,
|
||||
access: EUserWorkspaceRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/analytics`,
|
||||
Icon: BarChart2,
|
||||
},
|
||||
{
|
||||
key: "projects",
|
||||
label: "Projects",
|
||||
href: `/projects`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/projects`,
|
||||
Icon: Briefcase,
|
||||
},
|
||||
{
|
||||
key: "all-issues",
|
||||
label: "All Issues",
|
||||
href: `/workspace-views/all-issues`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/workspace-views/all-issues`,
|
||||
Icon: CheckCircle,
|
||||
},
|
||||
{
|
||||
key: "active-cycles",
|
||||
label: "Active cycles",
|
||||
href: `/active-cycles`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/active-cycles`,
|
||||
Icon: SendToBack,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from "react";
|
||||
// icons
|
||||
import { Activity, CircleUser, KeyRound, LucideProps, Settings2 } from "lucide-react";
|
||||
|
||||
export const PROFILE_ACTION_LINKS: {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
highlight: (pathname: string) => boolean;
|
||||
Icon: React.FC<LucideProps>;
|
||||
}[] = [
|
||||
{
|
||||
key: "profile",
|
||||
label: "Profile",
|
||||
href: `/profile`,
|
||||
highlight: (pathname: string) => pathname === "/profile",
|
||||
Icon: CircleUser,
|
||||
},
|
||||
{
|
||||
key: "change-password",
|
||||
label: "Change password",
|
||||
href: `/profile/change-password`,
|
||||
highlight: (pathname: string) => pathname === "/profile/change-password",
|
||||
Icon: KeyRound,
|
||||
},
|
||||
{
|
||||
key: "activity",
|
||||
label: "Activity",
|
||||
href: `/profile/activity`,
|
||||
highlight: (pathname: string) => pathname === "/profile/activity",
|
||||
Icon: Activity,
|
||||
},
|
||||
{
|
||||
key: "preferences",
|
||||
label: "Preferences",
|
||||
href: `/profile/preferences`,
|
||||
highlight: (pathname: string) => pathname.includes("/profile/preferences"),
|
||||
Icon: Settings2,
|
||||
},
|
||||
];
|
||||
@@ -1,4 +1,8 @@
|
||||
// icons
|
||||
import { Globe2, Lock, LucideIcon } from "lucide-react";
|
||||
import { SettingIcon } from "components/icons";
|
||||
// types
|
||||
import { Props } from "components/icons/types";
|
||||
|
||||
export enum EUserProjectRoles {
|
||||
GUEST = 5,
|
||||
@@ -71,3 +75,77 @@ export const PROJECT_UNSPLASH_COVERS = [
|
||||
"https://images.unsplash.com/photo-1691230995681-480d86cbc135?auto=format&fit=crop&q=80&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&w=870&q=80",
|
||||
"https://images.unsplash.com/photo-1675351066828-6fc770b90dd2?auto=format&fit=crop&q=80&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&w=870&q=80",
|
||||
];
|
||||
|
||||
export const PROJECT_SETTINGS_LINKS: {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
access: EUserProjectRoles;
|
||||
highlight: (pathname: string, baseUrl: string) => boolean;
|
||||
Icon: React.FC<Props>;
|
||||
}[] = [
|
||||
{
|
||||
key: "general",
|
||||
label: "General",
|
||||
href: `/settings`,
|
||||
access: EUserProjectRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "members",
|
||||
label: "Members",
|
||||
href: `/settings/members`,
|
||||
access: EUserProjectRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "features",
|
||||
label: "Features",
|
||||
href: `/settings/features`,
|
||||
access: EUserProjectRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/features`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "states",
|
||||
label: "States",
|
||||
href: `/settings/states`,
|
||||
access: EUserProjectRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/states`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "labels",
|
||||
label: "Labels",
|
||||
href: `/settings/labels`,
|
||||
access: EUserProjectRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/labels`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "integrations",
|
||||
label: "Integrations",
|
||||
href: `/settings/integrations`,
|
||||
access: EUserProjectRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/integrations`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "estimates",
|
||||
label: "Estimates",
|
||||
href: `/settings/estimates`,
|
||||
access: EUserProjectRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/estimates`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "automations",
|
||||
label: "Automations",
|
||||
href: `/settings/automations`,
|
||||
access: EUserProjectRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/automations`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -6,6 +6,9 @@ import ExcelLogo from "public/services/excel.svg";
|
||||
import JSONLogo from "public/services/json.svg";
|
||||
// types
|
||||
import { TStaticViewTypes } from "@plane/types";
|
||||
import { Props } from "components/icons/types";
|
||||
// icons
|
||||
import { SettingIcon } from "components/icons";
|
||||
|
||||
export enum EUserWorkspaceRoles {
|
||||
GUEST = 5,
|
||||
@@ -115,48 +118,75 @@ export const RESTRICTED_URLS = [
|
||||
];
|
||||
|
||||
export const WORKSPACE_SETTINGS_LINKS: {
|
||||
key: string;
|
||||
label: string;
|
||||
href: string;
|
||||
access: EUserWorkspaceRoles;
|
||||
highlight: (pathname: string, baseUrl: string) => boolean;
|
||||
Icon: React.FC<Props>;
|
||||
}[] = [
|
||||
{
|
||||
key: "general",
|
||||
label: "General",
|
||||
href: `/settings`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "members",
|
||||
label: "Members",
|
||||
href: `/settings/members`,
|
||||
access: EUserWorkspaceRoles.GUEST,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/members`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "billing-and-plans",
|
||||
label: "Billing and plans",
|
||||
href: `/settings/billing`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/billing`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "integrations",
|
||||
label: "Integrations",
|
||||
href: `/settings/integrations`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/integrations`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "import",
|
||||
label: "Imports",
|
||||
href: `/settings/imports`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/imports`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "export",
|
||||
label: "Exports",
|
||||
href: `/settings/exports`,
|
||||
access: EUserWorkspaceRoles.MEMBER,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/exports`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "webhooks",
|
||||
label: "Webhooks",
|
||||
href: `/settings/webhooks`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/webhooks`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
{
|
||||
key: "api-tokens",
|
||||
label: "API tokens",
|
||||
href: `/settings/api-tokens`,
|
||||
access: EUserWorkspaceRoles.ADMIN,
|
||||
highlight: (pathname: string, baseUrl: string) => pathname === `${baseUrl}/settings/api-tokens`,
|
||||
Icon: SettingIcon,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -4,39 +4,14 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { useTheme } from "next-themes";
|
||||
import { Activity, ChevronLeft, CircleUser, KeyRound, LogOut, MoveLeft, Plus, Settings2, UserPlus } from "lucide-react";
|
||||
import { ChevronLeft, LogOut, MoveLeft, Plus, UserPlus } from "lucide-react";
|
||||
// hooks
|
||||
import { useApplication, useUser, useWorkspace } from "hooks/store";
|
||||
import useToast from "hooks/use-toast";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
|
||||
const PROFILE_ACTION_LINKS = [
|
||||
{
|
||||
key: "profile",
|
||||
label: "Profile",
|
||||
href: `/profile`,
|
||||
Icon: CircleUser,
|
||||
},
|
||||
{
|
||||
key: "change-password",
|
||||
label: "Change password",
|
||||
href: `/profile/change-password`,
|
||||
Icon: KeyRound,
|
||||
},
|
||||
{
|
||||
key: "activity",
|
||||
label: "Activity",
|
||||
href: `/profile/activity`,
|
||||
Icon: Activity,
|
||||
},
|
||||
{
|
||||
key: "preferences",
|
||||
label: "Preferences",
|
||||
href: `/profile/preferences`,
|
||||
Icon: Settings2,
|
||||
},
|
||||
];
|
||||
// constants
|
||||
import { PROFILE_ACTION_LINKS } from "constants/profile";
|
||||
|
||||
const WORKSPACE_ACTION_LINKS = [
|
||||
{
|
||||
@@ -130,7 +105,7 @@ export const ProfileLayoutSidebar = observer(() => {
|
||||
<Tooltip tooltipContent={link.label} position="right" className="ml-2" disabled={!sidebarCollapsed}>
|
||||
<div
|
||||
className={`group flex w-full items-center gap-2.5 rounded-md px-3 py-2 text-sm font-medium outline-none ${
|
||||
router.pathname === link.href
|
||||
link.highlight(router.pathname)
|
||||
? "bg-custom-primary-100/10 text-custom-primary-100"
|
||||
: "text-custom-sidebar-text-200 hover:bg-custom-sidebar-background-80"
|
||||
} ${sidebarCollapsed ? "justify-center" : ""}`}
|
||||
|
||||
@@ -1,66 +1,42 @@
|
||||
import React from "react";
|
||||
import { useRouter } from "next/router";
|
||||
import Link from "next/link";
|
||||
// hooks
|
||||
import { useUser } from "hooks/store";
|
||||
// constants
|
||||
import { EUserProjectRoles, PROJECT_SETTINGS_LINKS } from "constants/project";
|
||||
|
||||
export const ProjectSettingsSidebar = () => {
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId } = router.query;
|
||||
// mobx store
|
||||
const {
|
||||
membership: { currentProjectRole },
|
||||
} = useUser();
|
||||
|
||||
const projectMemberInfo = currentProjectRole || EUserProjectRoles.GUEST;
|
||||
|
||||
const projectLinks: Array<{
|
||||
label: string;
|
||||
href: string;
|
||||
}> = [
|
||||
{
|
||||
label: "General",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings`,
|
||||
},
|
||||
{
|
||||
label: "Members",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings/members`,
|
||||
},
|
||||
{
|
||||
label: "Features",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings/features`,
|
||||
},
|
||||
{
|
||||
label: "States",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings/states`,
|
||||
},
|
||||
{
|
||||
label: "Labels",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings/labels`,
|
||||
},
|
||||
{
|
||||
label: "Integrations",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings/integrations`,
|
||||
},
|
||||
{
|
||||
label: "Estimates",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings/estimates`,
|
||||
},
|
||||
{
|
||||
label: "Automations",
|
||||
href: `/${workspaceSlug}/projects/${projectId}/settings/automations`,
|
||||
},
|
||||
];
|
||||
return (
|
||||
<div className="flex w-80 flex-col gap-6 px-5">
|
||||
<div className="flex flex-col gap-2">
|
||||
<span className="text-xs font-semibold text-custom-sidebar-text-400">SETTINGS</span>
|
||||
<div className="flex w-full flex-col gap-1">
|
||||
{projectLinks.map((link) => (
|
||||
<Link key={link.href} href={link.href}>
|
||||
<div
|
||||
className={`rounded-md px-4 py-2 text-sm font-medium ${
|
||||
(link.label === "Import" ? router.asPath.includes(link.href) : router.asPath === link.href)
|
||||
? "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"
|
||||
}`}
|
||||
>
|
||||
{link.label}
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
{PROJECT_SETTINGS_LINKS.map(
|
||||
(link) =>
|
||||
projectMemberInfo >= link.access && (
|
||||
<Link key={link.key} href={`/${workspaceSlug}/projects/${projectId}${link.href}`}>
|
||||
<div
|
||||
className={`rounded-md px-4 py-2 text-sm font-medium ${
|
||||
link.highlight(router.asPath, `/${workspaceSlug}/projects/${projectId}`)
|
||||
? "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"
|
||||
}`}
|
||||
>
|
||||
{link.label}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -25,11 +25,11 @@ export const WorkspaceSettingsSidebar = () => {
|
||||
{WORKSPACE_SETTINGS_LINKS.map(
|
||||
(link) =>
|
||||
workspaceMemberInfo >= link.access && (
|
||||
<Link key={link.href} href={`/${workspaceSlug}${link.href}`}>
|
||||
<Link key={link.key} href={`/${workspaceSlug}${link.href}`}>
|
||||
<span>
|
||||
<div
|
||||
className={`rounded-md px-4 py-2 text-sm font-medium ${
|
||||
router.pathname.split("/")?.[3] === link.href.split("/")?.[2]
|
||||
link.highlight(router.asPath, `/${workspaceSlug}`)
|
||||
? "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"
|
||||
}`}
|
||||
|
||||
Reference in New Issue
Block a user