import { useEffect, useState } from "react"; import { useRouter } from "next/router"; import Link from "next/link"; import dynamic from "next/dynamic"; import { mutate } from "swr"; // react-hook-form import { Controller, useForm } from "react-hook-form"; // services import pagesService from "services/pages.service"; import issuesService from "services/issues.service"; // hooks import useToast from "hooks/use-toast"; // components import { CreateUpdateIssueModal } from "components/issues"; import { GptAssistantModal } from "components/core"; // ui import { CustomMenu, Input, Loader, TextArea } from "components/ui"; // icons import { LayerDiagonalIcon } from "components/icons"; import { ArrowPathIcon } from "@heroicons/react/20/solid"; import { BoltIcon, CheckIcon, CursorArrowRaysIcon, SparklesIcon, } from "@heroicons/react/24/outline"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; // types import { IIssue, IPageBlock, IProject } from "types"; // fetch-keys import { PAGE_BLOCKS_LIST } from "constants/fetch-keys"; type Props = { block: IPageBlock; projectDetails: IProject | undefined; }; const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { ssr: false, loading: () => ( ), }); export const SinglePageBlock: React.FC = ({ block, projectDetails }) => { const [createUpdateIssueModal, setCreateUpdateIssueModal] = useState(false); const [isSyncing, setIsSyncing] = useState(false); const [gptAssistantModal, setGptAssistantModal] = useState(false); const router = useRouter(); const { workspaceSlug, projectId, pageId } = router.query; const { setToastAlert } = useToast(); const { handleSubmit, watch, reset, setValue, control } = useForm({ defaultValues: { name: "", description: {}, description_html: "

", }, }); const updatePageBlock = async (formData: Partial) => { if (!workspaceSlug || !projectId || !pageId) return; if (!formData.name || formData.name.length === 0 || formData.name === "") return; if (block.issue && block.sync) setIsSyncing(true); mutate( PAGE_BLOCKS_LIST(pageId as string), (prevData) => prevData?.map((p) => { if (p.id === block.id) return { ...p, ...formData }; return p; }), false ); await pagesService .patchPageBlock(workspaceSlug as string, projectId as string, pageId as string, block.id, { name: formData.name, description: formData.description, description_html: formData.description_html, }) .then((res) => { mutate(PAGE_BLOCKS_LIST(pageId as string)); if (block.issue && block.sync) issuesService .patchIssue(workspaceSlug as string, projectId as string, block.issue, { name: res.name, description: res.description, description_html: res.description_html, }) .finally(() => setIsSyncing(false)); }); }; const pushBlockIntoIssues = async () => { if (!workspaceSlug || !projectId || !pageId) return; await pagesService .convertPageBlockToIssue( workspaceSlug as string, projectId as string, pageId as string, block.id ) .then((res: IIssue) => { mutate( PAGE_BLOCKS_LIST(pageId as string), (prevData) => (prevData ?? []).map((p) => { if (p.id === block.id) return { ...p, issue: res.id, issue_detail: res }; return p; }), false ); setToastAlert({ type: "success", title: "Success!", message: "Page block converted to issue successfully.", }); }) .catch((res) => { setToastAlert({ type: "error", title: "Error!", message: "Page block could not be converted to issue. Please try again.", }); }); }; const editAndPushBlockIntoIssues = async () => { setCreateUpdateIssueModal(true); }; const deletePageBlock = async () => { if (!workspaceSlug || !projectId || !pageId) return; mutate( PAGE_BLOCKS_LIST(pageId as string), (prevData) => (prevData ?? []).filter((p) => p.id !== block.id), false ); await pagesService .deletePageBlock(workspaceSlug as string, projectId as string, pageId as string, block.id) .catch(() => { setToastAlert({ type: "error", title: "Error!", message: "Page could not be deleted. Please try again.", }); }); }; const handleAiAssistance = async (response: string) => { if (!workspaceSlug || !projectId) return; setValue("description", {}); setValue("description_html", `${watch("description_html")}

${response}

`); handleSubmit(updatePageBlock)() .then(() => { setToastAlert({ type: "success", title: "Success!", message: "Block description updated successfully.", }); }) .catch(() => { setToastAlert({ type: "error", title: "Error!", message: "Block description could not be updated. Please try again.", }); }); }; const handleBlockSync = () => { if (!workspaceSlug || !projectId || !pageId) return; mutate( PAGE_BLOCKS_LIST(pageId as string), (prevData) => (prevData ?? []).map((p) => { if (p.id === block.id) return { ...p, sync: !block.sync }; return p; }), false ); pagesService.patchPageBlock( workspaceSlug as string, projectId as string, pageId as string, block.id, { sync: !block.sync, } ); }; const handleCopyText = () => { const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; copyTextToClipboard( `${originURL}/${workspaceSlug}/projects/${projectId}/issues/${block.issue}` ).then(() => { setToastAlert({ type: "success", title: "Link Copied!", message: "Issue link copied to clipboard.", }); }); }; useEffect(() => { if (!block) return; reset({ ...block }); }, [reset, block]); return (
setCreateUpdateIssueModal(false)} prePopulateData={{ name: watch("name"), description: watch("description"), description_html: watch("description_html"), }} />
setValue("name", e.target.value)} required={true} className="min-h-10 block w-full resize-none overflow-hidden border-none bg-transparent py-1 text-base font-medium ring-0 focus:ring-1 focus:ring-gray-200" role="textbox" />
{block.issue && block.sync && (
{isSyncing ? ( ) : ( )} {isSyncing ? "Syncing..." : "Synced"}
)} {block.issue && ( {projectDetails?.identifier}-{block.issue_detail?.sequence_id} )} } noBorder noChevron> {block.issue ? ( <> <>Turn sync {block.sync ? "off" : "on"} Copy issue link ) : ( <> Push into issues Edit and push into issues )} Delete block
( setValue("description", jsonValue)} onHTMLChange={(htmlValue) => setValue("description_html", htmlValue)} placeholder="Block description..." customClassName="border border-transparent" noBorder borderOnFocus /> )} /> setGptAssistantModal(false)} inset="top-2 left-0" content={block.description_stripped} htmlContent={block.description_html} onResponse={handleAiAssistance} />
); };