refactor(lists): align client and project page controls
This commit is contained in:
@@ -1,13 +1,13 @@
|
|||||||
import { useEffect, useState } from "react"
|
import { useEffect, useState } from "react"
|
||||||
import { Plus, Building2, Loader2, Pencil, Trash2 } from "lucide-react"
|
import { Plus, Building2, Loader2, Pencil, Trash2 } from "lucide-react"
|
||||||
import { useWorkspace } from "../context/WorkspaceContext"
|
import { useWorkspace } from "../context/WorkspaceContext"
|
||||||
import { useTranslation } from "../hooks/useTranslation"
|
import { useTranslation } from "../hooks/useTranslation"
|
||||||
import {
|
import {
|
||||||
CLIENTS_CREATE,
|
CLIENTS_CREATE,
|
||||||
CLIENTS_DELETE,
|
CLIENTS_DELETE,
|
||||||
CLIENTS_EDIT,
|
CLIENTS_EDIT,
|
||||||
canWorkspace,
|
canWorkspace,
|
||||||
} from "../lib/permissions"
|
} from "../lib/permissions"
|
||||||
import { type Client } from "../types/client"
|
import { type Client } from "../types/client"
|
||||||
import { getClients } from "../api/clients"
|
import { getClients } from "../api/clients"
|
||||||
import CreateClientModal from "../components/CreateClientModal"
|
import CreateClientModal from "../components/CreateClientModal"
|
||||||
@@ -38,12 +38,12 @@ export default function Clients() {
|
|||||||
const [editClient, setEditClient] = useState<Client | null>(null)
|
const [editClient, setEditClient] = useState<Client | null>(null)
|
||||||
const [deleteClient, setDeleteClient] = useState<Client | null>(null)
|
const [deleteClient, setDeleteClient] = useState<Client | null>(null)
|
||||||
|
|
||||||
const { t, lang } = useTranslation()
|
const { t, lang } = useTranslation()
|
||||||
const isFa = lang === "fa"
|
const isFa = lang === "fa"
|
||||||
const workspaceRole = activeWorkspace?.my_role
|
const workspaceRole = activeWorkspace?.my_role
|
||||||
const canCreateClient = canWorkspace(workspaceRole, CLIENTS_CREATE)
|
const canCreateClient = canWorkspace(workspaceRole, CLIENTS_CREATE)
|
||||||
const canEditClient = canWorkspace(workspaceRole, CLIENTS_EDIT)
|
const canEditClient = canWorkspace(workspaceRole, CLIENTS_EDIT)
|
||||||
const canDeleteClient = canWorkspace(workspaceRole, CLIENTS_DELETE)
|
const canDeleteClient = canWorkspace(workspaceRole, CLIENTS_DELETE)
|
||||||
|
|
||||||
const orderingOptions = [
|
const orderingOptions = [
|
||||||
{ value: "-created_at", label: t.ordering?.createdAtDesc || "Newest First" },
|
{ value: "-created_at", label: t.ordering?.createdAtDesc || "Newest First" },
|
||||||
@@ -116,24 +116,24 @@ export default function Clients() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col p-6 min-h-[calc(100vh-73px)] bg-slate-50 dark:bg-slate-900">
|
<div className="flex flex-col p-6 min-h-[calc(100vh-73px)] bg-slate-50 dark:bg-slate-900">
|
||||||
<div className="flex justify-between items-center mb-8 gap-4">
|
<div className="flex justify-between items-center mb-8 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">{t.clients.title}</h1>
|
<h1 className="text-2xl font-bold text-slate-900 dark:text-white">{t.clients.title}</h1>
|
||||||
<p className="text-slate-500 dark:text-slate-400 text-sm mt-1">
|
<p className="text-slate-500 dark:text-slate-400 text-sm mt-1">
|
||||||
{t.clients.description(activeWorkspace.name)}
|
{t.clients.description(activeWorkspace.name)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{canCreateClient && (
|
{canCreateClient && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setIsCreateModalOpen(true)}
|
onClick={() => setIsCreateModalOpen(true)}
|
||||||
size="icon"
|
size="icon"
|
||||||
className="shadow-sm shrink-0"
|
className="shadow-sm shrink-0"
|
||||||
title={t.clients.addClient}
|
title={t.clients.addClient}
|
||||||
>
|
>
|
||||||
<Plus className="w-4 h-4" />
|
<Plus className="w-5 h-5" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<FilterBar
|
<FilterBar
|
||||||
@@ -170,35 +170,32 @@ export default function Clients() {
|
|||||||
{client.notes}
|
{client.notes}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="text-[11px] text-slate-400 mt-3 font-medium">
|
|
||||||
{t.clients.addedOn}: {formatDate(client.created_at)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{(canEditClient || canDeleteClient) && (
|
{(canEditClient || canDeleteClient) && (
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
{canEditClient && (
|
{canEditClient && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => setEditClient(client)}
|
onClick={() => setEditClient(client)}
|
||||||
className="h-8 w-8 text-slate-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:text-blue-400 dark:hover:bg-blue-900/20"
|
className="h-8 w-8 text-slate-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:text-blue-400 dark:hover:bg-blue-900/20"
|
||||||
>
|
>
|
||||||
<Pencil className="w-4 h-4" />
|
<Pencil className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{canDeleteClient && (
|
{canDeleteClient && (
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => setDeleteClient(client)}
|
onClick={() => setDeleteClient(client)}
|
||||||
className="h-8 w-8 text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900/20"
|
className="h-8 w-8 text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900/20"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4" />
|
<Trash2 className="w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
@@ -216,32 +213,32 @@ export default function Clients() {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canCreateClient && (
|
{canCreateClient && (
|
||||||
<CreateClientModal
|
<CreateClientModal
|
||||||
isOpen={isCreateModalOpen}
|
isOpen={isCreateModalOpen}
|
||||||
onClose={() => setIsCreateModalOpen(false)}
|
onClose={() => setIsCreateModalOpen(false)}
|
||||||
onSuccess={fetchClientsList}
|
onSuccess={fetchClientsList}
|
||||||
workspaceId={activeWorkspace.id}
|
workspaceId={activeWorkspace.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canEditClient && (
|
{canEditClient && (
|
||||||
<EditClientModal
|
<EditClientModal
|
||||||
isOpen={!!editClient}
|
isOpen={!!editClient}
|
||||||
onClose={() => setEditClient(null)}
|
onClose={() => setEditClient(null)}
|
||||||
onSuccess={fetchClientsList}
|
onSuccess={fetchClientsList}
|
||||||
client={editClient}
|
client={editClient}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canDeleteClient && (
|
{canDeleteClient && (
|
||||||
<DeleteClientModal
|
<DeleteClientModal
|
||||||
isOpen={!!deleteClient}
|
isOpen={!!deleteClient}
|
||||||
onClose={() => setDeleteClient(null)}
|
onClose={() => setDeleteClient(null)}
|
||||||
onSuccess={fetchClientsList}
|
onSuccess={fetchClientsList}
|
||||||
client={deleteClient}
|
client={deleteClient}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,26 +9,26 @@ import { Plus, Archive, Trash2, Pencil } from "lucide-react";
|
|||||||
|
|
||||||
import FilterBar from "../components/FilterBar";
|
import FilterBar from "../components/FilterBar";
|
||||||
import { Button } from "../components/ui/button";
|
import { Button } from "../components/ui/button";
|
||||||
import { Card } from "../components/ui/card";
|
import { Card } from "../components/ui/card";
|
||||||
import { Modal } from "../components/Modal";
|
import { Modal } from "../components/Modal";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { Input } from "../components/ui/input";
|
import { Input } from "../components/ui/input";
|
||||||
import {
|
import {
|
||||||
PROJECTS_ARCHIVE,
|
PROJECTS_ARCHIVE,
|
||||||
PROJECTS_CREATE,
|
PROJECTS_CREATE,
|
||||||
PROJECTS_DELETE,
|
PROJECTS_DELETE,
|
||||||
PROJECTS_EDIT,
|
PROJECTS_EDIT,
|
||||||
canWorkspace,
|
canWorkspace,
|
||||||
} from "../lib/permissions";
|
} from "../lib/permissions";
|
||||||
|
|
||||||
export const Projects: React.FC = () => {
|
export const Projects: React.FC = () => {
|
||||||
const { t, lang } = useTranslation();
|
const { t, lang } = useTranslation();
|
||||||
const { activeWorkspace } = useWorkspace();
|
const { activeWorkspace } = useWorkspace();
|
||||||
const workspaceRole = activeWorkspace?.my_role;
|
const workspaceRole = activeWorkspace?.my_role;
|
||||||
const canCreateProject = canWorkspace(workspaceRole, PROJECTS_CREATE);
|
const canCreateProject = canWorkspace(workspaceRole, PROJECTS_CREATE);
|
||||||
const canEditProject = canWorkspace(workspaceRole, PROJECTS_EDIT);
|
const canEditProject = canWorkspace(workspaceRole, PROJECTS_EDIT);
|
||||||
const canDeleteProject = canWorkspace(workspaceRole, PROJECTS_DELETE);
|
const canDeleteProject = canWorkspace(workspaceRole, PROJECTS_DELETE);
|
||||||
const canArchiveProject = canWorkspace(workspaceRole, PROJECTS_ARCHIVE);
|
const canArchiveProject = canWorkspace(workspaceRole, PROJECTS_ARCHIVE);
|
||||||
|
|
||||||
const [projects, setProjects] = useState<any[]>([]);
|
const [projects, setProjects] = useState<any[]>([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -100,7 +100,7 @@ export const Projects: React.FC = () => {
|
|||||||
setProjectToDelete(project);
|
setProjectToDelete(project);
|
||||||
};
|
};
|
||||||
|
|
||||||
const confirmDelete = async () => {
|
const confirmDelete = async () => {
|
||||||
if (!deleteModal.project) return;
|
if (!deleteModal.project) return;
|
||||||
try {
|
try {
|
||||||
const deletedId = deleteModal.project.id;
|
const deletedId = deleteModal.project.id;
|
||||||
@@ -118,20 +118,20 @@ export const Projects: React.FC = () => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(t.projects?.deleteError || 'Failed to delete project');
|
toast.error(t.projects?.deleteError || 'Failed to delete project');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (dateStr: string | undefined) => {
|
const formatDate = (dateStr: string | undefined) => {
|
||||||
if (!dateStr) return "-"
|
if (!dateStr) return "-"
|
||||||
try {
|
try {
|
||||||
const date = new Date(dateStr)
|
const date = new Date(dateStr)
|
||||||
return new Intl.DateTimeFormat(lang === "fa" ? "fa-IR" : "en-US", {
|
return new Intl.DateTimeFormat(lang === "fa" ? "fa-IR" : "en-US", {
|
||||||
dateStyle: "long",
|
dateStyle: "long",
|
||||||
timeZone: "Asia/Tehran",
|
timeZone: "Asia/Tehran",
|
||||||
}).format(date)
|
}).format(date)
|
||||||
} catch {
|
} catch {
|
||||||
return dateStr
|
return dateStr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -142,26 +142,26 @@ export const Projects: React.FC = () => {
|
|||||||
<p className="text-slate-500 dark:text-slate-400 mt-1">{t.projects?.description(activeWorkspace?.name || "-") || 'Manage your projects'}</p>
|
<p className="text-slate-500 dark:text-slate-400 mt-1">{t.projects?.description(activeWorkspace?.name || "-") || 'Manage your projects'}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 w-full sm:w-auto">
|
<div className="flex items-center gap-3 w-full sm:w-auto">
|
||||||
{canArchiveProject && (
|
{canArchiveProject && (
|
||||||
<Button
|
<Button
|
||||||
variant={isArchived ? "default" : "secondary"}
|
variant={isArchived ? "default" : "secondary"}
|
||||||
onClick={() => setIsArchived(!isArchived)}
|
onClick={() => setIsArchived(!isArchived)}
|
||||||
className="gap-2 shadow-sm flex-1 sm:flex-none"
|
className="gap-2 shadow-sm flex-1 sm:flex-none"
|
||||||
>
|
>
|
||||||
<Archive className="h-4 w-4" />
|
<Archive className="h-4 w-4" />
|
||||||
{isArchived ? (t.projects?.active || 'Active Projects') : (t.projects?.archived || 'Archived Projects')}
|
{isArchived ? (t.projects?.active || 'Active Projects') : (t.projects?.archived || 'Archived Projects')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{canCreateProject && (
|
{canCreateProject && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setIsCreateModalOpen(true)}
|
onClick={() => setIsCreateModalOpen(true)}
|
||||||
size="icon"
|
size="icon"
|
||||||
className="shadow-sm"
|
className="shadow-sm"
|
||||||
title={t.projects?.createNew || 'Create New'}
|
title={t.projects?.createNew || 'Create New'}
|
||||||
>
|
>
|
||||||
<Plus className="h-5 w-5" />
|
<Plus className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -174,76 +174,73 @@ export const Projects: React.FC = () => {
|
|||||||
searchPlaceholder={t.projects?.searchPlaceholder || 'Search projects...'}
|
searchPlaceholder={t.projects?.searchPlaceholder || 'Search projects...'}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-12 flex justify-center text-slate-500">
|
<div className="p-12 flex justify-center text-slate-500">
|
||||||
<div className="animate-pulse">{t.projects?.loading || 'Loading...'}</div>
|
<div className="animate-pulse">{t.projects?.loading || 'Loading...'}</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col flex-1">
|
<div className="flex flex-col flex-1">
|
||||||
<Card className="overflow-hidden dark:bg-slate-800 dark:border-slate-700 mb-6">
|
<Card className="overflow-hidden dark:bg-slate-800 dark:border-slate-700 mb-6">
|
||||||
<div className="p-0">
|
<div className="p-0">
|
||||||
{projects.length === 0 ? (
|
{projects.length === 0 ? (
|
||||||
<div className="py-16 flex flex-col items-center justify-center">
|
<div className="py-16 flex flex-col items-center justify-center">
|
||||||
<p className="text-slate-500 dark:text-slate-400 font-medium">{t.projects?.emptyState || 'No projects found'}</p>
|
<p className="text-slate-500 dark:text-slate-400 font-medium">{t.projects?.emptyState || 'No projects found'}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<ul className="divide-y divide-slate-200 dark:divide-slate-800">
|
<ul className="divide-y divide-slate-200 dark:divide-slate-800">
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
<li
|
<li
|
||||||
key={project.id}
|
key={project.id}
|
||||||
className="p-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors flex items-center justify-between gap-4"
|
className="p-4 hover:bg-slate-50 dark:hover:bg-slate-800/50 transition-colors flex items-center justify-between gap-4"
|
||||||
>
|
>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<h4 className="font-medium text-slate-900 dark:text-white truncate">{project.name}</h4>
|
<h4 className="font-medium text-slate-900 dark:text-white truncate">{project.name}</h4>
|
||||||
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 truncate">
|
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 truncate">
|
||||||
{project.client ? `${t.projects?.client || "Client"}: ${project.client.name}` : t.projects?.noClient || "No client"}
|
{project.client ? `${t.projects?.client || "Client"}: ${project.client.name}` : t.projects?.noClient || "No client"}
|
||||||
</p>
|
</p>
|
||||||
{project.description && (
|
{project.description && (
|
||||||
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 truncate">
|
<p className="text-sm text-slate-500 dark:text-slate-400 mt-1 truncate">
|
||||||
{project.description}
|
{project.description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className="text-[11px] text-slate-400 mt-3 font-medium">
|
</div>
|
||||||
{(t.projects as any)?.addedOn || "Added on"}: {formatDate(project.created_at)}
|
|
||||||
</div>
|
{(canEditProject || canDeleteProject) && (
|
||||||
</div>
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
|
{canEditProject && (
|
||||||
{(canEditProject || canDeleteProject) && (
|
<Button
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
variant="ghost"
|
||||||
{canEditProject && (
|
size="icon"
|
||||||
<Button
|
onClick={() => setEditingProject(project)}
|
||||||
variant="ghost"
|
className="h-8 w-8 text-slate-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:text-blue-400 dark:hover:bg-blue-900/20"
|
||||||
size="icon"
|
title={t.actions?.edit || "Edit"}
|
||||||
onClick={() => setEditingProject(project)}
|
>
|
||||||
className="h-8 w-8 text-slate-400 hover:text-blue-600 hover:bg-blue-50 dark:hover:text-blue-400 dark:hover:bg-blue-900/20"
|
<Pencil className="w-4 h-4" />
|
||||||
title={t.actions?.edit || "Edit"}
|
</Button>
|
||||||
>
|
)}
|
||||||
<Pencil className="w-4 h-4" />
|
|
||||||
</Button>
|
{canDeleteProject && (
|
||||||
)}
|
<Button
|
||||||
|
variant="ghost"
|
||||||
{canDeleteProject && (
|
size="icon"
|
||||||
<Button
|
onClick={() => setDeleteModal({ isOpen: true, project })}
|
||||||
variant="ghost"
|
className="h-8 w-8 text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900/20"
|
||||||
size="icon"
|
title={t.actions?.delete || "Delete"}
|
||||||
onClick={() => setDeleteModal({ isOpen: true, project })}
|
>
|
||||||
className="h-8 w-8 text-slate-400 hover:text-red-600 hover:bg-red-50 dark:hover:text-red-400 dark:hover:bg-red-900/20"
|
<Trash2 className="w-4 h-4" />
|
||||||
title={t.actions?.delete || "Delete"}
|
</Button>
|
||||||
>
|
)}
|
||||||
<Trash2 className="w-4 h-4" />
|
</div>
|
||||||
</Button>
|
)}
|
||||||
)}
|
</li>
|
||||||
</div>
|
))}
|
||||||
)}
|
</ul>
|
||||||
</li>
|
)}
|
||||||
))}
|
</div>
|
||||||
</ul>
|
</Card>
|
||||||
)}
|
|
||||||
</div>
|
<Pagination
|
||||||
</Card>
|
currentPage={currentPage}
|
||||||
|
|
||||||
<Pagination
|
|
||||||
currentPage={currentPage}
|
|
||||||
totalCount={totalItems}
|
totalCount={totalItems}
|
||||||
limit={limit}
|
limit={limit}
|
||||||
onPageChange={setCurrentPage}
|
onPageChange={setCurrentPage}
|
||||||
@@ -254,15 +251,15 @@ export const Projects: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Modals */}
|
{/* Modals */}
|
||||||
{canCreateProject && isCreateModalOpen && (
|
{canCreateProject && isCreateModalOpen && (
|
||||||
<ProjectCreateModal
|
<ProjectCreateModal
|
||||||
isOpen={isCreateModalOpen}
|
isOpen={isCreateModalOpen}
|
||||||
onClose={() => setIsCreateModalOpen(false)}
|
onClose={() => setIsCreateModalOpen(false)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{canEditProject && editingProject && (
|
{canEditProject && editingProject && (
|
||||||
<ProjectEditModal
|
<ProjectEditModal
|
||||||
project={editingProject}
|
project={editingProject}
|
||||||
isOpen={!!editingProject}
|
isOpen={!!editingProject}
|
||||||
onClose={() => setEditingProject(null)}
|
onClose={() => setEditingProject(null)}
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ export default function Tags() {
|
|||||||
</div>
|
</div>
|
||||||
{canCreateTag && (
|
{canCreateTag && (
|
||||||
<Button onClick={openCreateModal} size="icon" className="shadow-sm shrink-0" title={t.tags?.create || "Create Tag"}>
|
<Button onClick={openCreateModal} size="icon" className="shadow-sm shrink-0" title={t.tags?.create || "Create Tag"}>
|
||||||
<Plus className="h-4 w-4" />
|
<Plus className="h-5 w-5" />
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user