Reorganize components

This commit is contained in:
James Nuanez committed 2020-04-20 17:22:30 -07:00
1 parent 74572e3940
commit 13201ef03e
43 files changed
+87 -87

No files matched your search

+446
View File
@@ -0,0 +1,446 @@
import React, { useState, useEffect, useContext, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
import Form from '../elements/Form';
import Input from '../elements/Input';
import Label from '../elements/Label';
import Select from '../elements/Select';
import InputGroup from '../elements/InputGroup';
import PasswordInput from '../elements/PasswordInput';
import FormError from '../blocks/FormError';
import Loader from '../blocks/Loader';
import Button from '../elements/Button';
import Link from '../elements/Link';
const AccountForm = props => {
let history = useHistory();
const dispatch = useContext(NotificationDispatchContext);
// Refs
const refName = useRef(null);
const refSipRealm = useRef(null);
const refRegWebhook = useRef(null);
const refUser = useRef(null);
const refPassword = useRef(null);
// Form inputs
const [ name, setName ] = useState('');
const [ sipRealm, setSipRealm ] = useState('');
const [ regWebhook, setRegWebhook ] = useState('');
const [ method, setMethod ] = useState('POST');
const [ user, setUser ] = useState('' || '');
const [ password, setPassword ] = useState('' || '');
// Invalid form inputs
const [ invalidName, setInvalidName ] = useState(false);
const [ invalidSipRealm, setInvalidSipRealm ] = useState(false);
const [ invalidRegWebhook, setInvalidRegWebhook ] = useState(false);
const [ invalidUser, setInvalidUser ] = useState(false);
const [ invalidPassword, setInvalidPassword ] = useState(false);
const [ showLoader, setShowLoader ] = useState(true);
const [ errorMessage, setErrorMessage ] = useState('');
const [ showAuth, setShowAuth ] = useState(false);
const toggleAuth = () => setShowAuth(!showAuth);
const [ accounts, setAccounts ] = useState([]);
const [ accountSid, setAccountSid ] = useState('');
const [ serviceProviderSid, setServiceProviderSid ] = useState('');
useEffect(() => {
if (!localStorage.getItem('token')) {
history.push('/');
dispatch({
type: 'ADD',
level: 'error',
message: 'You must log in to view that page.',
});
}
}, [history, dispatch]);
useEffect(() => {
const getAccounts = async () => {
try {
const accountsResponse = await axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: '/Accounts',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
setAccounts(accountsResponse.data);
if (props.type === 'setup' && accountsResponse.data.length > 1) {
history.push('/internal/accounts');
dispatch({
type: 'ADD',
level: 'error',
message: 'That page is only accessible during setup',
});
}
if (props.type === 'setup' || props.type === 'edit') {
const currentAccount = props.account_sid
? accountsResponse.data.filter(a => a.account_sid === props.account_sid)
: accountsResponse.data;
const noAccountMessage = props.type === 'setup'
? 'You do not have an account. Please add one through the accounts page.'
: 'That account does not exist.';
if (!currentAccount.length) {
history.push('/internal/accounts');
dispatch({
type: 'ADD',
level: 'error',
message: noAccountMessage,
});
return;
}
const acc = currentAccount[0];
setAccountSid(acc.account_sid || '');
setName(acc.name || '');
setSipRealm(acc.sip_realm || '');
setRegWebhook((acc.registration_hook && acc.registration_hook.url ) || '');
setMethod((acc.registration_hook && acc.registration_hook.method ) || 'post');
setUser((acc.registration_hook && acc.registration_hook.username) || '');
setPassword((acc.registration_hook && acc.registration_hook.password) || '');
if (
(acc.registration_hook && acc.registration_hook.username) ||
(acc.registration_hook && acc.registration_hook.password)
) {
setShowAuth(true);
}
}
setShowLoader(false);
} catch (err) {
setErrorMessage((err.response && err.response.data && err.response.data.msg) || 'Something went wrong, please try again');
console.log(err.response || err);
}
};
getAccounts();
}, [props.account_sid, props.type, dispatch, history]);
useEffect(() => {
if (props.type === 'add') {
const getServiceProviders = async () => {
try {
const serviceProviders = await axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: '/ServiceProviders',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
setServiceProviderSid(serviceProviders.data[0].service_provider_sid);
} catch (err) {
setErrorMessage((err.response && err.response.data && err.response.data.msg) || 'Something went wrong, please try again');
console.log(err.response || err);
}
};
getServiceProviders();
}
}, [props.type]);
const handleSumit = async (e) => {
try {
e.preventDefault();
setErrorMessage('');
setInvalidName(false);
setInvalidSipRealm(false);
setInvalidRegWebhook(false);
setInvalidUser(false);
setInvalidPassword(false);
let errorMessages = [];
let focusHasBeenSet = false;
if ((props.type === 'add' || props.type === 'edit') && !name) {
errorMessages.push('Please provide a name.');
setInvalidName(true);
if (!focusHasBeenSet) {
refName.current.focus();
focusHasBeenSet = true;
}
}
// Check if name or sip_realm are already in use
accounts.forEach(a => {
if (a.account_sid === accountSid) {
return;
}
if (a.name === name) {
errorMessages.push(
'The name you have entered is already in use on another one of your accounts.'
);
setInvalidName(true);
if (!focusHasBeenSet) {
refName.current.focus();
focusHasBeenSet = true;
}
}
if (a.sip_realm === sipRealm) {
errorMessages.push(
'The SIP Realm you have entered is already in use on another one of your accounts.'
);
setInvalidSipRealm(true);
if (!focusHasBeenSet) {
refSipRealm.current.focus();
focusHasBeenSet = true;
}
}
});
if ((user && !password) || (!user && password)) {
errorMessages.push('Username and password must be either both filled out or both empty.');
setInvalidUser(true);
setInvalidPassword(true);
if (!focusHasBeenSet) {
if (!user) {
refUser.current.focus();
} else {
refPassword.current.focus();
}
focusHasBeenSet = true;
}
}
if (errorMessages.length > 1) {
setErrorMessage(errorMessages);
return;
} else if (errorMessages.length === 1) {
setErrorMessage(errorMessages[0]);
return;
}
const axiosData = {
name,
sip_realm: sipRealm,
registration_hook: {
url: regWebhook,
method: method,
username: user || null,
password: password || null,
},
};
if (props.type === 'add') {
axiosData.service_provider_sid = serviceProviderSid;
}
const url = props.type === 'add'
? `/Accounts`
: `/Accounts/${accountSid}`;
await axios({
method: props.type === 'add' ? 'post' : 'put',
baseURL: process.env.REACT_APP_API_BASE_URL,
url,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
data: axiosData,
});
if (props.type === 'setup') {
history.push('/create-application');
} else {
history.push('/internal/accounts');
const dispatchMessage = props.type === 'add'
? 'Account created successfully'
: 'Account updated successfully';
dispatch({
type: 'ADD',
level: 'success',
message: dispatchMessage
});
}
} catch (err) {
if (err.response && err.response.status === 401) {
localStorage.removeItem('token');
sessionStorage.clear();
history.push('/');
dispatch({
type: 'ADD',
level: 'error',
message: 'Your session has expired. Please log in and try again',
});
} else {
setErrorMessage((err.response && err.response.data && err.response.data.msg) || 'Something went wrong, please try again');
console.log(err.response || err);
}
}
};
return (
showLoader
? <Loader
height={
props.type === 'setup'
? '309px'
: '292px'
}
/>
: <Form
large
onSubmit={handleSumit}
>
{(props.type === 'add' || props.type === 'edit') && (
<React.Fragment>
<Label htmlFor="name">Name</Label>
<Input
large={props.type === 'setup'}
name="name"
id="name"
value={name}
onChange={e => setName(e.target.value)}
placeholder="Account name"
invalid={invalidName}
autoFocus
ref={refName}
/>
</React.Fragment>
)}
<Label htmlFor="sipRealm">SIP Realm</Label>
<Input
large={props.type === 'setup'}
name="sipRealm"
id="sipRealm"
value={sipRealm}
onChange={e => setSipRealm(e.target.value)}
placeholder="The domain name that SIP devices will register with"
invalid={invalidSipRealm}
autoFocus={props.type === 'setup'}
ref={refSipRealm}
/>
<Label htmlFor="regWebhook">Registration Webhook</Label>
<InputGroup>
<Input
large={props.type === 'setup'}
name="regWebhook"
id="regWebhook"
value={regWebhook}
onChange={e => setRegWebhook(e.target.value)}
placeholder="URL for your web application that handles registrations"
invalid={invalidRegWebhook}
ref={refRegWebhook}
/>
<Label
middle
htmlFor="method"
>
Method
</Label>
<Select
large={props.type === 'setup'}
name="method"
id="method"
value={method}
onChange={e => setMethod(e.target.value)}
>
<option value="POST">POST</option>
<option value="GET">GET</option>
</Select>
</InputGroup>
{showAuth ? (
<InputGroup>
<Label indented htmlFor="user">User</Label>
<Input
large={props.type === 'setup'}
name="user"
id="user"
value={user || ''}
onChange={e => setUser(e.target.value)}
invalid={invalidUser}
ref={refUser}
/>
<Label htmlFor="password" middle>Password</Label>
<PasswordInput
large={props.type === 'setup'}
allowShowPassword
name="password"
id="password"
password={password}
setPassword={setPassword}
setErrorMessage={setErrorMessage}
invalid={invalidPassword}
ref={refPassword}
/>
</InputGroup>
) : (
<Button
text
formLink
type="button"
onClick={toggleAuth}
>
Use HTTP Basic Authentication
</Button>
)}
{errorMessage && (
<FormError grid message={errorMessage} />
)}
<InputGroup flexEnd spaced>
{props.type === 'edit' && (
<Button
grid
gray
type="button"
onClick={() => {
history.push('/internal/accounts');
dispatch({
type: 'ADD',
level: 'info',
message: 'Changes canceled',
});
}}
>
Cancel
</Button>
)}
<Button
large={props.type === 'setup'}
grid
fullWidth={props.type === 'setup' || props.type === 'add'}
>
{props.type === 'setup'
? 'Save and Continue'
: props.type === 'add'
? 'Add Account'
: 'Save'
}
</Button>
</InputGroup>
{props.type === 'setup' && (
<Link
formLink
right
to="/create-application"
>
Skip for now &mdash; I'l complete later
</Link>
)}
</Form>
);
};
export default AccountForm;
+744
View File
@@ -0,0 +1,744 @@
import React, { useState, useEffect, useContext, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
import Form from '../elements/Form';
import Input from '../elements/Input';
import Label from '../elements/Label';
import Select from '../elements/Select';
import InputGroup from '../elements/InputGroup';
import PasswordInput from '../elements/PasswordInput';
import FormError from '../blocks/FormError';
import Button from '../elements/Button';
import SpeechSynthesisLanguageGoogle from '../../data/SpeechSynthesisLanguageGoogle';
import SpeechSynthesisLanguageAws from '../../data/SpeechSynthesisLanguageAws';
import SpeechRecognizerLanguageGoogle from '../../data/SpeechRecognizerLanguageGoogle';
import Loader from '../blocks/Loader';
const ApplicationForm = props => {
let history = useHistory();
const dispatch = useContext(NotificationDispatchContext);
// Refs
const refName = useRef(null);
const refCallWebhook = useRef(null);
const refCallWebhookUser = useRef(null);
const refCallWebhookPass = useRef(null);
const refStatusWebhook = useRef(null);
const refStatusWebhookUser = useRef(null);
const refStatusWebhookPass = useRef(null);
const refAccount = useRef(null);
// Form inputs
const [ name, setName ] = useState('');
const [ callWebhook, setCallWebhook ] = useState('');
const [ callWebhookMethod, setCallWebhookMethod ] = useState('POST');
const [ callWebhookUser, setCallWebhookUser ] = useState('');
const [ callWebhookPass, setCallWebhookPass ] = useState('');
const [ statusWebhook, setStatusWebhook ] = useState('');
const [ statusWebhookMethod, setStatusWebhookMethod ] = useState('POST');
const [ statusWebhookUser, setStatusWebhookUser ] = useState('');
const [ statusWebhookPass, setStatusWebhookPass ] = useState('');
const [ speechSynthesisVendor, setSpeechSynthesisVendor ] = useState('google');
const [ speechSynthesisLanguage, setSpeechSynthesisLanguage ] = useState('en-US');
const [ speechSynthesisVoice, setSpeechSynthesisVoice ] = useState('en-US-Standard-C');
const [ speechRecognizerVendor, setSpeechRecognizerVendor ] = useState('google');
const [ speechRecognizerLanguage, setSpeechRecognizerLanguage ] = useState('en-US');
const [ accountSid, setAccountSid ] = useState('');
// Invalid form inputs
const [ invalidName, setInvalidName ] = useState(false);
const [ invalidCallWebhook, setInvalidCallWebhook ] = useState(false);
const [ invalidCallWebhookUser, setInvalidCallWebhookUser ] = useState(false);
const [ invalidCallWebhookPass, setInvalidCallWebhookPass ] = useState(false);
const [ invalidStatusWebhook, setInvalidStatusWebhook ] = useState(false);
const [ invalidStatusWebhookUser, setInvalidStatusWebhookUser ] = useState(false);
const [ invalidStatusWebhookPass, setInvalidStatusWebhookPass ] = useState(false);
const [ invalidAccount, setInvalidAccount ] = useState(false);
const [ showLoader, setShowLoader ] = useState(true);
const [ errorMessage, setErrorMessage ] = useState('');
const [ showCallAuth, setShowCallAuth ] = useState(false);
const toggleCallAuth = () => setShowCallAuth(!showCallAuth);
const [ showStatusAuth, setShowStatusAuth ] = useState(false);
const toggleStatusAuth = () => setShowStatusAuth(!showStatusAuth);
const [ accounts, setAccounts ] = useState([]);
const [ applications, setApplications ] = useState([]);
const [ applicationSid, setApplicationSid ] = useState([]);
// See if user logged in
useEffect(() => {
if (!localStorage.getItem('token')) {
history.push('/');
dispatch({
type: 'ADD',
level: 'error',
message: 'You must log in to view that page.',
});
}
}, [history, dispatch]);
// Get Account info
// Account SID needed for setup/add (when creating a new application
// Account list needed add/edit
useEffect(() => {
const getAPIData = async () => {
try {
const accountsPromise = axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: '/Accounts',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
const applicationsPromise = axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: '/Applications',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
const promiseAllValues = await Promise.all([
accountsPromise,
applicationsPromise,
]);
const accounts = promiseAllValues[0].data;
const applications = promiseAllValues[1].data;
setAccounts(accounts);
setApplications(applications);
if (
(props.type === 'setup' && accounts.length > 1) ||
(props.type === 'setup' && applications.length > 1)
) {
history.push('/internal/applications');
dispatch({
type: 'ADD',
level: 'error',
message: 'That page is only accessible during setup',
});
}
if (props.type === 'add' && accounts.length === 1) {
setAccountSid(accounts[0].account_sid);
}
if (props.type === 'setup' || props.type === 'edit') {
const currentApplication = props.type === 'edit'
? applications.filter(a => a.application_sid === props.application_sid)
: applications;
if (props.type === 'edit' && !currentApplication.length) {
history.push('/internal/applications');
dispatch({
type: 'ADD',
level: 'error',
message: 'That application does not exist',
});
return;
}
if (!currentApplication.length) {
setName('default application');
setAccountSid(accounts[0].account_sid);
} else {
const app = currentApplication[0];
setName( app.name || '');
setCallWebhook( (app.call_hook && app.call_hook.url) || '');
setCallWebhookMethod( (app.call_hook && app.call_hook.method) || 'post');
setCallWebhookUser( (app.call_hook && app.call_hook.username) || '');
setCallWebhookPass( (app.call_hook && app.call_hook.password) || '');
setStatusWebhook( (app.call_status_hook && app.call_status_hook.url) || '');
setStatusWebhookMethod( (app.call_status_hook && app.call_status_hook.method) || 'post');
setStatusWebhookUser( (app.call_status_hook && app.call_status_hook.username) || '');
setStatusWebhookPass( (app.call_status_hook && app.call_status_hook.password) || '');
setSpeechSynthesisVendor( app.speech_synthesis_vendor || '');
setSpeechSynthesisLanguage( app.speech_synthesis_language || '');
setSpeechSynthesisVoice( app.speech_synthesis_voice || '');
setSpeechRecognizerVendor( app.speech_recognizer_vendor || '');
setSpeechRecognizerLanguage( app.speech_recognizer_language || '');
setAccountSid( app.account_sid || '');
setApplicationSid( app.application_sid);
if (
(app.call_hook && app.call_hook.username) ||
(app.call_hook && app.call_hook.password)
) {
setShowCallAuth(true);
}
if (
(app.call_status_hook && app.call_status_hook.username) ||
(app.call_status_hook && app.call_status_hook.password)
) {
setShowStatusAuth(true);
}
}
}
setShowLoader(false);
} catch (err) {
setErrorMessage('Something went wrong, please try again');
dispatch({
type: 'ADD',
level: 'error',
message: (err.response && err.response.message) || 'Unable to get accounts',
});
console.log(err.response || err);
setShowLoader(false);
}
};
getAPIData();
// eslint-disable-next-line
}, []);
const handleSumit = async (e) => {
try {
e.preventDefault();
setErrorMessage('');
setInvalidName(false);
setInvalidCallWebhook(false);
setInvalidCallWebhookUser(false);
setInvalidCallWebhookPass(false);
setInvalidStatusWebhook(false);
setInvalidStatusWebhookUser(false);
setInvalidStatusWebhookPass(false);
setInvalidAccount(false);
let errorMessages = [];
let focusHasBeenSet = false;
if ((props.type === 'add' || props.type === 'edit') && !name) {
errorMessages.push('Please provide a name.');
setInvalidName(true);
if (!focusHasBeenSet) {
refName.current.focus();
focusHasBeenSet = true;
}
}
// check if name is already in use
if (!callWebhook) {
errorMessages.push('Please enter a Calling Webhook.');
setInvalidCallWebhook(true);
if (!focusHasBeenSet) {
refCallWebhook.current.focus();
focusHasBeenSet = true;
}
}
if (!statusWebhook) {
errorMessages.push('Please enter a Call Status Webhook.');
setInvalidStatusWebhook(true);
if (!focusHasBeenSet) {
refStatusWebhook.current.focus();
focusHasBeenSet = true;
}
}
if ((callWebhookUser && !callWebhookPass) || (!callWebhookUser && callWebhookPass)) {
errorMessages.push('Calling Webhook username and password must be either both filled out or both empty.');
setInvalidCallWebhookUser(true);
setInvalidCallWebhookPass(true);
if (!focusHasBeenSet) {
if (!callWebhookUser) {
refCallWebhookUser.current.focus();
} else {
refCallWebhookPass.current.focus();
}
focusHasBeenSet = true;
}
}
if ((statusWebhookUser && !statusWebhookPass) || (!statusWebhookUser && statusWebhookPass)) {
errorMessages.push('Call Status Webhook username and password must be either both filled out or both empty.');
setInvalidStatusWebhookUser(true);
setInvalidStatusWebhookPass(true);
if (!focusHasBeenSet) {
if (!statusWebhookUser) {
refStatusWebhookUser.current.focus();
} else {
refStatusWebhookPass.current.focus();
}
focusHasBeenSet = true;
}
}
if ((props.type === 'add' || props.type === 'edit') && !accountSid) {
errorMessages.push('Please choose an account for this application to be associated with.');
setInvalidAccount(true);
if (!focusHasBeenSet) {
refAccount.current.focus();
focusHasBeenSet = true;
}
}
if (errorMessages.length > 1) {
setErrorMessage(errorMessages);
return;
} else if (errorMessages.length === 1) {
setErrorMessage(errorMessages[0]);
return;
}
//=============================================================================
// Submit
//=============================================================================
const shouldCreateNew = props.type === 'add' || (props.type === 'setup' && !applications.length);
const method = shouldCreateNew
? 'post'
: 'put';
const url = shouldCreateNew
? '/Applications'
: `/Applications/${applicationSid}`;
const data = {
account_sid: accountSid,
name,
call_hook: {
url: callWebhook,
method: callWebhookMethod,
username: callWebhookUser || null,
password: callWebhookPass || null,
},
call_status_hook: {
url: statusWebhook,
method: statusWebhookMethod,
username: statusWebhookUser || null,
password: statusWebhookPass || null,
},
speech_synthesis_vendor: speechSynthesisVendor,
speech_synthesis_language: speechSynthesisLanguage,
speech_synthesis_voice: speechSynthesisVoice,
speech_recognizer_vendor: speechRecognizerVendor,
speech_recognizer_language: speechRecognizerLanguage,
};
await axios({
method,
baseURL: process.env.REACT_APP_API_BASE_URL,
url,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
data,
});
if (props.type === 'setup') {
history.push('/configure-sip-trunk');
} else {
history.push('/internal/applications');
const dispatchMessage = props.type === 'add'
? 'Application created successfully'
: 'Application updated successfully';
dispatch({
type: 'ADD',
level: 'success',
message: dispatchMessage
});
}
} catch (err) {
if (err.response.status === 401) {
localStorage.removeItem('token');
sessionStorage.clear();
history.push('/');
dispatch({
type: 'ADD',
level: 'error',
message: 'Your session has expired. Please log in and try again',
});
} else {
setErrorMessage((err.response && err.response.data && err.response.data.msg) || 'Something went wrong, please try again');
console.log(err.response || err);
}
}
};
return (
showLoader
? <Loader height={props.type === 'setup' ? '505px' : '630px'}/>
: <Form
large
onSubmit={handleSumit}
>
{(props.type === 'add' || props.type === 'edit') && (
<React.Fragment>
<Label htmlFor="name">Name</Label>
<Input
large={props.type === 'setup'}
name="name"
id="name"
value={name}
onChange={e => setName(e.target.value)}
placeholder="Application name"
invalid={invalidName}
autoFocus
ref={refName}
/>
<hr />
</React.Fragment>
)}
<Label htmlFor="callWebhook">Calling Webhook</Label>
<InputGroup>
<Input
large={props.type === 'setup'}
name="callWebhook"
id="callWebhook"
value={callWebhook}
onChange={e => setCallWebhook(e.target.value)}
placeholder="URL for your web application that will handle calls"
invalid={invalidCallWebhook}
ref={refCallWebhook}
autoFocus={props.type === 'setup'}
/>
<Label
middle
htmlFor="callWebhookMethod"
>
Method
</Label>
<Select
large={props.type === 'setup'}
name="callWebhookMethod"
id="callWebhookMethod"
value={callWebhookMethod}
onChange={e => setCallWebhookMethod(e.target.value)}
>
<option value="POST">POST</option>
<option value="GET">GET</option>
</Select>
</InputGroup>
{showCallAuth ? (
<InputGroup>
<Label indented htmlFor="callWebhookUser">User</Label>
<Input
large={props.type === 'setup'}
name="callWebhookUser"
id="callWebhookUser"
value={callWebhookUser}
onChange={e => setCallWebhookUser(e.target.value)}
invalid={invalidCallWebhookUser}
ref={refCallWebhookUser}
/>
<Label htmlFor="callWebhookPass" middle>Password</Label>
<PasswordInput
large={props.type === 'setup'}
allowShowPassword
name="callWebhookPass"
id="callWebhookPass"
password={callWebhookPass}
setPassword={setCallWebhookPass}
setErrorMessage={setErrorMessage}
invalid={invalidCallWebhookPass}
ref={refCallWebhookPass}
/>
</InputGroup>
) : (
<Button
text
formLink
type="button"
onClick={toggleCallAuth}
>
Use HTTP Basic Authentication
</Button>
)}
<hr />
<Label htmlFor="statusWebhook">Call Status Webhook</Label>
<InputGroup>
<Input
large={props.type === 'setup'}
name="statusWebhook"
id="statusWebhook"
value={statusWebhook}
onChange={e => setStatusWebhook(e.target.value)}
placeholder="URL for your web application that will receive call status"
invalid={invalidStatusWebhook}
ref={refStatusWebhook}
/>
<Label
middle
htmlFor="statusWebhookMethod"
>
Method
</Label>
<Select
large={props.type === 'setup'}
name="statusWebhookMethod"
id="statusWebhookMethod"
value={statusWebhookMethod}
onChange={e => setStatusWebhookMethod(e.target.value)}
>
<option value="POST">POST</option>
<option value="GET">GET</option>
</Select>
</InputGroup>
{showStatusAuth ? (
<InputGroup>
<Label indented htmlFor="statusWebhookUser">User</Label>
<Input
large={props.type === 'setup'}
name="statusWebhookUser"
id="statusWebhookUser"
value={statusWebhookUser}
onChange={e => setStatusWebhookUser(e.target.value)}
invalid={invalidStatusWebhookUser}
ref={refStatusWebhookUser}
/>
<Label htmlFor="statusWebhookPass" middle>Password</Label>
<PasswordInput
large={props.type === 'setup'}
allowShowPassword
name="statusWebhookPass"
id="statusWebhookPass"
password={statusWebhookPass}
setPassword={setStatusWebhookPass}
setErrorMessage={setErrorMessage}
invalid={invalidStatusWebhookPass}
ref={refStatusWebhookPass}
/>
</InputGroup>
) : (
<Button
text
formLink
type="button"
onClick={toggleStatusAuth}
>
Use HTTP Basic Authentication
</Button>
)}
<hr />
<Label htmlFor="speechSynthesisVendor">Speech Synthesis Vendor</Label>
<InputGroup>
<Select
large={props.type === 'setup'}
name="speechSynthesisVendor"
id="speechSynthesisVendor"
value={speechSynthesisVendor}
onChange={e => {
setSpeechSynthesisVendor(e.target.value);
// When using Google and en-US, ensure "Standard-C" is used as default
if (
e.target.value === 'google' &&
speechSynthesisLanguage === 'en-US'
) {
setSpeechSynthesisVoice('en-US-Standard-C');
return;
}
// Google and AWS have different voice lists. See if the newly
// chosen vendor has the same language as what was already in use.
let newLang = e.target.value === 'google'
? SpeechSynthesisLanguageGoogle.find(l => (
l.code === speechSynthesisLanguage
))
: SpeechSynthesisLanguageAws.find(l => (
l.code === speechSynthesisLanguage
));
// if not, use en-US as fallback.
if (!newLang) {
setSpeechSynthesisLanguage('en-US');
if (e.target.value === 'google') {
setSpeechSynthesisVoice('en-US-Standard-C');
return;
}
newLang = SpeechSynthesisLanguageAws.find(l => (
l.code === 'en-US'
));
}
// Update state to reflect first voice option for language
setSpeechSynthesisVoice(newLang.voices[0].value);
}}
>
<option value="google">Google</option>
<option value="aws">AWS</option>
</Select>
<Label middle htmlFor="speechSynthesisLanguage">Language</Label>
<Select
large={props.type === 'setup'}
name="speechSynthesisLanguage"
id="speechSynthesisLanguage"
value={speechSynthesisLanguage}
onChange={e => {
setSpeechSynthesisLanguage(e.target.value);
// When using Google and en-US, ensure "Standard-C" is used as default
if (
(speechSynthesisVendor === 'google')
&& (e.target.value === 'en-US')
) {
setSpeechSynthesisVoice('en-US-Standard-C');
return;
}
const newLang = speechSynthesisVendor === 'google'
? SpeechSynthesisLanguageGoogle.find(l => (
l.code === e.target.value
))
: SpeechSynthesisLanguageAws.find(l => (
l.code === e.target.value
));
setSpeechSynthesisVoice(newLang.voices[0].value);
}}
>
{speechSynthesisVendor === 'google' ? (
SpeechSynthesisLanguageGoogle.map(l => (
<option key={l.code} value={l.code}>{l.name}</option>
))
) : (
SpeechSynthesisLanguageAws.map(l => (
<option key={l.code} value={l.code}>{l.name}</option>
))
)}
</Select>
<Label middle htmlFor="speechSynthesisVoice">Voice</Label>
<Select
large={props.type === 'setup'}
name="speechSynthesisVoice"
id="speechSynthesisVoice"
value={speechSynthesisVoice}
onChange={e => setSpeechSynthesisVoice(e.target.value)}
>
{speechSynthesisVendor === 'google' ? (
SpeechSynthesisLanguageGoogle
.filter(l => l.code === speechSynthesisLanguage)
.map(m => m.voices.map(v => (
<option key={v.value} value={v.value}>{v.name}</option>
)))
) : (
SpeechSynthesisLanguageAws
.filter(l => l.code === speechSynthesisLanguage)
.map(m => m.voices.map(v => (
<option key={v.value} value={v.value}>{v.name}</option>
)))
)}
</Select>
</InputGroup>
<hr />
<Label htmlFor="speechRecognizerVendor">Speech Recognizer Vendor</Label>
<InputGroup>
<Select
large={props.type === 'setup'}
name="speechRecognizerVendor"
id="speechRecognizerVendor"
value={speechRecognizerVendor}
onChange={e => setSpeechRecognizerVendor(e.target.value)}
>
<option value="google">Google</option>
</Select>
<Label middle htmlFor="speechRecognizerLanguage">Language</Label>
<Select
large={props.type === 'setup'}
name="speechRecognizerLanguage"
id="speechRecognizerLanguage"
value={speechRecognizerLanguage}
onChange={e => setSpeechRecognizerLanguage(e.target.value)}
>
{SpeechRecognizerLanguageGoogle.map(l => (
<option key={l.code} value={l.code}>{l.name}</option>
))}
</Select>
</InputGroup>
{(props.type === 'add' || props.type === 'edit') && (
<React.Fragment>
<hr />
<Label htmlFor="account">Account</Label>
<Select
large={props.type === 'setup'}
name="account"
id="account"
value={accountSid}
onChange={e => setAccountSid(e.target.value)}
invalid={invalidAccount}
ref={refAccount}
>
{(
(accounts.length > 1) ||
(props.type === 'edit' && accounts[0] && accountSid !== accounts[0].account_sid)
) && (
<option value="">
-- Choose the account this application will be associated with --
</option>
)}
{accounts.map(a => (
<option
key={a.account_sid}
value={a.account_sid}
>
{a.name}
</option>
))}
</Select>
</React.Fragment>
)}
{errorMessage && (
<FormError grid message={errorMessage} />
)}
<InputGroup flexEnd spaced>
{props.type === 'edit' && (
<Button
grid
gray
type="button"
onClick={() => {
history.push('/internal/applications');
dispatch({
type: 'ADD',
level: 'info',
message: 'Changes canceled',
});
}}
>
Cancel
</Button>
)}
<Button
large={props.type === 'setup'}
grid
fullWidth={props.type === 'setup' || props.type === 'add'}
>
{props.type === 'setup'
? 'Save and Continue'
: props.type === 'add'
? 'Add Application'
: 'Save'
}
</Button>
</InputGroup>
</Form>
);
};
export default ApplicationForm;
+363
View File
@@ -0,0 +1,363 @@
import React, { useState, useEffect, useContext, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
import Form from '../elements/Form';
import Input from '../elements/Input';
import Label from '../elements/Label';
import Select from '../elements/Select';
import InputGroup from '../elements/InputGroup';
import FormError from '../blocks/FormError';
import Loader from '../blocks/Loader';
import Button from '../elements/Button';
const PhoneNumberForm = props => {
let history = useHistory();
const dispatch = useContext(NotificationDispatchContext);
// Refs
const refPhoneNumber = useRef(null);
const refSipTrunk = useRef(null);
const refAccount = useRef(null);
// Form inputs
const [ phoneNumber, setPhoneNumber ] = useState('');
const [ sipTrunk, setSipTrunk ] = useState('');
const [ account, setAccount ] = useState('');
const [ application, setApplication ] = useState('');
// Select list values
const [ sipTrunkValues, setSipTrunkValues ] = useState('');
const [ accountValues, setAccountValues ] = useState('');
const [ applicationValues, setApplicationValues ] = useState('');
// Invalid form inputs
const [ invalidPhoneNumber, setInvalidPhoneNumber ] = useState(false);
const [ invalidSipTrunk, setInvalidSipTrunk ] = useState(false);
const [ invalidAccount, setInvalidAccount ] = useState(false);
const [ showLoader, setShowLoader ] = useState(true);
const [ errorMessage, setErrorMessage ] = useState('');
// Check if user is logged in
useEffect(() => {
if (!localStorage.getItem('token')) {
history.push('/');
dispatch({
type: 'ADD',
level: 'error',
message: 'You must log in to view that page.',
});
}
}, [history, dispatch]);
useEffect(() => {
const getAPIData = async () => {
try {
const sipTrunksPromise = axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: '/VoipCarriers',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
const accountsPromise = axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: '/Accounts',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
const applicationsPromise = axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: '/Applications',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
const promises = [
sipTrunksPromise,
accountsPromise,
applicationsPromise,
];
if (props.type === 'edit') {
const phoneNumberPromise = axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: `/PhoneNumbers/${props.phone_number_sid}`,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
promises.push(phoneNumberPromise);
}
const promiseAllValues = await Promise.all(promises);
const sipTrunks = promiseAllValues[0].data;
const accounts = promiseAllValues[1].data;
const applications = promiseAllValues[2].data;
setSipTrunkValues(sipTrunks);
setAccountValues(accounts);
setApplicationValues(applications);
if (props.type === 'edit') {
const phoneNumberData = promiseAllValues[3] && promiseAllValues[3].data;
setPhoneNumber (( phoneNumberData && phoneNumberData.number ) || '');
setSipTrunk (( phoneNumberData && phoneNumberData.voip_carrier_sid ) || '');
setAccount (( phoneNumberData && phoneNumberData.account_sid ) || '');
setApplication (( phoneNumberData && phoneNumberData.application_sid ) || '');
}
if (props.type === 'add') {
if (sipTrunks.length === 1) { setSipTrunk(sipTrunks[0].voip_carrier_sid); }
if ( accounts.length === 1) { setAccount( accounts[0].account_sid ); }
}
setShowLoader(false);
} catch (err) {
dispatch({
type: 'ADD',
level: 'error',
message: (err.response && err.response.data && err.response.data.msg) || 'Unable to get necessary data',
});
console.log(err.response || err);
setShowLoader(false);
}
};
getAPIData();
// eslint-disable-next-line
}, []);
const handleSubmit = async e => {
try {
e.preventDefault();
setErrorMessage('');
setInvalidPhoneNumber(false);
setInvalidSipTrunk(false);
setInvalidAccount(false);
let errorMessages = [];
let focusHasBeenSet = false;
if (!phoneNumber) {
errorMessages.push('Please provide a phone number');
setInvalidPhoneNumber(true);
if (!focusHasBeenSet) {
refPhoneNumber.current.focus();
focusHasBeenSet = true;
}
}
// check if phone number is already in use
if (!sipTrunk) {
errorMessages.push('Please select a SIP trunk');
setInvalidSipTrunk(true);
if (!focusHasBeenSet) {
refSipTrunk.current.focus();
focusHasBeenSet = true;
}
}
if (!account) {
errorMessages.push('Please select an account');
setInvalidAccount(true);
if (!focusHasBeenSet) {
refAccount.current.focus();
focusHasBeenSet = true;
}
}
if (errorMessages.length > 1) {
setErrorMessage(errorMessages);
return;
} else if (errorMessages.length === 1) {
setErrorMessage(errorMessages[0]);
return;
}
//=============================================================================
// Submit
//=============================================================================
const method = props.type === 'add'
? 'post'
: 'put';
const url = props.type === 'add'
? `/PhoneNumbers`
: `/PhoneNumbers/${props.phone_number_sid}`;
const data = {
account_sid: account,
application_sid: application || null,
};
if (props.type === 'add') {
data.number = phoneNumber;
data.voip_carrier_sid = sipTrunk;
}
await axios({
method,
baseURL: process.env.REACT_APP_API_BASE_URL,
url,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
data
});
const dispatchMessage = props.type === 'add'
? 'Phone number created successfully'
: 'Phone number updated successfully';
dispatch({
type: 'ADD',
level: 'success',
message: dispatchMessage
});
history.push('/internal/phone-numbers');
} catch (err) {
setErrorMessage(
(err.response && err.response.data && err.response.data.msg) ||
'Something went wrong, please try again'
);
console.log(err.response || err);
}
};
return (
showLoader
? <Loader height={'310px'}/>
: <Form
large
onSubmit={handleSubmit}
>
<Label htmlFor="phoneNumber">Phone Number</Label>
<Input
name="phoneNumber"
id="phoneNumber"
value={phoneNumber}
onChange={e => setPhoneNumber(e.target.value)}
placeholder="Phone number that will be sending calls to this service"
invalid={invalidPhoneNumber}
autoFocus
ref={refPhoneNumber}
disabled={props.type === 'edit'}
/>
<Label htmlFor="sipTrunk">SIP Trunk</Label>
<Select
name="sipTrunk"
id="sipTrunk"
value={sipTrunk}
onChange={e => setSipTrunk(e.target.value)}
invalid={invalidSipTrunk}
ref={refSipTrunk}
disabled={props.type === 'edit'}
>
{(
(sipTrunkValues.length > 1) ||
(props.type === 'edit' && sipTrunk !== sipTrunkValues[0].voip_carrier_sid)
) && (
<option value="">-- Choose the SIP trunk that this phone number belongs to --</option>
)}
{sipTrunkValues.map(s => (
<option
key={s.voip_carrier_sid}
value={s.voip_carrier_sid}
>
{s.name}
</option>
))}
</Select>
<Label htmlFor="account">Account</Label>
<Select
name="account"
id="account"
value={account}
onChange={e => setAccount(e.target.value)}
invalid={invalidAccount}
ref={refAccount}
>
{(
(accountValues.length > 1) ||
(props.type === 'edit' && account !== accountValues[0].account_sid)
) && (
<option value="">-- Choose the account that this phone number should be associated with --</option>
)}
{accountValues.map(a => (
<option
key={a.account_sid}
value={a.account_sid}
>
{a.name}
</option>
))}
</Select>
<Label htmlFor="application">Application</Label>
<Select
name="application"
id="application"
value={application}
onChange={e => setApplication(e.target.value)}
>
<option value="">-- OPTIONAL: Choose the application that will receive calls from this number --</option>
{applicationValues.map(a => (
<option
key={a.application_sid}
value={a.application_sid}
>
{a.name}
</option>
))}
</Select>
{errorMessage && (
<FormError grid message={errorMessage} />
)}
<InputGroup flexEnd spaced>
{props.type === 'edit' && (
<Button
grid
gray
type="button"
onClick={() => {
history.push('/internal/phone-numbers');
dispatch({
type: 'ADD',
level: 'info',
message: 'Changes canceled',
});
}}
>
Cancel
</Button>
)}
<Button
grid
fullWidth={props.type === 'add'}
>
{props.type === 'add'
? 'Add Phone Number'
: 'Save'
}
</Button>
</InputGroup>
</Form>
);
};
export default PhoneNumberForm;
+644
View File
@@ -0,0 +1,644 @@
import React, { useState, useEffect, useContext, useRef } from 'react';
import { useHistory } from 'react-router-dom';
import axios from 'axios';
import { NotificationDispatchContext } from '../../contexts/NotificationContext';
import Form from '../elements/Form';
import Input from '../elements/Input';
import Label from '../elements/Label';
import Checkbox from '../elements/Checkbox';
import InputGroup from '../elements/InputGroup';
import FormError from '../blocks/FormError';
import Button from '../elements/Button';
import TrashButton from '../elements/TrashButton';
import Loader from '../blocks/Loader';
const SipTrunkForm = props => {
const history = useHistory();
const dispatch = useContext(NotificationDispatchContext);
// Refs
const refName = useRef(null);
const refIp = useRef([]);
const refPort = useRef([]);
const refInbound = useRef([]);
const refOutbound = useRef([]);
const refTrash = useRef([]);
const refAdd = useRef(null);
// Form inputs
const [ name, setName ] = useState('');
const [ nameInvalid, setNameInvalid ] = useState(false);
const [ description, setDescription ] = useState('');
const [ sipGateways, setSipGateways ] = useState([
{
sip_gateway_sid: '',
ip: '',
port: 5060,
inbound: true,
outbound: true,
invalidIp: false,
invalidPort: false,
invalidInbound: false,
invalidOutbound: false,
}
]);
const [ sipTrunks, setSipTrunks ] = useState([]);
const [ sipTrunkSid, setSipTrunkSid ] = useState('');
const [ showLoader, setShowLoader ] = useState(true);
const [ errorMessage, setErrorMessage ] = useState('');
useEffect(() => {
if (!localStorage.getItem('token')) {
history.push('/');
dispatch({
type: 'ADD',
level: 'error',
message: 'You must log in to view that page.',
});
}
}, [history, dispatch]);
useEffect(() => {
const getAPIData = async () => {
const sipTrunksPromise = axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: `/VoipCarriers`,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
const sipGatewaysPromise = axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: `/SipGateways`,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
const promiseAllValues = await Promise.all([
sipTrunksPromise,
sipGatewaysPromise,
]);
const allSipTrunks = promiseAllValues[0].data;
const allSipGateways = promiseAllValues[1].data;
setSipTrunks(allSipTrunks);
if (props.type === 'setup' && allSipTrunks.length > 1) {
history.push('/internal/sip-trunks');
dispatch({
type: 'ADD',
level: 'error',
message: 'That page is only accessible during setup',
});
}
if (props.type === 'edit' || props.type === 'setup') {
const currentSipTrunk = props.type === 'edit'
? allSipTrunks.filter(s => s.voip_carrier_sid === props.voip_carrier_sid)
: allSipTrunks;
if (props.type === 'edit' && !currentSipTrunk.length) {
history.push('/internal/sip-trunks');
dispatch({
type: 'ADD',
level: 'error',
message: 'That SIP trunk does not exist',
});
return;
}
const currentSipGateways = allSipGateways.filter(s => {
return s.voip_carrier_sid === currentSipTrunk[0].voip_carrier_sid;
});
if (currentSipTrunk.length) {
setName(currentSipTrunk[0].name);
setDescription(currentSipTrunk[0].description);
setSipGateways(currentSipGateways.map(s => ({
sip_gateway_sid: s.sip_gateway_sid,
ip: s.ipv4,
port: s.port,
inbound: s.inbound === 1,
outbound: s.outbound === 1,
invalidIp: false,
invalidPort: false,
invalidInbound: false,
invalidOutbound: false,
})));
setSipTrunkSid(currentSipTrunk[0].voip_carrier_sid);
}
}
setShowLoader(false);
};
getAPIData();
// eslint-disable-next-line
}, []);
const addSipGateway = () => {
const newSipGateways = [
...sipGateways,
{
sip_gateway_sid: '',
ip: '',
port: 5060,
inbound: true,
outbound: true,
invalidIp: false,
invalidPort: false,
invalidInbound: false,
invalidOutbound: false,
}
];
setSipGateways(newSipGateways);
};
const removeSipGateway = index => {
const newSipGateways = sipGateways.filter((s,i) => i !== index);
setSipGateways(newSipGateways);
setErrorMessage('');
};
const updateSipGateways = (e, i, key) => {
const newSipGateways = [...sipGateways];
const newValue =
key === 'invalidIp' ||
key === 'invalidPort' ||
key === 'invalidInbound' ||
key === 'invalidOutbound'
? true
: (key === 'inbound') || (key === 'outbound')
? e.target.checked
: e.target.value;
newSipGateways[i][key] = newValue;
setSipGateways(newSipGateways);
};
const resetInvalidFields = () => {
setNameInvalid(false);
const newSipGateways = [...sipGateways];
newSipGateways.forEach((s, i) => {
newSipGateways[i].invalidIp = false;
newSipGateways[i].invalidPort = false;
newSipGateways[i].invalidInbound = false;
newSipGateways[i].invalidOutbound = false;
});
setSipGateways(newSipGateways);
};
const handleSumit = async e => {
try {
e.preventDefault();
setErrorMessage('');
resetInvalidFields();
let errorMessages = [];
let focusHasBeenSet = false;
if (!name) {
errorMessages.push('Please enter a name for this SIP Trunk.');
setNameInvalid(true);
if (!focusHasBeenSet) {
refName.current.focus();
focusHasBeenSet = true;
}
}
if (!sipGateways.length) {
errorMessages.push('You must provide at least one SIP Gateway.');
if (!focusHasBeenSet) {
refAdd.current.focus();
focusHasBeenSet = true;
}
}
const regIp = /^((25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])\.){3}(25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])$/;
const regFqdn = /^([a-zA-Z][^.]*)(\.[^.]+){2,}$/;
const regFqdnTopLevel = /^([a-zA-Z][^.]*)(\.[^.]+)$/;
const regPort = /^[0-9]+$/;
sipGateways.forEach(async (gateway, i) => {
//-----------------------------------------------------------------------------
// IP validation
//-----------------------------------------------------------------------------
const type = regIp.test(gateway.ip)
? 'ip'
: regFqdn.test(gateway.ip)
? 'fqdn'
: regFqdnTopLevel.test(gateway.ip)
? 'fqdn-top-level'
: 'invalid';
if (!gateway.ip) {
errorMessages.push('The IP Address cannot be blank. Please provide an IP address or delete the row.');
updateSipGateways(null, i, 'invalidIp');
if (!focusHasBeenSet) {
refIp.current[i].focus();
focusHasBeenSet = true;
}
}
else if (type === 'fqdn-top-level') {
errorMessages.push('When using an FQDN, you must use a subdomain (e.g. sip.example.com).');
updateSipGateways(null, i, 'invalidIp');
if (!focusHasBeenSet) {
refIp.current[i].focus();
focusHasBeenSet = true;
}
}
else if (type === 'invalid') {
errorMessages.push('Please provide a valid IP address or fully qualified domain name.');
updateSipGateways(null, i, 'invalidIp');
if (!focusHasBeenSet) {
refIp.current[i].focus();
focusHasBeenSet = true;
}
}
//-----------------------------------------------------------------------------
// Port validation
//-----------------------------------------------------------------------------
if (
gateway.port && (
!(regPort.test(gateway.port))
|| (parseInt(gateway.port) < 0)
|| (parseInt(gateway.port) > 65535)
)
) {
errorMessages.push('Please provide a valid port number between 0 and 65535');
updateSipGateways(null, i, 'invalidPort');
if (!focusHasBeenSet) {
refPort.current[i].focus();
focusHasBeenSet = true;
}
}
//-----------------------------------------------------------------------------
// inbound/outbound validation
//-----------------------------------------------------------------------------
if (type === 'fqdn' && (!gateway.outbound || gateway.inbound)) {
errorMessages.push('A fully qualified domain name may only be used for outbound calls.');
updateSipGateways(null, i, 'invalidIp');
if (gateway.inbound) updateSipGateways(null, i, 'invalidInbound');
if (!gateway.outbound) updateSipGateways(null, i, 'invalidOutbound');
if (!focusHasBeenSet) {
if (gateway.inbound) {
refInbound.current[i].focus();
} else {
refOutbound.current[i].focus();
}
focusHasBeenSet = true;
}
}
else if (!gateway.inbound && !gateway.outbound) {
errorMessages.push('Each SIP Gateway must accept inbound calls, outbound calls, or both.');
updateSipGateways(null, i, 'invalidInbound');
updateSipGateways(null, i, 'invalidOutbound');
if (!focusHasBeenSet) {
refInbound.current[i].focus();
focusHasBeenSet = true;
}
}
//-----------------------------------------------------------------------------
// duplicates validation
//-----------------------------------------------------------------------------
sipGateways.forEach((otherGateway, j) => {
if (i >= j) return;
if (!gateway.ip) return;
if (type === 'invalid') return;
if (gateway.ip === otherGateway.ip && gateway.port === otherGateway.port) {
errorMessages.push('Each row must have a unique IP/Port combination. Please delete the duplicate row.');
updateSipGateways(null, i, 'invalidIp');
updateSipGateways(null, i, 'invalidPort');
updateSipGateways(null, j, 'invalidIp');
updateSipGateways(null, j, 'invalidPort');
if (!focusHasBeenSet) {
refTrash.current[j].focus();
focusHasBeenSet = true;
}
}
});
});
// remove duplicate error messages
for (let i = 0; i < errorMessages.length; i++) {
for (let j = 0; j < errorMessages.length; j++) {
if (i >= j) continue;
if (errorMessages[i] === errorMessages[j]) {
errorMessages.splice(j, 1);
j = j - 1;
}
}
}
if (errorMessages.length > 1) {
setErrorMessage(errorMessages);
return;
} else if (errorMessages.length === 1) {
setErrorMessage(errorMessages[0]);
return;
}
//=============================================================================
// Submit
//=============================================================================
const creatingNewTrunk = props.type === 'add' || (props.type === 'setup' && !sipTrunks.length);
const method = creatingNewTrunk
? 'post'
: 'put';
const url = creatingNewTrunk
? '/VoipCarriers'
: `/VoipCarriers/${sipTrunkSid}`;
// Create or update SIP Trunk / VoIP Carrier
const voipCarrier = await axios({
method,
baseURL: process.env.REACT_APP_API_BASE_URL,
url,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
data: {
name,
description,
},
});
const voip_carrier_sid = voipCarrier.data.sid;
// get updated gateway info from API in order to delete ones that user has removed from UI
let sipGatewaysFromAPI;
if (!creatingNewTrunk) {
const results = await axios({
method: 'get',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: '/SipGateways',
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
sipGatewaysFromAPI = results.data.filter(s => s.voip_carrier_sid === sipTrunkSid);
}
//-----------------------------------------------------------------------------
// Create or Update SIP Gateways
//-----------------------------------------------------------------------------
// Keeping track of created SIP gateways in case one throws an error, then all
// of the ones created before that (as well as the sip trunk) have to be deleted.
let completedSipGateways = [];
try {
for (const s of sipGateways) {
const creatingNewGateway = creatingNewTrunk || s.sip_gateway_sid === '';
const method = creatingNewGateway
? 'post'
: 'put';
const url = creatingNewGateway
? '/SipGateways'
: `/SipGateways/${s.sip_gateway_sid}`;
const data = {
ipv4: s.ip,
port: s.port,
inbound: s.inbound,
outbound: s.outbound,
};
if (creatingNewGateway) {
data.voip_carrier_sid = voip_carrier_sid || sipTrunkSid;
}
const result = await axios({
method,
baseURL: process.env.REACT_APP_API_BASE_URL,
url,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
data,
});
completedSipGateways.push(result.data.sid);
};
} catch (err) {
if (completedSipGateways.length) {
for (const sid of completedSipGateways) {
await axios({
method: 'delete',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: `/SipGateways/${sid}`,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
}
}
await axios({
method: 'delete',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: `/VoipCarriers/${voip_carrier_sid}`,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
setErrorMessage((err.response && err.response.data && err.response.data.msg) || 'Something went wrong, please try again');
console.log(err.response || err);
}
// delete removed gateways (after add/update in case add/update caused errors)
if (!creatingNewTrunk) {
for (const remote of sipGatewaysFromAPI) {
const match = sipGateways.filter(local => local.sip_gateway_sid === remote.sip_gateway_sid);
if (!match.length) {
await axios({
method: 'delete',
baseURL: process.env.REACT_APP_API_BASE_URL,
url: `/SipGateways/${remote.sip_gateway_sid}`,
headers: {
Authorization: `Bearer ${localStorage.getItem('token')}`,
},
});
}
}
}
if (props.type === 'setup') {
history.push('/setup-complete');
} else {
history.push('/internal/sip-trunks');
const dispatchMessage = props.type === 'add'
? 'SIP trunk created successfully'
: 'SIP trunk updated successfully';
dispatch({
type: 'ADD',
level: 'success',
message: dispatchMessage
});
}
} catch(err) {
if (err.response && err.response.status === 401) {
localStorage.removeItem('token');
sessionStorage.clear();
history.push('/');
dispatch({
type: 'ADD',
level: 'error',
message: 'Your session has expired. Please log in and try again',
});
} else {
setErrorMessage((err.response && err.response.data && err.response.data.msg) || 'Something went wrong, please try again');
console.log(err.response || err);
}
}
};
return (
showLoader
? <Loader height={props.type === 'setup' ? '424px' : '376px'}/>
: <Form
large
onSubmit={handleSumit}
>
<Label htmlFor="name">Name</Label>
<Input
large={props.type === 'setup'}
name="name"
id="name"
value={name}
onChange={e => setName(e.target.value)}
placeholder="SIP trunk provider name"
invalid={nameInvalid}
autoFocus
ref={refName}
/>
<Label htmlFor="description">Description</Label>
<Input
large={props.type === 'setup'}
name="description"
id="description"
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="Optional"
/>
<hr style={{ margin: '0.5rem -2rem' }} />
<div
style={{ whiteSpace: 'nowrap' }}
>SIP Gateways</div>
{
sipGateways.length
? <div>{/* for CSS grid layout */}</div>
: null
}
{sipGateways.map((g, i) => (
<React.Fragment key={i}>
<Label htmlFor={`sipGatewaysIp[${i}]`}>IP Address</Label>
<InputGroup>
<Input
large={props.type === 'setup'}
name={`sipGatewaysIp[${i}]`}
id={`sipGatewaysIp[${i}]`}
value={sipGateways[i].ip}
onChange={e => updateSipGateways(e, i, 'ip')}
placeholder={'1.2.3.4'}
invalid={sipGateways[i].invalidIp}
ref={ref => refIp.current[i] = ref}
/>
<Label
middle
htmlFor={`sipGatewaysPort[${i}]`}
>
Port
</Label>
<Input
large={props.type === 'setup'}
width="5rem"
name={`sipGatewaysPort[${i}]`}
id={`sipGatewaysPort[${i}]`}
value={sipGateways[i].port}
onChange={e => updateSipGateways(e, i, 'port')}
placeholder="5060"
invalid={sipGateways[i].invalidPort}
ref={ref => refPort.current[i] = ref}
/>
<Checkbox
large={props.type === 'setup'}
id={`inbound[${i}]`}
label="Inbound"
tooltip="Sends us calls"
checked={sipGateways[i].inbound}
onChange={e => updateSipGateways(e, i, 'inbound')}
invalid={sipGateways[i].invalidInbound}
ref={ref => refInbound.current[i] = ref}
/>
<Checkbox
large={props.type === 'setup'}
id={`outbound[${i}]`}
label="Outbound"
tooltip="Accepts calls from us"
checked={sipGateways[i].outbound}
onChange={e => updateSipGateways(e, i, 'outbound')}
invalid={sipGateways[i].invalidOutbound}
ref={ref => refOutbound.current[i] = ref}
/>
<TrashButton
onClick={() => removeSipGateway(i)}
ref={ref => refTrash.current[i] = ref}
/>
</InputGroup>
</React.Fragment>
))}
<Button
square
type="button"
onClick={addSipGateway}
ref={refAdd}
>
+
</Button>
{errorMessage && (
<FormError grid message={errorMessage} />
)}
<InputGroup flexEnd spaced>
{props.type === 'edit' && (
<Button
grid
gray
type="button"
onClick={() => {
history.push('/internal/sip-trunks');
dispatch({
type: 'ADD',
level: 'info',
message: 'Changes canceled',
});
}}
>
Cancel
</Button>
)}
<Button
large={props.type === 'setup'}
grid
fullWidth={props.type === 'setup' || props.type === 'add'}
>
{props.type === 'setup'
? 'Save and Continue'
: props.type === 'add'
? 'Add SIP Trunk'
: 'Save'
}
</Button>
</InputGroup>
</Form>
);
};
export default SipTrunkForm;