From 561c412b9ef789a9f075dc6cee18002addb8c8d0 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Wed, 18 Feb 2026 18:20:36 -0800 Subject: [PATCH] feat: add customer dashboard to UI Add a comprehensive customer management dashboard following the Teams page pattern. Features: - Customer list view with filtering and search - Create new customers with budget, model, and region settings - Edit customer details including blocking/unblocking - Delete customers with confirmation - View customer spend and budget information - Filter by status (active/blocked) and region (US/EU) Components: - CustomersView: Main view component with state management - CustomersTable: Table component displaying customer data - CustomersFilters: Search and filter controls - CustomersHeaderTabs: Tab navigation with refresh - Modals: Create, Edit, and Delete customer modals - useFetchCustomers: Custom hook for data fetching API Integration: - Added customerCreateCall, customerUpdateCall, customerDeleteCall - Added Customer interface type definition - Integrated with existing allEndUsersCall Co-Authored-By: Claude Opus 4.6 --- .../(dashboard)/customers/CustomersView.tsx | 220 ++++++++++++++++++ .../customers/components/CustomersFilters.tsx | 88 +++++++ .../components/CustomersHeaderTabs.tsx | 45 ++++ .../customers/components/CustomersTable.tsx | 142 +++++++++++ .../components/modals/CreateCustomerModal.tsx | 137 +++++++++++ .../components/modals/CustomerInfoModal.tsx | 167 +++++++++++++ .../components/modals/DeleteCustomerModal.tsx | 52 +++++ .../customers/hooks/useFetchCustomers.ts | 36 +++ .../src/app/(dashboard)/customers/page.tsx | 45 ++++ .../src/app/(dashboard)/customers/types.ts | 26 +++ .../src/components/networking.tsx | 105 +++++++++ 11 files changed, 1063 insertions(+) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/CustomersView.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersFilters.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersHeaderTabs.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersTable.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/CreateCustomerModal.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/CustomerInfoModal.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/DeleteCustomerModal.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/hooks/useFetchCustomers.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/page.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/customers/types.ts diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/CustomersView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/customers/CustomersView.tsx new file mode 100644 index 00000000000..2890306abdb --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/CustomersView.tsx @@ -0,0 +1,220 @@ +import React, { useState } from "react"; +import { Button, Card, Col, Grid, TabPanel, Text } from "@tremor/react"; +import { RefreshCw } from "lucide-react"; +import CustomersTable from "@/app/(dashboard)/customers/components/CustomersTable"; +import CreateCustomerModal from "@/app/(dashboard)/customers/components/modals/CreateCustomerModal"; +import CustomerInfoModal from "@/app/(dashboard)/customers/components/modals/CustomerInfoModal"; +import DeleteCustomerModal from "@/app/(dashboard)/customers/components/modals/DeleteCustomerModal"; +import CustomersHeaderTabs from "@/app/(dashboard)/customers/components/CustomersHeaderTabs"; +import CustomersFilters from "@/app/(dashboard)/customers/components/CustomersFilters"; +import type { Customer, NewCustomerData } from "@/app/(dashboard)/customers/types"; +import { customerCreateCall, customerDeleteCall, customerUpdateCall } from "@/components/networking"; + +interface CustomersViewProps { + customers: Customer[]; + setCustomers: React.Dispatch>; + accessToken: string | null; + userID: string | null; + userRole: string | null; + isLoading: boolean; +} + +interface FilterState { + user_id: string; + alias: string; + blocked: string; + region: string; +} + +const CustomersView: React.FC = ({ + customers, + setCustomers, + accessToken, + userID, + userRole, + isLoading, +}) => { + const [showFilters, setShowFilters] = useState(false); + const [filters, setFilters] = useState({ + user_id: "", + alias: "", + blocked: "", + region: "", + }); + + const [showCreateModal, setShowCreateModal] = useState(false); + const [showInfoModal, setShowInfoModal] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const [selectedCustomer, setSelectedCustomer] = useState(null); + const [lastRefreshed, setLastRefreshed] = useState(new Date().toLocaleString("en-US")); + + const handleCreateCustomer = async (data: NewCustomerData) => { + if (!accessToken) return; + + try { + const response = await customerCreateCall(accessToken, data); + if (response) { + // Refresh the customer list + const { allEndUsersCall } = await import("@/components/networking"); + const listData = await allEndUsersCall(accessToken); + if (listData) { + setCustomers(Array.isArray(listData) ? listData : []); + } + } + setShowCreateModal(false); + } catch (error) { + console.error("Error creating customer:", error); + } + }; + + const handleEditCustomer = (customer: Customer) => { + setSelectedCustomer(customer); + setShowInfoModal(true); + }; + + const handleViewInfo = (customer: Customer) => { + setSelectedCustomer(customer); + setShowInfoModal(true); + }; + + const handleSaveCustomer = async (updated: Customer) => { + if (!accessToken) return; + + try { + await customerUpdateCall(accessToken, updated); + setCustomers( + customers.map((c) => (c.user_id === updated.user_id ? updated : c)) + ); + setShowInfoModal(false); + setSelectedCustomer(null); + } catch (error) { + console.error("Error updating customer:", error); + } + }; + + const handleDeleteClick = (customer: Customer) => { + setSelectedCustomer(customer); + setShowDeleteModal(true); + }; + + const handleConfirmDelete = async () => { + if (!selectedCustomer || !accessToken) return; + + try { + await customerDeleteCall(accessToken, selectedCustomer.user_id); + setCustomers( + customers.filter((c) => c.user_id !== selectedCustomer.user_id) + ); + setShowDeleteModal(false); + setSelectedCustomer(null); + } catch (error) { + console.error("Error deleting customer:", error); + } + }; + + const handleRefresh = () => { + setLastRefreshed(new Date().toLocaleString("en-US")); + // Trigger a re-fetch if needed + }; + + const handleFilterChange = (key: keyof FilterState, value: string) => { + setFilters({ ...filters, [key]: value }); + }; + + const handleFilterReset = () => { + setFilters({ + user_id: "", + alias: "", + blocked: "", + region: "", + }); + }; + + const filteredCustomers = customers.filter((c) => { + const matchesUserId = !filters.user_id || c.user_id.toLowerCase().includes(filters.user_id.toLowerCase()); + const matchesAlias = !filters.alias || (c.alias && c.alias.toLowerCase().includes(filters.alias.toLowerCase())); + const matchesBlocked = + !filters.blocked || + (filters.blocked === "active" && !c.blocked) || + (filters.blocked === "blocked" && c.blocked); + const matchesRegion = !filters.region || c.allowed_model_region === filters.region; + + return matchesUserId && matchesAlias && matchesBlocked && matchesRegion; + }); + + return ( +
+ + + {(userRole === "Admin" || userRole === "Org Admin") && ( + + )} + + + + + Click on “Customer ID” to view customer details and manage settings. + + + + +
+ +
+ +
+ +
+
+
+ + {(userRole === "Admin" || userRole === "Org Admin") && ( + <> + setShowCreateModal(false)} + onCreate={handleCreateCustomer} + /> + { + setShowInfoModal(false); + setSelectedCustomer(null); + }} + customer={selectedCustomer} + onSave={handleSaveCustomer} + /> + { + setShowDeleteModal(false); + setSelectedCustomer(null); + }} + onConfirm={handleConfirmDelete} + customerName={selectedCustomer?.alias || ""} + customerId={selectedCustomer?.user_id || ""} + /> + + )} + +
+
+ ); +}; + +export default CustomersView; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersFilters.tsx b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersFilters.tsx new file mode 100644 index 00000000000..27fc22adb11 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersFilters.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import { Button, TextInput } from "@tremor/react"; +import { Filter, RotateCcw, Search } from "lucide-react"; +import { Select } from "antd"; + +interface CustomersFiltersProps { + filters: { + user_id: string; + alias: string; + blocked: string; + region: string; + }; + showFilters: boolean; + onToggleFilters: (show: boolean) => void; + onChange: (key: string, value: string) => void; + onReset: () => void; +} + +const CustomersFilters: React.FC = ({ + filters, + showFilters, + onToggleFilters, + onChange, + onReset, +}) => { + return ( +
+
+
+ + onChange("user_id", e.target.value)} + className="pl-10" + /> +
+ + +
+ + {showFilters && ( +
+
+ + +
+
+ + +
+
+ )} +
+ ); +}; + +export default CustomersFilters; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersHeaderTabs.tsx b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersHeaderTabs.tsx new file mode 100644 index 00000000000..951f46b78ae --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersHeaderTabs.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import { Tab, TabGroup, TabList, TabPanels, Text } from "@tremor/react"; +import { RefreshCw } from "lucide-react"; + +interface CustomersHeaderTabsProps { + lastRefreshed: string; + onRefresh: () => void; + userRole: string | null; + children: React.ReactNode; +} + +const CustomersHeaderTabs: React.FC = ({ + lastRefreshed, + onRefresh, + userRole, + children, +}) => { + return ( +
+
+ + + Your Customers + {(userRole === "Admin" || userRole === "Org Admin") && ( + Customer Settings + )} + + {children} + +
+ Last Refreshed: {lastRefreshed} + +
+
+
+ ); +}; + +export default CustomersHeaderTabs; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersTable.tsx b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersTable.tsx new file mode 100644 index 00000000000..165f927ba63 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/CustomersTable.tsx @@ -0,0 +1,142 @@ +import React from "react"; +import { + Button, + Icon, + Table, + TableBody, + TableCell, + TableHead, + TableHeaderCell, + TableRow, + Badge, + Text, +} from "@tremor/react"; +import { PencilAltIcon, TrashIcon } from "@heroicons/react/outline"; +import { Tooltip } from "antd"; +import { formatNumberWithCommas } from "@/utils/dataUtils"; +import type { Customer } from "@/app/(dashboard)/customers/types"; + +interface CustomersTableProps { + customers: Customer[]; + userRole: string | null; + onEdit: (customer: Customer) => void; + onDelete: (customer: Customer) => void; + onViewInfo: (customer: Customer) => void; + isLoading: boolean; +} + +const CustomersTable: React.FC = ({ + customers, + userRole, + onEdit, + onDelete, + onViewInfo, + isLoading, +}) => { + const truncateId = (id: string) => { + if (id.length <= 10) return id; + return id.substring(0, 7) + "..."; + }; + + if (isLoading) { + return ( +
+ Loading customers... +
+ ); + } + + return ( + + + + Customer Name + Customer ID + Spend (USD) + Budget (USD) + Default Model + Region + Status + {(userRole === "Admin" || userRole === "Org Admin") && ( + Actions + )} + + + + {customers.length === 0 ? ( + + + No customers found. + + + ) : ( + customers.map((customer) => ( + + {customer.alias || "—"} + + + + + + {formatNumberWithCommas(customer.spend, 4)} + + {customer.litellm_budget_table?.max_budget !== null && + customer.litellm_budget_table?.max_budget !== undefined + ? customer.litellm_budget_table.max_budget.toString() + : "No limit"} + + + {customer.default_model ? ( + {customer.default_model} + ) : ( + + )} + + + {customer.allowed_model_region ? ( + {customer.allowed_model_region.toUpperCase()} + ) : ( + + )} + + + {customer.blocked ? ( + Blocked + ) : ( + Active + )} + + {(userRole === "Admin" || userRole === "Org Admin") && ( + +
+ onEdit(customer)} + className="cursor-pointer" + /> + onDelete(customer)} + className="cursor-pointer" + /> +
+
+ )} +
+ )) + )} +
+
+ ); +}; + +export default CustomersTable; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/CreateCustomerModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/CreateCustomerModal.tsx new file mode 100644 index 00000000000..46d4648c427 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/CreateCustomerModal.tsx @@ -0,0 +1,137 @@ +import React, { useState } from "react"; +import { Modal, Form, Input, InputNumber, Select as AntSelect } from "antd"; +import type { NewCustomerData } from "@/app/(dashboard)/customers/types"; + +interface CreateCustomerModalProps { + isOpen: boolean; + onClose: () => void; + onCreate: (customer: NewCustomerData) => void; +} + +const CreateCustomerModal: React.FC = ({ + isOpen, + onClose, + onCreate, +}) => { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + + const handleOk = async () => { + try { + const values = await form.validateFields(); + setLoading(true); + onCreate(values); + form.resetFields(); + } catch (error) { + console.error("Validation failed:", error); + } finally { + setLoading(false); + } + }; + + const handleCancel = () => { + form.resetFields(); + onClose(); + }; + + return ( + +
+ + + + + + + + +
+ + + + + + + +
+ +
+ + + gpt-4o + gpt-4o-mini + gpt-4-turbo + claude-3-sonnet + claude-3-opus + claude-3-haiku + + + + + + Any region + US + EU + + +
+ + + + + + + + +
+
+ ); +}; + +export default CreateCustomerModal; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/CustomerInfoModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/CustomerInfoModal.tsx new file mode 100644 index 00000000000..9c1f40933cc --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/CustomerInfoModal.tsx @@ -0,0 +1,167 @@ +import React, { useEffect, useState } from "react"; +import { Modal, Form, Input, InputNumber, Select as AntSelect, Switch } from "antd"; +import type { Customer } from "@/app/(dashboard)/customers/types"; + +interface CustomerInfoModalProps { + isOpen: boolean; + onClose: () => void; + customer: Customer | null; + onSave: (customer: Customer) => void; +} + +const CustomerInfoModal: React.FC = ({ + isOpen, + onClose, + customer, + onSave, +}) => { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (customer) { + form.setFieldsValue({ + ...customer, + max_budget: customer.litellm_budget_table?.max_budget, + budget_duration: customer.litellm_budget_table?.budget_duration, + }); + } + }, [customer, form]); + + const handleOk = async () => { + if (!customer) return; + + try { + const values = await form.validateFields(); + setLoading(true); + onSave({ + ...customer, + ...values, + }); + } catch (error) { + console.error("Validation failed:", error); + } finally { + setLoading(false); + } + }; + + const handleCancel = () => { + form.resetFields(); + onClose(); + }; + + if (!customer) return null; + + return ( + +
+ + + + + + + + +
+ + + + + + + +
+ + + + + +
+ + + None + gpt-4o + gpt-4o-mini + gpt-4-turbo + claude-3-sonnet + claude-3-opus + claude-3-haiku + + + + + + Any region + US + EU + + +
+ + + + + + +
+ + + {form.getFieldValue("blocked") + ? "This customer is currently blocked from making requests" + : "This customer can make requests normally"} + +
+
+
+
+ ); +}; + +export default CustomerInfoModal; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/DeleteCustomerModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/DeleteCustomerModal.tsx new file mode 100644 index 00000000000..6852e2b42e0 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/components/modals/DeleteCustomerModal.tsx @@ -0,0 +1,52 @@ +import React from "react"; +import { Modal } from "antd"; +import { AlertTriangle } from "lucide-react"; + +interface DeleteCustomerModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + customerName: string; + customerId: string; +} + +const DeleteCustomerModal: React.FC = ({ + isOpen, + onClose, + onConfirm, + customerName, + customerId, +}) => { + return ( + + + Delete Customer + + } + open={isOpen} + onOk={onConfirm} + onCancel={onClose} + okText="Delete" + okButtonProps={{ danger: true }} + cancelText="Cancel" + > +
+

+ Are you sure you want to delete this customer? +

+

+ This will permanently delete{" "} + + {customerName || customerId} + {" "} + ({customerId}). This action + cannot be undone. +

+
+
+ ); +}; + +export default DeleteCustomerModal; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/hooks/useFetchCustomers.ts b/ui/litellm-dashboard/src/app/(dashboard)/customers/hooks/useFetchCustomers.ts new file mode 100644 index 00000000000..42bab34dfc5 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/hooks/useFetchCustomers.ts @@ -0,0 +1,36 @@ +import { useCallback, useState } from "react"; +import { allEndUsersCall } from "@/components/networking"; + +interface UseFetchCustomersProps { + setCustomers: (customers: any[]) => void; +} + +const useFetchCustomers = ({ setCustomers }: UseFetchCustomersProps) => { + const [lastRefreshed, setLastRefreshed] = useState( + new Date().toLocaleString("en-US") + ); + + const onRefreshClick = useCallback( + async (accessToken: string | null) => { + if (!accessToken) return; + + try { + const data = await allEndUsersCall(accessToken); + if (data) { + setCustomers(data); + } + setLastRefreshed(new Date().toLocaleString("en-US")); + } catch (error) { + console.error("Error fetching customers:", error); + } + }, + [setCustomers] + ); + + return { + lastRefreshed, + onRefreshClick, + }; +}; + +export default useFetchCustomers; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/page.tsx b/ui/litellm-dashboard/src/app/(dashboard)/customers/page.tsx new file mode 100644 index 00000000000..782763bd615 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/page.tsx @@ -0,0 +1,45 @@ +"use client"; + +import CustomersView from "@/app/(dashboard)/customers/CustomersView"; +import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; +import { useEffect, useState } from "react"; +import { allEndUsersCall, type Customer } from "@/components/networking"; + +const CustomersPage = () => { + const { accessToken, userId, userRole } = useAuthorized(); + const [customers, setCustomers] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchCustomers = async () => { + if (!accessToken) return; + + setIsLoading(true); + try { + const response = await allEndUsersCall(accessToken); + if (response) { + setCustomers(Array.isArray(response) ? response : []); + } + } catch (error) { + console.error("Error fetching customers:", error); + } finally { + setIsLoading(false); + } + }; + + fetchCustomers(); + }, [accessToken]); + + return ( + + ); +}; + +export default CustomersPage; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/customers/types.ts b/ui/litellm-dashboard/src/app/(dashboard)/customers/types.ts new file mode 100644 index 00000000000..f054a9493a6 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/customers/types.ts @@ -0,0 +1,26 @@ +export interface Customer { + user_id: string; + alias: string | null; + spend: number; + allowed_model_region: string | null; + default_model: string | null; + budget_id: string | null; + blocked: boolean; + max_budget?: number | null; + budget_duration?: string | null; + litellm_budget_table?: { + max_budget: number | null; + budget_duration: string | null; + } | null; +} + +export interface NewCustomerData { + user_id: string; + alias?: string; + max_budget?: string; + budget_id?: string; + default_model?: string; + allowed_model_region?: string; + budget_duration?: string; + metadata?: string; +} diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 9d9ff575c8c..01dad184c59 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -194,6 +194,20 @@ export interface Organization { }; } +export interface Customer { + user_id: string; + alias: string | null; + spend: number; + allowed_model_region: string | null; + default_model: string | null; + budget_id: string | null; + blocked: boolean; + litellm_budget_table?: { + max_budget: number | null; + budget_duration: string | null; + } | null; +} + export interface CredentialItem { credential_name: string; credential_values: any; @@ -2511,6 +2525,97 @@ export const allEndUsersCall = async (accessToken: string) => { } }; +export const customerListCall = async (accessToken: string) => { + return allEndUsersCall(accessToken); +}; + +export const customerCreateCall = async (accessToken: string, data: any) => { + try { + let url = proxyBaseUrl ? `${proxyBaseUrl}/customer/new` : `/customer/new`; + + const response = await fetch(url, { + method: "POST", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + const errorData = await response.json(); + const errorMessage = deriveErrorMessage(errorData); + handleError(errorMessage); + throw new Error(errorMessage); + } + + const responseData = await response.json(); + console.log("Customer created:", responseData); + return responseData; + } catch (error) { + console.error("Failed to create customer:", error); + throw error; + } +}; + +export const customerUpdateCall = async (accessToken: string, data: any) => { + try { + let url = proxyBaseUrl ? `${proxyBaseUrl}/customer/update` : `/customer/update`; + + const response = await fetch(url, { + method: "POST", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + const errorData = await response.json(); + const errorMessage = deriveErrorMessage(errorData); + handleError(errorMessage); + throw new Error(errorMessage); + } + + const responseData = await response.json(); + console.log("Customer updated:", responseData); + return responseData; + } catch (error) { + console.error("Failed to update customer:", error); + throw error; + } +}; + +export const customerDeleteCall = async (accessToken: string, userId: string) => { + try { + let url = proxyBaseUrl ? `${proxyBaseUrl}/customer/delete` : `/customer/delete`; + + const response = await fetch(url, { + method: "POST", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ user_ids: [userId] }), + }); + + if (!response.ok) { + const errorData = await response.json(); + const errorMessage = deriveErrorMessage(errorData); + handleError(errorMessage); + throw new Error(errorMessage); + } + + const responseData = await response.json(); + console.log("Customer deleted:", responseData); + return responseData; + } catch (error) { + console.error("Failed to delete customer:", error); + throw error; + } +}; + export const userFilterUICall = async (accessToken: string, params: URLSearchParams) => { try { let url = proxyBaseUrl ? `${proxyBaseUrl}/user/filter/ui` : `/user/filter/ui`;