import { useRevalidator } from "@remix-run/react"; import { useEffect, useState } from "react"; import { useNavigate } from "@remix-run/react"; import { Box, Button, Flex, Label, Text, InputField, DialogActions, Dialog as BaseDialog, DialogTrigger, DialogContent as DialogContentBase, DialogTitle, DialogClose, DialogDescription, theme, } from "@webstudio-is/design-system"; import { PlusIcon } from "@webstudio-is/icons"; import { Title } from "@webstudio-is/project"; import { builderPath } from "~/shared/router-utils"; import { ShareProjectContainer } from "~/shared/share-project"; import { trpcClient } from "~/shared/trpc/trpc-client"; type DialogProps = { title: string; children: JSX.Element | Array; trigger?: JSX.Element; onOpenChange?: (open: boolean) => void; isOpen?: boolean; }; const Dialog = ({ title, children, trigger, onOpenChange, isOpen, }: DialogProps) => { return ( {trigger && {trigger}} {children} {title} ); }; const DialogContent = ({ onSubmit, onChange, placeholder, errors, primaryButton, title, description, label, width, }: { onSubmit: (data: { title: string }) => void; onChange?: (data: { title: string }) => void; errors?: string; placeholder?: string; title?: string; label: string | JSX.Element; description?: string; primaryButton: JSX.Element; width?: string; }) => { return (
{ event.preventDefault(); const formData = new FormData(event.currentTarget as HTMLFormElement); const title = String(formData.get("title") ?? "").trim(); onSubmit({ title }); }} > {description && ( {description} )} {typeof label === "string" ? : label} { onChange?.({ title: event.currentTarget.value }); }} /> {errors && {errors}} {primaryButton}
); }; const useCreateProject = () => { const navigate = useNavigate(); const { send, state } = trpcClient.dashboardProject.create.useMutation(); const [errors, setErrors] = useState(); const revalidator = useRevalidator(); const handleSubmit = ({ title }: { title: string }) => { const parsed = Title.safeParse(title); const errors = "error" in parsed ? parsed.error.issues.map((issue) => issue.message).join("\n") : undefined; setErrors(errors); if (parsed.success) { send({ title }, (data) => { revalidator.revalidate(); if (data?.id) { navigate(builderPath({ projectId: data.id })); } }); } }; const handleOpenChange = () => { setErrors(undefined); }; return { handleSubmit, handleOpenChange, state, errors, }; }; export const CreateProject = ({ buttonText = "New Project", }: { buttonText?: string; }) => { const { handleSubmit, handleOpenChange, state, errors } = useCreateProject(); return ( }>{buttonText}} onOpenChange={handleOpenChange} > Create Project } /> ); }; const useRenameProject = ({ projectId, onOpenChange, }: { projectId: string; onOpenChange: (isOpen: boolean) => void; }) => { const { send, state } = trpcClient.dashboardProject.rename.useMutation(); const [errors, setErrors] = useState(); const revalidator = useRevalidator(); const handleSubmit = ({ title }: { title: string }) => { const parsed = Title.safeParse(title); const errors = "error" in parsed ? parsed.error.issues.map((issue) => issue.message).join("\n") : undefined; setErrors(errors); if (parsed.success) { send({ projectId, title }, () => { revalidator.revalidate(); }); onOpenChange(false); } }; return { handleSubmit, errors, state, }; }; export const RenameProjectDialog = ({ isOpen, title, projectId, onOpenChange, }: { isOpen: boolean; title: string; projectId: string; onOpenChange: (isOpen: boolean) => void; }) => { const { handleSubmit, errors, state } = useRenameProject({ projectId, onOpenChange, }); return ( Rename Project } /> ); }; const useDeleteProject = ({ projectId, title, onOpenChange, onHiddenChange, }: { projectId: string; title: string; onOpenChange: (isOpen: boolean) => void; onHiddenChange: (isHidden: boolean) => void; }) => { const { send, data, state } = trpcClient.dashboardProject.delete.useMutation(); const [isMatch, setIsMatch] = useState(false); const errors = data && "errors" in data ? data.errors : undefined; const revalidator = useRevalidator(); useEffect(() => { if (errors) { onOpenChange(true); onHiddenChange(false); } }, [errors, onOpenChange, onHiddenChange]); const handleSubmit = () => { send({ projectId }, () => { revalidator.revalidate(); }); onHiddenChange(true); onOpenChange(false); }; const handleChange = ({ title: currentTitle }: { title: string }) => { setIsMatch( currentTitle.trim().toLocaleLowerCase() === title.trim().toLocaleLowerCase() ); }; return { handleSubmit, handleChange, errors, isMatch, state, }; }; export const DeleteProjectDialog = ({ isOpen, title, projectId, onOpenChange, onHiddenChange, }: { isOpen: boolean; title: string; projectId: string; onOpenChange: (isOpen: boolean) => void; onHiddenChange: (isHidden: boolean) => void; }) => { const { handleSubmit, handleChange, errors, isMatch, state } = useDeleteProject({ projectId, title, onOpenChange, onHiddenChange, }); return ( Confirm by typing {` ${title} `} below. } description="This project and its styles, pages and images will be deleted permanently." primaryButton={ } width={theme.spacing["33"]} /> ); }; export const useCloneProject = (projectId: string) => { const { send } = trpcClient.dashboardProject.clone.useMutation(); const revalidator = useRevalidator(); return () => { send({ projectId }, () => { revalidator.revalidate(); }); }; }; export const ShareProjectDialog = ({ isOpen, onOpenChange, projectId, hasProPlan, }: { isOpen: boolean; onOpenChange: (isOpen: boolean) => void; projectId: string; hasProPlan: boolean; }) => { return ( ); };