From 7aaf840fb11064b912fd044f5a3358e5b4dae91e Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:46:41 +0530 Subject: [PATCH 1/6] refactor: command k modal (#2803) * refactor: command palette file structure * fix: identifier search --- .../command-palette/actions/help-actions.tsx | 83 +++ .../command-palette/actions/index.ts | 6 + .../actions/issue-actions/actions-list.tsx | 166 ++++++ .../actions/issue-actions/change-assignee.tsx | 79 +++ .../actions/issue-actions/change-priority.tsx | 56 ++ .../actions/issue-actions/change-state.tsx | 65 +++ .../actions/issue-actions/index.ts | 4 + .../actions/project-actions.tsx | 83 +++ .../actions/search-results.tsx | 49 ++ .../theme-actions.tsx} | 21 +- .../actions/workspace-settings-actions.tsx | 61 +++ .../command-palette/command-modal.tsx | 499 +++--------------- .../command-palette/command-pallette.tsx | 14 +- web/components/command-palette/helpers.tsx | 21 +- web/components/command-palette/index.ts | 3 +- .../issue/change-issue-assignee.tsx | 111 ---- .../issue/change-issue-priority.tsx | 78 --- .../issue/change-issue-state.tsx | 93 ---- web/components/command-palette/issue/index.ts | 3 - 19 files changed, 741 insertions(+), 754 deletions(-) create mode 100644 web/components/command-palette/actions/help-actions.tsx create mode 100644 web/components/command-palette/actions/index.ts create mode 100644 web/components/command-palette/actions/issue-actions/actions-list.tsx create mode 100644 web/components/command-palette/actions/issue-actions/change-assignee.tsx create mode 100644 web/components/command-palette/actions/issue-actions/change-priority.tsx create mode 100644 web/components/command-palette/actions/issue-actions/change-state.tsx create mode 100644 web/components/command-palette/actions/issue-actions/index.ts create mode 100644 web/components/command-palette/actions/project-actions.tsx create mode 100644 web/components/command-palette/actions/search-results.tsx rename web/components/command-palette/{change-interface-theme.tsx => actions/theme-actions.tsx} (74%) create mode 100644 web/components/command-palette/actions/workspace-settings-actions.tsx delete mode 100644 web/components/command-palette/issue/change-issue-assignee.tsx delete mode 100644 web/components/command-palette/issue/change-issue-priority.tsx delete mode 100644 web/components/command-palette/issue/change-issue-state.tsx delete mode 100644 web/components/command-palette/issue/index.ts diff --git a/web/components/command-palette/actions/help-actions.tsx b/web/components/command-palette/actions/help-actions.tsx new file mode 100644 index 000000000..859a6d23a --- /dev/null +++ b/web/components/command-palette/actions/help-actions.tsx @@ -0,0 +1,83 @@ +import { Command } from "cmdk"; +import { FileText, GithubIcon, MessageSquare, Rocket } from "lucide-react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// ui +import { DiscordIcon } from "@plane/ui"; + +type Props = { + closePalette: () => void; +}; + +export const CommandPaletteHelpActions: React.FC = (props) => { + const { closePalette } = props; + + const { + commandPalette: { toggleShortcutModal }, + } = useMobxStore(); + + return ( + + { + closePalette(); + toggleShortcutModal(true); + }} + className="focus:outline-none" + > +
+ + Open keyboard shortcuts +
+
+ { + closePalette(); + window.open("https://docs.plane.so/", "_blank"); + }} + className="focus:outline-none" + > +
+ + Open Plane documentation +
+
+ { + closePalette(); + window.open("https://discord.com/invite/A92xrEGCge", "_blank"); + }} + className="focus:outline-none" + > +
+ + Join our Discord +
+
+ { + closePalette(); + window.open("https://github.com/makeplane/plane/issues/new/choose", "_blank"); + }} + className="focus:outline-none" + > +
+ + Report a bug +
+
+ { + closePalette(); + (window as any)?.$crisp.push(["do", "chat:open"]); + }} + className="focus:outline-none" + > +
+ + Chat with us +
+
+
+ ); +}; diff --git a/web/components/command-palette/actions/index.ts b/web/components/command-palette/actions/index.ts new file mode 100644 index 000000000..7c3af470e --- /dev/null +++ b/web/components/command-palette/actions/index.ts @@ -0,0 +1,6 @@ +export * from "./issue-actions"; +export * from "./help-actions"; +export * from "./project-actions"; +export * from "./search-results"; +export * from "./theme-actions"; +export * from "./workspace-settings-actions"; diff --git a/web/components/command-palette/actions/issue-actions/actions-list.tsx b/web/components/command-palette/actions/issue-actions/actions-list.tsx new file mode 100644 index 000000000..73a021cf8 --- /dev/null +++ b/web/components/command-palette/actions/issue-actions/actions-list.tsx @@ -0,0 +1,166 @@ +import { useRouter } from "next/router"; +import { observer } from "mobx-react-lite"; +import { Command } from "cmdk"; +import { LinkIcon, Signal, Trash2, UserMinus2, UserPlus2 } from "lucide-react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// hooks +import useToast from "hooks/use-toast"; +// ui +import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui"; +// helpers +import { copyTextToClipboard } from "helpers/string.helper"; +// types +import { IIssue } from "types"; + +type Props = { + closePalette: () => void; + issueDetails: IIssue | undefined; + pages: string[]; + setPages: (pages: string[]) => void; + setPlaceholder: (placeholder: string) => void; + setSearchTerm: (searchTerm: string) => void; +}; + +export const CommandPaletteIssueActions: React.FC = observer((props) => { + const { closePalette, issueDetails, pages, setPages, setPlaceholder, setSearchTerm } = props; + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { + commandPalette: { toggleCommandPaletteModal, toggleDeleteIssueModal }, + issueDetail: { updateIssue }, + user: { currentUser }, + } = useMobxStore(); + + const { setToastAlert } = useToast(); + + const handleUpdateIssue = async (formData: Partial) => { + if (!workspaceSlug || !projectId || !issueDetails) return; + + const payload = { ...formData }; + await updateIssue(workspaceSlug.toString(), projectId.toString(), issueDetails.id, payload).catch((e) => { + console.error(e); + }); + }; + + const handleIssueAssignees = (assignee: string) => { + if (!issueDetails || !assignee) return; + + closePalette(); + const updatedAssignees = issueDetails.assignees ?? []; + + if (updatedAssignees.includes(assignee)) updatedAssignees.splice(updatedAssignees.indexOf(assignee), 1); + else updatedAssignees.push(assignee); + + handleUpdateIssue({ assignees: updatedAssignees }); + }; + + const deleteIssue = () => { + toggleCommandPaletteModal(false); + toggleDeleteIssueModal(true); + }; + + const copyIssueUrlToClipboard = () => { + if (!router.query.issueId) return; + + const url = new URL(window.location.href); + copyTextToClipboard(url.href) + .then(() => { + setToastAlert({ + type: "success", + title: "Copied to clipboard", + }); + }) + .catch(() => { + setToastAlert({ + type: "error", + title: "Some error occurred", + }); + }); + }; + + return ( + + { + setPlaceholder("Change state..."); + setSearchTerm(""); + setPages([...pages, "change-issue-state"]); + }} + className="focus:outline-none" + > +
+ + Change state... +
+
+ { + setPlaceholder("Change priority..."); + setSearchTerm(""); + setPages([...pages, "change-issue-priority"]); + }} + className="focus:outline-none" + > +
+ + Change priority... +
+
+ { + setPlaceholder("Assign to..."); + setSearchTerm(""); + setPages([...pages, "change-issue-assignee"]); + }} + className="focus:outline-none" + > +
+ + Assign to... +
+
+ { + handleIssueAssignees(currentUser?.id ?? ""); + setSearchTerm(""); + }} + className="focus:outline-none" + > +
+ {issueDetails?.assignees.includes(currentUser?.id ?? "") ? ( + <> + + Un-assign from me + + ) : ( + <> + + Assign to me + + )} +
+
+ +
+ + Delete issue +
+
+ { + closePalette(); + copyIssueUrlToClipboard(); + }} + className="focus:outline-none" + > +
+ + Copy issue URL +
+
+
+ ); +}); diff --git a/web/components/command-palette/actions/issue-actions/change-assignee.tsx b/web/components/command-palette/actions/issue-actions/change-assignee.tsx new file mode 100644 index 000000000..4b7ee6b99 --- /dev/null +++ b/web/components/command-palette/actions/issue-actions/change-assignee.tsx @@ -0,0 +1,79 @@ +import { useRouter } from "next/router"; +import { observer } from "mobx-react-lite"; +import { Command } from "cmdk"; +import { Check } from "lucide-react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// ui +import { Avatar } from "@plane/ui"; +// types +import { IIssue } from "types"; + +type Props = { + closePalette: () => void; + issue: IIssue; +}; + +export const ChangeIssueAssignee: React.FC = observer((props) => { + const { closePalette, issue } = props; + // router + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + // store + const { + issueDetail: { updateIssue }, + projectMember: { projectMembers }, + } = useMobxStore(); + + const options = + projectMembers?.map(({ member }) => ({ + value: member.id, + query: member.display_name, + content: ( + <> +
+ + {member.display_name} +
+ {issue.assignees.includes(member.id) && ( +
+ +
+ )} + + ), + })) ?? []; + + const handleUpdateIssue = async (formData: Partial) => { + if (!workspaceSlug || !projectId || !issue) return; + + const payload = { ...formData }; + await updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, payload).catch((e) => { + console.error(e); + }); + }; + + const handleIssueAssignees = (assignee: string) => { + const updatedAssignees = issue.assignees ?? []; + + if (updatedAssignees.includes(assignee)) updatedAssignees.splice(updatedAssignees.indexOf(assignee), 1); + else updatedAssignees.push(assignee); + + handleUpdateIssue({ assignees: updatedAssignees }); + closePalette(); + }; + + return ( + <> + {options.map((option: any) => ( + handleIssueAssignees(option.value)} + className="focus:outline-none" + > + {option.content} + + ))} + + ); +}); diff --git a/web/components/command-palette/actions/issue-actions/change-priority.tsx b/web/components/command-palette/actions/issue-actions/change-priority.tsx new file mode 100644 index 000000000..bda152c33 --- /dev/null +++ b/web/components/command-palette/actions/issue-actions/change-priority.tsx @@ -0,0 +1,56 @@ +import { useRouter } from "next/router"; +import { observer } from "mobx-react-lite"; +import { Command } from "cmdk"; +import { Check } from "lucide-react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// ui +import { PriorityIcon } from "@plane/ui"; +// types +import { IIssue, TIssuePriorities } from "types"; +// constants +import { PRIORITIES } from "constants/project"; + +type Props = { + closePalette: () => void; + issue: IIssue; +}; + +export const ChangeIssuePriority: React.FC = observer((props) => { + const { closePalette, issue } = props; + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { + issueDetail: { updateIssue }, + } = useMobxStore(); + + const submitChanges = async (formData: Partial) => { + if (!workspaceSlug || !projectId || !issue) return; + + const payload = { ...formData }; + await updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, payload).catch((e) => { + console.error(e); + }); + }; + + const handleIssueState = (priority: TIssuePriorities) => { + submitChanges({ priority }); + closePalette(); + }; + + return ( + <> + {PRIORITIES.map((priority) => ( + handleIssueState(priority)} className="focus:outline-none"> +
+ + {priority ?? "None"} +
+
{priority === issue.priority && }
+
+ ))} + + ); +}); diff --git a/web/components/command-palette/actions/issue-actions/change-state.tsx b/web/components/command-palette/actions/issue-actions/change-state.tsx new file mode 100644 index 000000000..5b147e499 --- /dev/null +++ b/web/components/command-palette/actions/issue-actions/change-state.tsx @@ -0,0 +1,65 @@ +import { useRouter } from "next/router"; +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// cmdk +import { Command } from "cmdk"; +// ui +import { Spinner, StateGroupIcon } from "@plane/ui"; +// icons +import { Check } from "lucide-react"; +// types +import { IIssue } from "types"; + +type Props = { + closePalette: () => void; + issue: IIssue; +}; + +export const ChangeIssueState: React.FC = observer((props) => { + const { closePalette, issue } = props; + + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + + const { + projectState: { projectStates }, + issueDetail: { updateIssue }, + } = useMobxStore(); + + const submitChanges = async (formData: Partial) => { + if (!workspaceSlug || !projectId || !issue) return; + + const payload = { ...formData }; + await updateIssue(workspaceSlug.toString(), projectId.toString(), issue.id, payload).catch((e) => { + console.error(e); + }); + }; + + const handleIssueState = (stateId: string) => { + submitChanges({ state: stateId }); + closePalette(); + }; + + return ( + <> + {projectStates ? ( + projectStates.length > 0 ? ( + projectStates.map((state) => ( + handleIssueState(state.id)} className="focus:outline-none"> +
+ +

{state.name}

+
+
{state.id === issue.state && }
+
+ )) + ) : ( +
No states found
+ ) + ) : ( + + )} + + ); +}); diff --git a/web/components/command-palette/actions/issue-actions/index.ts b/web/components/command-palette/actions/issue-actions/index.ts new file mode 100644 index 000000000..305107d60 --- /dev/null +++ b/web/components/command-palette/actions/issue-actions/index.ts @@ -0,0 +1,4 @@ +export * from "./actions-list"; +export * from "./change-state"; +export * from "./change-priority"; +export * from "./change-assignee"; diff --git a/web/components/command-palette/actions/project-actions.tsx b/web/components/command-palette/actions/project-actions.tsx new file mode 100644 index 000000000..5db9b2c4e --- /dev/null +++ b/web/components/command-palette/actions/project-actions.tsx @@ -0,0 +1,83 @@ +import { Command } from "cmdk"; +import { ContrastIcon, FileText } from "lucide-react"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +// ui +import { DiceIcon, PhotoFilterIcon } from "@plane/ui"; + +type Props = { + closePalette: () => void; +}; + +export const CommandPaletteProjectActions: React.FC = (props) => { + const { closePalette } = props; + + const { + commandPalette: { toggleCreateCycleModal, toggleCreateModuleModal, toggleCreatePageModal, toggleCreateViewModal }, + } = useMobxStore(); + + return ( + <> + + { + closePalette(); + toggleCreateCycleModal(true); + }} + className="focus:outline-none" + > +
+ + Create new cycle +
+ Q +
+
+ + { + closePalette(); + toggleCreateModuleModal(true); + }} + className="focus:outline-none" + > +
+ + Create new module +
+ M +
+
+ + { + closePalette(); + toggleCreateViewModal(true); + }} + className="focus:outline-none" + > +
+ + Create new view +
+ V +
+
+ + { + closePalette(); + toggleCreatePageModal(true); + }} + className="focus:outline-none" + > +
+ + Create new page +
+ D +
+
+ + ); +}; diff --git a/web/components/command-palette/actions/search-results.tsx b/web/components/command-palette/actions/search-results.tsx new file mode 100644 index 000000000..791c62656 --- /dev/null +++ b/web/components/command-palette/actions/search-results.tsx @@ -0,0 +1,49 @@ +import { useRouter } from "next/router"; +import { Command } from "cmdk"; +// helpers +import { commandGroups } from "components/command-palette"; +// types +import { IWorkspaceSearchResults } from "types"; + +type Props = { + closePalette: () => void; + results: IWorkspaceSearchResults; +}; + +export const CommandPaletteSearchResults: React.FC = (props) => { + const { closePalette, results } = props; + + const router = useRouter(); + + return ( + <> + {Object.keys(results.results).map((key) => { + const section = (results.results as any)[key]; + const currentSection = commandGroups[key]; + + if (section.length > 0) { + return ( + + {section.map((item: any) => ( + { + closePalette(); + router.push(currentSection.path(item)); + }} + value={`${key}-${item?.id}-${item.name}-${item.project__identifier ?? ""}-${item.sequence_id ?? ""}`} + className="focus:outline-none" + > +
+ {currentSection.icon} +

{currentSection.itemName(item)}

+
+
+ ))} +
+ ); + } + })} + + ); +}; diff --git a/web/components/command-palette/change-interface-theme.tsx b/web/components/command-palette/actions/theme-actions.tsx similarity index 74% rename from web/components/command-palette/change-interface-theme.tsx rename to web/components/command-palette/actions/theme-actions.tsx index 0b899f811..f7266a48a 100644 --- a/web/components/command-palette/change-interface-theme.tsx +++ b/web/components/command-palette/actions/theme-actions.tsx @@ -1,4 +1,4 @@ -import React, { FC, Dispatch, SetStateAction, useEffect, useState } from "react"; +import React, { FC, useEffect, useState } from "react"; import { Command } from "cmdk"; import { useTheme } from "next-themes"; import { Settings } from "lucide-react"; @@ -10,22 +10,25 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { THEME_OPTIONS } from "constants/themes"; type Props = { - setIsPaletteOpen: Dispatch>; + closePalette: () => void; }; -export const ChangeInterfaceTheme: FC = observer((props) => { - const { setIsPaletteOpen } = props; - // store - const { user: userStore } = useMobxStore(); +export const CommandPaletteThemeActions: FC = observer((props) => { + const { closePalette } = props; // states const [mounted, setMounted] = useState(false); + // store + const { + user: { updateCurrentUserTheme }, + } = useMobxStore(); // hooks const { setTheme } = useTheme(); const { setToastAlert } = useToast(); - const updateUserTheme = (newTheme: string) => { + const updateUserTheme = async (newTheme: string) => { setTheme(newTheme); - return userStore.updateCurrentUserTheme(newTheme).catch(() => { + + return updateCurrentUserTheme(newTheme).catch(() => { setToastAlert({ title: "Failed to save user theme settings!", type: "error", @@ -47,7 +50,7 @@ export const ChangeInterfaceTheme: FC = observer((props) => { key={theme.value} onSelect={() => { updateUserTheme(theme.value); - setIsPaletteOpen(false); + closePalette(); }} className="focus:outline-none" > diff --git a/web/components/command-palette/actions/workspace-settings-actions.tsx b/web/components/command-palette/actions/workspace-settings-actions.tsx new file mode 100644 index 000000000..84e62593a --- /dev/null +++ b/web/components/command-palette/actions/workspace-settings-actions.tsx @@ -0,0 +1,61 @@ +import { useRouter } from "next/router"; +import { Command } from "cmdk"; +// icons +import { SettingIcon } from "components/icons"; + +type Props = { + closePalette: () => void; +}; + +export const CommandPaletteWorkspaceSettingsActions: React.FC = (props) => { + const { closePalette } = props; + + const router = useRouter(); + const { workspaceSlug } = router.query; + + const redirect = (path: string) => { + closePalette(); + router.push(path); + }; + + return ( + <> + redirect(`/${workspaceSlug}/settings`)} className="focus:outline-none"> +
+ + General +
+
+ redirect(`/${workspaceSlug}/settings/members`)} className="focus:outline-none"> +
+ + Members +
+
+ redirect(`/${workspaceSlug}/settings/billing`)} className="focus:outline-none"> +
+ + Billing and Plans +
+
+ redirect(`/${workspaceSlug}/settings/integrations`)} className="focus:outline-none"> +
+ + Integrations +
+
+ redirect(`/${workspaceSlug}/settings/imports`)} className="focus:outline-none"> +
+ + Import +
+
+ redirect(`/${workspaceSlug}/settings/exports`)} className="focus:outline-none"> +
+ + Export +
+
+ + ); +}; diff --git a/web/components/command-palette/command-modal.tsx b/web/components/command-palette/command-modal.tsx index bd066326d..f43032cf2 100644 --- a/web/components/command-palette/command-modal.tsx +++ b/web/components/command-palette/command-modal.tsx @@ -1,22 +1,10 @@ -import React, { useCallback, useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useRouter } from "next/router"; -import useSWR, { mutate } from "swr"; +import useSWR from "swr"; import { Command } from "cmdk"; import { Dialog, Transition } from "@headlessui/react"; import { observer } from "mobx-react-lite"; -import { - FileText, - FolderPlus, - LinkIcon, - MessageSquare, - Rocket, - Search, - Settings, - Signal, - Trash2, - UserMinus2, - UserPlus2, -} from "lucide-react"; +import { FolderPlus, Search, Settings } from "lucide-react"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; // services @@ -24,47 +12,29 @@ import { WorkspaceService } from "services/workspace.service"; import { IssueService } from "services/issue"; // hooks import useDebounce from "hooks/use-debounce"; -import useToast from "hooks/use-toast"; // components import { - ChangeInterfaceTheme, + CommandPaletteThemeActions, ChangeIssueAssignee, ChangeIssuePriority, ChangeIssueState, - commandGroups, + CommandPaletteHelpActions, + CommandPaletteIssueActions, + CommandPaletteProjectActions, + CommandPaletteWorkspaceSettingsActions, + CommandPaletteSearchResults, } from "components/command-palette"; -import { - ContrastIcon, - DiceIcon, - DoubleCircleIcon, - LayersIcon, - Loader, - PhotoFilterIcon, - ToggleSwitch, - Tooltip, - UserGroupIcon, -} from "@plane/ui"; -// icons -import { DiscordIcon, GithubIcon, SettingIcon } from "components/icons"; -// helpers -import { copyTextToClipboard } from "helpers/string.helper"; +import { LayersIcon, Loader, ToggleSwitch, Tooltip } from "@plane/ui"; // types -import { IIssue, IWorkspaceSearchResults } from "types"; +import { IWorkspaceSearchResults } from "types"; // fetch-keys -import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; - -type Props = { - deleteIssue: () => void; - isPaletteOpen: boolean; - closePalette: () => void; -}; +import { ISSUE_DETAILS } from "constants/fetch-keys"; // services const workspaceService = new WorkspaceService(); const issueService = new IssueService(); -export const CommandModal: React.FC = observer((props) => { - const { deleteIssue, isPaletteOpen, closePalette } = props; +export const CommandModal: React.FC = observer(() => { // states const [placeholder, setPlaceholder] = useState("Type a command or search..."); const [resultsCount, setResultsCount] = useState(0); @@ -85,8 +55,14 @@ export const CommandModal: React.FC = observer((props) => { const [isWorkspaceLevel, setIsWorkspaceLevel] = useState(false); const [pages, setPages] = useState([]); - const { user: userStore, commandPalette: commandPaletteStore } = useMobxStore(); - const user = userStore.currentUser ?? undefined; + const { + commandPalette: { + isCommandPaletteOpen, + toggleCommandPaletteModal, + toggleCreateIssueModal, + toggleCreateProjectModal, + }, + } = useMobxStore(); // router const router = useRouter(); @@ -96,64 +72,16 @@ export const CommandModal: React.FC = observer((props) => { const debouncedSearchTerm = useDebounce(searchTerm, 500); - const { setToastAlert } = useToast(); - + // TODO: update this to mobx store const { data: issueDetails } = useSWR( - workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId as string) : null, + workspaceSlug && projectId && issueId ? ISSUE_DETAILS(issueId.toString()) : null, workspaceSlug && projectId && issueId - ? () => issueService.retrieve(workspaceSlug as string, projectId as string, issueId as string) + ? () => issueService.retrieve(workspaceSlug.toString(), projectId.toString(), issueId.toString()) : null ); - const updateIssue = useCallback( - async (formData: Partial) => { - if (!workspaceSlug || !projectId || !issueId) return; - - mutate( - ISSUE_DETAILS(issueId as string), - - (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - ...formData, - }; - }, - false - ); - - const payload = { ...formData }; - await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) - .then(() => { - mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); - mutate(ISSUE_DETAILS(issueId as string)); - }) - .catch((e) => { - console.error(e); - }); - }, - [workspaceSlug, issueId, projectId] - ); - - const handleIssueAssignees = (assignee: string) => { - if (!issueDetails) return; - - closePalette(); - const updatedAssignees = issueDetails.assignees ?? []; - - if (updatedAssignees.includes(assignee)) { - updatedAssignees.splice(updatedAssignees.indexOf(assignee), 1); - } else { - updatedAssignees.push(assignee); - } - updateIssue({ assignees: updatedAssignees }); - }; - - const redirect = (path: string) => { - closePalette(); - router.push(path); + const closePalette = () => { + toggleCommandPaletteModal(false); }; const createNewWorkspace = () => { @@ -161,25 +89,6 @@ export const CommandModal: React.FC = observer((props) => { router.push("/create-workspace"); }; - const copyIssueUrlToClipboard = useCallback(() => { - if (!router.query.issueId) return; - - const url = new URL(window.location.href); - copyTextToClipboard(url.href) - .then(() => { - setToastAlert({ - type: "success", - title: "Copied to clipboard", - }); - }) - .catch(() => { - setToastAlert({ - type: "error", - title: "Some error occurred", - }); - }); - }, [router, setToastAlert]); - useEffect( () => { if (!workspaceSlug) return; @@ -189,7 +98,7 @@ export const CommandModal: React.FC = observer((props) => { if (debouncedSearchTerm) { setIsSearching(true); workspaceService - .searchWorkspace(workspaceSlug as string, { + .searchWorkspace(workspaceSlug.toString(), { ...(projectId ? { project_id: projectId.toString() } : {}), search: debouncedSearchTerm, workspace_search: !projectId ? true : isWorkspaceLevel, @@ -225,16 +134,8 @@ export const CommandModal: React.FC = observer((props) => { [debouncedSearchTerm, isWorkspaceLevel, projectId, workspaceSlug] // Only call effect if debounced search term changes ); - if (!user) return null; - return ( - { - setSearchTerm(""); - }} - as={React.Fragment} - > + setSearchTerm("")} as={React.Fragment}> closePalette()}> = observer((props) => { onKeyDown={(e) => { // when search is empty and page is undefined // when user tries to close the modal with esc - if (e.key === "Escape" && !page && !searchTerm) { - closePalette(); - } + if (e.key === "Escape" && !page && !searchTerm) closePalette(); + // Escape goes to previous page // Backspace goes to previous page when search is empty if (e.key === "Escape" || (e.key === "Backspace" && !searchTerm)) { @@ -318,9 +218,7 @@ export const CommandModal: React.FC = observer((props) => { className="w-full border-0 border-b border-custom-border-200 bg-transparent p-4 pl-11 text-custom-text-100 placeholder:text-custom-text-400 outline-none focus:ring-0 text-sm" placeholder={placeholder} value={searchTerm} - onValueChange={(e) => { - setSearchTerm(e); - }} + onValueChange={(e) => setSearchTerm(e)} autoFocus tabIndex={1} /> @@ -340,7 +238,7 @@ export const CommandModal: React.FC = observer((props) => { )} {!isLoading && resultsCount === 0 && searchTerm !== "" && debouncedSearchTerm !== "" && ( -
No results found.
+
No results found.
)} {(isLoading || isSearching) && ( @@ -354,125 +252,28 @@ export const CommandModal: React.FC = observer((props) => { )} - {debouncedSearchTerm !== "" && - Object.keys(results.results).map((key) => { - const section = (results.results as any)[key]; - const currentSection = commandGroups[key]; - - if (section.length > 0) { - return ( - - {section.map((item: any) => ( - { - closePalette(); - router.push(currentSection.path(item)); - }} - value={`${key}-${item?.name}`} - className="focus:outline-none" - > -
- {currentSection.icon} -

{currentSection.itemName(item)}

-
-
- ))} -
- ); - } - })} + {debouncedSearchTerm !== "" && ( + + )} {!page && ( <> + {/* issue actions */} {issueId && ( - - { - closePalette(); - setPlaceholder("Change state..."); - setSearchTerm(""); - setPages([...pages, "change-issue-state"]); - }} - className="focus:outline-none" - > -
- - Change state... -
-
- { - setPlaceholder("Change priority..."); - setSearchTerm(""); - setPages([...pages, "change-issue-priority"]); - }} - className="focus:outline-none" - > -
- - Change priority... -
-
- { - setPlaceholder("Assign to..."); - setSearchTerm(""); - setPages([...pages, "change-issue-assignee"]); - }} - className="focus:outline-none" - > -
- - Assign to... -
-
- { - handleIssueAssignees(user.id); - setSearchTerm(""); - }} - className="focus:outline-none" - > -
- {issueDetails?.assignees.includes(user.id) ? ( - <> - - Un-assign from me - - ) : ( - <> - - Assign to me - - )} -
-
- -
- - Delete issue -
-
- { - closePalette(); - copyIssueUrlToClipboard(); - }} - className="focus:outline-none" - > -
- - Copy issue URL -
-
-
+ setPages(newPages)} + setPlaceholder={(newPlaceholder) => setPlaceholder(newPlaceholder)} + setSearchTerm={(newSearchTerm) => setSearchTerm(newSearchTerm)} + /> )} { closePalette(); - commandPaletteStore.toggleCreateIssueModal(true); + toggleCreateIssueModal(true); }} className="focus:bg-custom-background-80" > @@ -489,7 +290,7 @@ export const CommandModal: React.FC = observer((props) => { { closePalette(); - commandPaletteStore.toggleCreateProjectModal(true); + toggleCreateProjectModal(true); }} className="focus:outline-none" > @@ -502,70 +303,8 @@ export const CommandModal: React.FC = observer((props) => { )} - {projectId && ( - <> - - { - closePalette(); - commandPaletteStore.toggleCreateCycleModal(true); - }} - className="focus:outline-none" - > -
- - Create new cycle -
- Q -
-
- - { - closePalette(); - commandPaletteStore.toggleCreateModuleModal(true); - }} - className="focus:outline-none" - > -
- - Create new module -
- M -
-
- - { - closePalette(); - commandPaletteStore.toggleCreateViewModal(true); - }} - className="focus:outline-none" - > -
- - Create new view -
- V -
-
- - { - closePalette(); - commandPaletteStore.toggleCreatePageModal(true); - }} - className="focus:outline-none" - > -
- - Create new page -
- D -
-
- - )} + {/* project actions */} + {projectId && } = observer((props) => { - - { - closePalette(); - commandPaletteStore.toggleShortcutModal(true); - }} - className="focus:outline-none" - > -
- - Open keyboard shortcuts -
-
- { - closePalette(); - window.open("https://docs.plane.so/", "_blank"); - }} - className="focus:outline-none" - > -
- - Open Plane documentation -
-
- { - closePalette(); - window.open("https://discord.com/invite/A92xrEGCge", "_blank"); - }} - className="focus:outline-none" - > -
- - Join our Discord -
-
- { - closePalette(); - window.open("https://github.com/makeplane/plane/issues/new/choose", "_blank"); - }} - className="focus:outline-none" - > -
- - Report a bug -
-
- { - closePalette(); - (window as any).$crisp.push(["do", "chat:open"]); - }} - className="focus:outline-none" - > -
- - Chat with us -
-
-
+ + {/* help options */} + )} + {/* workspace settings actions */} {page === "settings" && workspaceSlug && ( - <> - redirect(`/${workspaceSlug}/settings`)} - className="focus:outline-none" - > -
- - General -
-
- redirect(`/${workspaceSlug}/settings/members`)} - className="focus:outline-none" - > -
- - Members -
-
- redirect(`/${workspaceSlug}/settings/billing`)} - className="focus:outline-none" - > -
- - Billing and Plans -
-
- redirect(`/${workspaceSlug}/settings/integrations`)} - className="focus:outline-none" - > -
- - Integrations -
-
- redirect(`/${workspaceSlug}/settings/imports`)} - className="focus:outline-none" - > -
- - Import -
-
- redirect(`/${workspaceSlug}/settings/exports`)} - className="focus:outline-none" - > -
- - Export -
-
- + )} + + {/* issue details page actions */} {page === "change-issue-state" && issueDetails && ( - + )} {page === "change-issue-priority" && issueDetails && ( - + )} {page === "change-issue-assignee" && issueDetails && ( - + + )} + + {/* theme actions */} + {page === "change-interface-theme" && ( + { + closePalette(); + setPages((pages) => pages.slice(0, -1)); + }} + /> )} - {page === "change-interface-theme" && } diff --git a/web/components/command-palette/command-pallette.tsx b/web/components/command-palette/command-pallette.tsx index 0ca293475..7708ff926 100644 --- a/web/components/command-palette/command-pallette.tsx +++ b/web/components/command-palette/command-pallette.tsx @@ -32,7 +32,6 @@ export const CommandPalette: FC = observer(() => { // store const { commandPalette, theme: themeStore } = useMobxStore(); const { - isCommandPaletteOpen, toggleCommandPaletteModal, isCreateIssueModalOpen, toggleCreateIssueModal, @@ -156,11 +155,6 @@ export const CommandPalette: FC = observer(() => { if (!user) return null; - const deleteIssue = () => { - toggleCommandPaletteModal(false); - toggleDeleteIssueModal(true); - }; - return ( <> { }} user={user} /> - { - toggleCommandPaletteModal(false); - }} - /> + ); }); diff --git a/web/components/command-palette/helpers.tsx b/web/components/command-palette/helpers.tsx index 90511f907..cfa21cede 100644 --- a/web/components/command-palette/helpers.tsx +++ b/web/components/command-palette/helpers.tsx @@ -20,9 +20,7 @@ export const commandGroups: { icon: , itemName: (cycle: IWorkspaceDefaultSearchResult) => (
- {cycle.project__identifier} - {"- "} - {cycle.name} + {cycle.project__identifier} {cycle.name}
), path: (cycle: IWorkspaceDefaultSearchResult) => @@ -33,8 +31,9 @@ export const commandGroups: { icon: , itemName: (issue: IWorkspaceIssueSearchResult) => (
- {issue.project__identifier} - {"- "} + + {issue.project__identifier}-{issue.sequence_id} + {" "} {issue.name}
), @@ -46,9 +45,7 @@ export const commandGroups: { icon: , itemName: (view: IWorkspaceDefaultSearchResult) => (
- {view.project__identifier} - {"- "} - {view.name} + {view.project__identifier} {view.name}
), path: (view: IWorkspaceDefaultSearchResult) => @@ -59,9 +56,7 @@ export const commandGroups: { icon: , itemName: (module: IWorkspaceDefaultSearchResult) => (
- {module.project__identifier} - {"- "} - {module.name} + {module.project__identifier} {module.name}
), path: (module: IWorkspaceDefaultSearchResult) => @@ -72,9 +67,7 @@ export const commandGroups: { icon: , itemName: (page: IWorkspaceDefaultSearchResult) => (
- {page.project__identifier} - {"- "} - {page.name} + {page.project__identifier} {page.name}
), path: (page: IWorkspaceDefaultSearchResult) => diff --git a/web/components/command-palette/index.ts b/web/components/command-palette/index.ts index a1230ca32..1fac3f134 100644 --- a/web/components/command-palette/index.ts +++ b/web/components/command-palette/index.ts @@ -1,5 +1,4 @@ -export * from "./issue"; -export * from "./change-interface-theme"; +export * from "./actions"; export * from "./command-modal"; export * from "./command-pallette"; export * from "./helpers"; diff --git a/web/components/command-palette/issue/change-issue-assignee.tsx b/web/components/command-palette/issue/change-issue-assignee.tsx deleted file mode 100644 index 1693a6b94..000000000 --- a/web/components/command-palette/issue/change-issue-assignee.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import { Dispatch, SetStateAction, useCallback, FC } from "react"; -import { useRouter } from "next/router"; -import { observer } from "mobx-react-lite"; -import { mutate } from "swr"; -import { Command } from "cmdk"; -import { Check } from "lucide-react"; -// mobx store -import { useMobxStore } from "lib/mobx/store-provider"; -// services -import { IssueService } from "services/issue"; -// ui -import { Avatar } from "@plane/ui"; -// types -import { IUser, IIssue } from "types"; -// constants -import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; - -type Props = { - setIsPaletteOpen: Dispatch>; - issue: IIssue; - user: IUser | undefined; -}; - -// services -const issueService = new IssueService(); - -export const ChangeIssueAssignee: FC = observer((props) => { - const { setIsPaletteOpen, issue } = props; - // router - const router = useRouter(); - const { workspaceSlug, projectId, issueId } = router.query; - // store - const { - projectMember: { projectMembers }, - } = useMobxStore(); - - const options = - projectMembers?.map(({ member }) => ({ - value: member.id, - query: member.display_name, - content: ( - <> -
- - {member.display_name} -
- {issue.assignees.includes(member.id) && ( -
- -
- )} - - ), - })) ?? []; - - const updateIssue = useCallback( - async (formData: Partial) => { - if (!workspaceSlug || !projectId || !issueId) return; - - mutate( - ISSUE_DETAILS(issueId as string), - async (prevData) => { - if (!prevData) return prevData; - return { - ...prevData, - ...formData, - }; - }, - false - ); - - const payload = { ...formData }; - await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) - .then(() => { - mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); - }) - .catch((e) => { - console.error(e); - }); - }, - [workspaceSlug, issueId, projectId] - ); - - const handleIssueAssignees = (assignee: string) => { - const updatedAssignees = issue.assignees ?? []; - - if (updatedAssignees.includes(assignee)) { - updatedAssignees.splice(updatedAssignees.indexOf(assignee), 1); - } else { - updatedAssignees.push(assignee); - } - - updateIssue({ assignees: updatedAssignees }); - setIsPaletteOpen(false); - }; - - return ( - <> - {options.map((option: any) => ( - handleIssueAssignees(option.value)} - className="focus:outline-none" - > - {option.content} - - ))} - - ); -}); diff --git a/web/components/command-palette/issue/change-issue-priority.tsx b/web/components/command-palette/issue/change-issue-priority.tsx deleted file mode 100644 index c5e4bfa2a..000000000 --- a/web/components/command-palette/issue/change-issue-priority.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import React, { Dispatch, SetStateAction, useCallback } from "react"; -import { useRouter } from "next/router"; -import { mutate } from "swr"; -// cmdk -import { Command } from "cmdk"; -// services -import { IssueService } from "services/issue"; -// types -import { IIssue, IUser, TIssuePriorities } from "types"; -// constants -import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; -import { PRIORITIES } from "constants/project"; -// icons -import { PriorityIcon } from "@plane/ui"; -import { Check } from "lucide-react"; - -type Props = { - setIsPaletteOpen: Dispatch>; - issue: IIssue; - user: IUser; -}; - -// services -const issueService = new IssueService(); - -export const ChangeIssuePriority: React.FC = ({ setIsPaletteOpen, issue }) => { - const router = useRouter(); - const { workspaceSlug, projectId, issueId } = router.query; - - const submitChanges = useCallback( - async (formData: Partial) => { - if (!workspaceSlug || !projectId || !issueId) return; - - mutate( - ISSUE_DETAILS(issueId as string), - async (prevData) => { - if (!prevData) return prevData; - - return { - ...prevData, - ...formData, - }; - }, - false - ); - - const payload = { ...formData }; - await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) - .then(() => { - mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); - }) - .catch((e) => { - console.error(e); - }); - }, - [workspaceSlug, issueId, projectId] - ); - - const handleIssueState = (priority: TIssuePriorities) => { - submitChanges({ priority }); - setIsPaletteOpen(false); - }; - - return ( - <> - {PRIORITIES.map((priority) => ( - handleIssueState(priority)} className="focus:outline-none"> -
- - {priority ?? "None"} -
-
{priority === issue.priority && }
-
- ))} - - ); -}; diff --git a/web/components/command-palette/issue/change-issue-state.tsx b/web/components/command-palette/issue/change-issue-state.tsx deleted file mode 100644 index cbddfb688..000000000 --- a/web/components/command-palette/issue/change-issue-state.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import React, { Dispatch, SetStateAction, useCallback } from "react"; -import { useRouter } from "next/router"; -import useSWR, { mutate } from "swr"; -// cmdk -import { Command } from "cmdk"; -// services -import { IssueService } from "services/issue"; -import { ProjectStateService } from "services/project"; -// ui -import { Spinner, StateGroupIcon } from "@plane/ui"; -// icons -import { Check } from "lucide-react"; -// types -import { IUser, IIssue } from "types"; -// fetch keys -import { ISSUE_DETAILS, PROJECT_ISSUES_ACTIVITY, STATES_LIST } from "constants/fetch-keys"; - -type Props = { - setIsPaletteOpen: Dispatch>; - issue: IIssue; - user: IUser | undefined; -}; - -// services -const issueService = new IssueService(); -const stateService = new ProjectStateService(); - -export const ChangeIssueState: React.FC = ({ setIsPaletteOpen, issue }) => { - const router = useRouter(); - const { workspaceSlug, projectId, issueId } = router.query; - - const { data: states, mutate: mutateStates } = useSWR( - workspaceSlug && projectId ? STATES_LIST(projectId as string) : null, - workspaceSlug && projectId ? () => stateService.getStates(workspaceSlug as string, projectId as string) : null - ); - - const submitChanges = useCallback( - async (formData: Partial) => { - if (!workspaceSlug || !projectId || !issueId) return; - - mutate( - ISSUE_DETAILS(issueId as string), - async (prevData) => { - if (!prevData) return prevData; - return { - ...prevData, - ...formData, - }; - }, - false - ); - - const payload = { ...formData }; - await issueService - .patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload) - .then(() => { - mutateStates(); - mutate(PROJECT_ISSUES_ACTIVITY(issueId as string)); - }) - .catch((e) => { - console.error(e); - }); - }, - [workspaceSlug, issueId, projectId, mutateStates] - ); - - const handleIssueState = (stateId: string) => { - submitChanges({ state: stateId }); - setIsPaletteOpen(false); - }; - - return ( - <> - {states ? ( - states.length > 0 ? ( - states.map((state) => ( - handleIssueState(state.id)} className="focus:outline-none"> -
- -

{state.name}

-
-
{state.id === issue.state && }
-
- )) - ) : ( -
No states found
- ) - ) : ( - - )} - - ); -}; diff --git a/web/components/command-palette/issue/index.ts b/web/components/command-palette/issue/index.ts deleted file mode 100644 index 7d0bbd05d..000000000 --- a/web/components/command-palette/issue/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./change-issue-state"; -export * from "./change-issue-priority"; -export * from "./change-issue-assignee"; From 982eba0bd1c36a7eca0f198b9d52a59ec4eecc49 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:47:34 +0530 Subject: [PATCH 2/6] fix: complete pages editor not clickable, recent pages calculation logic (#2820) * fix: whole editor not clickable * fix: recent pages calculation * chore: update older pages calculation logic in recent pages list * fix: archived pages computed function * chore: add type for older pages --- .../src/ui/components/page-renderer.tsx | 53 +++++++++------- .../pages/create-update-page-modal.tsx | 4 +- web/components/pages/page-form.tsx | 20 ++----- .../pages/pages-list/recent-pages-list.tsx | 3 +- .../projects/[projectId]/pages/[pageId].tsx | 2 +- .../projects/[projectId]/pages/index.tsx | 2 +- web/store/page.store.ts | 60 ++++++++++++++----- web/types/pages.d.ts | 1 + 8 files changed, 89 insertions(+), 56 deletions(-) diff --git a/packages/editor/document-editor/src/ui/components/page-renderer.tsx b/packages/editor/document-editor/src/ui/components/page-renderer.tsx index aca50f3ff..746d46e45 100644 --- a/packages/editor/document-editor/src/ui/components/page-renderer.tsx +++ b/packages/editor/document-editor/src/ui/components/page-renderer.tsx @@ -1,33 +1,46 @@ -import { EditorContainer, EditorContentWrapper } from "@plane/editor-core" -import { Editor } from "@tiptap/react" -import { DocumentDetails } from "../types/editor-types" +import { EditorContainer, EditorContentWrapper } from "@plane/editor-core"; +import { Editor } from "@tiptap/react"; +import { DocumentDetails } from "../types/editor-types"; interface IPageRenderer { - sidePeakVisible: boolean, - documentDetails: DocumentDetails , - editor: Editor, - editorClassNames: string, - editorContentCustomClassNames?: string + sidePeakVisible: boolean; + documentDetails: DocumentDetails; + editor: Editor; + editorClassNames: string; + editorContentCustomClassNames?: string; } -export const PageRenderer = ({ sidePeakVisible, documentDetails, editor, editorClassNames, editorContentCustomClassNames }: IPageRenderer) => { +export const PageRenderer = ({ + sidePeakVisible, + documentDetails, + editor, + editorClassNames, + editorContentCustomClassNames, +}: IPageRenderer) => { return ( -
+
-

{documentDetails.title}

+

+ {documentDetails.title} +

-
-
+
+
-
- +
+
- +
- ) -} + ); +}; diff --git a/web/components/pages/create-update-page-modal.tsx b/web/components/pages/create-update-page-modal.tsx index 62e3d244e..7816e8dce 100644 --- a/web/components/pages/create-update-page-modal.tsx +++ b/web/components/pages/create-update-page-modal.tsx @@ -38,7 +38,7 @@ export const CreateUpdatePageModal: FC = (props) => { const createProjectPage = async (payload: IPage) => { if (!workspaceSlug) return; - createPage(workspaceSlug.toString(), projectId, payload) + await createPage(workspaceSlug.toString(), projectId, payload) .then((res) => { router.push(`/${workspaceSlug}/projects/${projectId}/pages/${res.id}`); onClose(); @@ -67,7 +67,7 @@ export const CreateUpdatePageModal: FC = (props) => { const updateProjectPage = async (payload: IPage) => { if (!data || !workspaceSlug) return; - return updatePage(workspaceSlug.toString(), projectId, data.id, payload) + await updatePage(workspaceSlug.toString(), projectId, data.id, payload) .then((res) => { onClose(); setToastAlert({ diff --git a/web/components/pages/page-form.tsx b/web/components/pages/page-form.tsx index 18366286c..594390255 100644 --- a/web/components/pages/page-form.tsx +++ b/web/components/pages/page-form.tsx @@ -1,9 +1,9 @@ -import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; // ui import { Button, Input, Tooltip } from "@plane/ui"; // types import { IPage } from "types"; +// constants import { PAGE_ACCESS_SPECIFIERS } from "constants/page"; type Props = { @@ -18,31 +18,21 @@ const defaultValues = { access: 0, }; -export const PageForm: React.FC = ({ handleFormSubmit, handleClose, data }) => { +export const PageForm: React.FC = (props) => { + const { handleFormSubmit, handleClose, data } = props; + const { formState: { errors, isSubmitting }, handleSubmit, control, - reset, } = useForm({ - defaultValues, + defaultValues: { ...defaultValues, ...data }, }); const handleCreateUpdatePage = async (formData: IPage) => { await handleFormSubmit(formData); - - reset({ - ...defaultValues, - }); }; - useEffect(() => { - reset({ - ...defaultValues, - ...data, - }); - }, [data, reset]); - return (
diff --git a/web/components/pages/pages-list/recent-pages-list.tsx b/web/components/pages/pages-list/recent-pages-list.tsx index 7122fa071..0f213db83 100644 --- a/web/components/pages/pages-list/recent-pages-list.tsx +++ b/web/components/pages/pages-list/recent-pages-list.tsx @@ -38,8 +38,9 @@ export const RecentPagesList: FC = observer(() => { <> {Object.keys(recentProjectPages).map((key) => { if (recentProjectPages[key].length === 0) return null; + return ( -
+

{replaceUnderscoreIfSnakeCase(key)}

diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx index 6906243ca..c725f2b89 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/[pageId].tsx @@ -235,7 +235,7 @@ const PageDetailsPage: NextPageWithLayout = () => { debouncedUpdatesEnabled={false} setIsSubmitting={setIsSubmitting} value={!value || value === "" ? "

" : value} - customClassName="tracking-tight self-center w-full max-w-full px-0" + customClassName="tracking-tight self-center px-0 h-full w-full" onChange={(_description_json: Object, description_html: string) => { onChange(description_html); setIsSubmitting("submitting"); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx index ce69f2c5e..edc2ef7d9 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/pages/index.tsx @@ -90,7 +90,7 @@ const ProjectPagesPage: NextPageWithLayout = observer(() => { projectId={projectId.toString()} /> )} -
+

Pages

diff --git a/web/store/page.store.ts b/web/store/page.store.ts index 44feaceb4..c86cd3814 100644 --- a/web/store/page.store.ts +++ b/web/store/page.store.ts @@ -86,39 +86,67 @@ export class PageStore implements IPageStore { } get projectPages() { - if (!this.rootStore.project.projectId) return; - return this.pages?.[this.rootStore.project.projectId] || []; + const projectId = this.rootStore.project.projectId; + + if (!projectId || !this.pages[projectId]) return undefined; + + return this.pages?.[projectId] || []; } get recentProjectPages() { - if (!this.rootStore.project.projectId) return; - const data: IRecentPages = { today: [], yesterday: [], this_week: [] }; - data["today"] = this.pages[this.rootStore.project.projectId]?.filter((p) => isToday(new Date(p.created_at))) || []; - data["yesterday"] = - this.pages[this.rootStore.project.projectId]?.filter((p) => isYesterday(new Date(p.created_at))) || []; + const projectId = this.rootStore.project.projectId; + + if (!projectId) return undefined; + + if (!this.pages[projectId]) return undefined; + + const data: IRecentPages = { today: [], yesterday: [], this_week: [], older: [] }; + + data["today"] = this.pages[projectId]?.filter((p) => isToday(new Date(p.created_at))) || []; + data["yesterday"] = this.pages[projectId]?.filter((p) => isYesterday(new Date(p.created_at))) || []; data["this_week"] = - this.pages[this.rootStore.project.projectId]?.filter((p) => isThisWeek(new Date(p.created_at))) || []; + this.pages[projectId]?.filter( + (p) => + isThisWeek(new Date(p.created_at)) && !isToday(new Date(p.created_at)) && !isYesterday(new Date(p.created_at)) + ) || []; + data["older"] = + this.pages[projectId]?.filter( + (p) => !isThisWeek(new Date(p.created_at)) && !isYesterday(new Date(p.created_at)) + ) || []; + return data; } get favoriteProjectPages() { - if (!this.rootStore.project.projectId) return; - return this.pages[this.rootStore.project.projectId]?.filter((p) => p.is_favorite); + const projectId = this.rootStore.project.projectId; + + if (!projectId || !this.pages[projectId]) return undefined; + + return this.pages[projectId]?.filter((p) => p.is_favorite); } get privateProjectPages() { - if (!this.rootStore.project.projectId) return; - return this.pages[this.rootStore.project.projectId]?.filter((p) => p.access === 1); + const projectId = this.rootStore.project.projectId; + + if (!projectId || !this.pages[projectId]) return undefined; + + return this.pages[projectId]?.filter((p) => p.access === 1); } get sharedProjectPages() { - if (!this.rootStore.project.projectId) return; - return this.pages[this.rootStore.project.projectId]?.filter((p) => p.access === 0); + const projectId = this.rootStore.project.projectId; + + if (!projectId || !this.pages[projectId]) return undefined; + + return this.pages[projectId]?.filter((p) => p.access === 0); } get archivedProjectPages() { - if (!this.rootStore.project.projectId) return; - return this.archivedPages[this.rootStore.project.projectId]; + const projectId = this.rootStore.project.projectId; + + if (!projectId || !this.archivedPages[projectId]) return undefined; + + return this.archivedPages[projectId]; } addToFavorites = async (workspaceSlug: string, projectId: string, pageId: string) => { diff --git a/web/types/pages.d.ts b/web/types/pages.d.ts index c3c87f572..a1c241f6a 100644 --- a/web/types/pages.d.ts +++ b/web/types/pages.d.ts @@ -30,6 +30,7 @@ export interface IRecentPages { today: IPage[]; yesterday: IPage[]; this_week: IPage[]; + older: IPage[]; [key: string]: IPage[]; } From 561223ea71fd67d8207600b6b6e43e51d73b278b Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Tue, 21 Nov 2023 17:35:15 +0530 Subject: [PATCH 3/6] chore: update join project endpoint (#2821) --- web/components/project/join-project-modal.tsx | 16 ++++++++-------- web/services/project/project.service.ts | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/components/project/join-project-modal.tsx b/web/components/project/join-project-modal.tsx index 22fc2e9f5..67adc881d 100644 --- a/web/components/project/join-project-modal.tsx +++ b/web/components/project/join-project-modal.tsx @@ -21,22 +21,21 @@ export const JoinProjectModal: React.FC = (props) => { // states const [isJoiningLoading, setIsJoiningLoading] = useState(false); // store - const { project: projectStore } = useMobxStore(); + const { + project: { joinProject }, + } = useMobxStore(); // router const router = useRouter(); const handleJoin = () => { setIsJoiningLoading(true); - projectStore - .joinProject(workspaceSlug, [project.id]) + joinProject(workspaceSlug, [project.id]) .then(() => { - setIsJoiningLoading(false); - router.push(`/${workspaceSlug}/projects/${project.id}/issues`); handleClose(); }) - .catch(() => { + .finally(() => { setIsJoiningLoading(false); }); }; @@ -73,8 +72,9 @@ export const JoinProjectModal: React.FC = (props) => { Join Project?

- Are you sure you want to join the project {project?.name}? - Please click the 'Join Project' button below to continue. + Are you sure you want to join the project{" "} + {project?.name}? Please click the 'Join + Project' button below to continue.

diff --git a/web/services/project/project.service.ts b/web/services/project/project.service.ts index e4f1149f2..5fb0e4a10 100644 --- a/web/services/project/project.service.ts +++ b/web/services/project/project.service.ts @@ -69,7 +69,7 @@ export class ProjectService extends APIService { } async joinProject(workspaceSlug: string, project_ids: string[]): Promise { - return this.post(`/api/workspaces/${workspaceSlug}/projects/join/`, { project_ids }) + return this.post(`/api/users/me/workspaces/${workspaceSlug}/projects/invitations/`, { project_ids }) .then((response) => response?.data) .catch((error) => { throw error?.response?.data; From d91b4e6fa1b22632c6d9a3438b2e9291d01503dd Mon Sep 17 00:00:00 2001 From: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Date: Tue, 21 Nov 2023 17:35:29 +0530 Subject: [PATCH 4/6] fix: bug fixes & UI improvements (#2819) * fix: profile setting fields border * fix: webhooks empty state UI * fix: cycle delete redirection from cycle detail * fix: integration access restriction --- web/components/api-token/empty-state.tsx | 2 +- web/components/cycles/delete-modal.tsx | 7 +++ .../integration/single-integration-card.tsx | 44 ++++++++++++++--- web/components/web-hooks/empty-webhooks.tsx | 46 +++++++++-------- .../[workspaceSlug]/me/profile/index.tsx | 5 +- .../settings/webhooks/index.tsx | 8 ++- web/public/empty-state/web-hook.svg | 49 +++++++++++++++++++ 7 files changed, 126 insertions(+), 35 deletions(-) create mode 100644 web/public/empty-state/web-hook.svg diff --git a/web/components/api-token/empty-state.tsx b/web/components/api-token/empty-state.tsx index 77618049f..fab8a9683 100644 --- a/web/components/api-token/empty-state.tsx +++ b/web/components/api-token/empty-state.tsx @@ -11,7 +11,7 @@ import emptyApiTokens from "public/empty-state/api-token.svg"; const ApiTokenEmptyState = () => { const router = useRouter(); return ( -
+
empty
No API Tokens
diff --git a/web/components/cycles/delete-modal.tsx b/web/components/cycles/delete-modal.tsx index f5024b3a2..aa28ea153 100644 --- a/web/components/cycles/delete-modal.tsx +++ b/web/components/cycles/delete-modal.tsx @@ -1,4 +1,6 @@ import { Fragment, useState } from "react"; +// next +import { useRouter } from "next/router"; import { Dialog, Transition } from "@headlessui/react"; import { observer } from "mobx-react-lite"; import { AlertTriangle } from "lucide-react"; @@ -27,6 +29,8 @@ export const CycleDeleteModal: React.FC = observer((props) => { const { setToastAlert } = useToast(); // states const [loader, setLoader] = useState(false); + const router = useRouter(); + const { cycleId } = router.query; const formSubmit = async () => { setLoader(true); @@ -38,6 +42,9 @@ export const CycleDeleteModal: React.FC = observer((props) => { title: "Success!", message: "Cycle deleted successfully.", }); + + if (cycleId) router.replace(`/${workspaceSlug}/projects/${projectId}/cycles`); + handleClose(); } catch (error) { setToastAlert({ diff --git a/web/components/integration/single-integration-card.tsx b/web/components/integration/single-integration-card.tsx index f5ff2b326..e0781b3f9 100644 --- a/web/components/integration/single-integration-card.tsx +++ b/web/components/integration/single-integration-card.tsx @@ -11,7 +11,7 @@ import { IntegrationService } from "services/integrations"; import useToast from "hooks/use-toast"; import useIntegrationPopup from "hooks/use-integration-popup"; // ui -import { Button, Loader } from "@plane/ui"; +import { Button, Loader, Tooltip } from "@plane/ui"; // icons import GithubLogo from "public/services/github.png"; import SlackLogo from "public/services/slack.png"; @@ -46,8 +46,11 @@ const integrationService = new IntegrationService(); export const SingleIntegrationCard: React.FC = observer(({ integration }) => { const { appConfig: { envConfig }, + user: { currentWorkspaceRole }, } = useMobxStore(); + const isUserAdmin = currentWorkspaceRole === 20; + const [deletingIntegration, setDeletingIntegration] = useState(false); const router = useRouter(); @@ -127,13 +130,40 @@ export const SingleIntegrationCard: React.FC = observer(({ integration }) {workspaceIntegrations ? ( isInstalled ? ( - + + + ) : ( - + + + ) ) : ( diff --git a/web/components/web-hooks/empty-webhooks.tsx b/web/components/web-hooks/empty-webhooks.tsx index d6ed6f2cd..d6a5d58de 100644 --- a/web/components/web-hooks/empty-webhooks.tsx +++ b/web/components/web-hooks/empty-webhooks.tsx @@ -1,28 +1,32 @@ -import { FC } from "react"; -import Link from "next/link"; -import { Button } from "@plane/ui"; +// next +import { useRouter } from "next/router"; import Image from "next/image"; -import EmptyWebhookLogo from "public/empty-state/issue.svg"; +// ui +import { Button } from "@plane/ui"; +// assets +import EmptyWebhook from "public/empty-state/web-hook.svg"; -interface IWebHookLists { - workspaceSlug: string; -} - -export const EmptyWebhooks: FC = (props) => { - const { workspaceSlug } = props; +export const EmptyWebhooks = () => { + const router = useRouter(); return ( -
-
- empty-webhook image - -
No Webhooks
-

Create webhooks to receive real-time updates and automate actions

- - - +
+
+ empty +
No Webhooks
+ { +

+ Create webhooks to receive real-time updates and automate actions +

+ } +
); diff --git a/web/pages/[workspaceSlug]/me/profile/index.tsx b/web/pages/[workspaceSlug]/me/profile/index.tsx index 33813fa52..a6ae7c784 100644 --- a/web/pages/[workspaceSlug]/me/profile/index.tsx +++ b/web/pages/[workspaceSlug]/me/profile/index.tsx @@ -303,7 +303,8 @@ const ProfilePage: NextPageWithLayout = () => { value={value} onChange={onChange} label={value ? value.toString() : "Select your role"} - buttonClassName={errors.role ? "border-red-500 bg-red-500/10" : ""} + buttonClassName={errors.role ? "border-red-500 bg-red-500/10" : "border-none"} + className="rounded-md border !border-custom-border-200" width="w-full" input > @@ -369,6 +370,8 @@ const ProfilePage: NextPageWithLayout = () => { options={timeZoneOptions} onChange={onChange} optionsClassName="w-full" + buttonClassName={"border-none"} + className="rounded-md border !border-custom-border-200" input /> )} diff --git a/web/pages/[workspaceSlug]/settings/webhooks/index.tsx b/web/pages/[workspaceSlug]/settings/webhooks/index.tsx index ce650e81c..f311741b8 100644 --- a/web/pages/[workspaceSlug]/settings/webhooks/index.tsx +++ b/web/pages/[workspaceSlug]/settings/webhooks/index.tsx @@ -31,7 +31,7 @@ const WebhooksPage: NextPage = observer(() => { return ( }> -
+
{loader ? (
@@ -41,10 +41,8 @@ const WebhooksPage: NextPage = observer(() => { {Object.keys(webhooks).length > 0 ? ( ) : ( -
-
- -
+
+
)} diff --git a/web/public/empty-state/web-hook.svg b/web/public/empty-state/web-hook.svg new file mode 100644 index 000000000..f8e32d3e5 --- /dev/null +++ b/web/public/empty-state/web-hook.svg @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From fb1f65c2c13c3fcfd9af86e69d191d6729970549 Mon Sep 17 00:00:00 2001 From: Lakhan Baheti <94619783+1akhanBaheti@users.noreply.github.com> Date: Tue, 21 Nov 2023 17:37:17 +0530 Subject: [PATCH 5/6] fix: sidebar project section hover (#2818) * fix: sidebar project section hover * fix: icons alignment --- web/components/project/sidebar-list-item.tsx | 44 ++++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/web/components/project/sidebar-list-item.tsx b/web/components/project/sidebar-list-item.tsx index 480a99a92..48c9d0b9e 100644 --- a/web/components/project/sidebar-list-item.tsx +++ b/web/components/project/sidebar-list-item.tsx @@ -1,11 +1,22 @@ -import { useState } from "react"; +import { useRef, useState } from "react"; import Link from "next/link"; import { useRouter } from "next/router"; import { DraggableProvided, DraggableStateSnapshot } from "@hello-pangea/dnd"; import { Disclosure, Transition } from "@headlessui/react"; import { observer } from "mobx-react-lite"; // icons -import { MoreVertical, PenSquare, LinkIcon, Star, FileText, Settings, Share2, LogOut, ChevronDown } from "lucide-react"; +import { + MoreVertical, + PenSquare, + LinkIcon, + Star, + FileText, + Settings, + Share2, + LogOut, + ChevronDown, + MoreHorizontal, +} from "lucide-react"; // hooks import useToast from "hooks/use-toast"; // helpers @@ -17,6 +28,7 @@ import { useMobxStore } from "lib/mobx/store-provider"; // components import { CustomMenu, Tooltip, ArchiveIcon, PhotoFilterIcon, DiceIcon, ContrastIcon, LayersIcon } from "@plane/ui"; import { LeaveProjectModal, PublishProjectModal } from "components/project"; +import useOutsideClickDetector from "hooks/use-outside-click-detector"; type Props = { project: IProject; @@ -72,12 +84,15 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { // states const [leaveProjectModalOpen, setLeaveProjectModal] = useState(false); const [publishModalOpen, setPublishModal] = useState(false); + const [isMenuActive, setIsMenuActive] = useState(false); const isAdmin = project.member_role === 20; const isViewerOrGuest = project.member_role === 10 || project.member_role === 5; const isCollapsed = themeStore.sidebarCollapsed; + const actionSectionRef = useRef(null); + const handleAddToFavorites = () => { if (!workspaceSlug) return; @@ -110,6 +125,8 @@ export const ProjectSidebarListItem: React.FC = observer((props) => { setLeaveProjectModal(false); }; + useOutsideClickDetector(actionSectionRef, () => setIsMenuActive(false)); + return ( <> setPublishModal(false)} /> @@ -120,7 +137,7 @@ export const ProjectSidebarListItem: React.FC = observer((props) => {
{provided && ( = observer((props) => { type="button" className={`absolute top-1/2 -translate-y-1/2 -left-2.5 hidden rounded p-0.5 text-custom-sidebar-text-400 ${ isCollapsed ? "" : "group-hover:!flex" - } ${project.sort_order === null ? "opacity-60 cursor-not-allowed" : ""}`} + } ${project.sort_order === null ? "opacity-60 cursor-not-allowed" : ""} ${ + isMenuActive ? "!flex" : "" + }`} {...provided?.dragHandleProps} > @@ -169,9 +188,9 @@ export const ProjectSidebarListItem: React.FC = observer((props) => {
{!isCollapsed && (
+ } + className={`hidden group-hover:block flex-shrink-0 ${isMenuActive ? "!block" : ""}`} buttonClassName="!text-custom-sidebar-text-400 hover:text-custom-sidebar-text-400" ellipsis placement="bottom-start" From 4cf3e69e22e9bb2506b57d4b214f99910950af52 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Tue, 21 Nov 2023 17:52:19 +0530 Subject: [PATCH 6/6] chore: file asset update (#2816) * chore: endpoint to update file asset * chore: aws storage endpoint change --- apiserver/plane/app/views/asset.py | 12 +++++------- apiserver/plane/app/views/project.py | 4 ++-- apiserver/plane/bgtasks/export_task.py | 8 ++++---- apiserver/plane/bgtasks/exporter_expired_task.py | 4 ++-- .../plane/db/management/commands/create_bucket.py | 4 ++-- apiserver/plane/settings/common.py | 4 ++-- 6 files changed, 17 insertions(+), 19 deletions(-) diff --git a/apiserver/plane/app/views/asset.py b/apiserver/plane/app/views/asset.py index 31e002e15..dc2827080 100644 --- a/apiserver/plane/app/views/asset.py +++ b/apiserver/plane/app/views/asset.py @@ -1,7 +1,7 @@ # Third party imports from rest_framework import status from rest_framework.response import Response -from rest_framework.parsers import MultiPartParser, FormParser +from rest_framework.parsers import MultiPartParser, FormParser, JSONParser # Module imports from .base import BaseAPIView @@ -10,7 +10,7 @@ from plane.app.serializers import FileAssetSerializer class FileAssetEndpoint(BaseAPIView): - parser_classes = (MultiPartParser, FormParser) + parser_classes = (MultiPartParser, FormParser, JSONParser,) """ A viewset for viewing and editing task instances. @@ -25,7 +25,6 @@ class FileAssetEndpoint(BaseAPIView): else: return Response({"error": "Asset key does not exist", "status": False}, status=status.HTTP_200_OK) - def post(self, request, slug): serializer = FileAssetSerializer(data=request.data) if serializer.is_valid(): @@ -34,12 +33,11 @@ class FileAssetEndpoint(BaseAPIView): serializer.save(workspace_id=workspace.id) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - - - def delete(self, request, workspace_id, asset_key): + + def patch(self, request, workspace_id, asset_key): asset_key = str(workspace_id) + "/" + asset_key file_asset = FileAsset.objects.get(asset=asset_key) - file_asset.is_deleted = True + file_asset.is_deleted = request.data.get("is_deleted", file_asset.is_deleted) file_asset.save() return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/app/views/project.py b/apiserver/plane/app/views/project.py index 727aa06ba..2d616e5a6 100644 --- a/apiserver/plane/app/views/project.py +++ b/apiserver/plane/app/views/project.py @@ -975,7 +975,7 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView): aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY, ) params = { - "Bucket": settings.AWS_S3_BUCKET_NAME, + "Bucket": settings.AWS_STORAGE_BUCKET_NAME, "Prefix": "static/project-cover/", } @@ -987,7 +987,7 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView): "/" ): # This line ensures we're only getting files, not "sub-folders" files.append( - f"https://{settings.AWS_S3_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}" + f"https://{settings.AWS_STORAGE_BUCKET_NAME}.s3.{settings.AWS_REGION}.amazonaws.com/{content['Key']}" ) return Response(files, status=status.HTTP_200_OK) diff --git a/apiserver/plane/bgtasks/export_task.py b/apiserver/plane/bgtasks/export_task.py index a49f8bb86..e895b859d 100644 --- a/apiserver/plane/bgtasks/export_task.py +++ b/apiserver/plane/bgtasks/export_task.py @@ -81,13 +81,13 @@ def upload_to_s3(zip_file, workspace_id, token_id, slug): ) s3.upload_fileobj( zip_file, - settings.AWS_S3_BUCKET_NAME, + settings.AWS_STORAGE_BUCKET_NAME, file_name, ExtraArgs={"ACL": "public-read", "ContentType": "application/zip"}, ) presigned_url = s3.generate_presigned_url( "get_object", - Params={"Bucket": settings.AWS_S3_BUCKET_NAME, "Key": file_name}, + Params={"Bucket": settings.AWS_STORAGE_BUCKET_NAME, "Key": file_name}, ExpiresIn=expires_in, ) # Create the new url with updated domain and protocol @@ -105,14 +105,14 @@ def upload_to_s3(zip_file, workspace_id, token_id, slug): ) s3.upload_fileobj( zip_file, - settings.AWS_S3_BUCKET_NAME, + settings.AWS_STORAGE_BUCKET_NAME, file_name, ExtraArgs={"ACL": "public-read", "ContentType": "application/zip"}, ) presigned_url = s3.generate_presigned_url( "get_object", - Params={"Bucket": settings.AWS_S3_BUCKET_NAME, "Key": file_name}, + Params={"Bucket": settings.AWS_STORAGE_BUCKET_NAME, "Key": file_name}, ExpiresIn=expires_in, ) diff --git a/apiserver/plane/bgtasks/exporter_expired_task.py b/apiserver/plane/bgtasks/exporter_expired_task.py index aef4408d4..30b638c84 100644 --- a/apiserver/plane/bgtasks/exporter_expired_task.py +++ b/apiserver/plane/bgtasks/exporter_expired_task.py @@ -42,8 +42,8 @@ def delete_old_s3_link(): # Delete object from S3 if file_name: if settings.USE_MINIO: - s3.delete_object(Bucket=settings.AWS_S3_BUCKET_NAME, Key=file_name) + s3.delete_object(Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=file_name) else: - s3.delete_object(Bucket=settings.AWS_S3_BUCKET_NAME, Key=file_name) + s3.delete_object(Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=file_name) ExporterHistory.objects.filter(id=exporter_id).update(url=None) diff --git a/apiserver/plane/db/management/commands/create_bucket.py b/apiserver/plane/db/management/commands/create_bucket.py index fbda34f77..054523bf9 100644 --- a/apiserver/plane/db/management/commands/create_bucket.py +++ b/apiserver/plane/db/management/commands/create_bucket.py @@ -40,7 +40,7 @@ class Command(BaseCommand): ) # Create an S3 client using the session s3_client = session.client('s3', endpoint_url=settings.AWS_S3_ENDPOINT_URL) - bucket_name = settings.AWS_S3_BUCKET_NAME + bucket_name = settings.AWS_STORAGE_BUCKET_NAME self.stdout.write(self.style.NOTICE("Checking bucket...")) @@ -50,7 +50,7 @@ class Command(BaseCommand): self.set_bucket_public_policy(s3_client, bucket_name) except ClientError as e: error_code = int(e.response['Error']['Code']) - bucket_name = settings.AWS_S3_BUCKET_NAME + bucket_name = settings.AWS_STORAGE_BUCKET_NAME if error_code == 404: # Bucket does not exist, create it self.stdout.write(self.style.WARNING(f"Bucket '{bucket_name}' does not exist. Creating bucket...")) diff --git a/apiserver/plane/settings/common.py b/apiserver/plane/settings/common.py index 4fa761f06..c76ec1340 100644 --- a/apiserver/plane/settings/common.py +++ b/apiserver/plane/settings/common.py @@ -224,7 +224,7 @@ STORAGES["default"] = { } AWS_ACCESS_KEY_ID = os.environ.get("AWS_ACCESS_KEY_ID", "access-key") AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY", "secret-key") -AWS_S3_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "uploads") +AWS_STORAGE_BUCKET_NAME = os.environ.get("AWS_S3_BUCKET_NAME", "uploads") AWS_REGION = os.environ.get("AWS_REGION", "") AWS_DEFAULT_ACL = "public-read" AWS_QUERYSTRING_AUTH = False @@ -234,7 +234,7 @@ AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL", None) or os.environ. ) if AWS_S3_ENDPOINT_URL: parsed_url = urlparse(os.environ.get("WEB_URL", "http://localhost")) - AWS_S3_CUSTOM_DOMAIN = f"{parsed_url.netloc}/{AWS_S3_BUCKET_NAME}" + AWS_S3_CUSTOM_DOMAIN = f"{parsed_url.netloc}/{AWS_STORAGE_BUCKET_NAME}" AWS_S3_URL_PROTOCOL = f"{parsed_url.scheme}:"