From 7f0e00f09d49554c6163f3ab885368fd8b25684a Mon Sep 17 00:00:00 2001
From: Amirhossein Khalili
Date: Sat, 25 Apr 2026 18:48:49 +0330
Subject: [PATCH] feat(permissions): gate workspace resources by role
---
.../projects/ProjectCreateModal.tsx | 22 +-
src/components/projects/ProjectEditModal.tsx | 53 +++--
src/lib/permissions.ts | 197 ++++++++++++++++++
src/pages/Clients.tsx | 114 ++++++----
src/pages/ProjectCreate.tsx | 21 +-
src/pages/ProjectEdit.tsx | 15 +-
src/pages/Projects.tsx | 120 ++++++-----
src/pages/Tags.tsx | 37 ++--
src/pages/WorkspaceDetail.tsx | 14 +-
src/pages/WorkspaceEdit.tsx | 66 ++++--
src/pages/Workspaces.tsx | 52 ++---
11 files changed, 511 insertions(+), 200 deletions(-)
create mode 100644 src/lib/permissions.ts
diff --git a/src/components/projects/ProjectCreateModal.tsx b/src/components/projects/ProjectCreateModal.tsx
index b9dd3db..fc7382b 100644
--- a/src/components/projects/ProjectCreateModal.tsx
+++ b/src/components/projects/ProjectCreateModal.tsx
@@ -3,11 +3,12 @@ import { useTranslation } from "../../hooks/useTranslation";
import { Modal } from "../Modal";
import { createProject } from "../../api/projects";
import { getClients } from "../../api/clients";
-import { useWorkspace } from "../../context/WorkspaceContext";
-import { Select } from "../ui/Select";
-import { Input } from "../ui/input";
-import { TextAreaInput } from "../ui/TextAreaInput";
-import { toast } from "sonner";
+import { useWorkspace } from "../../context/WorkspaceContext";
+import { Select } from "../ui/Select";
+import { Input } from "../ui/input";
+import { TextAreaInput } from "../ui/TextAreaInput";
+import { toast } from "sonner";
+import { PROJECTS_CREATE, canWorkspace } from "../../lib/permissions";
interface ProjectCreateModalProps {
isOpen: boolean;
@@ -15,8 +16,9 @@ interface ProjectCreateModalProps {
}
export const ProjectCreateModal: React.FC = ({ isOpen, onClose }) => {
- const { t } = useTranslation();
- const { activeWorkspace } = useWorkspace();
+ const { t } = useTranslation();
+ const { activeWorkspace } = useWorkspace();
+ const canCreateProject = canWorkspace(activeWorkspace?.my_role, PROJECTS_CREATE);
const [loading, setLoading] = useState(false);
const [clients, setClients] = useState([]);
const [formData, setFormData] = useState({
@@ -61,7 +63,7 @@ export const ProjectCreateModal: React.FC = ({ isOpen,
}
};
- const footer = (
+ const footer = (
<>
>
- );
+ );
+
+ if (!canCreateProject) return null;
return (
diff --git a/src/components/projects/ProjectEditModal.tsx b/src/components/projects/ProjectEditModal.tsx
index 99b5d94..2bf8ee0 100644
--- a/src/components/projects/ProjectEditModal.tsx
+++ b/src/components/projects/ProjectEditModal.tsx
@@ -5,10 +5,11 @@ import { updateProject, toggleArchiveProject } from "../../api/projects";
import { getClients } from "../../api/clients";
import { useWorkspace } from "../../context/WorkspaceContext";
import { Archive, RefreshCcw } from "lucide-react";
-import { Select } from "../ui/Select";
-import { Input } from "../ui/input";
-import { TextAreaInput } from "../ui/TextAreaInput";
-import { toast } from "sonner";
+import { Select } from "../ui/Select";
+import { Input } from "../ui/input";
+import { TextAreaInput } from "../ui/TextAreaInput";
+import { toast } from "sonner";
+import { PROJECTS_ARCHIVE, PROJECTS_EDIT, canWorkspace } from "../../lib/permissions";
interface ProjectEditModalProps {
isOpen: boolean;
@@ -17,8 +18,10 @@ interface ProjectEditModalProps {
}
export const ProjectEditModal: React.FC = ({ isOpen, onClose, project }) => {
- const { t } = useTranslation();
- const { activeWorkspace } = useWorkspace();
+ const { t } = useTranslation();
+ const { activeWorkspace } = useWorkspace();
+ const canEditProject = canWorkspace(activeWorkspace?.my_role, PROJECTS_EDIT);
+ const canArchiveProject = canWorkspace(activeWorkspace?.my_role, PROJECTS_ARCHIVE);
const [loading, setLoading] = useState(false);
const [clients, setClients] = useState([]);
const [formData, setFormData] = useState({
@@ -86,21 +89,25 @@ export const ProjectEditModal: React.FC = ({ isOpen, onCl
}
};
- const footer = (
-
-
+ const footer = (
+
+ {canArchiveProject ? (
+
+ ) : (
+
+ )}
- );
+ );
+
+ if (!canEditProject) return null;
return (
diff --git a/src/lib/permissions.ts b/src/lib/permissions.ts
new file mode 100644
index 0000000..151ac80
--- /dev/null
+++ b/src/lib/permissions.ts
@@ -0,0 +1,197 @@
+export type WorkspaceRole = "owner" | "admin" | "member" | "guest";
+export type ProjectRole = "manager" | "member" | string;
+
+export const WORKSPACE_VIEW = "workspace.view";
+export const WORKSPACE_EDIT = "workspace.edit";
+export const WORKSPACE_DELETE = "workspace.delete";
+export const WORKSPACE_MEMBERS_VIEW = "workspace.members.view";
+export const WORKSPACE_MEMBERS_ADD = "workspace.members.add";
+export const WORKSPACE_MEMBERS_REMOVE = "workspace.members.remove";
+export const WORKSPACE_MEMBERS_CHANGE_ROLE = "workspace.members.change_role";
+export const CLIENTS_VIEW = "clients.view";
+export const CLIENTS_CREATE = "clients.create";
+export const CLIENTS_EDIT = "clients.edit";
+export const CLIENTS_DELETE = "clients.delete";
+export const TAGS_VIEW = "tags.view";
+export const TAGS_CREATE = "tags.create";
+export const TAGS_EDIT = "tags.edit";
+export const TAGS_DELETE = "tags.delete";
+export const PROJECTS_VIEW = "projects.view";
+export const PROJECTS_CREATE = "projects.create";
+export const PROJECTS_EDIT = "projects.edit";
+export const PROJECTS_DELETE = "projects.delete";
+export const PROJECTS_ARCHIVE = "projects.archive";
+export const PROJECT_MEMBERS_VIEW = "project_members.view";
+export const PROJECT_MEMBERS_ADD = "project_members.add";
+export const PROJECT_MEMBERS_REMOVE = "project_members.remove";
+export const PROJECT_MEMBERS_CHANGE_ROLE = "project_members.change_role";
+export const TIME_ENTRIES_VIEW_OWN = "time_entries.view_own";
+export const TIME_ENTRIES_MANAGE_OWN = "time_entries.manage_own";
+
+export type WorkspaceCapability =
+ | typeof WORKSPACE_VIEW
+ | typeof WORKSPACE_EDIT
+ | typeof WORKSPACE_DELETE
+ | typeof WORKSPACE_MEMBERS_VIEW
+ | typeof WORKSPACE_MEMBERS_ADD
+ | typeof WORKSPACE_MEMBERS_REMOVE
+ | typeof WORKSPACE_MEMBERS_CHANGE_ROLE
+ | typeof CLIENTS_VIEW
+ | typeof CLIENTS_CREATE
+ | typeof CLIENTS_EDIT
+ | typeof CLIENTS_DELETE
+ | typeof TAGS_VIEW
+ | typeof TAGS_CREATE
+ | typeof TAGS_EDIT
+ | typeof TAGS_DELETE
+ | typeof PROJECTS_VIEW
+ | typeof PROJECTS_CREATE
+ | typeof PROJECTS_EDIT
+ | typeof PROJECTS_DELETE
+ | typeof PROJECTS_ARCHIVE
+ | typeof PROJECT_MEMBERS_VIEW
+ | typeof PROJECT_MEMBERS_ADD
+ | typeof PROJECT_MEMBERS_REMOVE
+ | typeof PROJECT_MEMBERS_CHANGE_ROLE
+ | typeof TIME_ENTRIES_VIEW_OWN
+ | typeof TIME_ENTRIES_MANAGE_OWN;
+
+const CAPABILITIES_BY_ROLE: Record> = {
+ owner: new Set([
+ WORKSPACE_VIEW,
+ WORKSPACE_EDIT,
+ WORKSPACE_DELETE,
+ WORKSPACE_MEMBERS_VIEW,
+ WORKSPACE_MEMBERS_ADD,
+ WORKSPACE_MEMBERS_REMOVE,
+ WORKSPACE_MEMBERS_CHANGE_ROLE,
+ CLIENTS_VIEW,
+ CLIENTS_CREATE,
+ CLIENTS_EDIT,
+ CLIENTS_DELETE,
+ TAGS_VIEW,
+ TAGS_CREATE,
+ TAGS_EDIT,
+ TAGS_DELETE,
+ PROJECTS_VIEW,
+ PROJECTS_CREATE,
+ PROJECTS_EDIT,
+ PROJECTS_DELETE,
+ PROJECTS_ARCHIVE,
+ PROJECT_MEMBERS_VIEW,
+ PROJECT_MEMBERS_ADD,
+ PROJECT_MEMBERS_REMOVE,
+ PROJECT_MEMBERS_CHANGE_ROLE,
+ TIME_ENTRIES_VIEW_OWN,
+ TIME_ENTRIES_MANAGE_OWN,
+ ]),
+ admin: new Set([
+ WORKSPACE_VIEW,
+ WORKSPACE_EDIT,
+ WORKSPACE_MEMBERS_VIEW,
+ WORKSPACE_MEMBERS_ADD,
+ WORKSPACE_MEMBERS_REMOVE,
+ WORKSPACE_MEMBERS_CHANGE_ROLE,
+ CLIENTS_VIEW,
+ CLIENTS_CREATE,
+ CLIENTS_EDIT,
+ CLIENTS_DELETE,
+ TAGS_VIEW,
+ TAGS_CREATE,
+ TAGS_EDIT,
+ TAGS_DELETE,
+ PROJECTS_VIEW,
+ PROJECTS_CREATE,
+ PROJECTS_EDIT,
+ PROJECTS_DELETE,
+ PROJECTS_ARCHIVE,
+ PROJECT_MEMBERS_VIEW,
+ PROJECT_MEMBERS_ADD,
+ PROJECT_MEMBERS_REMOVE,
+ PROJECT_MEMBERS_CHANGE_ROLE,
+ TIME_ENTRIES_VIEW_OWN,
+ TIME_ENTRIES_MANAGE_OWN,
+ ]),
+ member: new Set([
+ WORKSPACE_VIEW,
+ CLIENTS_VIEW,
+ TAGS_VIEW,
+ TAGS_CREATE,
+ PROJECTS_VIEW,
+ TIME_ENTRIES_VIEW_OWN,
+ TIME_ENTRIES_MANAGE_OWN,
+ ]),
+ guest: new Set([
+ WORKSPACE_VIEW,
+ CLIENTS_VIEW,
+ TAGS_VIEW,
+ PROJECTS_VIEW,
+ TIME_ENTRIES_VIEW_OWN,
+ ]),
+};
+
+const PROJECT_MANAGER_CAPABILITIES = new Set([
+ PROJECTS_EDIT,
+ PROJECTS_ARCHIVE,
+ PROJECT_MEMBERS_VIEW,
+ PROJECT_MEMBERS_ADD,
+ PROJECT_MEMBERS_REMOVE,
+ PROJECT_MEMBERS_CHANGE_ROLE,
+]);
+
+export const canWorkspace = (
+ role: WorkspaceRole | null | undefined,
+ capability: WorkspaceCapability,
+) => {
+ if (!role) return false;
+ return CAPABILITIES_BY_ROLE[role]?.has(capability) ?? false;
+};
+
+export const canProject = ({
+ workspaceRole,
+ projectRole,
+ capability,
+}: {
+ workspaceRole: WorkspaceRole | null | undefined;
+ projectRole?: ProjectRole | null;
+ capability: WorkspaceCapability;
+}) => {
+ if (canWorkspace(workspaceRole, capability)) return true;
+ if (workspaceRole === "member" || workspaceRole === "guest" || !workspaceRole) {
+ return false;
+ }
+ return projectRole === "manager" && PROJECT_MANAGER_CAPABILITIES.has(capability);
+};
+
+export const canChangeWorkspaceMember = ({
+ actorRole,
+ actorUserId,
+ targetRole,
+ targetUserId,
+ ownerUserId,
+ newRole,
+}: {
+ actorRole: WorkspaceRole | null | undefined;
+ actorUserId?: string | null;
+ targetRole: WorkspaceRole;
+ targetUserId?: string | null;
+ ownerUserId?: string | null;
+ newRole?: WorkspaceRole;
+}) => {
+ if (!actorRole || !actorUserId || !targetUserId) return false;
+ if (actorUserId === targetUserId) return false;
+
+ const targetIsCanonicalOwner = !!ownerUserId && targetUserId === ownerUserId;
+ if (actorRole === "admin") {
+ if (targetRole === "owner" || targetIsCanonicalOwner) return false;
+ if (newRole === "owner") return false;
+ return true;
+ }
+
+ if (actorRole === "owner") {
+ if (targetIsCanonicalOwner) return false;
+ return true;
+ }
+
+ return false;
+};
diff --git a/src/pages/Clients.tsx b/src/pages/Clients.tsx
index 799a1f4..cab433f 100644
--- a/src/pages/Clients.tsx
+++ b/src/pages/Clients.tsx
@@ -1,7 +1,13 @@
import { useEffect, useState } from "react"
import { Plus, Building2, Loader2, Pencil, Trash2 } from "lucide-react"
import { useWorkspace } from "../context/WorkspaceContext"
-import { useTranslation } from "../hooks/useTranslation"
+import { useTranslation } from "../hooks/useTranslation"
+import {
+ CLIENTS_CREATE,
+ CLIENTS_DELETE,
+ CLIENTS_EDIT,
+ canWorkspace,
+} from "../lib/permissions"
import { type Client } from "../types/client"
import { getClients } from "../api/clients"
import CreateClientModal from "../components/CreateClientModal"
@@ -32,8 +38,12 @@ export default function Clients() {
const [editClient, setEditClient] = useState(null)
const [deleteClient, setDeleteClient] = useState(null)
- const { t, lang } = useTranslation()
- const isFa = lang === "fa"
+ const { t, lang } = useTranslation()
+ const isFa = lang === "fa"
+ const workspaceRole = activeWorkspace?.my_role
+ const canCreateClient = canWorkspace(workspaceRole, CLIENTS_CREATE)
+ const canEditClient = canWorkspace(workspaceRole, CLIENTS_EDIT)
+ const canDeleteClient = canWorkspace(workspaceRole, CLIENTS_DELETE)
const orderingOptions = [
{ value: "-created_at", label: t.ordering?.createdAtDesc || "Newest First" },
@@ -114,10 +124,12 @@ export default function Clients() {
-
+ {canCreateClient && (
+
+ )}
-
-
-
-
+ {(canEditClient || canDeleteClient) && (
+
+ {canEditClient && (
+
+ )}
+ {canDeleteClient && (
+
+ )}
+
+ )}
))}
@@ -194,26 +212,32 @@ export default function Clients() {
/>
)}
- setIsCreateModalOpen(false)}
- onSuccess={fetchClientsList}
- workspaceId={activeWorkspace.id}
- />
-
- setEditClient(null)}
- onSuccess={fetchClientsList}
- client={editClient}
- />
-
- setDeleteClient(null)}
- onSuccess={fetchClientsList}
- client={deleteClient}
- />
+ {canCreateClient && (
+ setIsCreateModalOpen(false)}
+ onSuccess={fetchClientsList}
+ workspaceId={activeWorkspace.id}
+ />
+ )}
+
+ {canEditClient && (
+ setEditClient(null)}
+ onSuccess={fetchClientsList}
+ client={editClient}
+ />
+ )}
+
+ {canDeleteClient && (
+ setDeleteClient(null)}
+ onSuccess={fetchClientsList}
+ client={deleteClient}
+ />
+ )}
)
}
diff --git a/src/pages/ProjectCreate.tsx b/src/pages/ProjectCreate.tsx
index 2f7913e..f53fbea 100644
--- a/src/pages/ProjectCreate.tsx
+++ b/src/pages/ProjectCreate.tsx
@@ -15,7 +15,8 @@ import { fetchWorkspaceMemberships } from "../api/workspaces";
import { searchUserByExactMobile, type SearchedUser } from "../api/users";
import { useAppContext } from "../context/AppContext";
import { useWorkspace } from "../context/WorkspaceContext";
-import { useTranslation } from "../hooks/useTranslation";
+import { useTranslation } from "../hooks/useTranslation";
+import { PROJECTS_CREATE, canWorkspace } from "../lib/permissions";
import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input";
import { Select } from "../components/ui/Select";
@@ -57,7 +58,8 @@ export default function ProjectCreate() {
const { t } = useTranslation();
const { user } = useAppContext();
const { activeWorkspace } = useWorkspace();
- const currentUserId = user?.id || "";
+ const currentUserId = user?.id || "";
+ const canCreateProject = canWorkspace(activeWorkspace?.my_role, PROJECTS_CREATE);
// Project Detail States
const [name, setName] = useState("");
@@ -89,7 +91,14 @@ export default function ProjectCreate() {
const [memberIdToDelete, setMemberIdToDelete] = useState(null);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
- const hasUnsavedChanges = name.trim() !== "" || description.trim() !== "" || members.length > 1;
+ const hasUnsavedChanges = name.trim() !== "" || description.trim() !== "" || members.length > 1;
+
+ useEffect(() => {
+ if (activeWorkspace && !canCreateProject) {
+ toast.error("You do not have permission to create projects.");
+ navigate("/projects");
+ }
+ }, [activeWorkspace, canCreateProject, navigate]);
useBlocker(({ currentLocation, nextLocation }) => {
if (hasUnsavedChanges && !isSaving && currentLocation.pathname !== nextLocation.pathname) {
@@ -352,9 +361,9 @@ export default function ProjectCreate() {
...filteredWorkspaceMembers.map((m) => ({ listId: m.id || m.user.id, user: m.user }))
];
- if (!activeWorkspace) {
- return null;
- }
+ if (!activeWorkspace) {
+ return null;
+ }
return (
diff --git a/src/pages/ProjectEdit.tsx b/src/pages/ProjectEdit.tsx
index d7a99ea..93a3489 100644
--- a/src/pages/ProjectEdit.tsx
+++ b/src/pages/ProjectEdit.tsx
@@ -15,7 +15,8 @@ import { fetchWorkspaceMemberships } from "../api/workspaces";
import { searchUserByExactMobile, type SearchedUser } from "../api/users";
import { useAppContext } from "../context/AppContext";
import { useWorkspace } from "../context/WorkspaceContext";
-import { useTranslation } from "../hooks/useTranslation";
+import { useTranslation } from "../hooks/useTranslation";
+import { PROJECTS_EDIT, canWorkspace } from "../lib/permissions";
import { Button } from "../components/ui/button";
import { Input } from "../components/ui/input";
import { Select } from "../components/ui/Select";
@@ -58,7 +59,8 @@ export default function ProjectEdit() {
const { t } = useTranslation();
const { user } = useAppContext();
const { activeWorkspace } = useWorkspace();
- const currentUserId = user?.id || "";
+ const currentUserId = user?.id || "";
+ const canEditProject = canWorkspace(activeWorkspace?.my_role, PROJECTS_EDIT);
const [name, setName] = useState("");
const [description, setDescription] = useState("");
@@ -87,7 +89,14 @@ export default function ProjectEdit() {
const [memberIdToDelete, setMemberIdToDelete] = useState
(null);
const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
- const hasUnsavedChanges = name.trim() !== "";
+ const hasUnsavedChanges = name.trim() !== "";
+
+ useEffect(() => {
+ if (activeWorkspace && !canEditProject) {
+ toast.error("You do not have permission to edit projects.");
+ navigate("/projects");
+ }
+ }, [activeWorkspace, canEditProject, navigate]);
useBlocker(({ currentLocation, nextLocation }) => {
if (hasUnsavedChanges && !isSaving && !isProjectLoading && currentLocation.pathname !== nextLocation.pathname) {
diff --git a/src/pages/Projects.tsx b/src/pages/Projects.tsx
index af837fa..489dc19 100644
--- a/src/pages/Projects.tsx
+++ b/src/pages/Projects.tsx
@@ -11,12 +11,24 @@ import FilterBar from "../components/FilterBar";
import { Button } from "../components/ui/button";
import { Card, CardHeader, CardTitle, CardContent } from "../components/ui/card";
import { Modal } from "../components/Modal";
-import { toast } from "sonner";
-import { Input } from "../components/ui/input";
+import { toast } from "sonner";
+import { Input } from "../components/ui/input";
+import {
+ PROJECTS_ARCHIVE,
+ PROJECTS_CREATE,
+ PROJECTS_DELETE,
+ PROJECTS_EDIT,
+ canWorkspace,
+} from "../lib/permissions";
export const Projects: React.FC = () => {
- const { t } = useTranslation();
- const { activeWorkspace } = useWorkspace();
+ const { t } = useTranslation();
+ const { activeWorkspace } = useWorkspace();
+ const workspaceRole = activeWorkspace?.my_role;
+ const canCreateProject = canWorkspace(workspaceRole, PROJECTS_CREATE);
+ const canEditProject = canWorkspace(workspaceRole, PROJECTS_EDIT);
+ const canDeleteProject = canWorkspace(workspaceRole, PROJECTS_DELETE);
+ const canArchiveProject = canWorkspace(workspaceRole, PROJECTS_ARCHIVE);
const [projects, setProjects] = useState([]);
const [loading, setLoading] = useState(false);
@@ -117,21 +129,25 @@ export const Projects: React.FC = () => {
{t.projects?.description(activeWorkspace?.name || "-") || 'Manage your projects'}
-
-
+ {canArchiveProject && (
+
+ )}
+ {canCreateProject && (
+
+ )}
@@ -172,27 +188,33 @@ export const Projects: React.FC = () => {
-
-
-
-
-
+ {(canEditProject || canDeleteProject) && (
+
+ {canEditProject && (
+
+ )}
+
+ {canDeleteProject && (
+
+ )}
+
+ )}
))}
@@ -216,15 +238,15 @@ export const Projects: React.FC = () => {
)}
{/* Modals */}
- {isCreateModalOpen && (
- setIsCreateModalOpen(false)}
- />
- )}
-
- {editingProject && (
- setIsCreateModalOpen(false)}
+ />
+ )}
+
+ {canEditProject && editingProject && (
+ setEditingProject(null)}
diff --git a/src/pages/Tags.tsx b/src/pages/Tags.tsx
index 2113bb6..ed350a2 100644
--- a/src/pages/Tags.tsx
+++ b/src/pages/Tags.tsx
@@ -5,6 +5,7 @@ import { toast } from "sonner";
import { createTag, deleteTag, getTags, type Tag, updateTag } from "../api/tags";
import { useWorkspace } from "../context/WorkspaceContext";
import { useTranslation } from "../hooks/useTranslation";
+import { TAGS_CREATE, TAGS_DELETE, TAGS_EDIT, canWorkspace } from "../lib/permissions";
import FilterBar from "../components/FilterBar";
import { Modal } from "../components/Modal";
import { Pagination } from "../components/Pagination";
@@ -17,6 +18,10 @@ const DEFAULT_COLOR = "#3B82F6";
export default function Tags() {
const { t } = useTranslation();
const { activeWorkspace } = useWorkspace();
+ const workspaceRole = activeWorkspace?.my_role;
+ const canCreateTag = canWorkspace(workspaceRole, TAGS_CREATE);
+ const canEditTag = canWorkspace(workspaceRole, TAGS_EDIT);
+ const canDeleteTag = canWorkspace(workspaceRole, TAGS_DELETE);
const [tags, setTags] = useState([]);
const [isLoading, setIsLoading] = useState(false);
@@ -145,10 +150,12 @@ export default function Tags() {
{t.tags?.description?.(activeWorkspace.name) || `Manage tags for ${activeWorkspace.name}`}
-
+ {canCreateTag && (
+
+ )}
-
-
-
-
+ {(canEditTag || canDeleteTag) && (
+
+ {canEditTag && (
+
+ )}
+ {canDeleteTag && (
+
+ )}
+
+ )}
))}
diff --git a/src/pages/WorkspaceDetail.tsx b/src/pages/WorkspaceDetail.tsx
index ec7fa08..09edbf9 100644
--- a/src/pages/WorkspaceDetail.tsx
+++ b/src/pages/WorkspaceDetail.tsx
@@ -1,8 +1,9 @@
import { useEffect, useState } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
-import { ArrowLeft, ArrowRight, Edit2, Trash2 } from 'lucide-react';
-import { useTranslation } from '../hooks/useTranslation';
-import { getWorkspace, deleteWorkspace, type Workspace } from '../api/workspaces';
+import { ArrowLeft, ArrowRight, Edit2, Trash2 } from 'lucide-react';
+import { useTranslation } from '../hooks/useTranslation';
+import { getWorkspace, deleteWorkspace, type Workspace } from '../api/workspaces';
+import { WORKSPACE_DELETE, WORKSPACE_EDIT, canWorkspace } from '../lib/permissions';
export default function WorkspaceDetail() {
const { id } = useParams<{ id: string }>();
@@ -44,7 +45,8 @@ export default function WorkspaceDetail() {
return {t.workspace?.loading}
;
}
- const canEdit = workspace.my_role === 'owner' || workspace.my_role === 'admin';
+ const canEdit = canWorkspace(workspace.my_role, WORKSPACE_EDIT);
+ const canDelete = canWorkspace(workspace.my_role, WORKSPACE_DELETE);
return (
@@ -75,8 +77,8 @@ export default function WorkspaceDetail() {
>
- {workspace.my_role === 'owner' && (
-