diff --git a/apps/app/components/icons/attachment-icon.tsx b/apps/app/components/icons/attachment-icon.tsx index 6fe435493..59c32ea60 100644 --- a/apps/app/components/icons/attachment-icon.tsx +++ b/apps/app/components/icons/attachment-icon.tsx @@ -1,19 +1,46 @@ -import React from "react"; +import { + CssIcon, + CsvIcon, + DocIcon, + FigmaIcon, + HtmlIcon, + JavaScriptIcon, + JpgIcon, + PdfIcon, + PngIcon, + SheetIcon, + SvgIcon, + TxtIcon, +} from "components/icons"; -import type { Props } from "./types"; +export const getFileIcon = (fileType: string) => { + switch (fileType) { + case "pdf": + return ; + case "csv": + return ; + case "xlsx": + return ; + case "css": + return ; + case "doc": + return ; + case "fig": + return ; + case "html": + return ; + case "png": + return ; + case "jpg": + return ; + case "js": + return ; + case "txt": + return ; + case "svg": + return ; -export const AttachmentIcon: React.FC = ({ width, height, className }) => ( - - - - ); + default: + return ; + } +}; diff --git a/apps/app/components/icons/css-file-icon.tsx b/apps/app/components/icons/css-file-icon.tsx new file mode 100644 index 000000000..54b563d37 --- /dev/null +++ b/apps/app/components/icons/css-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import CssFileIcon from "public/attachment/css-icon.png"; + +export const CssIcon: React.FC = ({ width, height }) => ( + CssFileIcon +); diff --git a/apps/app/components/icons/csv-file-icon.tsx b/apps/app/components/icons/csv-file-icon.tsx new file mode 100644 index 000000000..63f01205e --- /dev/null +++ b/apps/app/components/icons/csv-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import CSVFileIcon from "public/attachment/csv-icon.png"; + +export const CsvIcon: React.FC = ({ width , height }) => ( + CSVFileIcon +); diff --git a/apps/app/components/icons/doc-file-icon.tsx b/apps/app/components/icons/doc-file-icon.tsx new file mode 100644 index 000000000..173d40df1 --- /dev/null +++ b/apps/app/components/icons/doc-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import DocFileIcon from "public/attachment/doc-icon.png"; + +export const DocIcon: React.FC = ({ width , height }) => ( + DocFileIcon +); diff --git a/apps/app/components/icons/figma-file-icon.tsx b/apps/app/components/icons/figma-file-icon.tsx new file mode 100644 index 000000000..7ff9f6ac0 --- /dev/null +++ b/apps/app/components/icons/figma-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import FigmaFileIcon from "public/attachment/figma-icon.png"; + +export const FigmaIcon: React.FC = ({ width , height }) => ( + FigmaFileIcon +); diff --git a/apps/app/components/icons/html-file-icon.tsx b/apps/app/components/icons/html-file-icon.tsx new file mode 100644 index 000000000..9de4080b0 --- /dev/null +++ b/apps/app/components/icons/html-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import HtmlFileIcon from "public/attachment/html-icon.png"; + +export const HtmlIcon: React.FC = ({ width, height }) => ( + HtmlFileIcon +); diff --git a/apps/app/components/icons/img-file-icon.tsx b/apps/app/components/icons/img-file-icon.tsx new file mode 100644 index 000000000..1333957c1 --- /dev/null +++ b/apps/app/components/icons/img-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import ImgFileIcon from "public/attachment/img-icon.png"; + +export const ImgIcon: React.FC = ({ width , height }) => ( + ImgFileIcon +); diff --git a/apps/app/components/icons/index.ts b/apps/app/components/icons/index.ts index a4f95eb4e..cd83cb5c6 100644 --- a/apps/app/components/icons/index.ts +++ b/apps/app/components/icons/index.ts @@ -56,4 +56,17 @@ export * from "./users"; export * from "./import-layers"; export * from "./check"; export * from "./water-drop-icon"; -export * from "./transfer-icon"; \ No newline at end of file +export * from "./transfer-icon"; +export * from "./pdf-file-icon"; +export * from "./csv-file-icon"; +export * from "./sheet-file-icon"; +export * from "./doc-file-icon"; +export * from "./html-file-icon"; +export * from "./css-file-icon"; +export * from "./js-file-icon"; +export * from "./figma-file-icon"; +export * from "./img-file-icon"; +export * from "./png-file-icon"; +export * from "./jpg-file-icon"; +export * from "./svg-file-icon"; +export * from "./txt-file-icon"; \ No newline at end of file diff --git a/apps/app/components/icons/jpg-file-icon.tsx b/apps/app/components/icons/jpg-file-icon.tsx new file mode 100644 index 000000000..e88f0e47f --- /dev/null +++ b/apps/app/components/icons/jpg-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import JpgFileIcon from "public/attachment/jpg-icon.png"; + +export const JpgIcon: React.FC = ({ width, height }) => ( + JpgFileIcon +); diff --git a/apps/app/components/icons/js-file-icon.tsx b/apps/app/components/icons/js-file-icon.tsx new file mode 100644 index 000000000..9f879ba6b --- /dev/null +++ b/apps/app/components/icons/js-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import JsFileIcon from "public/attachment/js-icon.png"; + +export const JavaScriptIcon: React.FC = ({ width, height }) => ( + JsFileIcon +); diff --git a/apps/app/components/icons/pdf-file-icon.tsx b/apps/app/components/icons/pdf-file-icon.tsx new file mode 100644 index 000000000..5467f0812 --- /dev/null +++ b/apps/app/components/icons/pdf-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import PDFFileIcon from "public/attachment/pdf-icon.png"; + +export const PdfIcon: React.FC = ({ width , height }) => ( + PDFFileIcon +); diff --git a/apps/app/components/icons/png-file-icon.tsx b/apps/app/components/icons/png-file-icon.tsx new file mode 100644 index 000000000..21b5dc116 --- /dev/null +++ b/apps/app/components/icons/png-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import PngFileIcon from "public/attachment/png-icon.png"; + +export const PngIcon: React.FC = ({ width, height }) => ( + PngFileIcon +); diff --git a/apps/app/components/icons/sheet-file-icon.tsx b/apps/app/components/icons/sheet-file-icon.tsx new file mode 100644 index 000000000..9e57ce71c --- /dev/null +++ b/apps/app/components/icons/sheet-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import SheetFileIcon from "public/attachment/excel-icon.png"; + +export const SheetIcon: React.FC = ({ width, height }) => ( + SheetFileIcon +); diff --git a/apps/app/components/icons/svg-file-icon.tsx b/apps/app/components/icons/svg-file-icon.tsx new file mode 100644 index 000000000..d04ae3167 --- /dev/null +++ b/apps/app/components/icons/svg-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import SvgFileIcon from "public/attachment/svg-icon.png"; + +export const SvgIcon: React.FC = ({ width, height }) => ( + SvgFileIcon +); diff --git a/apps/app/components/icons/txt-file-icon.tsx b/apps/app/components/icons/txt-file-icon.tsx new file mode 100644 index 000000000..f0150a4cd --- /dev/null +++ b/apps/app/components/icons/txt-file-icon.tsx @@ -0,0 +1,9 @@ +import React from "react"; +import Image from "next/image"; + +import type { Props } from "./types"; +import TxtFileIcon from "public/attachment/txt-icon.png"; + +export const TxtIcon: React.FC = ({ width, height }) => ( + TxtFileIcon +); diff --git a/apps/app/components/issues/attachment-upload.tsx b/apps/app/components/issues/attachment-upload.tsx new file mode 100644 index 000000000..487981f7e --- /dev/null +++ b/apps/app/components/issues/attachment-upload.tsx @@ -0,0 +1,100 @@ +import React, { useCallback, useState } from "react"; + +import { useRouter } from "next/router"; + +import { mutate } from "swr"; + +// react-dropzone +import { useDropzone } from "react-dropzone"; +// toast +import useToast from "hooks/use-toast"; +// fetch key +import { ISSUE_ATTACHMENTS } from "constants/fetch-keys"; +// services +import issuesService from "services/issues.service"; +// type +import { IIssueAttachment } from "types"; + +const maxFileSize = 5 * 1024 * 1024; // 5 MB + +export const IssueAttachmentUpload = () => { + const [isLoading, setIsLoading] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId, issueId } = router.query; + + const { setToastAlert } = useToast(); + + const onDrop = useCallback((acceptedFiles: File[]) => { + setIsLoading(true); + if (!acceptedFiles[0] || !workspaceSlug) return; + + const formData = new FormData(); + formData.append("asset", acceptedFiles[0]); + formData.append( + "attributes", + JSON.stringify({ + name: acceptedFiles[0].name, + size: acceptedFiles[0].size, + }) + ); + + issuesService + .uploadIssueAttachment( + workspaceSlug as string, + projectId as string, + issueId as string, + formData + ) + .then((res) => { + mutate(ISSUE_ATTACHMENTS(issueId as string)); + setToastAlert({ + type: "success", + title: "Success!", + message: "File added successfully.", + }); + setIsLoading(false); + }) + .catch((err) => { + setIsLoading(false); + setToastAlert({ + type: "error", + title: "error!", + message: "Something went wrong. please check file type & size (max 5 MB)", + }); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const { getRootProps, getInputProps, isDragActive, isDragReject, fileRejections } = useDropzone({ + onDrop, + maxSize: maxFileSize, + multiple: false, + }); + + const fileError = + fileRejections.length > 0 + ? `Invalid file type or size (max ${maxFileSize / 1024 / 1024} MB)` + : null; + + return ( +
+ + + {isDragActive ? ( +

Drop here...

+ ) : isLoading ? ( +

Uploading....

+ ) : ( +

Drag & Drop or Click to add new file

+ )} + {fileError &&

{fileError}

} +
+
+ ); +}; diff --git a/apps/app/components/issues/attachments.tsx b/apps/app/components/issues/attachments.tsx new file mode 100644 index 000000000..3408e5af0 --- /dev/null +++ b/apps/app/components/issues/attachments.tsx @@ -0,0 +1,111 @@ +import { useState } from "react"; + +import Link from "next/link"; +import { useRouter } from "next/router"; + +import useSWR from "swr"; + +// ui +import { Tooltip } from "components/ui"; +import { DeleteAttachmentModal } from "./delete-attachment-modal"; +// icons +import { XMarkIcon } from "@heroicons/react/24/outline"; +import { ExclamationIcon, getFileIcon } from "components/icons"; +// services +import issuesService from "services/issues.service"; +import projectService from "services/project.service"; +// fetch-key +import { ISSUE_ATTACHMENTS, PROJECT_MEMBERS } from "constants/fetch-keys"; +// helper +import { truncateText } from "helpers/string.helper"; +import { renderLongDateFormat } from "helpers/date-time.helper"; +import { convertBytesToSize, getFileExtension, getFileName } from "helpers/attachment.helper"; +// type +import { IIssueAttachment } from "types"; + +export const IssueAttachments = () => { + const [deleteAttachment, setDeleteAttachment] = useState(null); + const [attachmentDeleteModal, setAttachmentDeleteModal] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId, issueId } = router.query; + + const { data: attachments } = useSWR( + workspaceSlug && projectId && issueId ? ISSUE_ATTACHMENTS(issueId as string) : null, + workspaceSlug && projectId && issueId + ? () => + issuesService.getIssueAttachment( + workspaceSlug as string, + projectId as string, + issueId as string + ) + : null + ); + + const { data: people } = useSWR( + workspaceSlug && projectId ? PROJECT_MEMBERS(projectId as string) : null, + workspaceSlug && projectId + ? () => projectService.projectMembers(workspaceSlug as string, projectId as string) + : null + ); + + return ( + <> + + {attachments && + attachments.length > 0 && + attachments.map((file) => ( + + ))} + + ); +}; diff --git a/apps/app/components/issues/delete-attachment-modal.tsx b/apps/app/components/issues/delete-attachment-modal.tsx new file mode 100644 index 000000000..92906244a --- /dev/null +++ b/apps/app/components/issues/delete-attachment-modal.tsx @@ -0,0 +1,144 @@ +import React, { useState } from "react"; + +import { useRouter } from "next/router"; + +import { mutate } from "swr"; + +// headless ui +import { Dialog, Transition } from "@headlessui/react"; +// services +import issuesService from "services/issues.service"; +// hooks +import useToast from "hooks/use-toast"; +// ui +import { SecondaryButton, DangerButton } from "components/ui"; +// icons +import { ExclamationTriangleIcon } from "@heroicons/react/24/outline"; +// helper +import { getFileName } from "helpers/attachment.helper"; +// types +import type { IIssueAttachment } from "types"; +// fetch-keys +import { ISSUE_ATTACHMENTS } from "constants/fetch-keys"; + +type Props = { + isOpen: boolean; + setIsOpen: React.Dispatch>; + data: IIssueAttachment | null; +}; + +export const DeleteAttachmentModal: React.FC = ({ isOpen, setIsOpen, data }) => { + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + const router = useRouter(); + const { workspaceSlug, projectId, issueId } = router.query; + + const { setToastAlert } = useToast(); + + const handleClose = () => { + setIsOpen(false); + setIsDeleteLoading(false); + }; + + const handleDeletion = async (assetId: string) => { + setIsDeleteLoading(true); + + if (!workspaceSlug || !projectId || !data) return; + + mutate(ISSUE_ATTACHMENTS(issueId as string)); + + await issuesService + .deleteIssueAttachment( + workspaceSlug as string, + projectId as string, + issueId as string, + assetId as string + ) + .then((res) => { + mutate(ISSUE_ATTACHMENTS(issueId as string)); + setToastAlert({ + type: "success", + title: "Success!", + message: "File removed successfully.", + }); + handleClose(); + }) + .catch((err) => { + setToastAlert({ + type: "error", + title: "error!", + message: "Something went wrong please try again.", + }); + setIsDeleteLoading(false); + }); + }; + + return ( + data && ( + + + +
+ + +
+
+ + +
+
+
+
+
+ + Delete Attachment + +
+

+ Are you sure you want to delete attachment-{" "} + {getFileName(data.attributes.name)}? + This attachment will be permanently removed. This action cannot be + undone. +

+
+
+
+
+
+ Cancel + handleDeletion(data.id)} loading={isDeleteLoading}> + {isDeleteLoading ? "Deleting..." : "Delete"} + +
+
+
+
+
+
+
+ ) + ); +}; diff --git a/apps/app/components/issues/index.ts b/apps/app/components/issues/index.ts index 18253c6d3..1b6c63b4a 100644 --- a/apps/app/components/issues/index.ts +++ b/apps/app/components/issues/index.ts @@ -9,3 +9,6 @@ export * from "./my-issues-list-item"; export * from "./parent-issues-list-modal"; export * from "./sidebar"; export * from "./sub-issues-list"; +export * from "./attachment-upload"; +export * from "./attachments"; +export * from "./delete-attachment-modal"; diff --git a/apps/app/constants/fetch-keys.ts b/apps/app/constants/fetch-keys.ts index f94ca2bff..25ce6096d 100644 --- a/apps/app/constants/fetch-keys.ts +++ b/apps/app/constants/fetch-keys.ts @@ -115,6 +115,7 @@ export const VIEW_DETAILS = (viewId: string) => `VIEW_DETAILS_${viewId.toUpperCa // Issues export const ISSUE_DETAILS = (issueId: string) => `ISSUE_DETAILS_${issueId.toUpperCase()}`; export const SUB_ISSUES = (issueId: string) => `SUB_ISSUES_${issueId.toUpperCase()}`; +export const ISSUE_ATTACHMENTS = (issueId: string) => `ISSUE_ATTACHMENTS_${issueId.toUpperCase()}`; // integrations export const APP_INTEGRATIONS = "APP_INTEGRATIONS"; diff --git a/apps/app/helpers/attachment.helper.ts b/apps/app/helpers/attachment.helper.ts new file mode 100644 index 000000000..24cfb2c49 --- /dev/null +++ b/apps/app/helpers/attachment.helper.ts @@ -0,0 +1,22 @@ +export const getFileExtension = (filename: string) => + filename.slice(((filename.lastIndexOf(".") - 1) >>> 0) + 2); + +export const getFileName = (fileName: string) => { + const dotIndex = fileName.lastIndexOf("."); + + const nameWithoutExtension = fileName.substring(0, dotIndex); + + return nameWithoutExtension; +}; + +export const convertBytesToSize = (bytes: number) => { + let size; + + if (bytes < 1024 * 1024) { + size = Math.round(bytes / 1024) + " KB"; + } else { + size = Math.round(bytes / (1024 * 1024)) + " MB"; + } + + return size; +}; diff --git a/apps/app/helpers/date-time.helper.ts b/apps/app/helpers/date-time.helper.ts index 397e87c9d..65789fd2e 100644 --- a/apps/app/helpers/date-time.helper.ts +++ b/apps/app/helpers/date-time.helper.ts @@ -89,7 +89,10 @@ export const timeAgo = (time: any) => { return time; }; -export const getDateRangeStatus = (startDate: string | null | undefined, endDate: string | null | undefined) => { +export const getDateRangeStatus = ( + startDate: string | null | undefined, + endDate: string | null | undefined +) => { if (!startDate || !endDate) return "draft"; const today = renderDateFormat(new Date()); @@ -173,3 +176,37 @@ export const renderShortTime = (date: string | Date) => { export const isDateRangeValid = (startDate: string, endDate: string) => new Date(startDate) < new Date(endDate); + +export const renderLongDateFormat = (dateString: string) => { + const date = new Date(dateString); + const day = date.getDate(); + const year = date.getFullYear(); + const monthNames = [ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", + ]; + const monthIndex = date.getMonth(); + const monthName = monthNames[monthIndex]; + const suffixes = ["st", "nd", "rd", "th"]; + let suffix = ""; + if (day === 1 || day === 21 || day === 31) { + suffix = suffixes[0]; + } else if (day === 2 || day === 22) { + suffix = suffixes[1]; + } else if (day === 3 || day === 23) { + suffix = suffixes[2]; + } else { + suffix = suffixes[3]; + } + return `${day}${suffix} ${monthName} ${year}`; +}; diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx index 6a271428d..cf582439a 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx @@ -20,6 +20,8 @@ import { IssueDetailsSidebar, IssueActivitySection, AddComment, + IssueAttachmentUpload, + IssueAttachments, } from "components/issues"; // ui import { Loader, CustomMenu } from "components/ui"; @@ -198,6 +200,13 @@ const IssueDetailsPage: NextPage = (props) => { +
+

Attachments

+
+ + +
+

Comments/Activity

diff --git a/apps/app/public/attachment/css-icon.png b/apps/app/public/attachment/css-icon.png new file mode 100644 index 000000000..cfb502d97 Binary files /dev/null and b/apps/app/public/attachment/css-icon.png differ diff --git a/apps/app/public/attachment/csv-icon.png b/apps/app/public/attachment/csv-icon.png new file mode 100644 index 000000000..39d0ee713 Binary files /dev/null and b/apps/app/public/attachment/csv-icon.png differ diff --git a/apps/app/public/attachment/doc-icon.png b/apps/app/public/attachment/doc-icon.png new file mode 100644 index 000000000..d1433721b Binary files /dev/null and b/apps/app/public/attachment/doc-icon.png differ diff --git a/apps/app/public/attachment/excel-icon.png b/apps/app/public/attachment/excel-icon.png new file mode 100644 index 000000000..b3a1b851e Binary files /dev/null and b/apps/app/public/attachment/excel-icon.png differ diff --git a/apps/app/public/attachment/figma-icon.png b/apps/app/public/attachment/figma-icon.png new file mode 100644 index 000000000..b4a1b63b3 Binary files /dev/null and b/apps/app/public/attachment/figma-icon.png differ diff --git a/apps/app/public/attachment/html-icon.png b/apps/app/public/attachment/html-icon.png new file mode 100644 index 000000000..b6259a2be Binary files /dev/null and b/apps/app/public/attachment/html-icon.png differ diff --git a/apps/app/public/attachment/img-icon.png b/apps/app/public/attachment/img-icon.png new file mode 100644 index 000000000..6c5b8fce0 Binary files /dev/null and b/apps/app/public/attachment/img-icon.png differ diff --git a/apps/app/public/attachment/jpg-icon.png b/apps/app/public/attachment/jpg-icon.png new file mode 100644 index 000000000..dfd2c9fde Binary files /dev/null and b/apps/app/public/attachment/jpg-icon.png differ diff --git a/apps/app/public/attachment/js-icon.png b/apps/app/public/attachment/js-icon.png new file mode 100644 index 000000000..66aacdaff Binary files /dev/null and b/apps/app/public/attachment/js-icon.png differ diff --git a/apps/app/public/attachment/pdf-icon.png b/apps/app/public/attachment/pdf-icon.png new file mode 100644 index 000000000..21c42d73f Binary files /dev/null and b/apps/app/public/attachment/pdf-icon.png differ diff --git a/apps/app/public/attachment/png-icon.png b/apps/app/public/attachment/png-icon.png new file mode 100644 index 000000000..f04207daa Binary files /dev/null and b/apps/app/public/attachment/png-icon.png differ diff --git a/apps/app/public/attachment/svg-icon.png b/apps/app/public/attachment/svg-icon.png new file mode 100644 index 000000000..856f94fbe Binary files /dev/null and b/apps/app/public/attachment/svg-icon.png differ diff --git a/apps/app/public/attachment/txt-icon.png b/apps/app/public/attachment/txt-icon.png new file mode 100644 index 000000000..1c1babf9c Binary files /dev/null and b/apps/app/public/attachment/txt-icon.png differ diff --git a/apps/app/services/issues.service.ts b/apps/app/services/issues.service.ts index 3c9b28bce..c5a23b232 100644 --- a/apps/app/services/issues.service.ts +++ b/apps/app/services/issues.service.ts @@ -440,6 +440,51 @@ class ProjectIssuesServices extends APIService { throw error?.response?.data; }); } + + async uploadIssueAttachment( + workspaceSlug: string, + projectId: string, + issueId: string, + file: FormData + ): Promise { + return this.mediaUpload( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-attachments/`, + file + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async getIssueAttachment( + workspaceSlug: string, + projectId: string, + issueId: string + ): Promise { + return this.get( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-attachments/` + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } + + async deleteIssueAttachment( + workspaceSlug: string, + projectId: string, + issueId: string, + assetId: string + ): Promise { + return this.delete( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/issues/${issueId}/issue-attachments/${assetId}/` + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } } export default new ProjectIssuesServices(); diff --git a/apps/app/types/issues.d.ts b/apps/app/types/issues.d.ts index e911cd5ef..d800b42ea 100644 --- a/apps/app/types/issues.d.ts +++ b/apps/app/types/issues.d.ts @@ -256,3 +256,19 @@ export interface IIssueViewOptions { filters: IIssueFilterOptions; target_date: string; } + +export interface IIssueAttachment { + asset: string; + attributes: { + name: string; + size: number; + }; + created_at: string; + created_by: string; + id: string; + issue: string; + project: string; + updated_at: string; + updated_by: string; + workspace: string; +} \ No newline at end of file