changes to sync

This commit is contained in:
khang
2022-08-16 14:09:02 -04:00
parent 2571bd50b5
commit 6cc44ecae0
5 changed files with 202 additions and 163 deletions

View File

@@ -283,6 +283,10 @@ export const getAccountWebhook = (sid: string) => {
);
};
export const getApplications = (sid: string) => {
return getFetch<Application[]>(`${API_ACCOUNTS}/${sid}/Applications`);
};
/** Wrappers for APIs that can have a mock dev server response */
export const getRecentCalls = (sid: string) => {

View File

@@ -13,32 +13,19 @@ type DeleteProps = {
handleSubmit: () => void;
};
type DeleteInfoParameter = {
item: Application | Account;
key1: string;
key2?: string;
const DeleteInfo = ({
label,
text,
}: {
label: string;
};
const DeleteInfo = ({ item, key1, key2, label }: DeleteInfoParameter) => {
text: string | null | undefined;
}) => {
return (
<ul className="m">
<li>
<strong>{label}:</strong>
</li>
<li className="txt--teal">
{
// this is getting out of hand
(item &&
((item[key1 as keyof typeof item] &&
key2 &&
item[key1 as keyof typeof item]![
key2 as keyof typeof item[keyof typeof item]
]) ||
item[key1 as keyof typeof item])) ||
"[None]"
}
</li>
<li className="txt--teal">{text}</li>
</ul>
);
};
@@ -65,25 +52,23 @@ export const DeleteApplication = ({
Are you sure you want to delete the application{" "}
<strong>{application.name}</strong>?
</P>
{account && <DeleteInfo item={account} key1="name" label="Account" />}
<DeleteInfo
item={application}
key1="call_hook"
key2="webhook_sid"
label="Calling Webhook"
/>
<DeleteInfo
item={application}
key1="call_status_hook"
key2="webhook_sid"
label="Call Status Webhook"
/>
<DeleteInfo
item={application}
key1="messaging_hook"
key2="webhook_sid"
label="Messaging Webhook"
/>
{account && <DeleteInfo label="Account" text={account.name} />}
{application && (
<>
<DeleteInfo
label="Calling Webhook"
text={application.call_hook?.webhook_sid || "[None]"}
/>
<DeleteInfo
label="Call Status Webhook"
text={application.call_status_hook?.webhook_sid || "[None]"}
/>
<DeleteInfo
label="Messaging Webhook"
text={application.messaging_hook?.webhook_sid || "[None]"}
/>
</>
)}
</Modal>
</>
);

View File

@@ -10,7 +10,7 @@ import type { Application } from "src/api/types";
export const EditApplication = () => {
const params = useParams();
const [data, refetch, error] = useApiData<Application[]>(
const [data, refetch, error] = useApiData<Application>(
`Applications/${params.application_sid}`
);

View File

@@ -1,5 +1,5 @@
import React, { useEffect, useState, useRef } from "react";
import { P, Button, ButtonGroup } from "jambonz-ui";
import { Button, ButtonGroup } from "jambonz-ui";
import { Link, useNavigate } from "react-router-dom";
import { toastError, toastSuccess } from "src/store";
@@ -30,11 +30,16 @@ import type {
Language,
} from "src/vendor/types";
import type { Account, WebHook, Application, FetchError } from "src/api/types";
import type {
Account,
WebHook,
Application,
FetchError,
WebhookMethod,
} from "src/api/types";
export type UseApplicationData = {
// data fed from ./edit
data: Application[] | null; // data returned is an array but there is only 1 (0) that matters
data: Application | null;
error: FetchError | null;
refetch: () => void;
};
@@ -47,36 +52,38 @@ export const ApplicationForm = ({
application = null,
}: ApplicationFormProps) => {
const navigate = useNavigate();
const [applicationName, setApplicationName] = useState<string>("");
const [applicationName, setApplicationName] = useState("");
const [applications] = useApiData<Application[]>("Applications");
const [accountSid, setAccountSid] = useState<string>("");
const [accountSid, setAccountSid] = useState("");
const [accounts] = useServiceProviderData<Account[]>("Accounts");
const refCallWebhookUser = useRef<HTMLInputElement>(null);
const refCallWebhookPass = useRef<HTMLInputElement>(null);
const [callWebhook, setCallWebhook] = useState<WebHook>(DEFAULT_WEBHOOK);
const [initialCallWebhook] = useState<boolean>(false);
const [initialCallWebhook, setInitialCallWebhook] = useState(false);
const refStatusWebhookUser = useRef<HTMLInputElement>(null);
const refStatusWebhookPass = useRef<HTMLInputElement>(null);
const [statusWebhook, setStatusWebhook] = useState<WebHook>(DEFAULT_WEBHOOK);
const [initialStatusWebhook] = useState<boolean>(false);
const [initialStatusWebhook, setInitialStatusWebhook] =
useState<boolean>(false);
const refMessageWebhookUser = useRef<HTMLInputElement>(null);
const refMessageWebhookPass = useRef<HTMLInputElement>(null);
const [messageWebhook, setMessageWebhook] =
useState<WebHook>(DEFAULT_WEBHOOK);
const [initialMessageWebhook] = useState<boolean>(false);
const [initialMessageWebhook, setInitialMessageWebhook] =
useState<boolean>(false);
const [synthVendor, setSynthVendor] =
useState<keyof SynthesisVendors>(VENDOR_GOOGLE);
const [synthLang, setSynthLang] = useState<string>(LANG_EN_US);
const [synthVoice, setSynthVoice] = useState<string>(LANG_EN_US_STANDARD_C);
const [synthLang, setSynthLang] = useState(LANG_EN_US);
const [synthVoice, setSynthVoice] = useState(LANG_EN_US_STANDARD_C);
const [recogVendor, setRecogVendor] =
useState<keyof RecognizerVendors>(VENDOR_GOOGLE);
const [recogLang, setRecogLang] = useState<string>(LANG_EN_US);
const [recogLang, setRecogLang] = useState(LANG_EN_US);
const [synthesis, setSynthesis] = useState<SynthesisVendors | null>(null);
const [recognizers, setRecognizers] = useState<RecognizerVendors | null>(
@@ -98,31 +105,34 @@ export const ApplicationForm = ({
const webhooks = [
{
label: "Call",
label: "Calling",
prefix: "call_webhook",
stateVal: callWebhook,
stateSet: setCallWebhook,
initialCheck: initialCallWebhook,
refUser: refCallWebhookUser,
refPass: refCallWebhookPass,
required: true,
},
{
label: "Status",
label: "Call Status",
prefix: "status_webhook",
stateVal: statusWebhook,
stateSet: setStatusWebhook,
initialCheck: initialStatusWebhook,
refUser: refStatusWebhookUser,
refPass: refStatusWebhookPass,
required: true,
},
{
label: "Message",
label: "Messaging",
prefix: "message_webhook",
stateVal: messageWebhook,
stateSet: setMessageWebhook,
initialCheck: initialMessageWebhook,
refUser: refMessageWebhookUser,
refPass: refMessageWebhookPass,
required: false,
},
];
@@ -144,16 +154,6 @@ export const ApplicationForm = ({
setMessage("");
if (!applicationName) {
setMessage("Application name must not be empty");
return;
}
if (!accountSid) {
setMessage("Account must be selected");
return;
}
webhooks.map((webhook) => {
if (
(webhook.stateVal?.username && !webhook.stateVal?.password) ||
@@ -169,14 +169,13 @@ export const ApplicationForm = ({
});
if (applications) {
// update can still have the same name
if (
applications.find(
(a) =>
a.name === applicationName &&
(!application ||
!application.data ||
a.application_sid !== application.data[0].application_sid)
a.application_sid !== application.data.application_sid)
)
) {
setMessage(
@@ -186,19 +185,21 @@ export const ApplicationForm = ({
}
}
const payload = {
name: applicationName,
call_hook: callWebhook || null,
account_sid: accountSid || null,
messaging_hook: messageWebhook || null,
call_status_hook: statusWebhook || null,
speech_synthesis_vendor: synthVendor || null,
speech_synthesis_language: synthLang || null,
speech_synthesis_voice: synthVoice || null,
speech_recognizer_vendor: recogVendor || null,
speech_recognizer_language: recogLang || null,
};
if (application && application.data) {
putApplication(application.data[0].application_sid, {
name: applicationName,
call_hook: callWebhook || null,
account_sid: accountSid || null,
messaging_hook: messageWebhook || null,
call_status_hook: statusWebhook || null,
speech_synthesis_vendor: synthVendor || null,
speech_synthesis_language: synthLang || null,
speech_synthesis_voice: synthVoice || null,
speech_recognizer_vendor: recogVendor || null,
speech_recognizer_language: recogLang || null,
})
putApplication(application.data.application_sid, payload)
.then(() => {
application.refetch();
toastSuccess("Application updated successfully");
@@ -207,19 +208,7 @@ export const ApplicationForm = ({
toastError(error.msg);
});
} else {
postApplication({
name: applicationName,
call_hook: callWebhook || null,
account_sid: accountSid || null,
messaging_hook: messageWebhook || null,
call_status_hook: statusWebhook || null,
application_sid: null,
speech_synthesis_vendor: synthVendor || null,
speech_synthesis_language: synthLang || null,
speech_synthesis_voice: synthVoice || null,
speech_recognizer_vendor: recogVendor || null,
speech_recognizer_language: recogLang || null,
})
postApplication(payload)
.then(() => {
toastSuccess("Application created successfully");
navigate(`${ROUTE_INTERNAL_APPLICATIONS}`);
@@ -234,39 +223,67 @@ export const ApplicationForm = ({
let ignore = false;
if (application && application.data) {
setApplicationName(application.data[0].name);
setApplicationName(application.data.name);
if (application.data[0].call_hook)
setCallWebhook(application.data[0].call_hook);
if (application.data.call_hook) {
setCallWebhook(application.data.call_hook);
if (application.data[0].account_sid)
setAccountSid(application.data[0].account_sid);
if (
application.data.call_hook.username ||
application.data.call_hook.password
)
setInitialCallWebhook(true);
else setInitialCallWebhook(false);
}
if (application.data[0].messaging_hook)
setMessageWebhook(application.data[0].messaging_hook);
if (application.data.call_status_hook) {
setStatusWebhook(application.data.call_status_hook);
if (application.data[0].speech_synthesis_vendor)
if (
application.data.call_status_hook.username ||
application.data.call_status_hook.password
)
setInitialStatusWebhook(true);
else setInitialStatusWebhook(false);
}
if (application.data.messaging_hook) {
setMessageWebhook(application.data.messaging_hook);
if (
application.data.messaging_hook.username ||
application.data.messaging_hook.password
)
setInitialMessageWebhook(true);
else setInitialMessageWebhook(false);
}
if (application.data.account_sid)
setAccountSid(application.data.account_sid);
if (application.data.messaging_hook)
setMessageWebhook(application.data.messaging_hook);
if (application.data.speech_synthesis_vendor)
setSynthVendor(
application.data[0].speech_synthesis_vendor as keyof SynthesisVendors
application.data.speech_synthesis_vendor as keyof SynthesisVendors
);
if (application.data[0].speech_synthesis_language)
if (application.data.speech_synthesis_language)
setSynthLang(
application.data[0]
.speech_synthesis_language as keyof RecognizerVendors
application.data.speech_synthesis_language as keyof RecognizerVendors
);
if (application.data[0].speech_synthesis_voice)
setSynthVoice(application.data[0].speech_synthesis_voice);
if (application.data.speech_synthesis_voice)
setSynthVoice(application.data.speech_synthesis_voice);
if (application.data[0].speech_recognizer_vendor)
if (application.data.speech_recognizer_vendor)
setRecogVendor(
application.data[0]
.speech_recognizer_vendor as keyof RecognizerVendors
application.data.speech_recognizer_vendor as keyof RecognizerVendors
);
if (application.data[0].speech_recognizer_language)
setRecogLang(application.data[0].speech_recognizer_language);
if (application.data.speech_recognizer_language)
setRecogLang(application.data.speech_recognizer_language);
}
Promise.all([
@@ -306,7 +323,7 @@ export const ApplicationForm = ({
return function cleanup() {
ignore = true;
};
}, [application]); // makes application loaded
}, [application, accounts]);
return (
<>
@@ -330,6 +347,7 @@ export const ApplicationForm = ({
<Selector
id="account_name"
name="account_name"
required
value={accountSid}
placeholder="Select an account"
options={[
@@ -350,29 +368,39 @@ export const ApplicationForm = ({
{webhooks.map((webhook) => {
return (
<fieldset key={webhook.prefix}>
<label htmlFor="registration_hook_url">
{webhook.label} Webhook
</label>
<input
id={`${webhook.prefix}_url`}
type="text"
name="url"
placeholder={`${webhook.label} Webhook`}
value={webhook.stateVal?.url || ""}
onChange={(e) =>
handleSetHook(e, webhook.stateVal, webhook.stateSet)
}
/>
<label htmlFor={`${webhook.prefix}_method`}>Method</label>
<Selector
id={`${webhook.prefix}_method`}
name="method"
value={webhook.stateVal?.method || ""}
onChange={(e) =>
handleSetHook(e, webhook.stateVal, webhook.stateSet)
}
options={webhook_method}
/>
<div className="multi">
<div className="inp">
<label htmlFor="registration_hook_url">
{webhook.label} Webhook
</label>
<input
id={`${webhook.prefix}_url`}
type="text"
name="url"
required={webhook.required}
placeholder={`${webhook.label} Webhook`}
value={webhook.stateVal?.url || ""}
onChange={(e) =>
handleSetHook(e, webhook.stateVal, webhook.stateSet)
}
/>
</div>
<div className="sel">
<label htmlFor={`${webhook.prefix}_method`}>Method</label>
<Selector
id={`${webhook.prefix}_method`}
name={`${webhook.prefix}_method`}
value={webhook.stateVal?.method || ""}
onChange={(e) => {
webhook.stateSet({
...webhook.stateVal,
method: e.target.value as WebhookMethod,
});
}}
options={webhook_method}
/>
</div>
</div>
<Checkzone
hidden
name={webhook.prefix}
@@ -384,7 +412,7 @@ export const ApplicationForm = ({
ref={webhook.refUser}
id={`${webhook.prefix}_username`}
type="text"
name="username"
name={`${webhook.prefix}_username`}
placeholder="Optional"
value={webhook.stateVal?.username || ""}
onChange={(e) =>
@@ -395,7 +423,7 @@ export const ApplicationForm = ({
<Passwd
ref={webhook.refPass}
id={`${webhook.prefix}_password`}
name="password"
name={`${webhook.prefix}_password`}
value={webhook.stateVal?.password || ""}
placeholder="Optional"
onChange={(e) =>
@@ -511,14 +539,6 @@ export const ApplicationForm = ({
)}
</>
)}
<P>
Selected recognizer vendor:{" "}
<strong>{recogVendor || "undefined"}</strong>.
</P>
<P>
Selected recognizer language:{" "}
<strong>{recogLang || "undefined"}</strong>.
</P>
{recognizers && (
<>
<fieldset>
@@ -573,19 +593,21 @@ export const ApplicationForm = ({
</>
)}
{message && <Message message={message} />}
<ButtonGroup left>
<Button
small
subStyle="grey"
as={Link}
to={ROUTE_INTERNAL_APPLICATIONS}
>
Cancel
</Button>
<Button type="submit" small>
Save
</Button>
</ButtonGroup>
<fieldset>
<ButtonGroup left>
<Button
small
subStyle="grey"
as={Link}
to={ROUTE_INTERNAL_APPLICATIONS}
>
Cancel
</Button>
<Button type="submit" small>
Save
</Button>
</ButtonGroup>
</fieldset>
</form>
</Section>
</>

View File

@@ -1,24 +1,28 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { H1, Button, Icon } from "jambonz-ui";
import { Link } from "react-router-dom";
import { useApiData, deleteApplication } from "src/api";
import { deleteApplication, getApplications, getFetch } from "src/api";
import { API_APPLICATIONS } from "src/api/constants";
import { ROUTE_INTERNAL_APPLICATIONS } from "src/router/routes";
import { Icons, Section, Spinner } from "src/components";
import { Icons, Section, Spinner, AccountFilter } from "src/components";
import { DeleteApplication } from "./delete";
import { toastError, toastSuccess } from "src/store";
import type { Application } from "src/api/types";
export const Applications = () => {
const [applications, refetch] = useApiData<Application[]>("Applications");
const [accountSid, setAccountSid] = useState("");
const [applications, setApplications] = useState<Application[] | null>(null);
const [application, setApplication] = useState<Application | null>(null);
const [refetch, setRefetch] = useState(0);
const handleDelete = () => {
if (application) {
deleteApplication(application.application_sid)
.then(() => {
refetch();
setRefetch(refetch + 1);
setApplication(null);
toastSuccess(
<>
@@ -32,6 +36,23 @@ export const Applications = () => {
}
};
useEffect(() => {
if (accountSid) {
getApplications(accountSid)
.then(({ json }) => setApplications(json))
.catch((error) => {
toastError(error.msg);
});
} else {
// accountSid is null is "All accounts"
getFetch<Application[]>(API_APPLICATIONS)
.then(({ json }) => setApplications(json))
.catch((error) => {
toastError(error.msg);
});
}
}, [accountSid, refetch]);
return (
<>
<section className="mast">
@@ -45,6 +66,13 @@ export const Applications = () => {
</Icon>
</Link>
</section>
<section className="filters">
<AccountFilter
label="Used by"
account={[accountSid, setAccountSid]}
defaultOption
/>
</section>
<Section
{...(applications && applications.length > 0 ? { slim: true } : {})}
>