import { FC, useCallback, useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useRouter } from "next/router"; import { CircleCheck, CircleX, ChevronDown, ChevronUp, Clock, ExternalLink, FileStack, Link, Trash2, } from "lucide-react"; import { Button, ControlLink, CustomMenu, TOAST_TYPE, setToast } from "@plane/ui"; // components import { DeclineIssueModal, DeleteInboxIssueModal, InboxIssueActionsMobileHeader, InboxIssueCreateEditModalRoot, InboxIssueSnoozeModal, InboxIssueStatus, SelectDuplicateInboxIssueModal, } from "@/components/inbox"; import { IssueUpdateStatus } from "@/components/issues"; // constants import { EUserProjectRoles } from "@/constants/project"; // helpers import { EInboxIssueStatus } from "@/helpers/inbox.helper"; import { copyUrlToClipboard } from "@/helpers/string.helper"; // hooks import { useUser, useProjectInbox, useProject } from "@/hooks/store"; // store types import type { IInboxIssueStore } from "@/store/inbox/inbox-issue.store"; type TInboxIssueActionsHeader = { workspaceSlug: string; projectId: string; inboxIssue: IInboxIssueStore | undefined; isSubmitting: "submitting" | "submitted" | "saved"; toggleMobileSidebar: boolean; setToggleMobileSidebar: (value: boolean) => void; }; export const InboxIssueActionsHeader: FC = observer((props) => { const { workspaceSlug, projectId, inboxIssue, isSubmitting, toggleMobileSidebar, setToggleMobileSidebar } = props; // states const [isSnoozeDateModalOpen, setIsSnoozeDateModalOpen] = useState(false); const [selectDuplicateIssue, setSelectDuplicateIssue] = useState(false); const [acceptIssueModal, setAcceptIssueModal] = useState(false); const [declineIssueModal, setDeclineIssueModal] = useState(false); const [deleteIssueModal, setDeleteIssueModal] = useState(false); // store const { currentTab, deleteInboxIssue, inboxIssuesArray } = useProjectInbox(); const { currentUser, membership: { currentProjectRole }, } = useUser(); const router = useRouter(); const { getProjectById } = useProject(); const issue = inboxIssue?.issue; // derived values const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const canMarkAsDuplicate = isAllowed && (inboxIssue?.status === 0 || inboxIssue?.status === -2); const canMarkAsAccepted = isAllowed && (inboxIssue?.status === 0 || inboxIssue?.status === -2); const canMarkAsDeclined = isAllowed && (inboxIssue?.status === 0 || inboxIssue?.status === -2); const canDelete = isAllowed || inboxIssue?.created_by === currentUser?.id; const isAcceptedOrDeclined = inboxIssue?.status ? [-1, 1, 2].includes(inboxIssue.status) : undefined; const currentInboxIssueId = inboxIssue?.issue?.id; const issueLink = `${workspaceSlug}/projects/${issue?.project_id}/issues/${currentInboxIssueId}`; const redirectIssue = (): string | undefined => { let nextOrPreviousIssueId: string | undefined = undefined; const currentIssueIndex = inboxIssuesArray.findIndex((i) => i.issue.id === currentInboxIssueId); if (inboxIssuesArray[currentIssueIndex + 1]) nextOrPreviousIssueId = inboxIssuesArray[currentIssueIndex + 1].issue.id; else if (inboxIssuesArray[currentIssueIndex - 1]) nextOrPreviousIssueId = inboxIssuesArray[currentIssueIndex - 1].issue.id; else nextOrPreviousIssueId = undefined; return nextOrPreviousIssueId; }; const handleRedirection = (nextOrPreviousIssueId: string | undefined) => { if (nextOrPreviousIssueId) router.push( `/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${currentTab}&inboxIssueId=${nextOrPreviousIssueId}` ); else router.push(`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${currentTab}`); }; const handleInboxIssueAccept = async () => { const nextOrPreviousIssueId = redirectIssue(); await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.ACCEPTED); setAcceptIssueModal(false); handleRedirection(nextOrPreviousIssueId); }; const handleInboxIssueDecline = async () => { const nextOrPreviousIssueId = redirectIssue(); await inboxIssue?.updateInboxIssueStatus(EInboxIssueStatus.DECLINED); setDeclineIssueModal(false); handleRedirection(nextOrPreviousIssueId); }; const handleInboxSIssueSnooze = async (date: Date) => { const nextOrPreviousIssueId = redirectIssue(); await inboxIssue?.updateInboxIssueSnoozeTill(date); setIsSnoozeDateModalOpen(false); handleRedirection(nextOrPreviousIssueId); }; const handleInboxIssueDuplicate = async (issueId: string) => { await inboxIssue?.updateInboxIssueDuplicateTo(issueId); }; const handleInboxIssueDelete = async () => { if (!inboxIssue || !currentInboxIssueId) return; await deleteInboxIssue(workspaceSlug, projectId, currentInboxIssueId).finally(() => { router.push(`/${workspaceSlug}/projects/${projectId}/inbox`); }); }; const handleCopyIssueLink = () => copyUrlToClipboard(issueLink).then(() => setToast({ type: TOAST_TYPE.SUCCESS, title: "Link copied", message: "Issue link copied to clipboard", }) ); const currentIssueIndex = inboxIssuesArray.findIndex((issue) => issue.issue.id === currentInboxIssueId) ?? 0; const handleInboxIssueNavigation = useCallback( (direction: "next" | "prev") => { if (!inboxIssuesArray || !currentInboxIssueId) return; const activeElement = document.activeElement as HTMLElement; if (activeElement && (activeElement.classList.contains("tiptap") || activeElement.id === "title-input")) return; const nextIssueIndex = direction === "next" ? (currentIssueIndex + 1) % inboxIssuesArray.length : (currentIssueIndex - 1 + inboxIssuesArray.length) % inboxIssuesArray.length; const nextIssueId = inboxIssuesArray[nextIssueIndex].issue.id; if (!nextIssueId) return; router.push(`/${workspaceSlug}/projects/${projectId}/inbox?inboxIssueId=${nextIssueId}`); }, [currentInboxIssueId, currentIssueIndex, inboxIssuesArray, projectId, router, workspaceSlug] ); const onKeyDown = useCallback( (e: KeyboardEvent) => { if (e.key === "ArrowUp") { handleInboxIssueNavigation("prev"); } else if (e.key === "ArrowDown") { handleInboxIssueNavigation("next"); } }, [handleInboxIssueNavigation] ); useEffect(() => { document.addEventListener("keydown", onKeyDown); return () => { document.removeEventListener("keydown", onKeyDown); }; }, [onKeyDown]); if (!inboxIssue) return null; return ( <> <> setSelectDuplicateIssue(false)} value={inboxIssue?.duplicate_to} onSubmit={handleInboxIssueDuplicate} /> setAcceptIssueModal(false)} issue={inboxIssue?.issue} onSubmit={handleInboxIssueAccept} /> setDeclineIssueModal(false)} onSubmit={handleInboxIssueDecline} /> setDeleteIssueModal(false)} onSubmit={handleInboxIssueDelete} /> setIsSnoozeDateModalOpen(false)} value={inboxIssue?.snoozed_till} onConfirm={handleInboxSIssueSnooze} />
{issue?.project_id && issue.sequence_id && (

{getProjectById(issue.project_id)?.identifier}-{issue.sequence_id}

)}
{canMarkAsAccepted && (
)} {canMarkAsDeclined && (
)} {isAcceptedOrDeclined ? (
router.push(`/${workspaceSlug}/projects/${issue?.project_id}/issues/${currentInboxIssueId}`) } >
) : ( {canMarkAsAccepted && ( setIsSnoozeDateModalOpen(true)}>
Snooze
)} {canMarkAsDuplicate && ( setSelectDuplicateIssue(true)}>
Mark as duplicate
)} {canDelete && ( setDeleteIssueModal(true)}>
Delete
)}
)}
); });