mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-10-29 23:04:30 +00:00
Reorganize components
This commit is contained in:
1 parent
74572e3940
commit
13201ef03e
43 files changed
+87
-87
No files matched your search
@@ -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 — I'l complete later
|
||||
</Link>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountForm;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
Reference in new issue
Block a user