mirror of
https://github.com/jambonz/chrome-extension-dialer.git
synced 2025-12-19 04:47:45 +00:00
wip
This commit is contained in:
@@ -7,6 +7,7 @@ import {
|
||||
Queue,
|
||||
RegisteredUser,
|
||||
StatusCodes,
|
||||
UpdateCall,
|
||||
} from "./types";
|
||||
import { MSG_SOMETHING_WRONG } from "./constants";
|
||||
import { getAdvancedSettings } from "src/storage";
|
||||
@@ -168,8 +169,10 @@ export const updateConferenceParticipantAction = (
|
||||
payload: ConferenceParticipantAction
|
||||
) => {
|
||||
const advancedSettings = getAdvancedSettings();
|
||||
return putFetch<EmptyData, ConferenceParticipantAction>(
|
||||
return putFetch<EmptyData, UpdateCall>(
|
||||
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Calls/${callSid}`,
|
||||
payload
|
||||
{
|
||||
conferenceParticipantAction: payload,
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -50,11 +50,14 @@ export type ConferenceParticipantActions =
|
||||
| "tag"
|
||||
| "untag"
|
||||
| "coach"
|
||||
| "uncoach"
|
||||
| "mute"
|
||||
| "unmute"
|
||||
| "hold"
|
||||
| "unhold";
|
||||
|
||||
export type ConferenceModes = "full_participant" | "muted" | "coach";
|
||||
|
||||
export interface ConferenceParticipantAction {
|
||||
action: ConferenceParticipantActions;
|
||||
tag: string;
|
||||
|
||||
@@ -3,7 +3,11 @@ function normalizeNumber(number: string): string {
|
||||
return number;
|
||||
} else if (/@/i.test(number)) {
|
||||
return number;
|
||||
} else if (number.startsWith("app-") || number.startsWith("queue-")) {
|
||||
} else if (
|
||||
number.startsWith("app-") ||
|
||||
number.startsWith("queue-") ||
|
||||
number.startsWith("conference-")
|
||||
) {
|
||||
return number;
|
||||
} else {
|
||||
return number.replace(/[()\-. ]*/g, "");
|
||||
|
||||
190
src/window/phone/conference.tsx
Normal file
190
src/window/phone/conference.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
HStack,
|
||||
Input,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { FormEvent, useEffect, useState } from "react";
|
||||
import { updateConferenceParticipantAction } from "src/api";
|
||||
import { ConferenceModes } from "src/api/types";
|
||||
import OutlineBox from "src/components/outline-box";
|
||||
import { SipConstants } from "src/lib";
|
||||
|
||||
type JoinConferenceProbs = {
|
||||
conferenceId?: string;
|
||||
callSid: string;
|
||||
callDuration: number;
|
||||
callStatus: string;
|
||||
handleCancel: () => void;
|
||||
call: (conference: string) => void;
|
||||
};
|
||||
export const JoinConference = ({
|
||||
conferenceId,
|
||||
callSid,
|
||||
callDuration,
|
||||
callStatus,
|
||||
handleCancel,
|
||||
call,
|
||||
}: JoinConferenceProbs) => {
|
||||
const [conferenceName, setConferenceName] = useState(conferenceId || "");
|
||||
const [appTitle, setAppTitle] = useState(
|
||||
!!conferenceId ? "Joining Conference" : "Start Conference"
|
||||
);
|
||||
const [submitTitle, setSubmitTitle] = useState(
|
||||
!!conferenceId ? "Joining Conference" : "Start Conference"
|
||||
);
|
||||
const [cancelTitle, setCancelTitle] = useState("Cancel");
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [speakOnlyTo, setSpeakOnlyTo] = useState("");
|
||||
const [tags, setTags] = useState("");
|
||||
const [mode, setMode] = useState<ConferenceModes>("full_participant");
|
||||
|
||||
useEffect(() => {
|
||||
switch (callStatus) {
|
||||
case SipConstants.SESSION_ANSWERED:
|
||||
setAppTitle("Conference");
|
||||
setSubmitTitle("Update");
|
||||
setCancelTitle("Hangup");
|
||||
setIsLoading(false);
|
||||
configureConferenceSession();
|
||||
break;
|
||||
case SipConstants.SESSION_ENDED:
|
||||
case SipConstants.SESSION_FAILED:
|
||||
setIsLoading(false);
|
||||
break;
|
||||
}
|
||||
}, [callStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
switch (mode) {
|
||||
case "full_participant":
|
||||
break;
|
||||
case "muted":
|
||||
break;
|
||||
case "coach":
|
||||
break;
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (callStatus !== SipConstants.SESSION_ANSWERED) {
|
||||
call(conferenceName);
|
||||
if (!callSid) {
|
||||
setIsLoading(true);
|
||||
}
|
||||
} else {
|
||||
configureConferenceSession();
|
||||
}
|
||||
};
|
||||
|
||||
const configureConferenceSession = async () => {
|
||||
if (callSid) {
|
||||
await updateConferenceParticipantAction(callSid, {
|
||||
action: mode === "muted" ? "mute" : "unmute",
|
||||
tag: "",
|
||||
});
|
||||
|
||||
await updateConferenceParticipantAction(callSid, {
|
||||
action: tags ? "tag" : "untag",
|
||||
tag: tags,
|
||||
});
|
||||
|
||||
await updateConferenceParticipantAction(callSid, {
|
||||
action: mode === "coach" ? "coach" : "uncoach",
|
||||
tag: speakOnlyTo,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box as="form" onSubmit={handleSubmit} w="full">
|
||||
<VStack spacing={4} mt="20px" w="full">
|
||||
<Text fontWeight="bold">{appTitle}</Text>
|
||||
{callDuration > 0 && (
|
||||
<Text fontSize="15px">
|
||||
{new Date(callDuration * 1000).toISOString().substr(11, 8)}
|
||||
</Text>
|
||||
)}
|
||||
<FormControl id="conference_name">
|
||||
<FormLabel>Conference name</FormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
isRequired
|
||||
value={conferenceName}
|
||||
onChange={(e) => setConferenceName(e.target.value)}
|
||||
disabled={!!conferenceId}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<OutlineBox title="Join as">
|
||||
<RadioGroup
|
||||
onChange={(e) => setMode(e as ConferenceModes)}
|
||||
value={mode}
|
||||
colorScheme="jambonz"
|
||||
>
|
||||
<VStack align="start">
|
||||
<Radio value="full_participant" variant="">
|
||||
Full participant
|
||||
</Radio>
|
||||
<Radio value="muted">Muted</Radio>
|
||||
<Radio value="coach">Coach mode</Radio>
|
||||
</VStack>
|
||||
</RadioGroup>
|
||||
|
||||
<FormControl id="speak_only_to">
|
||||
<FormLabel>Speak only to</FormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="tag"
|
||||
value={speakOnlyTo}
|
||||
onChange={(e) => setSpeakOnlyTo(e.target.value)}
|
||||
disabled={mode !== "coach"}
|
||||
required={mode === "coach"}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<FormControl id="tag">
|
||||
<FormLabel>Tag</FormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="tag"
|
||||
value={tags}
|
||||
onChange={(e) => setTags(e.target.value)}
|
||||
/>
|
||||
</FormControl>
|
||||
</OutlineBox>
|
||||
<HStack w="full">
|
||||
<Button
|
||||
colorScheme="jambonz"
|
||||
type="submit"
|
||||
w="full"
|
||||
isLoading={isLoading}
|
||||
>
|
||||
{submitTitle}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
colorScheme="grey"
|
||||
type="button"
|
||||
w="full"
|
||||
textColor="black"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
{cancelTitle}
|
||||
</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default JoinConference;
|
||||
@@ -62,7 +62,7 @@ import {
|
||||
faUserGroup,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import JoinConference from "./join-conference";
|
||||
import JoinConference from "./conference";
|
||||
|
||||
type PhoneProbs = {
|
||||
sipDomain: string;
|
||||
@@ -198,7 +198,9 @@ export const Phone = ({
|
||||
if (calledANumber) {
|
||||
if (
|
||||
!(
|
||||
calledANumber.startsWith("app-") || calledANumber.startsWith("queue-")
|
||||
calledANumber.startsWith("app-") ||
|
||||
calledANumber.startsWith("queue-") ||
|
||||
calledANumber.startsWith("conference-")
|
||||
)
|
||||
) {
|
||||
setInputNumber(calledANumber);
|
||||
@@ -641,41 +643,44 @@ export const Phone = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<IconButtonMenu
|
||||
icon={<FontAwesomeIcon icon={faPeopleGroup} />}
|
||||
tooltip="Join a conference"
|
||||
noResultLabel="No conference"
|
||||
onClick={(name, value) => {
|
||||
setPageView(PAGE_VIEW.JOIN_CONFERENCE);
|
||||
setSelectedConference(
|
||||
value === PAGE_VIEW.JOIN_CONFERENCE.toString() ? "" : value
|
||||
);
|
||||
}}
|
||||
onOpen={() => {
|
||||
return new Promise<IconButtonMenuItems[]>(
|
||||
(resolve, reject) => {
|
||||
getConferences()
|
||||
.then(({ json }) => {
|
||||
const sortedApps = json.sort((a, b) =>
|
||||
a.localeCompare(b)
|
||||
);
|
||||
resolve([
|
||||
{
|
||||
name: "Start new conference",
|
||||
value: PAGE_VIEW.JOIN_CONFERENCE.toString(),
|
||||
},
|
||||
...sortedApps.map((a) => ({
|
||||
name: a,
|
||||
value: a,
|
||||
})),
|
||||
]);
|
||||
})
|
||||
.catch((err) => reject(err));
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{registeredUser.allow_direct_app_calling && (
|
||||
<IconButtonMenu
|
||||
icon={<FontAwesomeIcon icon={faPeopleGroup} />}
|
||||
tooltip="Join a conference"
|
||||
noResultLabel="No conference"
|
||||
onClick={(name, value) => {
|
||||
setPageView(PAGE_VIEW.JOIN_CONFERENCE);
|
||||
setSelectedConference(
|
||||
value === PAGE_VIEW.JOIN_CONFERENCE.toString()
|
||||
? ""
|
||||
: value
|
||||
);
|
||||
}}
|
||||
onOpen={() => {
|
||||
return new Promise<IconButtonMenuItems[]>(
|
||||
(resolve, reject) => {
|
||||
getConferences()
|
||||
.then(({ json }) => {
|
||||
const sortedApps = json.sort((a, b) =>
|
||||
a.localeCompare(b)
|
||||
);
|
||||
resolve([
|
||||
{
|
||||
name: "Start new conference",
|
||||
value: PAGE_VIEW.JOIN_CONFERENCE.toString(),
|
||||
},
|
||||
...sortedApps.map((a) => ({
|
||||
name: a,
|
||||
value: a,
|
||||
})),
|
||||
]);
|
||||
})
|
||||
.catch((err) => reject(err));
|
||||
}
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
)}
|
||||
|
||||
@@ -793,12 +798,17 @@ export const Phone = ({
|
||||
<JoinConference
|
||||
conferenceId={selectedConference}
|
||||
callSid={callSid}
|
||||
callDuration={seconds}
|
||||
callStatus={callStatus}
|
||||
handleCancel={() => {
|
||||
if (isSipClientAnswered(callStatus)) {
|
||||
sipUA.current?.terminate(480, "Call Finished", undefined);
|
||||
}
|
||||
setPageView(PAGE_VIEW.DIAL_PAD);
|
||||
}}
|
||||
call={(name) => {
|
||||
setSelectedConference(name);
|
||||
sipUA.current?.call(`conf:${name}`);
|
||||
sipUA.current?.call(`conference-${name}`);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
Checkbox,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
HStack,
|
||||
Input,
|
||||
Text,
|
||||
VStack,
|
||||
} from "@chakra-ui/react";
|
||||
import { FormEvent, useState } from "react";
|
||||
import OutlineBox from "src/components/outline-box";
|
||||
|
||||
type JoinConferenceProbs = {
|
||||
conferenceId?: string;
|
||||
callSid: string;
|
||||
handleCancel: () => void;
|
||||
call: (conference: string) => void;
|
||||
};
|
||||
export const JoinConference = ({
|
||||
conferenceId,
|
||||
handleCancel,
|
||||
call,
|
||||
}: JoinConferenceProbs) => {
|
||||
const [conferenceName, setConferenceName] = useState(conferenceId || "");
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
call(conferenceName);
|
||||
};
|
||||
return (
|
||||
<Box as="form" onSubmit={handleSubmit} w="full">
|
||||
<VStack spacing={4} mt="20px" w="full">
|
||||
<Text fontWeight="bold">
|
||||
{!!conferenceId ? "Joining" : "Start"} conference
|
||||
</Text>
|
||||
<FormControl id="conference_name">
|
||||
<FormLabel>Conference name</FormLabel>
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
isRequired
|
||||
value={conferenceName}
|
||||
onChange={(e) => setConferenceName(e.target.value)}
|
||||
disabled={!!conferenceId}
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
<OutlineBox title="Join as">
|
||||
<Checkbox colorScheme="jambonz">Full participant</Checkbox>
|
||||
<Checkbox colorScheme="jambonz">Muted</Checkbox>
|
||||
<Checkbox colorScheme="jambonz">Coach mode</Checkbox>
|
||||
|
||||
<FormControl id="speak_only_to">
|
||||
<FormLabel>Speak only to</FormLabel>
|
||||
<Input type="text" placeholder="agent" />
|
||||
</FormControl>
|
||||
|
||||
<FormControl id="tag">
|
||||
<FormLabel>Tag</FormLabel>
|
||||
<Input type="text" placeholder="tags" />
|
||||
</FormControl>
|
||||
</OutlineBox>
|
||||
<HStack w="full">
|
||||
<Button colorScheme="jambonz" type="submit" w="full">
|
||||
{!!conferenceId ? "Join conference" : "Start conference"}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
colorScheme="grey"
|
||||
type="button"
|
||||
w="full"
|
||||
textColor="black"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</HStack>
|
||||
</VStack>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default JoinConference;
|
||||
Reference in New Issue
Block a user