feat(permissions): gate workspace resources by role
This commit is contained in:
@@ -2,9 +2,14 @@ import { useEffect, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Plus, Trash2, Pencil, ChevronRight } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
import { fetchWorkspaces, deleteWorkspace, type Workspace } from '../api/workspaces';
|
||||
import { useAppContext } from '../context/AppContext';
|
||||
import { useTranslation } from '../hooks/useTranslation';
|
||||
import { fetchWorkspaces, deleteWorkspace, type Workspace } from '../api/workspaces';
|
||||
import { useTranslation } from '../hooks/useTranslation';
|
||||
import {
|
||||
WORKSPACE_DELETE,
|
||||
WORKSPACE_EDIT,
|
||||
canWorkspace,
|
||||
type WorkspaceRole,
|
||||
} from '../lib/permissions';
|
||||
import FilterBar from '../components/FilterBar';
|
||||
import { Button } from '../components/ui/button';
|
||||
import { Input } from '../components/ui/input';
|
||||
@@ -12,9 +17,7 @@ import { Card, CardContent, CardTitle } from '../components/ui/card';
|
||||
import { Pagination } from '../components/Pagination';
|
||||
import { Modal } from '../components/Modal';
|
||||
|
||||
type WorkspaceRole = "owner" | "admin" | "member" | "guest";
|
||||
|
||||
const RoleBadge = ({ role }: { role?: WorkspaceRole }) => {
|
||||
const RoleBadge = ({ role }: { role?: WorkspaceRole }) => {
|
||||
const { t } = useTranslation();
|
||||
if (!role) return null;
|
||||
|
||||
@@ -45,9 +48,8 @@ export default function Workspaces() {
|
||||
const [deleteModal, setDeleteModal] = useState<{isOpen: boolean; workspace: Workspace | null}>({isOpen: false, workspace: null});
|
||||
const [deleteInput, setDeleteInput] = useState('');
|
||||
|
||||
const navigate = useNavigate();
|
||||
const { user } = useAppContext();
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const orderingOptions = [
|
||||
{ value: '-created_at', label: t.ordering?.createdAtDesc || 'Newest First' },
|
||||
@@ -120,13 +122,13 @@ export default function Workspaces() {
|
||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">{t.workspace?.title || 'Workspaces'}</h1>
|
||||
<p className="text-slate-500 dark:text-slate-400 mt-1">{t.workspace?.subtitle || 'Manage your workspaces'}</p>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => navigate('/workspaces/create')}
|
||||
className="gap-2 shadow-sm"
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
{t.workspace?.createNew || 'Create New'}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigate('/workspaces/create')}
|
||||
className="gap-2 shadow-sm"
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
{t.workspace?.createNew || 'Create New'}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<FilterBar
|
||||
@@ -145,11 +147,11 @@ export default function Workspaces() {
|
||||
) : (
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="flex flex-col gap-4 mb-6">
|
||||
{workspaces.map((workspace) => {
|
||||
const isOwner = workspace.owner === user?.id || workspace.my_role === 'owner';
|
||||
const isAdmin = workspace.my_role === 'admin' || isOwner;
|
||||
|
||||
return (
|
||||
{workspaces.map((workspace) => {
|
||||
const canDeleteWorkspace = canWorkspace(workspace.my_role, WORKSPACE_DELETE);
|
||||
const canEditWorkspace = canWorkspace(workspace.my_role, WORKSPACE_EDIT);
|
||||
|
||||
return (
|
||||
<Card key={workspace.id} className="flex flex-col text-slate-800 dark:text-slate-100 dark:bg-slate-800 dark:border-slate-700 shadow-sm">
|
||||
<CardContent className="flex flex-col sm:flex-row items-start sm:items-center justify-between py-4 px-6 gap-4">
|
||||
<div className="flex-1">
|
||||
@@ -165,8 +167,8 @@ export default function Workspaces() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{isOwner && (
|
||||
<Button
|
||||
{canDeleteWorkspace && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setDeleteModal({ isOpen: true, workspace })}
|
||||
@@ -177,8 +179,8 @@ export default function Workspaces() {
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{isAdmin && (
|
||||
<Button
|
||||
{canEditWorkspace && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => navigate(`/workspaces/${workspace.id}/edit`)}
|
||||
|
||||
Reference in New Issue
Block a user