import { useEffect, useState } from "react"; import { observer } from "mobx-react"; import { useParams } from "next/navigation"; import { Controller, useForm } from "react-hook-form"; import { Check, ExternalLink, Globe2 } from "lucide-react"; // types import { IProject, TProjectPublishLayouts, TPublishSettings } from "@plane/types"; // ui import { Button, Loader, ToggleSwitch, TOAST_TYPE, setToast, CustomSelect } from "@plane/ui"; // components import { EModalWidth, ModalCore } from "@/components/core"; // helpers import { SPACE_BASE_URL } from "@/helpers/common.helper"; import { copyTextToClipboard } from "@/helpers/string.helper"; // hooks import { useProjectPublish } from "@/hooks/store"; type Props = { isOpen: boolean; project: IProject; onClose: () => void; }; const defaultValues: Partial = { is_comments_enabled: false, is_reactions_enabled: false, is_votes_enabled: false, inbox: null, view_props: { list: true, kanban: true, }, }; const VIEW_OPTIONS: { key: TProjectPublishLayouts; label: string; }[] = [ { key: "list", label: "List" }, { key: "kanban", label: "Kanban" }, ]; export const PublishProjectModal: React.FC = observer((props) => { const { isOpen, project, onClose } = props; // states const [isUnPublishing, setIsUnPublishing] = useState(false); // router const { workspaceSlug } = useParams(); // store hooks const { fetchPublishSettings, getPublishSettingsByProjectID, publishProject, updatePublishSettings, unPublishProject, fetchSettingsLoader, } = useProjectPublish(); // derived values const projectPublishSettings = getPublishSettingsByProjectID(project.id); // form info const { control, formState: { isDirty, isSubmitting }, handleSubmit, reset, watch, } = useForm({ defaultValues, }); const handleClose = () => { onClose(); }; // fetch publish settings useEffect(() => { if (!workspaceSlug || !isOpen) return; if (!projectPublishSettings) { fetchPublishSettings(workspaceSlug.toString(), project.id); } }, [fetchPublishSettings, isOpen, project, projectPublishSettings, workspaceSlug]); const handlePublishProject = async (payload: Partial) => { if (!workspaceSlug) return; await publishProject(workspaceSlug.toString(), project.id, payload); }; const handleUpdatePublishSettings = async (payload: Partial) => { if (!workspaceSlug || !payload.id) return; await updatePublishSettings(workspaceSlug.toString(), project.id, payload.id, payload).then((res) => { setToast({ type: TOAST_TYPE.SUCCESS, title: "Success!", message: "Publish settings updated successfully!", }); handleClose(); return res; }); }; const handleUnPublishProject = async (publishId: string) => { if (!workspaceSlug || !publishId) return; setIsUnPublishing(true); await unPublishProject(workspaceSlug.toString(), project.id, publishId) .catch(() => setToast({ type: TOAST_TYPE.ERROR, title: "Error!", message: "Something went wrong while unpublishing the project.", }) ) .finally(() => setIsUnPublishing(false)); }; const selectedLayouts = Object.entries(watch("view_props") ?? {}) // eslint-disable-next-line @typescript-eslint/no-unused-vars .filter(([key, value]) => value) // eslint-disable-next-line @typescript-eslint/no-unused-vars .map(([key, value]) => key) .filter((l) => VIEW_OPTIONS.find((o) => o.key === l)); const handleFormSubmit = async (formData: Partial) => { if (!selectedLayouts || selectedLayouts.length === 0) { setToast({ type: TOAST_TYPE.ERROR, title: "Error!", message: "Please select at least one view layout to publish the project.", }); return; } const payload: Partial = { id: formData.id, is_comments_enabled: formData.is_comments_enabled, is_reactions_enabled: formData.is_reactions_enabled, is_votes_enabled: formData.is_votes_enabled, view_props: formData.view_props, }; if (formData.id && project.anchor) await handleUpdatePublishSettings(payload); else await handlePublishProject(payload); }; // prefill form values for already published projects useEffect(() => { if (!projectPublishSettings?.anchor) return; reset({ ...defaultValues, ...projectPublishSettings, }); }, [projectPublishSettings, reset]); const publishLink = `${SPACE_BASE_URL}/issues/${projectPublishSettings?.anchor}`; const handleCopyLink = () => copyTextToClipboard(publishLink).then(() => setToast({ type: TOAST_TYPE.SUCCESS, title: "", message: "Published page link copied successfully.", }) ); return (
Publish page
{project.anchor && ( )}
{/* content */} {fetchSettingsLoader ? ( ) : (
{project.anchor && projectPublishSettings && ( <>

This project is now live on web

)}
Views
( selectedLayouts.includes(o.key)) .map((o) => o.label) .join(", ")} onChange={(val: TProjectPublishLayouts) => { if (selectedLayouts.length === 1 && selectedLayouts[0] === val) return; onChange({ ...value, [val]: !value?.[val], }); }} buttonClassName="border-none" placement="bottom-end" > {VIEW_OPTIONS.map((option) => ( {option.label} {selectedLayouts.includes(option.key) && } ))} )} />
Allow comments
( )} />
Allow reactions
( )} />
Allow voting
( )} />
)} {/* modal handlers */}
Anyone with the link can access
{!fetchSettingsLoader && (
{project.anchor ? ( isDirty && ( ) ) : ( )}
)}
); });