fix: v3 issues for the layouts (#2941)
* fix drag n drop exception error * fix peek overlay close buttons * fix project empty state view * fix cycle and module empty state view * add ai options to inbox issue creation * fix inbox filters for viewers * fix inbox filters for viewers for project * disable editing permission for members and viewers * define accurate types for drag and drop
This commit is contained in:
parent
f7fa4d8b65
commit
90ca459b4a
17 changed files with 290 additions and 147 deletions
|
|
@ -16,6 +16,10 @@ import { Button, Input, ToggleSwitch } from "@plane/ui";
|
|||
// types
|
||||
import { IIssue } from "types";
|
||||
import useEditorSuggestions from "hooks/use-editor-suggestions";
|
||||
import { GptAssistantModal } from "components/core";
|
||||
import { Sparkle } from "lucide-react";
|
||||
import useToast from "hooks/use-toast";
|
||||
import { AIService } from "services/ai.service";
|
||||
|
||||
type Props = {
|
||||
isOpen: boolean;
|
||||
|
|
@ -31,6 +35,7 @@ const defaultValues: Partial<IIssue> = {
|
|||
};
|
||||
|
||||
// services
|
||||
const aiService = new AIService();
|
||||
const fileService = new FileService();
|
||||
|
||||
export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -38,21 +43,35 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
|||
|
||||
// states
|
||||
const [createMore, setCreateMore] = useState(false);
|
||||
const [gptAssistantModal, setGptAssistantModal] = useState(false);
|
||||
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
|
||||
|
||||
const editorRef = useRef<any>(null);
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
|
||||
const router = useRouter();
|
||||
const { workspaceSlug, projectId, inboxId } = router.query;
|
||||
const { workspaceSlug, projectId, inboxId } = router.query as {
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
inboxId: string;
|
||||
};
|
||||
|
||||
const { inboxIssueDetails: inboxIssueDetailsStore, trackEvent: { postHogEventTracker } } = useMobxStore();
|
||||
const {
|
||||
inboxIssueDetails: inboxIssueDetailsStore,
|
||||
trackEvent: { postHogEventTracker },
|
||||
appConfig: { envConfig },
|
||||
} = useMobxStore();
|
||||
|
||||
const {
|
||||
control,
|
||||
formState: { errors, isSubmitting },
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
getValues,
|
||||
setValue,
|
||||
} = useForm({ defaultValues });
|
||||
|
||||
const handleClose = () => {
|
||||
|
|
@ -60,6 +79,8 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
|||
reset(defaultValues);
|
||||
};
|
||||
|
||||
const issueName = watch("name");
|
||||
|
||||
const handleFormSubmit = async (formData: Partial<IIssue>) => {
|
||||
if (!workspaceSlug || !projectId || !inboxId) return;
|
||||
|
||||
|
|
@ -70,24 +91,66 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
|||
router.push(`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}`);
|
||||
handleClose();
|
||||
} else reset(defaultValues);
|
||||
postHogEventTracker(
|
||||
"ISSUE_CREATE",
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
}).catch((error) => {
|
||||
postHogEventTracker("ISSUE_CREATE", {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
postHogEventTracker(
|
||||
"ISSUE_CREATE",
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
postHogEventTracker("ISSUE_CREATE", {
|
||||
state: "FAILED",
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleAiAssistance = async (response: string) => {
|
||||
if (!workspaceSlug || !projectId) return;
|
||||
|
||||
setValue("description", {});
|
||||
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
|
||||
editorRef.current?.setEditorValue(`${watch("description_html")}`);
|
||||
};
|
||||
|
||||
const handleAutoGenerateDescription = async () => {
|
||||
if (!workspaceSlug || !projectId || !issueName) return;
|
||||
|
||||
setIAmFeelingLucky(true);
|
||||
|
||||
aiService
|
||||
.createGptTask(workspaceSlug as string, projectId as string, {
|
||||
prompt: issueName,
|
||||
task: "Generate a proper description for this issue.",
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.response === "")
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message:
|
||||
"Issue title isn't informative enough to generate the description. Please try with a different title.",
|
||||
});
|
||||
else handleAiAssistance(res.response_html);
|
||||
})
|
||||
.catch((err) => {
|
||||
const error = err?.data?.error;
|
||||
|
||||
if (err.status === 429)
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: error || "You have reached the maximum number of requests of 50 requests per month per user.",
|
||||
});
|
||||
else
|
||||
setToastAlert({
|
||||
type: "error",
|
||||
title: "Error!",
|
||||
message: error || "Some error occurred. Please try again.",
|
||||
});
|
||||
})
|
||||
.finally(() => setIAmFeelingLucky(false));
|
||||
};
|
||||
|
||||
return (
|
||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={onClose}>
|
||||
|
|
@ -146,7 +209,35 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
|||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="relative">
|
||||
<div className="flex justify-end">
|
||||
{issueName && issueName !== "" && (
|
||||
<button
|
||||
type="button"
|
||||
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${
|
||||
iAmFeelingLucky ? "cursor-wait" : ""
|
||||
}`}
|
||||
onClick={handleAutoGenerateDescription}
|
||||
disabled={iAmFeelingLucky}
|
||||
>
|
||||
{iAmFeelingLucky ? (
|
||||
"Generating response..."
|
||||
) : (
|
||||
<>
|
||||
<Sparkle className="h-4 w-4" />I{"'"}m feeling lucky
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90"
|
||||
onClick={() => setGptAssistantModal((prevData) => !prevData)}
|
||||
>
|
||||
<Sparkle className="h-4 w-4" />
|
||||
AI
|
||||
</button>
|
||||
</div>
|
||||
<Controller
|
||||
name="description_html"
|
||||
control={control}
|
||||
|
|
@ -168,6 +259,23 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
|||
/>
|
||||
)}
|
||||
/>
|
||||
{envConfig?.has_openai_configured && (
|
||||
<GptAssistantModal
|
||||
isOpen={gptAssistantModal}
|
||||
handleClose={() => {
|
||||
setGptAssistantModal(false);
|
||||
// this is done so that the title do not reset after gpt popover closed
|
||||
reset(getValues());
|
||||
}}
|
||||
inset="top-2 left-0"
|
||||
content=""
|
||||
htmlContent={watch("description_html")}
|
||||
onResponse={(response) => {
|
||||
handleAiAssistance(response);
|
||||
}}
|
||||
projectId={projectId}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
|
|
@ -188,7 +296,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
|||
onClick={() => setCreateMore((prevData) => !prevData)}
|
||||
>
|
||||
<span className="text-xs">Create more</span>
|
||||
<ToggleSwitch value={createMore} onChange={() => { }} size="md" />
|
||||
<ToggleSwitch value={createMore} onChange={() => {}} size="md" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="neutral-primary" size="sm" onClick={() => handleClose()}>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue