import React, { useEffect, useState } from "react"; // next imports import { useRouter } from "next/router"; // react-hook-form import { Controller, useForm } from "react-hook-form"; // headless ui import { Dialog, Transition } from "@headlessui/react"; // ui components import { Button, Loader, ToggleSwitch } from "@plane/ui"; import { Check, CircleDot, Globe2 } from "lucide-react"; import { CustomPopover } from "./popover"; // mobx react lite import { observer } from "mobx-react-lite"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; import { IProjectPublishSettings, TProjectPublishViews } from "store/project"; // hooks import useToast from "hooks/use-toast"; import useProjectDetails from "hooks/use-project-details"; import useUser from "hooks/use-user"; type Props = { // user: IUser | undefined; }; type FormData = { id: string | null; comments: boolean; reactions: boolean; votes: boolean; inbox: string | null; views: TProjectPublishViews[]; }; const defaultValues: FormData = { id: null, comments: false, reactions: false, votes: false, inbox: null, views: ["list", "kanban"], }; const viewOptions: { key: TProjectPublishViews; label: string; }[] = [ { key: "list", label: "List" }, { key: "kanban", label: "Kanban" }, // { key: "calendar", label: "Calendar" }, // { key: "gantt", label: "Gantt" }, // { key: "spreadsheet", label: "Spreadsheet" }, ]; export const PublishProjectModal: React.FC = observer(() => { const [isUnpublishing, setIsUnpublishing] = useState(false); const [isUpdateRequired, setIsUpdateRequired] = useState(false); let plane_deploy_url = process.env.NEXT_PUBLIC_DEPLOY_URL; if (typeof window !== "undefined" && !plane_deploy_url) { plane_deploy_url = window.location.protocol + "//" + window.location.host + "/spaces"; } // router const router = useRouter(); const { workspaceSlug } = router.query; // store const store: RootStore = useMobxStore(); const { projectPublish } = store; // hooks const { user } = useUser(); const { mutateProjectDetails } = useProjectDetails(); const { setToastAlert } = useToast(); // form info const { control, formState: { isSubmitting }, getValues, handleSubmit, reset, watch, } = useForm({ defaultValues, }); const handleClose = () => { projectPublish.handleProjectModal(null); setIsUpdateRequired(false); reset({ ...defaultValues }); }; // prefill form with the saved settings if the project is already published useEffect(() => { if (projectPublish.projectPublishSettings && projectPublish.projectPublishSettings !== "not-initialized") { let userBoards: TProjectPublishViews[] = []; if (projectPublish.projectPublishSettings?.views) { const savedViews = projectPublish.projectPublishSettings?.views; if (!savedViews) return; if (savedViews.list) userBoards.push("list"); if (savedViews.kanban) userBoards.push("kanban"); if (savedViews.calendar) userBoards.push("calendar"); if (savedViews.gantt) userBoards.push("gantt"); if (savedViews.spreadsheet) userBoards.push("spreadsheet"); userBoards = userBoards && userBoards.length > 0 ? userBoards : ["list"]; } const updatedData = { id: projectPublish.projectPublishSettings?.id || null, comments: projectPublish.projectPublishSettings?.comments || false, reactions: projectPublish.projectPublishSettings?.reactions || false, votes: projectPublish.projectPublishSettings?.votes || false, inbox: projectPublish.projectPublishSettings?.inbox || null, views: userBoards, }; reset({ ...updatedData }); } }, [reset, projectPublish.projectPublishSettings]); // fetch publish settings useEffect(() => { if (!workspaceSlug) return; if ( projectPublish.projectPublishModal && projectPublish.project_id !== null && projectPublish?.projectPublishSettings === "not-initialized" ) { projectPublish.getProjectSettingsAsync(workspaceSlug.toString(), projectPublish.project_id, null); } }, [workspaceSlug, projectPublish, projectPublish.projectPublishModal]); const handlePublishProject = async (payload: IProjectPublishSettings) => { if (!workspaceSlug || !user) return; const projectId = projectPublish.project_id; return projectPublish .publishProject(workspaceSlug.toString(), projectId?.toString() ?? "", payload, user) .then((res) => { mutateProjectDetails(); handleClose(); if (projectId) window.open(`${plane_deploy_url}/${workspaceSlug}/${projectId}`, "_blank"); return res; }) .catch((err) => err); }; const handleUpdatePublishSettings = async (payload: IProjectPublishSettings) => { if (!workspaceSlug || !user) return; await projectPublish .updateProjectSettingsAsync( workspaceSlug.toString(), projectPublish.project_id?.toString() ?? "", payload.id ?? "", payload, user ) .then((res) => { mutateProjectDetails(); setToastAlert({ type: "success", title: "Success!", message: "Publish settings updated successfully!", }); handleClose(); return res; }) .catch((error) => { console.log("error", error); return error; }); }; const handleUnpublishProject = async (publishId: string) => { if (!workspaceSlug || !publishId) return; setIsUnpublishing(true); projectPublish .unPublishProject(workspaceSlug.toString(), projectPublish.project_id as string, publishId, null) .then((res) => { mutateProjectDetails(); handleClose(); return res; }) .catch((err) => err) .finally(() => setIsUnpublishing(false)); }; const CopyLinkToClipboard = ({ copy_link }: { copy_link: string }) => { const [status, setStatus] = useState(false); const copyText = () => { navigator.clipboard.writeText(copy_link); setStatus(true); setTimeout(() => { setStatus(false); }, 1000); }; return (
copyText()} > {status ? "Copied" : "Copy Link"}
); }; const handleFormSubmit = async (formData: FormData) => { if (!formData.views || formData.views.length === 0) { setToastAlert({ type: "error", title: "Error!", message: "Please select at least one view layout to publish the project.", }); return; } const payload = { comments: formData.comments, reactions: formData.reactions, votes: formData.votes, inbox: formData.inbox, views: { list: formData.views.includes("list"), kanban: formData.views.includes("kanban"), calendar: formData.views.includes("calendar"), gantt: formData.views.includes("gantt"), spreadsheet: formData.views.includes("spreadsheet"), }, }; if (watch("id")) await handleUpdatePublishSettings({ id: watch("id") ?? "", ...payload }); else await handlePublishProject(payload); }; // check if an update is required or not const checkIfUpdateIsRequired = () => { if (!projectPublish.projectPublishSettings || projectPublish.projectPublishSettings === "not-initialized") return; const currentSettings = projectPublish.projectPublishSettings as IProjectPublishSettings; const newSettings = getValues(); if ( currentSettings.comments !== newSettings.comments || currentSettings.reactions !== newSettings.reactions || currentSettings.votes !== newSettings.votes ) { setIsUpdateRequired(true); return; } let viewCheckFlag = 0; viewOptions.forEach((option) => { if (currentSettings.views[option.key] !== newSettings.views.includes(option.key)) viewCheckFlag++; }); if (viewCheckFlag !== 0) { setIsUpdateRequired(true); return; } setIsUpdateRequired(false); }; return (
{/* heading */}
Publish
{projectPublish.projectPublishSettings !== "not-initialized" && ( )}
{/* content */} {projectPublish.fetchSettingsLoader ? ( ) : (
{watch("id") && ( <>
{`${plane_deploy_url}/${workspaceSlug}/${projectPublish.project_id}`}
This project is live on web
)}
Views
( 0 ? viewOptions .filter((v) => value.includes(v.key)) .map((v) => v.label) .join(", ") : `` } placeholder="Select views" > <> {viewOptions.map((option) => (
{ const _views = value.length > 0 ? value.includes(option.key) ? value.filter((_o: string) => _o !== option.key) : [...value, option.key] : [option.key]; if (_views.length === 0) return; onChange(_views); checkIfUpdateIsRequired(); }} >
{option.label}
{value.length > 0 && value.includes(option.key) && ( )}
))}
)} />
Allow comments
( { onChange(val); checkIfUpdateIsRequired(); }} size="sm" /> )} />
Allow reactions
( { onChange(val); checkIfUpdateIsRequired(); }} size="sm" /> )} />
Allow voting
( { onChange(val); checkIfUpdateIsRequired(); }} size="sm" /> )} />
{/*
Allow issue proposals
( )} />
*/}
)} {/* modal handlers */}
Anyone with the link can access
{!projectPublish.fetchSettingsLoader && (
{watch("id") ? ( <> {isUpdateRequired && ( )} ) : ( )}
)}
); });