From 065a4a3cf7ab071e9e4df37c7bfe018dc25a080f Mon Sep 17 00:00:00 2001 From: dakshesh14 Date: Tue, 5 Sep 2023 14:42:34 +0530 Subject: [PATCH 1/2] feat: issue detail for web view --- web/components/web-view/index.ts | 7 + web/components/web-view/issue-attachments.tsx | 150 ++++++++++++ .../web-view/issue-web-view-form.tsx | 164 ++++++++++++++ web/components/web-view/label.tsx | 7 + web/components/web-view/select-priority.tsx | 83 +++++++ web/components/web-view/select-state.tsx | 89 ++++++++ web/components/web-view/sub-issues.tsx | 108 +++++++++ web/components/web-view/web-view-modal.tsx | 108 +++++++++ web/constants/fetch-keys.ts | 6 +- .../projects/[projectId]/issues/[issueId].tsx | 214 ++++++++++++++++++ 10 files changed, 935 insertions(+), 1 deletion(-) create mode 100644 web/components/web-view/index.ts create mode 100644 web/components/web-view/issue-attachments.tsx create mode 100644 web/components/web-view/issue-web-view-form.tsx create mode 100644 web/components/web-view/label.tsx create mode 100644 web/components/web-view/select-priority.tsx create mode 100644 web/components/web-view/select-state.tsx create mode 100644 web/components/web-view/sub-issues.tsx create mode 100644 web/components/web-view/web-view-modal.tsx create mode 100644 web/pages/m/[workspaceSlug]/projects/[projectId]/issues/[issueId].tsx diff --git a/web/components/web-view/index.ts b/web/components/web-view/index.ts new file mode 100644 index 000000000..48178dfc6 --- /dev/null +++ b/web/components/web-view/index.ts @@ -0,0 +1,7 @@ +export * from "./web-view-modal"; +export * from "./select-state"; +export * from "./select-priority"; +export * from "./issue-web-view-form"; +export * from "./label"; +export * from "./sub-issues"; +export * from "./issue-attachments"; diff --git a/web/components/web-view/issue-attachments.tsx b/web/components/web-view/issue-attachments.tsx new file mode 100644 index 000000000..e2636fc53 --- /dev/null +++ b/web/components/web-view/issue-attachments.tsx @@ -0,0 +1,150 @@ +// react +import React, { useState, useCallback } from "react"; + +// next +import Link from "next/link"; +import { useRouter } from "next/router"; + +// swr +import useSWR, { mutate } from "swr"; + +// services +import issuesService from "services/issues.service"; + +// react dropzone +import { useDropzone } from "react-dropzone"; + +// fetch key +import { ISSUE_ATTACHMENTS, PROJECT_ISSUES_ACTIVITY } from "constants/fetch-keys"; + +// hooks +import useToast from "hooks/use-toast"; + +// icons +import { ChevronRightIcon } from "@heroicons/react/24/outline"; + +// components +import { Label, WebViewModal } from "components/web-view"; + +// types +import type { IIssueAttachment } from "types"; + +export const IssueAttachments: React.FC = () => { + const router = useRouter(); + const { workspaceSlug, projectId, issueId } = router.query; + + const [isOpen, setIsOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const { setToastAlert } = useToast(); + + const onDrop = useCallback( + (acceptedFiles: File[]) => { + 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, + }) + ); + setIsLoading(true); + + issuesService + .uploadIssueAttachment( + workspaceSlug as string, + projectId as string, + issueId as string, + formData + ) + .then((res) => { + mutate( + ISSUE_ATTACHMENTS(issueId as string), + (prevData) => [res, ...(prevData ?? [])], + false + ); + mutate(PROJECT_ISSUES_ACTIVITY(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)", + }); + }); + }, + [issueId, projectId, setToastAlert, workspaceSlug] + ); + + const { getRootProps } = useDropzone({ + onDrop, + maxSize: 5 * 1024 * 1024, + }); + + const { data: attachments } = useSWR( + workspaceSlug && projectId && issueId ? ISSUE_ATTACHMENTS(issueId as string) : null, + workspaceSlug && projectId && issueId + ? () => + issuesService.getIssueAttachment( + workspaceSlug.toString(), + projectId.toString(), + issueId.toString() + ) + : null + ); + + return ( +
+ setIsOpen(false)} modalTitle="Insert file"> +
+
+ {isLoading ? ( +

Uploading...

+ ) : ( + <> +

Upload

+ + + )} +
+
+
+ + +
+ {attachments?.map((attachment) => ( + + ))} + +
+
+ ); +}; diff --git a/web/components/web-view/issue-web-view-form.tsx b/web/components/web-view/issue-web-view-form.tsx new file mode 100644 index 000000000..863464764 --- /dev/null +++ b/web/components/web-view/issue-web-view-form.tsx @@ -0,0 +1,164 @@ +// react +import React, { useCallback, useEffect, useState } from "react"; + +// next +import { useRouter } from "next/router"; + +// react hook forms +import { Controller } from "react-hook-form"; + +// hooks + +import { useDebouncedCallback } from "use-debounce"; +import useReloadConfirmations from "hooks/use-reload-confirmation"; + +// ui +import { TextArea } from "components/ui"; + +// components +import { TipTapEditor } from "components/tiptap"; +import { Label } from "components/web-view"; + +// types +import type { IIssue } from "types"; + +type Props = { + isAllowed: boolean; + issueDetails: IIssue; + submitChanges: (data: Partial) => Promise; + register: any; + control: any; + watch: any; + handleSubmit: any; +}; + +export const IssueWebViewForm: React.FC = (props) => { + const { isAllowed, issueDetails, submitChanges, register, control, watch, handleSubmit } = props; + + const router = useRouter(); + const { workspaceSlug } = router.query; + + const [characterLimit, setCharacterLimit] = useState(false); + const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved"); + + const { setShowAlert } = useReloadConfirmations(); + + useEffect(() => { + if (isSubmitting === "submitted") { + setShowAlert(false); + setTimeout(async () => { + setIsSubmitting("saved"); + }, 2000); + } else if (isSubmitting === "submitting") { + setShowAlert(true); + } + }, [isSubmitting, setShowAlert]); + + const debouncedTitleSave = useDebouncedCallback(async () => { + setTimeout(async () => { + handleSubmit(handleDescriptionFormSubmit)().finally(() => setIsSubmitting("submitted")); + }, 500); + }, 1000); + + const handleDescriptionFormSubmit = useCallback( + async (formData: Partial) => { + if (!formData?.name || formData?.name.length === 0 || formData?.name.length > 255) return; + + await submitChanges({ + name: formData.name ?? "", + description_html: formData.description_html ?? "

", + }); + }, + [submitChanges] + ); + + return ( + <> +
+ +
+ {isAllowed ? ( +