Chore/carrier apps accounts (#23)

* Scope accounts and applications to current service provider for Phone Numbers

* Scope accounts to current service provider when adding or editing an Application

* Implement account and application logic for add or edit Carrier form

* Implement delete action for service providers
This commit is contained in:
Brandon Lee Kitajchuk
2021-08-28 06:20:49 -07:00
committed by GitHub
parent 0174315a68
commit eda1fa0dc4
4 changed files with 234 additions and 81 deletions
+1 -1
View File
@@ -120,7 +120,7 @@ const ApplicationForm = props => {
applicationsPromise,
]);
const accounts = promiseAllValues[0].data;
const accounts = promiseAllValues[0].data.filter(a => a.service_provider_sid === currentServiceProvider);
const applications = promiseAllValues[1].data;
setAccounts(accounts);
+65 -15
View File
@@ -5,6 +5,7 @@ import styled from "styled-components/macro";
import { Menu, Dropdown } from "antd";
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
import Form from '../elements/Form';
import Input from '../elements/Input';
import PasswordInput from '../elements/PasswordInput';
@@ -18,7 +19,6 @@ import Loader from '../blocks/Loader';
import sortSipGateways from '../../helpers/sortSipGateways';
import Select from '../elements/Select';
import handleErrors from "../../helpers/handleErrors";
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext'
const StyledForm = styled(Form)`
@media (max-width: 978.98px) {
@@ -185,6 +185,8 @@ const CarrierForm = (props) => {
]);
const [ applicationValues, setApplicationValues ] = useState([]);
const [ accounts, setAccounts ] = useState([]);
const [ accountSid, setAccountSid ] = useState('');
const [ carrierSid, setCarrierSid ] = useState('');
const [ showLoader, setShowLoader ] = useState(true);
@@ -220,7 +222,16 @@ const CarrierForm = (props) => {
Authorization: `Bearer ${jwt}`,
},
});
const accountsPromise = axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: '/Accounts',
headers: {
Authorization: `Bearer ${jwt}`,
},
});
promises.push(applicationPromise);
promises.push(accountsPromise);
if (type === 'edit') {
const carrierPromise = axios({
@@ -248,11 +259,12 @@ const CarrierForm = (props) => {
const promiseResponses = await Promise.all(promises);
setApplicationValues(promiseResponses[0].data);
setAccounts(promiseResponses[1].data);
if (type === 'edit') {
const carrier = promiseResponses[1].data;
const allSipGateways = promiseResponses[2].data;
const carrier = promiseResponses[2].data;
const allSipGateways = promiseResponses[3].data;
if (!carrier) {
isMounted = false;
@@ -274,6 +286,7 @@ const CarrierForm = (props) => {
setName(carrier.name || '');
setE164(carrier.e164_leading_plus === 1);
setApplication(carrier.application_sid || '');
setAccountSid(carrier.account_sid || '');
setAuthenticate(carrier.register_username ? true : false);
setRegister(carrier.requires_register === 1);
setUsername(carrier.register_username || '');
@@ -639,6 +652,7 @@ const CarrierForm = (props) => {
name: name.trim() || null,
e164_leading_plus: e164 ? 1 : 0,
application_sid: application || null,
account_sid: accountSid || null,
requires_register: register ? 1 : 0,
register_username: username ? username.trim() : null,
register_password: password ? password : null,
@@ -894,29 +908,65 @@ const CarrierForm = (props) => {
onChange={e => setE164(e.target.checked)}
/>
<Label htmlFor="application">Application</Label>
<Label htmlFor="account">Used by</Label>
<Select
name="application"
id="application"
value={application}
onChange={e => setApplication(e.target.value)}
name="account"
id="account"
value={accountSid}
onChange={(e) => {
setAccountSid(e.target.value);
setApplication('');
}}
>
<option value="">
{type === 'add'
? '-- OPTIONAL: Application to invoke on calls arriving from this carrier --'
: '-- NONE --'
}
All accounts
</option>
{applicationValues.map(a => (
{accounts.filter(a => a.service_provider_sid === currentServiceProvider).map(a => (
<option
key={a.application_sid}
value={a.application_sid}
key={a.account_sid}
value={a.account_sid}
>
{a.name}
</option>
))}
</Select>
{accountSid && (
<>
<Label htmlFor="application">Default Application</Label>
<Select
name="application"
id="application"
value={application}
onChange={e => setApplication(e.target.value)}
>
<option value="">
{type === 'add'
? '-- OPTIONAL: Application to invoke on calls arriving from this carrier --'
: '-- NONE --'
}
</option>
{applicationValues.filter((a) => {
// Map an application to a service provider through it's account_sid
const acct = accounts.find(ac => a.account_sid === ac.account_sid);
if (accountSid) {
return a.account_sid === accountSid;
}
return acct.service_provider_sid === currentServiceProvider;
}).map(a => (
<option
key={a.application_sid}
value={a.application_sid}
>
{a.name}
</option>
))}
</Select>
</>
)}
<hr style={{ margin: '0.5rem -2rem' }} />
{
+17 -3
View File
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useContext, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
import Form from '../elements/Form';
import Input from '../elements/Input';
import Label from '../elements/Label';
@@ -16,6 +17,7 @@ const PhoneNumberForm = props => {
let history = useHistory();
const dispatch = useContext(NotificationDispatchContext);
const currentServiceProvider = useContext(ServiceProviderValueContext);
// Refs
const refPhoneNumber = useRef(null);
@@ -358,7 +360,10 @@ const PhoneNumberForm = props => {
name="account"
id="account"
value={account}
onChange={e => setAccount(e.target.value)}
onChange={(e) => {
setAccount(e.target.value);
setApplication('');
}}
invalid={invalidAccount}
ref={refAccount}
>
@@ -368,7 +373,7 @@ const PhoneNumberForm = props => {
) && (
<option value="">-- Choose the account that this phone number should be associated with --</option>
)}
{accountValues.map(a => (
{accountValues.filter(a => a.service_provider_sid === currentServiceProvider).map(a => (
<option
key={a.account_sid}
value={a.account_sid}
@@ -391,7 +396,16 @@ const PhoneNumberForm = props => {
: '-- NONE --'
}
</option>
{applicationValues.map(a => (
{applicationValues.filter((a) => {
// Map an application to a service provider through it's account_sid
const acct = accountValues.find(ac => a.account_sid === ac.account_sid);
if (account) {
return a.account_sid === account;
}
return acct.service_provider_sid === currentServiceProvider;
}).map(a => (
<option
key={a.application_sid}
value={a.application_sid}
+151 -62
View File
@@ -1,6 +1,7 @@
import React, { useState, useEffect, useContext, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import styled from 'styled-components';
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
import { ShowMsTeamsDispatchContext } from '../../contexts/ShowMsTeamsContext';
import Form from '../elements/Form';
@@ -11,9 +12,23 @@ import InputGroup from '../elements/InputGroup';
import FormError from '../blocks/FormError';
import Button from '../elements/Button';
import Loader from '../blocks/Loader';
import Modal from '../blocks/Modal';
import { ServiceProviderValueContext } from '../../contexts/ServiceProviderContext';
import handleErrors from "../../helpers/handleErrors";
const Td = styled.td`
padding: 0.5rem 0;
&:first-child {
font-weight: 500;
padding-right: 1.5rem;
vertical-align: top;
}
& ul {
margin: 0;
padding-left: 1.25rem;
}
`;
const SettingsForm = () => {
const history = useHistory();
const dispatch = useContext(NotificationDispatchContext);
@@ -40,8 +55,9 @@ const SettingsForm = () => {
const [ showLoader, setShowLoader ] = useState(true);
const [ errorMessage, setErrorMessage ] = useState('');
const [ serviceProviderSid, setServiceProviderSid ] = useState('');
const [ serviceProviders, setServiceProviders ] = useState([]);
const [ confirmDelete, setConfirmDelete ] = useState(false);
useEffect(() => {
const getSettingsData = async () => {
@@ -59,14 +75,16 @@ const SettingsForm = () => {
const serviceProvidersResponse = await axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: `/ServiceProviders/${currentServiceProvider}`,
url: `/ServiceProviders`,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
const sp = serviceProvidersResponse.data;
const sps = serviceProvidersResponse.data;
const sp = sps.find(s => s.service_provider_sid === currentServiceProvider);
setServiceProviders(sps);
setServiceProviderName(sp.name || '');
setServiceProviderSid(sp.service_provider_sid || '');
setEnableMsTeams(sp.ms_teams_fqdn ? true : false);
@@ -96,6 +114,32 @@ const SettingsForm = () => {
setEnableMsTeams(e.target.checked);
};
const handleDelete = () => {
setErrorMessage('');
axios({
method: 'delete',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: `/ServiceProviders/${serviceProviderSid}`,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
})
.then(() => {
setConfirmDelete(false);
setErrorMessage('');
history.push('/internal/accounts');
dispatch({
type: 'ADD',
level: 'success',
message: 'Service Provider Deleted'
});
})
.catch((error) => {
setErrorMessage(error.response.data.msg);
});
};
const handleSubmit = async (e) => {
let isMounted = true;
try {
@@ -210,69 +254,114 @@ const SettingsForm = () => {
return (
showLoader
? <Loader height="365px" />
: <Form
large
wideLabel
onSubmit={handleSubmit}
>
<Label htmlFor="serviceProviderName">Service Provider Name</Label>
<Input
name="serviceProviderName"
id="serviceProviderName"
value={serviceProviderName}
onChange={e => setServiceProviderName(e.target.value)}
invalid={invalidServiceProviderName}
ref={refServiceProviderName}
/>
<div>{/* needed for CSS grid layout */}</div>
<Checkbox
noLeftMargin
id="enableMsTeams"
label="Enable Microsoft Teams Direct Routing"
checked={enableMsTeams}
onChange={toggleMsTeams}
invalid={invalidEnableMsTeams}
ref={refEnableMsTeams}
/>
: (
<>
<Form
large
wideLabel
onSubmit={handleSubmit}
>
<Label htmlFor="serviceProviderName">Service Provider Name</Label>
<Input
name="serviceProviderName"
id="serviceProviderName"
value={serviceProviderName}
onChange={e => setServiceProviderName(e.target.value)}
invalid={invalidServiceProviderName}
ref={refServiceProviderName}
/>
<div>{/* needed for CSS grid layout */}</div>
<Checkbox
noLeftMargin
id="enableMsTeams"
label="Enable Microsoft Teams Direct Routing"
checked={enableMsTeams}
onChange={toggleMsTeams}
invalid={invalidEnableMsTeams}
ref={refEnableMsTeams}
/>
<Label htmlFor="sbcDomainName">SBC Domain Name</Label>
<Input
name="sbcDomainName"
id="sbcDomainName"
value={sbcDomainName}
onChange={e => setSbcDomainName(e.target.value)}
placeholder="Fully qualified domain name used for Microsoft Teams"
invalid={invalidSbcDomainName}
autoFocus={enableMsTeams}
ref={refSbcDomainName}
disabled={!enableMsTeams}
title={(!enableMsTeams && "You must enable Microsoft Teams Direct Routing in order to provide an SBC Domain Name") || ""}
/>
<Label htmlFor="sbcDomainName">SBC Domain Name</Label>
<Input
name="sbcDomainName"
id="sbcDomainName"
value={sbcDomainName}
onChange={e => setSbcDomainName(e.target.value)}
placeholder="Fully qualified domain name used for Microsoft Teams"
invalid={invalidSbcDomainName}
autoFocus={enableMsTeams}
ref={refSbcDomainName}
disabled={!enableMsTeams}
title={(!enableMsTeams && "You must enable Microsoft Teams Direct Routing in order to provide an SBC Domain Name") || ""}
/>
{errorMessage && (
<FormError grid message={errorMessage} />
)}
{errorMessage && !confirmDelete && (
<FormError grid message={errorMessage} />
)}
<InputGroup flexEnd spaced>
<Button
grid
gray
type="button"
onClick={() => {
history.push('/internal/accounts');
dispatch({
type: 'ADD',
level: 'info',
message: 'Changes canceled',
});
<InputGroup flexEnd spaced>
<Button
grid
gray
type="button"
onClick={() => {
history.push('/internal/accounts');
dispatch({
type: 'ADD',
level: 'info',
message: 'Changes canceled',
});
}}
>
Cancel
</Button>
{serviceProviders.length > 1 && (
<Button
grid
gray
type="button"
onClick={() => setConfirmDelete(true)}
>
Delete
</Button>
)}
<Button grid>Save</Button>
</InputGroup>
</Form>
{confirmDelete && serviceProviders.length > 1 && (
<Modal
title="Are you sure you want to delete the Service Provider?"
loader={false}
content={
<div>
<table>
<tbody>
<tr>
<Td>Service Provider Name:</Td>
<Td>{serviceProviderName}</Td>
</tr>
<tr>
<Td>SBC Domain Name:</Td>
<Td>{sbcDomainName || '[none]'}</Td>
</tr>
</tbody>
</table>
{errorMessage && (
<FormError message={errorMessage} />
)}
</div>
}
handleCancel={() => {
setConfirmDelete(false);
setErrorMessage('');
}}
>
Cancel
</Button>
<Button grid>Save</Button>
</InputGroup>
</Form>
handleSubmit={handleDelete}
actionText="Delete"
/>
)}
</>
)
);
};