/* ┌──────────────────────────────────────────────────────────────────────────────┐ │ @author: Davidson Gomes │ │ @file: /app/clients/page.tsx │ │ Developed by: Davidson Gomes │ │ Creation date: May 13, 2025 │ │ Contact: contato@evolution-api.com │ ├──────────────────────────────────────────────────────────────────────────────┤ │ @copyright © Evolution API 2025. All rights reserved. │ │ Licensed under the Apache License, Version 2.0 │ │ │ │ You may not use this file except in compliance with the License. │ │ You may obtain a copy of the License at │ │ │ │ http://www.apache.org/licenses/LICENSE-2.0 │ │ │ │ Unless required by applicable law or agreed to in writing, software │ │ distributed under the License is distributed on an "AS IS" BASIS, │ │ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │ │ See the License for the specific language governing permissions and │ │ limitations under the License. │ ├──────────────────────────────────────────────────────────────────────────────┤ │ @important │ │ For any future changes to the code in this file, it is recommended to │ │ include, together with the modification, the information of the developer │ │ who changed it and the date of modification. │ └──────────────────────────────────────────────────────────────────────────────┘ */ "use client" import type React from "react" import { useState, useEffect } from "react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { useToast } from "@/components/ui/use-toast" import { Plus, MoreHorizontal, Edit, Trash2, Search, Users, UserPlus } from "lucide-react" import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog" import { createClient, listClients, getClient, updateClient, deleteClient, impersonateClient, Client, } from "@/services/clientService" import { useRouter } from "next/navigation" export default function ClientsPage() { const { toast } = useToast() const router = useRouter() const [isLoading, setIsLoading] = useState(false) const [searchQuery, setSearchQuery] = useState("") const [isDialogOpen, setIsDialogOpen] = useState(false) const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false) const [selectedClient, setSelectedClient] = useState(null) const [clientData, setClientData] = useState({ name: "", email: "", }) const [page, setPage] = useState(1) const [limit, setLimit] = useState(1000) const [total, setTotal] = useState(0) const [clients, setClients] = useState([]) useEffect(() => { const fetchClients = async () => { setIsLoading(true) try { const res = await listClients((page - 1) * limit, limit) setClients(res.data) setTotal(res.data.length) } catch (error) { toast({ title: "Error loading clients", description: "Unable to load clients.", variant: "destructive", }) } finally { setIsLoading(false) } } fetchClients() }, [page, limit]) const filteredClients = Array.isArray(clients) ? clients.filter( (client) => client.name.toLowerCase().includes(searchQuery.toLowerCase()) || client.email.toLowerCase().includes(searchQuery.toLowerCase()), ) : [] const handleAddClient = async (e: React.FormEvent) => { e.preventDefault() setIsLoading(true) try { if (selectedClient) { await updateClient(selectedClient.id, clientData) toast({ title: "Client updated", description: `${clientData.name} was updated successfully.`, }) } else { await createClient({ ...clientData, password: "Password@123" }) toast({ title: "Client added", description: `${clientData.name} was added successfully.`, }) } setIsDialogOpen(false) resetForm() const res = await listClients((page - 1) * limit, limit) setClients(res.data) setTotal(res.data.length) } catch (error) { toast({ title: "Error", description: "Unable to save client. Please try again.", variant: "destructive", }) } finally { setIsLoading(false) } } const handleEditClient = async (client: Client) => { setIsLoading(true) try { const res = await getClient(client.id) setSelectedClient(res.data) setClientData({ name: res.data.name, email: res.data.email, }) setIsDialogOpen(true) } catch (error) { toast({ title: "Error searching client", description: "Unable to search client.", variant: "destructive", }) } finally { setIsLoading(false) } } const confirmDeleteClient = async () => { if (!selectedClient) return setIsLoading(true) try { await deleteClient(selectedClient.id) toast({ title: "Client deleted", description: `${selectedClient.name} was deleted successfully.`, }) setIsDeleteDialogOpen(false) setSelectedClient(null) const res = await listClients((page - 1) * limit, limit) setClients(res.data) setTotal(res.data.length) } catch (error) { toast({ title: "Error", description: "Unable to delete client. Please try again.", variant: "destructive", }) } finally { setIsLoading(false) } } const handleImpersonateClient = async (client: Client) => { setIsLoading(true) try { const response = await impersonateClient(client.id) const currentUser = localStorage.getItem("user") if (currentUser) { localStorage.setItem("adminUser", currentUser) } const currentToken = document.cookie.match(/access_token=([^;]+)/)?.[1] if (currentToken) { localStorage.setItem("adminToken", currentToken) } localStorage.setItem("isImpersonating", "true") localStorage.setItem("impersonatedClient", client.name) document.cookie = `isImpersonating=true; path=/; max-age=${60 * 60 * 24 * 7}` document.cookie = `impersonatedClient=${encodeURIComponent(client.name)}; path=/; max-age=${60 * 60 * 24 * 7}` document.cookie = `access_token=${response.access_token}; path=/; max-age=${60 * 60 * 24 * 7}` const userData = { ...JSON.parse(localStorage.getItem("user") || "{}"), is_admin: false, client_id: client.id } localStorage.setItem("user", JSON.stringify(userData)) document.cookie = `user=${encodeURIComponent(JSON.stringify(userData))}; path=/; max-age=${60 * 60 * 24 * 7}` toast({ title: "Impersonation mode activated", description: `You are viewing as ${client.name}`, }) router.push("/agents") } catch (error) { console.error("Error impersonating client:", error) toast({ title: "Error", description: "Unable to impersonate client", variant: "destructive", }) } finally { setIsLoading(false) } } const resetForm = () => { setClientData({ name: "", email: "", }) setSelectedClient(null) } return (

Client Management

{selectedClient ? "Edit Client" : "New Client"} {selectedClient ? "Edit the existing client information." : "Fill in the information to create a new client."}
setClientData({ ...clientData, name: e.target.value })} className="bg-[#222] border-[#444] text-white" placeholder="Company name" required />
setClientData({ ...clientData, email: e.target.value })} className="bg-[#222] border-[#444] text-white" placeholder="contact@company.com" required />
Confirm delete Are you sure you want to delete the client "{selectedClient?.name}"? This action cannot be undone. Cancel {isLoading ? "Deleting..." : "Delete"}
Search Clients
setSearchQuery(e.target.value)} className="bg-[#222] border-[#444] text-white pl-10" />
Name Email Created At Users Agents Actions {filteredClients.length > 0 ? ( filteredClients.map((client) => ( {client.name} {client.email} {new Date(client.created_at).toLocaleDateString("pt-BR")}
{client.users_count ?? 0}
{client.agents_count ?? 0} Actions handleEditClient(client)} > Edit handleImpersonateClient(client)} > Enter as client { setSelectedClient(client) setIsDeleteDialogOpen(true) }} > Delete
)) ) : ( No clients found. )}
Page {page} of {Math.ceil(total / limit) || 1}
) }