Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WEB-738] chore: conditionally render projects in the dropdown #3952

Merged
merged 5 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
184 changes: 135 additions & 49 deletions web/components/command-palette/command-palette.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, FC } from "react";
import React, { useCallback, useEffect, FC, useMemo } from "react";
import { observer } from "mobx-react-lite";
import { useRouter } from "next/router";
import useSWR from "swr";
Expand All @@ -23,24 +23,29 @@ import { EIssuesStoreType } from "constants/issue";
import { copyTextToClipboard } from "helpers/string.helper";
import { useApplication, useEventTracker, useIssues, useUser } from "hooks/store";
import { IssueService } from "services/issue";
import { EUserProjectRoles } from "constants/project";
import { EUserWorkspaceRoles } from "constants/workspace";

// services
const issueService = new IssueService();

export const CommandPalette: FC = observer(() => {
// router
const router = useRouter();
const { workspaceSlug, projectId, issueId, cycleId, moduleId } = router.query;

// store hooks
const {
commandPalette,
theme: { toggleSidebar },
} = useApplication();
const { setTrackElement } = useEventTracker();
const { currentUser } = useUser();
const {
currentUser,
membership: { currentWorkspaceRole, currentProjectRole },
} = useUser();
const {
issues: { removeIssue },
} = useIssues(EIssuesStoreType.PROJECT);

const {
toggleCommandPaletteModal,
isCreateIssueModalOpen,
Expand Down Expand Up @@ -91,6 +96,105 @@ export const CommandPalette: FC = observer(() => {
});
}, [issueId]);

// auth
const canPerformProjectCreateActions = useCallback(
(showToast: boolean = true) => {
const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
if (!isAllowed && showToast)
setToast({
type: TOAST_TYPE.ERROR,
title: "You don't have permission to perform this action.",
});

return isAllowed;
},
[currentProjectRole]
);
const canPerformWorkspaceCreateActions = useCallback(
(showToast: boolean = true) => {
const isAllowed = !!currentWorkspaceRole && currentWorkspaceRole >= EUserWorkspaceRoles.MEMBER;
console.log("currentWorkspaceRole", currentWorkspaceRole);
console.log("isAllowed", isAllowed);
if (!isAllowed && showToast)
setToast({
type: TOAST_TYPE.ERROR,
title: "You don't have permission to perform this action.",
});
return isAllowed;
},
[currentWorkspaceRole]
);

const shortcutsList: {
global: Record<string, { title: string; description: string; action: () => void }>;
workspace: Record<string, { title: string; description: string; action: () => void }>;
project: Record<string, { title: string; description: string; action: () => void }>;
} = useMemo(
() => ({
global: {
c: {
title: "Create a new issue",
description: "Create a new issue in the current project",
action: () => toggleCreateIssueModal(true),
},
h: {
title: "Show shortcuts",
description: "Show all the available shortcuts",
action: () => toggleShortcutModal(true),
},
},
workspace: {
p: {
title: "Create a new project",
description: "Create a new project in the current workspace",
action: () => toggleCreateProjectModal(true),
},
},
project: {
d: {
title: "Create a new page",
description: "Create a new page in the current project",
action: () => toggleCreatePageModal(true),
},
m: {
title: "Create a new module",
description: "Create a new module in the current project",
action: () => toggleCreateModuleModal(true),
},
q: {
title: "Create a new cycle",
description: "Create a new cycle in the current project",
action: () => toggleCreateCycleModal(true),
},
v: {
title: "Create a new view",
description: "Create a new view in the current project",
action: () => toggleCreateViewModal(true),
},
backspace: {
title: "Bulk delete issues",
description: "Bulk delete issues in the current project",
action: () => toggleBulkDeleteIssueModal(true),
},
delete: {
title: "Bulk delete issues",
description: "Bulk delete issues in the current project",
action: () => toggleBulkDeleteIssueModal(true),
},
},
}),
[
toggleBulkDeleteIssueModal,
toggleCreateCycleModal,
toggleCreateIssueModal,
toggleCreateModuleModal,
toggleCreatePageModal,
toggleCreateProjectModal,
toggleCreateViewModal,
toggleShortcutModal,
]
);

const handleKeyDown = useCallback(
(e: KeyboardEvent) => {
const { key, ctrlKey, metaKey, altKey } = e;
Expand All @@ -102,7 +206,7 @@ export const CommandPalette: FC = observer(() => {
if (
e.target instanceof HTMLTextAreaElement ||
e.target instanceof HTMLInputElement ||
(e.target as Element).classList?.contains("ProseMirror")
(e.target as Element)?.classList?.contains("ProseMirror")
)
return;

Expand All @@ -119,42 +223,35 @@ export const CommandPalette: FC = observer(() => {
}
} else if (!isAnyModalOpen) {
setTrackElement("Shortcut key");
if (keyPressed === "c") {
toggleCreateIssueModal(true);
} else if (keyPressed === "p") {
toggleCreateProjectModal(true);
} else if (keyPressed === "h") {
toggleShortcutModal(true);
} else if (keyPressed === "v" && workspaceSlug && projectId) {
toggleCreateViewModal(true);
} else if (keyPressed === "d" && workspaceSlug && projectId) {
toggleCreatePageModal(true);
} else if (keyPressed === "q" && workspaceSlug && projectId) {
toggleCreateCycleModal(true);
} else if (keyPressed === "m" && workspaceSlug && projectId) {
toggleCreateModuleModal(true);
} else if (keyPressed === "backspace" || keyPressed === "delete") {
e.preventDefault();
toggleBulkDeleteIssueModal(true);
aaryan610 marked this conversation as resolved.
Show resolved Hide resolved
}
if (Object.keys(shortcutsList.global).includes(keyPressed)) shortcutsList.global[keyPressed].action();
// workspace authorized actions
else if (
Object.keys(shortcutsList.workspace).includes(keyPressed) &&
workspaceSlug &&
canPerformWorkspaceCreateActions()
)
shortcutsList.workspace[keyPressed].action();
// project authorized actions
else if (
Object.keys(shortcutsList.project).includes(keyPressed) &&
projectId &&
canPerformProjectCreateActions()
)
// actions that can be performed only inside a project
shortcutsList.project[keyPressed].action();
}
},
[
canPerformProjectCreateActions,
canPerformWorkspaceCreateActions,
copyIssueUrlToClipboard,
toggleCreateProjectModal,
toggleCreateViewModal,
toggleCreatePageModal,
toggleShortcutModal,
toggleCreateCycleModal,
toggleCreateModuleModal,
toggleBulkDeleteIssueModal,
isAnyModalOpen,
projectId,
setTrackElement,
shortcutsList,
toggleCommandPaletteModal,
toggleSidebar,
toggleCreateIssueModal,
projectId,
workspaceSlug,
isAnyModalOpen,
setTrackElement,
]
);

Expand All @@ -169,18 +266,11 @@ export const CommandPalette: FC = observer(() => {

return (
<>
<ShortcutsModal
isOpen={isShortcutModalOpen}
onClose={() => {
toggleShortcutModal(false);
}}
/>
<ShortcutsModal isOpen={isShortcutModalOpen} onClose={() => toggleShortcutModal(false)} />
{workspaceSlug && (
<CreateProjectModal
isOpen={isCreateProjectModalOpen}
onClose={() => {
toggleCreateProjectModal(false);
}}
onClose={() => toggleCreateProjectModal(false)}
workspaceSlug={workspaceSlug.toString()}
/>
)}
Expand All @@ -194,9 +284,7 @@ export const CommandPalette: FC = observer(() => {
/>
<CreateUpdateModuleModal
isOpen={isCreateModuleModalOpen}
onClose={() => {
toggleCreateModuleModal(false);
}}
onClose={() => toggleCreateModuleModal(false)}
workspaceSlug={workspaceSlug.toString()}
projectId={projectId.toString()}
/>
Expand Down Expand Up @@ -236,9 +324,7 @@ export const CommandPalette: FC = observer(() => {

<BulkDeleteIssuesModal
isOpen={isBulkDeleteIssueModalOpen}
onClose={() => {
toggleBulkDeleteIssueModal(false);
}}
onClose={() => toggleBulkDeleteIssueModal(false)}
user={currentUser}
/>
<CommandModal />
Expand Down
4 changes: 4 additions & 0 deletions web/components/cycles/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { DateRangeDropdown, ProjectDropdown } from "components/dropdowns";
import { renderFormattedPayloadDate } from "helpers/date-time.helper";
// types
import { ICycle } from "@plane/types";
import { EUserProjectRoles } from "constants/project";

type Props = {
handleFormSubmit: (values: Partial<ICycle>, dirtyFields: any) => Promise<void>;
Expand Down Expand Up @@ -66,6 +67,9 @@ export const CycleForm: React.FC<Props> = (props) => {
setActiveProject(val);
}}
buttonVariant="background-with-text"
renderCondition={(project) =>
!!project.member_role && project.member_role >= EUserProjectRoles.MEMBER
}
tabIndex={7}
/>
)}
Expand Down
46 changes: 26 additions & 20 deletions web/components/dropdowns/project.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ProjectLogo } from "components/project";
// types
import { BUTTON_VARIANTS_WITH_TEXT } from "./constants";
import { TDropdownProps } from "./types";
import { IProject } from "@plane/types";
// constants

type Props = TDropdownProps & {
Expand All @@ -23,6 +24,7 @@ type Props = TDropdownProps & {
dropdownArrowClassName?: string;
onChange: (val: string) => void;
onClose?: () => void;
renderCondition?: (project: IProject) => boolean;
aaryan610 marked this conversation as resolved.
Show resolved Hide resolved
value: string | null;
};

Expand All @@ -41,6 +43,7 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
onClose,
placeholder = "Project",
placement,
renderCondition,
showTooltip = false,
tabIndex,
value,
Expand Down Expand Up @@ -71,7 +74,7 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {

const options = joinedProjectIds?.map((projectId) => {
const projectDetails = getProjectById(projectId);

if (renderCondition && projectDetails && !renderCondition(projectDetails)) return;
return {
value: projectId,
query: `${projectDetails?.name}`,
Expand All @@ -89,7 +92,7 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
});

const filteredOptions =
query === "" ? options : options?.filter((o) => o.query.toLowerCase().includes(query.toLowerCase()));
query === "" ? options : options?.filter((o) => o?.query.toLowerCase().includes(query.toLowerCase()));

const selectedProject = value ? getProjectById(value) : null;

Expand Down Expand Up @@ -205,24 +208,27 @@ export const ProjectDropdown: React.FC<Props> = observer((props) => {
<div className="mt-2 max-h-48 space-y-1 overflow-y-scroll">
{filteredOptions ? (
filteredOptions.length > 0 ? (
filteredOptions.map((option) => (
<Combobox.Option
key={option.value}
value={option.value}
className={({ active, selected }) =>
`w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${
active ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
>
{({ selected }) => (
<>
<span className="flex-grow truncate">{option.content}</span>
{selected && <Check className="h-3.5 w-3.5 flex-shrink-0" />}
</>
)}
</Combobox.Option>
))
filteredOptions.map((option) => {
if (!option) return;
return (
<Combobox.Option
key={option.value}
value={option.value}
className={({ active, selected }) =>
`w-full truncate flex items-center justify-between gap-2 rounded px-1 py-1.5 cursor-pointer select-none ${
active ? "bg-custom-background-80" : ""
} ${selected ? "text-custom-text-100" : "text-custom-text-200"}`
}
>
{({ selected }) => (
<>
<span className="flex-grow truncate">{option.content}</span>
{selected && <Check className="h-3.5 w-3.5 flex-shrink-0" />}
</>
)}
</Combobox.Option>
);
})
) : (
<p className="text-custom-text-400 italic py-1 px-1.5">No matching results</p>
)
Expand Down
5 changes: 4 additions & 1 deletion web/components/issues/issue-modal/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { FileService } from "services/file.service";
import { getChangedIssuefields } from "helpers/issue.helper";
// types
import type { TIssue, ISearchIssueResponse } from "@plane/types";
import { EUserProjectRoles } from "constants/project";

const defaultValues: Partial<TIssue> = {
project_id: "",
Expand Down Expand Up @@ -304,7 +305,9 @@ export const IssueFormRoot: FC<IssueFormProps> = observer((props) => {
handleFormChange();
}}
buttonVariant="border-with-text"
// TODO: update tabIndex logic
renderCondition={(project) =>
!!project.member_role && project.member_role >= EUserProjectRoles.MEMBER
aaryan610 marked this conversation as resolved.
Show resolved Hide resolved
}
tabIndex={getTabIndex("project_id")}
/>
</div>
Expand Down
Loading
Loading