diff --git a/apps/web/src/views/board/components/BoardDropdown.tsx b/apps/web/src/views/board/components/BoardDropdown.tsx index e46bdd51..2a52e392 100644 --- a/apps/web/src/views/board/components/BoardDropdown.tsx +++ b/apps/web/src/views/board/components/BoardDropdown.tsx @@ -7,6 +7,7 @@ import { } from "react-icons/hi2"; 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"; @@ -23,6 +24,7 @@ export default function BoardDropdown({ workspacePublicId: string; }) { const { openModal } = useModal(); + const { canEditBoard, canDeleteBoard } = usePermissions(); return ( openModal("CREATE_TEMPLATE"), - icon: ( - - ), - }, - { - label: t`Edit board URL`, - action: () => openModal("UPDATE_BOARD_SLUG"), - icon: , - }, + ...(canEditBoard + ? [ + { + label: t`Make template`, + action: () => openModal("CREATE_TEMPLATE"), + icon: ( + + ), + }, + { + label: t`Edit board URL`, + action: () => openModal("UPDATE_BOARD_SLUG"), + icon: , + }, + ] + : []), ]), - { - label: isTemplate ? t`Delete template` : t`Delete board`, - action: () => openModal("DELETE_BOARD"), - icon: , - }, + ...(canDeleteBoard + ? [ + { + label: isTemplate ? t`Delete template` : t`Delete board`, + action: () => openModal("DELETE_BOARD"), + icon: , + }, + ] + : []), ]} > diff --git a/apps/web/src/views/boards/index.tsx b/apps/web/src/views/boards/index.tsx index 72dc4c8d..09e0a0e9 100644 --- a/apps/web/src/views/boards/index.tsx +++ b/apps/web/src/views/boards/index.tsx @@ -7,6 +7,7 @@ import Modal from "~/components/modal"; import { NewWorkspaceForm } from "~/components/NewWorkspaceForm"; import { PageHead } from "~/components/PageHead"; import { Tooltip } from "~/components/Tooltip"; +import { usePermissions } from "~/hooks/usePermissions"; import { useKeyboardShortcut } from "~/providers/keyboard-shortcuts"; import { useModal } from "~/providers/modal"; import { useWorkspace } from "~/providers/workspace"; @@ -17,12 +18,13 @@ import { NewBoardForm } from "./components/NewBoardForm"; export default function BoardsPage({ isTemplate }: { isTemplate?: boolean }) { const { openModal, modalContentType, isOpen } = useModal(); const { workspace } = useWorkspace(); + const { canCreateBoard } = usePermissions(); const { tooltipContent: createModalShortcutTooltipContent } = useKeyboardShortcut({ type: "PRESS", stroke: { key: "C" }, - action: () => openModal("NEW_BOARD"), + action: () => canCreateBoard && openModal("NEW_BOARD"), description: t`Create new ${isTemplate ? "template" : "board"}`, group: "ACTIONS", }); diff --git a/packages/api/src/routers/board.ts b/packages/api/src/routers/board.ts index ef0197cd..829cc2ff 100644 --- a/packages/api/src/routers/board.ts +++ b/packages/api/src/routers/board.ts @@ -15,7 +15,7 @@ import { } from "@kan/shared/utils"; import { createTRPCRouter, protectedProcedure, publicProcedure } from "../trpc"; -import { assertUserInWorkspace } from "../utils/auth"; +import { assertPermission } from "../utils/permissions"; export const boardRouter = createTRPCRouter({ all: protectedProcedure @@ -58,7 +58,7 @@ export const boardRouter = createTRPCRouter({ code: "NOT_FOUND", }); - await assertUserInWorkspace(ctx.db, userId, workspace.id); + await assertPermission(ctx.db, userId, workspace.id, "board:view"); const result = boardRepo.getAllByWorkspaceId(ctx.db, workspace.id, { type: input.type, @@ -119,7 +119,7 @@ export const boardRouter = createTRPCRouter({ code: "NOT_FOUND", }); - await assertUserInWorkspace(ctx.db, userId, board.workspaceId); + await assertPermission(ctx.db, userId, board.workspaceId, "board:view"); // Convert semantic string filters to date ranges expected by the repo const dueDateFilters = input.dueDateFilters @@ -255,7 +255,7 @@ export const boardRouter = createTRPCRouter({ code: "NOT_FOUND", }); - await assertUserInWorkspace(ctx.db, userId, workspace.id); + await assertPermission(ctx.db, userId, workspace.id, "board:create"); // If sourceBoardPublicId is provided, clone the source board if (input.sourceBoardPublicId) { @@ -422,7 +422,7 @@ export const boardRouter = createTRPCRouter({ code: "NOT_FOUND", }); - await assertUserInWorkspace(ctx.db, userId, board.workspaceId); + await assertPermission(ctx.db, userId, board.workspaceId, "board:edit"); if (input.slug) { const isBoardSlugAvailable = await boardRepo.isBoardSlugAvailable( @@ -491,7 +491,7 @@ export const boardRouter = createTRPCRouter({ code: "NOT_FOUND", }); - await assertUserInWorkspace(ctx.db, userId, board.workspaceId); + await assertPermission(ctx.db, userId, board.workspaceId, "board:delete"); const listIds = board.lists.map((list) => list.id);