[WEB-6534] feat: update work item creation toast with clickable identifier (#6766)

* chore: work item create toast ux enhancements

* chore: code refactoring
This commit is contained in:
Anmol Singh Bhatia
2026-04-16 15:03:36 +05:30
committed by GitHub
parent 8b6b659053
commit 15dbd4fe6f
5 changed files with 50 additions and 35 deletions
@@ -145,7 +145,6 @@ export const CreateUpdateEpicModalBase = observer(function CreateUpdateEpicModal
setToast({
type: TOAST_TYPE.SUCCESS,
title: "Success!",
message: "Epic created successfully.",
actionItems: response?.project_id && (
<CreateIssueToastActionItems
workspaceSlug={workspaceSlug.toString()}
@@ -47,20 +47,23 @@ export const CreateIssueToastActionItems = observer(function CreateIssueToastAct
// derived values
const issue = getIssueById(issueId);
const projectIdentifier = getProjectIdentifierById(issue?.project_id);
const hasIdentifier = !!(projectIdentifier && issue?.sequence_id);
const identifier = hasIdentifier ? `${projectIdentifier}-${issue.sequence_id}` : null;
const workItemLink = generateWorkItemLink({
workspaceSlug,
projectId: issue?.project_id,
issueId,
projectIdentifier,
sequenceId: issue?.sequence_id,
isEpic,
});
const workItemIdentifier = projectIdentifier && issue?.sequence_id ? `${projectIdentifier}-${issue.sequence_id}` : "";
const workItemLink = hasIdentifier
? generateWorkItemLink({
workspaceSlug,
projectId: issue?.project_id,
issueId,
projectIdentifier,
sequenceId: issue?.sequence_id,
isEpic,
})
: null;
const handleCopyLink = useCallback(
async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (!workItemLink) return;
try {
await copyUrlToClipboard(workItemLink);
setLinkCopied(true);
@@ -77,8 +80,9 @@ export const CreateIssueToastActionItems = observer(function CreateIssueToastAct
const handleCopyId = useCallback(
async (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (!identifier) return;
try {
await copyTextToClipboard(workItemIdentifier);
await copyTextToClipboard(identifier);
setIdCopied(true);
if (idTimerRef.current) clearTimeout(idTimerRef.current);
idTimerRef.current = setTimeout(() => setIdCopied(false), 3000);
@@ -88,33 +92,45 @@ export const CreateIssueToastActionItems = observer(function CreateIssueToastAct
e.preventDefault();
e.stopPropagation();
},
[workItemIdentifier]
[identifier]
);
if (!issue) return null;
return (
<div className="flex items-center justify-between text-11 text-secondary w-full">
<div className="flex items-center gap-1">
<a
href={workItemLink}
target="_blank"
rel="noopener noreferrer"
className={cn(getButtonStyling("ghost", "sm"), "text-accent-primary no-underline")}
>
{t("common.view")}
</a>
<div className="flex flex-col gap-1 w-full">
<p className="text-body-xs-regular text-tertiary">
{identifier ? (
<button onClick={handleCopyId} className="cursor-pointer hover:underline">
{identifier}
</button>
) : (
<span>{isEpic ? "Epic" : "Work item"}</span>
)}
{" created successfully"}
</p>
<div className="flex items-center justify-between text-11 text-secondary -ml-2">
<div className="flex items-center gap-1">
{workItemLink && (
<>
<a
href={workItemLink}
target="_blank"
rel="noopener noreferrer"
className={cn(getButtonStyling("ghost", "sm"), "text-accent-primary no-underline")}
>
{t("common.view")}
</a>
<Button variant="ghost" size="sm" onClick={handleCopyLink} disabled={linkCopied}>
{linkCopied ? t("common.copied") : t("common.actions.copy_link")}
</Button>
<Button variant="ghost" size="sm" onClick={handleCopyLink} disabled={linkCopied}>
{linkCopied ? t("common.copied") : t("common.actions.copy_link")}
</Button>
</>
)}
</div>
{idCopied && <span className="cursor-default px-2 py-1 text-secondary">{t("common.actions.id_copied")}</span>}
</div>
{workItemIdentifier && (
<Button variant="ghost" size="sm" onClick={handleCopyId} disabled={idCopied}>
{idCopied ? t("common.actions.id_copied") : workItemIdentifier}
</Button>
)}
</div>
);
});
@@ -178,7 +178,7 @@ export const QuickAddIssueRoot = observer(function QuickAddIssueRoot(props: TQui
loading: isEpic ? t("epic.adding") : t("issue.adding"),
success: {
title: t("common.success"),
message: () => `${isEpic ? t("epic.create.success") : t("issue.create.success")}`,
message: () => undefined,
actionItems: (data) => (
<CreateIssueToastActionItems
workspaceSlug={workspaceSlug.toString()}
@@ -257,7 +257,7 @@ export const CreateUpdateIssueModalBase = observer(function CreateUpdateIssueMod
setToast({
type: TOAST_TYPE.SUCCESS,
title: t("success"),
message: `${is_draft_issue ? t("draft_created") : t("issue_created_successfully")} `,
message: is_draft_issue ? `${t("draft_created")}` : undefined,
actionItems: !is_draft_issue && response?.project_id && (
<CreateIssueToastActionItems
workspaceSlug={workspaceSlug.toString()}
+1 -1
View File
@@ -41,7 +41,7 @@ type SetToastProps =
actionItems?: React.ReactNode;
};
type PromiseToastCallback<ToastData> = (data: ToastData) => string;
type PromiseToastCallback<ToastData> = (data: ToastData) => string | undefined;
type ActionItemsPromiseToastCallback<ToastData> = (data: ToastData) => React.ReactNode;
type PromiseToastData<ToastData> = {