This commit is contained in:
Quan HL
2024-05-08 19:27:25 +07:00
parent 7d6efbfbaf
commit 7046813a4e
6 changed files with 251 additions and 125 deletions

View File

@@ -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,
}
);
};

View File

@@ -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;

View File

@@ -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, "");

View 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;

View File

@@ -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,7 +643,7 @@ export const Phone = ({
}}
/>
)}
{registeredUser.allow_direct_app_calling && (
<IconButtonMenu
icon={<FontAwesomeIcon icon={faPeopleGroup} />}
tooltip="Join a conference"
@@ -649,7 +651,9 @@ export const Phone = ({
onClick={(name, value) => {
setPageView(PAGE_VIEW.JOIN_CONFERENCE);
setSelectedConference(
value === PAGE_VIEW.JOIN_CONFERENCE.toString() ? "" : value
value === PAGE_VIEW.JOIN_CONFERENCE.toString()
? ""
: value
);
}}
onOpen={() => {
@@ -676,6 +680,7 @@ export const Phone = ({
);
}}
/>
)}
</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}`);
}}
/>
)}

View File

@@ -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;