feat: add board favourites with animations (#318)

* feat: add board favorites with animations

* feat: add board favorites functionality with admin-only access

* refactor: make board favorites user-specific via junction table

* fix: assertUserInWorkspace

* Update pnpm lockfile to match package.json

* chore: clean up migrations

---------

Co-authored-by: Henry <henry_ball@hotmail.co.uk>
This commit is contained in:
Eliott Herbert-Byrnes
2026-02-01 22:29:51 +00:00
committed by GitHub
parent ec37f5480a
commit ef5bf87fdf
16 changed files with 6729 additions and 47 deletions

View File

@@ -4,6 +4,8 @@ import {
HiLink,
HiOutlineDocumentDuplicate,
HiOutlineTrash,
HiOutlineStar,
HiStar,
} from "react-icons/hi2";
import Dropdown from "~/components/Dropdown";
@@ -16,39 +18,86 @@ export default function BoardDropdown({
isTemplate,
isLoading,
boardPublicId,
workspacePublicId,
isFavorite,
boardName,
}: {
isTemplate: boolean;
isLoading: boolean;
boardPublicId: string;
workspacePublicId: string;
isFavorite?: boolean;
boardName?: string;
}) {
const { openModal } = useModal();
const { canEditBoard, canDeleteBoard, canCreateBoard } = usePermissions();
const { showPopup } = usePopup();
const utils = api.useUtils();
const handleToggleFavorite = () => {
updateBoard.mutate({
boardPublicId,
favorite: !isFavorite,
});
};
const updateBoard = api.board.update.useMutation({
onSuccess: (data, variables) => {
void utils.board.all.invalidate();
void utils.board.byId.invalidate();
// Show popup notification
if (variables.favorite !== undefined) {
showPopup({
header: variables.favorite
? t`Added to favorites`
: t`Removed from favorites`,
message: variables.favorite
? t`${boardName ?? "Board"} has been added to your favorites.`
: t`${boardName ?? "Board"} has been removed from your favorites.`,
icon: "success",
});
}
},
onError: () => {
showPopup({
header: t`Unable to update board`,
message: t`Please try again later, or contact customer support.`,
icon: "error",
});
},
});
const items = [
...(isTemplate
? []
: [
...(isTemplate && canCreateBoard
? [
{
label: t`Make template`,
action: canCreateBoard ? () => openModal("CREATE_TEMPLATE") : undefined,
action: () => openModal("CREATE_TEMPLATE"),
icon: (
<HiOutlineDocumentDuplicate className="h-[16px] w-[16px] text-dark-900" />
),
disabled: !canCreateBoard,
},
...(canEditBoard
? [
{
label: t`Edit board URL`,
action: () => openModal("UPDATE_BOARD_SLUG"),
icon: <HiLink className="h-[16px] w-[16px] text-dark-900" />,
},
]
: []),
]),
]
: []),
...(!isTemplate && canEditBoard
? [
{
label: t`Edit board URL`,
action: () => openModal("UPDATE_BOARD_SLUG"),
icon: <HiLink className="h-[16px] w-[16px] text-dark-900" />,
},
]
: []),
{
label: isFavorite
? t`Remove from favorites`
: t`Add to favorites`,
action: handleToggleFavorite,
icon: isFavorite ? (
<HiStar className="h-[16px] w-[16px] text-dark-900" />
) : (
<HiOutlineStar className="h-[16px] w-[16px] text-dark-900" />
),
},
...(canDeleteBoard
? [
{
@@ -59,6 +108,7 @@ export default function BoardDropdown({
]
: []),
];
if (items.length === 0) {
return null;