From bc2690c494cd5b470873489bb06e306632a6a349 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Fri, 8 Dec 2023 16:40:39 +0530 Subject: [PATCH 1/6] fix: handled undefined issue_id in list layout --- web/components/issues/issue-layouts/list/blocks-list.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/components/issues/issue-layouts/list/blocks-list.tsx b/web/components/issues/issue-layouts/list/blocks-list.tsx index c51d3754788..29709496cb7 100644 --- a/web/components/issues/issue-layouts/list/blocks-list.tsx +++ b/web/components/issues/issue-layouts/list/blocks-list.tsx @@ -24,6 +24,7 @@ export const IssueBlocksList: FC = (props) => { {issueIds && issueIds.length > 0 ? ( issueIds.map( (issueId: string) => + issueId != undefined && issues[issueId] && ( Date: Thu, 11 Jan 2024 20:11:52 +0530 Subject: [PATCH 2/6] chore: refactor peek overview and user role validation. --- web/components/issues/description-form.tsx | 14 +- .../issue-detail/label/label-list-item.tsx | 2 +- .../issues/issue-detail/links/root.tsx | 2 +- web/components/issues/issue-detail/root.tsx | 39 ++- .../issues/issue-detail/sidebar.tsx | 8 +- .../issues/peek-overview/issue-detail.tsx | 225 +++--------------- .../issues/peek-overview/properties.tsx | 155 +++++------- web/components/issues/peek-overview/root.tsx | 177 ++++++-------- web/components/issues/peek-overview/view.tsx | 205 +++++++--------- 9 files changed, 303 insertions(+), 524 deletions(-) diff --git a/web/components/issues/description-form.tsx b/web/components/issues/description-form.tsx index 8dc3d01d347..f020672d22e 100644 --- a/web/components/issues/description-form.tsx +++ b/web/components/issues/description-form.tsx @@ -78,10 +78,16 @@ export const IssueDescriptionForm: FC = (props) => { async (formData: Partial) => { if (!formData?.name || formData?.name.length === 0 || formData?.name.length > 255) return; - await issueOperations.update(workspaceSlug, projectId, issueId, { - name: formData.name ?? "", - description_html: formData.description_html ?? "

", - }); + await issueOperations.update( + workspaceSlug, + projectId, + issueId, + { + name: formData.name ?? "", + description_html: formData.description_html ?? "

", + }, + false + ); }, [workspaceSlug, projectId, issueId, issueOperations] ); diff --git a/web/components/issues/issue-detail/label/label-list-item.tsx b/web/components/issues/issue-detail/label/label-list-item.tsx index 926d287aa57..3c3164c5a25 100644 --- a/web/components/issues/issue-detail/label/label-list-item.tsx +++ b/web/components/issues/issue-detail/label/label-list-item.tsx @@ -25,7 +25,7 @@ export const LabelListItem: FC = (props) => { const label = getLabelById(labelId); const handleLabel = async () => { - if (issue) { + if (issue && !disabled) { const currentLabels = issue.label_ids.filter((_labelId) => _labelId !== labelId); await labelOperations.updateIssue(workspaceSlug, projectId, issueId, { label_ids: currentLabels }); } diff --git a/web/components/issues/issue-detail/links/root.tsx b/web/components/issues/issue-detail/links/root.tsx index 1c226b7a7ea..94124085a49 100644 --- a/web/components/issues/issue-detail/links/root.tsx +++ b/web/components/issues/issue-detail/links/root.tsx @@ -107,7 +107,7 @@ export const IssueLinkRoot: FC = (props) => { linkOperations={handleLinkOperations} /> -
+

Links

{!disabled && ( diff --git a/web/components/issues/issue-detail/root.tsx b/web/components/issues/issue-detail/root.tsx index b52857e0a35..c82f790417c 100644 --- a/web/components/issues/issue-detail/root.tsx +++ b/web/components/issues/issue-detail/root.tsx @@ -8,16 +8,23 @@ import { EmptyState } from "components/common"; // images import emptyIssue from "public/empty-state/issue.svg"; // hooks -import { useIssueDetail, useUser } from "hooks/store"; +import { useIssueDetail, useIssues, useUser } from "hooks/store"; import useToast from "hooks/use-toast"; // types import { TIssue } from "@plane/types"; // constants import { EUserProjectRoles } from "constants/project"; +import { EIssuesStoreType } from "constants/issue"; export type TIssueOperations = { fetch: (workspaceSlug: string, projectId: string, issueId: string) => Promise; - update: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; + update: ( + workspaceSlug: string, + projectId: string, + issueId: string, + data: Partial, + showToast?: boolean + ) => Promise; remove: (workspaceSlug: string, projectId: string, issueId: string) => Promise; addIssueToCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueIds: string[]) => Promise; removeIssueFromCycle: (workspaceSlug: string, projectId: string, cycleId: string, issueId: string) => Promise; @@ -47,6 +54,9 @@ export const IssueDetailRoot: FC = (props) => { addIssueToModule, removeIssueFromModule, } = useIssueDetail(); + const { + issues: { removeIssue: removeArchivedIssue }, + } = useIssues(EIssuesStoreType.ARCHIVED); const { setToastAlert } = useToast(); const { membership: { currentProjectRole }, @@ -61,14 +71,22 @@ export const IssueDetailRoot: FC = (props) => { console.error("Error fetching the parent issue"); } }, - update: async (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => { + update: async ( + workspaceSlug: string, + projectId: string, + issueId: string, + data: Partial, + showToast: boolean = true + ) => { try { await updateIssue(workspaceSlug, projectId, issueId, data); - setToastAlert({ - title: "Issue updated successfully", - type: "success", - message: "Issue updated successfully", - }); + if (showToast) { + setToastAlert({ + title: "Issue updated successfully", + type: "success", + message: "Issue updated successfully", + }); + } } catch (error) { setToastAlert({ title: "Issue update failed", @@ -79,7 +97,8 @@ export const IssueDetailRoot: FC = (props) => { }, remove: async (workspaceSlug: string, projectId: string, issueId: string) => { try { - await removeIssue(workspaceSlug, projectId, issueId); + if (is_archived) await removeArchivedIssue(workspaceSlug, projectId, issueId); + else await removeIssue(workspaceSlug, projectId, issueId); setToastAlert({ title: "Issue deleted successfully", type: "success", @@ -159,9 +178,11 @@ export const IssueDetailRoot: FC = (props) => { }, }), [ + is_archived, fetchIssue, updateIssue, removeIssue, + removeArchivedIssue, addIssueToCycle, removeIssueFromCycle, addIssueToModule, diff --git a/web/components/issues/issue-detail/sidebar.tsx b/web/components/issues/issue-detail/sidebar.tsx index a80f8873005..6b249f4bd52 100644 --- a/web/components/issues/issue-detail/sidebar.tsx +++ b/web/components/issues/issue-detail/sidebar.tsx @@ -162,7 +162,7 @@ export const IssueDetailsSidebar: React.FC = observer((props) => { /> )} - {/* {(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && ( + {(fieldsToShow.includes("all") || fieldsToShow.includes("link")) && ( - )} */} + )} - {/* {isAllowed && (fieldsToShow.includes("all") || fieldsToShow.includes("delete")) && ( + {is_editable && (fieldsToShow.includes("all") || fieldsToShow.includes("delete")) && ( - )} */} + )}
diff --git a/web/components/issues/peek-overview/issue-detail.tsx b/web/components/issues/peek-overview/issue-detail.tsx index 3eb7037f246..8a0ab0fe759 100644 --- a/web/components/issues/peek-overview/issue-detail.tsx +++ b/web/components/issues/peek-overview/issue-detail.tsx @@ -1,133 +1,32 @@ -import { ChangeEvent, FC, useCallback, useEffect, useState } from "react"; -import { Controller, useForm } from "react-hook-form"; -import debounce from "lodash/debounce"; -// packages -import { RichTextEditor } from "@plane/rich-text-editor"; +import { FC } from "react"; // hooks -import { useMention, useProject, useUser } from "hooks/store"; -import useReloadConfirmations from "hooks/use-reload-confirmation"; +import { useIssueDetail, useProject, useUser } from "hooks/store"; // components -import { IssuePeekOverviewReactions } from "components/issues"; -// ui -import { TextArea } from "@plane/ui"; -// types -import { TIssue, IUser } from "@plane/types"; -// services -import { FileService } from "services/file.service"; -// constants -import { EUserProjectRoles } from "constants/project"; - -const fileService = new FileService(); +import { IssueDescriptionForm, TIssueOperations } from "components/issues"; +import { IssueReaction } from "../issue-detail/reactions"; interface IPeekOverviewIssueDetails { workspaceSlug: string; - issue: TIssue; - issueReactions: any; - user: IUser | null; - issueUpdate: (issue: Partial) => void; - issueReactionCreate: (reaction: string) => void; - issueReactionRemove: (reaction: string) => void; + projectId: string; + issueId: string; + issueOperations: TIssueOperations; + is_archived: boolean; + disabled: boolean; isSubmitting: "submitting" | "submitted" | "saved"; setIsSubmitting: (value: "submitting" | "submitted" | "saved") => void; } export const PeekOverviewIssueDetails: FC = (props) => { - const { - workspaceSlug, - issue, - issueReactions, - user, - issueUpdate, - issueReactionCreate, - issueReactionRemove, - isSubmitting, - setIsSubmitting, - } = props; - // states - const [characterLimit, setCharacterLimit] = useState(false); + const { workspaceSlug, projectId, issueId, issueOperations, disabled, isSubmitting, setIsSubmitting } = props; // store hooks - const { - membership: { currentProjectRole }, - } = useUser(); - const { mentionHighlights, mentionSuggestions } = useMention(); const { getProjectById } = useProject(); - // derived values - const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; - // toast alert - const { setShowAlert } = useReloadConfirmations(); - // form info + const { currentUser } = useUser(); const { - handleSubmit, - watch, - reset, - control, - formState: { errors }, - } = useForm({ - defaultValues: { - name: issue.name, - description_html: issue.description_html, - }, - }); - - const handleDescriptionFormSubmit = useCallback( - async (formData: Partial) => { - if (!formData?.name || formData?.name.length === 0 || formData?.name.length > 255) return; - - await issueUpdate({ - ...issue, - name: formData.name ?? "", - description_html: formData.description_html ?? "

", - }); - }, - [issue, issueUpdate] - ); - - const [localTitleValue, setLocalTitleValue] = useState(""); - const [localIssueDescription, setLocalIssueDescription] = useState({ - id: issue.id, - description_html: issue.description_html, - }); - - // adding issue.description_html or issue.name to dependency array causes - // editor rerendering on every save - useEffect(() => { - if (issue.id) { - setLocalIssueDescription({ id: issue.id, description_html: issue.description_html }); - setLocalTitleValue(issue.name); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [issue.id]); // TODO: Verify the exhaustive-deps warning - - // ADDING handleDescriptionFormSubmit TO DEPENDENCY ARRAY PRODUCES ADVERSE EFFECTS - // TODO: Verify the exhaustive-deps warning - // eslint-disable-next-line react-hooks/exhaustive-deps - const debouncedFormSave = useCallback( - debounce(async () => { - handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted")); - }, 1500), - [handleSubmit] - ); - - useEffect(() => { - if (isSubmitting === "submitted") { - setShowAlert(false); - setTimeout(async () => { - setIsSubmitting("saved"); - }, 2000); - } else if (isSubmitting === "submitting") { - setShowAlert(true); - } - }, [isSubmitting, setShowAlert, setIsSubmitting]); - - // reset form values - useEffect(() => { - if (!issue) return; - - reset({ - ...issue, - }); - }, [issue, reset]); - + issue: { getIssueById }, + } = useIssueDetail(); + // derived values + const issue = getIssueById(issueId); + if (!issue) return <>; const projectDetails = getProjectById(issue?.project_id); return ( @@ -135,82 +34,24 @@ export const PeekOverviewIssueDetails: FC = (props) = {projectDetails?.identifier}-{issue?.sequence_id} - -
- {isAllowed ? ( - ( -