mirror of
https://github.com/jambonz/chrome-extension-dialer.git
synced 2026-10-04 02:04:28 +00:00
done with implementation
This commit is contained in:
+2
-27
@@ -1,27 +1,2 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
node_modules
|
||||||
|
dist
|
||||||
# dependencies
|
|
||||||
/node_modules
|
|
||||||
/.pnp
|
|
||||||
.pnp.js
|
|
||||||
|
|
||||||
# testing
|
|
||||||
/coverage
|
|
||||||
|
|
||||||
# production
|
|
||||||
/build
|
|
||||||
|
|
||||||
/dist
|
|
||||||
/lib
|
|
||||||
/components
|
|
||||||
|
|
||||||
# misc
|
|
||||||
.DS_Store
|
|
||||||
.env.local
|
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|||||||
+15
-15
@@ -10,7 +10,7 @@ import {
|
|||||||
UpdateCall,
|
UpdateCall,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { MSG_SOMETHING_WRONG } from "./constants";
|
import { MSG_SOMETHING_WRONG } from "./constants";
|
||||||
import { getAdvancedSettings } from "src/storage";
|
import { getActiveSettings } from "src/storage";
|
||||||
import { EmptyData } from "src/common/types";
|
import { EmptyData } from "src/common/types";
|
||||||
|
|
||||||
const fetchTransport = <Type>(
|
const fetchTransport = <Type>(
|
||||||
@@ -87,8 +87,8 @@ const fetchTransport = <Type>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getAuthHeaders = () => {
|
const getAuthHeaders = () => {
|
||||||
const advancedSettings = getAdvancedSettings();
|
const advancedSettings = getActiveSettings();
|
||||||
let token = advancedSettings.apiKey ?? null;
|
let token = advancedSettings?.decoded?.apiKey ?? null;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
@@ -130,37 +130,37 @@ export const deleteFetch = <Type>(url: string) => {
|
|||||||
|
|
||||||
// GET Devices Users
|
// GET Devices Users
|
||||||
export const getRegisteredUser = () => {
|
export const getRegisteredUser = () => {
|
||||||
const advancedSettings = getAdvancedSettings();
|
const advancedSettings = getActiveSettings();
|
||||||
return getFetch<string[]>(
|
return getFetch<string[]>(
|
||||||
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/RegisteredSipUsers`
|
`${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/RegisteredSipUsers`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getApplications = () => {
|
export const getApplications = () => {
|
||||||
const advancedSettings = getAdvancedSettings();
|
const advancedSettings = getActiveSettings();
|
||||||
return getFetch<Application[]>(
|
return getFetch<Application[]>(
|
||||||
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Applications`
|
`${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/Applications`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getQueues = () => {
|
export const getQueues = () => {
|
||||||
const advancedSettings = getAdvancedSettings();
|
const advancedSettings = getActiveSettings();
|
||||||
return getFetch<Queue[]>(
|
return getFetch<Queue[]>(
|
||||||
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Queues`
|
`${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/Queues`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSelfRegisteredUser = (username: string) => {
|
export const getSelfRegisteredUser = (username: string) => {
|
||||||
const advancedSettings = getAdvancedSettings();
|
const advancedSettings = getActiveSettings();
|
||||||
return getFetch<RegisteredUser>(
|
return getFetch<RegisteredUser>(
|
||||||
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/RegisteredSipUsers/${username}`
|
`${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/RegisteredSipUsers/${username}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getConferences = () => {
|
export const getConferences = () => {
|
||||||
const advancedSettings = getAdvancedSettings();
|
const advancedSettings = getActiveSettings();
|
||||||
return getFetch<string[]>(
|
return getFetch<string[]>(
|
||||||
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Conferences`
|
`${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/Conferences`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -168,9 +168,9 @@ export const updateConferenceParticipantAction = (
|
|||||||
callSid: string,
|
callSid: string,
|
||||||
payload: ConferenceParticipantAction
|
payload: ConferenceParticipantAction
|
||||||
) => {
|
) => {
|
||||||
const advancedSettings = getAdvancedSettings();
|
const advancedSettings = getActiveSettings();
|
||||||
return putFetch<EmptyData, UpdateCall>(
|
return putFetch<EmptyData, UpdateCall>(
|
||||||
`${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Calls/${callSid}`,
|
`${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/Calls/${callSid}`,
|
||||||
{
|
{
|
||||||
conferenceParticipantAction: payload,
|
conferenceParticipantAction: payload,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,6 +57,16 @@ export interface AppSettings {
|
|||||||
sipUsername: string;
|
sipUsername: string;
|
||||||
sipPassword: string;
|
sipPassword: string;
|
||||||
sipDisplayName: string;
|
sipDisplayName: string;
|
||||||
|
|
||||||
|
accountSid?: string;
|
||||||
|
apiKey?: string;
|
||||||
|
apiServer?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppSettings {
|
||||||
|
active: boolean;
|
||||||
|
decoded: AppSettings;
|
||||||
|
id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConferenceSettings {
|
export interface ConferenceSettings {
|
||||||
@@ -71,6 +81,12 @@ export interface AdvancedAppSettings {
|
|||||||
apiServer: string;
|
apiServer: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IAdvancedAppSettings {
|
||||||
|
active: boolean;
|
||||||
|
decoded: AdvancedAppSettings;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CallHistory {
|
export interface CallHistory {
|
||||||
callSid: string;
|
callSid: string;
|
||||||
direction: SipCallDirection;
|
direction: SipCallDirection;
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
function AnimateOnShow({
|
||||||
|
children,
|
||||||
|
initial = -20,
|
||||||
|
exit = -20,
|
||||||
|
duration = 0.3,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
initial?: number;
|
||||||
|
exit?: number;
|
||||||
|
duration?: number;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: initial }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
exit={{ opacity: 0, y: exit }}
|
||||||
|
transition={{ duration: duration }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AnimateOnShow;
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Box, Text } from "@chakra-ui/react";
|
import { Box, Text } from "@chakra-ui/react";
|
||||||
|
|
||||||
type JambonzSwitchProbs = {
|
type JambonzSwitchProbs = {
|
||||||
onlabel: string;
|
onlabel?: string;
|
||||||
offLabel: string;
|
offLabel?: string;
|
||||||
checked: [boolean, React.Dispatch<React.SetStateAction<boolean>>];
|
checked: [boolean, React.Dispatch<React.SetStateAction<boolean>>];
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
onChange: (value: boolean) => void;
|
onChange: (value: boolean) => void;
|
||||||
@@ -17,7 +17,7 @@ function JambonzSwitch({
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
position="relative"
|
position="relative"
|
||||||
w="90px"
|
w="50px"
|
||||||
h="30px"
|
h="30px"
|
||||||
bg={isToggled ? "green.500" : "grey.500"}
|
bg={isToggled ? "green.500" : "grey.500"}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
@@ -30,22 +30,24 @@ function JambonzSwitch({
|
|||||||
}}
|
}}
|
||||||
_hover={{ cursor: "pointer" }}
|
_hover={{ cursor: "pointer" }}
|
||||||
>
|
>
|
||||||
<Text
|
{onlabel && offLabel && (
|
||||||
position="absolute"
|
<Text
|
||||||
top="50%"
|
position="absolute"
|
||||||
left={isToggled ? "40%" : "60%"}
|
top="50%"
|
||||||
transform="translate(-50%, -50%)"
|
left={isToggled ? "40%" : "60%"}
|
||||||
color={isToggled ? "white" : "black"}
|
transform="translate(-50%, -50%)"
|
||||||
fontWeight="bold"
|
color={isToggled ? "white" : "black"}
|
||||||
>
|
fontWeight="bold"
|
||||||
{isToggled ? onlabel : offLabel}
|
>
|
||||||
</Text>
|
{isToggled ? onlabel : offLabel}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
<Box
|
<Box
|
||||||
position="absolute"
|
position="absolute"
|
||||||
top="50%"
|
top="50%"
|
||||||
left={isToggled ? "70%" : "5%"}
|
left={isToggled ? "50%" : "5%"}
|
||||||
w="24px"
|
w="22px"
|
||||||
h="24px"
|
h="22px"
|
||||||
bg="white"
|
bg="white"
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
transform="translateY(-50%)"
|
transform="translateY(-50%)"
|
||||||
|
|||||||
+204
-12
@@ -3,6 +3,8 @@ import {
|
|||||||
AppSettings,
|
AppSettings,
|
||||||
CallHistory,
|
CallHistory,
|
||||||
ConferenceSettings,
|
ConferenceSettings,
|
||||||
|
IAdvancedAppSettings,
|
||||||
|
IAppSettings,
|
||||||
} from "src/common/types";
|
} from "src/common/types";
|
||||||
import { Buffer } from "buffer";
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
@@ -25,21 +27,154 @@ export const deleteConferenceSettings = () => {
|
|||||||
// Settings
|
// Settings
|
||||||
const SETTINGS_KEY = "SettingsKey";
|
const SETTINGS_KEY = "SettingsKey";
|
||||||
|
|
||||||
|
interface saveSettingFormat {
|
||||||
|
active: boolean;
|
||||||
|
encoded: string;
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const saveSettings = (settings: AppSettings) => {
|
export const saveSettings = (settings: AppSettings) => {
|
||||||
const encoded = Buffer.from(JSON.stringify(settings), "utf-8").toString(
|
const encoded = Buffer.from(JSON.stringify(settings), "utf-8").toString(
|
||||||
"base64"
|
"base64"
|
||||||
);
|
);
|
||||||
|
|
||||||
localStorage.setItem(SETTINGS_KEY, encoded);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getSettings = (): AppSettings => {
|
|
||||||
const str = localStorage.getItem(SETTINGS_KEY);
|
const str = localStorage.getItem(SETTINGS_KEY);
|
||||||
if (str) {
|
if (str) {
|
||||||
const planText = Buffer.from(str, "base64").toString("utf-8");
|
const parsed = JSON.parse(str);
|
||||||
return JSON.parse(planText) as AppSettings;
|
|
||||||
|
const data: IAppSettings[] = parsed.map((el: saveSettingFormat) => {
|
||||||
|
return {
|
||||||
|
active: el.active,
|
||||||
|
decoded: JSON.parse(
|
||||||
|
Buffer.from(el.encoded, "base64").toString("utf-8")
|
||||||
|
),
|
||||||
|
id: el.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const alreadyExists = data.filter(
|
||||||
|
(el) =>
|
||||||
|
el.decoded.sipDomain === settings.sipDomain &&
|
||||||
|
el.decoded.sipServerAddress === settings.sipServerAddress
|
||||||
|
);
|
||||||
|
if (!!alreadyExists.length) return;
|
||||||
|
localStorage.setItem(
|
||||||
|
SETTINGS_KEY,
|
||||||
|
JSON.stringify([
|
||||||
|
...parsed,
|
||||||
|
{ encoded, active: false, id: parsed.length + 1 },
|
||||||
|
])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(
|
||||||
|
SETTINGS_KEY,
|
||||||
|
JSON.stringify([{ id: 1, encoded, active: true }])
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return {} as AppSettings;
|
};
|
||||||
|
|
||||||
|
export const editSettings = (settings: AppSettings, id: number) => {
|
||||||
|
const encoded = Buffer.from(JSON.stringify(settings), "utf-8").toString(
|
||||||
|
"base64"
|
||||||
|
);
|
||||||
|
|
||||||
|
const str = localStorage.getItem(SETTINGS_KEY);
|
||||||
|
if (str) {
|
||||||
|
const parsed = JSON.parse(str);
|
||||||
|
|
||||||
|
// for edit:
|
||||||
|
const newData = parsed.map((el: saveSettingFormat) => {
|
||||||
|
if (el.id === id)
|
||||||
|
return {
|
||||||
|
id: el.id,
|
||||||
|
active: el.active,
|
||||||
|
encoded: encoded,
|
||||||
|
};
|
||||||
|
else return el;
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem(SETTINGS_KEY, JSON.stringify(newData));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const setActiveSettings = (id: number) => {
|
||||||
|
const str = localStorage.getItem(SETTINGS_KEY);
|
||||||
|
if (str) {
|
||||||
|
const parsed = JSON.parse(str);
|
||||||
|
|
||||||
|
// for edit:
|
||||||
|
const newData = parsed.map((el: saveSettingFormat) => {
|
||||||
|
if (el.id === id)
|
||||||
|
return {
|
||||||
|
id: el.id,
|
||||||
|
active: true,
|
||||||
|
encoded: el.encoded,
|
||||||
|
};
|
||||||
|
else
|
||||||
|
return {
|
||||||
|
id: el.id,
|
||||||
|
active: false,
|
||||||
|
encoded: el.encoded,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem(SETTINGS_KEY, JSON.stringify(newData));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteSettings = (id: number) => {
|
||||||
|
const str = localStorage.getItem(SETTINGS_KEY);
|
||||||
|
if (str) {
|
||||||
|
const parsed = JSON.parse(str);
|
||||||
|
|
||||||
|
// for edit:
|
||||||
|
const newData = parsed.filter((el: saveSettingFormat) => el.id !== id);
|
||||||
|
|
||||||
|
localStorage.setItem(SETTINGS_KEY, JSON.stringify(newData));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getSettings = (): IAppSettings[] => {
|
||||||
|
const str = localStorage.getItem(SETTINGS_KEY);
|
||||||
|
if (str) {
|
||||||
|
const data: { active: boolean; encoded: string; id: number }[] =
|
||||||
|
JSON.parse(str);
|
||||||
|
const decoded: IAppSettings[] = data.map((el) => {
|
||||||
|
return {
|
||||||
|
active: el.active,
|
||||||
|
decoded: JSON.parse(
|
||||||
|
Buffer.from(el.encoded, "base64").toString("utf-8")
|
||||||
|
),
|
||||||
|
id: el.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return decoded;
|
||||||
|
// const planText = Buffer.from(str, "base64").toString("utf-8");
|
||||||
|
// return JSON.parse(planText) as AppSettings;
|
||||||
|
}
|
||||||
|
// return {} as AppSettings;
|
||||||
|
return [] as IAppSettings[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActiveSettings = (): IAppSettings => {
|
||||||
|
const str = localStorage.getItem(SETTINGS_KEY);
|
||||||
|
if (str) {
|
||||||
|
const data: { active: boolean; encoded: string; id: number }[] =
|
||||||
|
JSON.parse(str);
|
||||||
|
const decoded: IAppSettings[] = data.map((el) => {
|
||||||
|
return {
|
||||||
|
active: el.active,
|
||||||
|
decoded: JSON.parse(
|
||||||
|
Buffer.from(el.encoded, "base64").toString("utf-8")
|
||||||
|
),
|
||||||
|
id: el.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return decoded.find((el) => el.active) as IAppSettings;
|
||||||
|
// const planText = Buffer.from(str, "base64").toString("utf-8");
|
||||||
|
// return JSON.parse(planText) as AppSettings;
|
||||||
|
}
|
||||||
|
// return {} as AppSettings;
|
||||||
|
return {} as IAppSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Advanced settings
|
// Advanced settings
|
||||||
@@ -50,19 +185,76 @@ export const saveAddvancedSettings = (settings: AdvancedAppSettings) => {
|
|||||||
"base64"
|
"base64"
|
||||||
);
|
);
|
||||||
|
|
||||||
localStorage.setItem(ADVANCED_SETTINGS_KET, encoded);
|
const str = localStorage.getItem(ADVANCED_SETTINGS_KET);
|
||||||
|
|
||||||
|
if (str) {
|
||||||
|
const data = JSON.parse(str);
|
||||||
|
const alreadyExists = data.filter(
|
||||||
|
(el: { encoded: string; active: boolean }) => el.encoded === encoded
|
||||||
|
);
|
||||||
|
if (!!alreadyExists.length) return;
|
||||||
|
|
||||||
|
localStorage.setItem(
|
||||||
|
ADVANCED_SETTINGS_KET,
|
||||||
|
JSON.stringify([...data, { encoded, active: false, id: data.length + 1 }])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(
|
||||||
|
ADVANCED_SETTINGS_KET,
|
||||||
|
JSON.stringify([{ encoded, active: true, id: 1 }])
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getAdvancedSettings = (): AdvancedAppSettings => {
|
export const getAdvancedSettings = (): IAdvancedAppSettings[] => {
|
||||||
const str = localStorage.getItem(ADVANCED_SETTINGS_KET);
|
const str = localStorage.getItem(ADVANCED_SETTINGS_KET);
|
||||||
|
|
||||||
if (str) {
|
if (str) {
|
||||||
const planText = Buffer.from(str, "base64").toString("utf-8");
|
const data: { active: boolean; encoded: string; id: number }[] =
|
||||||
return JSON.parse(planText) as AdvancedAppSettings;
|
JSON.parse(str);
|
||||||
|
const decoded: IAdvancedAppSettings[] = data.map((el) => {
|
||||||
|
return {
|
||||||
|
active: el.active,
|
||||||
|
decoded: JSON.parse(
|
||||||
|
Buffer.from(el.encoded, "base64").toString("utf-8")
|
||||||
|
),
|
||||||
|
id: el.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return decoded;
|
||||||
|
// const planText = Buffer.from(str, "base64").toString("utf-8");
|
||||||
|
// return JSON.parse(planText) as AppSettings;
|
||||||
}
|
}
|
||||||
return {} as AdvancedAppSettings;
|
// return {} as AppSettings;
|
||||||
|
return [] as IAdvancedAppSettings[];
|
||||||
|
// return [] as IAppSettings[];
|
||||||
|
};
|
||||||
|
export const getActiveAdvancedSettings = (): IAdvancedAppSettings => {
|
||||||
|
const str = localStorage.getItem(ADVANCED_SETTINGS_KET);
|
||||||
|
|
||||||
|
if (str) {
|
||||||
|
const data: { active: boolean; encoded: string; id: number }[] =
|
||||||
|
JSON.parse(str);
|
||||||
|
const decoded: IAdvancedAppSettings[] = data.map((el) => {
|
||||||
|
return {
|
||||||
|
active: el.active,
|
||||||
|
decoded: JSON.parse(
|
||||||
|
Buffer.from(el.encoded, "base64").toString("utf-8")
|
||||||
|
),
|
||||||
|
id: el.id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return decoded.find((el) => el.active) as IAdvancedAppSettings;
|
||||||
|
// const planText = Buffer.from(str, "base64").toString("utf-8");
|
||||||
|
// return JSON.parse(planText) as AppSettings;
|
||||||
|
}
|
||||||
|
// return {} as AppSettings;
|
||||||
|
return {} as IAdvancedAppSettings;
|
||||||
|
// return [] as IAppSettings[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Call History
|
// Call History
|
||||||
|
|
||||||
const historyKey = "History";
|
const historyKey = "History";
|
||||||
const MAX_HISTORY_COUNT = 20;
|
const MAX_HISTORY_COUNT = 20;
|
||||||
export const saveCallHistory = (username: string, call: CallHistory) => {
|
export const saveCallHistory = (username: string, call: CallHistory) => {
|
||||||
|
|||||||
@@ -47,4 +47,9 @@ const mainTheme = extendTheme({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const colors = {
|
||||||
|
//to use outside of chakra component
|
||||||
|
jambonz: "#DA1C5C",
|
||||||
|
};
|
||||||
|
|
||||||
export default mainTheme;
|
export default mainTheme;
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// fn to format call date:
|
||||||
|
export function transform(t1: number, t2: number) {
|
||||||
|
const diff = Math.abs(t1 - t2) / 1000; // Get the difference in seconds
|
||||||
|
|
||||||
|
const hours = Math.floor(diff / 3600);
|
||||||
|
const minutes = Math.floor((diff % 3600) / 60);
|
||||||
|
const seconds = Math.floor(diff % 60);
|
||||||
|
|
||||||
|
// Pad the values with a leading zero if they are less than 10
|
||||||
|
const hours1 = hours < 10 ? "0" + hours : hours;
|
||||||
|
const minutes1 = minutes < 10 ? "0" + minutes : minutes;
|
||||||
|
const seconds1 = seconds < 10 ? "0" + seconds : seconds;
|
||||||
|
|
||||||
|
return `${hours1}:${minutes1}:${seconds1}`;
|
||||||
|
}
|
||||||
+35
-29
@@ -5,25 +5,17 @@ import {
|
|||||||
TabPanel,
|
TabPanel,
|
||||||
TabPanels,
|
TabPanels,
|
||||||
Tabs,
|
Tabs,
|
||||||
Text,
|
|
||||||
Grid,
|
Grid,
|
||||||
Center,
|
|
||||||
HStack,
|
|
||||||
Image,
|
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import Phone from "./phone";
|
import Phone from "./phone";
|
||||||
import Settings from "./settings";
|
import Settings from "./settings";
|
||||||
import { DEFAULT_COLOR_SCHEME } from "src/common/constants";
|
import { DEFAULT_COLOR_SCHEME } from "src/common/constants";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import {
|
import { getActiveSettings, getCallHistories, getSettings } from "src/storage";
|
||||||
getAdvancedSettings,
|
|
||||||
getCallHistories,
|
|
||||||
getSettings,
|
|
||||||
} from "src/storage";
|
|
||||||
|
|
||||||
import jambonz from "src/imgs/jambonz.svg";
|
|
||||||
import CallHistories from "./history";
|
import CallHistories from "./history";
|
||||||
import { AdvancedAppSettings, CallHistory } from "src/common/types";
|
import { CallHistory, IAppSettings, SipClientStatus } from "src/common/types";
|
||||||
|
import Footer from "./footer/footer";
|
||||||
|
|
||||||
export const WindowApp = () => {
|
export const WindowApp = () => {
|
||||||
const [sipDomain, setSipDomain] = useState("");
|
const [sipDomain, setSipDomain] = useState("");
|
||||||
@@ -35,9 +27,26 @@ export const WindowApp = () => {
|
|||||||
const [calledNumber, setCalledNumber] = useState("");
|
const [calledNumber, setCalledNumber] = useState("");
|
||||||
const [calledName, setCalledName] = useState("");
|
const [calledName, setCalledName] = useState("");
|
||||||
const [tabIndex, setTabIndex] = useState(0);
|
const [tabIndex, setTabIndex] = useState(0);
|
||||||
const [advancedSettings, setAdvancedSettings] = useState<AdvancedAppSettings>(
|
const [status, setStatus] = useState<SipClientStatus>("stop");
|
||||||
getAdvancedSettings()
|
const [allSettings, setAllSettings] = useState<IAppSettings[]>([]);
|
||||||
|
const [advancedSettings, setAdvancedSettings] = useState<IAppSettings | null>(
|
||||||
|
null
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const loadSettings = () => {
|
||||||
|
const settings = getSettings();
|
||||||
|
|
||||||
|
const activeSettings = settings.find((el) => el.active);
|
||||||
|
|
||||||
|
setAllSettings(getSettings());
|
||||||
|
setAdvancedSettings(getActiveSettings());
|
||||||
|
setSipDomain(activeSettings?.decoded.sipDomain || "");
|
||||||
|
setSipServerAddress(activeSettings?.decoded.sipServerAddress || "");
|
||||||
|
setSipUsername(activeSettings?.decoded.sipUsername || "");
|
||||||
|
setSipPassword(activeSettings?.decoded.sipPassword || "");
|
||||||
|
setSipDisplayName(activeSettings?.decoded.sipDisplayName || "");
|
||||||
|
};
|
||||||
|
|
||||||
const tabsSettings = [
|
const tabsSettings = [
|
||||||
{
|
{
|
||||||
title: "Dialer",
|
title: "Dialer",
|
||||||
@@ -50,7 +59,10 @@ export const WindowApp = () => {
|
|||||||
sipServerAddress={sipServerAddress}
|
sipServerAddress={sipServerAddress}
|
||||||
calledNumber={[calledNumber, setCalledNumber]}
|
calledNumber={[calledNumber, setCalledNumber]}
|
||||||
calledName={[calledName, setCalledName]}
|
calledName={[calledName, setCalledName]}
|
||||||
|
stat={[status, setStatus]}
|
||||||
advancedSettings={advancedSettings}
|
advancedSettings={advancedSettings}
|
||||||
|
allSettings={allSettings}
|
||||||
|
reload={loadSettings}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -83,16 +95,6 @@ export const WindowApp = () => {
|
|||||||
setTabIndex(i);
|
setTabIndex(i);
|
||||||
setCallHistories(getCallHistories(sipUsername));
|
setCallHistories(getCallHistories(sipUsername));
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadSettings = () => {
|
|
||||||
const settings = getSettings();
|
|
||||||
setAdvancedSettings(getAdvancedSettings());
|
|
||||||
setSipDomain(settings.sipDomain);
|
|
||||||
setSipServerAddress(settings.sipServerAddress);
|
|
||||||
setSipUsername(settings.sipUsername);
|
|
||||||
setSipPassword(settings.sipPassword);
|
|
||||||
setSipDisplayName(settings.sipDisplayName);
|
|
||||||
};
|
|
||||||
return (
|
return (
|
||||||
<Grid h="100vh" templateRows="1fr auto">
|
<Grid h="100vh" templateRows="1fr auto">
|
||||||
<Box p={2}>
|
<Box p={2}>
|
||||||
@@ -122,12 +124,16 @@ export const WindowApp = () => {
|
|||||||
</TabPanels>
|
</TabPanels>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
<Center>
|
|
||||||
<HStack spacing={1} mb={2} align="start">
|
<Footer
|
||||||
<Text fontSize="14px">Powered by</Text>
|
sipServerAddress={sipServerAddress}
|
||||||
<Image src={jambonz} alt="Jambonz Logo" w="91px" h="31px" />
|
sipUsername={sipUsername}
|
||||||
</HStack>
|
sipDomain={sipDomain}
|
||||||
</Center>
|
sipDisplayName={sipDisplayName}
|
||||||
|
sipPassword={sipPassword}
|
||||||
|
status={status}
|
||||||
|
setStatus={setStatus}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,193 @@
|
|||||||
|
import { HStack, Image, Text, useToast } from "@chakra-ui/react";
|
||||||
|
import jambonz from "src/imgs/jambonz.svg";
|
||||||
|
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
|
||||||
|
import { SipClientStatus } from "src/common/types";
|
||||||
|
import { SipConstants, SipUA } from "src/lib";
|
||||||
|
import { DEFAULT_TOAST_DURATION } from "src/common/constants";
|
||||||
|
import JambonzSwitch from "src/components/switch";
|
||||||
|
import "./styles.scss";
|
||||||
|
|
||||||
|
function Footer({
|
||||||
|
status,
|
||||||
|
setStatus,
|
||||||
|
sipServerAddress,
|
||||||
|
sipUsername,
|
||||||
|
sipDomain,
|
||||||
|
sipPassword,
|
||||||
|
sipDisplayName,
|
||||||
|
}: {
|
||||||
|
status: string;
|
||||||
|
setStatus: Dispatch<SetStateAction<SipClientStatus>>;
|
||||||
|
sipServerAddress: string;
|
||||||
|
sipUsername: string;
|
||||||
|
sipDomain: string;
|
||||||
|
sipPassword: string;
|
||||||
|
sipDisplayName: string;
|
||||||
|
}) {
|
||||||
|
const [isConfigured, setIsConfigured] = useState(false);
|
||||||
|
|
||||||
|
const [isSwitchingUserStatus, setIsSwitchingUserStatus] = useState(false);
|
||||||
|
const [isOnline, setIsOnline] = useState(false);
|
||||||
|
|
||||||
|
const sipUA = useRef<SipUA | null>(null);
|
||||||
|
const sipUsernameRef = useRef("");
|
||||||
|
const sipPasswordRef = useRef("");
|
||||||
|
const sipServerAddressRef = useRef("");
|
||||||
|
const sipDomainRef = useRef("");
|
||||||
|
const sipDisplayNameRef = useRef("");
|
||||||
|
const unregisteredReasonRef = useRef("");
|
||||||
|
const isRestartRef = useRef(false);
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === "registered" || status === "disconnected") {
|
||||||
|
setIsSwitchingUserStatus(false);
|
||||||
|
setIsOnline(status === "registered");
|
||||||
|
}
|
||||||
|
}, [status]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
sipDomainRef.current = sipDomain;
|
||||||
|
sipUsernameRef.current = sipUsername;
|
||||||
|
sipPasswordRef.current = sipPassword;
|
||||||
|
sipServerAddressRef.current = sipServerAddress;
|
||||||
|
sipDisplayNameRef.current = sipDisplayName;
|
||||||
|
if (sipDomain && sipUsername && sipPassword && sipServerAddress) {
|
||||||
|
if (sipUA.current) {
|
||||||
|
if (sipUA.current.isConnected()) {
|
||||||
|
clientGoOffline();
|
||||||
|
isRestartRef.current = true;
|
||||||
|
} else {
|
||||||
|
createSipClient();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
createSipClient();
|
||||||
|
}
|
||||||
|
setIsConfigured(true);
|
||||||
|
} else {
|
||||||
|
setIsConfigured(false);
|
||||||
|
clientGoOffline();
|
||||||
|
}
|
||||||
|
}, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]);
|
||||||
|
|
||||||
|
const clientGoOffline = () => {
|
||||||
|
if (sipUA.current) {
|
||||||
|
sipUA.current.stop();
|
||||||
|
sipUA.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleGoOffline = (s: SipClientStatus) => {
|
||||||
|
if (s === status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (s === "unregistered") {
|
||||||
|
if (sipUA.current) {
|
||||||
|
sipUA.current.stop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (sipUA.current) {
|
||||||
|
sipUA.current.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createSipClient = () => {
|
||||||
|
setIsSwitchingUserStatus(true);
|
||||||
|
const client = {
|
||||||
|
username: `${sipUsernameRef.current}@${sipDomainRef.current}`,
|
||||||
|
password: sipPasswordRef.current,
|
||||||
|
name: sipDisplayNameRef.current ?? sipUsernameRef.current,
|
||||||
|
};
|
||||||
|
|
||||||
|
const settings = {
|
||||||
|
pcConfig: {
|
||||||
|
iceServers: [{ urls: ["stun:stun.l.google.com:19302"] }],
|
||||||
|
},
|
||||||
|
wsUri: sipServerAddressRef.current,
|
||||||
|
register: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const sipClient = new SipUA(client, settings);
|
||||||
|
|
||||||
|
// UA Status
|
||||||
|
sipClient.on(SipConstants.UA_REGISTERED, (args) => {
|
||||||
|
setStatus("registered");
|
||||||
|
});
|
||||||
|
sipClient.on(SipConstants.UA_UNREGISTERED, (args) => {
|
||||||
|
setStatus("unregistered");
|
||||||
|
if (sipUA.current) {
|
||||||
|
sipUA.current.stop();
|
||||||
|
}
|
||||||
|
unregisteredReasonRef.current = `User is not registered${
|
||||||
|
args.cause ? `, ${args.cause}` : ""
|
||||||
|
}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
sipClient.on(SipConstants.UA_DISCONNECTED, (args) => {
|
||||||
|
if (unregisteredReasonRef.current) {
|
||||||
|
toast({
|
||||||
|
title: unregisteredReasonRef.current,
|
||||||
|
status: "warning",
|
||||||
|
duration: DEFAULT_TOAST_DURATION,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
unregisteredReasonRef.current = "";
|
||||||
|
}
|
||||||
|
setStatus("disconnected");
|
||||||
|
if (isRestartRef.current) {
|
||||||
|
createSipClient();
|
||||||
|
isRestartRef.current = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.error) {
|
||||||
|
toast({
|
||||||
|
title: `Cannot connect to ${sipServerAddress}, ${args.reason}`,
|
||||||
|
status: "warning",
|
||||||
|
duration: DEFAULT_TOAST_DURATION,
|
||||||
|
isClosable: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sipClient.start();
|
||||||
|
sipUA.current = sipClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
paddingX={6}
|
||||||
|
mb={2}
|
||||||
|
justifyContent={"space-between"}
|
||||||
|
alignItems={"center"}
|
||||||
|
>
|
||||||
|
{isConfigured ? (
|
||||||
|
<HStack alignItems={"center"} flexWrap={"nowrap"} className="xs">
|
||||||
|
<JambonzSwitch
|
||||||
|
isDisabled={isSwitchingUserStatus}
|
||||||
|
checked={[isOnline, setIsOnline]}
|
||||||
|
onChange={(v) => {
|
||||||
|
setIsSwitchingUserStatus(true);
|
||||||
|
handleGoOffline(v ? "registered" : "unregistered");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text>You are {isOnline ? "online" : "offline"}</Text>
|
||||||
|
</HStack>
|
||||||
|
) : (
|
||||||
|
<span></span>
|
||||||
|
)}
|
||||||
|
<HStack
|
||||||
|
spacing={1}
|
||||||
|
alignItems={"start"}
|
||||||
|
justify={"flex-end"}
|
||||||
|
flexWrap={"wrap"}
|
||||||
|
>
|
||||||
|
<Text fontSize="14px">Powered by</Text>
|
||||||
|
<Image src={jambonz} alt="Jambonz Logo" w="91px" h="31px" />
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Footer;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@media screen and (max-width: 400px) {
|
||||||
|
.xs {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,9 +102,10 @@ export const CallHistoryItem = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
if (settings.sipUsername) {
|
const activeSettings = settings.find(el => el.active);
|
||||||
|
if (activeSettings?.decoded.sipUsername) {
|
||||||
isSaveCallHistory(
|
isSaveCallHistory(
|
||||||
settings.sipUsername,
|
activeSettings?.decoded.sipUsername,
|
||||||
call.callSid,
|
call.callSid,
|
||||||
!call.isSaved
|
!call.isSaved
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { HStack, Text, VStack } from "@chakra-ui/react";
|
||||||
|
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import React from "react";
|
||||||
|
import { IAppSettings } from "src/common/types";
|
||||||
|
|
||||||
|
function AvailableAccounts({
|
||||||
|
allSettings,
|
||||||
|
onSetActive,
|
||||||
|
}: {
|
||||||
|
allSettings: IAppSettings[];
|
||||||
|
onSetActive: (x: number) => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<VStack
|
||||||
|
w={"full"}
|
||||||
|
alignItems={"start"}
|
||||||
|
bg={"grey.200"}
|
||||||
|
borderRadius={"xl"}
|
||||||
|
className="absolute"
|
||||||
|
padding={3}
|
||||||
|
border={"1px"}
|
||||||
|
borderColor={"gray.400"}
|
||||||
|
boxShadow={"lg"}
|
||||||
|
>
|
||||||
|
{allSettings.map((el, i) => (
|
||||||
|
<HStack
|
||||||
|
key={i}
|
||||||
|
justifyContent={"start"}
|
||||||
|
_hover={{
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => onSetActive(el.id)}
|
||||||
|
>
|
||||||
|
{el.active && <FontAwesomeIcon icon={faCheck} />}
|
||||||
|
<Text marginLeft={el.active ? "-0.5" : "5"}>
|
||||||
|
{el.decoded.sipDisplayName || el.decoded.sipUsername}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Text>({`${el.decoded.sipUsername}@${el.decoded.sipDomain}`})</Text>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AvailableAccounts;
|
||||||
+78
-59
@@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
|
Box,
|
||||||
Button,
|
Button,
|
||||||
Center,
|
|
||||||
Circle,
|
Circle,
|
||||||
HStack,
|
HStack,
|
||||||
Heading,
|
Heading,
|
||||||
@@ -13,9 +13,9 @@ import {
|
|||||||
VStack,
|
VStack,
|
||||||
useToast,
|
useToast,
|
||||||
} from "@chakra-ui/react";
|
} from "@chakra-ui/react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
|
||||||
import {
|
import {
|
||||||
AdvancedAppSettings,
|
IAppSettings,
|
||||||
SipCallDirection,
|
SipCallDirection,
|
||||||
SipClientStatus,
|
SipClientStatus,
|
||||||
} from "src/common/types";
|
} from "src/common/types";
|
||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
getCurrentCall,
|
getCurrentCall,
|
||||||
saveCallHistory,
|
saveCallHistory,
|
||||||
saveCurrentCall,
|
saveCurrentCall,
|
||||||
|
setActiveSettings,
|
||||||
} from "src/storage";
|
} from "src/storage";
|
||||||
import { OutGoingCall } from "./outgoing-call";
|
import { OutGoingCall } from "./outgoing-call";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
@@ -47,10 +48,10 @@ import {
|
|||||||
getRegisteredUser,
|
getRegisteredUser,
|
||||||
getSelfRegisteredUser,
|
getSelfRegisteredUser,
|
||||||
} from "src/api";
|
} from "src/api";
|
||||||
import JambonzSwitch from "src/components/switch";
|
|
||||||
import { DEFAULT_TOAST_DURATION } from "src/common/constants";
|
import { DEFAULT_TOAST_DURATION } from "src/common/constants";
|
||||||
import { RegisteredUser } from "src/api/types";
|
import { RegisteredUser } from "src/api/types";
|
||||||
import {
|
import {
|
||||||
|
faChevronDown,
|
||||||
faCodeMerge,
|
faCodeMerge,
|
||||||
faList,
|
faList,
|
||||||
faMicrophone,
|
faMicrophone,
|
||||||
@@ -63,6 +64,8 @@ import {
|
|||||||
} 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 "./conference";
|
import JoinConference from "./conference";
|
||||||
|
import AnimateOnShow from "src/components/animate";
|
||||||
|
import AvailableAccounts from "./availableAccounts";
|
||||||
|
|
||||||
type PhoneProbs = {
|
type PhoneProbs = {
|
||||||
sipDomain: string;
|
sipDomain: string;
|
||||||
@@ -72,7 +75,10 @@ type PhoneProbs = {
|
|||||||
sipDisplayName: string;
|
sipDisplayName: string;
|
||||||
calledNumber: [string, React.Dispatch<React.SetStateAction<string>>];
|
calledNumber: [string, React.Dispatch<React.SetStateAction<string>>];
|
||||||
calledName: [string, React.Dispatch<React.SetStateAction<string>>];
|
calledName: [string, React.Dispatch<React.SetStateAction<string>>];
|
||||||
advancedSettings: AdvancedAppSettings;
|
advancedSettings: IAppSettings | null;
|
||||||
|
stat: [string, Dispatch<SetStateAction<SipClientStatus>>];
|
||||||
|
allSettings: IAppSettings[];
|
||||||
|
reload: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PAGE_VIEW {
|
enum PAGE_VIEW {
|
||||||
@@ -82,19 +88,24 @@ enum PAGE_VIEW {
|
|||||||
JOIN_CONFERENCE,
|
JOIN_CONFERENCE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add some basic details to advanced to match them, make basic compulsory to fill advanced.
|
||||||
|
|
||||||
export const Phone = ({
|
export const Phone = ({
|
||||||
sipDomain,
|
sipDomain,
|
||||||
sipServerAddress,
|
sipServerAddress,
|
||||||
sipUsername,
|
sipUsername,
|
||||||
sipPassword,
|
sipPassword,
|
||||||
sipDisplayName,
|
sipDisplayName,
|
||||||
|
stat: [status, setStatus],
|
||||||
calledNumber: [calledANumber, setCalledANumber],
|
calledNumber: [calledANumber, setCalledANumber],
|
||||||
calledName: [calledAName, setCalledAName],
|
calledName: [calledAName, setCalledAName],
|
||||||
advancedSettings,
|
advancedSettings,
|
||||||
|
allSettings,
|
||||||
|
reload,
|
||||||
}: PhoneProbs) => {
|
}: PhoneProbs) => {
|
||||||
const [inputNumber, setInputNumber] = useState("");
|
const [inputNumber, setInputNumber] = useState("");
|
||||||
const [appName, setAppName] = useState("");
|
const [appName, setAppName] = useState("");
|
||||||
const [status, setStatus] = useState<SipClientStatus>("stop");
|
// const [status, setStatus] = useState<SipClientStatus>("stop");
|
||||||
const [isConfigured, setIsConfigured] = useState(false);
|
const [isConfigured, setIsConfigured] = useState(false);
|
||||||
const [callStatus, setCallStatus] = useState(SipConstants.SESSION_ENDED);
|
const [callStatus, setCallStatus] = useState(SipConstants.SESSION_ENDED);
|
||||||
const [sessionDirection, setSessionDirection] =
|
const [sessionDirection, setSessionDirection] =
|
||||||
@@ -116,6 +127,8 @@ export const Phone = ({
|
|||||||
const [callSid, setCallSid] = useState("");
|
const [callSid, setCallSid] = useState("");
|
||||||
const [showConference, setShowConference] = useState(false);
|
const [showConference, setShowConference] = useState(false);
|
||||||
|
|
||||||
|
const [showAccounts, setShowAccounts] = useState(false);
|
||||||
|
|
||||||
const inputNumberRef = useRef(inputNumber);
|
const inputNumberRef = useRef(inputNumber);
|
||||||
const sessionDirectionRef = useRef(sessionDirection);
|
const sessionDirectionRef = useRef(sessionDirection);
|
||||||
const sipUA = useRef<SipUA | null>(null);
|
const sipUA = useRef<SipUA | null>(null);
|
||||||
@@ -156,7 +169,7 @@ export const Phone = ({
|
|||||||
}
|
}
|
||||||
fetchRegisterUser();
|
fetchRegisterUser();
|
||||||
getConferences()
|
getConferences()
|
||||||
.then(() => {
|
?.then(() => {
|
||||||
setShowConference(true);
|
setShowConference(true);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@@ -165,7 +178,7 @@ export const Phone = ({
|
|||||||
}, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]);
|
}, [sipDomain, sipUsername, sipPassword, sipServerAddress, sipDisplayName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setIsAdvancedMode(!!advancedSettings.accountSid);
|
setIsAdvancedMode(!!advancedSettings?.decoded?.accountSid);
|
||||||
fetchRegisterUser();
|
fetchRegisterUser();
|
||||||
}, [advancedSettings]);
|
}, [advancedSettings]);
|
||||||
|
|
||||||
@@ -440,21 +453,6 @@ export const Phone = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGoOffline = (s: SipClientStatus) => {
|
|
||||||
if (s === status) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (s === "unregistered") {
|
|
||||||
if (sipUA.current) {
|
|
||||||
sipUA.current.stop();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (sipUA.current) {
|
|
||||||
sipUA.current.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleHangup = () => {
|
const handleHangup = () => {
|
||||||
if (isSipClientAnswered(callStatus) || isSipClientRinging(callStatus)) {
|
if (isSipClientAnswered(callStatus) || isSipClientRinging(callStatus)) {
|
||||||
sipUA.current?.terminate(480, "Call Finished", undefined);
|
sipUA.current?.terminate(480, "Call Finished", undefined);
|
||||||
@@ -497,47 +495,68 @@ export const Phone = ({
|
|||||||
return status === "registered";
|
return status === "registered";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetActive = (id: number) => {
|
||||||
|
setActiveSettings(id);
|
||||||
|
setShowAccounts(false);
|
||||||
|
reload();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Center flexDirection="column">
|
<Box flexDirection="column">
|
||||||
{isConfigured ? (
|
{isConfigured ? (
|
||||||
<>
|
<>
|
||||||
<HStack spacing={2} boxShadow="md" w="full" borderRadius={5} p={2}>
|
<Text fontSize={"small"} fontWeight={"semibold"} color={"gray.600"}>
|
||||||
<Image
|
Account
|
||||||
src={isStatusRegistered() ? GreenAvatar : Avatar}
|
</Text>
|
||||||
boxSize="35px"
|
<Box className="relative" w={"full"}>
|
||||||
/>
|
<HStack
|
||||||
<VStack alignItems="start" w="full" spacing={0}>
|
onClick={() => setShowAccounts(true)}
|
||||||
<HStack spacing={2} w="full">
|
_hover={{
|
||||||
<Text fontWeight="bold" fontSize="13px">
|
cursor: "pointer",
|
||||||
{sipDisplayName || sipUsername}
|
}}
|
||||||
</Text>
|
spacing={2}
|
||||||
<Circle
|
boxShadow="md"
|
||||||
size="8px"
|
w="full"
|
||||||
bg={isStatusRegistered() ? "green.500" : "gray.500"}
|
borderRadius={5}
|
||||||
/>
|
paddingY={2}
|
||||||
</HStack>
|
paddingX={3.5}
|
||||||
<Text fontWeight="bold" w="full">
|
>
|
||||||
{`${sipUsername}@${sipDomain}`}
|
<Image
|
||||||
</Text>
|
src={isStatusRegistered() ? GreenAvatar : Avatar}
|
||||||
</VStack>
|
boxSize="35px"
|
||||||
|
|
||||||
<Spacer />
|
|
||||||
<VStack h="full" align="center">
|
|
||||||
<JambonzSwitch
|
|
||||||
isDisabled={isSwitchingUserStatus}
|
|
||||||
onlabel="Online"
|
|
||||||
offLabel="Offline"
|
|
||||||
checked={[isOnline, setIsOnline]}
|
|
||||||
onChange={(v) => {
|
|
||||||
setIsSwitchingUserStatus(true);
|
|
||||||
handleGoOffline(v ? "registered" : "unregistered");
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</VStack>
|
<VStack alignItems="start" w="full" spacing={0}>
|
||||||
</HStack>
|
<HStack spacing={2} w="full">
|
||||||
|
<Text fontWeight="bold" fontSize="13px">
|
||||||
|
{sipDisplayName || sipUsername}
|
||||||
|
</Text>
|
||||||
|
<Circle
|
||||||
|
size="8px"
|
||||||
|
bg={isStatusRegistered() ? "green.500" : "gray.500"}
|
||||||
|
/>
|
||||||
|
</HStack>
|
||||||
|
<Text fontWeight="bold" w="full">
|
||||||
|
{`${sipUsername}@${sipDomain}`}
|
||||||
|
</Text>
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
<Spacer />
|
||||||
|
<VStack h="full" align="center">
|
||||||
|
<FontAwesomeIcon icon={faChevronDown} />
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
{showAccounts && (
|
||||||
|
<AnimateOnShow initial={2} exit={0} duration={0.01}>
|
||||||
|
<AvailableAccounts
|
||||||
|
allSettings={allSettings}
|
||||||
|
onSetActive={handleSetActive}
|
||||||
|
/>
|
||||||
|
</AnimateOnShow>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Heading size="md" mb={2}>
|
<Heading textAlign={"center"} size="md" mb={2}>
|
||||||
Go to Settings to configure your account
|
Go to Settings to configure your account
|
||||||
</Heading>
|
</Heading>
|
||||||
)}
|
)}
|
||||||
@@ -822,7 +841,7 @@ export const Phone = ({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Center>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,11 @@
|
|||||||
filter: blur(5px);
|
filter: blur(5px);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.absolute {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionButton,
|
||||||
|
AccordionItem,
|
||||||
|
AccordionPanel,
|
||||||
|
useDisclosure,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { IAppSettings } from "src/common/types";
|
||||||
|
import AccountForm from "src/window/settings/accountForm";
|
||||||
|
import SettingItem from "src/window/settings/settingItem";
|
||||||
|
|
||||||
|
export function AccordionList({
|
||||||
|
allSettings,
|
||||||
|
reload,
|
||||||
|
}: {
|
||||||
|
allSettings: IAppSettings[];
|
||||||
|
reload: () => void;
|
||||||
|
}) {
|
||||||
|
const { isOpen, onToggle } = useDisclosure();
|
||||||
|
const [openAcc, setOpenAcc] = useState(0);
|
||||||
|
|
||||||
|
const closeFormInAccordion = function () {
|
||||||
|
reload();
|
||||||
|
onToggle();
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleToggleAcc(accIndex: number) {
|
||||||
|
setOpenAcc(accIndex);
|
||||||
|
onToggle();
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Accordion index={isOpen ? [openAcc] : []} allowToggle>
|
||||||
|
{allSettings.map((data, index) => (
|
||||||
|
<AccordionItem key={index}>
|
||||||
|
<AccordionButton onClick={() => handleToggleAcc(index)}>
|
||||||
|
<SettingItem data={data} />
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel pb={4}>
|
||||||
|
<AccountForm
|
||||||
|
formData={data}
|
||||||
|
handleClose={closeFormInAccordion}
|
||||||
|
inputUniqueId={`${data.id}`}
|
||||||
|
/>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,298 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
HStack,
|
||||||
|
Input,
|
||||||
|
Text,
|
||||||
|
useToast,
|
||||||
|
VStack,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { DEFAULT_TOAST_DURATION } from "src/common/constants";
|
||||||
|
import { AppSettings, IAppSettings } from "src/common/types";
|
||||||
|
import PasswordInput from "src/components/password-input";
|
||||||
|
import { deleteSettings, editSettings, saveSettings } from "src/storage";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import {
|
||||||
|
faCheckCircle,
|
||||||
|
faCircleXmark,
|
||||||
|
faShuffle,
|
||||||
|
faTrashCan,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { normalizeUrl } from "src/utils";
|
||||||
|
import { getApplications } from "src/api";
|
||||||
|
import { colors } from "src/theme";
|
||||||
|
|
||||||
|
function AccountForm({
|
||||||
|
closeForm,
|
||||||
|
formData,
|
||||||
|
handleClose,
|
||||||
|
inputUniqueId,
|
||||||
|
}: {
|
||||||
|
closeForm?: () => void;
|
||||||
|
formData?: IAppSettings;
|
||||||
|
handleClose?: () => void;
|
||||||
|
inputUniqueId?: string; //duplicate form input id error fix
|
||||||
|
}) {
|
||||||
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||||
|
const [sipDomain, setSipDomain] = useState("");
|
||||||
|
const [sipServerAddress, setSipServerAddress] = useState("");
|
||||||
|
const [sipUsername, setSipUsername] = useState("");
|
||||||
|
const [sipPassword, setSipPassword] = useState("");
|
||||||
|
const [sipDisplayName, setSipDisplayName] = useState("");
|
||||||
|
|
||||||
|
const [apiKey, setApiKey] = useState<string>("");
|
||||||
|
const [apiServer, setApiServer] = useState<string | undefined>("");
|
||||||
|
const [accountSid, setAccountSid] = useState<string | undefined>("");
|
||||||
|
const [isCredentialOk, setIsCredentialOk] = useState<boolean>(false);
|
||||||
|
const [isAdvancedMode, setIsAdvancedMode] = useState<boolean>(false);
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
function () {
|
||||||
|
if (formData) {
|
||||||
|
setSipDisplayName(formData.decoded.sipDisplayName);
|
||||||
|
setSipDomain(formData.decoded.sipDomain);
|
||||||
|
setSipServerAddress(formData.decoded.sipServerAddress);
|
||||||
|
setSipUsername(formData.decoded.sipUsername);
|
||||||
|
setSipPassword(formData.decoded.sipPassword);
|
||||||
|
|
||||||
|
setAccountSid(formData.decoded.accountSid);
|
||||||
|
setApiKey(formData.decoded.apiKey || "");
|
||||||
|
setApiServer(formData.decoded.apiServer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formData, handleClose]
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkCredential = () => {
|
||||||
|
getApplications()
|
||||||
|
.then(() => {
|
||||||
|
setIsCredentialOk(true);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
setIsCredentialOk(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const settings: AppSettings = {
|
||||||
|
sipDomain,
|
||||||
|
sipServerAddress,
|
||||||
|
sipUsername,
|
||||||
|
sipPassword,
|
||||||
|
sipDisplayName,
|
||||||
|
accountSid,
|
||||||
|
apiKey,
|
||||||
|
apiServer: apiServer ? normalizeUrl(apiServer) : "",
|
||||||
|
};
|
||||||
|
|
||||||
|
formData ? editSettings(settings, formData.id) : saveSettings(settings);
|
||||||
|
|
||||||
|
if (showAdvanced) {
|
||||||
|
setIsAdvancedMode(true);
|
||||||
|
checkCredential();
|
||||||
|
}
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "Settings saved successfully",
|
||||||
|
status: "success",
|
||||||
|
duration: DEFAULT_TOAST_DURATION,
|
||||||
|
isClosable: true,
|
||||||
|
colorScheme: "jambonz",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (formData) {
|
||||||
|
handleClose && handleClose();
|
||||||
|
} else {
|
||||||
|
closeForm && closeForm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteSetting = (id: number) => {
|
||||||
|
deleteSettings(id);
|
||||||
|
if (formData) {
|
||||||
|
handleClose && handleClose();
|
||||||
|
} else {
|
||||||
|
closeForm && closeForm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetSetting = () => {
|
||||||
|
// saveSettings({} as AppSettings);
|
||||||
|
setSipDomain("");
|
||||||
|
setSipServerAddress("");
|
||||||
|
setSipUsername("");
|
||||||
|
setSipPassword("");
|
||||||
|
setSipDisplayName("");
|
||||||
|
|
||||||
|
setApiKey("");
|
||||||
|
setApiServer("");
|
||||||
|
setAccountSid("");
|
||||||
|
setIsAdvancedMode(false);
|
||||||
|
|
||||||
|
if (formData) {
|
||||||
|
handleClose && handleClose();
|
||||||
|
} else {
|
||||||
|
closeForm && closeForm();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<VStack spacing={2} w="full" p={0}>
|
||||||
|
<VStack spacing={2} w="full" p={0}>
|
||||||
|
<FormControl id={`sip_display_name${inputUniqueId}`}>
|
||||||
|
<FormLabel>SIP Display Name (Optional)</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Display name"
|
||||||
|
value={sipDisplayName}
|
||||||
|
onChange={(e) => setSipDisplayName(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl id={`jambonz_sip_domain${inputUniqueId}`}>
|
||||||
|
<FormLabel>Jambonz SIP Domain</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Domain"
|
||||||
|
isRequired
|
||||||
|
value={sipDomain}
|
||||||
|
onChange={(e) => setSipDomain(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl id={`jambonz_server_address${inputUniqueId}`}>
|
||||||
|
<FormLabel>Jambonz Server Address</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="wss://sip.jambonz.cloud:8443/"
|
||||||
|
isRequired
|
||||||
|
value={sipServerAddress}
|
||||||
|
onChange={(e) => setSipServerAddress(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl id={`username${inputUniqueId}`}>
|
||||||
|
<FormLabel>SIP Username</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="Username"
|
||||||
|
isRequired
|
||||||
|
value={sipUsername}
|
||||||
|
onChange={(e) => setSipUsername(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl id={`password${inputUniqueId}`}>
|
||||||
|
<FormLabel fontWeight="">SIP Password</FormLabel>
|
||||||
|
<PasswordInput
|
||||||
|
password={[sipPassword, setSipPassword]}
|
||||||
|
placeHolder="Enter your password"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
{showAdvanced && (
|
||||||
|
<VStack w={"full"} bg={"gray.50"} borderRadius={"2xl"} p={"3.5"}>
|
||||||
|
<FormControl id={`jambonz_api_server${inputUniqueId}`}>
|
||||||
|
<FormLabel>Jambonz API Server Base URL</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
placeholder="https://jambonz.cloud/api"
|
||||||
|
isRequired
|
||||||
|
value={apiServer}
|
||||||
|
onChange={(e) => setApiServer(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl id={`jambonz_account_sid${inputUniqueId}`}>
|
||||||
|
<FormLabel>Jambonz Account Sid</FormLabel>
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
isRequired
|
||||||
|
value={accountSid}
|
||||||
|
onChange={(e) => setAccountSid(e.target.value)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormControl id={`api_key${inputUniqueId}`}>
|
||||||
|
<FormLabel>API Key</FormLabel>
|
||||||
|
<PasswordInput
|
||||||
|
password={[apiKey || "", setApiKey]}
|
||||||
|
isRequired
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</VStack>
|
||||||
|
)}
|
||||||
|
<Center flexDirection={"column"} gap={"2.5"}>
|
||||||
|
<Button
|
||||||
|
gap={"2.5"}
|
||||||
|
alignItems={"center"}
|
||||||
|
onClick={() => setShowAdvanced((prev) => !prev)}
|
||||||
|
>
|
||||||
|
<Text textColor={"jambonz.500"}>
|
||||||
|
{" "}
|
||||||
|
{showAdvanced ? "Hide" : "Show"} Advanced Settings
|
||||||
|
</Text>
|
||||||
|
<FontAwesomeIcon color={colors.jambonz} icon={faShuffle} />
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
{isAdvancedMode && (
|
||||||
|
<HStack w="full" mt={2} mb={2}>
|
||||||
|
<Box
|
||||||
|
as={FontAwesomeIcon}
|
||||||
|
icon={isCredentialOk ? faCheckCircle : faCircleXmark}
|
||||||
|
color={isCredentialOk ? "green.500" : "red.500"}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
fontSize="14px"
|
||||||
|
color={isCredentialOk ? "green.500" : "red.500"}
|
||||||
|
>
|
||||||
|
Credential is {isCredentialOk ? "valid" : "invalid"}
|
||||||
|
</Text>
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<HStack
|
||||||
|
w="full"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent={"space-between"}
|
||||||
|
mt={2}
|
||||||
|
>
|
||||||
|
<HStack>
|
||||||
|
<Button colorScheme="jambonz" type="submit" w="full">
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
colorScheme="jambonz"
|
||||||
|
type="reset"
|
||||||
|
w="full"
|
||||||
|
onClick={resetSetting}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
<HStack
|
||||||
|
_hover={{
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formData && (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
onClick={() => handleDeleteSetting(formData.id)}
|
||||||
|
icon={faTrashCan}
|
||||||
|
color={colors.jambonz}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
</HStack>
|
||||||
|
</VStack>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AccountForm;
|
||||||
@@ -33,16 +33,19 @@ export const AdvancedSettings = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const settings = getAdvancedSettings();
|
const settings = getAdvancedSettings();
|
||||||
if (settings.apiServer) {
|
const activeSettings = settings.find(
|
||||||
|
(el: { active: boolean }) => el.active
|
||||||
|
);
|
||||||
|
if (activeSettings?.decoded.apiServer) {
|
||||||
setIsAdvancedMode(true);
|
setIsAdvancedMode(true);
|
||||||
checkCredential();
|
checkCredential();
|
||||||
setApiServer(settings.apiServer);
|
setApiServer(activeSettings?.decoded.apiServer);
|
||||||
}
|
}
|
||||||
if (settings.apiKey) {
|
if (activeSettings?.decoded.apiKey) {
|
||||||
setApiKey(settings.apiKey);
|
setApiKey(activeSettings?.decoded.apiKey);
|
||||||
}
|
}
|
||||||
if (settings.accountSid) {
|
if (activeSettings?.decoded.accountSid) {
|
||||||
setAccountSid(settings.accountSid);
|
setAccountSid(activeSettings?.decoded.accountSid);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import {
|
import {
|
||||||
Box,
|
|
||||||
Button,
|
Button,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
HStack,
|
HStack,
|
||||||
Image,
|
Image,
|
||||||
Input,
|
Input,
|
||||||
Spacer,
|
|
||||||
Text,
|
Text,
|
||||||
VStack,
|
VStack,
|
||||||
useToast,
|
useToast,
|
||||||
@@ -59,20 +57,22 @@ export const BasicSettings = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
if (settings.sipDomain) {
|
const activeSettings = settings.find((el) => el.active);
|
||||||
setSipDomain(settings.sipDomain);
|
|
||||||
|
if (activeSettings?.decoded.sipDomain) {
|
||||||
|
setSipDomain(activeSettings?.decoded.sipDomain);
|
||||||
}
|
}
|
||||||
if (settings.sipServerAddress) {
|
if (activeSettings?.decoded.sipServerAddress) {
|
||||||
setSipServerAddress(settings.sipServerAddress);
|
setSipServerAddress(activeSettings?.decoded.sipServerAddress);
|
||||||
}
|
}
|
||||||
if (settings.sipUsername) {
|
if (activeSettings?.decoded.sipUsername) {
|
||||||
setSipUsername(settings.sipUsername);
|
setSipUsername(activeSettings?.decoded.sipUsername);
|
||||||
}
|
}
|
||||||
if (settings.sipPassword) {
|
if (activeSettings?.decoded.sipPassword) {
|
||||||
setSipPassword(settings.sipPassword);
|
setSipPassword(activeSettings?.decoded.sipPassword);
|
||||||
}
|
}
|
||||||
if (settings.sipDisplayName) {
|
if (activeSettings?.decoded.sipDisplayName) {
|
||||||
setSipDisplayName(settings.sipDisplayName);
|
setSipDisplayName(activeSettings?.decoded.sipDisplayName);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,26 +1,82 @@
|
|||||||
import { Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
|
import { Box, Button, Center, Text } from "@chakra-ui/react";
|
||||||
import React from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { DEFAULT_COLOR_SCHEME } from "src/common/constants";
|
import { getSettings } from "src/storage";
|
||||||
import BasicSettings from "./basic";
|
import { IAppSettings } from "src/common/types";
|
||||||
import AdvancedSettings from "./advanced";
|
import AccountForm from "./accountForm";
|
||||||
|
import AnimateOnShow from "src/components/animate";
|
||||||
|
import { AccordionList } from "src/window/settings/accordionList";
|
||||||
|
|
||||||
|
const MAX_NUM_OF_ACCOUNTS = 5;
|
||||||
|
|
||||||
export const Settings = () => {
|
export const Settings = () => {
|
||||||
return (
|
const [showForm, setShowForm] = useState(false);
|
||||||
<Tabs isFitted colorScheme={DEFAULT_COLOR_SCHEME}>
|
const [allSettings, setAllSettings] = useState<IAppSettings[]>([]);
|
||||||
<TabList mb="1em" gap={1}>
|
|
||||||
<Tab>Basic</Tab>
|
|
||||||
<Tab>Advanced</Tab>
|
|
||||||
</TabList>
|
|
||||||
|
|
||||||
<TabPanels mt={1}>
|
useEffect(
|
||||||
<TabPanel p={0}>
|
function () {
|
||||||
<BasicSettings />
|
loadSettings();
|
||||||
</TabPanel>
|
},
|
||||||
<TabPanel p={0}>
|
[showForm]
|
||||||
<AdvancedSettings />
|
);
|
||||||
</TabPanel>
|
|
||||||
</TabPanels>
|
function handleOpenForm() {
|
||||||
</Tabs>
|
setShowForm(true);
|
||||||
|
}
|
||||||
|
function handleCloseForm() {
|
||||||
|
setShowForm(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadSettings = function () {
|
||||||
|
setAllSettings(getSettings());
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Box>
|
||||||
|
<AccordionList allSettings={allSettings} reload={loadSettings} />
|
||||||
|
</Box>
|
||||||
|
{!showForm && (
|
||||||
|
<Button
|
||||||
|
marginY={"2.5"}
|
||||||
|
colorScheme="jambonz"
|
||||||
|
w="full"
|
||||||
|
onClick={handleOpenForm}
|
||||||
|
isDisabled={allSettings.length >= MAX_NUM_OF_ACCOUNTS}
|
||||||
|
>
|
||||||
|
Add Account
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Center marginBottom={"2.5"} flexDirection={"column"}>
|
||||||
|
<Text>
|
||||||
|
{allSettings.length} of {MAX_NUM_OF_ACCOUNTS}{" "}
|
||||||
|
</Text>
|
||||||
|
{allSettings.length >= MAX_NUM_OF_ACCOUNTS && (
|
||||||
|
<Text>Limit has been reached</Text>
|
||||||
|
)}
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
{showForm && (
|
||||||
|
<AnimateOnShow>
|
||||||
|
<AccountForm closeForm={handleCloseForm} />
|
||||||
|
</AnimateOnShow>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* <Tabs isFitted colorScheme={DEFAULT_COLOR_SCHEME}>
|
||||||
|
<TabList mb="1em" gap={1}>
|
||||||
|
<Tab>Basic</Tab>
|
||||||
|
<Tab>Advanced</Tab>
|
||||||
|
</TabList>
|
||||||
|
|
||||||
|
<TabPanels mt={1}>
|
||||||
|
<TabPanel p={0}>
|
||||||
|
<BasicSettings />
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel p={0}>
|
||||||
|
<AdvancedSettings />
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs> */}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { HStack, Text, VStack } from "@chakra-ui/react";
|
||||||
|
import React from "react";
|
||||||
|
import { IAppSettings } from "src/common/types";
|
||||||
|
|
||||||
|
function SettingItem({ data }: { data: IAppSettings }) {
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
w={"full"}
|
||||||
|
display={"flex"}
|
||||||
|
marginY={"1.5"}
|
||||||
|
border={"1px"}
|
||||||
|
borderColor={"gray.200"}
|
||||||
|
justifyContent={"start"}
|
||||||
|
borderRadius={"2xl"}
|
||||||
|
padding={"2.5"}
|
||||||
|
>
|
||||||
|
<VStack gap={"0"} alignItems={"start"}>
|
||||||
|
<Text fontWeight={"bold"}>
|
||||||
|
{data.decoded.sipDisplayName || data.decoded.sipUsername}
|
||||||
|
</Text>
|
||||||
|
<Text>{`${data.decoded.sipUsername}@${data.decoded.sipDomain}`}</Text>
|
||||||
|
</VStack>
|
||||||
|
</HStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingItem;
|
||||||
Reference in New Issue
Block a user