import { Listbox, ListboxButton, ListboxOption, ListboxOptions, Transition, } from "@headlessui/react"; import { t } from "@lingui/core/macro"; import { useState } from "react"; import { HiChevronDown } from "react-icons/hi2"; import { twMerge } from "tailwind-merge"; import Button from "~/components/Button"; import Input from "~/components/Input"; import { useModal } from "~/providers/modal"; import { usePopup } from "~/providers/popup"; import { api } from "~/utils/api"; interface CardContextDuplicateModalProps { boardPublicId?: string; isTemplate?: boolean; } export function CardContextDuplicateModal({ boardPublicId: boardPublicIdProp, isTemplate: isTemplateProp, }: CardContextDuplicateModalProps = {}) { const { entityId: cardPublicId, closeModal, getModalState } = useModal(); const { showPopup } = usePopup(); const utils = api.useUtils(); const modalState = getModalState("CARD_CONTEXT_DUPLICATE") as | { boardPublicId: string; isTemplate?: boolean } | undefined; const boardPublicId = boardPublicIdProp ?? modalState?.boardPublicId ?? ""; const isTemplate = isTemplateProp ?? modalState?.isTemplate ?? false; const [listPublicId, setListPublicId] = useState(""); const [copyLabels, setCopyLabels] = useState(true); const [copyMembers, setCopyMembers] = useState(true); const [copyChecklists, setCopyChecklists] = useState(true); const [position, setPosition] = useState(""); const [title, setTitle] = useState(""); const { data: card, isLoading: isCardLoading } = api.card.byId.useQuery( { cardPublicId: cardPublicId ?? "" }, { enabled: !!cardPublicId && cardPublicId.length >= 12 }, ); const boardType = isTemplate ? "template" : "regular"; const { data: board } = api.board.byId.useQuery( { boardPublicId, type: boardType }, { enabled: !!boardPublicId }, ); const lists = board?.lists ?? []; const listOptions = lists.map((l) => ({ publicId: l.publicId, name: l.name })); const currentListPublicId = card?.list?.publicId; const hasLabels = (card?.labels?.length ?? 0) > 0; const hasMembers = (card?.members?.length ?? 0) > 0; const hasChecklists = (card?.checklists?.length ?? 0) > 0; const hasAnyCopyOption = hasLabels || hasMembers || hasChecklists; const duplicateCard = api.card.duplicate.useMutation({ onSuccess: () => { showPopup({ header: t`Card duplicated`, icon: "success", message: t`The card has been duplicated.`, }); closeModal(); }, onError: () => { showPopup({ header: t`Unable to duplicate card`, message: t`Please try again.`, icon: "error", }); }, onSettled: async () => { await utils.board.byId.invalidate(); }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!cardPublicId || !listPublicId) return; const indexNum = position === "" ? undefined : parseInt(position, 10); if ( position !== "" && (indexNum === undefined || isNaN(indexNum) || indexNum < 0) ) return; duplicateCard.mutate({ cardPublicId, listPublicId, copyLabels, copyMembers, copyChecklists, ...(typeof indexNum === "number" && { index: indexNum }), title: title.trim() || undefined, }); }; if (!cardPublicId) return null; return (

{t`Duplicate card`}

{listPublicId ? listOptions.find((o) => o.publicId === listPublicId) ?.name : t`Select a list`}
{listOptions.map((option) => { const isCurrentList = option.publicId === currentListPublicId; return ( twMerge( "relative select-none py-2 pl-3 pr-9 text-sm", isCurrentList ? "cursor-default opacity-50" : "cursor-pointer", !isCurrentList && focus ? "bg-light-200 text-light-1000 dark:bg-dark-400 dark:text-dark-1000" : "text-light-900 dark:text-dark-900", ) } > {option.name} ); })}
setTitle(e.target.value)} placeholder={card?.title ?? ""} className="w-full" />
setPosition(e.target.value)} placeholder={t`End of list`} className="w-full" />
{hasAnyCopyOption && (
{hasLabels && (
{t`Copy labels`}
)} {hasMembers && (
{t`Copy members`}
)} {hasChecklists && (
{t`Copy checklists`}
)}
)}
); }