From d9286fb871406d9c79695dfb8c6f8516f3f6fd38 Mon Sep 17 00:00:00 2001 From: abdurahman Date: Sun, 25 Aug 2024 17:52:46 +0100 Subject: [PATCH 01/13] done with implementation --- .gitignore | 29 +-- src/api/index.ts | 30 +-- src/common/types.ts | 16 ++ src/components/animate/index.tsx | 27 ++ src/components/switch/index.tsx | 34 +-- src/storage/index.ts | 216 +++++++++++++++- src/theme.tsx | 5 + src/utils/formatDate.ts | 15 ++ src/window/app.tsx | 64 ++--- src/window/footer/footer.tsx | 193 +++++++++++++++ src/window/footer/styles.scss | 5 + src/window/history/call-history-item.tsx | 5 +- src/window/phone/availableAccounts.tsx | 47 ++++ src/window/phone/index.tsx | 137 ++++++----- src/window/phone/styles.scss | 8 + src/window/settings/accordionList.tsx | 50 ++++ src/window/settings/accountForm.tsx | 298 +++++++++++++++++++++++ src/window/settings/advanced.tsx | 15 +- src/window/settings/basic.tsx | 24 +- src/window/settings/index.tsx | 96 ++++++-- src/window/settings/settingItem.tsx | 27 ++ 21 files changed, 1143 insertions(+), 198 deletions(-) create mode 100644 src/components/animate/index.tsx create mode 100644 src/utils/formatDate.ts create mode 100644 src/window/footer/footer.tsx create mode 100644 src/window/footer/styles.scss create mode 100644 src/window/phone/availableAccounts.tsx create mode 100644 src/window/settings/accordionList.tsx create mode 100644 src/window/settings/accountForm.tsx create mode 100644 src/window/settings/settingItem.tsx diff --git a/.gitignore b/.gitignore index 8718b41..f06235c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,2 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# 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* +node_modules +dist diff --git a/src/api/index.ts b/src/api/index.ts index e87d391..45ca172 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -10,7 +10,7 @@ import { UpdateCall, } from "./types"; import { MSG_SOMETHING_WRONG } from "./constants"; -import { getAdvancedSettings } from "src/storage"; +import { getActiveSettings } from "src/storage"; import { EmptyData } from "src/common/types"; const fetchTransport = ( @@ -87,8 +87,8 @@ const fetchTransport = ( }; const getAuthHeaders = () => { - const advancedSettings = getAdvancedSettings(); - let token = advancedSettings.apiKey ?? null; + const advancedSettings = getActiveSettings(); + let token = advancedSettings?.decoded?.apiKey ?? null; return { "Content-Type": "application/json", @@ -130,37 +130,37 @@ export const deleteFetch = (url: string) => { // GET Devices Users export const getRegisteredUser = () => { - const advancedSettings = getAdvancedSettings(); + const advancedSettings = getActiveSettings(); return getFetch( - `${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/RegisteredSipUsers` + `${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/RegisteredSipUsers` ); }; export const getApplications = () => { - const advancedSettings = getAdvancedSettings(); + const advancedSettings = getActiveSettings(); return getFetch( - `${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Applications` + `${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/Applications` ); }; export const getQueues = () => { - const advancedSettings = getAdvancedSettings(); + const advancedSettings = getActiveSettings(); return getFetch( - `${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Queues` + `${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/Queues` ); }; export const getSelfRegisteredUser = (username: string) => { - const advancedSettings = getAdvancedSettings(); + const advancedSettings = getActiveSettings(); return getFetch( - `${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/RegisteredSipUsers/${username}` + `${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/RegisteredSipUsers/${username}` ); }; export const getConferences = () => { - const advancedSettings = getAdvancedSettings(); + const advancedSettings = getActiveSettings(); return getFetch( - `${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Conferences` + `${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/Conferences` ); }; @@ -168,9 +168,9 @@ export const updateConferenceParticipantAction = ( callSid: string, payload: ConferenceParticipantAction ) => { - const advancedSettings = getAdvancedSettings(); + const advancedSettings = getActiveSettings(); return putFetch( - `${advancedSettings.apiServer}/Accounts/${advancedSettings.accountSid}/Calls/${callSid}`, + `${advancedSettings?.decoded?.apiServer}/Accounts/${advancedSettings?.decoded?.accountSid}/Calls/${callSid}`, { conferenceParticipantAction: payload, } diff --git a/src/common/types.ts b/src/common/types.ts index 2d7be05..a0635e4 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -57,6 +57,16 @@ export interface AppSettings { sipUsername: string; sipPassword: string; sipDisplayName: string; + + accountSid?: string; + apiKey?: string; + apiServer?: string; +} + +export interface IAppSettings { + active: boolean; + decoded: AppSettings; + id: number; } export interface ConferenceSettings { @@ -71,6 +81,12 @@ export interface AdvancedAppSettings { apiServer: string; } +export interface IAdvancedAppSettings { + active: boolean; + decoded: AdvancedAppSettings; + id: number; +} + export interface CallHistory { callSid: string; direction: SipCallDirection; diff --git a/src/components/animate/index.tsx b/src/components/animate/index.tsx new file mode 100644 index 0000000..0e3348f --- /dev/null +++ b/src/components/animate/index.tsx @@ -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 ( + + {children} + + ); +} + +export default AnimateOnShow; diff --git a/src/components/switch/index.tsx b/src/components/switch/index.tsx index df38d1c..b3863d2 100644 --- a/src/components/switch/index.tsx +++ b/src/components/switch/index.tsx @@ -1,8 +1,8 @@ import { Box, Text } from "@chakra-ui/react"; type JambonzSwitchProbs = { - onlabel: string; - offLabel: string; + onlabel?: string; + offLabel?: string; checked: [boolean, React.Dispatch>]; isDisabled?: boolean; onChange: (value: boolean) => void; @@ -17,7 +17,7 @@ function JambonzSwitch({ return ( - - {isToggled ? onlabel : offLabel} - + {onlabel && offLabel && ( + + {isToggled ? onlabel : offLabel} + + )} { // Settings const SETTINGS_KEY = "SettingsKey"; +interface saveSettingFormat { + active: boolean; + encoded: string; + id: number; +} + export const saveSettings = (settings: AppSettings) => { const encoded = Buffer.from(JSON.stringify(settings), "utf-8").toString( "base64" ); - localStorage.setItem(SETTINGS_KEY, encoded); -}; - -export const getSettings = (): AppSettings => { const str = localStorage.getItem(SETTINGS_KEY); if (str) { - const planText = Buffer.from(str, "base64").toString("utf-8"); - return JSON.parse(planText) as AppSettings; + const parsed = JSON.parse(str); + + 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 @@ -50,19 +185,76 @@ export const saveAddvancedSettings = (settings: AdvancedAppSettings) => { "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); + if (str) { - const planText = Buffer.from(str, "base64").toString("utf-8"); - return JSON.parse(planText) as AdvancedAppSettings; + 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; + // 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 + const historyKey = "History"; const MAX_HISTORY_COUNT = 20; export const saveCallHistory = (username: string, call: CallHistory) => { diff --git a/src/theme.tsx b/src/theme.tsx index d0c3299..661aa77 100644 --- a/src/theme.tsx +++ b/src/theme.tsx @@ -47,4 +47,9 @@ const mainTheme = extendTheme({ }, }); +export const colors = { + //to use outside of chakra component + jambonz: "#DA1C5C", +}; + export default mainTheme; diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts new file mode 100644 index 0000000..1b5e0f9 --- /dev/null +++ b/src/utils/formatDate.ts @@ -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}`; +} diff --git a/src/window/app.tsx b/src/window/app.tsx index 944e8fd..14cd4e0 100644 --- a/src/window/app.tsx +++ b/src/window/app.tsx @@ -5,25 +5,17 @@ import { TabPanel, TabPanels, Tabs, - Text, Grid, - Center, - HStack, - Image, } from "@chakra-ui/react"; import Phone from "./phone"; import Settings from "./settings"; import { DEFAULT_COLOR_SCHEME } from "src/common/constants"; import { useEffect, useState } from "react"; -import { - getAdvancedSettings, - getCallHistories, - getSettings, -} from "src/storage"; +import { getActiveSettings, getCallHistories, getSettings } from "src/storage"; -import jambonz from "src/imgs/jambonz.svg"; 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 = () => { const [sipDomain, setSipDomain] = useState(""); @@ -35,9 +27,26 @@ export const WindowApp = () => { const [calledNumber, setCalledNumber] = useState(""); const [calledName, setCalledName] = useState(""); const [tabIndex, setTabIndex] = useState(0); - const [advancedSettings, setAdvancedSettings] = useState( - getAdvancedSettings() + const [status, setStatus] = useState("stop"); + const [allSettings, setAllSettings] = useState([]); + const [advancedSettings, setAdvancedSettings] = useState( + 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 = [ { title: "Dialer", @@ -50,7 +59,10 @@ export const WindowApp = () => { sipServerAddress={sipServerAddress} calledNumber={[calledNumber, setCalledNumber]} calledName={[calledName, setCalledName]} + stat={[status, setStatus]} advancedSettings={advancedSettings} + allSettings={allSettings} + reload={loadSettings} /> ), }, @@ -83,16 +95,6 @@ export const WindowApp = () => { setTabIndex(i); 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 ( @@ -122,12 +124,16 @@ export const WindowApp = () => { -
- - Powered by - Jambonz Logo - -
+ +