forked from github/plane
feat: editor for issue description (#2038)
This commit is contained in:
parent
099bce87b5
commit
eab1d9329b
192
apps/app/pages/[workspaceSlug]/editor.tsx
Normal file
192
apps/app/pages/[workspaceSlug]/editor.tsx
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import { TipTapEditor } from "components/tiptap";
|
||||||
|
import type { NextPage } from "next";
|
||||||
|
import { useCallback, useEffect, useState } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import issuesService from "services/issues.service";
|
||||||
|
import { ICurrentUserResponse, IIssue } from "types";
|
||||||
|
import useReloadConfirmations from "hooks/use-reload-confirmation";
|
||||||
|
import { Spinner } from "components/ui";
|
||||||
|
import Image404 from "public/404.svg";
|
||||||
|
import DefaultLayout from "layouts/default-layout";
|
||||||
|
import Image from "next/image";
|
||||||
|
import userService from "services/user.service";
|
||||||
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
|
const Editor: NextPage = () => {
|
||||||
|
const [user, setUser] = useState<ICurrentUserResponse | undefined>();
|
||||||
|
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
|
||||||
|
const [isLoading, setIsLoading] = useState("false");
|
||||||
|
const { setShowAlert } = useReloadConfirmations();
|
||||||
|
const [cookies, setCookies] = useState<any>({});
|
||||||
|
const [issueDetail, setIssueDetail] = useState<IIssue | null>(null);
|
||||||
|
const router = useRouter();
|
||||||
|
const { editable } = router.query;
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
watch,
|
||||||
|
setValue,
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
} = useForm<IIssue>({
|
||||||
|
defaultValues: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
|
description_html: "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getCookies = () => {
|
||||||
|
const cookies = document.cookie.split(";");
|
||||||
|
const cookieObj: any = {};
|
||||||
|
cookies.forEach((cookie) => {
|
||||||
|
const cookieArr = cookie.split("=");
|
||||||
|
cookieObj[cookieArr[0].trim()] = cookieArr[1];
|
||||||
|
});
|
||||||
|
|
||||||
|
setCookies(cookieObj);
|
||||||
|
return cookieObj;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIssueDetail = async (cookiesData: any) => {
|
||||||
|
try {
|
||||||
|
setIsLoading("true");
|
||||||
|
const userData = await userService.currentUser();
|
||||||
|
setUser(userData);
|
||||||
|
const issueDetail = await issuesService.retrieve(
|
||||||
|
cookiesData.MOBILE_slug,
|
||||||
|
cookiesData.MOBILE_project_id,
|
||||||
|
cookiesData.MOBILE_issue_id
|
||||||
|
);
|
||||||
|
setIssueDetail(issueDetail);
|
||||||
|
setIsLoading("false");
|
||||||
|
setValue("description_html", issueDetail.description_html);
|
||||||
|
setValue("description", issueDetail.description);
|
||||||
|
} catch (e) {
|
||||||
|
setIsLoading("error");
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
const cookiesData = getCookies();
|
||||||
|
|
||||||
|
getIssueDetail(cookiesData);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSubmitting === "submitted") {
|
||||||
|
setShowAlert(false);
|
||||||
|
setTimeout(async () => {
|
||||||
|
setIsSubmitting("saved");
|
||||||
|
}, 2000);
|
||||||
|
} else if (isSubmitting === "submitting") {
|
||||||
|
setShowAlert(true);
|
||||||
|
}
|
||||||
|
}, [isSubmitting, setShowAlert]);
|
||||||
|
|
||||||
|
const submitChanges = async (
|
||||||
|
formData: Partial<IIssue>,
|
||||||
|
workspaceSlug: string,
|
||||||
|
projectId: string,
|
||||||
|
issueId: string
|
||||||
|
) => {
|
||||||
|
if (!workspaceSlug || !projectId || !issueId) return;
|
||||||
|
|
||||||
|
const payload: Partial<IIssue> = {
|
||||||
|
...formData,
|
||||||
|
};
|
||||||
|
|
||||||
|
delete payload.blocker_issues;
|
||||||
|
delete payload.blocked_issues;
|
||||||
|
await issuesService
|
||||||
|
.patchIssue(workspaceSlug as string, projectId as string, issueId as string, payload, user)
|
||||||
|
.catch((e) => {
|
||||||
|
console.log(e);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDescriptionFormSubmit = useCallback(
|
||||||
|
async (formData: Partial<IIssue>) => {
|
||||||
|
if (!formData) return;
|
||||||
|
|
||||||
|
await submitChanges(
|
||||||
|
{
|
||||||
|
name: issueDetail?.name ?? "",
|
||||||
|
description: formData.description ?? "",
|
||||||
|
description_html: formData.description_html ?? "<p></p>",
|
||||||
|
},
|
||||||
|
cookies.MOBILE_slug,
|
||||||
|
cookies.MOBILE_project_id,
|
||||||
|
cookies.MOBILE_issue_id
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[submitChanges]
|
||||||
|
);
|
||||||
|
|
||||||
|
return isLoading === "error" ? (
|
||||||
|
<ErrorEncountered />
|
||||||
|
) : isLoading === "true" ? (
|
||||||
|
<div className="grid place-items-center h-screen w-full">
|
||||||
|
<Spinner />
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="flex blur-none shadow-none backdrop:backdrop-blur-none justify-center items-center">
|
||||||
|
<Controller
|
||||||
|
name="description_html"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { value, onChange } }) => (
|
||||||
|
<TipTapEditor
|
||||||
|
borderOnFocus={false}
|
||||||
|
value={
|
||||||
|
!value ||
|
||||||
|
value === "" ||
|
||||||
|
(typeof value === "object" && Object.keys(value).length === 0)
|
||||||
|
? watch("description_html")
|
||||||
|
: value
|
||||||
|
}
|
||||||
|
editable={editable === "true"}
|
||||||
|
noBorder={true}
|
||||||
|
workspaceSlug={cookies.MOBILE_slug ?? ""}
|
||||||
|
debouncedUpdatesEnabled={true}
|
||||||
|
setShouldShowAlert={setShowAlert}
|
||||||
|
setIsSubmitting={setIsSubmitting}
|
||||||
|
customClassName="min-h-[150px] shadow-sm"
|
||||||
|
editorContentCustomClassNames="pb-9"
|
||||||
|
onChange={(description: Object, description_html: string) => {
|
||||||
|
setShowAlert(true);
|
||||||
|
setIsSubmitting("submitting");
|
||||||
|
onChange(description_html);
|
||||||
|
setValue("description", description);
|
||||||
|
handleSubmit(handleDescriptionFormSubmit)().finally(() => {
|
||||||
|
setIsSubmitting("submitted");
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className={`absolute right-5 bottom-5 text-xs text-custom-text-200 border border-custom-border-400 rounded-xl w-[6.5rem] py-1 z-10 flex items-center justify-center ${
|
||||||
|
isSubmitting === "saved" ? "fadeOut" : "fadeIn"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{isSubmitting === "submitting" ? "Saving..." : "Saved"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ErrorEncountered: NextPage = () => (
|
||||||
|
<DefaultLayout>
|
||||||
|
<div className="grid max-h-fit place-items-center p-4">
|
||||||
|
<div className="space-y-8 text-center">
|
||||||
|
<div className="relative mx-auto h-40 w-40 lg:h-40 lg:w-40">
|
||||||
|
<Image src={Image404} layout="fill" alt="404- Page not found" />
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="text-lg font-semibold">Oops! Something went wrong.</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DefaultLayout>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Editor;
|
Loading…
Reference in New Issue
Block a user