forked from RiseUP/riseup-squad20
feat(api): implementação e integração das APIs de médicos
This commit is contained in:
parent
e53d7fb96e
commit
791d31a5a6
@ -1,40 +1,39 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||
import { MoreHorizontal, Plus, Search, Edit, Trash2, ArrowLeft, Eye } from "lucide-react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { DoctorRegistrationForm, Medico } from "@/components/forms/doctor-registration-form";
|
||||
import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form";
|
||||
|
||||
// Mock data for doctors
|
||||
const initialDoctors: Medico[] = [
|
||||
{
|
||||
id: "1",
|
||||
nome: "Dr. João Silva",
|
||||
especialidade: "Cardiologia",
|
||||
crm: "12345-SP",
|
||||
email: "joao.silva@example.com",
|
||||
telefone: "(11) 99999-1234",
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
nome: "Dra. Maria Oliveira",
|
||||
especialidade: "Pediatria",
|
||||
crm: "54321-RJ",
|
||||
email: "maria.oliveira@example.com",
|
||||
telefone: "(21) 98888-5678",
|
||||
},
|
||||
];
|
||||
// >>> IMPORTES DA API <<<
|
||||
import { listarMedicos, excluirMedico, Medico } from "@/lib/api";
|
||||
|
||||
export default function DoutoresPage() {
|
||||
const [doctors, setDoctors] = useState<Medico[]>(initialDoctors);
|
||||
const [doctors, setDoctors] = useState<Medico[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [search, setSearch] = useState("");
|
||||
const [showForm, setShowForm] = useState(false);
|
||||
const [editingId, setEditingId] = useState<string | null>(null);
|
||||
|
||||
// Carrega da API
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
try {
|
||||
const list = await listarMedicos({ limit: 50 });
|
||||
setDoctors(list ?? []);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
load();
|
||||
}, []);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
if (!search.trim()) return doctors;
|
||||
const q = search.toLowerCase();
|
||||
@ -56,26 +55,17 @@ export default function DoutoresPage() {
|
||||
setShowForm(true);
|
||||
}
|
||||
|
||||
function handleDelete(id: string) {
|
||||
// Excluir via API e recarregar
|
||||
async function handleDelete(id: string) {
|
||||
if (!confirm("Excluir este médico?")) return;
|
||||
setDoctors((prev) => prev.filter((x) => String(x.id) !== String(id)));
|
||||
await excluirMedico(id);
|
||||
await load();
|
||||
}
|
||||
|
||||
function handleSaved(medico: Medico) {
|
||||
const saved = medico;
|
||||
setDoctors((prev) => {
|
||||
// Se não houver ID, é um novo médico
|
||||
if (!saved.id) {
|
||||
return [{ ...saved, id: String(Date.now()) }, ...prev];
|
||||
}
|
||||
// Se houver ID, é uma edição
|
||||
const i = prev.findIndex((x) => String(x.id) === String(saved.id));
|
||||
if (i < 0) return [{ ...saved, id: String(Date.now()) }, ...prev]; // Caso não encontre, adiciona
|
||||
const clone = [...prev];
|
||||
clone[i] = saved;
|
||||
return clone;
|
||||
});
|
||||
// Após salvar/criar/editar no form, fecha e recarrega
|
||||
async function handleSaved() {
|
||||
setShowForm(false);
|
||||
await load();
|
||||
}
|
||||
|
||||
if (showForm) {
|
||||
@ -117,7 +107,7 @@ export default function DoutoresPage() {
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<Button onClick={handleAdd}>
|
||||
<Button onClick={handleAdd} disabled={loading}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Novo Médico
|
||||
</Button>
|
||||
@ -136,7 +126,13 @@ export default function DoutoresPage() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{filtered.length > 0 ? (
|
||||
{loading ? (
|
||||
<TableRow>
|
||||
<TableCell colSpan={5} className="text-center text-muted-foreground">
|
||||
Carregando…
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : filtered.length > 0 ? (
|
||||
filtered.map((doctor) => (
|
||||
<TableRow key={doctor.id}>
|
||||
<TableCell className="font-medium">{doctor.nome}</TableCell>
|
||||
@ -186,7 +182,9 @@ export default function DoutoresPage() {
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
<div className="text-sm text-muted-foreground">Mostrando {filtered.length} de {doctors.length}</div>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
Mostrando {filtered.length} de {doctors.length}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -13,6 +13,18 @@ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/u
|
||||
import { AlertCircle, ChevronDown, ChevronUp, FileImage, Loader2, Save, Upload, User, X, XCircle, Trash2 } from "lucide-react";
|
||||
import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover";
|
||||
import {
|
||||
criarMedico,
|
||||
atualizarMedico,
|
||||
buscarMedicoPorId,
|
||||
uploadFotoMedico,
|
||||
listarAnexosMedico,
|
||||
adicionarAnexoMedico,
|
||||
removerAnexoMedico,
|
||||
MedicoInput,
|
||||
} from "@/lib/api";
|
||||
|
||||
import { buscarCepAPI } from "@/lib/api"; // use o seu já existente
|
||||
|
||||
// Mock data and types since API is not used for now
|
||||
|
||||
@ -162,13 +174,57 @@ export function DoctorRegistrationForm({
|
||||
const title = useMemo(() => (mode === "create" ? "Cadastro de Médico" : "Editar Médico"), [mode]);
|
||||
|
||||
useEffect(() => {
|
||||
// Data loading logic would go here in a real scenario
|
||||
let alive = true;
|
||||
async function load() {
|
||||
if (mode === "edit" && doctorId) {
|
||||
console.log("Loading doctor data for ID:", doctorId);
|
||||
// Example: setForm(loadedDoctorData);
|
||||
const medico = await buscarMedicoPorId(doctorId);
|
||||
if (!alive) return;
|
||||
// mapeia API -> estado do formulário
|
||||
setForm({
|
||||
photo: null,
|
||||
nome: medico.nome ?? "",
|
||||
nome_social: medico.nome_social ?? "",
|
||||
crm: medico.crm ?? "",
|
||||
estado_crm: medico.estado_crm ?? "",
|
||||
rqe: medico.rqe ?? "",
|
||||
formacao_academica: medico.formacao_academica ?? [],
|
||||
curriculo: null, // se a API devolver URL, você pode exibir ao lado
|
||||
especialidade: medico.especialidade ?? "",
|
||||
cpf: medico.cpf ?? "",
|
||||
rg: medico.rg ?? "",
|
||||
sexo: medico.sexo ?? "",
|
||||
data_nascimento: medico.data_nascimento ?? "",
|
||||
email: medico.email ?? "",
|
||||
telefone: medico.telefone ?? "",
|
||||
celular: medico.celular ?? "",
|
||||
contato_emergencia: medico.contato_emergencia ?? "",
|
||||
cep: "",
|
||||
logradouro: "",
|
||||
numero: "",
|
||||
complemento: "",
|
||||
bairro: "",
|
||||
cidade: "",
|
||||
estado: "",
|
||||
observacoes: medico.observacoes ?? "",
|
||||
anexos: [],
|
||||
tipo_vinculo: medico.tipo_vinculo ?? "",
|
||||
dados_bancarios: medico.dados_bancarios ?? { banco: "", agencia: "", conta: "", tipo_conta: "" },
|
||||
agenda_horario: medico.agenda_horario ?? "",
|
||||
valor_consulta: medico.valor_consulta ? String(medico.valor_consulta) : "",
|
||||
});
|
||||
|
||||
// (Opcional) listar anexos que já existem no servidor
|
||||
try {
|
||||
const list = await listarAnexosMedico(doctorId);
|
||||
setServerAnexos(list ?? []);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
load();
|
||||
return () => { alive = false; };
|
||||
}, [mode, doctorId]);
|
||||
|
||||
|
||||
function setField<T extends keyof FormData>(k: T, v: FormData[T]) {
|
||||
setForm((s) => ({ ...s, [k]: v }));
|
||||
if (errors[k as string]) setErrors((e) => ({ ...e, [k]: "" }));
|
||||
@ -229,16 +285,14 @@ export function DoctorRegistrationForm({
|
||||
if (clean.length !== 8) return;
|
||||
setSearchingCEP(true);
|
||||
try {
|
||||
// Mocking API call
|
||||
console.log("Searching CEP:", clean);
|
||||
// In a real app: const res = await buscarCepAPI(clean);
|
||||
// Mock response:
|
||||
const res = { logradouro: "Rua Fictícia", bairro: "Bairro dos Sonhos", localidade: "Cidade Exemplo", uf: "EX" };
|
||||
if (res) {
|
||||
const res = await buscarCepAPI(clean);
|
||||
if (res && !res.erro) {
|
||||
setField("logradouro", res.logradouro ?? "");
|
||||
setField("bairro", res.bairro ?? "");
|
||||
setField("cidade", res.localidade ?? "");
|
||||
setField("estado", res.uf ?? "");
|
||||
} else {
|
||||
setErrors((e) => ({ ...e, cep: "CEP não encontrado" }));
|
||||
}
|
||||
} catch {
|
||||
setErrors((e) => ({ ...e, cep: "Erro ao buscar CEP" }));
|
||||
@ -247,6 +301,7 @@ export function DoctorRegistrationForm({
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function validateLocal(): boolean {
|
||||
const e: Record<string, string> = {};
|
||||
if (!form.nome.trim()) e.nome = "Nome é obrigatório";
|
||||
@ -262,21 +317,71 @@ export function DoctorRegistrationForm({
|
||||
if (!validateLocal()) return;
|
||||
|
||||
setSubmitting(true);
|
||||
console.log("Submitting form with data:", form);
|
||||
setErrors((e) => ({ ...e, submit: "" }));
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
setSubmitting(false);
|
||||
const savedData: Medico = {
|
||||
id: doctorId ? String(doctorId) : String(Date.now()),
|
||||
...form,
|
||||
try {
|
||||
// monta o payload esperado pela API
|
||||
const payload: MedicoInput = {
|
||||
nome: form.nome,
|
||||
nome_social: form.nome_social || null,
|
||||
cpf: form.cpf || null,
|
||||
rg: form.rg || null,
|
||||
sexo: form.sexo || null,
|
||||
data_nascimento: form.data_nascimento || null,
|
||||
telefone: form.telefone || null,
|
||||
celular: form.celular || null,
|
||||
contato_emergencia: form.contato_emergencia || null,
|
||||
email: form.email || null,
|
||||
crm: form.crm,
|
||||
estado_crm: form.estado_crm || null,
|
||||
rqe: form.rqe || null,
|
||||
formacao_academica: form.formacao_academica ?? [],
|
||||
curriculo_url: null, // se quiser, suba arquivo do currículo num endpoint próprio e salve a URL aqui
|
||||
especialidade: form.especialidade,
|
||||
observacoes: form.observacoes || null,
|
||||
tipo_vinculo: form.tipo_vinculo || null,
|
||||
dados_bancarios: form.dados_bancarios ?? null,
|
||||
agenda_horario: form.agenda_horario || null,
|
||||
valor_consulta: form.valor_consulta || null,
|
||||
};
|
||||
onSaved?.(savedData);
|
||||
alert(mode === "create" ? "Médico cadastrado com sucesso! (simulado)" : "Médico atualizado com sucesso! (simulado)");
|
||||
|
||||
// cria ou atualiza
|
||||
const saved = mode === "create"
|
||||
? await criarMedico(payload)
|
||||
: await atualizarMedico(doctorId as number, payload);
|
||||
|
||||
const medicoId = saved.id;
|
||||
|
||||
// foto (opcional)
|
||||
if (form.photo) {
|
||||
try {
|
||||
await uploadFotoMedico(medicoId, form.photo);
|
||||
} catch (e) {
|
||||
console.warn("Falha ao enviar foto:", e);
|
||||
}
|
||||
}
|
||||
|
||||
// anexos locais (opcional)
|
||||
if (form.anexos?.length) {
|
||||
for (const f of form.anexos) {
|
||||
try {
|
||||
await adicionarAnexoMedico(medicoId, f);
|
||||
} catch (e) {
|
||||
console.warn("Falha ao enviar anexo:", f.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onSaved?.(saved);
|
||||
if (inline) onClose?.();
|
||||
else onOpenChange?.(false);
|
||||
}, 1000);
|
||||
} catch (err: any) {
|
||||
setErrors((e) => ({ ...e, submit: err?.message || "Erro ao salvar médico" }));
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function handlePhoto(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const f = e.target.files?.[0];
|
||||
|
||||
@ -61,8 +61,10 @@ export type PacienteInput = {
|
||||
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "https://mock.apidog.com/m1/1053378-0-default";
|
||||
const MEDICOS_BASE = process.env.NEXT_PUBLIC_MEDICOS_BASE_PATH ?? "/medicos";
|
||||
|
||||
const PATHS = {
|
||||
export const PATHS = {
|
||||
// Pacientes (já existia)
|
||||
pacientes: "/pacientes",
|
||||
pacienteId: (id: string | number) => `/pacientes/${id}`,
|
||||
foto: (id: string | number) => `/pacientes/${id}/foto`,
|
||||
@ -70,8 +72,16 @@ const PATHS = {
|
||||
anexoId: (id: string | number, anexoId: string | number) => `/pacientes/${id}/anexos/${anexoId}`,
|
||||
validarCPF: "/pacientes/validar-cpf",
|
||||
cep: (cep: string) => `/utils/cep/${cep}`,
|
||||
|
||||
// Médicos (APONTANDO PARA PACIENTES por enquanto)
|
||||
medicos: MEDICOS_BASE,
|
||||
medicoId: (id: string | number) => `${MEDICOS_BASE}/${id}`,
|
||||
medicoFoto: (id: string | number) => `${MEDICOS_BASE}/${id}/foto`,
|
||||
medicoAnexos: (id: string | number) => `${MEDICOS_BASE}/${id}/anexos`,
|
||||
medicoAnexoId: (id: string | number, anexoId: string | number) => `${MEDICOS_BASE}/${id}/anexos/${anexoId}`,
|
||||
} as const;
|
||||
|
||||
|
||||
function headers(kind: "json" | "form" = "json"): Record<string, string> {
|
||||
const h: Record<string, string> = {};
|
||||
const token = process.env.NEXT_PUBLIC_API_TOKEN?.trim();
|
||||
@ -95,9 +105,13 @@ async function parse<T>(res: Response): Promise<T> {
|
||||
try {
|
||||
json = await res.json();
|
||||
} catch {
|
||||
|
||||
// ignora erro de parse vazio
|
||||
}
|
||||
|
||||
if (!res.ok) {
|
||||
// 🔴 ADICIONE ESSA LINHA AQUI:
|
||||
console.error("[API ERROR]", res.url, res.status, json);
|
||||
|
||||
const code = json?.apidogError?.code ?? res.status;
|
||||
const msg = json?.apidogError?.message ?? res.statusText;
|
||||
throw new Error(`${code}: ${msg}`);
|
||||
@ -106,6 +120,7 @@ async function parse<T>(res: Response): Promise<T> {
|
||||
return (json?.data ?? json) as T;
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Pacientes (CRUD)
|
||||
//
|
||||
@ -250,3 +265,150 @@ export async function buscarCepAPI(cep: string): Promise<{ logradouro?: string;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// >>> ADICIONE (ou mova) ESTES TIPOS <<<
|
||||
export type FormacaoAcademica = {
|
||||
instituicao: string;
|
||||
curso: string;
|
||||
ano_conclusao: string;
|
||||
};
|
||||
|
||||
export type DadosBancarios = {
|
||||
banco: string;
|
||||
agencia: string;
|
||||
conta: string;
|
||||
tipo_conta: string;
|
||||
};
|
||||
|
||||
export type Medico = {
|
||||
id: string;
|
||||
nome?: string;
|
||||
nome_social?: string | null;
|
||||
cpf?: string;
|
||||
rg?: string | null;
|
||||
sexo?: string | null;
|
||||
data_nascimento?: string | null;
|
||||
telefone?: string;
|
||||
celular?: string;
|
||||
contato_emergencia?: string;
|
||||
email?: string;
|
||||
crm?: string;
|
||||
estado_crm?: string;
|
||||
rqe?: string;
|
||||
formacao_academica?: FormacaoAcademica[];
|
||||
curriculo_url?: string | null;
|
||||
especialidade?: string;
|
||||
observacoes?: string | null;
|
||||
foto_url?: string | null;
|
||||
tipo_vinculo?: string;
|
||||
dados_bancarios?: DadosBancarios;
|
||||
agenda_horario?: string;
|
||||
valor_consulta?: number | string;
|
||||
};
|
||||
|
||||
export type MedicoInput = {
|
||||
nome: string;
|
||||
nome_social?: string | null;
|
||||
cpf?: string | null;
|
||||
rg?: string | null;
|
||||
sexo?: string | null;
|
||||
data_nascimento?: string | null;
|
||||
telefone?: string | null;
|
||||
celular?: string | null;
|
||||
contato_emergencia?: string | null;
|
||||
email?: string | null;
|
||||
crm: string;
|
||||
estado_crm?: string | null;
|
||||
rqe?: string | null;
|
||||
formacao_academica?: FormacaoAcademica[];
|
||||
curriculo_url?: string | null;
|
||||
especialidade: string;
|
||||
observacoes?: string | null;
|
||||
tipo_vinculo?: string | null;
|
||||
dados_bancarios?: DadosBancarios | null;
|
||||
agenda_horario?: string | null;
|
||||
valor_consulta?: number | string | null;
|
||||
};
|
||||
|
||||
//
|
||||
// MÉDICOS (CRUD)
|
||||
//
|
||||
// ======= MÉDICOS (forçando usar rotas de PACIENTES no mock) =======
|
||||
|
||||
export async function listarMedicos(params?: { page?: number; limit?: number; q?: string }): Promise<Medico[]> {
|
||||
const query = new URLSearchParams();
|
||||
if (params?.page) query.set("page", String(params.page));
|
||||
if (params?.limit) query.set("limit", String(params.limit));
|
||||
if (params?.q) query.set("q", params.q);
|
||||
|
||||
// FORÇA /pacientes
|
||||
const url = `${API_BASE}/pacientes${query.toString() ? `?${query.toString()}` : ""}`;
|
||||
const res = await fetch(url, { method: "GET", headers: headers("json") });
|
||||
const data = await parse<ApiOk<Medico[]>>(res);
|
||||
return (data as any)?.data ?? (data as any);
|
||||
}
|
||||
|
||||
export async function buscarMedicoPorId(id: string | number): Promise<Medico> {
|
||||
const url = `${API_BASE}/pacientes/${id}`; // FORÇA /pacientes
|
||||
const res = await fetch(url, { method: "GET", headers: headers("json") });
|
||||
const data = await parse<ApiOk<Medico>>(res);
|
||||
return (data as any)?.data ?? (data as any);
|
||||
}
|
||||
|
||||
export async function criarMedico(input: MedicoInput): Promise<Medico> {
|
||||
const url = `${API_BASE}/pacientes`; // FORÇA /pacientes
|
||||
const res = await fetch(url, { method: "POST", headers: headers("json"), body: JSON.stringify(input) });
|
||||
const data = await parse<ApiOk<Medico>>(res);
|
||||
return (data as any)?.data ?? (data as any);
|
||||
}
|
||||
|
||||
export async function atualizarMedico(id: string | number, input: MedicoInput): Promise<Medico> {
|
||||
const url = `${API_BASE}/pacientes/${id}`; // FORÇA /pacientes
|
||||
const res = await fetch(url, { method: "PUT", headers: headers("json"), body: JSON.stringify(input) });
|
||||
const data = await parse<ApiOk<Medico>>(res);
|
||||
return (data as any)?.data ?? (data as any);
|
||||
}
|
||||
|
||||
export async function excluirMedico(id: string | number): Promise<void> {
|
||||
const url = `${API_BASE}/pacientes/${id}`; // FORÇA /pacientes
|
||||
const res = await fetch(url, { method: "DELETE", headers: headers("json") });
|
||||
await parse<any>(res);
|
||||
}
|
||||
|
||||
export async function uploadFotoMedico(id: string | number, file: File): Promise<{ foto_url?: string; thumbnail_url?: string }> {
|
||||
const url = `${API_BASE}/pacientes/${id}/foto`; // FORÇA /pacientes
|
||||
const fd = new FormData();
|
||||
fd.append("foto", file);
|
||||
const res = await fetch(url, { method: "POST", headers: headers("form"), body: fd });
|
||||
const data = await parse<ApiOk<{ foto_url?: string; thumbnail_url?: string }>>(res);
|
||||
return (data as any)?.data ?? (data as any);
|
||||
}
|
||||
|
||||
export async function removerFotoMedico(id: string | number): Promise<void> {
|
||||
const url = `${API_BASE}/pacientes/${id}/foto`; // FORÇA /pacientes
|
||||
const res = await fetch(url, { method: "DELETE", headers: headers("json") });
|
||||
await parse<any>(res);
|
||||
}
|
||||
|
||||
export async function listarAnexosMedico(id: string | number): Promise<any[]> {
|
||||
const url = `${API_BASE}/pacientes/${id}/anexos`; // FORÇA /pacientes
|
||||
const res = await fetch(url, { method: "GET", headers: headers("json") });
|
||||
const data = await parse<ApiOk<any[]>>(res);
|
||||
return (data as any)?.data ?? (data as any);
|
||||
}
|
||||
|
||||
export async function adicionarAnexoMedico(id: string | number, file: File): Promise<any> {
|
||||
const url = `${API_BASE}/pacientes/${id}/anexos`; // FORÇA /pacientes
|
||||
const fd = new FormData();
|
||||
fd.append("arquivo", file);
|
||||
const res = await fetch(url, { method: "POST", headers: headers("form"), body: fd });
|
||||
const data = await parse<ApiOk<any>>(res);
|
||||
return (data as any)?.data ?? (data as any);
|
||||
}
|
||||
|
||||
export async function removerAnexoMedico(id: string | number, anexoId: string | number): Promise<void> {
|
||||
const url = `${API_BASE}/pacientes/${id}/anexos/${anexoId}`; // FORÇA /pacientes
|
||||
const res = await fetch(url, { method: "DELETE", headers: headers("json") });
|
||||
await parse<any>(res);
|
||||
}
|
||||
// ======= FIM: médicos usando rotas de pacientes =======
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user