From 7153639a96d7583fbe34ec1a53f159439b236397 Mon Sep 17 00:00:00 2001 From: sai80082 Date: Wed, 25 Feb 2026 17:16:19 +0530 Subject: [PATCH 1/3] feat: Implement card archiving and trashing functionality with new database migrations, API endpoints, and UI components. - Fixes issue of same prevId in snapshots --- apps/web/src/hooks/usePermissions.ts | 3 + .../board/components/ArchivedCardsModal.tsx | 99 + .../views/board/components/BoardDropdown.tsx | 11 + .../src/views/board/components/TrashModal.tsx | 117 + apps/web/src/views/board/index.tsx | 16 + .../views/card/components/ActivityList.tsx | 10 + .../components/ArchiveCardConfirmation.tsx | 69 + .../components/DeleteCardConfirmation.tsx | 3 - .../src/views/card/components/Dropdown.tsx | 50 +- apps/web/src/views/card/index.tsx | 19 +- .../components/EditMemberPermissionsModal.tsx | 1 + .../settings/components/RolePermissions.tsx | 1 + packages/api/src/routers/board.ts | 93 +- packages/api/src/routers/card.ts | 224 + packages/api/src/routers/list.ts | 9 +- .../20260224111014_AddTrashArchive.sql | 29 + .../meta/20260208033314_snapshot.json | 2 +- .../meta/20260224111014_snapshot.json | 3847 +++++++++++++++++ packages/db/migrations/meta/_journal.json | 7 + packages/db/src/repository/board.repo.ts | 4 + packages/db/src/repository/card.repo.ts | 169 +- packages/db/src/schema/cards.ts | 4 + packages/shared/src/permissions.ts | 3 + 23 files changed, 4745 insertions(+), 45 deletions(-) create mode 100644 apps/web/src/views/board/components/ArchivedCardsModal.tsx create mode 100644 apps/web/src/views/board/components/TrashModal.tsx create mode 100644 apps/web/src/views/card/components/ArchiveCardConfirmation.tsx create mode 100644 packages/db/migrations/20260224111014_AddTrashArchive.sql create mode 100644 packages/db/migrations/meta/20260224111014_snapshot.json diff --git a/apps/web/src/hooks/usePermissions.ts b/apps/web/src/hooks/usePermissions.ts index ed282b3aa..729e4dd1a 100644 --- a/apps/web/src/hooks/usePermissions.ts +++ b/apps/web/src/hooks/usePermissions.ts @@ -13,6 +13,7 @@ interface UsePermissionsResult { canCreateCard: boolean; canEditCard: boolean; canDeleteCard: boolean; + canArchiveCard: boolean; canCreateList: boolean; canEditList: boolean; canDeleteList: boolean; @@ -45,6 +46,7 @@ export function usePermissions(): UsePermissionsResult { canCreateCard: false, canEditCard: false, canDeleteCard: false, + canArchiveCard: false, canCreateList: false, canEditList: false, canDeleteList: false, @@ -89,6 +91,7 @@ export function usePermissions(): UsePermissionsResult { canCreateCard: hasPermission("card:create"), canEditCard: hasPermission("card:edit"), canDeleteCard: hasPermission("card:delete"), + canArchiveCard: hasPermission("card:archive"), canCreateList: hasPermission("list:create"), canEditList: hasPermission("list:edit"), canDeleteList: hasPermission("list:delete"), diff --git a/apps/web/src/views/board/components/ArchivedCardsModal.tsx b/apps/web/src/views/board/components/ArchivedCardsModal.tsx new file mode 100644 index 000000000..ab7182150 --- /dev/null +++ b/apps/web/src/views/board/components/ArchivedCardsModal.tsx @@ -0,0 +1,99 @@ +import { t } from "@lingui/core/macro"; +import { formatDistanceToNow } from "date-fns"; +import * as React from "react"; +import { HiOutlineArchiveBoxXMark, HiXMark } from "react-icons/hi2"; + +import { useModal } from "~/providers/modal"; +import { usePermissions } from "~/hooks/usePermissions"; +import { api } from "~/utils/api"; +import Button from "~/components/Button"; + +interface Props { + boardPublicId: string; +} + +export default function ArchivedCardsModal({ boardPublicId }: Props) { + const { closeModal } = useModal(); + const utils = api.useUtils(); + const { canArchiveCard } = usePermissions(); + + const { data: archivedCards, isLoading } = api.board.archivedCards.useQuery( + { boardPublicId }, + { enabled: !!boardPublicId } + ); + + const unarchiveMutation = api.card.unarchive.useMutation({ + onSuccess: () => { + utils.board.archivedCards.invalidate({ boardPublicId }); + utils.board.byId.invalidate({ boardPublicId }); + }, + }); + + const handleUnarchive = (cardPublicId: string) => { + unarchiveMutation.mutate({ cardPublicId }); + }; + + return ( +
+
+

+ {t`Archived Cards`} +

+ +
+
+ {isLoading ? ( +
+
+
+ ) : !archivedCards || archivedCards.length === 0 ? ( +
+ +

{t`No archived cards`}

+
+ ) : ( +
+ {archivedCards.map((card) => ( +
+
+

{card.title}

+
+ {t`in ${card.listName}`} + {card.archivedAt && ( + <> + + {t`Archived ${formatDistanceToNow(new Date(card.archivedAt), { addSuffix: true })}`} + + )} +
+
+
+ {canArchiveCard && ( + + )} +
+
+ ))} +
+ )} +
+
+ ); +} diff --git a/apps/web/src/views/board/components/BoardDropdown.tsx b/apps/web/src/views/board/components/BoardDropdown.tsx index e04dd4117..fee4ab92c 100644 --- a/apps/web/src/views/board/components/BoardDropdown.tsx +++ b/apps/web/src/views/board/components/BoardDropdown.tsx @@ -6,6 +6,7 @@ import { HiOutlineTrash, HiOutlineStar, HiStar, + HiOutlineRectangleStack, } from "react-icons/hi2"; import Dropdown from "~/components/Dropdown"; @@ -107,6 +108,16 @@ export default function BoardDropdown({ }, ] : []), + { + label: t`Archived cards`, + action: () => openModal("ARCHIVED_CARDS"), + icon: , + }, + { + label: t`Trash`, + action: () => openModal("TRASH"), + icon: , + }, ]; diff --git a/apps/web/src/views/board/components/TrashModal.tsx b/apps/web/src/views/board/components/TrashModal.tsx new file mode 100644 index 000000000..d8e86fd5c --- /dev/null +++ b/apps/web/src/views/board/components/TrashModal.tsx @@ -0,0 +1,117 @@ +import { t } from "@lingui/core/macro"; +import { formatDistanceToNow } from "date-fns"; +import * as React from "react"; +import { HiOutlineTrash, HiXMark } from "react-icons/hi2"; + +import { useModal } from "~/providers/modal"; +import { api } from "~/utils/api"; +import Button from "~/components/Button"; + +interface Props { + boardPublicId: string; +} + +export default function TrashModal({ boardPublicId }: Props) { + const { closeModal } = useModal(); + const utils = api.useUtils(); + + const { data: trashedCards, isLoading } = api.board.trashedCards.useQuery( + { boardPublicId }, + { enabled: !!boardPublicId } + ); + + const restoreMutation = api.card.restore.useMutation({ + onSuccess: () => { + utils.board.trashedCards.invalidate({ boardPublicId }); + utils.board.byId.invalidate({ boardPublicId }); + }, + }); + + const hardDeleteMutation = api.card.hardDelete.useMutation({ + onSuccess: () => { + utils.board.trashedCards.invalidate({ boardPublicId }); + }, + }); + + const handleRestore = (cardPublicId: string) => { + restoreMutation.mutate({ cardPublicId }); + }; + + const handleHardDelete = (cardPublicId: string) => { + if (confirm(t`Are you sure you want to permanently delete this card? This action cannot be undone.`)) { + hardDeleteMutation.mutate({ cardPublicId }); + } + }; + + return ( +
+
+

+ {t`Trash`} +

+ +
+
+ {isLoading ? ( +
+
+
+ ) : !trashedCards || trashedCards.length === 0 ? ( +
+ +

{t`Trash is empty`}

+

{t`Cards you delete will appear here.`}

+
+ ) : ( +
+ {trashedCards.map((card) => ( +
+
+

{card.title}

+
+ {t`in ${card.listName}`} + {card.deletedAt && ( + <> + + {t`Deleted ${formatDistanceToNow(new Date(card.deletedAt), { addSuffix: true })}`} + + )} +
+
+
+ + +
+
+ ))} +
+ )} +
+
+ ); +} diff --git a/apps/web/src/views/board/index.tsx b/apps/web/src/views/board/index.tsx index 0de78204e..808327aa2 100644 --- a/apps/web/src/views/board/index.tsx +++ b/apps/web/src/views/board/index.tsx @@ -45,6 +45,8 @@ import { NewTemplateForm } from "./components/NewTemplateForm"; import UpdateBoardSlugButton from "./components/UpdateBoardSlugButton"; import { UpdateBoardSlugForm } from "./components/UpdateBoardSlugForm"; import VisibilityButton from "./components/VisibilityButton"; +import ArchivedCardsModal from "./components/ArchivedCardsModal"; +import TrashModal from "./components/TrashModal"; type PublicListId = string; @@ -390,6 +392,20 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) { > + + + + + + + + ); }; diff --git a/apps/web/src/views/card/components/ActivityList.tsx b/apps/web/src/views/card/components/ActivityList.tsx index 3cfc397a1..4dead058e 100644 --- a/apps/web/src/views/card/components/ActivityList.tsx +++ b/apps/web/src/views/card/components/ActivityList.tsx @@ -15,6 +15,8 @@ import { HiOutlineTrash, HiOutlineUserMinus, HiOutlineUserPlus, + HiOutlineArchiveBoxXMark, + HiOutlineArrowUturnUp, } from "react-icons/hi2"; import type { @@ -142,6 +144,10 @@ const getActivityText = ({ "card.updated.dueDate.added": t`set the due date`, "card.updated.dueDate.updated": t`updated the due date`, "card.updated.dueDate.removed": t`removed the due date`, + "card.archived": t`archived the card`, + "card.unarchived": t`unarchived the card`, + "card.restored": t`restored the card from trash`, + "card.deleted": t`deleted the card`, } as const; if (!(type in ACTIVITY_TYPE_MAP)) return null; @@ -349,6 +355,10 @@ const ACTIVITY_ICON_MAP: Partial> = "card.updated.dueDate.added": , "card.updated.dueDate.updated": , "card.updated.dueDate.removed": , + "card.archived": , + "card.unarchived": , + "card.restored": , + "card.deleted": , } as const; const getActivityIcon = ( diff --git a/apps/web/src/views/card/components/ArchiveCardConfirmation.tsx b/apps/web/src/views/card/components/ArchiveCardConfirmation.tsx new file mode 100644 index 000000000..44268d3a3 --- /dev/null +++ b/apps/web/src/views/card/components/ArchiveCardConfirmation.tsx @@ -0,0 +1,69 @@ +import { useRouter } from "next/navigation"; +import { t } from "@lingui/core/macro"; + +import Button from "~/components/Button"; +import { useModal } from "~/providers/modal"; +import { usePopup } from "~/providers/popup"; +import { api } from "~/utils/api"; + +interface ArchiveCardConfirmationProps { + cardPublicId: string; + boardPublicId?: string; +} + +export function ArchiveCardConfirmation({ + cardPublicId, + boardPublicId, +}: ArchiveCardConfirmationProps) { + const { closeModal } = useModal(); + const utils = api.useUtils(); + const router = useRouter(); + const { showPopup } = usePopup(); + + const archiveCardMutation = api.card.archive.useMutation({ + onSuccess: () => { + if (boardPublicId) { + router.push(`/boards/${boardPublicId}`); + utils.board.byId.invalidate({ boardPublicId }); + } + closeModal(); + }, + onError: () => { + showPopup({ + header: t`Unable to archive card`, + message: t`Please try again.`, + icon: "error", + }); + }, + }); + + const handleArchiveCard = () => { + archiveCardMutation.mutate({ + cardPublicId, + }); + }; + + return ( +
+
+

+ {t`Are you sure you want to archive this card?`} +

+
+
+ + +
+
+ ); +} diff --git a/apps/web/src/views/card/components/DeleteCardConfirmation.tsx b/apps/web/src/views/card/components/DeleteCardConfirmation.tsx index 235091695..89f3bd6ed 100644 --- a/apps/web/src/views/card/components/DeleteCardConfirmation.tsx +++ b/apps/web/src/views/card/components/DeleteCardConfirmation.tsx @@ -74,9 +74,6 @@ export function DeleteCardConfirmation({

{t`Are you sure you want to delete this card?`}

-

- {t`This action can't be undone.`} -

+ )} +
+ + ))} + + )} + + + ); +} diff --git a/apps/web/src/views/board/DeletedCardsView.tsx b/apps/web/src/views/board/DeletedCardsView.tsx new file mode 100644 index 000000000..8952d49db --- /dev/null +++ b/apps/web/src/views/board/DeletedCardsView.tsx @@ -0,0 +1,100 @@ +import { t } from "@lingui/core/macro"; +import { formatDistanceToNow } from "date-fns"; +import * as React from "react"; +import { HiOutlineTrash } from "react-icons/hi2"; + +import { api } from "~/utils/api"; +import Button from "~/components/Button"; + +interface Props { + boardPublicId: string; +} + +export default function DeletedCardsView({ boardPublicId }: Props) { + const utils = api.useUtils(); + + const { data: deletedcards, isLoading } = api.board.deletedCards.useQuery( + { boardPublicId }, + { enabled: !!boardPublicId } + ); + + const restoreMutation = api.card.restore.useMutation({ + onSuccess: () => { + utils.board.deletedCards.invalidate({ boardPublicId }); + utils.board.byId.invalidate({ boardPublicId }); + }, + }); + + const hardDeleteMutation = api.card.hardDelete.useMutation({ + onSuccess: () => { + utils.board.deletedCards.invalidate({ boardPublicId }); + }, + }); + + const handleRestore = (cardPublicId: string) => { + restoreMutation.mutate({ cardPublicId }); + }; + + const handleHardDelete = (cardPublicId: string) => { + if (confirm(t`Are you sure you want to permanently delete this card? This action cannot be undone.`)) { + hardDeleteMutation.mutate({ cardPublicId }); + } + }; + + return ( +
+
+ {isLoading ? ( +
+
+
+ ) : !deletedcards || deletedcards.length === 0 ? ( +
+ +

{t`No deleted cards`}

+

{t`Cards you delete will appear here.`}

+
+ ) : ( +
+ {deletedcards.map((card) => ( +
+
+

{card.title}

+
+ {t`in ${card.listName}`} + {card.deletedAt && ( + <> + + {t`Deleted ${formatDistanceToNow(new Date(card.deletedAt), { addSuffix: true })}`} + + )} +
+
+
+ + +
+
+ ))} +
+ )} +
+
+ ); +} diff --git a/apps/web/src/views/board/components/ArchivedCardsModal.tsx b/apps/web/src/views/board/components/ArchivedCardsModal.tsx deleted file mode 100644 index ab7182150..000000000 --- a/apps/web/src/views/board/components/ArchivedCardsModal.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { t } from "@lingui/core/macro"; -import { formatDistanceToNow } from "date-fns"; -import * as React from "react"; -import { HiOutlineArchiveBoxXMark, HiXMark } from "react-icons/hi2"; - -import { useModal } from "~/providers/modal"; -import { usePermissions } from "~/hooks/usePermissions"; -import { api } from "~/utils/api"; -import Button from "~/components/Button"; - -interface Props { - boardPublicId: string; -} - -export default function ArchivedCardsModal({ boardPublicId }: Props) { - const { closeModal } = useModal(); - const utils = api.useUtils(); - const { canArchiveCard } = usePermissions(); - - const { data: archivedCards, isLoading } = api.board.archivedCards.useQuery( - { boardPublicId }, - { enabled: !!boardPublicId } - ); - - const unarchiveMutation = api.card.unarchive.useMutation({ - onSuccess: () => { - utils.board.archivedCards.invalidate({ boardPublicId }); - utils.board.byId.invalidate({ boardPublicId }); - }, - }); - - const handleUnarchive = (cardPublicId: string) => { - unarchiveMutation.mutate({ cardPublicId }); - }; - - return ( -
-
-

- {t`Archived Cards`} -

- -
-
- {isLoading ? ( -
-
-
- ) : !archivedCards || archivedCards.length === 0 ? ( -
- -

{t`No archived cards`}

-
- ) : ( -
- {archivedCards.map((card) => ( -
-
-

{card.title}

-
- {t`in ${card.listName}`} - {card.archivedAt && ( - <> - - {t`Archived ${formatDistanceToNow(new Date(card.archivedAt), { addSuffix: true })}`} - - )} -
-
-
- {canArchiveCard && ( - - )} -
-
- ))} -
- )} -
-
- ); -} diff --git a/apps/web/src/views/board/components/BoardDropdown.tsx b/apps/web/src/views/board/components/BoardDropdown.tsx index fee4ab92c..63e76dc96 100644 --- a/apps/web/src/views/board/components/BoardDropdown.tsx +++ b/apps/web/src/views/board/components/BoardDropdown.tsx @@ -1,4 +1,5 @@ import { t } from "@lingui/core/macro"; +import { useRouter } from "next/router"; import { HiEllipsisHorizontal, HiLink, @@ -28,6 +29,7 @@ export default function BoardDropdown({ isFavorite?: boolean; boardName?: string; }) { + const router = useRouter(); const { openModal } = useModal(); const { canEditBoard, canDeleteBoard, canCreateBoard } = usePermissions(); const { showPopup } = usePopup(); @@ -110,12 +112,12 @@ export default function BoardDropdown({ : []), { label: t`Archived cards`, - action: () => openModal("ARCHIVED_CARDS"), + action: () => router.push(`/boards/${boardPublicId}/archived`), icon: , }, { - label: t`Trash`, - action: () => openModal("TRASH"), + label: t`Deleted cards`, + action: () => router.push(`/boards/${boardPublicId}/deleted`), icon: , }, ]; diff --git a/apps/web/src/views/board/components/TrashModal.tsx b/apps/web/src/views/board/components/TrashModal.tsx deleted file mode 100644 index d8e86fd5c..000000000 --- a/apps/web/src/views/board/components/TrashModal.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import { t } from "@lingui/core/macro"; -import { formatDistanceToNow } from "date-fns"; -import * as React from "react"; -import { HiOutlineTrash, HiXMark } from "react-icons/hi2"; - -import { useModal } from "~/providers/modal"; -import { api } from "~/utils/api"; -import Button from "~/components/Button"; - -interface Props { - boardPublicId: string; -} - -export default function TrashModal({ boardPublicId }: Props) { - const { closeModal } = useModal(); - const utils = api.useUtils(); - - const { data: trashedCards, isLoading } = api.board.trashedCards.useQuery( - { boardPublicId }, - { enabled: !!boardPublicId } - ); - - const restoreMutation = api.card.restore.useMutation({ - onSuccess: () => { - utils.board.trashedCards.invalidate({ boardPublicId }); - utils.board.byId.invalidate({ boardPublicId }); - }, - }); - - const hardDeleteMutation = api.card.hardDelete.useMutation({ - onSuccess: () => { - utils.board.trashedCards.invalidate({ boardPublicId }); - }, - }); - - const handleRestore = (cardPublicId: string) => { - restoreMutation.mutate({ cardPublicId }); - }; - - const handleHardDelete = (cardPublicId: string) => { - if (confirm(t`Are you sure you want to permanently delete this card? This action cannot be undone.`)) { - hardDeleteMutation.mutate({ cardPublicId }); - } - }; - - return ( -
-
-

- {t`Trash`} -

- -
-
- {isLoading ? ( -
-
-
- ) : !trashedCards || trashedCards.length === 0 ? ( -
- -

{t`Trash is empty`}

-

{t`Cards you delete will appear here.`}

-
- ) : ( -
- {trashedCards.map((card) => ( -
-
-

{card.title}

-
- {t`in ${card.listName}`} - {card.deletedAt && ( - <> - - {t`Deleted ${formatDistanceToNow(new Date(card.deletedAt), { addSuffix: true })}`} - - )} -
-
-
- - -
-
- ))} -
- )} -
-
- ); -} diff --git a/apps/web/src/views/board/index.tsx b/apps/web/src/views/board/index.tsx index 808327aa2..27d850bad 100644 --- a/apps/web/src/views/board/index.tsx +++ b/apps/web/src/views/board/index.tsx @@ -45,8 +45,6 @@ import { NewTemplateForm } from "./components/NewTemplateForm"; import UpdateBoardSlugButton from "./components/UpdateBoardSlugButton"; import { UpdateBoardSlugForm } from "./components/UpdateBoardSlugForm"; import VisibilityButton from "./components/VisibilityButton"; -import ArchivedCardsModal from "./components/ArchivedCardsModal"; -import TrashModal from "./components/TrashModal"; type PublicListId = string; @@ -386,26 +384,6 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) { /> - - - - - - - - - - - ); }; diff --git a/apps/web/src/views/card/components/ActivityList.tsx b/apps/web/src/views/card/components/ActivityList.tsx index 4dead058e..b35c7f683 100644 --- a/apps/web/src/views/card/components/ActivityList.tsx +++ b/apps/web/src/views/card/components/ActivityList.tsx @@ -146,7 +146,7 @@ const getActivityText = ({ "card.updated.dueDate.removed": t`removed the due date`, "card.archived": t`archived the card`, "card.unarchived": t`unarchived the card`, - "card.restored": t`restored the card from trash`, + "card.restored": t`restored the card from deleted cards`, "card.deleted": t`deleted the card`, } as const; diff --git a/packages/api/src/routers/board.ts b/packages/api/src/routers/board.ts index bd7862178..0f246e585 100644 --- a/packages/api/src/routers/board.ts +++ b/packages/api/src/routers/board.ts @@ -715,13 +715,13 @@ export const boardRouter = createTRPCRouter({ return cardRepo.getArchivedByBoardId(ctx.db, board.id); }), - trashedCards: protectedProcedure + deletedCards: protectedProcedure .meta({ openapi: { method: "GET", - path: "/boards/{boardPublicId}/trashed", - summary: "Get trashed cards", - description: "Retrieves trashed cards for a given board", + path: "/boards/{boardPublicId}/deletedcards", + summary: "Get deleted cards", + description: "Retrieves deleted cards for a given board", tags: ["Boards"], protect: true, }, @@ -731,7 +731,7 @@ export const boardRouter = createTRPCRouter({ boardPublicId: z.string().min(12), }), ) - .output(z.custom>>()) + .output(z.custom>>()) .query(async ({ ctx, input }) => { const userId = ctx.user?.id; @@ -754,6 +754,6 @@ export const boardRouter = createTRPCRouter({ await assertPermission(ctx.db, userId, board.workspaceId, "board:view"); - return cardRepo.getTrashedByBoardId(ctx.db, board.id); + return cardRepo.getDeletedCardsByBoardId(ctx.db, board.id); }), }); diff --git a/packages/api/src/routers/card.ts b/packages/api/src/routers/card.ts index 6ef4663b4..085240bdf 100644 --- a/packages/api/src/routers/card.ts +++ b/packages/api/src/routers/card.ts @@ -1073,10 +1073,10 @@ export const cardRouter = createTRPCRouter({ hardDelete: protectedProcedure .meta({ openapi: { - summary: "Permanently delete a card from trash", + summary: "Permanently delete a card from deleted cards", method: "DELETE", path: "/cards/{cardPublicId}/hard", - description: "Permanently deletes a card by its public ID from the trash", + description: "Permanently deletes a card by its public ID from the deleted cards", tags: ["Cards"], protect: true, }, @@ -1238,7 +1238,7 @@ export const cardRouter = createTRPCRouter({ restore: protectedProcedure .meta({ openapi: { - summary: "Restore a card from trash", + summary: "Restore a card from deleted cards", method: "POST", path: "/cards/{cardPublicId}/restore", description: "Restores a soft-deleted card by its public ID", @@ -1281,7 +1281,7 @@ export const cardRouter = createTRPCRouter({ card.createdBy, ); - await cardRepo.restoreFromTrash(ctx.db, { + await cardRepo.restoreFromDeletedCards(ctx.db, { cardId: card.id, restoredBy: userId, }); diff --git a/packages/db/src/repository/card.repo.ts b/packages/db/src/repository/card.repo.ts index ba30e4210..f723366e6 100644 --- a/packages/db/src/repository/card.repo.ts +++ b/packages/db/src/repository/card.repo.ts @@ -1048,7 +1048,7 @@ export const unarchive = async ( }); }; -export const restoreFromTrash = async ( +export const restoreFromDeletedCards = async ( db: dbClient, args: { cardId: number; @@ -1109,7 +1109,7 @@ export const getArchivedByBoardId = async ( .orderBy(desc(cards.archivedAt)); }; -export const getTrashedByBoardId = async ( +export const getDeletedCardsByBoardId = async ( db: dbClient, boardId: number, ) => { From 40debaefbbcf64e5b22452d086849d910c6c69ad Mon Sep 17 00:00:00 2001 From: sai80082 Date: Wed, 25 Feb 2026 19:30:05 +0530 Subject: [PATCH 3/3] fix: revert the EditYouTubeModal and acitivity logging deletion --- apps/web/src/views/board/index.tsx | 6 ++++++ packages/api/src/routers/board.ts | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/web/src/views/board/index.tsx b/apps/web/src/views/board/index.tsx index 27d850bad..b360e8c13 100644 --- a/apps/web/src/views/board/index.tsx +++ b/apps/web/src/views/board/index.tsx @@ -383,6 +383,12 @@ export default function BoardPage({ isTemplate }: { isTemplate?: boolean }) { sourceBoardName={boardData?.name ?? ""} /> + + + ); diff --git a/packages/api/src/routers/board.ts b/packages/api/src/routers/board.ts index 0f246e585..3538ff76b 100644 --- a/packages/api/src/routers/board.ts +++ b/packages/api/src/routers/board.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import * as boardRepo from "@kan/db/repository/board.repo"; import * as cardRepo from "@kan/db/repository/card.repo"; +import * as activityRepo from "@kan/db/repository/cardActivity.repo"; import * as labelRepo from "@kan/db/repository/label.repo"; import * as listRepo from "@kan/db/repository/list.repo"; import * as workspaceRepo from "@kan/db/repository/workspace.repo"; @@ -621,7 +622,15 @@ export const boardRouter = createTRPCRouter({ }); } - // No activity logging needed for soft-deleted cards from board deletion + if (deletedCards.length) { + const activities = deletedCards.map((card) => ({ + type: "card.archived" as const, + createdBy: userId, + cardId: card.id, + })); + + await activityRepo.bulkCreate(ctx.db, activities); + } } return { success: true };