diff --git a/apps/web/src/views/card/components/Dropdown.tsx b/apps/web/src/views/card/components/Dropdown.tsx index 4283d94f..94bbc25d 100644 --- a/apps/web/src/views/card/components/Dropdown.tsx +++ b/apps/web/src/views/card/components/Dropdown.tsx @@ -4,6 +4,7 @@ import { HiHashtag, HiLink, HiOutlineCheckCircle, + HiOutlineDocumentDuplicate, HiOutlineTrash, } from "react-icons/hi2"; @@ -13,6 +14,7 @@ import Dropdown from "~/components/Dropdown"; import { usePermissions } from "~/hooks/usePermissions"; import { useModal } from "~/providers/modal"; import { usePopup } from "~/providers/popup"; +import { api } from "~/utils/api"; export default function CardDropdown({ cardPublicId, @@ -20,19 +22,44 @@ export default function CardDropdown({ boardPublicId, cardCreatedBy, ticketNumber, + listPublicId, + cardIndex, }: { cardPublicId: string; isTemplate?: boolean; boardPublicId?: string; cardCreatedBy?: string | null; ticketNumber?: string | null; + listPublicId?: string; + cardIndex?: number; }) { const { openModal } = useModal(); const { showPopup } = usePopup(); const { canEditCard, canDeleteCard } = usePermissions(); const { data: session } = authClient.useSession(); + const utils = api.useUtils(); const isCreator = cardCreatedBy && session?.user.id === cardCreatedBy; + const duplicateCard = api.card.duplicate.useMutation({ + onSuccess: () => { + showPopup({ + header: t`Card duplicated`, + icon: "success", + message: t`Card duplicated successfully.`, + }); + }, + onError: () => { + showPopup({ + header: t`Unable to duplicate card`, + icon: "error", + message: t`Please try again.`, + }); + }, + onSettled: async () => { + await utils.board.byId.invalidate(); + }, + }); + const handleCopyCardLink = async () => { const path = isTemplate && boardPublicId @@ -99,6 +126,24 @@ export default function CardDropdown({ ), }, + { + label: t`Duplicate card`, + action: () => { + if (!listPublicId || cardIndex === undefined) return; + duplicateCard.mutate({ + cardPublicId, + listPublicId, + index: cardIndex + 1, + copyLabels: true, + copyMembers: true, + copyChecklists: true, + }); + }, + icon: ( + + ), + disabled: duplicateCard.isPending || !listPublicId, + }, ] : []), ...(canDeleteCard || isCreator diff --git a/apps/web/src/views/card/index.tsx b/apps/web/src/views/card/index.tsx index df84382c..b7135583 100644 --- a/apps/web/src/views/card/index.tsx +++ b/apps/web/src/views/card/index.tsx @@ -185,7 +185,11 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) { ? router.query.cardId[0] : router.query.cardId; - const { data: card, isLoading, error } = api.card.byId.useQuery( + const { + data: card, + isLoading, + error, + } = api.card.byId.useQuery( { cardPublicId: cardId ?? "" }, { enabled: !!cardId && cardId.length >= 12 }, ); @@ -340,14 +344,15 @@ export default function CardPage({ isTemplate }: { isTemplate?: boolean }) { > {board?.name} - {card.cardNumber != null && card.list.board.workspace.cardPrefix && ( - <> - - - {card.list.board.workspace.cardPrefix}-{card.cardNumber} - - - )} + {card.cardNumber != null && + card.list.board.workspace.cardPrefix && ( + <> + + + {card.list.board.workspace.cardPrefix}-{card.cardNumber} + + + )}