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,
|
Queue,
|
||||||
RegisteredUser,
|
RegisteredUser,
|
||||||
StatusCodes,
|
StatusCodes,
|
||||||
|
UpdateCall,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { MSG_SOMETHING_WRONG } from "./constants";
|
import { MSG_SOMETHING_WRONG } from "./constants";
|
||||||
import { getAdvancedSettings } from "src/storage";
|
import { getAdvancedSettings } from "src/storage";
|
||||||
@@ -168,8 +169,10 @@ export const updateConferenceParticipantAction = (
|
|||||||
payload: ConferenceParticipantAction
|
payload: ConferenceParticipantAction
|
||||||
) => {
|
) => {
|
||||||
const advancedSettings = getAdvancedSettings();
|
const advancedSettings = getAdvancedSettings();
|
||||||
return putFetch<EmptyData, ConferenceParticipantAction>(
|
return putFetch<EmptyData, UpdateCall>(
|
||||||
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Calls/${callSid}`,
|
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Calls/${callSid}`,
|
||||||
payload
|
{
|
||||||
|
conferenceParticipantAction: payload,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -50,11 +50,14 @@ export type ConferenceParticipantActions =
|
|||||||
| "tag"
|
| "tag"
|
||||||
| "untag"
|
| "untag"
|
||||||
| "coach"
|
| "coach"
|
||||||
|
| "uncoach"
|
||||||
| "mute"
|
| "mute"
|
||||||
| "unmute"
|
| "unmute"
|
||||||
| "hold"
|
| "hold"
|
||||||
| "unhold";
|
| "unhold";
|
||||||
|
|
||||||
|
export type ConferenceModes = "full_participant" | "muted" | "coach";
|
||||||
|
|
||||||
export interface ConferenceParticipantAction {
|
export interface ConferenceParticipantAction {
|
||||||
action: ConferenceParticipantActions;
|
action: ConferenceParticipantActions;
|
||||||
tag: string;
|
tag: string;
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ function normalizeNumber(number: string): string {
|
|||||||
return number;
|
return number;
|
||||||
} else if (/@/i.test(number)) {
|
} else if (/@/i.test(number)) {
|
||||||
return number;
|
return number;
|
||||||
} else if (number.startsWith("app-") || number.startsWith("queue-")) {
|
} else if (
|
||||||
|
number.startsWith("app-") ||
|
||||||
|
number.startsWith("queue-") ||
|
||||||
|
number.startsWith("conference-")
|
||||||
|
) {
|
||||||
return number;
|
return number;
|
||||||
} else {
|
} else {
|
||||||
return number.replace(/[()\-. ]*/g, "");
|
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,
|
faUserGroup,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import JoinConference from "./join-conference";
|
import JoinConference from "./conference";
|
||||||
|
|
||||||
type PhoneProbs = {
|
type PhoneProbs = {
|
||||||
sipDomain: string;
|
sipDomain: string;
|
||||||
@@ -198,7 +198,9 @@ export const Phone = ({
|
|||||||
if (calledANumber) {
|
if (calledANumber) {
|
||||||
if (
|
if (
|
||||||
!(
|
!(
|
||||||
calledANumber.startsWith("app-") || calledANumber.startsWith("queue-")
|
calledANumber.startsWith("app-") ||
|
||||||
|
calledANumber.startsWith("queue-") ||
|
||||||
|
calledANumber.startsWith("conference-")
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
setInputNumber(calledANumber);
|
setInputNumber(calledANumber);
|
||||||
@@ -641,41 +643,44 @@ export const Phone = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{registeredUser.allow_direct_app_calling && (
|
||||||
<IconButtonMenu
|
<IconButtonMenu
|
||||||
icon={<FontAwesomeIcon icon={faPeopleGroup} />}
|
icon={<FontAwesomeIcon icon={faPeopleGroup} />}
|
||||||
tooltip="Join a conference"
|
tooltip="Join a conference"
|
||||||
noResultLabel="No conference"
|
noResultLabel="No conference"
|
||||||
onClick={(name, value) => {
|
onClick={(name, value) => {
|
||||||
setPageView(PAGE_VIEW.JOIN_CONFERENCE);
|
setPageView(PAGE_VIEW.JOIN_CONFERENCE);
|
||||||
setSelectedConference(
|
setSelectedConference(
|
||||||
value === PAGE_VIEW.JOIN_CONFERENCE.toString() ? "" : value
|
value === PAGE_VIEW.JOIN_CONFERENCE.toString()
|
||||||
);
|
? ""
|
||||||
}}
|
: value
|
||||||
onOpen={() => {
|
);
|
||||||
return new Promise<IconButtonMenuItems[]>(
|
}}
|
||||||
(resolve, reject) => {
|
onOpen={() => {
|
||||||
getConferences()
|
return new Promise<IconButtonMenuItems[]>(
|
||||||
.then(({ json }) => {
|
(resolve, reject) => {
|
||||||
const sortedApps = json.sort((a, b) =>
|
getConferences()
|
||||||
a.localeCompare(b)
|
.then(({ json }) => {
|
||||||
);
|
const sortedApps = json.sort((a, b) =>
|
||||||
resolve([
|
a.localeCompare(b)
|
||||||
{
|
);
|
||||||
name: "Start new conference",
|
resolve([
|
||||||
value: PAGE_VIEW.JOIN_CONFERENCE.toString(),
|
{
|
||||||
},
|
name: "Start new conference",
|
||||||
...sortedApps.map((a) => ({
|
value: PAGE_VIEW.JOIN_CONFERENCE.toString(),
|
||||||
name: a,
|
},
|
||||||
value: a,
|
...sortedApps.map((a) => ({
|
||||||
})),
|
name: a,
|
||||||
]);
|
value: a,
|
||||||
})
|
})),
|
||||||
.catch((err) => reject(err));
|
]);
|
||||||
}
|
})
|
||||||
);
|
.catch((err) => reject(err));
|
||||||
}}
|
}
|
||||||
/>
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</HStack>
|
</HStack>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -793,12 +798,17 @@ export const Phone = ({
|
|||||||
<JoinConference
|
<JoinConference
|
||||||
conferenceId={selectedConference}
|
conferenceId={selectedConference}
|
||||||
callSid={callSid}
|
callSid={callSid}
|
||||||
|
callDuration={seconds}
|
||||||
|
callStatus={callStatus}
|
||||||
handleCancel={() => {
|
handleCancel={() => {
|
||||||
|
if (isSipClientAnswered(callStatus)) {
|
||||||
|
sipUA.current?.terminate(480, "Call Finished", undefined);
|
||||||
|
}
|
||||||
setPageView(PAGE_VIEW.DIAL_PAD);
|
setPageView(PAGE_VIEW.DIAL_PAD);
|
||||||
}}
|
}}
|
||||||
call={(name) => {
|
call={(name) => {
|
||||||
setSelectedConference(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