mirror of
https://github.com/jambonz/chrome-extension-dialer.git
synced 2025-12-19 04:47:45 +00:00
Merge pull request #34 from mister-abdurahman/main
Allow adding multiple accounts
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -24,4 +24,4 @@
|
|||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
BIN
public/fonts/SourceSans3-Regular.ttf
Normal file
BIN
public/fonts/SourceSans3-Regular.ttf
Normal file
Binary file not shown.
@@ -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,47 @@ 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`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// validate user advanced credential
|
||||||
|
export const getAdvancedValidation = (
|
||||||
|
apiServer: string,
|
||||||
|
accountSid: string
|
||||||
|
) => {
|
||||||
|
return getFetch<Application[]>(
|
||||||
|
`${apiServer}/Accounts/${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 +178,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;
|
||||||
|
|||||||
28
src/components/animate/index.tsx
Normal file
28
src/components/animate/index.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React, { ReactNode } from "react";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
|
function AnimateOnShow({
|
||||||
|
children,
|
||||||
|
initial = -20,
|
||||||
|
exit = -20,
|
||||||
|
duration = 0.5,
|
||||||
|
}: {
|
||||||
|
children: ReactNode;
|
||||||
|
initial?: number;
|
||||||
|
exit?: number;
|
||||||
|
duration?: number;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
initial={{ opacity: 0, y: initial, height: 0 }}
|
||||||
|
animate={{ opacity: 1, y: 0, height: "auto" }}
|
||||||
|
exit={{ opacity: 0, y: exit, height: 0 }}
|
||||||
|
transition={{ duration: duration }}
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
>
|
||||||
|
{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,9 +17,9 @@ 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 ? "blue.600" : "grey.500"}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!isDisabled) {
|
if (!isDisabled) {
|
||||||
@@ -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%)"
|
||||||
|
|||||||
8
src/imgs/icons/Switch.svg
Normal file
8
src/imgs/icons/Switch.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="16" viewBox="0 0 15 16" fill="none">
|
||||||
|
<path d="M10.5994 6.59375C10.3405 6.59375 10.1306 6.38388 10.1306 6.125L10.1306 1.75C10.1306 1.49112 10.3405 1.28125 10.5994 1.28125C10.8583 1.28125 11.0681 1.49112 11.0681 1.75L11.0681 6.125C11.0681 6.38388 10.8583 6.59375 10.5994 6.59375Z" fill="#BB225B"/>
|
||||||
|
<path d="M4.375 8.3125C3.33947 8.3125 2.5 7.47303 2.5 6.4375C2.5 5.40197 3.33947 4.5625 4.375 4.5625C5.41053 4.5625 6.25 5.40197 6.25 6.4375C6.25 7.47303 5.41053 8.3125 4.375 8.3125Z" fill="#BB225B"/>
|
||||||
|
<path d="M10.625 7.6875C9.58947 7.6875 8.75 8.52697 8.75 9.5625C8.75 10.598 9.58947 11.4375 10.625 11.4375C11.6605 11.4375 12.5 10.598 12.5 9.5625C12.5 8.52697 11.6605 7.6875 10.625 7.6875Z" fill="#BB225B"/>
|
||||||
|
<path d="M3.88063 9.875C3.88063 9.61612 4.0905 9.40625 4.34938 9.40625C4.60826 9.40625 4.81813 9.61612 4.81813 9.875V14.25C4.81813 14.5089 4.60826 14.7188 4.34938 14.7188C4.0905 14.7188 3.88063 14.5089 3.88063 14.25V9.875Z" fill="#BB225B"/>
|
||||||
|
<path d="M10.5994 14.7188C10.3405 14.7188 10.1306 14.5089 10.1306 14.25V13C10.1306 12.7411 10.3405 12.5312 10.5994 12.5312C10.8583 12.5312 11.0681 12.7411 11.0681 13V14.25C11.0681 14.5089 10.8583 14.7188 10.5994 14.7188Z" fill="#BB225B"/>
|
||||||
|
<path d="M3.88063 1.75C3.88063 1.49112 4.0905 1.28125 4.34938 1.28125C4.60826 1.28125 4.81813 1.49112 4.81813 1.75V3C4.81813 3.25888 4.60826 3.46875 4.34938 3.46875C4.0905 3.46875 3.88063 3.25888 3.88063 3V1.75Z" fill="#BB225B"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
4
src/imgs/icons/Trash.svg
Normal file
4
src/imgs/icons/Trash.svg
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||||
|
<path d="M3 6.38597C3 5.90152 3.34538 5.50879 3.77143 5.50879L6.43567 5.50832C6.96502 5.49306 7.43202 5.11033 7.61214 4.54412C7.61688 4.52923 7.62232 4.51087 7.64185 4.44424L7.75665 4.05256C7.8269 3.81241 7.8881 3.60318 7.97375 3.41617C8.31209 2.67736 8.93808 2.16432 9.66147 2.03297C9.84457 1.99972 10.0385 1.99986 10.2611 2.00002H13.7391C13.9617 1.99986 14.1556 1.99972 14.3387 2.03297C15.0621 2.16432 15.6881 2.67736 16.0264 3.41617C16.1121 3.60318 16.1733 3.81241 16.2435 4.05256L16.3583 4.44424C16.3778 4.51087 16.3833 4.52923 16.388 4.54412C16.5682 5.11033 17.1278 5.49353 17.6571 5.50879H20.2286C20.6546 5.50879 21 5.90152 21 6.38597C21 6.87043 20.6546 7.26316 20.2286 7.26316H3.77143C3.34538 7.26316 3 6.87043 3 6.38597Z" fill="#BB225B"/>
|
||||||
|
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5956 22.0001H12.4044C15.1871 22.0001 16.5785 22.0001 17.4831 21.1142C18.3878 20.2283 18.4803 18.7751 18.6654 15.8686L18.9321 11.6807C19.0326 10.1037 19.0828 9.31524 18.6289 8.81558C18.1751 8.31592 17.4087 8.31592 15.876 8.31592H8.12404C6.59127 8.31592 5.82488 8.31592 5.37105 8.81558C4.91722 9.31524 4.96744 10.1037 5.06788 11.6807L5.33459 15.8686C5.5197 18.7751 5.61225 20.2283 6.51689 21.1142C7.42153 22.0001 8.81289 22.0001 11.5956 22.0001ZM10.2463 12.1886C10.2051 11.7548 9.83753 11.4382 9.42537 11.4816C9.01321 11.525 8.71251 11.9119 8.75372 12.3457L9.25372 17.6089C9.29494 18.0427 9.66247 18.3593 10.0746 18.3159C10.4868 18.2725 10.7875 17.8856 10.7463 17.4518L10.2463 12.1886ZM14.5746 11.4816C14.9868 11.525 15.2875 11.9119 15.2463 12.3457L14.7463 17.6089C14.7051 18.0427 14.3375 18.3593 13.9254 18.3159C13.5132 18.2725 13.2125 17.8856 13.2537 17.4518L13.7537 12.1886C13.7949 11.7548 14.1625 11.4382 14.5746 11.4816Z" fill="#BB225B"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.8 KiB |
3
src/imgs/icons/invalid.svg
Normal file
3
src/imgs/icons/invalid.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="18" height="15" viewBox="0 0 18 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M10.5 13.75C12.1576 13.75 13.7473 13.0915 14.9194 11.9194C16.0915 10.7473 16.75 9.1576 16.75 7.5C16.75 5.8424 16.0915 4.25269 14.9194 3.08058C13.7473 1.90848 12.1576 1.25 10.5 1.25C8.8424 1.25 7.25269 1.90848 6.08058 3.08058C4.90848 4.25269 4.25 5.8424 4.25 7.5C4.25 9.1576 4.90848 10.7473 6.08058 11.9194C7.25269 13.0915 8.8424 13.75 10.5 13.75ZM9.5625 10.3125C9.5625 10.0639 9.66127 9.8254 9.83709 9.64959C10.0129 9.47377 10.2514 9.375 10.5 9.375C10.7486 9.375 10.9871 9.47377 11.1629 9.64959C11.3387 9.8254 11.4375 10.0639 11.4375 10.3125C11.4375 10.5611 11.3387 10.7996 11.1629 10.9754C10.9871 11.1512 10.7486 11.25 10.5 11.25C10.2514 11.25 10.0129 11.1512 9.83709 10.9754C9.66127 10.7996 9.5625 10.5611 9.5625 10.3125ZM9.885 4.2625C9.91109 4.1184 9.98696 3.98803 10.0994 3.89416C10.2118 3.80028 10.3536 3.74886 10.5 3.74886C10.6464 3.74886 10.7882 3.80028 10.9006 3.89416C11.013 3.98803 11.0889 4.1184 11.115 4.2625L11.125 4.375V7.5L11.115 7.6125C11.0889 7.7566 11.013 7.88697 10.9006 7.98084C10.7882 8.07472 10.6464 8.12614 10.5 8.12614C10.3536 8.12614 10.2118 8.07472 10.0994 7.98084C9.98696 7.88697 9.91109 7.7566 9.885 7.6125L9.875 7.5V4.375L9.885 4.2625Z" fill="#BD4782"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -5,6 +5,7 @@ import mainTheme from "./theme";
|
|||||||
import WindowApp from "./window/app";
|
import WindowApp from "./window/app";
|
||||||
|
|
||||||
// This file is being used only for dev.
|
// This file is being used only for dev.
|
||||||
|
|
||||||
const root = document.createElement("div");
|
const root = document.createElement("div");
|
||||||
root.className = "container";
|
root.className = "container";
|
||||||
document.body.appendChild(root);
|
document.body.appendChild(root);
|
||||||
|
|||||||
@@ -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,115 @@ 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);
|
const str = localStorage.getItem(SETTINGS_KEY);
|
||||||
|
|
||||||
|
const parsed = str ? JSON.parse(str) : [];
|
||||||
|
const newItem = {
|
||||||
|
id: parsed.length + 1,
|
||||||
|
encoded,
|
||||||
|
active: parsed.length === 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
localStorage.setItem(SETTINGS_KEY, JSON.stringify([...parsed, newItem]));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSettings = (): 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);
|
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;
|
|
||||||
|
// 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));
|
||||||
}
|
}
|
||||||
return {} as AppSettings;
|
};
|
||||||
|
export const setActiveSettings = (id: number) => {
|
||||||
|
const str = localStorage.getItem(SETTINGS_KEY);
|
||||||
|
|
||||||
|
if (str) {
|
||||||
|
const parsed = JSON.parse(str);
|
||||||
|
|
||||||
|
const newData = parsed.map((el: saveSettingFormat) => ({
|
||||||
|
...el,
|
||||||
|
active: el.id === id,
|
||||||
|
}));
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return [] as IAppSettings[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActiveSettings = (): IAppSettings => {
|
||||||
|
const str = localStorage.getItem(SETTINGS_KEY);
|
||||||
|
if (str) {
|
||||||
|
const parsed: { active: boolean; encoded: string; id: number }[] =
|
||||||
|
JSON.parse(str);
|
||||||
|
|
||||||
|
const activeSettings = parsed.find((el) => el.active);
|
||||||
|
const decoded = {
|
||||||
|
active: activeSettings?.active,
|
||||||
|
decoded: JSON.parse(
|
||||||
|
Buffer.from(activeSettings?.encoded!, "base64").toString("utf-8")
|
||||||
|
),
|
||||||
|
id: activeSettings?.id,
|
||||||
|
};
|
||||||
|
return decoded as IAppSettings;
|
||||||
|
}
|
||||||
|
return {} as IAppSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Advanced settings
|
// Advanced settings
|
||||||
@@ -50,16 +146,52 @@ export const saveAddvancedSettings = (settings: AdvancedAppSettings) => {
|
|||||||
"base64"
|
"base64"
|
||||||
);
|
);
|
||||||
|
|
||||||
localStorage.setItem(ADVANCED_SETTINGS_KET, encoded);
|
const str = localStorage.getItem(ADVANCED_SETTINGS_KET);
|
||||||
|
const data = str ? JSON.parse(str) : [];
|
||||||
|
|
||||||
|
if (data.some((el: { encoded: string }) => el.encoded === encoded)) return;
|
||||||
|
|
||||||
|
data.push({ encoded, active: data.length === 0, id: data.length + 1 });
|
||||||
|
localStorage.setItem(ADVANCED_SETTINGS_KET, JSON.stringify(data));
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
return {} as AdvancedAppSettings;
|
return [] as IAdvancedAppSettings[];
|
||||||
|
};
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
return {} as IAdvancedAppSettings;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Call History
|
// Call History
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
|
@font-face {
|
||||||
|
font-family: "Source Sans";
|
||||||
|
src: url("../public/fonts/SourceSans3-Regular.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
height: 480px;
|
height: 480px;
|
||||||
font-family: Arial, sans-serif;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
import { extendTheme } from "@chakra-ui/react";
|
import { extendTheme } from "@chakra-ui/react";
|
||||||
|
|
||||||
const mainTheme = extendTheme({
|
const mainTheme = extendTheme({
|
||||||
|
fonts: {
|
||||||
|
heading: `'Source Sans 3', Arial, sans-serif`,
|
||||||
|
body: `'Source Sans 3', Arial, sans-serif`,
|
||||||
|
},
|
||||||
colors: {
|
colors: {
|
||||||
jambonz: {
|
jambonz: {
|
||||||
|
0: "#EDDEE3",
|
||||||
50: "#ffe1f1",
|
50: "#ffe1f1",
|
||||||
100: "#ffb3c6",
|
100: "#ffb3c6",
|
||||||
200: "#fc839d",
|
200: "#fc839d",
|
||||||
300: "#fa5575",
|
300: "#fa5575",
|
||||||
400: "#f8274e",
|
400: "#f8274e",
|
||||||
500: "#DA1C5C",
|
450: "#BD4782",
|
||||||
|
500: "#BB225B",
|
||||||
|
550: "#DA1C5C",
|
||||||
600: "#c60921",
|
600: "#c60921",
|
||||||
700: "#99081a",
|
700: "#99081a",
|
||||||
800: "#6c0714",
|
800: "#6c0714",
|
||||||
@@ -16,6 +23,7 @@ const mainTheme = extendTheme({
|
|||||||
},
|
},
|
||||||
grey: {
|
grey: {
|
||||||
50: "#FFFFFF",
|
50: "#FFFFFF",
|
||||||
|
75: "#F9F9F9",
|
||||||
100: "#F5F5F5",
|
100: "#F5F5F5",
|
||||||
200: "#ECECEC",
|
200: "#ECECEC",
|
||||||
300: "#E3E3E3",
|
300: "#E3E3E3",
|
||||||
@@ -26,6 +34,12 @@ const mainTheme = extendTheme({
|
|||||||
800: "#6D6D6D",
|
800: "#6D6D6D",
|
||||||
900: "#434343",
|
900: "#434343",
|
||||||
},
|
},
|
||||||
|
greenish: {
|
||||||
|
500: "#158477",
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
600: "#4492FF", //for toggle icon
|
||||||
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
FormLabel: {
|
FormLabel: {
|
||||||
@@ -47,4 +61,9 @@ const mainTheme = extendTheme({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const colors = {
|
||||||
|
//to use outside of chakra component
|
||||||
|
jambonz: "#DA1C5C",
|
||||||
|
};
|
||||||
|
|
||||||
export default mainTheme;
|
export default mainTheme;
|
||||||
|
|||||||
15
src/utils/formatDate.ts
Normal file
15
src/utils/formatDate.ts
Normal file
@@ -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}`;
|
||||||
|
}
|
||||||
@@ -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, useRef, 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,14 +27,50 @@ 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 [isSwitchingUserStatus, setIsSwitchingUserStatus] = useState(false);
|
||||||
|
const [isOnline, setIsOnline] = useState(false);
|
||||||
|
const phoneSipAschildRef = useRef<{
|
||||||
|
updateGoOffline: (x: string) => void;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
const handleGoOffline = (s: SipClientStatus) => {
|
||||||
|
if (s === status) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (phoneSipAschildRef.current) {
|
||||||
|
if (s === "unregistered") {
|
||||||
|
phoneSipAschildRef.current.updateGoOffline("stop");
|
||||||
|
} else {
|
||||||
|
phoneSipAschildRef.current.updateGoOffline("start");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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",
|
||||||
content: (
|
content: (
|
||||||
<Phone
|
<Phone
|
||||||
|
ref={phoneSipAschildRef}
|
||||||
sipUsername={sipUsername}
|
sipUsername={sipUsername}
|
||||||
sipPassword={sipPassword}
|
sipPassword={sipPassword}
|
||||||
sipDomain={sipDomain}
|
sipDomain={sipDomain}
|
||||||
@@ -50,7 +78,12 @@ 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}
|
||||||
|
setIsSwitchingUserStatus={setIsSwitchingUserStatus}
|
||||||
|
setIsOnline={setIsOnline}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -83,16 +116,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 +145,21 @@ 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}
|
||||||
|
isSwitchingUserStatus={isSwitchingUserStatus}
|
||||||
|
setIsSwitchingUserStatus={setIsSwitchingUserStatus}
|
||||||
|
isOnline={isOnline}
|
||||||
|
setIsOnline={setIsOnline}
|
||||||
|
onHandleGoOffline={handleGoOffline}
|
||||||
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
87
src/window/footer/footer.tsx
Normal file
87
src/window/footer/footer.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { HStack, Image, Text } from "@chakra-ui/react";
|
||||||
|
import jambonz from "src/imgs/jambonz.svg";
|
||||||
|
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
|
import { SipClientStatus } from "src/common/types";
|
||||||
|
import JambonzSwitch from "src/components/switch";
|
||||||
|
import "./styles.scss";
|
||||||
|
|
||||||
|
function Footer({
|
||||||
|
status,
|
||||||
|
setStatus,
|
||||||
|
sipServerAddress,
|
||||||
|
sipUsername,
|
||||||
|
sipDomain,
|
||||||
|
sipPassword,
|
||||||
|
sipDisplayName,
|
||||||
|
isSwitchingUserStatus,
|
||||||
|
setIsSwitchingUserStatus,
|
||||||
|
isOnline,
|
||||||
|
setIsOnline,
|
||||||
|
onHandleGoOffline,
|
||||||
|
}: {
|
||||||
|
status: string;
|
||||||
|
setStatus: Dispatch<SetStateAction<SipClientStatus>>;
|
||||||
|
sipServerAddress: string;
|
||||||
|
sipUsername: string;
|
||||||
|
sipDomain: string;
|
||||||
|
sipPassword: string;
|
||||||
|
sipDisplayName: string;
|
||||||
|
isSwitchingUserStatus: boolean;
|
||||||
|
setIsSwitchingUserStatus: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
isOnline: boolean;
|
||||||
|
setIsOnline: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
|
onHandleGoOffline: (s: SipClientStatus) => void;
|
||||||
|
}) {
|
||||||
|
const [isConfigured, setIsConfigured] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status === "registered" || status === "disconnected") {
|
||||||
|
setIsSwitchingUserStatus(false);
|
||||||
|
setIsOnline(status === "registered");
|
||||||
|
}
|
||||||
|
}, [status, setIsSwitchingUserStatus, setIsOnline]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (sipDomain && sipUsername && sipPassword && sipServerAddress) {
|
||||||
|
setIsConfigured(true);
|
||||||
|
} else {
|
||||||
|
setIsConfigured(false);
|
||||||
|
}
|
||||||
|
}, [sipDomain, sipUsername, sipPassword, sipServerAddress]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
padding={"15px"}
|
||||||
|
justifyContent={"space-between"}
|
||||||
|
alignItems={"center"}
|
||||||
|
bg={"grey.75"}
|
||||||
|
>
|
||||||
|
{isConfigured ? (
|
||||||
|
<HStack alignItems={"center"} flexWrap={"nowrap"} className="xs">
|
||||||
|
<JambonzSwitch
|
||||||
|
isDisabled={isSwitchingUserStatus}
|
||||||
|
checked={[isOnline, setIsOnline]}
|
||||||
|
onChange={(v) => {
|
||||||
|
setIsSwitchingUserStatus(true);
|
||||||
|
onHandleGoOffline(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;
|
||||||
5
src/window/footer/styles.scss
Normal file
5
src/window/footer/styles.scss
Normal file
@@ -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
|
||||||
);
|
);
|
||||||
|
|||||||
52
src/window/phone/availableAccounts.tsx
Normal file
52
src/window/phone/availableAccounts.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import { Box, HStack, Text, VStack } from "@chakra-ui/react";
|
||||||
|
import { faCheck } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
import React, { RefObject } from "react";
|
||||||
|
import { IAppSettings } from "src/common/types";
|
||||||
|
|
||||||
|
function AvailableAccounts({
|
||||||
|
allSettings,
|
||||||
|
onSetActive,
|
||||||
|
refData,
|
||||||
|
}: {
|
||||||
|
allSettings: IAppSettings[];
|
||||||
|
onSetActive: (x: number) => void;
|
||||||
|
refData: RefObject<HTMLDivElement>;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<VStack
|
||||||
|
zIndex={"modal"}
|
||||||
|
ref={refData}
|
||||||
|
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}
|
||||||
|
display={"flex"}
|
||||||
|
justifyContent={"start"}
|
||||||
|
_hover={{
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
onClick={() => onSetActive(el.id)}
|
||||||
|
>
|
||||||
|
<Box w={"12px"}>
|
||||||
|
{el.active ? <FontAwesomeIcon icon={faCheck} /> : null}
|
||||||
|
</Box>
|
||||||
|
<Text>{el.decoded.sipDisplayName || el.decoded.sipUsername}</Text>
|
||||||
|
|
||||||
|
<Text>({`${el.decoded.sipUsername}@${el.decoded.sipDomain}`})</Text>
|
||||||
|
</HStack>
|
||||||
|
))}
|
||||||
|
</VStack>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AvailableAccounts;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
81
src/window/settings/accordionList.tsx
Normal file
81
src/window/settings/accordionList.tsx
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
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,
|
||||||
|
handleOpenFormInAccordion,
|
||||||
|
handleCloseFormInAccordion,
|
||||||
|
isNewFormOpen,
|
||||||
|
handleCloseNewForm,
|
||||||
|
}: {
|
||||||
|
allSettings: IAppSettings[];
|
||||||
|
reload: () => void;
|
||||||
|
handleOpenFormInAccordion: () => void;
|
||||||
|
handleCloseFormInAccordion: () => void;
|
||||||
|
isNewFormOpen: boolean;
|
||||||
|
handleCloseNewForm: () => void;
|
||||||
|
}) {
|
||||||
|
const { isOpen, onOpen, onClose } = useDisclosure();
|
||||||
|
const [openAcc, setOpenAcc] = useState(0);
|
||||||
|
|
||||||
|
const closeFormInAccordion = function () {
|
||||||
|
onClose();
|
||||||
|
handleCloseFormInAccordion();
|
||||||
|
reload();
|
||||||
|
};
|
||||||
|
|
||||||
|
function handleToggleAcc(accIndex: number) {
|
||||||
|
if (isNewFormOpen) handleCloseNewForm(); //closes new form if open
|
||||||
|
handleOpenFormInAccordion();
|
||||||
|
setOpenAcc(accIndex);
|
||||||
|
onOpen();
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Accordion
|
||||||
|
index={isOpen ? [openAcc] : []}
|
||||||
|
allowToggle
|
||||||
|
sx={{
|
||||||
|
"& > *:last-child": {
|
||||||
|
marginBottom: "7px",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{allSettings.map((data, index) => (
|
||||||
|
<AccordionItem borderColor={"white"} key={index}>
|
||||||
|
<AccordionButton
|
||||||
|
padding={0}
|
||||||
|
marginBottom={-1}
|
||||||
|
_hover={{
|
||||||
|
backgroundColor: "#fff",
|
||||||
|
cursor: "default",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isOpen && index === openAcc ? null : (
|
||||||
|
<SettingItem
|
||||||
|
onToggleAcc={() => handleToggleAcc(index)}
|
||||||
|
data={data}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</AccordionButton>
|
||||||
|
<AccordionPanel pb={4} px={0}>
|
||||||
|
<AccountForm
|
||||||
|
formData={data}
|
||||||
|
handleClose={closeFormInAccordion}
|
||||||
|
inputUniqueId={`${data.id}`}
|
||||||
|
/>
|
||||||
|
</AccordionPanel>
|
||||||
|
</AccordionItem>
|
||||||
|
))}
|
||||||
|
</Accordion>
|
||||||
|
);
|
||||||
|
}
|
||||||
352
src/window/settings/accountForm.tsx
Normal file
352
src/window/settings/accountForm.tsx
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Center,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
HStack,
|
||||||
|
Image,
|
||||||
|
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 } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { normalizeUrl } from "src/utils";
|
||||||
|
import { getAdvancedValidation } from "src/api";
|
||||||
|
import Switch from "src/imgs/icons/Switch.svg";
|
||||||
|
import Trash from "src/imgs/icons/Trash.svg";
|
||||||
|
import invalid from "src/imgs/icons/invalid.svg";
|
||||||
|
import AnimateOnShow from "src/components/animate";
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (
|
||||||
|
formData.decoded.accountSid ||
|
||||||
|
formData.decoded.apiKey ||
|
||||||
|
formData.decoded.apiServer
|
||||||
|
) {
|
||||||
|
setIsAdvancedMode(true);
|
||||||
|
checkCredential(
|
||||||
|
formData.decoded.apiServer || "",
|
||||||
|
formData.decoded.accountSid || ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[formData, handleClose]
|
||||||
|
);
|
||||||
|
|
||||||
|
const checkCredential = (apiServer: string, accountSid: string) => {
|
||||||
|
getAdvancedValidation(apiServer, accountSid)
|
||||||
|
.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(settings.apiServer || "", settings.accountSid || "");
|
||||||
|
}
|
||||||
|
|
||||||
|
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} className="formStyle">
|
||||||
|
<VStack
|
||||||
|
spacing={2}
|
||||||
|
w="full"
|
||||||
|
p={0}
|
||||||
|
border={"1px"}
|
||||||
|
borderColor={"gray.200"}
|
||||||
|
borderRadius={"6px"}
|
||||||
|
paddingY={"15px"}
|
||||||
|
paddingRight={"15px"}
|
||||||
|
paddingLeft={"10px"}
|
||||||
|
>
|
||||||
|
<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 && (
|
||||||
|
<AnimateOnShow>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
{isAdvancedMode && (
|
||||||
|
<HStack
|
||||||
|
w="full"
|
||||||
|
mt={2}
|
||||||
|
mb={2}
|
||||||
|
gap={"1.5"}
|
||||||
|
alignItems={"center"}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
fontSize="12px"
|
||||||
|
fontWeight={600}
|
||||||
|
color={isCredentialOk ? "greenish.500" : "jambonz.450"}
|
||||||
|
>
|
||||||
|
{isCredentialOk
|
||||||
|
? "Credentials are valid"
|
||||||
|
: "We cant verify your credentials"}
|
||||||
|
</Text>
|
||||||
|
{isCredentialOk ? (
|
||||||
|
<Box
|
||||||
|
as={FontAwesomeIcon}
|
||||||
|
icon={faCheckCircle}
|
||||||
|
color={"greenish.500"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Box w={"22px"} h={"22px"} p={0} marginLeft={-1}>
|
||||||
|
<Image
|
||||||
|
width={"full"}
|
||||||
|
height={"full"}
|
||||||
|
p={0}
|
||||||
|
src={invalid}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</HStack>
|
||||||
|
)}
|
||||||
|
</VStack>
|
||||||
|
</AnimateOnShow>
|
||||||
|
)}
|
||||||
|
<Center flexDirection={"column"} gap={"2.5"}>
|
||||||
|
<Button
|
||||||
|
variant={"ghost"}
|
||||||
|
gap={"2.5"}
|
||||||
|
alignItems={"center"}
|
||||||
|
onClick={() => setShowAdvanced((prev) => !prev)}
|
||||||
|
>
|
||||||
|
<Text textColor={"jambonz.500"}>
|
||||||
|
{" "}
|
||||||
|
{showAdvanced ? "Hide" : "Show"} Advanced Settings
|
||||||
|
</Text>
|
||||||
|
<Image width={"15px"} height={"15px"} src={Switch} />
|
||||||
|
</Button>
|
||||||
|
</Center>
|
||||||
|
</VStack>
|
||||||
|
|
||||||
|
<HStack
|
||||||
|
w="full"
|
||||||
|
alignItems="center"
|
||||||
|
justifyContent={"space-between"}
|
||||||
|
mt={2}
|
||||||
|
>
|
||||||
|
<HStack>
|
||||||
|
<Button
|
||||||
|
textColor={"jambonz.500"}
|
||||||
|
fontWeight={"semibold"}
|
||||||
|
borderRadius={"11px"}
|
||||||
|
bg="jambonz.0"
|
||||||
|
type="submit"
|
||||||
|
w="full"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={"ghost"}
|
||||||
|
colorScheme="jambonz"
|
||||||
|
type="reset"
|
||||||
|
fontWeight={"semibold"}
|
||||||
|
w="full"
|
||||||
|
onClick={resetSetting}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
<HStack
|
||||||
|
_hover={{
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formData && (
|
||||||
|
<Image
|
||||||
|
width={"24px"}
|
||||||
|
height={"24px"}
|
||||||
|
src={Trash}
|
||||||
|
onClick={() => handleDeleteSetting(formData.id)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</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,97 @@
|
|||||||
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 [showFormInAccordion, setShowFormInAccordion] = useState(false);
|
||||||
<TabList mb="1em" gap={1}>
|
const [allSettings, setAllSettings] = useState<IAppSettings[]>([]);
|
||||||
<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);
|
||||||
|
}
|
||||||
|
function handleOpenFormInAccordion() {
|
||||||
|
setShowFormInAccordion(true);
|
||||||
|
}
|
||||||
|
function handleCloseFormInAccordion() {
|
||||||
|
setShowFormInAccordion(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadSettings = function () {
|
||||||
|
setAllSettings(getSettings());
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnIsDisabled = allSettings.length >= MAX_NUM_OF_ACCOUNTS;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Box>
|
||||||
|
<AccordionList
|
||||||
|
handleOpenFormInAccordion={handleOpenFormInAccordion}
|
||||||
|
handleCloseFormInAccordion={handleCloseFormInAccordion}
|
||||||
|
allSettings={allSettings}
|
||||||
|
reload={loadSettings}
|
||||||
|
isNewFormOpen={showForm}
|
||||||
|
handleCloseNewForm={handleCloseForm}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
{!showForm && !showFormInAccordion && (
|
||||||
|
<Button
|
||||||
|
marginY={"3"}
|
||||||
|
colorScheme="jambonz"
|
||||||
|
w="full"
|
||||||
|
onClick={handleOpenForm}
|
||||||
|
isDisabled={btnIsDisabled}
|
||||||
|
>
|
||||||
|
Add Account
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showForm && (
|
||||||
|
<AnimateOnShow>
|
||||||
|
<AccountForm closeForm={handleCloseForm} />
|
||||||
|
</AnimateOnShow>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Center marginBottom={"2.5"} flexDirection={"column"}>
|
||||||
|
<Text>
|
||||||
|
{allSettings.length} of {MAX_NUM_OF_ACCOUNTS}{" "}
|
||||||
|
</Text>
|
||||||
|
{btnIsDisabled && <Text>Limit has been reached</Text>}
|
||||||
|
</Center>
|
||||||
|
|
||||||
|
{/* <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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
40
src/window/settings/settingItem.tsx
Normal file
40
src/window/settings/settingItem.tsx
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { HStack, Text, VStack } from "@chakra-ui/react";
|
||||||
|
import React from "react";
|
||||||
|
import { IAppSettings } from "src/common/types";
|
||||||
|
|
||||||
|
function SettingItem({
|
||||||
|
data,
|
||||||
|
onToggleAcc,
|
||||||
|
}: {
|
||||||
|
data: IAppSettings;
|
||||||
|
onToggleAcc: () => void;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<HStack
|
||||||
|
w={"full"}
|
||||||
|
display={"flex"}
|
||||||
|
marginY={"1.5"}
|
||||||
|
border={"1px"}
|
||||||
|
borderColor={"gray.200"}
|
||||||
|
justifyContent={"start"}
|
||||||
|
borderRadius={"6px"}
|
||||||
|
paddingTop={"7px"}
|
||||||
|
paddingBottom={"5px"}
|
||||||
|
paddingX={"10px"}
|
||||||
|
onClick={onToggleAcc}
|
||||||
|
_hover={{
|
||||||
|
backgroundColor: "gray.200",
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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