mirror of
https://github.com/jambonz/jambonz-webapp.git
synced 2026-10-16 08:24:20 +00:00
544 lines
17 KiB
JavaScript
544 lines
17 KiB
JavaScript
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';
|
||
import Tooltip from '../elements/Tooltip';
|
||
import CopyableText from '../elements/CopyableText';
|
||
|
||
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 [ deviceCallingApplication, setDeviceCallingApplication ] = 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('');
|
||
const [ accountApplications, setAccountApplications ] = useState([]);
|
||
|
||
useEffect(() => {
|
||
const getAccounts = async () => {
|
||
try {
|
||
if (!localStorage.getItem('token')) {
|
||
history.push('/');
|
||
dispatch({
|
||
type: 'ADD',
|
||
level: 'error',
|
||
message: 'You must log in to view that page.',
|
||
});
|
||
return;
|
||
}
|
||
|
||
const promiseList = [];
|
||
const accountsPromise = axios({
|
||
method: 'get',
|
||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||
url: '/Accounts',
|
||
headers: {
|
||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||
},
|
||
});
|
||
promiseList.push(accountsPromise);
|
||
|
||
if (props.type === 'edit') {
|
||
const applicationsPromise = axios({
|
||
method: 'get',
|
||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||
url: '/Applications',
|
||
headers: {
|
||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||
},
|
||
});
|
||
promiseList.push(applicationsPromise);
|
||
}
|
||
|
||
if (props.type === 'add') {
|
||
const serviceProvidersPromise = axios({
|
||
method: 'get',
|
||
baseURL: process.env.REACT_APP_API_BASE_URL,
|
||
url: '/ServiceProviders',
|
||
headers: {
|
||
Authorization: `Bearer ${localStorage.getItem('token')}`,
|
||
},
|
||
});
|
||
promiseList.push(serviceProvidersPromise);
|
||
}
|
||
|
||
const promiseAllValues = await Promise.all(promiseList);
|
||
|
||
const accountsData = (promiseAllValues[0] && promiseAllValues[0].data) || [];
|
||
setAccounts(accountsData);
|
||
|
||
if (props.type === 'edit') {
|
||
const allApplications = (promiseAllValues[1] && promiseAllValues[1].data) || [];
|
||
const accountApplicationsData = allApplications.filter(app => {
|
||
return app.account_sid === props.account_sid;
|
||
});
|
||
setAccountApplications(accountApplicationsData);
|
||
}
|
||
|
||
if (props.type === 'add') {
|
||
const serviceProviders = (promiseAllValues[1] && promiseAllValues[1].data) || '';
|
||
setServiceProviderSid(serviceProviders[0].service_provider_sid);
|
||
}
|
||
|
||
if (props.type === 'setup' && accountsData.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
|
||
? accountsData.filter(a => a.account_sid === props.account_sid)
|
||
: accountsData;
|
||
|
||
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 || '');
|
||
setDeviceCallingApplication(acc.device_calling_application_sid || '');
|
||
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) {
|
||
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 {
|
||
dispatch({
|
||
type: 'ADD',
|
||
level: 'error',
|
||
message: (err.response && err.response.data && err.response.data.msg) || 'Something went wrong, please try again.',
|
||
});
|
||
console.log(err.response || err);
|
||
}
|
||
setShowLoader(false);
|
||
}
|
||
};
|
||
getAccounts();
|
||
// eslint-disable-next-line
|
||
}, []);
|
||
|
||
const handleSubmit = async (e) => {
|
||
let isMounted = true;
|
||
try {
|
||
setShowLoader(true);
|
||
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 (sipRealm && 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: name.trim(),
|
||
sip_realm: sipRealm.trim() || null,
|
||
registration_hook: {
|
||
url: regWebhook.trim(),
|
||
method: method,
|
||
username: user.trim() || null,
|
||
password: password || null,
|
||
},
|
||
};
|
||
|
||
if (props.type === 'add') {
|
||
axiosData.service_provider_sid = serviceProviderSid;
|
||
}
|
||
|
||
if (props.type === 'edit') {
|
||
axiosData.device_calling_application_sid = deviceCallingApplication || null;
|
||
}
|
||
|
||
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') {
|
||
isMounted = false;
|
||
history.push('/create-application');
|
||
} else {
|
||
isMounted = false;
|
||
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();
|
||
isMounted = false;
|
||
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);
|
||
}
|
||
} finally {
|
||
if (isMounted) {
|
||
setShowLoader(false);
|
||
}
|
||
}
|
||
};
|
||
|
||
return (
|
||
showLoader
|
||
? <Loader
|
||
height={
|
||
props.type === 'setup'
|
||
? '309px'
|
||
: props.type === 'edit'
|
||
? '381px'
|
||
: '292px'
|
||
}
|
||
/>
|
||
: <Form
|
||
large
|
||
wideLabel={props.type === 'edit'}
|
||
onSubmit={handleSubmit}
|
||
>
|
||
|
||
{props.type === 'edit' && (
|
||
<React.Fragment>
|
||
<Label>AccountSid</Label>
|
||
<CopyableText text={accountSid} textType="AccountSid" />
|
||
</React.Fragment>
|
||
)}
|
||
|
||
{(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}
|
||
/>
|
||
|
||
{props.type === 'edit' && (
|
||
<React.Fragment>
|
||
<Label tooltip htmlFor="deviceCallingApplication">
|
||
<span style={{ position: 'relative' }}>
|
||
Application for SIP Device Calls
|
||
<Tooltip large>
|
||
This application is used to handle incoming calls from SIP users who have registered to the Account’s SIP Realm.
|
||
</Tooltip>
|
||
</span>
|
||
</Label>
|
||
<Select
|
||
large={props.type === 'setup'}
|
||
name="deviceCallingApplication"
|
||
id="deviceCallingApplication"
|
||
value={deviceCallingApplication}
|
||
onChange={e => setDeviceCallingApplication(e.target.value)}
|
||
>
|
||
<option value="">-- NONE --</option>
|
||
{accountApplications && accountApplications.map(app => (
|
||
<option
|
||
key={app.application_sid}
|
||
value={app.application_sid}
|
||
>
|
||
{app.name}
|
||
</option>
|
||
))}
|
||
</Select>
|
||
</React.Fragment>
|
||
)}
|
||
|
||
<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)}
|
||
placeholder="Optional"
|
||
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}
|
||
placeholder="Optional"
|
||
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'll complete later
|
||
</Link>
|
||
)}
|
||
</Form>
|
||
);
|
||
};
|
||
|
||
export default AccountForm;
|