diff --git a/.compilerc b/.compilerc index 09fce71..8bcfce7 100644 --- a/.compilerc +++ b/.compilerc @@ -2,7 +2,10 @@ "env": { "development": { "application/javascript": { - "plugins": ["react-hot-loader/babel", "@babel/plugin-transform-runtime"] + "plugins": [ + "react-hot-loader/babel", + "@babel/plugin-transform-runtime" + ] } } } diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a0d305d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-present sms77 e.K. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0462066 --- /dev/null +++ b/README.md @@ -0,0 +1,51 @@ +![alt text](https://www.sms77.io/wp-content/uploads/2019/07/sms77-Logo-400x79.png "sms77") +# sms77io Desktop Application + +Distributed for Linux, MacOS and Windows. + +## Installation +Head to /releases and download the latest installer for your operating system. +Follow the installer instructions to install the application on your disk. + +You can alternatively clone the project +and build the application yourself by running ```npm install``` and ```npm run make```. +Make sure you have NodeJS installed. + +### Features +- Send SMS to one/multiple user(s) +- Message utilites: emojis, date/time, system-related +- Message history +- Number lookups: CNAM, HLR, MNP, format +- Lookup history +- Retrieve pricing +- Read contacts + +#### ToDo +- Write tests +- Add translations + +##### Screenshots +
+
Send SMS
+Send SMS +
+ +
+
Lookup Number
+Lookup Number +
+ +
+
Options
+Options +
+ +
+
Contacts
+Contacts +
+ +
+
Pricing
+Pricing +
\ No newline at end of file diff --git a/src/assets/img/1920x377.svg b/src/assets/img/1920x377.svg index d504c36..60eda84 100644 --- a/src/assets/img/1920x377.svg +++ b/src/assets/img/1920x377.svg @@ -3,7 +3,6 @@ xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 2052.4534 402.64001" height="402.64001" diff --git a/src/components/History/BaseHistory.tsx b/src/components/BaseHistory/BaseHistory.tsx similarity index 55% rename from src/components/History/BaseHistory.tsx rename to src/components/BaseHistory/BaseHistory.tsx index 01ed8db..82b13de 100644 --- a/src/components/History/BaseHistory.tsx +++ b/src/components/BaseHistory/BaseHistory.tsx @@ -1,32 +1,36 @@ -import React, {useState} from 'react'; +import React, {useEffect, useState} from 'react'; import TableContainer from '@material-ui/core/TableContainer'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; -import {useTranslation} from 'react-i18next'; import {ILocalStore, LocalStore} from '../../util/LocalStore'; +import {usePrevious} from '../../util/usePrevious'; import {Navigation} from './Navigation'; export type BaseHistoryProps = { - nsKey: string path?: string rowHandler: (row: any, i: number) => any storeKey: keyof ILocalStore } -export const BaseHistory = ({path, nsKey, storeKey, rowHandler}: BaseHistoryProps) => { - const {t} = useTranslation(nsKey); - const list = LocalStore.get(storeKey) as any[]; - const [index, setIndex] = useState(list.length - 1); +export const BaseHistory = ({path, storeKey, rowHandler}: BaseHistoryProps) => { + const list = LocalStore.get(storeKey) as any; + const previousList = usePrevious(list); + const getLastIndex = () => list.length - 1; + const [index, setIndex] = useState(getLastIndex()); const entry = list[index]; - return <> -

{t(storeKey)}

+ useEffect(() => { + if (previousList && previousList.length !== list.length) { + setIndex(getLastIndex()); + } + }, [list]); - {entry && } + return <> + {entry && setIndex(n)}/>} - +
{entry && (path ? eval(`entry${path}`) : [entry].flat()).map(rowHandler)} diff --git a/src/components/BaseHistory/Navigation.tsx b/src/components/BaseHistory/Navigation.tsx new file mode 100644 index 0000000..0f18924 --- /dev/null +++ b/src/components/BaseHistory/Navigation.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import ArrowLeftIcon from '@material-ui/icons/ArrowLeft'; +import ArrowRightIcon from '@material-ui/icons/ArrowRight'; + +import {NavigationBaseButton} from './NavigationBaseButton'; +import {NavigationBaseProps} from './types'; + +export type NavigationProps = NavigationBaseProps & { + onNavigation: (i: number) => void +} + +export const Navigation = ({index, list, onNavigation}: NavigationProps) => { + const handleNavigation = (operator: '+' | '-'): void => { + let newIndex: number = eval(`${index} ${operator} 1`); + + if (!list[newIndex]) { + newIndex = eval(`${newIndex} ${'+' === operator ? '-' : '+'} 1`); + } + + onNavigation(newIndex); + }; + + return <> + handleNavigation('-')}} + Icon={ArrowLeftIcon} operator='-'/> + + + handleNavigation('+')}} + Icon={ArrowRightIcon} operator='+'/> + ; +}; \ No newline at end of file diff --git a/src/components/BaseHistory/NavigationBaseButton.tsx b/src/components/BaseHistory/NavigationBaseButton.tsx new file mode 100644 index 0000000..80216af --- /dev/null +++ b/src/components/BaseHistory/NavigationBaseButton.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import IconButton from '@material-ui/core/IconButton'; +import {IconButtonProps, SvgIconProps} from '@material-ui/core'; + +import {NavigationBaseProps} from './types'; + +export type NavigationBaseButtonProps = NavigationBaseProps & { + Icon: React.JSXElementConstructor + IconButtonProps: IconButtonProps + operator: '+' | '-' +} + +export const NavigationBaseButton = (props: NavigationBaseButtonProps) => { + props.IconButtonProps.style!.position = 'absolute'; + + return + + ; +}; \ No newline at end of file diff --git a/src/components/BaseHistory/types.ts b/src/components/BaseHistory/types.ts new file mode 100644 index 0000000..0921017 --- /dev/null +++ b/src/components/BaseHistory/types.ts @@ -0,0 +1,7 @@ +export type NavigationBaseProps = { + index: number + list: any[] +} + + + diff --git a/src/components/BoolChip.tsx b/src/components/BoolChip.tsx index a698d14..b97da5e 100644 --- a/src/components/BoolChip.tsx +++ b/src/components/BoolChip.tsx @@ -1,6 +1,6 @@ -import Chip from '@material-ui/core/Chip'; import React from 'react'; import {useTranslation} from 'react-i18next'; +import Chip from '@material-ui/core/Chip'; export type BoolChipProps = { value: boolean @@ -9,6 +9,8 @@ export type BoolChipProps = { export const BoolChip = ({value}: BoolChipProps) => { const {t} = useTranslation(); - return ; + return ; }; \ No newline at end of file diff --git a/src/components/Contacts/Contact.tsx b/src/components/Contacts/Contact.tsx index d3fd007..c6f6e53 100644 --- a/src/components/Contacts/Contact.tsx +++ b/src/components/Contacts/Contact.tsx @@ -1,11 +1,12 @@ import React from 'react'; +import {useTranslation} from 'react-i18next'; +import {Contact as IContact} from 'sms77-client/dist/types'; + import TableContainer from '@material-ui/core/TableContainer'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableRow from '@material-ui/core/TableRow'; import TableCell from '@material-ui/core/TableCell'; -import {useTranslation} from 'react-i18next'; -import {Contact as IContact} from 'sms77-client'; export type ContactProps = { contact: IContact diff --git a/src/components/CountryFlag.tsx b/src/components/CountryFlag.tsx index 71ecebe..96735c2 100644 --- a/src/components/CountryFlag.tsx +++ b/src/components/CountryFlag.tsx @@ -14,7 +14,5 @@ export const CountryFlag = ({pricing}: CountryFlagProps) => { const flag = pricing.countryCode ? toFlag() : 'eu'; - const className = `flag-icon flag-icon-${flag}`; - - return ; + return ; }; \ No newline at end of file diff --git a/src/components/Documentation.tsx b/src/components/Documentation.tsx index ab353c8..093f96b 100644 --- a/src/components/Documentation.tsx +++ b/src/components/Documentation.tsx @@ -10,6 +10,7 @@ export const Documentation = () => {

{t('help')}

+

; }; \ No newline at end of file diff --git a/src/components/From.tsx b/src/components/From.tsx index 01f0b69..474613c 100644 --- a/src/components/From.tsx +++ b/src/components/From.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useState} from 'react'; -import TextField from '@material-ui/core/TextField'; import {useTranslation} from 'react-i18next'; +import TextField from '@material-ui/core/TextField'; export type FromProps = { value: string, diff --git a/src/components/History/Navigation.tsx b/src/components/History/Navigation.tsx deleted file mode 100644 index ccb131f..0000000 --- a/src/components/History/Navigation.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; - -import {PreviousListItemButton} from './PreviousListItemButton'; -import {NextListItemButton} from './NextListItemButton'; - -export type NavigationProps = { - index: number - list: any[] - onNavigation: (i: number) => void -} - -export const Navigation = ({index, list, onNavigation}: NavigationProps) => { - const handleNavigation = (operator: '+' | '-'): void => { - let newIndex: number = eval(`${index} ${operator} 1`); - - if (!list[newIndex]) { - newIndex = eval(`${newIndex} ${'+' === operator ? '-' : '+'} 1`); - } - - onNavigation(newIndex); - }; - - return <> - handleNavigation('-')}}/> - - handleNavigation('+')}}/> - ; -}; \ No newline at end of file diff --git a/src/components/History/NavigationBaseButton.tsx b/src/components/History/NavigationBaseButton.tsx deleted file mode 100644 index b4bc942..0000000 --- a/src/components/History/NavigationBaseButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import IconButton, {IconButtonProps as IIconButtonProps} from '@material-ui/core/IconButton'; -import React from 'react'; -import {SvgIconProps} from '@material-ui/core'; - -export type NavigationBaseButtonBaseProps = { - IconButtonProps: IIconButtonProps - index: number - list: any[] -} - -export type NavigationBaseButtonProps = NavigationBaseButtonBaseProps & { - Icon: React.JSXElementConstructor - operator: '+' | '-' -} - -export const NavigationBaseButton = ({ - Icon, - IconButtonProps, - index, - list, - operator - }: NavigationBaseButtonProps) => { - IconButtonProps.style!.position = 'absolute'; - - return - - ; -}; \ No newline at end of file diff --git a/src/components/History/NextListItemButton.tsx b/src/components/History/NextListItemButton.tsx deleted file mode 100644 index d4a3bdb..0000000 --- a/src/components/History/NextListItemButton.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react'; -import ArrowRightIcon from '@material-ui/icons/ArrowRight'; - -import {NavigationBaseButton, NavigationBaseButtonBaseProps} from './NavigationBaseButton'; - -export const NextListItemButton = (props: NavigationBaseButtonBaseProps) => { - props.IconButtonProps.style = {right: '0px'}; - - return ; -}; \ No newline at end of file diff --git a/src/components/History/PreviousListItemButton.tsx b/src/components/History/PreviousListItemButton.tsx deleted file mode 100644 index 39ebb78..0000000 --- a/src/components/History/PreviousListItemButton.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import ArrowLeftIcon from '@material-ui/icons/ArrowLeft'; -import React from 'react'; - -import {NavigationBaseButton, NavigationBaseButtonBaseProps} from './NavigationBaseButton'; - -export const PreviousListItemButton = (props: NavigationBaseButtonBaseProps) => { - props.IconButtonProps.style = {left: '0px'}; - - return ; -}; \ No newline at end of file diff --git a/src/components/Layout/BottomNav.tsx b/src/components/Layout/BottomNav.tsx index 7cec15f..2fcee58 100644 --- a/src/components/Layout/BottomNav.tsx +++ b/src/components/Layout/BottomNav.tsx @@ -1,12 +1,11 @@ import React from 'react'; +import {useDispatch, useSelector} from 'react-redux'; +import {useTranslation} from 'react-i18next'; import {makeStyles} from '@material-ui/core/styles'; import BottomNavigation from '@material-ui/core/BottomNavigation'; -import BottomNavigationAction from '@material-ui/core/BottomNavigationAction'; +import BottomNavigationAction, {BottomNavigationActionProps} from '@material-ui/core/BottomNavigationAction'; import SettingsIcon from '@material-ui/icons/Settings'; import SmsIcon from '@material-ui/icons/Sms'; -import {useTranslation} from 'react-i18next'; -import {useDispatch, useSelector} from 'react-redux'; -import HistoryIcon from '@material-ui/icons/History'; import ContactsIcon from '@material-ui/icons/Contacts'; import LocalAtmIcon from '@material-ui/icons/LocalAtm'; import PolicyIcon from '@material-ui/icons/Policy'; @@ -28,22 +27,21 @@ export const BottomNav = () => { }, })(); + const actions: BottomNavigationActionProps[] = [ + {label: 'sms', value: 'send', icon: }, + {label: 'lookup', value: 'lookup', icon: }, + {label: 'options', value: 'options', icon: }, + {label: 'contacts', value: 'contacts', icon: }, + {label: 'pricing', value: 'pricing', icon: }, + ]; + return dispatch(setNav(newNavId))} showLabels value={navId} > - }/> - - }/> - - }/> - - }/> - - }/> - - }/> + {actions.map((a, i) => )} ; }; \ No newline at end of file diff --git a/src/components/Layout/Layout.tsx b/src/components/Layout/Layout.tsx index 9c7cf7e..33184a4 100644 --- a/src/components/Layout/Layout.tsx +++ b/src/components/Layout/Layout.tsx @@ -1,15 +1,14 @@ import React from 'react'; -import Container from '@material-ui/core/Container'; import {useSelector} from 'react-redux'; +import Container from '@material-ui/core/Container'; +import {TopNav} from './TopNav'; +import {Snackbars} from './Snackbars'; +import {BottomNav} from './BottomNav'; import {RootState} from '../../store/reducers'; import {Options} from '../Options/Options'; import {Send} from '../Send/Send'; import {Documentation} from '../Documentation'; -import {Snackbars} from './Snackbars'; -import {BottomNav} from './BottomNav'; -import {History} from '../History/History'; -import {TopNav} from './TopNav'; import {Contacts} from '../Contacts/Contacts'; import {Pricings} from '../Pricing/Pricings'; import {Lookup} from '../Lookup/Lookup'; @@ -28,15 +27,13 @@ export const Layout = () => { ? : 'options' === nav ? - : 'history' === nav - ? - : 'contacts' === nav - ? - : 'pricing' === nav - ? - : 'lookup' === nav - ? - : + : 'contacts' === nav + ? + : 'pricing' === nav + ? + : 'lookup' === nav + ? + : } diff --git a/src/components/Layout/Snackbars.tsx b/src/components/Layout/Snackbars.tsx index 1af3329..3c3fa82 100644 --- a/src/components/Layout/Snackbars.tsx +++ b/src/components/Layout/Snackbars.tsx @@ -11,12 +11,14 @@ import {RootState} from '../../store/reducers'; export const Snackbars = () => { const {t} = useTranslation(); const dispatch = useDispatch(); - const handleClose = (i: number) => dispatch(removeSnackbar(i)); return useSelector((s: RootState) => s.snackbars).map((msg: string, i: number) => { - const Action = () => handleClose(i)} - size='small'> + const Action = () => handleClose(i)} + size='small'> ; diff --git a/src/components/Layout/TopNav.tsx b/src/components/Layout/TopNav.tsx index c714032..7dd1a1c 100644 --- a/src/components/Layout/TopNav.tsx +++ b/src/components/Layout/TopNav.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import {useDispatch} from 'react-redux'; import {makeStyles} from '@material-ui/core/styles'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; @@ -17,7 +18,6 @@ import Dialog from '@material-ui/core/Dialog'; import DialogTitle from '@material-ui/core/DialogTitle'; import LocalAtmIcon from '@material-ui/icons/LocalAtm'; import DescriptionIcon from '@material-ui/icons/Description'; -import {useDispatch} from 'react-redux'; import {System} from '../System'; import {setNav} from '../../store/actions'; diff --git a/src/components/Lookup/CarrierTable.tsx b/src/components/Lookup/CarrierTable.tsx index a854d88..9dbbd5b 100644 --- a/src/components/Lookup/CarrierTable.tsx +++ b/src/components/Lookup/CarrierTable.tsx @@ -1,31 +1,20 @@ import React from 'react'; -import {useTranslation} from 'react-i18next'; +import {Carrier} from 'sms77-client'; import TableContainer from '@material-ui/core/TableContainer'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; -import TableRow from '@material-ui/core/TableRow'; -import TableCell from '@material-ui/core/TableCell'; -import {Carrier} from 'sms77-client'; + +import {TableRowSpreader} from '../TableRowSpreader'; export type CarrierTableProps = { carrier: Carrier } export const CarrierTable = ({carrier}: CarrierTableProps) => { - const {t} = useTranslation('lookup'); - return -

- {Object.entries(carrier).map(([k, v], i) => - - - {t(`carrier.${k}`)} - - - - {v} - - )} +
+ + `carrier.${k}`}/>
; diff --git a/src/components/Lookup/History.tsx b/src/components/Lookup/History.tsx index 0aabbe3..051a2f6 100644 --- a/src/components/Lookup/History.tsx +++ b/src/components/Lookup/History.tsx @@ -1,13 +1,21 @@ import React from 'react'; +import {useTranslation} from 'react-i18next'; -import {BaseHistory} from '../History/BaseHistory'; -import {LookupResponse} from './Lookup'; import {TableRowSpreader} from '../TableRowSpreader'; +import {BaseHistory} from '../BaseHistory/BaseHistory'; +import {LookupResponse} from './types'; -export const History = () => - - } - storeKey={'lookups'} -/>; \ No newline at end of file +export const History = () => { + const {t} = useTranslation('lookup'); + + return <> +

{t('lookups')}

+ + + + } + storeKey={'lookups'} + /> + ; +}; \ No newline at end of file diff --git a/src/components/Lookup/Lookup.tsx b/src/components/Lookup/Lookup.tsx index 7d8aa4b..45a40c7 100644 --- a/src/components/Lookup/Lookup.tsx +++ b/src/components/Lookup/Lookup.tsx @@ -1,13 +1,13 @@ import React, {SyntheticEvent, useEffect} from 'react'; import {useDispatch} from 'react-redux'; import {useTranslation} from 'react-i18next'; -import Sms77Client, {HLR, LookupType, Format, CNAMApiJsonResponse, MNPApiJsonResponse} from 'sms77-client'; +import Sms77Client, {LookupType} from 'sms77-client'; import {LOOKUP_TYPES} from 'sms77-client/dist/constants/LOOKUP_TYPES'; import FormControl from '@material-ui/core/FormControl'; import FormLabel from '@material-ui/core/FormLabel'; import RadioGroup from '@material-ui/core/RadioGroup'; import FormControlLabel from '@material-ui/core/FormControlLabel'; -import {makeStyles, Theme, createStyles} from '@material-ui/core/styles'; +import {createStyles, makeStyles, Theme} from '@material-ui/core/styles'; import Radio from '@material-ui/core/Radio'; import Grid from '@material-ui/core/Grid'; import Tooltip from '@material-ui/core/Tooltip'; @@ -17,11 +17,9 @@ import Button from '@material-ui/core/Button'; import {addSnackbar, setNav} from '../../store/actions'; import {LocalStore} from '../../util/LocalStore'; import {toString} from '../../util/toString'; -import {Response} from './Response'; -import {Divider} from '@material-ui/core'; import {History} from './History'; +import {LookupResponse} from './types'; -export type LookupResponse = Format | HLR & { success?: never } | CNAMApiJsonResponse | MNPApiJsonResponse; export const Lookup = () => { const {t} = useTranslation('lookup'); const [type, setType] = React.useState('format'); @@ -33,7 +31,6 @@ export const Lookup = () => { }))(); const dispatch = useDispatch(); const apiKey = LocalStore.get('options.apiKey') as string; - const [response, setResponse] = React.useState(); useEffect(() => { if ('' === apiKey || !apiKey) { @@ -51,61 +48,56 @@ export const Lookup = () => { LocalStore.append('lookups', res); dispatch(addSnackbar(getPairs(res).map(([k, v]) => `${t(k)}: ${toString(v)}`).join(' ● '))); - - setResponse(res); }; const getPairs = (res: LookupResponse) => Object.entries(res!).filter(([, v]) => null !== v); return <> -
- - -

{t('lookup')}

- -
- - - - + + +

{t('lookup')}

+
+ + + + +
+ + + + + + {t('type')} + + setType((e.target as HTMLInputElement).value as LookupType)}> + { + Object.values(LOOKUP_TYPES) + .filter(v => !Number.isInteger(v as number)) + .map((type, i) => + } + label={t(type as string)} + labelPlacement='bottom' value={type}/> + ) + } + + - - - - {t('type')} - - setType((e.target as HTMLInputElement).value as LookupType)}> - { - Object.values(LOOKUP_TYPES) - .filter(v => !Number.isInteger(v as number)) - .map((type, i) => - } - label={t(type as string)} - labelPlacement='bottom' value={type}/> - ) - } - - - - setNumber(ev.target.value)} - required - value={number} - /> - - - - - + + setNumber(ev.target.value)} + required + value={number} + /> - + - {response ? <> : null} + ; }; \ No newline at end of file diff --git a/src/components/Lookup/Response.tsx b/src/components/Lookup/Response.tsx deleted file mode 100644 index cf8fe91..0000000 --- a/src/components/Lookup/Response.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import {useTranslation} from 'react-i18next'; -import {HLR} from 'sms77-client'; -import TableContainer from '@material-ui/core/TableContainer'; -import TableBody from '@material-ui/core/TableBody'; -import TableRow from '@material-ui/core/TableRow'; -import TableCell from '@material-ui/core/TableCell'; -import Table from '@material-ui/core/Table'; - -import {BoolChip} from '../BoolChip'; -import {CarrierTable} from './CarrierTable'; -import {toString} from '../../util/toString'; - -export type ResponseProps = { - pairs: any[] -} - -export const Response = ({pairs}: ResponseProps) => { - const {t} = useTranslation('lookup'); - const carrierKeys: string[] = ['current_carrier' as keyof HLR, 'original_carrier' as keyof HLR]; - - return - - - - {pairs.map(([k, v], i) => - - - {t(k)} - - - - {'boolean' === typeof v - ? - : carrierKeys.includes(k) - ? - : toString(v)} - - - )} -
{t('response')}
-
; -}; \ No newline at end of file diff --git a/src/components/Lookup/types.ts b/src/components/Lookup/types.ts new file mode 100644 index 0000000..2d9f450 --- /dev/null +++ b/src/components/Lookup/types.ts @@ -0,0 +1,3 @@ +import {CNAMApiJsonResponse, Format, HLR, MNPApiJsonResponse} from 'sms77-client'; + +export type LookupResponse = Format | HLR & { success?: never } | CNAMApiJsonResponse | MNPApiJsonResponse; \ No newline at end of file diff --git a/src/components/Options/ApiKey.tsx b/src/components/Options/ApiKey.tsx index 1dcc53d..7847d50 100644 --- a/src/components/Options/ApiKey.tsx +++ b/src/components/Options/ApiKey.tsx @@ -1,6 +1,6 @@ -import React, {useEffect, useState, SyntheticEvent} from 'react'; -import TextField, {TextFieldProps} from '@material-ui/core/TextField'; +import React, {SyntheticEvent, useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; +import TextField, {TextFieldProps} from '@material-ui/core/TextField'; export type ApiKeyProps = TextFieldProps & { value: string, diff --git a/src/components/Options/Options.tsx b/src/components/Options/Options.tsx index e316d56..3fd283b 100644 --- a/src/components/Options/Options.tsx +++ b/src/components/Options/Options.tsx @@ -1,22 +1,15 @@ import React, {useEffect, useRef, useState} from 'react'; -import {makeStyles} from '@material-ui/core/styles'; import {useTranslation} from 'react-i18next'; import {useDispatch} from 'react-redux'; +import {makeStyles} from '@material-ui/core/styles'; import {LocalStore} from '../../util/LocalStore'; +import {setTo} from '../../store/actions'; import {ApiKey} from './ApiKey'; import {From} from '../From'; import {To} from '../To'; -import {Signature, SignaturePosition} from './Signature'; -import {setTo} from '../../store/actions'; - -export type IOptions = { - apiKey: string, - from: string, - signature: string, - signaturePosition: SignaturePosition, - to: string, -}; +import {Signature} from './Signature'; +import {IOptions} from './types'; export const Options = () => { const $apiKey = useRef(); diff --git a/src/components/Options/Signature.tsx b/src/components/Options/Signature.tsx index bed7caf..3cd0d21 100644 --- a/src/components/Options/Signature.tsx +++ b/src/components/Options/Signature.tsx @@ -7,6 +7,8 @@ export type SignatureProps = { signature: string } +export type SignaturePosition = 'append' | 'prepend'; + export const Signature = ({onChange, signature}: SignatureProps) => { const {t} = useTranslation(); @@ -18,5 +20,4 @@ export const Signature = ({onChange, signature}: SignatureProps) => { onChange={onChange} value={signature} />; -}; -export type SignaturePosition = 'append' | 'prepend'; \ No newline at end of file +}; \ No newline at end of file diff --git a/src/components/Options/types.ts b/src/components/Options/types.ts new file mode 100644 index 0000000..7ec1f1e --- /dev/null +++ b/src/components/Options/types.ts @@ -0,0 +1,9 @@ +import {SignaturePosition} from './Signature'; + +export type IOptions = { + apiKey: string, + from: string, + signature: string, + signaturePosition: SignaturePosition, + to: string, +}; \ No newline at end of file diff --git a/src/components/PopupMenu.tsx b/src/components/PopupMenu.tsx index e68d6dc..8cc1e0b 100644 --- a/src/components/PopupMenu.tsx +++ b/src/components/PopupMenu.tsx @@ -1,30 +1,32 @@ import React, {PropsWithChildren} from 'react'; -import Button from '@material-ui/core/Button'; import PopupStateComp, {bindMenu, bindTrigger} from 'material-ui-popup-state'; import {PopupState} from 'material-ui-popup-state/core'; import Menu from '@material-ui/core/Menu'; +import Button from '@material-ui/core/Button'; export type MessageToolbarBaseProps = { buttonText: string identifier: string } -export type MessageToolbarProps = PropsWithChildren +export type PopupMenuProps = PropsWithChildren -export const PopupMenu = ({children, identifier, buttonText}: MessageToolbarProps) => { +export const PopupMenu = ({children, identifier, buttonText}: PopupMenuProps) => { const state: PopupState | {} = {}; const popupId = `${identifier}-popup`; const menuId = `${popupId}-menu`; - return + return {pS => { Object.assign(state, pS); return <> - + {children} diff --git a/src/components/Pricing/CountryNetworks.tsx b/src/components/Pricing/CountryNetworks.tsx index dc7ac63..8847bdd 100644 --- a/src/components/Pricing/CountryNetworks.tsx +++ b/src/components/Pricing/CountryNetworks.tsx @@ -1,11 +1,11 @@ import React from 'react'; +import {useTranslation} from 'react-i18next'; +import {CountryPricing} from 'sms77-client'; import TableContainer from '@material-ui/core/TableContainer'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableRow from '@material-ui/core/TableRow'; import TableCell from '@material-ui/core/TableCell'; -import {useTranslation} from 'react-i18next'; -import {CountryPricing} from 'sms77-client'; import Chip from '@material-ui/core/Chip'; import Divider from '@material-ui/core/Divider'; diff --git a/src/components/Pricing/Pricing.tsx b/src/components/Pricing/Pricing.tsx index f5ddb33..bec590b 100644 --- a/src/components/Pricing/Pricing.tsx +++ b/src/components/Pricing/Pricing.tsx @@ -1,11 +1,11 @@ import React from 'react'; +import {useTranslation} from 'react-i18next'; +import {CountryPricing} from 'sms77-client'; import TableContainer from '@material-ui/core/TableContainer'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableRow from '@material-ui/core/TableRow'; import TableCell from '@material-ui/core/TableCell'; -import {useTranslation} from 'react-i18next'; -import {CountryPricing} from 'sms77-client'; import {CountryNetworks} from './CountryNetworks'; @@ -16,38 +16,27 @@ export type PricingProps = { export const Pricing = ({pricing}: PricingProps) => { const {t} = useTranslation('pricing'); + const populationFields = [ + {key: 'code', value: pricing.countryCode}, + {key: 'name', value: pricing.countryName}, + {key: 'prefix', value: pricing.countryPrefix}, + ]; + return - - - {t('code')} - - - {pricing.countryCode} - - - - - - {t('name')} - - - - {pricing.countryName} - - - - - - {t('prefix')} - - - - {pricing.countryPrefix} - - + { + populationFields.map((o, i) => + + {t(o.key)} + + + + {o.value} + + ) + } diff --git a/src/components/Pricing/Pricings.tsx b/src/components/Pricing/Pricings.tsx index 7986757..8cdc69e 100644 --- a/src/components/Pricing/Pricings.tsx +++ b/src/components/Pricing/Pricings.tsx @@ -1,20 +1,20 @@ -import React, {useEffect, useState, ChangeEvent} from 'react'; +import React, {ChangeEvent, useEffect, useState} from 'react'; +import {useDispatch} from 'react-redux'; +import Sms77Client, {CountryPricing, PricingResponse} from 'sms77-client'; import TableContainer from '@material-ui/core/TableContainer'; import TableBody from '@material-ui/core/TableBody'; import TableRow from '@material-ui/core/TableRow'; import TableCell from '@material-ui/core/TableCell'; import Autocomplete from '@material-ui/lab/Autocomplete'; import {useTranslation} from 'react-i18next'; -import Sms77Client, {CountryPricing, PricingResponse} from 'sms77-client'; -import {useDispatch} from 'react-redux'; import Table from '@material-ui/core/Table'; import TextField, {TextFieldProps} from '@material-ui/core/TextField'; +import Button from '@material-ui/core/Button'; import {LocalStore} from '../../util/LocalStore'; import {addSnackbar, setNav} from '../../store/actions'; -import {Pricing} from './Pricing'; import {CountryFlag} from '../CountryFlag'; -import Button from '@material-ui/core/Button'; +import {Pricing} from './Pricing'; export const Pricings = () => { const {t} = useTranslation('pricing'); @@ -51,6 +51,8 @@ export const Pricings = () => { } }; + const populationFields: (keyof PricingResponse)[] = ['countCountries', 'countNetworks']; + return <>

{t('pricing')}

@@ -61,27 +63,18 @@ export const Pricings = () => {
-
+
- - - {t('countCountries')} - - - - {pricing.countCountries} - - - + {populationFields.map((o, i) => - {t('countNetworks')} + {t(o)} - {pricing.countNetworks} + {pricing[o]} - + )}
diff --git a/src/components/Send/DateTimeUtils.tsx b/src/components/Send/DateTimeUtils.tsx index 01c0176..4d6642c 100644 --- a/src/components/Send/DateTimeUtils.tsx +++ b/src/components/Send/DateTimeUtils.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import MenuItem from '@material-ui/core/MenuItem'; import {useTranslation} from 'react-i18next'; +import MenuItem from '@material-ui/core/MenuItem'; import {PopupMenu} from '../PopupMenu'; diff --git a/src/components/Send/EmojiPicker.tsx b/src/components/Send/EmojiPicker.tsx index 7a1692e..9291f06 100644 --- a/src/components/Send/EmojiPicker.tsx +++ b/src/components/Send/EmojiPicker.tsx @@ -1,6 +1,6 @@ -import MenuItem from '@material-ui/core/MenuItem'; -import Picker, {IEmojiPickerProps} from 'emoji-picker-react'; import React from 'react'; +import Picker, {IEmojiPickerProps} from 'emoji-picker-react'; +import MenuItem from '@material-ui/core/MenuItem'; import {PopupMenu} from '../PopupMenu'; diff --git a/src/components/History/History.tsx b/src/components/Send/History.tsx similarity index 98% rename from src/components/History/History.tsx rename to src/components/Send/History.tsx index 2c0909f..f384e60 100644 --- a/src/components/History/History.tsx +++ b/src/components/Send/History.tsx @@ -6,14 +6,13 @@ import TableCell from '@material-ui/core/TableCell'; import Tooltip from '@material-ui/core/Tooltip'; import {numberFormatter} from '../../util/numberFormatter'; +import {BaseHistory} from '../BaseHistory/BaseHistory'; import {BoolChip} from '../BoolChip'; -import {BaseHistory} from './BaseHistory'; export const History = () => { const {t} = useTranslation('history'); return diff --git a/src/components/Send/MessageToolbar.tsx b/src/components/Send/MessageToolbar.tsx index a7f7186..f96e0c1 100644 --- a/src/components/Send/MessageToolbar.tsx +++ b/src/components/Send/MessageToolbar.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import ButtonGroup from '@material-ui/core/ButtonGroup'; import {useTranslation} from 'react-i18next'; +import ButtonGroup from '@material-ui/core/ButtonGroup'; import {EmojiPicker} from './EmojiPicker'; import {DateTimeUtils} from './DateTimeUtils'; @@ -12,7 +12,7 @@ export type MessageToolbarProps = { } export const MessageToolbar = ({onAction, textarea}: MessageToolbarProps) => { - const {t} = useTranslation('messageToolbar'); + const {t} = useTranslation('send'); const setRangeText = (msg: string) => { textarea.setRangeText(msg, textarea.selectionStart, textarea.selectionEnd, 'end'); @@ -20,7 +20,7 @@ export const MessageToolbar = ({onAction, textarea}: MessageToolbarProps) => { onAction(textarea!.value); }; - return + return setRangeText(data.emoji)}/> setRangeText(s)}/> diff --git a/src/components/Send/Send.tsx b/src/components/Send/Send.tsx index be8e6d5..a77d4e3 100644 --- a/src/components/Send/Send.tsx +++ b/src/components/Send/Send.tsx @@ -1,10 +1,9 @@ import React, {SyntheticEvent, useEffect, useRef, useState} from 'react'; +import {useTranslation} from 'react-i18next'; +import {useDispatch, useSelector} from 'react-redux'; import TextField from '@material-ui/core/TextField'; import Button from '@material-ui/core/Button'; import makeStyles from '@material-ui/core/styles/makeStyles'; -import {red} from '@material-ui/core/colors'; -import {useDispatch, useSelector} from 'react-redux'; -import {useTranslation} from 'react-i18next'; import ClearIcon from '@material-ui/icons/Clear'; import Grid from '@material-ui/core/Grid'; import SendIcon from '@material-ui/icons/Send'; @@ -14,16 +13,15 @@ import {From} from '../From'; import {LocalStore} from '../../util/LocalStore'; import {sendSms} from '../../util/sendSms'; import {addSnackbar, setNav, setTo} from '../../store/actions'; -import {MessageToolbar} from './MessageToolbar'; import {RootState} from '../../store/reducers'; - -const _red = red['900']; +import {History} from './History'; +import {MessageToolbar} from './MessageToolbar'; export const Send = () => { const dispatch = useDispatch(); const classes = makeStyles(theme => ({ clear: { - color: _red, + color: 'red', }, form: { '& .MuiTextField-root': { @@ -73,42 +71,45 @@ export const Send = () => { setTo(LocalStore.get('options.to') as string); }; - return
+ return <>

{t('h1')}

- - - setText(ev.target.value)} - required - rows='3' - value={text} - /> - - dispatch(setTo(to))} value={to}/> - - setFrom(from)} value={from}/> - - - - - - - - + + + + setText(ev.target.value)} + required + rows='3' + value={text} + /> + + dispatch(setTo(to))} value={to}/> + + setFrom(from)} value={from}/> + + + + + + + + + + - - ; + + ; }; \ No newline at end of file diff --git a/src/components/Send/SystemUtils.tsx b/src/components/Send/SystemUtils.tsx index 7a3a3ce..3dd2215 100644 --- a/src/components/Send/SystemUtils.tsx +++ b/src/components/Send/SystemUtils.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import MenuItem from '@material-ui/core/MenuItem'; import {useTranslation} from 'react-i18next'; +import MenuItem from '@material-ui/core/MenuItem'; import {PopupMenu} from '../PopupMenu'; import {SYSTEM_PAIRS} from '../System'; diff --git a/src/components/System.tsx b/src/components/System.tsx index ed90a85..05b3ee2 100644 --- a/src/components/System.tsx +++ b/src/components/System.tsx @@ -1,16 +1,16 @@ import React from 'react'; +import {useTranslation} from 'react-i18next'; import os from 'os'; +import electron from 'electron'; import TableContainer from '@material-ui/core/TableContainer'; import Table from '@material-ui/core/Table'; import TableBody from '@material-ui/core/TableBody'; import TableRow from '@material-ui/core/TableRow'; import TableCell from '@material-ui/core/TableCell'; -import {useTranslation} from 'react-i18next'; -import electron from 'electron'; -const userInfo = os.userInfo(); +import {toMegaBytes} from '../util/toMegaBytes'; -const toMB = (bytes: number) => (bytes / (1024 * 1024)).toFixed(2) + ' MB'; +const userInfo = os.userInfo(); export const SYSTEM_PAIRS: { name: string, value: string | number }[] = [ { @@ -47,7 +47,7 @@ export const SYSTEM_PAIRS: { name: string, value: string | number }[] = [ }, { name: 'ramFree', - value: toMB(os.freemem()), + value: toMegaBytes(os.freemem()), }, { name: 'homeDir', @@ -71,7 +71,7 @@ export const SYSTEM_PAIRS: { name: string, value: string | number }[] = [ }, { name: 'ramTotal', - value: toMB(os.totalmem()), + value: toMegaBytes(os.totalmem()), }, { name: 'osType', diff --git a/src/components/TableRowSpreader.tsx b/src/components/TableRowSpreader.tsx index dd2d5e6..087d6f8 100644 --- a/src/components/TableRowSpreader.tsx +++ b/src/components/TableRowSpreader.tsx @@ -1,17 +1,22 @@ import React from 'react'; import {useTranslation} from 'react-i18next'; +import {HLR} from 'sms77-client'; import TableRow from '@material-ui/core/TableRow'; import TableCell from '@material-ui/core/TableCell'; -import {BoolChip} from './BoolChip'; import {toString} from '../util/toString'; +import {BoolChip} from './BoolChip'; +import {CarrierTable} from './Lookup/CarrierTable'; export type TableRowSpreader = { nsKey: string pairs: any[] + transEditor?: (k: string) => string } -export const TableRowSpreader = ({nsKey, pairs}: TableRowSpreader) => { +const carrierKeys: string[] = ['current_carrier' as keyof HLR, 'original_carrier' as keyof HLR]; + +export const TableRowSpreader = ({nsKey, pairs, transEditor}: TableRowSpreader) => { const {t} = useTranslation(nsKey); return <> @@ -19,13 +24,15 @@ export const TableRowSpreader = ({nsKey, pairs}: TableRowSpreader) => { pairs.map(([k, v], i) => - {t(k)} + {t(transEditor ? transEditor(k) : k)} {'boolean' === typeof v ? - : toString(v)} + : carrierKeys.includes(k) + ? + : toString(v)} ) } diff --git a/src/i18n/en/index.ts b/src/i18n/en/index.ts index cbebc75..92926de 100644 --- a/src/i18n/en/index.ts +++ b/src/i18n/en/index.ts @@ -1,5 +1,4 @@ import {send} from './send'; -import {messageToolbar} from './messageToolbar'; import {system} from './system'; import {history} from './history'; import {contacts} from './contacts'; @@ -10,7 +9,6 @@ export default { contacts, history, lookup, - messageToolbar, pricing, send, system, diff --git a/src/i18n/en/messageToolbar.ts b/src/i18n/en/messageToolbar.ts deleted file mode 100644 index 3755af1..0000000 --- a/src/i18n/en/messageToolbar.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const messageToolbar = { - 'timestamp': 'Timestamp', - 'date': 'Date', - 'locale': 'Locale', - 'time': 'Time', - 'messageUtilitiesBtnGroup': 'message helper utilities button group' -}; \ No newline at end of file diff --git a/src/i18n/en/pricing.ts b/src/i18n/en/pricing.ts index e40e6e4..895be6f 100644 --- a/src/i18n/en/pricing.ts +++ b/src/i18n/en/pricing.ts @@ -13,11 +13,11 @@ export const pricing = { noEntries: 'No locally saved pricing found. Click on the reload button to fetch pricing information.', pricing: 'Pricing Information', pricingFor: 'Pricing for {{countNetworks}} network(s) in {{countCountries}} country/countries.', - prefix: 'prefix', + prefix: 'Prefix', mcc: 'MCC', mncs: 'MNCS', - name: 'name', - networkName: 'Network name', + name: 'Name', + networkName: 'Network Name', networks: 'Networks', price: 'Price', }; \ No newline at end of file diff --git a/src/i18n/en/send.ts b/src/i18n/en/send.ts index a5bf110..f1da09a 100644 --- a/src/i18n/en/send.ts +++ b/src/i18n/en/send.ts @@ -1,8 +1,15 @@ export const send = { - 'h1': 'Send SMS', - 'label': 'Message Content', - 'helperText': 'This defines the actual SMS content.', - 'clear': 'Clear', - 'send': 'Send', - 'pleaseSetApiKey': 'Please set your API key from sms77.io.' + clear: 'Clear', + h1: 'Send SMS', + helperText: 'This defines the actual SMS content.', + label: 'Message Content', + pleaseSetApiKey: 'Please set your API key from sms77.io.', + send: 'Send', + toolbar: { + date: 'Date', + label: 'Message Utilities Button Group', + locale: 'Locale', + time: 'Time', + timestamp: 'Timestamp', + }, }; \ No newline at end of file diff --git a/src/index.html b/src/index.html index cb3335f..0b7bc42 100644 --- a/src/index.html +++ b/src/index.html @@ -6,8 +6,8 @@ sms77io diff --git a/src/store/reducers/nav.ts b/src/store/reducers/nav.ts index 904704c..949b768 100644 --- a/src/store/reducers/nav.ts +++ b/src/store/reducers/nav.ts @@ -1,4 +1,4 @@ -export type Route = 'send' | 'options' | 'history' | 'docs' | 'contacts' | 'pricing' | 'lookup' +export type Route = 'send' | 'options' | 'docs' | 'contacts' | 'pricing' | 'lookup' export default (state: Route = 'send', action: any) => { switch (action.type) { diff --git a/src/util/LocalStore.ts b/src/util/LocalStore.ts index 3eb8d68..257ad45 100644 --- a/src/util/LocalStore.ts +++ b/src/util/LocalStore.ts @@ -1,7 +1,7 @@ import Store from 'electron-store'; -import {IOptions} from '../components/Options/Options'; -import {LookupResponse} from '../components/Lookup/Lookup'; +import {LookupResponse} from '../components/Lookup/types'; +import {IOptions} from '../components/Options/types'; import {SmsDump} from './sendSms'; const options: IOptions = { diff --git a/src/util/toMegaBytes.ts b/src/util/toMegaBytes.ts new file mode 100644 index 0000000..b2e4cfb --- /dev/null +++ b/src/util/toMegaBytes.ts @@ -0,0 +1 @@ +export const toMegaBytes = (bytes: number): string => (bytes / (1024 * 1024)).toFixed(2) + ' MB'; \ No newline at end of file diff --git a/src/util/toString.ts b/src/util/toString.ts index 607840c..54fcdeb 100644 --- a/src/util/toString.ts +++ b/src/util/toString.ts @@ -1 +1 @@ -export const toString = (v: any) => 'object' === typeof v ? JSON.stringify(v) : v; \ No newline at end of file +export const toString = (v: any): string => 'object' === typeof v ? JSON.stringify(v) : v; \ No newline at end of file diff --git a/src/util/usePrevious.ts b/src/util/usePrevious.ts new file mode 100644 index 0000000..e2adcd5 --- /dev/null +++ b/src/util/usePrevious.ts @@ -0,0 +1,11 @@ +import {useEffect, useRef} from 'react'; + +export const usePrevious = (value: T): T | undefined => { + const ref = useRef(); + + useEffect(() => { + ref.current = value; + }); + + return ref.current; +}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index ac24d9b..6cd0eb8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,16 @@ { - "compilerOptions": { - "esModuleInterop": true, - "experimentalDecorators": true, - "jsx": "react", - "module": "commonjs", - "moduleResolution": "node", - "outDir": "dist", - "skipLibCheck": true, - "sourceMap": true, - "strict": true - }, - "include": [ - "src/**/*" - ] + "compilerOptions": { + "esModuleInterop": true, + "experimentalDecorators": true, + "jsx": "react", + "module": "commonjs", + "moduleResolution": "node", + "outDir": "dist", + "skipLibCheck": true, + "sourceMap": true, + "strict": true + }, + "include": [ + "src/**/*" + ] }