From 3eda3845faff4015836c08bb4de75b4a574fc6d2 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:07:27 +0530 Subject: [PATCH] [WEB-1008] chore: issue toast alert improvement (#4914) * chore: toast alert improvement * chore: create issue toast action items component added * chore: toast alert improvement * chore: issue toast alert implementation * fix: spreadsheet layout quick add toast action --- packages/ui/src/toast/index.tsx | 72 ++++++++++--------- .../create-issue-toast-action-items.tsx | 70 ++++++++++++++++++ web/core/components/issues/index.ts | 1 + .../calendar/quick-add-issue-form.tsx | 8 +++ .../gantt/quick-add-issue-form.tsx | 8 +++ .../kanban/quick-add-issue-form.tsx | 8 +++ .../list/quick-add-issue-form.tsx | 8 +++ .../spreadsheet/quick-add-issue-form.tsx | 8 +++ .../components/issues/issue-modal/modal.tsx | 8 +++ 9 files changed, 158 insertions(+), 33 deletions(-) create mode 100644 web/core/components/issues/create-issue-toast-action-items.tsx diff --git a/packages/ui/src/toast/index.tsx b/packages/ui/src/toast/index.tsx index f38050532..ce2d05ef7 100644 --- a/packages/ui/src/toast/index.tsx +++ b/packages/ui/src/toast/index.tsx @@ -25,13 +25,16 @@ type SetToastProps = type: Exclude; title: string; message?: string; + actionItems?: React.ReactNode; }; type PromiseToastCallback = (data: ToastData) => string; +type ActionItemsPromiseToastCallback = (data: ToastData) => JSX.Element; type PromiseToastData = { title: string; message?: PromiseToastCallback; + actionItems?: ActionItemsPromiseToastCallback; }; type PromiseToastOptions = { @@ -54,7 +57,7 @@ type ToastProps = { export const Toast = (props: ToastProps) => { const { theme } = props; - return ; + return ; }; export const setToast = (props: SetToastProps) => { @@ -66,29 +69,27 @@ export const setToast = (props: SetToastProps) => { borderColorClassName, }: ToastContentProps) => props.type === TOAST_TYPE.LOADING ? ( -
{ - e.stopPropagation(); - e.preventDefault(); - }} - className={cn( - "w-[350px] h-[67.3px] rounded-lg border shadow-sm p-2", - backgroundColorClassName, - borderColorClassName - )} - > -
- {icon &&
{icon}
} -
-
{props.title ?? "Loading..."}
-
- toast.dismiss(toastId)} - /> +
+
{ + e.stopPropagation(); + e.preventDefault(); + }} + className={cn("w-full rounded-lg border shadow-sm p-2", backgroundColorClassName, borderColorClassName)} + > +
+ {icon &&
{icon}
} +
+
{props.title ?? "Loading..."}
+
+ toast.dismiss(toastId)} + /> +
@@ -100,7 +101,7 @@ export const setToast = (props: SetToastProps) => { e.preventDefault(); }} className={cn( - "relative flex flex-col w-[350px] rounded-lg border shadow-sm p-2", + "relative group flex flex-col w-[350px] rounded-lg border shadow-sm p-2", backgroundColorClassName, borderColorClassName )} @@ -112,12 +113,15 @@ export const setToast = (props: SetToastProps) => { height={14} onClick={() => toast.dismiss(toastId)} /> -
- {icon &&
{icon}
} -
-
{props.title}
- {props.message &&
{props.message}
} +
+
+ {icon &&
{icon}
} +
+
{props.title}
+ {props.message &&
{props.message}
} +
+ {props.actionItems &&
{props.actionItems}
}
); @@ -128,7 +132,7 @@ export const setToast = (props: SetToastProps) => { (toastId) => renderToastContent({ toastId, - icon: , + icon: , textColorClassName: "text-toast-text-success", backgroundColorClassName: "bg-toast-background-success", borderColorClassName: "border-toast-border-success", @@ -140,7 +144,7 @@ export const setToast = (props: SetToastProps) => { (toastId) => renderToastContent({ toastId, - icon: , + icon: , textColorClassName: "text-toast-text-error", backgroundColorClassName: "bg-toast-background-error", borderColorClassName: "border-toast-border-error", @@ -152,7 +156,7 @@ export const setToast = (props: SetToastProps) => { (toastId) => renderToastContent({ toastId, - icon: , + icon: , textColorClassName: "text-toast-text-warning", backgroundColorClassName: "bg-toast-background-warning", borderColorClassName: "border-toast-border-warning", @@ -197,6 +201,7 @@ export const setPromiseToast = ( id: tId, title: options.success.title, message: options.success.message?.(data), + actionItems: options.success.actionItems?.(data), }); }) .catch((data: ToastData) => { @@ -205,6 +210,7 @@ export const setPromiseToast = ( id: tId, title: options.error.title, message: options.error.message?.(data), + actionItems: options.error.actionItems?.(data), }); }); }; diff --git a/web/core/components/issues/create-issue-toast-action-items.tsx b/web/core/components/issues/create-issue-toast-action-items.tsx new file mode 100644 index 000000000..0fe76fb9b --- /dev/null +++ b/web/core/components/issues/create-issue-toast-action-items.tsx @@ -0,0 +1,70 @@ +"use client"; +import React, { FC, useState } from "react"; +import { observer } from "mobx-react"; +// helpers +import { copyUrlToClipboard } from "@/helpers/string.helper"; +// hooks +import { useIssueDetail } from "@/hooks/store"; + +type TCreateIssueToastActionItems = { + workspaceSlug: string; + projectId: string; + issueId: string; +}; + +export const CreateIssueToastActionItems: FC = observer((props) => { + const { workspaceSlug, projectId, issueId } = props; + // state + const [copied, setCopied] = useState(false); + // store hooks + const { + issue: { getIssueById }, + } = useIssueDetail(); + + // derived values + const issue = getIssueById(issueId); + + if (!issue) return null; + + const issueLink = `${workspaceSlug}/projects/${projectId}/issues/${issueId}`; + + const copyToClipboard = async (e: React.MouseEvent) => { + try { + await copyUrlToClipboard(issueLink); + setCopied(true); + setTimeout(() => setCopied(false), 3000); + } catch (error) { + setCopied(false); + } + e.preventDefault(); + e.stopPropagation(); + }; + + return ( +
+ + View issue + + + {copied ? ( + <> + Copied! + + ) : ( + <> + + + )} +
+ ); +}); diff --git a/web/core/components/issues/index.ts b/web/core/components/issues/index.ts index 454d85110..8b8180a13 100644 --- a/web/core/components/issues/index.ts +++ b/web/core/components/issues/index.ts @@ -9,6 +9,7 @@ export * from "./parent-issues-list-modal"; export * from "./label"; export * from "./confirm-issue-discard"; export * from "./issue-update-status"; +export * from "./create-issue-toast-action-items"; // issue details export * from "./issue-detail"; diff --git a/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx index b40a7050a..6ee3e19e3 100644 --- a/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx +++ b/web/core/components/issues/issue-layouts/calendar/quick-add-issue-form.tsx @@ -12,6 +12,7 @@ import { ISearchIssueResponse, TIssue } from "@plane/types"; import { TOAST_TYPE, setPromiseToast, setToast, CustomMenu } from "@plane/ui"; // components import { ExistingIssuesListModal } from "@/components/core"; +import { CreateIssueToastActionItems } from "@/components/issues"; // constants import { ISSUE_CREATED } from "@/constants/event-tracker"; // helpers @@ -135,6 +136,13 @@ export const CalendarQuickAddIssueForm: React.FC = observer((props) => { success: { title: "Success!", message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), }, error: { title: "Error!", diff --git a/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx index 9cb02b677..f5996af69 100644 --- a/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx +++ b/web/core/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx @@ -8,6 +8,7 @@ import { PlusIcon } from "lucide-react"; import { IProject, TIssue } from "@plane/types"; // hooks import { setPromiseToast } from "@plane/ui"; +import { CreateIssueToastActionItems } from "@/components/issues"; import { ISSUE_CREATED } from "@/constants/event-tracker"; import { cn } from "@/helpers/common.helper"; import { renderFormattedPayloadDate } from "@/helpers/date-time.helper"; @@ -113,6 +114,13 @@ export const GanttQuickAddIssueForm: React.FC = observe success: { title: "Success!", message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), }, error: { title: "Error!", diff --git a/web/core/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx index f0914fcdd..263ccc99c 100644 --- a/web/core/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx +++ b/web/core/components/issues/issue-layouts/kanban/quick-add-issue-form.tsx @@ -8,6 +8,7 @@ import { PlusIcon } from "lucide-react"; import { TIssue } from "@plane/types"; // hooks import { setPromiseToast } from "@plane/ui"; +import { CreateIssueToastActionItems } from "@/components/issues"; import { ISSUE_CREATED } from "@/constants/event-tracker"; import { createIssuePayload } from "@/helpers/issue.helper"; import { useEventTracker, useProject } from "@/hooks/store"; @@ -102,6 +103,13 @@ export const KanBanQuickAddIssueForm: React.FC = obser success: { title: "Success!", message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), }, error: { title: "Error!", diff --git a/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx index 5bc85837a..0025f2dfc 100644 --- a/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx +++ b/web/core/components/issues/issue-layouts/list/quick-add-issue-form.tsx @@ -8,6 +8,7 @@ import { PlusIcon } from "lucide-react"; import { TIssue, IProject } from "@plane/types"; // hooks import { setPromiseToast } from "@plane/ui"; +import { CreateIssueToastActionItems } from "@/components/issues"; import { ISSUE_CREATED } from "@/constants/event-tracker"; import { createIssuePayload } from "@/helpers/issue.helper"; import { useEventTracker, useProject } from "@/hooks/store"; @@ -104,6 +105,13 @@ export const ListQuickAddIssueForm: FC = observer((props success: { title: "Success!", message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), }, error: { title: "Error!", diff --git a/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx b/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx index 701d8aa53..e88887b43 100644 --- a/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx +++ b/web/core/components/issues/issue-layouts/spreadsheet/quick-add-issue-form.tsx @@ -8,6 +8,7 @@ import { PlusIcon } from "lucide-react"; import { TIssue } from "@plane/types"; // hooks import { TOAST_TYPE, setPromiseToast, setToast } from "@plane/ui"; +import { CreateIssueToastActionItems } from "@/components/issues"; import { ISSUE_CREATED } from "@/constants/event-tracker"; import { createIssuePayload } from "@/helpers/issue.helper"; import { useEventTracker, useProject, useWorkspace } from "@/hooks/store"; @@ -162,6 +163,13 @@ export const SpreadsheetQuickAddIssueForm: React.FC = observer((props) => success: { title: "Success!", message: () => "Issue created successfully.", + actionItems: (data) => ( + + ), }, error: { title: "Error!", diff --git a/web/core/components/issues/issue-modal/modal.tsx b/web/core/components/issues/issue-modal/modal.tsx index 5877069ef..2ff03df66 100644 --- a/web/core/components/issues/issue-modal/modal.tsx +++ b/web/core/components/issues/issue-modal/modal.tsx @@ -7,6 +7,7 @@ import { useParams, usePathname } from "next/navigation"; import type { TIssue } from "@plane/types"; // ui import { EModalPosition, EModalWidth, ModalCore, TOAST_TYPE, setToast } from "@plane/ui"; +import { CreateIssueToastActionItems } from "@/components/issues"; // constants import { ISSUE_CREATED, ISSUE_UPDATED } from "@/constants/event-tracker"; import { EIssuesStoreType } from "@/constants/issue"; @@ -189,6 +190,13 @@ export const CreateUpdateIssueModal: React.FC = observer((prop type: TOAST_TYPE.SUCCESS, title: "Success!", message: `${is_draft_issue ? "Draft issue" : "Issue"} created successfully.`, + actionItems: !is_draft_issue && ( + + ), }); captureIssueEvent({ eventName: ISSUE_CREATED,