feat(workspaces): add thumbnail UI across workspace surfaces
This commit is contained in:
@@ -2,14 +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 { useTranslation } from '../hooks/useTranslation';
|
||||
import {
|
||||
WORKSPACE_DELETE,
|
||||
WORKSPACE_EDIT,
|
||||
canWorkspace,
|
||||
type WorkspaceRole,
|
||||
} from '../lib/permissions';
|
||||
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';
|
||||
@@ -17,7 +17,7 @@ import { Card, CardContent, CardTitle } from '../components/ui/card';
|
||||
import { Pagination } from '../components/Pagination';
|
||||
import { Modal } from '../components/Modal';
|
||||
|
||||
const RoleBadge = ({ role }: { role?: WorkspaceRole }) => {
|
||||
const RoleBadge = ({ role }: { role?: WorkspaceRole }) => {
|
||||
const { t } = useTranslation();
|
||||
if (!role) return null;
|
||||
|
||||
@@ -48,8 +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 { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const orderingOptions = [
|
||||
{ value: '-created_at', label: t.ordering?.createdAtDesc || 'Newest First' },
|
||||
@@ -122,14 +122,14 @@ 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')}
|
||||
size="icon"
|
||||
className="shadow-sm"
|
||||
title={t.workspace?.createNew || 'Create New'}
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => navigate('/workspaces/create')}
|
||||
size="icon"
|
||||
className="shadow-sm"
|
||||
title={t.workspace?.createNew || 'Create New'}
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<FilterBar
|
||||
@@ -148,19 +148,26 @@ export default function Workspaces() {
|
||||
) : (
|
||||
<div className="flex flex-col flex-1">
|
||||
<div className="flex flex-col gap-4 mb-6">
|
||||
{workspaces.map((workspace) => {
|
||||
const canDeleteWorkspace = canWorkspace(workspace.my_role, WORKSPACE_DELETE);
|
||||
const canEditWorkspace = canWorkspace(workspace.my_role, WORKSPACE_EDIT);
|
||||
|
||||
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">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<div className="h-9 w-9 shrink-0 overflow-hidden rounded-lg bg-slate-100 dark:bg-slate-600 flex items-center justify-center text-sm font-semibold text-slate-700 dark:text-slate-200">
|
||||
{workspace.thumbnail ? (
|
||||
<img src={workspace.thumbnail} alt={workspace.name} className="h-full w-full object-cover" />
|
||||
) : (
|
||||
workspace.name.trim().charAt(0).toUpperCase() || "W"
|
||||
)}
|
||||
</div>
|
||||
<CardTitle className="text-lg line-clamp-1">
|
||||
{workspace.name}
|
||||
</CardTitle>
|
||||
<RoleBadge role={workspace.my_role} />
|
||||
<RoleBadge role={workspace.my_role} />
|
||||
</div>
|
||||
<p className="text-sm text-slate-500 dark:text-slate-400 line-clamp-1">
|
||||
{workspace.description || t.workspace?.noDescription || 'No description'}
|
||||
@@ -168,8 +175,8 @@ export default function Workspaces() {
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 shrink-0">
|
||||
{canDeleteWorkspace && (
|
||||
<Button
|
||||
{canDeleteWorkspace && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => setDeleteModal({ isOpen: true, workspace })}
|
||||
@@ -180,8 +187,8 @@ export default function Workspaces() {
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{canEditWorkspace && (
|
||||
<Button
|
||||
{canEditWorkspace && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => navigate(`/workspaces/${workspace.id}/edit`)}
|
||||
|
||||
Reference in New Issue
Block a user