diff --git a/src/api/constants.ts b/src/api/constants.ts index 3c87003..3f6c910 100644 --- a/src/api/constants.ts +++ b/src/api/constants.ts @@ -278,3 +278,4 @@ export const API_LCRS = `${API_BASE_URL}/Lcrs`; export const API_LCR_ROUTES = `${API_BASE_URL}/LcrRoutes`; export const API_LCR_CARRIER_SET_ENTRIES = `${API_BASE_URL}/LcrCarrierSetEntries`; export const API_TTS_CACHE = `${API_BASE_URL}/TtsCache`; +export const API_CLIENTS = `${API_BASE_URL}/Clients`; diff --git a/src/api/index.ts b/src/api/index.ts index 9a11132..d47cf56 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -25,6 +25,7 @@ import { API_LCR_CARRIER_SET_ENTRIES, API_LCRS, API_TTS_CACHE, + API_CLIENTS, } from "./constants"; import { ROUTE_LOGIN } from "src/router/routes"; import { @@ -72,6 +73,7 @@ import type { LcrCarrierSetEntry, BucketCredential, BucketCredentialTestResult, + Client, } from "./types"; import { StatusCodes } from "./types"; import { JaegerRoot } from "./jaeger-types"; @@ -407,6 +409,10 @@ export const postLcrCarrierSetEntry = ( payload ); }; + +export const postClient = (payload: Partial) => { + return postFetch>(API_CLIENTS, payload); +}; /** Named wrappers for `putFetch` */ export const putUser = (sid: string, payload: Partial) => { @@ -520,6 +526,12 @@ export const putLcrCarrierSetEntries = ( ); }; +export const putClient = (sid: string, payload: Partial) => { + return putFetch>( + `${API_CLIENTS}/${sid}`, + payload + ); +}; /** Named wrappers for `deleteFetch` */ export const deleteUser = (sid: string) => { @@ -599,6 +611,9 @@ export const deleteAccountTtsCache = (sid: string) => { return deleteFetch(`${API_BASE_URL}/Accounts/${sid}/TtsCache`); }; +export const deleteClient = (sid: string) => { + return deleteFetch(`${API_CLIENTS}/${sid}`); +}; /** Named wrappers for `getFetch` */ export const getUser = (sid: string) => { @@ -637,6 +652,14 @@ export const getLcrCarrierSetEtries = (sid: string) => { ); }; +export const getClients = () => { + return getFetch(API_CLIENTS); +}; + +export const getClient = (sid: string) => { + return getFetch(`${API_CLIENTS}/${sid}`); +}; + /** Wrappers for APIs that can have a mock dev server response */ export const getRecentCalls = (sid: string, query: Partial) => { diff --git a/src/api/types.ts b/src/api/types.ts index 6bd0771..4c60481 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -453,6 +453,14 @@ export interface LcrCarrierSetEntry { priority: number; } +export interface Client { + client_sid?: null | string; + account_sid: null | string; + username: null | string; + password?: null | string; + is_active: boolean; +} + export interface PageQuery { page: number; count: number; diff --git a/src/components/account-filter.tsx b/src/components/account-filter.tsx index 02ead11..67decb4 100644 --- a/src/components/account-filter.tsx +++ b/src/components/account-filter.tsx @@ -38,7 +38,7 @@ export const AccountFilter = ({ return (
- + {label && }
setUsername(e.target.value)} + /> +
+
+ + +
+ + +
+ {user?.scope !== USER_ACCOUNT && ( +
+ +
+ )} +
+ + + + {client && client.data && ( + + )} + +
+ + + {client && client.data && modal && ( + + )} + + ); +}; + +export default ClientsForm; diff --git a/src/containers/internal/views/clients/index.tsx b/src/containers/internal/views/clients/index.tsx new file mode 100644 index 0000000..e8a0487 --- /dev/null +++ b/src/containers/internal/views/clients/index.tsx @@ -0,0 +1,175 @@ +import { Button, H1, Icon, M } from "@jambonz/ui-kit"; +import React, { useMemo, useState } from "react"; +import { Link } from "react-router-dom"; +import { deleteClient, useApiData, useServiceProviderData } from "src/api"; +import { Account, Client } from "src/api/types"; +import { + AccountFilter, + Icons, + ScopedAccess, + SearchFilter, + Section, + Spinner, +} from "src/components"; +import { ROUTE_INTERNAL_CLIENTS } from "src/router/routes"; +import { toastError, toastSuccess, useSelectState } from "src/store"; +import { Scope } from "src/store/types"; +import { hasLength, hasValue, useFilteredResults } from "src/utils"; +import ClientsDelete from "./delete"; +import { USER_ACCOUNT } from "src/api/constants"; + +export const Clients = () => { + const user = useSelectState("user"); + const [accounts] = useServiceProviderData("Accounts"); + const [clients, refetch] = useApiData("Clients"); + + const [accountSid, setAccountSid] = useState(""); + const [filter, setFilter] = useState(""); + const [client, setClient] = useState(); + + const tmpFilteredClients = useMemo(() => { + if (user?.account_sid && user?.scope === USER_ACCOUNT) { + setAccountSid(user?.account_sid); + return clients; + } + + return clients + ? clients.filter((c) => { + return accountSid ? c.account_sid === accountSid : true; + }) + : []; + }, [accountSid, clients]); + + const filteredClients = useFilteredResults(filter, tmpFilteredClients); + + const handleDelete = () => { + if (client) { + deleteClient(client.client_sid || "") + .then(() => { + toastSuccess( + <> + Deleted outbound call route {client.username} + + ); + setClient(null); + refetch(); + }) + .catch((error) => { + toastError(error.msg); + }); + } + }; + + return ( + <> +
+

Clients

+ + {" "} + + + + +
+ +
+ + + + +
+
+
+ {!hasValue(filteredClients) && hasLength(accounts) ? ( + + ) : hasLength(filteredClients) ? ( + filteredClients.map((c) => ( +
+
+
+ + {c.username} + + +
+
+
+
+ {c.is_active ? ( + + ) : ( + + )} + {c.is_active ? "Active" : "Inactive"} +
+
+
+
+ + + { + accounts?.find( + (acct) => acct.account_sid === c.account_sid + )?.name + } + +
+
+
+
+
+ + + + +
+
+ )) + ) : ( + No Clients. + )} +
+
+
+ +
+ {client && ( + setClient(null)} + handleSubmit={handleDelete} + /> + )} + + ); +}; + +export default Clients; diff --git a/src/containers/internal/views/least-cost-routing/index.tsx b/src/containers/internal/views/least-cost-routing/index.tsx index 6ff25a9..2b4e4d5 100644 --- a/src/containers/internal/views/least-cost-routing/index.tsx +++ b/src/containers/internal/views/least-cost-routing/index.tsx @@ -183,7 +183,7 @@ export const Lcrs = () => {
diff --git a/src/router/index.tsx b/src/router/index.tsx index 8a157f6..f9a99a2 100644 --- a/src/router/index.tsx +++ b/src/router/index.tsx @@ -42,6 +42,9 @@ import MSTeamsTenantsEdit from "src/containers/internal/views/ms-teams-tenants/e import Lcrs from "src/containers/internal/views/least-cost-routing"; import LcrsAdd from "src/containers/internal/views/least-cost-routing/add"; import LcrsEdit from "src/containers/internal/views/least-cost-routing/edit"; +import Clients from "src/containers/internal/views/clients"; +import ClientsAdd from "src/containers/internal/views/clients/add"; +import ClientsEdit from "src/containers/internal/views/clients/edit"; export const Router = () => { const toast = useSelectState("toast"); @@ -138,6 +141,13 @@ export const Router = () => { element={} /> + } /> + } /> + } + /> + {/* 404 page not found */} } /> diff --git a/src/router/routes.ts b/src/router/routes.ts index 7019f60..383125b 100644 --- a/src/router/routes.ts +++ b/src/router/routes.ts @@ -4,6 +4,7 @@ export const ROUTE_FORGOT_PASSWORD = "/forgot-password"; export const ROUTE_INTERNAL_USERS = "/internal/users"; export const ROUTE_INTERNAL_SETTINGS = "/internal/settings"; export const ROUTE_INTERNAL_ACCOUNTS = "/internal/accounts"; +export const ROUTE_INTERNAL_CLIENTS = "/internal/clients"; export const ROUTE_INTERNAL_APPLICATIONS = "/internal/applications"; export const ROUTE_INTERNAL_RECENT_CALLS = "/internal/recent-calls"; export const ROUTE_INTERNAL_ALERTS = "/internal/alerts";