import type { DropResult } from "react-beautiful-dnd"; import Link from "next/link"; import { useParams } from "next/navigation"; import { useRouter } from "next/router"; import { t } from "@lingui/core/macro"; import { keepPreviousData } from "@tanstack/react-query"; import { useEffect, useState } from "react"; import { DragDropContext, Draggable } from "react-beautiful-dnd"; import { useForm } from "react-hook-form"; import { HiOutlinePlusSmall, HiOutlineSquare3Stack3D } from "react-icons/hi2"; import type { UpdateBoardInput } from "@kan/api/types"; import Button from "~/components/Button"; import { DeleteLabelConfirmation } from "~/components/DeleteLabelConfirmation"; import { LabelForm } from "~/components/LabelForm"; import Modal from "~/components/modal"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm"; import { PageHead } from "~/components/PageHead"; import PatternedBackground from "~/components/PatternedBackground"; import { StrictModeDroppable as Droppable } from "~/components/StrictModeDroppable"; import { useModal } from "~/providers/modal"; import { usePopup } from "~/providers/popup"; import { useWorkspace } from "~/providers/workspace"; import { api } from "~/utils/api"; import { formatToArray } from "~/utils/helpers"; import BoardDropdown from "./components/BoardDropdown"; import Card from "./components/Card"; import { DeleteBoardConfirmation } from "./components/DeleteBoardConfirmation"; import { DeleteListConfirmation } from "./components/DeleteListConfirmation"; import Filters from "./components/Filters"; import List from "./components/List"; import { NewCardForm } from "./components/NewCardForm"; import { NewListForm } from "./components/NewListForm"; import UpdateBoardSlugButton from "./components/UpdateBoardSlugButton"; import { UpdateBoardSlugForm } from "./components/UpdateBoardSlugForm"; import VisibilityButton from "./components/VisibilityButton"; type PublicListId = string; export default function BoardPage() { const params = useParams() as { boardId: string[] } | null; const router = useRouter(); const utils = api.useUtils(); const { showPopup } = usePopup(); const { workspace } = useWorkspace(); const { openModal, modalContentType, entityId, isOpen } = useModal(); const [selectedPublicListId, setSelectedPublicListId] = useState(""); const [isInitialLoading, setIsInitialLoading] = useState(true); const boardId = params?.boardId.length ? params.boardId[0] : null; const updateBoard = api.board.update.useMutation(); const { register, handleSubmit, setValue } = useForm({ values: { boardPublicId: boardId ?? "", name: "", }, }); const onSubmit = (values: UpdateBoardInput) => { updateBoard.mutate({ boardPublicId: values.boardPublicId, name: values.name, }); }; const queryParams = { boardPublicId: boardId ?? "", members: formatToArray(router.query.members), labels: formatToArray(router.query.labels), }; const { data: boardData, isSuccess, isLoading: isQueryLoading, } = api.board.byId.useQuery(queryParams, { enabled: !!boardId, placeholderData: keepPreviousData, }); const refetchBoard = async () => { if (boardId) await utils.board.byId.refetch({ boardPublicId: boardId }); }; useEffect(() => { if (boardId) { setIsInitialLoading(false); } }, [boardId]); const isLoading = isInitialLoading || isQueryLoading; const updateListMutation = api.list.update.useMutation({ onMutate: async (args) => { await utils.board.byId.cancel(); const currentState = utils.board.byId.getData(queryParams); utils.board.byId.setData(queryParams, (oldBoard) => { if (!oldBoard) return oldBoard; const updatedLists = Array.from(oldBoard.lists); const sourceList = updatedLists.find( (list) => list.publicId === args.listPublicId, ); const currentIndex = sourceList?.index; if (currentIndex === undefined) return oldBoard; const removedList = updatedLists.splice(currentIndex, 1)[0]; if (removedList && args.index !== undefined) { updatedLists.splice(args.index, 0, removedList); return { ...oldBoard, lists: updatedLists, }; } }); return { previousState: currentState }; }, onError: (_error, _newList, context) => { utils.board.byId.setData(queryParams, context?.previousState); showPopup({ header: t`Unable to update list`, message: t`Please try again later, or contact customer support.`, icon: "error", }); }, onSettled: async () => { await utils.board.byId.invalidate(queryParams); }, }); const updateCardMutation = api.card.update.useMutation({ onMutate: async (args) => { await utils.board.byId.cancel(); const currentState = utils.board.byId.getData(queryParams); utils.board.byId.setData(queryParams, (oldBoard) => { if (!oldBoard) return oldBoard; const updatedLists = Array.from(oldBoard.lists); const sourceList = updatedLists.find((list) => list.cards.some((card) => card.publicId === args.cardPublicId), ); const destinationList = updatedLists.find( (list) => list.publicId === args.listPublicId, ); const cardToMove = sourceList?.cards.find( (card) => card.publicId === args.cardPublicId, ); if (!cardToMove) return oldBoard; const removedCard = sourceList?.cards.splice(cardToMove.index, 1)[0]; if ( sourceList && destinationList && removedCard && args.index !== undefined ) { destinationList.cards.splice(args.index, 0, removedCard); return { ...oldBoard, lists: updatedLists, }; } }); return { previousState: currentState }; }, onError: (_error, _newList, context) => { utils.board.byId.setData(queryParams, context?.previousState); showPopup({ header: t`Unable to update card`, message: t`Please try again later, or contact customer support.`, icon: "error", }); }, onSettled: async () => { await utils.board.byId.invalidate(queryParams); }, }); useEffect(() => { if (isSuccess && boardData) { setValue("name", boardData.name || ""); } }, [isSuccess, boardData, setValue]); const openNewListForm = (publicBoardId: string) => { openModal("NEW_LIST"); setSelectedPublicListId(publicBoardId); }; const onDragEnd = ({ source, destination, draggableId, type, }: DropResult): void => { if (!destination) { return; } if (type === "LIST") { updateListMutation.mutate({ listPublicId: draggableId, index: destination.index, }); } if (type === "CARD") { updateCardMutation.mutate({ cardPublicId: draggableId, listPublicId: destination.droppableId, index: destination.index, }); } }; const renderModalContent = () => { return ( <> ); }; return ( <>
{isLoading && !boardData && (
)} {boardData && (
)} {!boardData && !isLoading && (

{t`Board not found`}

)}
openModal("UPDATE_BOARD_SLUG")} isLoading={isLoading} workspaceSlug={workspace.slug ?? ""} boardSlug={boardData?.slug ?? ""} /> member.user !== null) ?? []} position="left" isLoading={!boardData} />
{isLoading ? (
) : boardData ? ( <> {boardData.lists.length === 0 ? (

{t`No lists`}

{t`Get started by creating a new list`}

) : ( {(provided) => (
{boardData.lists.map((list, index) => ( setSelectedPublicListId(publicListId) } > {(provided) => (
{list.cards.map((card, index) => ( {(provided) => ( { if ( card.publicId.startsWith( "PLACEHOLDER", ) ) e.preventDefault(); }} key={card.publicId} href={`/cards/${card.publicId}`} className={`mb-2 flex !cursor-pointer flex-col ${ card.publicId.startsWith( "PLACEHOLDER", ) ? "pointer-events-none" : "" }`} ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps} > )} ))} {provided.placeholder}
)}
))}
{provided.placeholder}
)} )} ) : null}
{renderModalContent()}
); }